333 lines
No EOL
10 KiB
JavaScript
333 lines
No EOL
10 KiB
JavaScript
/**
|
|
* @classdesc This provides a common dictionnary for a Paged.js tools.
|
|
*
|
|
* Web Component will fetch the list of features. For each one a class will be instantiated.
|
|
* This class groups all the key data. From this list of instances
|
|
* the component will refine the data, import all necessary scripts, hooks and stylesheet.
|
|
*
|
|
* This very logic can be observed at the end of the component in the setup() function.
|
|
*
|
|
* @author Benjamin G. <ecrire@bnjm.eu>
|
|
* @author Julie Blanc <contact@julie-blanc.fr>
|
|
* @tutorial https://gitlab.com/csspageweaver/csspageweaver/-/wikis/home
|
|
*/
|
|
|
|
class CssPageWeaver_Dict {
|
|
constructor(){
|
|
console.log('CSS Page Weaver Dict initialized');
|
|
|
|
// Define a Feature class to manage individual features
|
|
this.Feature = class {
|
|
constructor(featureConfig, id, directory, config) {
|
|
this.id = id
|
|
this.directory = directory
|
|
this.ui = featureConfig.ui
|
|
this.parameters = featureConfig.parameters
|
|
this.hook = featureConfig.hook
|
|
this.script = featureConfig.script
|
|
this.stylesheet = featureConfig.stylesheet
|
|
this.globalParameters = config
|
|
}
|
|
}
|
|
|
|
this.setup()
|
|
}
|
|
|
|
/*-- Generics functions --*/
|
|
|
|
/**
|
|
* Clear and split feature directory path to define an unique ID
|
|
* @param {string} path - Feature folder path.
|
|
* @returns {string} - Feature ID.
|
|
*/
|
|
getIdFromPath(path){
|
|
return path.trim().split("/").filter(segment => segment.trim() !== '').pop()
|
|
}
|
|
|
|
/*-- Dict --*/
|
|
|
|
setSharedDictionnary(){
|
|
|
|
window.cssPageWeaver = {}
|
|
// Get the document title and remove spaces for use as a variable
|
|
cssPageWeaver.docTitle = document.getElementsByTagName("title")[0].text.replace(/ /g, "");
|
|
|
|
// Set path & directory
|
|
cssPageWeaver.directory = {}
|
|
cssPageWeaver.directory.root = `${window.location.origin}/csspageweaver`
|
|
cssPageWeaver.directory.plugins = `${cssPageWeaver.directory.root}/plugins`
|
|
|
|
// Initialise user custom files
|
|
cssPageWeaver.user = {}
|
|
|
|
// Object to hold all features
|
|
cssPageWeaver.features = {}
|
|
}
|
|
|
|
/*-- Import or list files --*/
|
|
|
|
/**
|
|
* Import the list of feature names from the manifest file or directory.
|
|
*
|
|
* This method attempts to import the feature names from a manifest.json file.
|
|
* If the manifest is not found, it falls back to listing the feature names
|
|
* from the plugin directory.
|
|
*
|
|
* @returns {Promise<Array>} A promise that resolves to an array of feature names.
|
|
*/
|
|
async importManifest(){
|
|
|
|
try{
|
|
// Attempt to read the manifest file to get the list of features
|
|
return await this.importJson(`${cssPageWeaver.directory.root}/`,`manifest.json`);
|
|
} catch(error){
|
|
console.log('Manifest not found, trying alternative method.');
|
|
// If the manifest is not found, list feature names from the plugin directory
|
|
let featuresNamesList = await this.listDir(cssPageWeaver.directory.plugins);
|
|
// Return object
|
|
return {"plugins": featuresNamesList }
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Imports an HTML template file from a given directory and file name.
|
|
* @param {string} dir - The directory where the template file is located.
|
|
* @param {string} file - The name of the template file.
|
|
* @returns {Promise<string>} - A promise that resolves to the contents of the template file.
|
|
*/
|
|
async importTemplate(dir, file) {
|
|
const response = await fetch(`${dir}/${file}`);
|
|
const template = await response.text();
|
|
return template;
|
|
}
|
|
|
|
/**
|
|
* Imports a Javascript file from a specified directory.
|
|
*
|
|
* @param {string} path - The path path where the JS file is located.
|
|
* @returns {Promise<Object>} A promise that resolves to the JS content of the file.
|
|
* @throws {Error} Throws an error if the fetch request fails or if the file is not found.
|
|
*/
|
|
async importJs(path) {
|
|
try {
|
|
return await import(path)
|
|
} catch (error) {
|
|
console.error(`Error loading JS for ${path}:`, error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Imports a JSON file from a specified directory.
|
|
*
|
|
* @param {string} dir - The directory path where the JSON file is located.
|
|
* @param {string} file - The name of the JSON file to import.
|
|
* @returns {Promise<Object>} A promise that resolves to the JSON content of the file.
|
|
* @throws {Error} Throws an error if the fetch request fails or if the file is not found.
|
|
*/
|
|
async importJson(dir, file) {
|
|
|
|
try {
|
|
const response = await fetch(`${dir}/${file}`);
|
|
if (!response.ok) {
|
|
throw new Error(`🚨 Oups. Can't find ${file} in ${this.getIdFromPath(dir)}`);
|
|
}
|
|
const json = await response.json();
|
|
return json;
|
|
} catch (error) {
|
|
console.error(`Error fetching json file in ${this.getIdFromPath(dir)}:`, error);
|
|
throw error; // Re-throw the error to be caught in importJson
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Lists the directories within a specified directory by fetching and parsing its HTML content.
|
|
*
|
|
* This method fetches the automatically generated HTML page listing content of a directory,
|
|
* parses it to find links, and extracts directory names based on a date pattern.
|
|
*
|
|
* Server must allow listing directory.
|
|
* Yes. It's dirty.
|
|
*
|
|
* @param {string} dir - The URL of the directory to list.
|
|
* @returns {Promise<Array<string>>} A promise that resolves to an array of directory names.
|
|
* @throws {Error} Throws an error if the fetch request fails or if there is an issue parsing the content.
|
|
*/
|
|
async listDir(dir) {
|
|
try {
|
|
const response = await fetch(dir);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch plugin directory'); }
|
|
const text = await response.text();
|
|
|
|
const parser = new DOMParser();
|
|
const doc = parser.parseFromString(text, 'text/html');
|
|
const links = doc.querySelectorAll('a');
|
|
|
|
const directories = [];
|
|
const datePattern = /\d+\//; // Matches the first number followed by a '/'
|
|
|
|
links.forEach(link => {
|
|
const name = link.textContent;
|
|
if (name) {
|
|
const match = name.match(datePattern);
|
|
if (match) {
|
|
// Remove everything from the date part onward
|
|
const cleanedName = name.split(datePattern)[0].trim();
|
|
directories.push(cleanedName);
|
|
}
|
|
}
|
|
});
|
|
|
|
return directories;
|
|
} catch (error) {
|
|
console.error(`Error listing ${dir} items`, error);
|
|
throw error
|
|
}
|
|
}
|
|
|
|
|
|
/*-- CSS --*/
|
|
|
|
/**
|
|
* Compare declared and DOM loaded stylesheet. Warn user if he missed one.
|
|
* {array} - An array of stylesheet declared by user in manifest
|
|
*/
|
|
lookForForgottenStylesheet(userStylesheets) {
|
|
// Get all stylesheet (not for screen) already loaded
|
|
const links = document.querySelectorAll(`link[rel="stylesheet"]:not([media="screen"]`)
|
|
|
|
let domStylesheets = []
|
|
|
|
// Iterate over each link element
|
|
links.forEach(link => {
|
|
// Exlude CSS Page Weaver stylesheet
|
|
if(!link.href.includes('csspageweaver')){
|
|
domStylesheets.push(link.href.split('?')[0])
|
|
}
|
|
});
|
|
|
|
// We'll not retrieve already loaded CSS just warn user if he forget one stylesheet
|
|
//return CSSArray
|
|
|
|
let missedStylesheets = domStylesheets.filter(sheet => !userStylesheets.includes(sheet));
|
|
|
|
if(missedStylesheets.length > 0){
|
|
console.warn(`😶🌫️ Did you missed to include ${missedStylesheets.join(', ') } into csspageweaver/manifest.json? `)
|
|
}
|
|
}
|
|
|
|
|
|
/*-- Features --*/
|
|
|
|
/**
|
|
* Initializes features by loading their configurations and creating instances.
|
|
*
|
|
* This method reads feature names from a manifest, imports their configurations,
|
|
* and sets up their UI, hooks and scripts if available.
|
|
*/
|
|
async initializeFeatures(featuresNamesList, featuresParameters) {
|
|
|
|
// Loop all features
|
|
for (const featureName of featuresNamesList) {
|
|
const featureDir = `${cssPageWeaver.directory.plugins}/${featureName}/`
|
|
|
|
const featureEmbedConfig = await this.importJson(featureDir, `config.json`)
|
|
const featureManifestConfig = {"parameters" : featuresParameters[featureName]} || {}
|
|
const featureConfig = {...featureEmbedConfig, ...featureManifestConfig}
|
|
|
|
// Create a new instance of the feature
|
|
let feature = new this.Feature(featureConfig, featureName, featureDir, cssPageWeaver.parameters);
|
|
|
|
// Import feature's HTML template if specified
|
|
if(feature.ui){
|
|
if(feature.ui.template){
|
|
feature.ui.html = await this.importTemplate(featureDir, feature.ui.template);
|
|
}
|
|
}
|
|
|
|
// Import the feature's script and hook if specified
|
|
['script', 'hook'].forEach(property => {
|
|
if (feature[property]) {
|
|
let fileName = feature[property];
|
|
feature[property] = this.importJs(`${featureDir}/${fileName}`);
|
|
}
|
|
});
|
|
|
|
// Store the initialized feature
|
|
cssPageWeaver.features[featureName] = feature
|
|
|
|
}
|
|
}
|
|
|
|
async initializeUserHook(handlerList = []) {
|
|
cssPageWeaver.user.hook = await Promise.all(
|
|
handlerList.map(async path => {
|
|
return await this.importJs(path) // Return the promise from importJs
|
|
})
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Retrieves features that have a hook from the `cssPageWeaver` object.
|
|
* @returns {Array} An array of features that have a hook. Returns an empty array if `cssPageWeaver` is undefined or does not have features.
|
|
*/
|
|
getFeaturesHookAsArray(){
|
|
// Convert the features object to an array
|
|
const featuresArray = Object.values(cssPageWeaver.features);
|
|
// Filter to only features with a hook
|
|
return featuresArray
|
|
.filter(feature => feature.hook)
|
|
.map(feature => feature.hook);
|
|
}
|
|
|
|
/**
|
|
* Aggregates the CSS paths from all features that have a stylesheet.
|
|
* @returns {Array} An array of CSS file paths.
|
|
*/
|
|
getFeaturesStyleAsArray(){
|
|
// Convert the features object to an array
|
|
const featuresArray = Object.values(cssPageWeaver.features);
|
|
// Filter to only features with a hook
|
|
return featuresArray
|
|
.filter(feature => feature.stylesheet)
|
|
.map(feature => `${feature.directory}${feature.stylesheet}`);
|
|
}
|
|
|
|
/*-- Setup --*/
|
|
|
|
async setup(){
|
|
|
|
// Set Shared dict
|
|
this.setSharedDictionnary()
|
|
|
|
// Import Manifest
|
|
let manifest = await this.importManifest()
|
|
|
|
// Get features list as an array of class for each
|
|
await this.initializeFeatures(manifest.plugins, manifest.pluginsParameters)
|
|
|
|
// list user stylesheets
|
|
cssPageWeaver.user.css = manifest.css || []
|
|
|
|
// list stylesheets as convenient object
|
|
cssPageWeaver.stylesheet = {
|
|
features: this.getFeaturesStyleAsArray(),
|
|
user: cssPageWeaver.user.css
|
|
}
|
|
|
|
// Warn user if he missed a stylesheet
|
|
this.lookForForgottenStylesheet(cssPageWeaver.user.css)
|
|
|
|
// Import User handlers
|
|
await this.initializeUserHook(manifest.hook)
|
|
|
|
// Dispatch an event to signal that features are loaded
|
|
const event = new Event('cssPageWeaver-dictInit');
|
|
document.dispatchEvent(event)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
export default CssPageWeaver_Dict |