hyperclayjs 1.7.0 → 1.9.0

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.
Files changed (67) hide show
  1. package/README.md +25 -21
  2. package/package.json +17 -25
  3. package/src/communication/live-sync.js +396 -0
  4. package/{communication → src/communication}/sendMessage.js +2 -8
  5. package/src/core/adminContenteditable.js +51 -0
  6. package/{core → src/core}/adminInputs.js +29 -8
  7. package/src/core/adminOnClick.js +54 -0
  8. package/{core → src/core}/adminResources.js +25 -5
  9. package/{core → src/core}/autosave.js +6 -17
  10. package/src/core/enablePersistentFormInputValues.js +67 -0
  11. package/{core → src/core}/optionVisibility.js +7 -35
  12. package/{core → src/core}/savePage.js +1 -1
  13. package/src/core/savePageCore.js +256 -0
  14. package/src/core/snapshot.js +203 -0
  15. package/src/core/unsavedWarning.js +26 -0
  16. package/{custom-attributes → src/custom-attributes}/ajaxElements.js +3 -10
  17. package/{custom-attributes → src/custom-attributes}/domHelpers.js +17 -4
  18. package/{custom-attributes → src/custom-attributes}/events.js +2 -0
  19. package/{custom-attributes → src/custom-attributes}/onaftersave.js +5 -8
  20. package/src/custom-attributes/onmutation.js +90 -0
  21. package/src/custom-attributes/onpagemutation.js +32 -0
  22. package/{custom-attributes → src/custom-attributes}/sortable.js +16 -1
  23. package/{dom-utilities → src/dom-utilities}/All.js +22 -0
  24. package/src/dom-utilities/insertStyleTag.js +61 -0
  25. package/{hyperclay.js → src/hyperclay.js} +20 -3
  26. package/{module-dependency-graph.json → src/module-dependency-graph.json} +121 -34
  27. package/{ui → src/ui}/prompts.js +13 -18
  28. package/{ui → src/ui}/theModal.js +103 -0
  29. package/{ui → src/ui}/toast.js +4 -3
  30. package/src/utilities/cacheBust.js +19 -0
  31. package/{vendor → src/vendor}/idiomorph.min.js +1 -0
  32. package/core/adminContenteditable.js +0 -36
  33. package/core/adminOnClick.js +0 -31
  34. package/core/enablePersistentFormInputValues.js +0 -72
  35. package/core/savePageCore.js +0 -245
  36. package/custom-attributes/onpagemutation.js +0 -20
  37. package/dom-utilities/insertStyleTag.js +0 -38
  38. /package/{communication → src/communication}/behaviorCollector.js +0 -0
  39. /package/{communication → src/communication}/uploadFile.js +0 -0
  40. /package/{core → src/core}/adminSystem.js +0 -0
  41. /package/{core → src/core}/editmode.js +0 -0
  42. /package/{core → src/core}/editmodeSystem.js +0 -0
  43. /package/{core → src/core}/exportToWindow.js +0 -0
  44. /package/{core → src/core}/isAdminOfCurrentResource.js +0 -0
  45. /package/{core → src/core}/saveToast.js +0 -0
  46. /package/{core → src/core}/setPageTypeOnDocumentElement.js +0 -0
  47. /package/{custom-attributes → src/custom-attributes}/autosize.js +0 -0
  48. /package/{custom-attributes → src/custom-attributes}/inputHelpers.js +0 -0
  49. /package/{custom-attributes → src/custom-attributes}/onclickaway.js +0 -0
  50. /package/{custom-attributes → src/custom-attributes}/onclone.js +0 -0
  51. /package/{custom-attributes → src/custom-attributes}/onrender.js +0 -0
  52. /package/{custom-attributes → src/custom-attributes}/preventEnter.js +0 -0
  53. /package/{dom-utilities → src/dom-utilities}/getDataFromForm.js +0 -0
  54. /package/{dom-utilities → src/dom-utilities}/onDomReady.js +0 -0
  55. /package/{dom-utilities → src/dom-utilities}/onLoad.js +0 -0
  56. /package/{string-utilities → src/string-utilities}/copy-to-clipboard.js +0 -0
  57. /package/{string-utilities → src/string-utilities}/query.js +0 -0
  58. /package/{string-utilities → src/string-utilities}/slugify.js +0 -0
  59. /package/{ui → src/ui}/toast-hyperclay.js +0 -0
  60. /package/{utilities → src/utilities}/cookie.js +0 -0
  61. /package/{utilities → src/utilities}/debounce.js +0 -0
  62. /package/{utilities → src/utilities}/loadVendorScript.js +0 -0
  63. /package/{utilities → src/utilities}/mutation.js +0 -0
  64. /package/{utilities → src/utilities}/nearest.js +0 -0
  65. /package/{utilities → src/utilities}/pipe.js +0 -0
  66. /package/{utilities → src/utilities}/throttle.js +0 -0
  67. /package/{vendor → src/vendor}/Sortable.vendor.js +0 -0
@@ -429,6 +429,7 @@ const modalCss = `<style class="micromodal-css">
429
429
  align-items: center;
430
430
  width: 100%;
431
431
  height: 39px;
432
+ line-height: 0;
432
433
  border: 3px solid;
433
434
  border-top-color: #94BA6F;
434
435
  border-left-color: #94BA6F;
@@ -449,6 +450,66 @@ const modalCss = `<style class="micromodal-css">
449
450
  border-right-color: #94BA6F;
450
451
  }
451
452
 
453
+ .micromodal button.micromodal__secondary-btn {
454
+ display: flex;
455
+ justify-content: center;
456
+ align-items: center;
457
+ width: 100%;
458
+ height: 39px;
459
+ line-height: 0;
460
+ border: 3px solid;
461
+ border-top-color: #474C65;
462
+ border-left-color: #474C65;
463
+ border-bottom-color: #131725;
464
+ border-right-color: #131725;
465
+ background-color: #1D1F2F;
466
+ color: #E5E7EB;
467
+ font-family: inherit;
468
+ font-size: 16px;
469
+ font-weight: bold;
470
+ }
471
+
472
+ .micromodal button.micromodal__secondary-btn:focus,
473
+ .micromodal button.micromodal__secondary-btn:hover {
474
+ background-color: #232639;
475
+ }
476
+
477
+ .micromodal button.micromodal__secondary-btn:active {
478
+ border-top-color: #131725;
479
+ border-left-color: #131725;
480
+ border-bottom-color: #474C65;
481
+ border-right-color: #474C65;
482
+ }
483
+
484
+ .micromodal:has(.snippet-code-block) .micromodal__content {
485
+ margin-bottom: 0;
486
+ }
487
+
488
+ .micromodal .snippet-code-block {
489
+ background-color: #292E54;
490
+ padding: 1rem;
491
+ margin-bottom: 14px;
492
+ max-width: 420px;
493
+ overflow-x: auto;
494
+ }
495
+
496
+ .micromodal .snippet-code-block pre {
497
+ color: white;
498
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
499
+ font-size: 0.875rem;
500
+ white-space: nowrap;
501
+ margin: 0;
502
+ }
503
+
504
+ .micromodal .snippet-warning {
505
+ padding: 0.75rem;
506
+ border: 2px solid #989742;
507
+ background-color: #1E1E11;
508
+ font-size: 0.875rem;
509
+ color: #FBF7B7;
510
+ max-width: 420px;
511
+ }
512
+
452
513
  .micromodal button.micromodal__close {
453
514
  clip-path: polygon(0% 4%, 0% 0%, 100% 0%, 100% 100%, 94% 100%);
454
515
  position: absolute;
@@ -456,6 +517,46 @@ const modalCss = `<style class="micromodal-css">
456
517
  right: -1px;
457
518
  width: 68px;
458
519
  }
520
+
521
+ .micromodal .micromodal__close-bg {
522
+ fill: #1D2032;
523
+ }
524
+
525
+ .micromodal .micromodal__close:hover .micromodal__close-bg {
526
+ fill: #212543;
527
+ }
528
+
529
+ .micromodal .micromodal__close-x {
530
+ fill: #fff;
531
+ }
532
+
533
+ .micromodal .micromodal__tell {
534
+ max-width: 440px;
535
+ margin-bottom: 28px;
536
+ }
537
+
538
+ .micromodal .micromodal__tell > * + * {
539
+ margin-top: 20px;
540
+ }
541
+
542
+ .micromodal .micromodal__tell-title {
543
+ font-size: 20px;
544
+ font-weight: bold;
545
+ }
546
+
547
+ .micromodal .micromodal__tell-content {
548
+ font-size: 16px;
549
+ font-weight: normal;
550
+ }
551
+
552
+ @media (min-width: 640px) {
553
+ .micromodal .micromodal__tell-title {
554
+ font-size: 22px;
555
+ }
556
+ .micromodal .micromodal__tell-content {
557
+ font-size: 18px;
558
+ }
559
+ }
459
560
  </style>`;
460
561
 
461
562
  const modalHtml = `<div class="micromodal" id="micromodal" aria-hidden="true">
@@ -625,6 +726,8 @@ const themodal = (() => {
625
726
 
626
727
  this.isShowing = true;
627
728
 
729
+ onOpen.forEach(cb => cb());
730
+
628
731
  if (!disableFocus) {
629
732
  let firstInput = modalOverlayElem.querySelector(".micromodal__content :is(input,textarea,button):not(.micromodal__hide), .micromodal__buttons :is(input,textarea,button):not(.micromodal__hide)");
630
733
  firstInput?.focus();
@@ -1,10 +1,10 @@
1
1
  // a nice, simple alert
2
2
  // ❗️ don't use too much text!
3
3
 
4
- // Default modern icons
4
+ // Default modern icons (normalized to 48x48 viewBox)
5
5
  const defaultIcons = {
6
- success: `<svg viewBox="0 0 48 45" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13.9404 22.4475L21.9099 29.724L35.1906 14.4045M3 3H44.9804V42.809H3V3Z" stroke="#33D131" stroke-width="4.3"/></svg>`,
7
- error: `<svg viewBox="0 0 46 44" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M31.7383 12.4045L13 31.1429M31.7451 31.1429L13.0068 12.4046M2.00977 2H43.9902V41.809H2.00977V2Z" stroke="#FF4450" stroke-width="4"/></svg>`
6
+ success: `<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13.9404 23.9475L21.9099 31.224L35.1906 15.9045M3 4.5H44.9804V44.309H3V4.5Z" stroke="#33D131" stroke-width="4.3"/></svg>`,
7
+ error: `<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M32.7383 14.4045L14 33.1429M32.7451 33.1429L14.0068 14.4046M3.01 4H44.99V43.809H3.01V4Z" stroke="#FF4450" stroke-width="4"/></svg>`
8
8
  };
9
9
 
10
10
  // Hyperclay icons
@@ -100,6 +100,7 @@ const modernStyles = `
100
100
  }
101
101
 
102
102
  [data-toast-theme="modern"] .toast-icon svg {
103
+ display: block;
103
104
  width: 22px;
104
105
  height: 22px;
105
106
  }
@@ -0,0 +1,19 @@
1
+ // cacheBust.js
2
+ // Cache-bust an element's href or src attribute by adding/updating a version query param
3
+
4
+ function cacheBust(el) {
5
+ const attr = el.href !== undefined ? 'href' : 'src';
6
+ const url = new URL(el[attr], location.href);
7
+ url.searchParams.set('v', Date.now());
8
+ el[attr] = url.href;
9
+ }
10
+
11
+ // Auto-export to window unless suppressed by loader
12
+ if (!window.__hyperclayNoAutoExport) {
13
+ window.cacheBust = cacheBust;
14
+ window.hyperclay = window.hyperclay || {};
15
+ window.hyperclay.cacheBust = cacheBust;
16
+ window.h = window.hyperclay;
17
+ }
18
+
19
+ export default cacheBust;
@@ -12,6 +12,7 @@ if (!window.__hyperclayNoAutoExport) {
12
12
  window.hyperclay = window.hyperclay || {};
13
13
  window.hyperclay.Idiomorph = Idiomorph;
14
14
  window.hyperclay.morph = morph;
15
+ window.morph = morph;
15
16
  window.h = window.hyperclay;
16
17
  }
17
18
 
@@ -1,36 +0,0 @@
1
- import { isEditMode, isOwner } from "./isAdminOfCurrentResource.js";
2
- import onDomReady from "../dom-utilities/onDomReady.js";
3
- import {beforeSave} from "./savePage.js";
4
-
5
- export function disableContentEditableBeforeSave () {
6
- beforeSave(docElem => {
7
- docElem.querySelectorAll('[edit-mode-contenteditable]').forEach(resource => {
8
- const originalValue = resource.getAttribute("contenteditable");
9
- resource.setAttribute("inert-contenteditable", originalValue);
10
- resource.removeAttribute("contenteditable");
11
- });
12
- });
13
- }
14
-
15
- export function enableContentEditableForAdminOnPageLoad () {
16
- if (!isEditMode) return;
17
-
18
- onDomReady(() => {
19
- document.querySelectorAll('[edit-mode-contenteditable]').forEach(resource => {
20
- let originalValue = resource.getAttribute("inert-contenteditable");
21
-
22
- if (!["false", "plaintext-only"].includes(originalValue)) {
23
- originalValue = "true";
24
- }
25
-
26
- resource.setAttribute("contenteditable", originalValue);
27
- resource.removeAttribute("inert-contenteditable");
28
- });
29
- });
30
- }
31
-
32
- // Auto-initialize
33
- export function init() {
34
- disableContentEditableBeforeSave();
35
- enableContentEditableForAdminOnPageLoad();
36
- }
@@ -1,31 +0,0 @@
1
- import { isEditMode, isOwner } from "./isAdminOfCurrentResource.js";
2
- import onDomReady from "../dom-utilities/onDomReady.js";
3
- import {beforeSave} from "./savePage.js";
4
-
5
- export function disableOnClickBeforeSave () {
6
- beforeSave(docElem => {
7
- docElem.querySelectorAll('[edit-mode-onclick]').forEach(resource => {
8
- const originalValue = resource.getAttribute("onclick");
9
- resource.setAttribute("inert-onclick", originalValue);
10
- resource.removeAttribute("onclick");
11
- });
12
- });
13
- }
14
-
15
- export function enableOnClickForAdminOnPageLoad () {
16
- if (!isEditMode) return;
17
-
18
- onDomReady(() => {
19
- document.querySelectorAll('[edit-mode-onclick]').forEach(resource => {
20
- const originalValue = resource.getAttribute("inert-onclick");
21
- resource.setAttribute("onclick", originalValue);
22
- resource.removeAttribute("inert-onclick");
23
- });
24
- });
25
- }
26
-
27
- // Auto-initialize
28
- export function init() {
29
- disableOnClickBeforeSave();
30
- enableOnClickForAdminOnPageLoad();
31
- }
@@ -1,72 +0,0 @@
1
- import { beforeSave } from './savePage.js';
2
-
3
- // <input type="checkbox" persist>
4
- export default function enablePersistentFormInputValues(filterBySelector = "[persist]") {
5
- const selector = `input${filterBySelector}:not([type="password"]):not([type="hidden"]):not([type="file"]), textarea${filterBySelector}, select${filterBySelector}`;
6
-
7
- document.addEventListener('input', (event) => {
8
- const elem = event.target;
9
- if (elem.matches(selector) && !(elem.type === 'checkbox' || elem.type === 'radio')) {
10
- if (elem.tagName.toLowerCase() === 'textarea') {
11
- // Store in value attribute instead of textContent to preserve cursor position
12
- elem.setAttribute('value', elem.value);
13
- } else {
14
- elem.setAttribute('value', elem.value);
15
- }
16
- }
17
- });
18
-
19
- document.addEventListener('change', (event) => {
20
- const elem = event.target;
21
- if (elem.matches(selector)) {
22
- // Handle checkboxes and radios
23
- if (elem.type === 'checkbox' || elem.type === 'radio') {
24
- if (elem.checked) {
25
- elem.setAttribute('checked', '');
26
- } else {
27
- elem.removeAttribute('checked');
28
- }
29
- }
30
- // Handle select elements
31
- else if (elem.tagName.toLowerCase() === 'select') {
32
- // Remove selected from all options
33
- const options = elem.querySelectorAll('option');
34
- options.forEach(option => option.removeAttribute('selected'));
35
-
36
- // Add selected to currently selected option(s)
37
- if (elem.multiple) {
38
- const selectedOptions = elem.selectedOptions;
39
- Array.from(selectedOptions).forEach(option => {
40
- option.setAttribute('selected', '');
41
- });
42
- } else if (elem.selectedIndex >= 0) {
43
- options[elem.selectedIndex].setAttribute('selected', '');
44
- }
45
- }
46
- }
47
- });
48
-
49
- // Before save, transfer value attribute to textContent for textareas
50
- beforeSave((doc) => {
51
- const textareas = doc.querySelectorAll('textarea[value]');
52
- textareas.forEach(textarea => {
53
- textarea.textContent = textarea.getAttribute('value');
54
- textarea.removeAttribute('value');
55
- });
56
- });
57
- }
58
-
59
- // Auto-initialize with default selector
60
- export function init() {
61
- enablePersistentFormInputValues("[persist]");
62
- }
63
-
64
- // Auto-export to window unless suppressed by loader
65
- if (!window.__hyperclayNoAutoExport) {
66
- window.hyperclay = window.hyperclay || {};
67
- window.hyperclay.enablePersistentFormInputValues = enablePersistentFormInputValues;
68
- window.h = window.hyperclay;
69
- }
70
-
71
- // Auto-init when module is imported
72
- init();
@@ -1,245 +0,0 @@
1
- /**
2
- * Core save functionality for Hyperclay
3
- *
4
- * This is the minimal save system - just the basic save function you can call yourself.
5
- * No toast notifications, no auto-save, no keyboard shortcuts.
6
- *
7
- * Use this if you want full control over save behavior and notifications.
8
- * For the full save system with conveniences, use savePage.js instead.
9
- */
10
-
11
- import cookie from "../utilities/cookie.js";
12
- import { isEditMode } from "./isAdminOfCurrentResource.js";
13
-
14
- let beforeSaveCallbacks = [];
15
- let saveInProgress = false;
16
- const saveEndpoint = `/save/${cookie.get("currentResource")}`;
17
-
18
- /**
19
- * Register a callback to run before saving
20
- * Callbacks receive the cloned document element
21
- *
22
- * @param {Function} cb - Callback function(docElem)
23
- */
24
- export function beforeSave(cb) {
25
- beforeSaveCallbacks.push(cb);
26
- }
27
-
28
- /**
29
- * Get the current page contents as HTML
30
- * Handles CodeMirror pages, runs [onbeforesave] attributes, removes [save-ignore] elements
31
- *
32
- * @returns {string} HTML string of current page
33
- */
34
- export function getPageContents() {
35
- const isCodeMirrorPage = !!document.querySelector('.CodeMirror')?.CodeMirror;
36
-
37
- if (!isCodeMirrorPage) {
38
- let docElem = document.documentElement.cloneNode(true);
39
-
40
- // Run onbeforesave callbacks
41
- docElem.querySelectorAll('[onbeforesave]').forEach(el =>
42
- new Function(el.getAttribute('onbeforesave')).call(el)
43
- );
44
-
45
- // Remove elements marked save-ignore
46
- docElem.querySelectorAll('[save-ignore]').forEach(el =>
47
- el.remove()
48
- );
49
-
50
- // Run registered beforeSave callbacks
51
- beforeSaveCallbacks.forEach(cb => cb(docElem));
52
-
53
- return "<!DOCTYPE html>" + docElem.outerHTML;
54
- } else {
55
- // For CodeMirror pages, get value from editor
56
- return document.querySelector('.CodeMirror').CodeMirror.getValue();
57
- }
58
- }
59
-
60
- /**
61
- * Save the current page contents to the server
62
- *
63
- * @param {Function} callback - Called with {msg, msgType} on completion
64
- * msgType will be 'success' or 'error'
65
- *
66
- * @example
67
- * savePage(({msg, msgType}) => {
68
- * if (msgType === 'error') {
69
- * console.error('Save failed:', msg);
70
- * } else {
71
- * console.log('Saved:', msg);
72
- * }
73
- * });
74
- */
75
- export function savePage(callback = () => {}) {
76
- if (!isEditMode || saveInProgress) {
77
- return;
78
- }
79
-
80
- const currentContents = getPageContents();
81
- saveInProgress = true;
82
-
83
- // Test mode: skip network request, return mock success
84
- if (window.hyperclay?.testMode) {
85
- setTimeout(() => {
86
- saveInProgress = false;
87
- if (typeof callback === 'function') {
88
- callback({msg: "Test mode: save skipped", msgType: "success"});
89
- }
90
- }, 0);
91
- return;
92
- }
93
-
94
- // Add timeout - abort if server doesn't respond within 5 seconds
95
- const controller = new AbortController();
96
- const timeoutId = setTimeout(() => controller.abort(), 5000);
97
-
98
- fetch(saveEndpoint, {
99
- method: 'POST',
100
- credentials: 'include',
101
- body: currentContents,
102
- signal: controller.signal
103
- })
104
- .then(res => {
105
- clearTimeout(timeoutId);
106
- return res.json().then(data => {
107
- if (!res.ok) {
108
- throw new Error(data.msg || data.error || `HTTP ${res.status}: ${res.statusText}`);
109
- }
110
- return data;
111
- });
112
- })
113
- .then(data => {
114
- if (typeof callback === 'function') {
115
- callback({msg: data.msg, msgType: data.msgType || 'success'});
116
- }
117
- })
118
- .catch(err => {
119
- clearTimeout(timeoutId);
120
- console.error('Failed to save page:', err);
121
-
122
- const msg = err.name === 'AbortError'
123
- ? 'Server not responding'
124
- : 'Save failed';
125
-
126
- if (typeof callback === 'function') {
127
- callback({msg, msgType: "error"});
128
- }
129
- })
130
- .finally(() => {
131
- clearTimeout(timeoutId);
132
- saveInProgress = false;
133
- });
134
- }
135
-
136
- /**
137
- * Save specific HTML content to the server
138
- *
139
- * @param {string} html - HTML string to save
140
- * @param {Function} callback - Called with (err, data) on completion
141
- * err will be null on success, Error object on failure
142
- *
143
- * @example
144
- * saveHtml(myHtml, (err, data) => {
145
- * if (err) {
146
- * console.error('Save failed:', err);
147
- * } else {
148
- * console.log('Saved:', data);
149
- * }
150
- * });
151
- */
152
- export function saveHtml(html, callback = () => {}) {
153
- if (!isEditMode || saveInProgress) {
154
- return;
155
- }
156
-
157
- saveInProgress = true;
158
-
159
- // Test mode: skip network request, return mock success
160
- if (window.hyperclay?.testMode) {
161
- setTimeout(() => {
162
- saveInProgress = false;
163
- if (typeof callback === 'function') {
164
- callback(null, {msg: "Test mode: save skipped", msgType: "success"});
165
- }
166
- }, 0);
167
- return;
168
- }
169
-
170
- fetch(saveEndpoint, {
171
- method: 'POST',
172
- credentials: 'include',
173
- body: html
174
- })
175
- .then(res => {
176
- return res.json().then(data => {
177
- if (!res.ok) {
178
- throw new Error(data.msg || data.error || `HTTP ${res.status}: ${res.statusText}`);
179
- }
180
- return data;
181
- });
182
- })
183
- .then(data => {
184
- if (typeof callback === 'function') {
185
- callback(null, data); // Success: no error
186
- }
187
- })
188
- .catch(err => {
189
- console.error('Failed to save page:', err);
190
- if (typeof callback === 'function') {
191
- callback(err); // Pass error
192
- }
193
- })
194
- .finally(() => {
195
- saveInProgress = false;
196
- });
197
- }
198
-
199
- /**
200
- * Fetch HTML from a URL and save it to replace the current page
201
- *
202
- * @param {string} url - URL to fetch HTML from
203
- * @param {Function} callback - Called with (err, data) on completion
204
- *
205
- * @example
206
- * replacePageWith('/templates/blog.html', (err, data) => {
207
- * if (err) {
208
- * console.error('Failed:', err);
209
- * } else {
210
- * window.location.reload();
211
- * }
212
- * });
213
- */
214
- export function replacePageWith(url, callback = () => {}) {
215
- if (!isEditMode || saveInProgress) {
216
- return;
217
- }
218
-
219
- fetch(url)
220
- .then(res => res.text())
221
- .then(html => {
222
- saveHtml(html, (err, data) => {
223
- if (typeof callback === 'function') {
224
- callback(err, data);
225
- }
226
- });
227
- })
228
- .catch(err => {
229
- console.error('Failed to fetch template:', err);
230
- if (typeof callback === 'function') {
231
- callback(err);
232
- }
233
- });
234
- }
235
-
236
- // Auto-export to window unless suppressed by loader
237
- if (!window.__hyperclayNoAutoExport) {
238
- window.hyperclay = window.hyperclay || {};
239
- window.hyperclay.savePage = savePage;
240
- window.hyperclay.saveHtml = saveHtml;
241
- window.hyperclay.replacePageWith = replacePageWith;
242
- window.hyperclay.beforeSave = beforeSave;
243
- window.hyperclay.getPageContents = getPageContents;
244
- window.h = window.hyperclay;
245
- }
@@ -1,20 +0,0 @@
1
- import Mutation from "../utilities/mutation.js";
2
-
3
- function init() {
4
- const executePageMutation = async element => {
5
- try {
6
- const code = element.getAttribute('onpagemutation');
7
- const asyncFn = new Function(`return (async function() { ${code} })`)();
8
- await asyncFn.call(element);
9
- } catch (error) {
10
- console.error('Error in onpagemutation execution:', error);
11
- }
12
- };
13
-
14
- Mutation.onAnyChange({
15
- debounce: 200,
16
- omitChangeDetails: true
17
- }, () => document.querySelectorAll('[onpagemutation]').forEach(executePageMutation));
18
- }
19
- export { init };
20
- export default init;
@@ -1,38 +0,0 @@
1
- function insertStyleTag(href) {
2
- // First check if there's already a link with this exact href
3
- if (document.querySelector(`link[href="${href}"]`)) {
4
- return;
5
- }
6
-
7
- // Extract a more reliable identifier from the URL
8
- let identifier;
9
- try {
10
- const url = new URL(href, window.location.href);
11
- // Get the filename from the path
12
- identifier = url.pathname.split('/').pop();
13
- } catch (e) {
14
- // Fallback to using the href itself if URL parsing fails
15
- identifier = href;
16
- }
17
-
18
- // Look for any link that contains this identifier
19
- if (identifier && document.querySelector(`link[href*="${identifier}"]`)) {
20
- return;
21
- }
22
-
23
- // If no duplicate found, add the stylesheet
24
- const link = document.createElement('link');
25
- link.rel = 'stylesheet';
26
- link.href = href;
27
- document.head.appendChild(link);
28
- }
29
-
30
- // Auto-export to window unless suppressed by loader
31
- if (!window.__hyperclayNoAutoExport) {
32
- window.insertStyleTag = insertStyleTag;
33
- window.hyperclay = window.hyperclay || {};
34
- window.hyperclay.insertStyleTag = insertStyleTag;
35
- window.h = window.hyperclay;
36
- }
37
-
38
- export default insertStyleTag;
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes