mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-06-20 04:13:08 +08:00
JS: Converted come common services to typescript
This commit is contained in:
153
resources/js/services/components.ts
Normal file
153
resources/js/services/components.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import {kebabToCamel, camelToKebab} from './text';
|
||||
import {Component} from "../components/component";
|
||||
|
||||
/**
|
||||
* Parse out the element references within the given element
|
||||
* for the given component name.
|
||||
*/
|
||||
function parseRefs(name: string, element: HTMLElement):
|
||||
{refs: Record<string, HTMLElement>, manyRefs: Record<string, HTMLElement[]>} {
|
||||
const refs: Record<string, HTMLElement> = {};
|
||||
const manyRefs: Record<string, HTMLElement[]> = {};
|
||||
|
||||
const prefix = `${name}@`;
|
||||
const selector = `[refs*="${prefix}"]`;
|
||||
const refElems = [...element.querySelectorAll(selector)];
|
||||
if (element.matches(selector)) {
|
||||
refElems.push(element);
|
||||
}
|
||||
|
||||
for (const el of refElems as HTMLElement[]) {
|
||||
const refNames = (el.getAttribute('refs') || '')
|
||||
.split(' ')
|
||||
.filter(str => str.startsWith(prefix))
|
||||
.map(str => str.replace(prefix, ''))
|
||||
.map(kebabToCamel);
|
||||
for (const ref of refNames) {
|
||||
refs[ref] = el;
|
||||
if (typeof manyRefs[ref] === 'undefined') {
|
||||
manyRefs[ref] = [];
|
||||
}
|
||||
manyRefs[ref].push(el);
|
||||
}
|
||||
}
|
||||
return {refs, manyRefs};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse out the element component options.
|
||||
*/
|
||||
function parseOpts(componentName: string, element: HTMLElement): Record<string, string> {
|
||||
const opts: Record<string, string> = {};
|
||||
const prefix = `option:${componentName}:`;
|
||||
for (const {name, value} of element.attributes) {
|
||||
if (name.startsWith(prefix)) {
|
||||
const optName = name.replace(prefix, '');
|
||||
opts[kebabToCamel(optName)] = value || '';
|
||||
}
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
export class ComponentStore {
|
||||
/**
|
||||
* A mapping of active components keyed by name, with values being arrays of component
|
||||
* instances since there can be multiple components of the same type.
|
||||
*/
|
||||
protected components: Record<string, Component[]> = {};
|
||||
|
||||
/**
|
||||
* A mapping of component class models, keyed by name.
|
||||
*/
|
||||
protected componentModelMap: Record<string, typeof Component> = {};
|
||||
|
||||
/**
|
||||
* A mapping of active component maps, keyed by the element components are assigned to.
|
||||
*/
|
||||
protected elementComponentMap: WeakMap<HTMLElement, Record<string, Component>> = new WeakMap();
|
||||
|
||||
/**
|
||||
* Initialize a component instance on the given dom element.
|
||||
*/
|
||||
protected initComponent(name: string, element: HTMLElement): void {
|
||||
const ComponentModel = this.componentModelMap[name];
|
||||
if (ComponentModel === undefined) return;
|
||||
|
||||
// Create our component instance
|
||||
let instance: Component|null = null;
|
||||
try {
|
||||
instance = new ComponentModel();
|
||||
instance.$name = name;
|
||||
instance.$el = element;
|
||||
const allRefs = parseRefs(name, element);
|
||||
instance.$refs = allRefs.refs;
|
||||
instance.$manyRefs = allRefs.manyRefs;
|
||||
instance.$opts = parseOpts(name, element);
|
||||
instance.setup();
|
||||
} catch (e) {
|
||||
console.error('Failed to create component', e, name, element);
|
||||
}
|
||||
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add to global listing
|
||||
if (typeof this.components[name] === 'undefined') {
|
||||
this.components[name] = [];
|
||||
}
|
||||
this.components[name].push(instance);
|
||||
|
||||
// Add to element mapping
|
||||
const elComponents = this.elementComponentMap.get(element) || {};
|
||||
elComponents[name] = instance;
|
||||
this.elementComponentMap.set(element, elComponents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all components found within the given element.
|
||||
*/
|
||||
public init(parentElement: Document|HTMLElement = document) {
|
||||
const componentElems = parentElement.querySelectorAll('[component],[components]');
|
||||
|
||||
for (const el of componentElems) {
|
||||
const componentNames = `${el.getAttribute('component') || ''} ${(el.getAttribute('components'))}`.toLowerCase().split(' ').filter(Boolean);
|
||||
for (const name of componentNames) {
|
||||
this.initComponent(name, el as HTMLElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given component mapping into the component system.
|
||||
* @param {Object<String, ObjectConstructor<Component>>} mapping
|
||||
*/
|
||||
public register(mapping: Record<string, typeof Component>) {
|
||||
const keys = Object.keys(mapping);
|
||||
for (const key of keys) {
|
||||
this.componentModelMap[camelToKebab(key)] = mapping[key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first component of the given name.
|
||||
*/
|
||||
public first(name: string): Component|null {
|
||||
return (this.components[name] || [null])[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the components of the given name.
|
||||
*/
|
||||
public get(name: string): Component[] {
|
||||
return this.components[name] || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first component, of the given name, that's assigned to the given element.
|
||||
*/
|
||||
public firstOnElement(element: HTMLElement, name: string): Component|null {
|
||||
const elComponents = this.elementComponentMap.get(element) || {};
|
||||
return elComponents[name] || null;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user