create-microact-app 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +95 -0
- package/package.json +21 -0
- package/templates/vanilla/.github/workflows/deploy.yml +38 -0
- package/templates/vanilla/index.html +13 -0
- package/templates/vanilla/node_modules/.package-lock.json +207 -0
- package/templates/vanilla/node_modules/@esbuild/darwin-x64/README.md +3 -0
- package/templates/vanilla/node_modules/@esbuild/darwin-x64/bin/esbuild +0 -0
- package/templates/vanilla/node_modules/@esbuild/darwin-x64/package.json +17 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/README.md +154 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/dist/microact.cjs.js +1749 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/dist/microact.cjs.js.map +1 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/dist/microact.esm.js +1743 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/dist/microact.esm.js.map +1 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/dist/microact.umd.js +2 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/dist/microact.umd.js.map +1 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/example/index.html +13 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/example/index.js +63 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/package.json +38 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/rollup.config.cjs +30 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/src/Component.js +831 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/src/DOMUpdater.js +320 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/src/EventBus.js +123 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/src/Router.js +253 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/src/UpdateScheduler.js +218 -0
- package/templates/vanilla/node_modules/@monygroupcorp/microact/src/index.js +6 -0
- package/templates/vanilla/node_modules/esbuild/LICENSE.md +21 -0
- package/templates/vanilla/node_modules/esbuild/README.md +3 -0
- package/templates/vanilla/node_modules/esbuild/bin/esbuild +0 -0
- package/templates/vanilla/node_modules/esbuild/install.js +287 -0
- package/templates/vanilla/node_modules/esbuild/lib/main.d.ts +660 -0
- package/templates/vanilla/node_modules/esbuild/lib/main.js +2393 -0
- package/templates/vanilla/node_modules/esbuild/package.json +42 -0
- package/templates/vanilla/node_modules/nanoid/LICENSE +20 -0
- package/templates/vanilla/node_modules/nanoid/README.md +39 -0
- package/templates/vanilla/node_modules/nanoid/async/index.browser.cjs +69 -0
- package/templates/vanilla/node_modules/nanoid/async/index.browser.js +34 -0
- package/templates/vanilla/node_modules/nanoid/async/index.cjs +71 -0
- package/templates/vanilla/node_modules/nanoid/async/index.d.ts +56 -0
- package/templates/vanilla/node_modules/nanoid/async/index.js +35 -0
- package/templates/vanilla/node_modules/nanoid/async/index.native.js +26 -0
- package/templates/vanilla/node_modules/nanoid/async/package.json +12 -0
- package/templates/vanilla/node_modules/nanoid/bin/nanoid.cjs +55 -0
- package/templates/vanilla/node_modules/nanoid/index.browser.cjs +72 -0
- package/templates/vanilla/node_modules/nanoid/index.browser.js +34 -0
- package/templates/vanilla/node_modules/nanoid/index.cjs +85 -0
- package/templates/vanilla/node_modules/nanoid/index.d.cts +91 -0
- package/templates/vanilla/node_modules/nanoid/index.d.ts +91 -0
- package/templates/vanilla/node_modules/nanoid/index.js +45 -0
- package/templates/vanilla/node_modules/nanoid/nanoid.js +1 -0
- package/templates/vanilla/node_modules/nanoid/non-secure/index.cjs +34 -0
- package/templates/vanilla/node_modules/nanoid/non-secure/index.d.ts +33 -0
- package/templates/vanilla/node_modules/nanoid/non-secure/index.js +21 -0
- package/templates/vanilla/node_modules/nanoid/non-secure/package.json +6 -0
- package/templates/vanilla/node_modules/nanoid/package.json +89 -0
- package/templates/vanilla/node_modules/nanoid/url-alphabet/index.cjs +7 -0
- package/templates/vanilla/node_modules/nanoid/url-alphabet/index.js +3 -0
- package/templates/vanilla/node_modules/nanoid/url-alphabet/package.json +6 -0
- package/templates/vanilla/node_modules/picocolors/LICENSE +15 -0
- package/templates/vanilla/node_modules/picocolors/README.md +21 -0
- package/templates/vanilla/node_modules/picocolors/package.json +25 -0
- package/templates/vanilla/node_modules/picocolors/picocolors.browser.js +4 -0
- package/templates/vanilla/node_modules/picocolors/picocolors.d.ts +5 -0
- package/templates/vanilla/node_modules/picocolors/picocolors.js +75 -0
- package/templates/vanilla/node_modules/picocolors/types.d.ts +51 -0
- package/templates/vanilla/node_modules/postcss/LICENSE +20 -0
- package/templates/vanilla/node_modules/postcss/README.md +29 -0
- package/templates/vanilla/node_modules/postcss/lib/at-rule.d.ts +140 -0
- package/templates/vanilla/node_modules/postcss/lib/at-rule.js +25 -0
- package/templates/vanilla/node_modules/postcss/lib/comment.d.ts +68 -0
- package/templates/vanilla/node_modules/postcss/lib/comment.js +13 -0
- package/templates/vanilla/node_modules/postcss/lib/container.d.ts +483 -0
- package/templates/vanilla/node_modules/postcss/lib/container.js +447 -0
- package/templates/vanilla/node_modules/postcss/lib/css-syntax-error.d.ts +248 -0
- package/templates/vanilla/node_modules/postcss/lib/css-syntax-error.js +133 -0
- package/templates/vanilla/node_modules/postcss/lib/declaration.d.ts +151 -0
- package/templates/vanilla/node_modules/postcss/lib/declaration.js +24 -0
- package/templates/vanilla/node_modules/postcss/lib/document.d.ts +69 -0
- package/templates/vanilla/node_modules/postcss/lib/document.js +33 -0
- package/templates/vanilla/node_modules/postcss/lib/fromJSON.d.ts +9 -0
- package/templates/vanilla/node_modules/postcss/lib/fromJSON.js +54 -0
- package/templates/vanilla/node_modules/postcss/lib/input.d.ts +227 -0
- package/templates/vanilla/node_modules/postcss/lib/input.js +265 -0
- package/templates/vanilla/node_modules/postcss/lib/lazy-result.d.ts +190 -0
- package/templates/vanilla/node_modules/postcss/lib/lazy-result.js +550 -0
- package/templates/vanilla/node_modules/postcss/lib/list.d.ts +60 -0
- package/templates/vanilla/node_modules/postcss/lib/list.js +58 -0
- package/templates/vanilla/node_modules/postcss/lib/map-generator.js +368 -0
- package/templates/vanilla/node_modules/postcss/lib/no-work-result.d.ts +46 -0
- package/templates/vanilla/node_modules/postcss/lib/no-work-result.js +138 -0
- package/templates/vanilla/node_modules/postcss/lib/node.d.ts +556 -0
- package/templates/vanilla/node_modules/postcss/lib/node.js +449 -0
- package/templates/vanilla/node_modules/postcss/lib/parse.d.ts +9 -0
- package/templates/vanilla/node_modules/postcss/lib/parse.js +42 -0
- package/templates/vanilla/node_modules/postcss/lib/parser.js +611 -0
- package/templates/vanilla/node_modules/postcss/lib/postcss.d.mts +69 -0
- package/templates/vanilla/node_modules/postcss/lib/postcss.d.ts +458 -0
- package/templates/vanilla/node_modules/postcss/lib/postcss.js +101 -0
- package/templates/vanilla/node_modules/postcss/lib/postcss.mjs +30 -0
- package/templates/vanilla/node_modules/postcss/lib/previous-map.d.ts +81 -0
- package/templates/vanilla/node_modules/postcss/lib/previous-map.js +144 -0
- package/templates/vanilla/node_modules/postcss/lib/processor.d.ts +115 -0
- package/templates/vanilla/node_modules/postcss/lib/processor.js +67 -0
- package/templates/vanilla/node_modules/postcss/lib/result.d.ts +205 -0
- package/templates/vanilla/node_modules/postcss/lib/result.js +42 -0
- package/templates/vanilla/node_modules/postcss/lib/root.d.ts +87 -0
- package/templates/vanilla/node_modules/postcss/lib/root.js +61 -0
- package/templates/vanilla/node_modules/postcss/lib/rule.d.ts +126 -0
- package/templates/vanilla/node_modules/postcss/lib/rule.js +27 -0
- package/templates/vanilla/node_modules/postcss/lib/stringifier.d.ts +46 -0
- package/templates/vanilla/node_modules/postcss/lib/stringifier.js +353 -0
- package/templates/vanilla/node_modules/postcss/lib/stringify.d.ts +9 -0
- package/templates/vanilla/node_modules/postcss/lib/stringify.js +11 -0
- package/templates/vanilla/node_modules/postcss/lib/symbols.js +5 -0
- package/templates/vanilla/node_modules/postcss/lib/terminal-highlight.js +70 -0
- package/templates/vanilla/node_modules/postcss/lib/tokenize.js +266 -0
- package/templates/vanilla/node_modules/postcss/lib/warn-once.js +13 -0
- package/templates/vanilla/node_modules/postcss/lib/warning.d.ts +147 -0
- package/templates/vanilla/node_modules/postcss/lib/warning.js +37 -0
- package/templates/vanilla/node_modules/postcss/package.json +88 -0
- package/templates/vanilla/node_modules/rollup/LICENSE.md +695 -0
- package/templates/vanilla/node_modules/rollup/README.md +125 -0
- package/templates/vanilla/node_modules/rollup/dist/bin/rollup +1715 -0
- package/templates/vanilla/node_modules/rollup/dist/es/getLogFilter.js +64 -0
- package/templates/vanilla/node_modules/rollup/dist/es/package.json +1 -0
- package/templates/vanilla/node_modules/rollup/dist/es/rollup.js +17 -0
- package/templates/vanilla/node_modules/rollup/dist/es/shared/node-entry.js +27273 -0
- package/templates/vanilla/node_modules/rollup/dist/es/shared/watch.js +4857 -0
- package/templates/vanilla/node_modules/rollup/dist/getLogFilter.d.ts +5 -0
- package/templates/vanilla/node_modules/rollup/dist/getLogFilter.js +69 -0
- package/templates/vanilla/node_modules/rollup/dist/loadConfigFile.d.ts +20 -0
- package/templates/vanilla/node_modules/rollup/dist/loadConfigFile.js +29 -0
- package/templates/vanilla/node_modules/rollup/dist/rollup.d.ts +1012 -0
- package/templates/vanilla/node_modules/rollup/dist/rollup.js +31 -0
- package/templates/vanilla/node_modules/rollup/dist/shared/fsevents-importer.js +37 -0
- package/templates/vanilla/node_modules/rollup/dist/shared/index.js +4571 -0
- package/templates/vanilla/node_modules/rollup/dist/shared/loadConfigFile.js +546 -0
- package/templates/vanilla/node_modules/rollup/dist/shared/rollup.js +27351 -0
- package/templates/vanilla/node_modules/rollup/dist/shared/watch-cli.js +561 -0
- package/templates/vanilla/node_modules/rollup/dist/shared/watch-proxy.js +87 -0
- package/templates/vanilla/node_modules/rollup/dist/shared/watch.js +316 -0
- package/templates/vanilla/node_modules/rollup/package.json +181 -0
- package/templates/vanilla/node_modules/source-map-js/LICENSE +28 -0
- package/templates/vanilla/node_modules/source-map-js/README.md +765 -0
- package/templates/vanilla/node_modules/source-map-js/lib/array-set.js +121 -0
- package/templates/vanilla/node_modules/source-map-js/lib/base64-vlq.js +140 -0
- package/templates/vanilla/node_modules/source-map-js/lib/base64.js +67 -0
- package/templates/vanilla/node_modules/source-map-js/lib/binary-search.js +111 -0
- package/templates/vanilla/node_modules/source-map-js/lib/mapping-list.js +79 -0
- package/templates/vanilla/node_modules/source-map-js/lib/quick-sort.js +132 -0
- package/templates/vanilla/node_modules/source-map-js/lib/source-map-consumer.d.ts +1 -0
- package/templates/vanilla/node_modules/source-map-js/lib/source-map-consumer.js +1188 -0
- package/templates/vanilla/node_modules/source-map-js/lib/source-map-generator.d.ts +1 -0
- package/templates/vanilla/node_modules/source-map-js/lib/source-map-generator.js +444 -0
- package/templates/vanilla/node_modules/source-map-js/lib/source-node.d.ts +1 -0
- package/templates/vanilla/node_modules/source-map-js/lib/source-node.js +413 -0
- package/templates/vanilla/node_modules/source-map-js/lib/util.js +594 -0
- package/templates/vanilla/node_modules/source-map-js/package.json +71 -0
- package/templates/vanilla/node_modules/source-map-js/source-map.d.ts +104 -0
- package/templates/vanilla/node_modules/source-map-js/source-map.js +8 -0
- package/templates/vanilla/node_modules/vite/LICENSE.md +3396 -0
- package/templates/vanilla/node_modules/vite/README.md +20 -0
- package/templates/vanilla/node_modules/vite/bin/openChrome.applescript +95 -0
- package/templates/vanilla/node_modules/vite/bin/vite.js +61 -0
- package/templates/vanilla/node_modules/vite/client.d.ts +281 -0
- package/templates/vanilla/node_modules/vite/dist/client/client.mjs +725 -0
- package/templates/vanilla/node_modules/vite/dist/client/client.mjs.map +1 -0
- package/templates/vanilla/node_modules/vite/dist/client/env.mjs +30 -0
- package/templates/vanilla/node_modules/vite/dist/client/env.mjs.map +1 -0
- package/templates/vanilla/node_modules/vite/dist/node/chunks/dep-7ec6f216.js +914 -0
- package/templates/vanilla/node_modules/vite/dist/node/chunks/dep-827b23df.js +66713 -0
- package/templates/vanilla/node_modules/vite/dist/node/chunks/dep-c423598f.js +561 -0
- package/templates/vanilla/node_modules/vite/dist/node/chunks/dep-f0c7dae0.js +7930 -0
- package/templates/vanilla/node_modules/vite/dist/node/chunks/dep-f1e8587f.js +7646 -0
- package/templates/vanilla/node_modules/vite/dist/node/cli.js +929 -0
- package/templates/vanilla/node_modules/vite/dist/node/constants.js +130 -0
- package/templates/vanilla/node_modules/vite/dist/node/index.d.ts +3548 -0
- package/templates/vanilla/node_modules/vite/dist/node/index.js +158 -0
- package/templates/vanilla/node_modules/vite/dist/node-cjs/publicUtils.cjs +4555 -0
- package/templates/vanilla/node_modules/vite/index.cjs +34 -0
- package/templates/vanilla/node_modules/vite/package.json +173 -0
- package/templates/vanilla/node_modules/vite/types/customEvent.d.ts +35 -0
- package/templates/vanilla/node_modules/vite/types/hmrPayload.d.ts +61 -0
- package/templates/vanilla/node_modules/vite/types/hot.d.ts +32 -0
- package/templates/vanilla/node_modules/vite/types/importGlob.d.ts +97 -0
- package/templates/vanilla/node_modules/vite/types/importMeta.d.ts +26 -0
- package/templates/vanilla/node_modules/vite/types/metadata.d.ts +10 -0
- package/templates/vanilla/node_modules/vite/types/package.json +4 -0
- package/templates/vanilla/package-lock.json +589 -0
- package/templates/vanilla/package.json +17 -0
- package/templates/vanilla/src/components/App.js +60 -0
- package/templates/vanilla/src/components/Card.js +21 -0
- package/templates/vanilla/src/components/Hero.js +15 -0
- package/templates/vanilla/src/components/InteractiveDemo.js +59 -0
- package/templates/vanilla/src/main.js +9 -0
- package/templates/vanilla/src/style/main.css +172 -0
- package/templates/vanilla/vite.config.js +8 -0
- package/templates/web3/.env.example +15 -0
- package/templates/web3/.github/workflows/deploy.yml +38 -0
- package/templates/web3/README.md +33 -0
- package/templates/web3/contracts/foundry.toml +11 -0
- package/templates/web3/contracts/script/Deploy.s.sol +13 -0
- package/templates/web3/contracts/src/Counter.sol +21 -0
- package/templates/web3/index.html +13 -0
- package/templates/web3/package.json +25 -0
- package/templates/web3/scripts/chain-start.mjs +305 -0
- package/templates/web3/scripts/chain-stop.mjs +34 -0
- package/templates/web3/scripts/deploy.mjs +155 -0
- package/templates/web3/scripts/setup.mjs +42 -0
- package/templates/web3/src/components/App.js +49 -0
- package/templates/web3/src/components/CounterCard.js +111 -0
- package/templates/web3/src/main.js +54 -0
- package/templates/web3/src/style/main.css +345 -0
- package/templates/web3/vite.config.js +29 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOMUpdater - Utility for granular DOM updates
|
|
3
|
+
* Preserves focus state, scroll position, and form input values during updates
|
|
4
|
+
*/
|
|
5
|
+
export class DOMUpdater {
|
|
6
|
+
constructor() {
|
|
7
|
+
this._activeElement = null;
|
|
8
|
+
this._activeElementPath = null;
|
|
9
|
+
this._scrollPositions = new Map();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Save the currently focused element and scroll positions
|
|
14
|
+
* @param {HTMLElement} rootElement - Root element to save state from
|
|
15
|
+
*/
|
|
16
|
+
saveState(rootElement) {
|
|
17
|
+
// Save active element
|
|
18
|
+
const activeElement = document.activeElement;
|
|
19
|
+
if (activeElement && rootElement.contains(activeElement)) {
|
|
20
|
+
this._activeElement = activeElement;
|
|
21
|
+
this._activeElementPath = this._getElementPath(activeElement, rootElement);
|
|
22
|
+
} else {
|
|
23
|
+
this._activeElement = null;
|
|
24
|
+
this._activeElementPath = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Save scroll positions for all scrollable containers
|
|
28
|
+
this._scrollPositions.clear();
|
|
29
|
+
const scrollableElements = rootElement.querySelectorAll('[data-scroll-container], [style*="overflow"]');
|
|
30
|
+
scrollableElements.forEach(el => {
|
|
31
|
+
if (el.scrollTop !== undefined || el.scrollLeft !== undefined) {
|
|
32
|
+
const path = this._getElementPath(el, rootElement);
|
|
33
|
+
this._scrollPositions.set(path, {
|
|
34
|
+
scrollTop: el.scrollTop,
|
|
35
|
+
scrollLeft: el.scrollLeft
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Restore focus and scroll positions after DOM update
|
|
43
|
+
* @param {HTMLElement} rootElement - Root element to restore state in
|
|
44
|
+
*/
|
|
45
|
+
restoreState(rootElement) {
|
|
46
|
+
// Restore focus
|
|
47
|
+
if (this._activeElementPath) {
|
|
48
|
+
const element = this._getElementByPath(rootElement, this._activeElementPath);
|
|
49
|
+
if (element && element.focus) {
|
|
50
|
+
try {
|
|
51
|
+
// Restore cursor position if it's an input/textarea
|
|
52
|
+
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
|
|
53
|
+
const selectionStart = element.selectionStart || 0;
|
|
54
|
+
element.focus();
|
|
55
|
+
if (element.setSelectionRange) {
|
|
56
|
+
element.setSelectionRange(selectionStart, selectionStart);
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
element.focus();
|
|
60
|
+
}
|
|
61
|
+
} catch (e) {
|
|
62
|
+
// Focus may fail if element is not focusable, ignore
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Restore scroll positions
|
|
68
|
+
this._scrollPositions.forEach((position, path) => {
|
|
69
|
+
const element = this._getElementByPath(rootElement, path);
|
|
70
|
+
if (element) {
|
|
71
|
+
element.scrollTop = position.scrollTop;
|
|
72
|
+
element.scrollLeft = position.scrollLeft;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get a path string to identify an element within its root
|
|
79
|
+
* @private
|
|
80
|
+
*/
|
|
81
|
+
_getElementPath(element, root) {
|
|
82
|
+
const path = [];
|
|
83
|
+
let current = element;
|
|
84
|
+
|
|
85
|
+
while (current && current !== root && current.parentNode) {
|
|
86
|
+
const parent = current.parentNode;
|
|
87
|
+
const index = Array.from(parent.children).indexOf(current);
|
|
88
|
+
path.unshift(index);
|
|
89
|
+
current = parent;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return path.join('/');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get an element by its path within root
|
|
97
|
+
* @private
|
|
98
|
+
*/
|
|
99
|
+
_getElementByPath(root, path) {
|
|
100
|
+
const indices = path.split('/').map(Number);
|
|
101
|
+
let current = root;
|
|
102
|
+
|
|
103
|
+
for (const index of indices) {
|
|
104
|
+
if (!current.children || !current.children[index]) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
current = current.children[index];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return current;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Compare two HTML strings and determine if they're structurally different
|
|
115
|
+
* @param {string} oldHTML - Previous HTML
|
|
116
|
+
* @param {string} newHTML - New HTML
|
|
117
|
+
* @returns {boolean} - True if structure is significantly different
|
|
118
|
+
*/
|
|
119
|
+
diffHTML(oldHTML, newHTML) {
|
|
120
|
+
if (oldHTML === newHTML) return false;
|
|
121
|
+
|
|
122
|
+
// Simple heuristic: if length difference is > 50%, likely structural change
|
|
123
|
+
const lengthDiff = Math.abs(oldHTML.length - newHTML.length);
|
|
124
|
+
const avgLength = (oldHTML.length + newHTML.length) / 2;
|
|
125
|
+
if (lengthDiff / avgLength > 0.5) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Check for tag changes (simplified)
|
|
130
|
+
const oldTags = oldHTML.match(/<[^>]+>/g) || [];
|
|
131
|
+
const newTags = newHTML.match(/<[^>]+>/g) || [];
|
|
132
|
+
if (oldTags.length !== newTags.length) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Update text content of a node
|
|
141
|
+
* @param {Node} node - Node to update
|
|
142
|
+
* @param {string} newText - New text content
|
|
143
|
+
*/
|
|
144
|
+
updateText(node, newText) {
|
|
145
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
146
|
+
node.textContent = newText;
|
|
147
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
148
|
+
// For elements, update only if it's a text-only element
|
|
149
|
+
if (node.children.length === 0) {
|
|
150
|
+
node.textContent = newText;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Update attributes of an element
|
|
157
|
+
* @param {HTMLElement} element - Element to update
|
|
158
|
+
* @param {Object} newAttrs - New attributes object
|
|
159
|
+
*/
|
|
160
|
+
updateAttributes(element, newAttrs) {
|
|
161
|
+
if (!element || element.nodeType !== Node.ELEMENT_NODE) return;
|
|
162
|
+
|
|
163
|
+
// Get current attributes
|
|
164
|
+
const currentAttrs = {};
|
|
165
|
+
Array.from(element.attributes).forEach(attr => {
|
|
166
|
+
currentAttrs[attr.name] = attr.value;
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Update changed attributes
|
|
170
|
+
Object.keys(newAttrs).forEach(name => {
|
|
171
|
+
const newValue = newAttrs[name];
|
|
172
|
+
if (currentAttrs[name] !== newValue) {
|
|
173
|
+
if (newValue === null || newValue === undefined) {
|
|
174
|
+
element.removeAttribute(name);
|
|
175
|
+
} else {
|
|
176
|
+
element.setAttribute(name, newValue);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Remove attributes that are no longer present
|
|
182
|
+
Object.keys(currentAttrs).forEach(name => {
|
|
183
|
+
if (!(name in newAttrs)) {
|
|
184
|
+
element.removeAttribute(name);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Update children of a parent element using a simple reconciliation algorithm
|
|
191
|
+
* @param {HTMLElement} parent - Parent element
|
|
192
|
+
* @param {NodeList|Array} oldChildren - Current children
|
|
193
|
+
* @param {DocumentFragment|HTMLElement} newChildrenContainer - Container with new children
|
|
194
|
+
*/
|
|
195
|
+
updateChildren(parent, oldChildren, newChildrenContainer) {
|
|
196
|
+
const oldArray = Array.from(oldChildren);
|
|
197
|
+
const newArray = Array.from(newChildrenContainer.children || []);
|
|
198
|
+
|
|
199
|
+
// If structure is too different, replace entirely
|
|
200
|
+
if (Math.abs(oldArray.length - newArray.length) > oldArray.length * 0.3) {
|
|
201
|
+
return false; // Signal to fall back to full replacement
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Simple reconciliation: update in place where possible
|
|
205
|
+
const maxLength = Math.max(oldArray.length, newArray.length);
|
|
206
|
+
|
|
207
|
+
for (let i = 0; i < maxLength; i++) {
|
|
208
|
+
const oldChild = oldArray[i];
|
|
209
|
+
const newChild = newArray[i];
|
|
210
|
+
|
|
211
|
+
if (!oldChild && newChild) {
|
|
212
|
+
// New child - append
|
|
213
|
+
parent.appendChild(newChild);
|
|
214
|
+
} else if (oldChild && !newChild) {
|
|
215
|
+
// Old child removed
|
|
216
|
+
oldChild.remove();
|
|
217
|
+
} else if (oldChild && newChild) {
|
|
218
|
+
// Both exist - try to update in place
|
|
219
|
+
if (this._canUpdateInPlace(oldChild, newChild)) {
|
|
220
|
+
this._updateNodeInPlace(oldChild, newChild);
|
|
221
|
+
} else {
|
|
222
|
+
// Replace
|
|
223
|
+
parent.replaceChild(newChild, oldChild);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return true; // Successfully updated
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Check if two nodes can be updated in place
|
|
233
|
+
* @private
|
|
234
|
+
*/
|
|
235
|
+
_canUpdateInPlace(oldNode, newNode) {
|
|
236
|
+
if (oldNode.nodeType !== newNode.nodeType) return false;
|
|
237
|
+
if (oldNode.nodeType === Node.TEXT_NODE) return true;
|
|
238
|
+
if (oldNode.nodeType === Node.ELEMENT_NODE) {
|
|
239
|
+
return oldNode.tagName === newNode.tagName;
|
|
240
|
+
}
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Update a node in place with new content
|
|
246
|
+
* @private
|
|
247
|
+
*/
|
|
248
|
+
_updateNodeInPlace(oldNode, newNode) {
|
|
249
|
+
if (oldNode.nodeType === Node.TEXT_NODE) {
|
|
250
|
+
oldNode.textContent = newNode.textContent;
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (oldNode.nodeType === Node.ELEMENT_NODE) {
|
|
255
|
+
// Update attributes
|
|
256
|
+
const newAttrs = {};
|
|
257
|
+
Array.from(newNode.attributes).forEach(attr => {
|
|
258
|
+
newAttrs[attr.name] = attr.value;
|
|
259
|
+
});
|
|
260
|
+
this.updateAttributes(oldNode, newAttrs);
|
|
261
|
+
|
|
262
|
+
// Update children recursively
|
|
263
|
+
const oldChildren = oldNode.childNodes;
|
|
264
|
+
const newChildren = newNode.childNodes;
|
|
265
|
+
|
|
266
|
+
// For simple text-only updates
|
|
267
|
+
if (oldChildren.length === 1 && newChildren.length === 1 &&
|
|
268
|
+
oldChildren[0].nodeType === Node.TEXT_NODE &&
|
|
269
|
+
newChildren[0].nodeType === Node.TEXT_NODE) {
|
|
270
|
+
oldChildren[0].textContent = newChildren[0].textContent;
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// For more complex structures, try to reconcile
|
|
275
|
+
if (!this.updateChildren(oldNode, oldChildren, newNode)) {
|
|
276
|
+
// Fall back to replacing content
|
|
277
|
+
oldNode.innerHTML = newNode.innerHTML;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Perform a granular update of an element's content
|
|
284
|
+
* @param {HTMLElement} element - Element to update
|
|
285
|
+
* @param {string} newHTML - New HTML content
|
|
286
|
+
* @returns {boolean} - True if granular update succeeded, false if fallback needed
|
|
287
|
+
*/
|
|
288
|
+
updateGranular(element, newHTML) {
|
|
289
|
+
// Save state before update
|
|
290
|
+
this.saveState(element);
|
|
291
|
+
|
|
292
|
+
// Create temporary container for new HTML
|
|
293
|
+
const temp = document.createElement('div');
|
|
294
|
+
temp.innerHTML = newHTML;
|
|
295
|
+
|
|
296
|
+
// Try to update children granularly
|
|
297
|
+
const oldHTML = element.innerHTML;
|
|
298
|
+
const isStructuralChange = this.diffHTML(oldHTML, newHTML);
|
|
299
|
+
|
|
300
|
+
if (isStructuralChange) {
|
|
301
|
+
// Structural change detected - fall back to full replacement
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Try granular update
|
|
306
|
+
try {
|
|
307
|
+
const success = this.updateChildren(element, element.childNodes, temp);
|
|
308
|
+
if (success) {
|
|
309
|
+
// Restore state after successful update
|
|
310
|
+
this.restoreState(element);
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
} catch (e) {
|
|
314
|
+
console.warn('[DOMUpdater] Granular update failed, falling back:', e);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
class EventBus {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.listeners = new Map();
|
|
4
|
+
this.debugMode = false;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Enable or disable debug logging
|
|
9
|
+
* @param {boolean} enabled
|
|
10
|
+
*/
|
|
11
|
+
setDebugMode(enabled) {
|
|
12
|
+
this.debugMode = enabled;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Subscribe to an event
|
|
17
|
+
* @param {string} eventName
|
|
18
|
+
* @param {Function} callback
|
|
19
|
+
* @returns {Function} Unsubscribe function
|
|
20
|
+
*/
|
|
21
|
+
on(eventName, callback) {
|
|
22
|
+
if (!this.listeners.has(eventName)) {
|
|
23
|
+
this.listeners.set(eventName, new Set());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.listeners.get(eventName).add(callback);
|
|
27
|
+
|
|
28
|
+
if (this.debugMode) {
|
|
29
|
+
console.log(`[EventBus] Listener added for "${eventName}"`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Return unsubscribe function
|
|
33
|
+
return () => this.off(eventName, callback);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Remove a specific event listener
|
|
38
|
+
* @param {string} eventName
|
|
39
|
+
* @param {Function} callback
|
|
40
|
+
*/
|
|
41
|
+
off(eventName, callback) {
|
|
42
|
+
if (!this.listeners.has(eventName)) return;
|
|
43
|
+
|
|
44
|
+
this.listeners.get(eventName).delete(callback);
|
|
45
|
+
|
|
46
|
+
if (this.debugMode) {
|
|
47
|
+
console.log(`[EventBus] Listener removed for "${eventName}"`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Cleanup empty event sets
|
|
51
|
+
if (this.listeners.get(eventName).size === 0) {
|
|
52
|
+
this.listeners.delete(eventName);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Remove all listeners for an event
|
|
58
|
+
* @param {string} eventName
|
|
59
|
+
*/
|
|
60
|
+
removeAllListeners(eventName) {
|
|
61
|
+
if (eventName) {
|
|
62
|
+
this.listeners.delete(eventName);
|
|
63
|
+
if (this.debugMode) {
|
|
64
|
+
console.log(`[EventBus] All listeners removed for "${eventName}"`);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
this.listeners.clear();
|
|
68
|
+
if (this.debugMode) {
|
|
69
|
+
console.log('[EventBus] All listeners removed');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Emit an event with data
|
|
76
|
+
* @param {string} eventName
|
|
77
|
+
* @param {any} data
|
|
78
|
+
*/
|
|
79
|
+
emit(eventName, data) {
|
|
80
|
+
if (!this.listeners.has(eventName)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (this.debugMode) {
|
|
85
|
+
console.log(`[EventBus] Emitting "${eventName}"`, data);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.listeners.get(eventName).forEach(callback => {
|
|
89
|
+
try {
|
|
90
|
+
callback(data);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error(`[EventBus] Error in listener for "${eventName}":`, error);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Subscribe to an event for one-time use
|
|
99
|
+
* @param {string} eventName
|
|
100
|
+
* @param {Function} callback
|
|
101
|
+
* @returns {Function} Unsubscribe function
|
|
102
|
+
*/
|
|
103
|
+
once(eventName, callback) {
|
|
104
|
+
// Create a wrapper that will call the callback and then unsubscribe
|
|
105
|
+
const wrappedCallback = (data) => {
|
|
106
|
+
// Unsubscribe first to prevent issues if the callback triggers the same event
|
|
107
|
+
this.off(eventName, wrappedCallback);
|
|
108
|
+
// Call the original callback
|
|
109
|
+
try {
|
|
110
|
+
callback(data);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(`[EventBus] Error in once callback for "${eventName}":`, error);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Register the wrapped callback
|
|
117
|
+
const unsubscribe = this.on(eventName, wrappedCallback);
|
|
118
|
+
return unsubscribe;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Create a single instance for the application
|
|
123
|
+
export const eventBus = new EventBus();
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple client-side router for SPA navigation
|
|
3
|
+
* Supports static routes and browser history
|
|
4
|
+
*/
|
|
5
|
+
class Router {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.routes = new Map();
|
|
8
|
+
this.currentRoute = null;
|
|
9
|
+
this.currentHandler = null;
|
|
10
|
+
this.notFoundHandler = null;
|
|
11
|
+
|
|
12
|
+
// Bind methods
|
|
13
|
+
this.handleRoute = this.handleRoute.bind(this);
|
|
14
|
+
this.navigate = this.navigate.bind(this);
|
|
15
|
+
|
|
16
|
+
// Listen for browser back/forward
|
|
17
|
+
window.addEventListener('popstate', async (e) => {
|
|
18
|
+
await this.handleRoute(window.location.pathname);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Register a route
|
|
24
|
+
* @param {string} path - Route path (e.g., '/', '/cultexecs')
|
|
25
|
+
* @param {Function} handler - Route handler function
|
|
26
|
+
*/
|
|
27
|
+
on(path, handler) {
|
|
28
|
+
this.routes.set(path, handler);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Register a 404 handler
|
|
33
|
+
* @param {Function} handler - Handler for unmatched routes
|
|
34
|
+
*/
|
|
35
|
+
notFound(handler) {
|
|
36
|
+
this.notFoundHandler = handler;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Navigate to a route
|
|
41
|
+
* @param {string} path - Route path
|
|
42
|
+
* @param {boolean} replace - Whether to replace history entry
|
|
43
|
+
*/
|
|
44
|
+
async navigate(path, replace = false) {
|
|
45
|
+
if (path === window.location.pathname) {
|
|
46
|
+
return; // Already on this route
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (replace) {
|
|
50
|
+
window.history.replaceState({ path }, '', path);
|
|
51
|
+
} else {
|
|
52
|
+
window.history.pushState({ path }, '', path);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await this.handleRoute(path);
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Match a path against a route pattern
|
|
62
|
+
* @param {string} pattern - Route pattern (e.g., '/project/:id')
|
|
63
|
+
* @param {string} path - Actual path to match
|
|
64
|
+
* @returns {object|null} Matched params or null if no match
|
|
65
|
+
* @private
|
|
66
|
+
*/
|
|
67
|
+
_matchRoute(pattern, path) {
|
|
68
|
+
// Exact match for static routes
|
|
69
|
+
if (pattern === path) {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Split into parts, filtering empty strings
|
|
74
|
+
const patternParts = pattern.split('/').filter(p => p);
|
|
75
|
+
const pathParts = path.split('/').filter(p => p);
|
|
76
|
+
|
|
77
|
+
// Must have same number of parts
|
|
78
|
+
if (patternParts.length !== pathParts.length) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const params = {};
|
|
83
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
84
|
+
const patternPart = patternParts[i];
|
|
85
|
+
const pathPart = pathParts[i];
|
|
86
|
+
|
|
87
|
+
// Check if this is a parameter (starts with :)
|
|
88
|
+
if (patternPart.startsWith(':')) {
|
|
89
|
+
const paramName = patternPart.slice(1);
|
|
90
|
+
// Decode URL component
|
|
91
|
+
try {
|
|
92
|
+
params[paramName] = decodeURIComponent(pathPart);
|
|
93
|
+
} catch (e) {
|
|
94
|
+
// If decoding fails, use raw value
|
|
95
|
+
params[paramName] = pathPart;
|
|
96
|
+
}
|
|
97
|
+
} else if (patternPart !== pathPart) {
|
|
98
|
+
// Static part doesn't match
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return params;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Find matching route handler
|
|
108
|
+
* @param {string} path - Route path
|
|
109
|
+
* @returns {object|null} { handler, params } or null
|
|
110
|
+
* @private
|
|
111
|
+
*/
|
|
112
|
+
_findRoute(path) {
|
|
113
|
+
// First check for exact static route match (static routes take precedence)
|
|
114
|
+
if (this.routes.has(path)) {
|
|
115
|
+
return {
|
|
116
|
+
handler: this.routes.get(path),
|
|
117
|
+
params: {}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Collect all dynamic routes and sort by specificity
|
|
122
|
+
const dynamicRoutes = [];
|
|
123
|
+
for (const [pattern, handler] of this.routes.entries()) {
|
|
124
|
+
// Skip if it's an exact match (already checked above)
|
|
125
|
+
if (pattern === path) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Check if pattern contains dynamic parameters
|
|
130
|
+
if (pattern.includes(':')) {
|
|
131
|
+
const paramCount = (pattern.match(/:/g) || []).length;
|
|
132
|
+
// Count literal (non-param) parts for better specificity
|
|
133
|
+
const literalCount = pattern.split('/').filter(p => p && !p.startsWith(':')).length;
|
|
134
|
+
dynamicRoutes.push({ pattern, handler, paramCount, literalCount });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Sort by literal count first (more literals = more specific), then by param count
|
|
139
|
+
// This ensures routes with literal parts (like /create) are matched before fully dynamic routes
|
|
140
|
+
dynamicRoutes.sort((a, b) => {
|
|
141
|
+
if (b.literalCount !== a.literalCount) {
|
|
142
|
+
return b.literalCount - a.literalCount;
|
|
143
|
+
}
|
|
144
|
+
return b.paramCount - a.paramCount;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Try each route in order of specificity
|
|
148
|
+
for (const { pattern, handler } of dynamicRoutes) {
|
|
149
|
+
const params = this._matchRoute(pattern, path);
|
|
150
|
+
if (params !== null) {
|
|
151
|
+
return { handler, params };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Handle route change
|
|
160
|
+
* @param {string} path - Route path
|
|
161
|
+
*/
|
|
162
|
+
async handleRoute(path) {
|
|
163
|
+
// Clean up current handler if it exists
|
|
164
|
+
if (this.currentHandler && typeof this.currentHandler.cleanup === 'function') {
|
|
165
|
+
this.currentHandler.cleanup();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Find matching route
|
|
169
|
+
const match = this._findRoute(path);
|
|
170
|
+
|
|
171
|
+
if (match) {
|
|
172
|
+
this.currentRoute = path;
|
|
173
|
+
// Call handler with params and store the result (which may include cleanup function)
|
|
174
|
+
// Handle both sync and async handlers
|
|
175
|
+
const result = await Promise.resolve(match.handler(match.params));
|
|
176
|
+
this.currentHandler = result || null;
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
} else if (this.notFoundHandler) {
|
|
180
|
+
this.currentRoute = null;
|
|
181
|
+
this.currentHandler = null;
|
|
182
|
+
this.notFoundHandler(path);
|
|
183
|
+
} else {
|
|
184
|
+
console.warn(`No route handler for: ${path}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Start the router
|
|
190
|
+
*/
|
|
191
|
+
async start() {
|
|
192
|
+
// Handle initial route
|
|
193
|
+
await this.handleRoute(window.location.pathname);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get current route
|
|
198
|
+
*/
|
|
199
|
+
getCurrentRoute() {
|
|
200
|
+
return this.currentRoute || window.location.pathname;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Encode a title for use in URL (slug)
|
|
205
|
+
* @param {string} title - Title to encode
|
|
206
|
+
* @returns {string} URL-safe slug
|
|
207
|
+
*/
|
|
208
|
+
_encodeTitle(title) {
|
|
209
|
+
if (!title) return '';
|
|
210
|
+
return title
|
|
211
|
+
.toLowerCase()
|
|
212
|
+
.trim()
|
|
213
|
+
.replace(/[^a-z0-9]+/g, '-') // Replace non-alphanumeric with hyphens
|
|
214
|
+
.replace(/^-+|-+$/g, ''); // Remove leading/trailing hyphens
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Decode a URL slug back to title (approximate)
|
|
219
|
+
* @param {string} slug - URL slug
|
|
220
|
+
* @returns {string} Decoded title
|
|
221
|
+
*/
|
|
222
|
+
_decodeTitle(slug) {
|
|
223
|
+
if (!slug) return '';
|
|
224
|
+
return slug
|
|
225
|
+
.split('-')
|
|
226
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
227
|
+
.join(' ');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Generate URL from chain ID, factory title, instance name, and optional piece title
|
|
232
|
+
* @param {string|number} chainId - Chain ID (e.g., 1 for Ethereum mainnet)
|
|
233
|
+
* @param {string} factoryTitle - Factory title
|
|
234
|
+
* @param {string} instanceName - Instance name
|
|
235
|
+
* @param {string} [pieceTitle] - Optional piece title (for ERC1155)
|
|
236
|
+
* @returns {string} URL path
|
|
237
|
+
*/
|
|
238
|
+
generateURL(chainId, factoryTitle, instanceName, pieceTitle = null) {
|
|
239
|
+
const chainIdStr = String(chainId || '1'); // Default to 1 (Ethereum mainnet)
|
|
240
|
+
const factorySlug = this._encodeTitle(factoryTitle);
|
|
241
|
+
const instanceSlug = this._encodeTitle(instanceName);
|
|
242
|
+
|
|
243
|
+
if (pieceTitle) {
|
|
244
|
+
const pieceSlug = this._encodeTitle(pieceTitle);
|
|
245
|
+
return `/${chainIdStr}/${factorySlug}/${instanceSlug}/${pieceSlug}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return `/${chainIdStr}/${factorySlug}/${instanceSlug}`;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export default Router;
|
|
253
|
+
|