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.
- package/README.md +25 -21
- package/package.json +17 -25
- package/src/communication/live-sync.js +396 -0
- package/{communication → src/communication}/sendMessage.js +2 -8
- package/src/core/adminContenteditable.js +51 -0
- package/{core → src/core}/adminInputs.js +29 -8
- package/src/core/adminOnClick.js +54 -0
- package/{core → src/core}/adminResources.js +25 -5
- package/{core → src/core}/autosave.js +6 -17
- package/src/core/enablePersistentFormInputValues.js +67 -0
- package/{core → src/core}/optionVisibility.js +7 -35
- package/{core → src/core}/savePage.js +1 -1
- package/src/core/savePageCore.js +256 -0
- package/src/core/snapshot.js +203 -0
- package/src/core/unsavedWarning.js +26 -0
- package/{custom-attributes → src/custom-attributes}/ajaxElements.js +3 -10
- package/{custom-attributes → src/custom-attributes}/domHelpers.js +17 -4
- package/{custom-attributes → src/custom-attributes}/events.js +2 -0
- package/{custom-attributes → src/custom-attributes}/onaftersave.js +5 -8
- package/src/custom-attributes/onmutation.js +90 -0
- package/src/custom-attributes/onpagemutation.js +32 -0
- package/{custom-attributes → src/custom-attributes}/sortable.js +16 -1
- package/{dom-utilities → src/dom-utilities}/All.js +22 -0
- package/src/dom-utilities/insertStyleTag.js +61 -0
- package/{hyperclay.js → src/hyperclay.js} +20 -3
- package/{module-dependency-graph.json → src/module-dependency-graph.json} +121 -34
- package/{ui → src/ui}/prompts.js +13 -18
- package/{ui → src/ui}/theModal.js +103 -0
- package/{ui → src/ui}/toast.js +4 -3
- package/src/utilities/cacheBust.js +19 -0
- package/{vendor → src/vendor}/idiomorph.min.js +1 -0
- package/core/adminContenteditable.js +0 -36
- package/core/adminOnClick.js +0 -31
- package/core/enablePersistentFormInputValues.js +0 -72
- package/core/savePageCore.js +0 -245
- package/custom-attributes/onpagemutation.js +0 -20
- package/dom-utilities/insertStyleTag.js +0 -38
- /package/{communication → src/communication}/behaviorCollector.js +0 -0
- /package/{communication → src/communication}/uploadFile.js +0 -0
- /package/{core → src/core}/adminSystem.js +0 -0
- /package/{core → src/core}/editmode.js +0 -0
- /package/{core → src/core}/editmodeSystem.js +0 -0
- /package/{core → src/core}/exportToWindow.js +0 -0
- /package/{core → src/core}/isAdminOfCurrentResource.js +0 -0
- /package/{core → src/core}/saveToast.js +0 -0
- /package/{core → src/core}/setPageTypeOnDocumentElement.js +0 -0
- /package/{custom-attributes → src/custom-attributes}/autosize.js +0 -0
- /package/{custom-attributes → src/custom-attributes}/inputHelpers.js +0 -0
- /package/{custom-attributes → src/custom-attributes}/onclickaway.js +0 -0
- /package/{custom-attributes → src/custom-attributes}/onclone.js +0 -0
- /package/{custom-attributes → src/custom-attributes}/onrender.js +0 -0
- /package/{custom-attributes → src/custom-attributes}/preventEnter.js +0 -0
- /package/{dom-utilities → src/dom-utilities}/getDataFromForm.js +0 -0
- /package/{dom-utilities → src/dom-utilities}/onDomReady.js +0 -0
- /package/{dom-utilities → src/dom-utilities}/onLoad.js +0 -0
- /package/{string-utilities → src/string-utilities}/copy-to-clipboard.js +0 -0
- /package/{string-utilities → src/string-utilities}/query.js +0 -0
- /package/{string-utilities → src/string-utilities}/slugify.js +0 -0
- /package/{ui → src/ui}/toast-hyperclay.js +0 -0
- /package/{utilities → src/utilities}/cookie.js +0 -0
- /package/{utilities → src/utilities}/debounce.js +0 -0
- /package/{utilities → src/utilities}/loadVendorScript.js +0 -0
- /package/{utilities → src/utilities}/mutation.js +0 -0
- /package/{utilities → src/utilities}/nearest.js +0 -0
- /package/{utilities → src/utilities}/pipe.js +0 -0
- /package/{utilities → src/utilities}/throttle.js +0 -0
- /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();
|
package/{ui → src/ui}/toast.js
RENAMED
|
@@ -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
|
|
7
|
-
error: `<svg viewBox="0 0
|
|
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;
|
|
@@ -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
|
-
}
|
package/core/adminOnClick.js
DELETED
|
@@ -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();
|
package/core/savePageCore.js
DELETED
|
@@ -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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|