]> BookStack Code Mirror - bookstack/commitdiff
TS: Converted app file and animations service
authorDan Brown <redacted>
Fri, 11 Oct 2024 14:19:19 +0000 (15:19 +0100)
committerDan Brown <redacted>
Fri, 11 Oct 2024 14:19:19 +0000 (15:19 +0100)
Extracted functions out of app file during changes to clean up.
Altered animation function to use normal css prop names instead of JS
CSS prop names.

resources/js/app.js [deleted file]
resources/js/app.ts [new file with mode: 0644]
resources/js/components/chapter-contents.js
resources/js/components/collapsible.js
resources/js/components/dropdown-search.js
resources/js/components/expand-toggle.js
resources/js/components/index.ts [moved from resources/js/components/index.js with 100% similarity]
resources/js/components/popup.js
resources/js/global.d.ts
resources/js/services/animations.ts [moved from resources/js/services/animations.js with 63% similarity]
resources/js/services/util.ts

diff --git a/resources/js/app.js b/resources/js/app.js
deleted file mode 100644 (file)
index 5f4902f..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-import {EventManager} from './services/events.ts';
-import {HttpManager} from './services/http.ts';
-import {Translator} from './services/translations.ts';
-import * as componentMap from './components';
-import {ComponentStore} from './services/components.ts';
-
-// eslint-disable-next-line no-underscore-dangle
-window.__DEV__ = false;
-
-// Url retrieval function
-window.baseUrl = function baseUrl(path) {
-    let targetPath = path;
-    let basePath = document.querySelector('meta[name="base-url"]').getAttribute('content');
-    if (basePath[basePath.length - 1] === '/') basePath = basePath.slice(0, basePath.length - 1);
-    if (targetPath[0] === '/') targetPath = targetPath.slice(1);
-    return `${basePath}/${targetPath}`;
-};
-
-window.importVersioned = function importVersioned(moduleName) {
-    const version = document.querySelector('link[href*="/dist/styles.css?version="]').href.split('?version=').pop();
-    const importPath = window.baseUrl(`dist/${moduleName}.js?version=${version}`);
-    return import(importPath);
-};
-
-// Set events, http & translation services on window
-window.$http = new HttpManager();
-window.$events = new EventManager();
-window.$trans = new Translator();
-
-// Load & initialise components
-window.$components = new ComponentStore();
-window.$components.register(componentMap);
-window.$components.init();
diff --git a/resources/js/app.ts b/resources/js/app.ts
new file mode 100644 (file)
index 0000000..141a50e
--- /dev/null
@@ -0,0 +1,23 @@
+import {EventManager} from './services/events';
+import {HttpManager} from './services/http';
+import {Translator} from './services/translations';
+import * as componentMap from './components/index';
+import {ComponentStore} from './services/components';
+import {baseUrl, importVersioned} from "./services/util";
+
+// eslint-disable-next-line no-underscore-dangle
+window.__DEV__ = false;
+
+// Make common important util functions global
+window.baseUrl = baseUrl;
+window.importVersioned = importVersioned;
+
+// Setup events, http & translation services
+window.$http = new HttpManager();
+window.$events = new EventManager();
+window.$trans = new Translator();
+
+// Load & initialise components
+window.$components = new ComponentStore();
+window.$components.register(componentMap);
+window.$components.init();
index 7c6480a1af0b8a6c176e1f6dd8beca9ada8a5033..6b0707bdd0570b4acbae3d32a7cf23f2864232ba 100644 (file)
@@ -1,4 +1,4 @@
-import {slideUp, slideDown} from '../services/animations';
+import {slideUp, slideDown} from '../services/animations.ts';
 import {Component} from './component';
 
 export class ChapterContents extends Component {
index 6f740ed7163204020fcbd6569393b14729d787c4..7b6fa79fb3bcef4ab8c13b59090711754efac7c9 100644 (file)
@@ -1,4 +1,4 @@
-import {slideDown, slideUp} from '../services/animations';
+import {slideDown, slideUp} from '../services/animations.ts';
 import {Component} from './component';
 
 /**
index 787e11c875df3441bd8a81dabdcd5a975d7195d0..fcbabc022a7c21395272c092e175fb968d773ab2 100644 (file)
@@ -1,5 +1,5 @@
 import {debounce} from '../services/util.ts';
-import {transitionHeight} from '../services/animations';
+import {transitionHeight} from '../services/animations.ts';
 import {Component} from './component';
 
 export class DropdownSearch extends Component {
index 0d2018b9da23531b27d98b18db682335ab47615b..29173a058293d99f88ed25390892961ea39e92e1 100644 (file)
@@ -1,4 +1,4 @@
-import {slideUp, slideDown} from '../services/animations';
+import {slideUp, slideDown} from '../services/animations.ts';
 import {Component} from './component';
 
 export class ExpandToggle extends Component {
index 6627365483e69f6c90b37d5eac7528f1f0c71be3..edd428037334c64ecec9d1dcb9a22621184dc4c8 100644 (file)
@@ -1,4 +1,4 @@
-import {fadeIn, fadeOut} from '../services/animations';
+import {fadeIn, fadeOut} from '../services/animations.ts';
 import {onSelect} from '../services/dom';
 import {Component} from './component';
 
index e505c96e0d4a24f1a5d83fcee75a9e1b70962273..b637c97c1b92a7cf55dd3556c427d4e0e1992e54 100644 (file)
@@ -7,10 +7,12 @@ declare global {
     const __DEV__: boolean;
 
     interface Window {
+        __DEV__: boolean;
         $components: ComponentStore;
         $events: EventManager;
         $trans: Translator;
         $http: HttpManager;
         baseUrl: (path: string) => string;
+        importVersioned: (module: string) => Promise<object>;
     }
 }
\ No newline at end of file
similarity index 63%
rename from resources/js/services/animations.js
rename to resources/js/services/animations.ts
index bc983c8072aa529d7693018716c7b0ed255db087..adf4cb3c932f66afb140fbcd66386dbdb6adeb40 100644 (file)
@@ -1,30 +1,30 @@
 /**
  * Used in the function below to store references of clean-up functions.
  * Used to ensure only one transitionend function exists at any time.
- * @type {WeakMap<object, any>}
  */
-const animateStylesCleanupMap = new WeakMap();
+const animateStylesCleanupMap: WeakMap<object, any> = new WeakMap();
 
 /**
  * Animate the css styles of an element using FLIP animation techniques.
- * Styles must be an object where the keys are style properties, camelcase, and the values
+ * Styles must be an object where the keys are style rule names and the values
  * are an array of two items in the format [initialValue, finalValue]
- * @param {Element} element
- * @param {Object} styles
- * @param {Number} animTime
- * @param {Function} onComplete
  */
-function animateStyles(element, styles, animTime = 400, onComplete = null) {
+function animateStyles(
+    element: HTMLElement,
+    styles: Record<string, string[]>,
+    animTime: number = 400,
+    onComplete: Function | null = null
+): void {
     const styleNames = Object.keys(styles);
     for (const style of styleNames) {
-        element.style[style] = styles[style][0];
+        element.style.setProperty(style, styles[style][0]);
     }
 
     const cleanup = () => {
         for (const style of styleNames) {
-            element.style[style] = null;
+            element.style.removeProperty(style);
         }
-        element.style.transition = null;
+        element.style.removeProperty('transition');
         element.removeEventListener('transitionend', cleanup);
         animateStylesCleanupMap.delete(element);
         if (onComplete) onComplete();
@@ -33,7 +33,7 @@ function animateStyles(element, styles, animTime = 400, onComplete = null) {
     setTimeout(() => {
         element.style.transition = `all ease-in-out ${animTime}ms`;
         for (const style of styleNames) {
-            element.style[style] = styles[style][1];
+            element.style.setProperty(style, styles[style][1]);
         }
 
         element.addEventListener('transitionend', cleanup);
@@ -43,9 +43,8 @@ function animateStyles(element, styles, animTime = 400, onComplete = null) {
 
 /**
  * Run the active cleanup action for the given element.
- * @param {Element} element
  */
-function cleanupExistingElementAnimation(element) {
+function cleanupExistingElementAnimation(element: Element) {
     if (animateStylesCleanupMap.has(element)) {
         const oldCleanup = animateStylesCleanupMap.get(element);
         oldCleanup();
@@ -54,15 +53,12 @@ function cleanupExistingElementAnimation(element) {
 
 /**
  * Fade in the given element.
- * @param {Element} element
- * @param {Number} animTime
- * @param {Function|null} onComplete
  */
-export function fadeIn(element, animTime = 400, onComplete = null) {
+export function fadeIn(element: HTMLElement, animTime: number = 400, onComplete: Function | null = null): void {
     cleanupExistingElementAnimation(element);
     element.style.display = 'block';
     animateStyles(element, {
-        opacity: ['0', '1'],
+        'opacity': ['0', '1'],
     }, animTime, () => {
         if (onComplete) onComplete();
     });
@@ -70,14 +66,11 @@ export function fadeIn(element, animTime = 400, onComplete = null) {
 
 /**
  * Fade out the given element.
- * @param {Element} element
- * @param {Number} animTime
- * @param {Function|null} onComplete
  */
-export function fadeOut(element, animTime = 400, onComplete = null) {
+export function fadeOut(element: HTMLElement, animTime: number = 400, onComplete: Function | null = null): void {
     cleanupExistingElementAnimation(element);
     animateStyles(element, {
-        opacity: ['1', '0'],
+        'opacity': ['1', '0'],
     }, animTime, () => {
         element.style.display = 'none';
         if (onComplete) onComplete();
@@ -86,20 +79,18 @@ export function fadeOut(element, animTime = 400, onComplete = null) {
 
 /**
  * Hide the element by sliding the contents upwards.
- * @param {Element} element
- * @param {Number} animTime
  */
-export function slideUp(element, animTime = 400) {
+export function slideUp(element: HTMLElement, animTime: number = 400) {
     cleanupExistingElementAnimation(element);
     const currentHeight = element.getBoundingClientRect().height;
     const computedStyles = getComputedStyle(element);
     const currentPaddingTop = computedStyles.getPropertyValue('padding-top');
     const currentPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
     const animStyles = {
-        maxHeight: [`${currentHeight}px`, '0px'],
-        overflow: ['hidden', 'hidden'],
-        paddingTop: [currentPaddingTop, '0px'],
-        paddingBottom: [currentPaddingBottom, '0px'],
+        'max-height': [`${currentHeight}px`, '0px'],
+        'overflow': ['hidden', 'hidden'],
+        'padding-top': [currentPaddingTop, '0px'],
+        'padding-bottom': [currentPaddingBottom, '0px'],
     };
 
     animateStyles(element, animStyles, animTime, () => {
@@ -109,10 +100,8 @@ export function slideUp(element, animTime = 400) {
 
 /**
  * Show the given element by expanding the contents.
- * @param {Element} element - Element to animate
- * @param {Number} animTime - Animation time in ms
  */
-export function slideDown(element, animTime = 400) {
+export function slideDown(element: HTMLElement, animTime: number = 400) {
     cleanupExistingElementAnimation(element);
     element.style.display = 'block';
     const targetHeight = element.getBoundingClientRect().height;
@@ -120,10 +109,10 @@ export function slideDown(element, animTime = 400) {
     const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
     const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
     const animStyles = {
-        maxHeight: ['0px', `${targetHeight}px`],
-        overflow: ['hidden', 'hidden'],
-        paddingTop: ['0px', targetPaddingTop],
-        paddingBottom: ['0px', targetPaddingBottom],
+        'max-height': ['0px', `${targetHeight}px`],
+        'overflow': ['hidden', 'hidden'],
+        'padding-top': ['0px', targetPaddingTop],
+        'padding-bottom': ['0px', targetPaddingBottom],
     };
 
     animateStyles(element, animStyles, animTime);
@@ -134,11 +123,8 @@ export function slideDown(element, animTime = 400) {
  * Call with first state, and you'll receive a function in return.
  * Call the returned function in the second state to animate between those two states.
  * If animating to/from 0-height use the slide-up/slide down as easier alternatives.
- * @param {Element} element - Element to animate
- * @param {Number} animTime - Animation time in ms
- * @returns {function} - Function to run in second state to trigger animation.
  */
-export function transitionHeight(element, animTime = 400) {
+export function transitionHeight(element: HTMLElement, animTime: number = 400): () => void {
     const startHeight = element.getBoundingClientRect().height;
     const initialComputedStyles = getComputedStyle(element);
     const startPaddingTop = initialComputedStyles.getPropertyValue('padding-top');
@@ -151,10 +137,10 @@ export function transitionHeight(element, animTime = 400) {
         const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
         const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
         const animStyles = {
-            height: [`${startHeight}px`, `${targetHeight}px`],
-            overflow: ['hidden', 'hidden'],
-            paddingTop: [startPaddingTop, targetPaddingTop],
-            paddingBottom: [startPaddingBottom, targetPaddingBottom],
+            'height': [`${startHeight}px`, `${targetHeight}px`],
+            'overflow': ['hidden', 'hidden'],
+            'padding-top': [startPaddingTop, targetPaddingTop],
+            'padding-bottom': [startPaddingBottom, targetPaddingBottom],
         };
 
         animateStyles(element, animStyles, animTime);
index 7f684dd424770b0225606a426df7a6ada3830e9c..c5a5d2db804915153b96d197ea81df808916f1ea 100644 (file)
@@ -99,3 +99,49 @@ export function wait(timeMs: number): Promise<any> {
         setTimeout(res, timeMs);
     });
 }
+
+/**
+ * Generate a full URL from the given relative URL, using a base
+ * URL defined in the head of the page.
+ */
+export function baseUrl(path: string): string {
+    let targetPath = path;
+    const baseUrlMeta = document.querySelector('meta[name="base-url"]');
+    if (!baseUrlMeta) {
+        throw new Error('Could not find expected base-url meta tag in document');
+    }
+
+    let basePath = baseUrlMeta.getAttribute('content') || '';
+    if (basePath[basePath.length - 1] === '/') {
+        basePath = basePath.slice(0, basePath.length - 1);
+    }
+
+    if (targetPath[0] === '/') {
+        targetPath = targetPath.slice(1);
+    }
+
+    return `${basePath}/${targetPath}`;
+}
+
+/**
+ * Get the current version of BookStack in use.
+ * Grabs this from the version query used on app assets.
+ */
+function getVersion(): string {
+    const styleLink = document.querySelector('link[href*="/dist/styles.css?version="]');
+    if (!styleLink) {
+        throw new Error('Could not find expected style link in document for version use');
+    }
+
+    const href = (styleLink.getAttribute('href') || '');
+    return href.split('?version=').pop() || '';
+}
+
+/**
+ * Perform a module import, Ensuring the import is fetched with the current
+ * app version as a cache-breaker.
+ */
+export function importVersioned(moduleName: string): Promise<object> {
+    const importPath = window.baseUrl(`dist/${moduleName}.js?version=${getVersion()}`);
+    return import(importPath);
+}
\ No newline at end of file
Morty Proxy This is a proxified and sanitized view of the page, visit original site.