first commit

This commit is contained in:
Llewellyn van der Merwe 2023-11-28 10:36:29 +02:00
commit 8a932f6a88
Signed by: Llewellyn
GPG Key ID: A9201372263741E7
14 changed files with 3753 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
coverage
.idea

7
LICENSE.md Normal file
View File

@ -0,0 +1,7 @@
Copyright (c) 2023 Llewellyn van der Merwe
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

65
README.md Normal file
View File

@ -0,0 +1,65 @@
# PreUpVer Library
## Introduction
PreUpVer is a versatile JavaScript library designed to automatically update the version numbers of library scripts in your documentation. It identifies specific `<pre>` tags on your webpage and updates them with the latest version tag fetched from a specified repository, ensuring your documentation always displays up-to-date information.
## Installation
To use PreUpVer in your project, include the minified library in the header of your HTML page:
```html
<!-- Include the PreUpVer script from jsDelivr CDN -->
<script src="https://cdn.jsdelivr.net/gh/Llewellynvdm/PreUpVer@1.0.0/dist/js/preupver.min.js"></script>
```
## How It Works
PreUpVer operates by searching for `<pre>` tags with a designated class (`preupver`) and using their data attributes to perform version updates. It simplifies the process of keeping your library references up-to-date in documentation.
### Automatic Detection and Updating
When the webpage loads, PreUpVer finds all `<pre>` tags marked with the `preupver` class. It then extracts necessary details from their data attributes and updates each tag with the latest library version.
### Usage
1. **Marking `<pre>` Tags:**
Add the `class="preupver"` to `<pre>` tags in your HTML and define the required data attributes for automatic updating:
```html
<pre id="unique-id" class="preupver"
data-api-url="https://api.github.com/repos/username/library/tags"
data-description="Description of the library script"
data-url="https://cdn.jsdelivr.net/gh/username/library@${version}/dist/library.min.js">
</pre>
```
Replace `unique-id`, `username`, `library`, and other placeholders with your specific details.
2. **Attributes Explained:**
- `id`: A unique identifier for the `<pre>` tag.
- `data-api-url`: The API URL to fetch the latest library version.
- `data-description`: A brief description of the library script.
- `data-url`: The URL of the script, where `${version}` will be replaced with the latest version number.
### Example
Check out the [tests](https://git.vdm.dev/Llewellyn/PreUpVer/src/branch/master/tests/) folder for the examples we use to test if this library works as expected.
## Advanced Usage
For more advanced scenarios, PreUpVer also provides `TargetLibrary` and `IncludeVersionUpdater` classes, which can be used to manually configure and update `<pre>` tags. This is particularly useful for custom implementations or testing purposes.
### Manual Configuration
You can manually create instances of `TargetLibrary` and `IncludeVersionUpdater` for specific `<pre>` tags. Refer to the `/tests` directory in the repository for examples and test cases.
## Contributing
We welcome contributions to PreUpVer. Please refer to the repository's contribution guidelines for more information on how to contribute.
## License
PreUpVer is open-source software licensed under the [MIT license](https://git.vdm.dev/Llewellyn/PreUpVer/src/branch/master/LICENSE.md).

294
dist/js/preupver.js vendored Normal file
View File

@ -0,0 +1,294 @@
/**
* PreUpVer v1.0.0
* https://git.vdm.dev/Llewellyn/PreUpVer
* (c) 2014 - 2023 Llewellyn van der Merwe
* MIT License
**/
(function (factory) {
typeof define === 'function' && define.amd ? define(factory) :
factory();
})((function () { 'use strict';
/**
* Class for managing local storage of data.
*/
class Memory {
// Constant representing one day in milliseconds.
static ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000;
/**
* Stores data in local storage.
*
* @param {string} key - The data key.
* @param {Object} data - The scripture data to be stored.
* @throws {Error} If storing data fails.
*/
static set(key, data) {
const item = {
data,
timestamp: Date.now(),
};
try {
localStorage.setItem(key, JSON.stringify(item));
} catch (error) {
console.error('Error storing data in local storage:', error);
throw error;
}
}
/**
* Retrieves data from local storage.
*
* @param {string} key - The data key.
* @returns {Promise<Object|null>} The scripture data or null if not found.
*/
static async get(key) {
try {
const storedItem = localStorage.getItem(key);
if (storedItem) {
const {data, timestamp} = JSON.parse(storedItem);
if (timestamp > Date.now() - Memory.ONE_DAY_IN_MILLISECONDS) {
return data;
}
}
return null;
} catch (error) {
console.error('Error parsing or retrieving data from local storage:', error);
throw error;
}
}
}
/**
* Class for handling API calls to fetch scripture data.
*/
class Api {
/**
* Fetches data from the API, using local storage to cache responses.
*
* @param {string} apiEndpoint - The endpoint URL for the API.
* @param {string} key - The data key.
* @returns {Promise<Object>} A promise that resolves with the scripture data.
* @throws {Error} If the API request fails.
*/
async get(apiEndpoint, key) {
try {
const localStorageData = await Memory.get(key);
if (localStorageData !== null) {
return localStorageData;
}
const response = await fetch(apiEndpoint);
if (!response.ok) {
throw new Error(`${response.status} - ${response.statusText || 'Failed to fetch data'}`);
}
const data = await response.json();
await Memory.set(key, data);
return data;
} catch (error) {
console.error('Error fetching data:', error);
throw new Error(error.message || 'Error fetching data');
}
}
}
/**
* Class for managing actions based on element data attributes.
*/
class Action {
#element;
#apiURL;
#description;
#libUrl;
#id;
/**
* Initializes the Actions object with a DOM element and its data attributes.
*
* @param {HTMLElement} element - The DOM element containing data attributes.
*/
constructor(element) {
if (!(element instanceof HTMLElement)) {
throw new Error("triggerElement must be an instance of HTMLElement.");
}
this.#element = element;
this.#id = element.id;
this.#apiURL = element.dataset.apiUrl;
this.#description = element.dataset.description;
this.#libUrl = element.dataset.url;
}
/**
* Retrieves the id/key.
*
* @returns {string} The element key.
*/
get id() {
return this.#id;
}
/**
* Retrieves the api URL.
*
* @returns {string} The library API url to get the version data.
*/
get api() {
return this.#apiURL;
}
/**
* Retrieves the library description.
*
* @returns {string} The library description.
*/
get description() {
return this.#description;
}
/**
* Retrieves the Library URL.
*
* @returns {string} The library url to include.
*/
get libUrl() {
return this.#libUrl;
}
/**
* Retrieves the DOM element.
*
* @returns {HTMLElement} The DOM element associated with this object.
*/
get element() {
return this.#element;
}
}
/**
* Loader class responsible for handling the loading of pre updated version details.
* It initializes necessary components and loads data into a specified HTML element.
*/
class Loader {
#api;
#action;
/**
* Constructs a Loader instance.
* Allows for dependency injection of the Api class for easier testing and flexibility.
* @param {Api} api - Instance of Api class for making API calls.
*/
constructor(api = new Api()) {
this.#api = api;
}
/**
* Load the Reference references into the specified HTML element.
*
* @param {HTMLElement} element - The element to update.
*/
async load(element) {
if (!this.#validate(element)) {
return;
}
this.#init(element);
await this.#process();
}
/**
* Validates the element
* @param {HTMLElement} element - The element to update.
* @returns {boolean} true if valid
* @private
*/
#validate(element) {
if (!element.id) {
console.error("Element missing ID:", element);
return false;
}
return true;
}
/**
* Processes a valid pre tag by fetching the new version and loading it.
* This method handles the asynchronous nature of API calls.
* @private
*/
async #process() {
try {
const data = await this.#api.get(this.#action.api, this.#action.id);
if (data) {
this.#load(data);
}
} catch (error) {
console.error(`Error loading preupver:`, error);
}
}
/**
* Initializes components necessary.
*
* @param {HTMLElement} element - The element to be initialized for loading.
* @private
*/
#init(element) {
this.#action = new Action(element);
}
/**
* Load the data to the pre tag.
*
* @param {object} data - The version data.
* @private
*/
#load(data) {
const version = data[0].name;
const preTag = document.getElementById(this.#action.id);
if (!preTag) {
throw new Error('Pre tag not found.');
}
preTag.innerHTML = `&lt;!-- ${this.#action.description} --&gt;\n&lt;script src&#x3D;&quot;${this.#action.libUrl.replace('${version}', version)}&quot;&gt;&lt;/script&gt;`;
{
console.log(data);
}
}
}
/**
* Initializes loaders for elements with class 'preupver'.
* Each element gets its own Loader instance because each loader maintains state
* specific to the element it operates on. This function encapsulates the logic
* for finding relevant elements and initializing loaders for them.
* @param {Api} api - The Api instance to be used by each Loader.
*/
function initializePreUpVerLoaders(api) {
const elements = document.querySelectorAll('.preupver');
elements.forEach(element => {
// Create a new loader instance for each element
const loader = new Loader(api);
loader.load(element).catch(error => {
// Error handling for each loader instance
console.error(`Loading error for element ${element}:`, error);
});
});
}
/**
* Entry point to load pre tag version updater.
* Attaches event listener to DOMContentLoaded to ensure the DOM is fully loaded
* before attempting to initialize loaders.
*/
document.addEventListener('DOMContentLoaded', (event) => {
try {
const api = new Api();
initializePreUpVerLoaders(api);
} catch (error) {
console.error("Error initializing PreUp loaders:", error);
}
});
}));

2
dist/js/preupver.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2982
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

32
package.json Normal file
View File

@ -0,0 +1,32 @@
{
"name": "preupver",
"title": "PreUpVer",
"description": "PreUpVer is a versatile JavaScript library designed to automatically update the version numbers of library scripts in your documentation.",
"version": "1.0.0",
"main": "dist/js/preupver.min.js",
"scripts": {
"build": "rollup -c"
},
"repository": {
"type": "git",
"url": "git+https://git.vdm.dev/Llewellyn/PreUpVer.git"
},
"license": "MIT",
"bugs": {
"url": "https://git.vdm.dev/Llewellyn/PreUpVer/issues"
},
"homepage": "https://git.vdm.dev/Llewellyn/PreUpVer",
"dependencies": {
},
"devDependencies": {
"@babel/core": "latest",
"@babel/preset-env": "latest",
"@rollup/plugin-babel": "latest",
"@rollup/plugin-commonjs": "latest",
"@rollup/plugin-node-resolve": "latest",
"rollup": "latest",
"rollup-plugin-terser": "latest",
"rollup-plugin-license": "latest",
"@rollup/plugin-replace": "latest"
}
}

58
rollup.config.js Normal file
View File

@ -0,0 +1,58 @@
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { babel } from '@rollup/plugin-babel';
import { terser } from 'rollup-plugin-terser';
import license from 'rollup-plugin-license';
import replace from '@rollup/plugin-replace';
const licenseLine = {
banner: `/*! PreUpVer v${require('./package.json').version} | https://git.vdm.dev/Llewellyn/PreUpVer | (c) 2014 - ${new Date().getFullYear()} Llewellyn van der Merwe | MIT License */`
};
const licenseHeader = {
banner: `/**
* PreUpVer v${require('./package.json').version}
* https://git.vdm.dev/Llewellyn/PreUpVer
* (c) 2014 - ${new Date().getFullYear()} Llewellyn van der Merwe
* MIT License
**/
`};
export default [
{
input: 'src/js/preupver.js',
plugins: [
license(licenseHeader),
replace({
'process.env.DEBUG': true,
preventAssignment: true,
})],
output: {
file: 'dist/js/preupver.js',
format: 'umd',
name: 'preupver',
},
},
{
input: 'src/js/preupver.js',
plugins: [
resolve(),
commonjs(),
babel({
babelHelpers: 'bundled',
presets: ['@babel/preset-env'],
}),
terser(),
license(licenseLine),
replace({
'process.env.DEBUG': false,
preventAssignment: true,
})
],
output: {
file: 'dist/js/preupver.min.js',
format: 'umd',
name: 'preupver',
},
},
];

73
src/js/core/Action.js Normal file
View File

@ -0,0 +1,73 @@
/**
* Class for managing actions based on element data attributes.
*/
export class Action {
#element;
#apiURL;
#description;
#libUrl;
#id;
/**
* Initializes the Actions object with a DOM element and its data attributes.
*
* @param {HTMLElement} element - The DOM element containing data attributes.
*/
constructor(element) {
if (!(element instanceof HTMLElement)) {
throw new Error("triggerElement must be an instance of HTMLElement.");
}
this.#element = element;
this.#id = element.id;
this.#apiURL = element.dataset.apiUrl;
this.#description = element.dataset.description;
this.#libUrl = element.dataset.url;
}
/**
* Retrieves the id/key.
*
* @returns {string} The element key.
*/
get id() {
return this.#id;
}
/**
* Retrieves the api URL.
*
* @returns {string} The library API url to get the version data.
*/
get api() {
return this.#apiURL;
}
/**
* Retrieves the library description.
*
* @returns {string} The library description.
*/
get description() {
return this.#description;
}
/**
* Retrieves the Library URL.
*
* @returns {string} The library url to include.
*/
get libUrl() {
return this.#libUrl;
}
/**
* Retrieves the DOM element.
*
* @returns {HTMLElement} The DOM element associated with this object.
*/
get element() {
return this.#element;
}
}

36
src/js/core/Api.js Normal file
View File

@ -0,0 +1,36 @@
import {Memory} from './Memory.js';
/**
* Class for handling API calls to fetch scripture data.
*/
export class Api {
/**
* Fetches data from the API, using local storage to cache responses.
*
* @param {string} apiEndpoint - The endpoint URL for the API.
* @param {string} key - The data key.
* @returns {Promise<Object>} A promise that resolves with the scripture data.
* @throws {Error} If the API request fails.
*/
async get(apiEndpoint, key) {
try {
const localStorageData = await Memory.get(key);
if (localStorageData !== null) {
return localStorageData;
}
const response = await fetch(apiEndpoint);
if (!response.ok) {
throw new Error(`${response.status} - ${response.statusText || 'Failed to fetch data'}`);
}
const data = await response.json();
await Memory.set(key, data);
return data;
} catch (error) {
console.error('Error fetching data:', error);
throw new Error(error.message || 'Error fetching data');
}
}
}

91
src/js/core/Loader.js Normal file
View File

@ -0,0 +1,91 @@
import {Api} from './Api.js';
import {Action} from './Action.js';
/**
* Loader class responsible for handling the loading of pre updated version details.
* It initializes necessary components and loads data into a specified HTML element.
*/
export class Loader {
#api;
#action;
/**
* Constructs a Loader instance.
* Allows for dependency injection of the Api class for easier testing and flexibility.
* @param {Api} api - Instance of Api class for making API calls.
*/
constructor(api = new Api()) {
this.#api = api;
}
/**
* Load the Reference references into the specified HTML element.
*
* @param {HTMLElement} element - The element to update.
*/
async load(element) {
if (!this.#validate(element)) {
return;
}
this.#init(element);
await this.#process();
}
/**
* Validates the element
* @param {HTMLElement} element - The element to update.
* @returns {boolean} true if valid
* @private
*/
#validate(element) {
if (!element.id) {
console.error("Element missing ID:", element);
return false;
}
return true;
}
/**
* Processes a valid pre tag by fetching the new version and loading it.
* This method handles the asynchronous nature of API calls.
* @private
*/
async #process() {
try {
const data = await this.#api.get(this.#action.api, this.#action.id);
if (data) {
this.#load(data);
}
} catch (error) {
console.error(`Error loading preupver:`, error);
}
}
/**
* Initializes components necessary.
*
* @param {HTMLElement} element - The element to be initialized for loading.
* @private
*/
#init(element) {
this.#action = new Action(element);
}
/**
* Load the data to the pre tag.
*
* @param {object} data - The version data.
* @private
*/
#load(data) {
const version = data[0].name;
const preTag = document.getElementById(this.#action.id);
if (!preTag) {
throw new Error('Pre tag not found.');
}
preTag.innerHTML = `&lt;!-- ${this.#action.description} --&gt;\n&lt;script src&#x3D;&quot;${this.#action.libUrl.replace('${version}', version)}&quot;&gt;&lt;/script&gt;`;
if (process.env.DEBUG) {
console.log(data);
}
}
}

49
src/js/core/Memory.js Normal file
View File

@ -0,0 +1,49 @@
/**
* Class for managing local storage of data.
*/
export class Memory {
// Constant representing one day in milliseconds.
static ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000;
/**
* Stores data in local storage.
*
* @param {string} key - The data key.
* @param {Object} data - The scripture data to be stored.
* @throws {Error} If storing data fails.
*/
static set(key, data) {
const item = {
data,
timestamp: Date.now(),
};
try {
localStorage.setItem(key, JSON.stringify(item));
} catch (error) {
console.error('Error storing data in local storage:', error);
throw error;
}
}
/**
* Retrieves data from local storage.
*
* @param {string} key - The data key.
* @returns {Promise<Object|null>} The scripture data or null if not found.
*/
static async get(key) {
try {
const storedItem = localStorage.getItem(key);
if (storedItem) {
const {data, timestamp} = JSON.parse(storedItem);
if (timestamp > Date.now() - Memory.ONE_DAY_IN_MILLISECONDS) {
return data;
}
}
return null;
} catch (error) {
console.error('Error parsing or retrieving data from local storage:', error);
throw error;
}
}
}

35
src/js/preupver.js Normal file
View File

@ -0,0 +1,35 @@
import {Api} from './core/Api.js';
import {Loader} from "./core/Loader.js";
/**
* Initializes loaders for elements with class 'preupver'.
* Each element gets its own Loader instance because each loader maintains state
* specific to the element it operates on. This function encapsulates the logic
* for finding relevant elements and initializing loaders for them.
* @param {Api} api - The Api instance to be used by each Loader.
*/
function initializePreUpVerLoaders(api) {
const elements = document.querySelectorAll('.preupver');
elements.forEach(element => {
// Create a new loader instance for each element
const loader = new Loader(api);
loader.load(element).catch(error => {
// Error handling for each loader instance
console.error(`Loading error for element ${element}:`, error);
});
});
}
/**
* Entry point to load pre tag version updater.
* Attaches event listener to DOMContentLoaded to ensure the DOM is fully loaded
* before attempting to initialize loaders.
*/
document.addEventListener('DOMContentLoaded', (event) => {
try {
const api = new Api();
initializePreUpVerLoaders(api);
} catch (error) {
console.error("Error initializing PreUp loaders:", error);
}
});

26
tests/basic.html Normal file
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Basic PreUpVer Test</title>
<!-- getBible JS -->
<script src="../dist/js/preupver.js"></script>
</head>
<body>
<h1>Updated version in Pre Include text</h1>
<p>The version of the include text is updated automatically:</p>
<pre id="getBible-include-details"class="preupver"
data-api-url="https://git.vdm.dev/api/v1/repos/getBible/loader/tags?page=1&limit=1"
data-description="Include the GetBible tooltips script from jsDelivr CDN"
data-url="https://cdn.jsdelivr.net/gh/getbible/loader@${version}/dist/js/getBible.min.js">
// will be updated via the library
</pre>
</body>
</html>