@webround/editor 0.0.0-stage → 0.3.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/dist/blocks.d.ts +90 -0
- package/dist/blocks.js +119 -0
- package/dist/bridge.d.ts +96 -0
- package/dist/bridge.js +970 -0
- package/dist/check/aria.d.ts +19 -0
- package/dist/check/aria.js +106 -0
- package/dist/check/cli.d.ts +3 -0
- package/dist/check/cli.js +30 -0
- package/dist/check/css.d.ts +13 -0
- package/dist/check/css.js +63 -0
- package/dist/check/html.d.ts +8 -0
- package/dist/check/html.js +29 -0
- package/dist/check/index.d.ts +4 -0
- package/dist/check/index.js +5 -0
- package/dist/check/links.d.ts +7 -0
- package/dist/check/links.js +23 -0
- package/dist/check/orphans.d.ts +20 -0
- package/dist/check/orphans.js +57 -0
- package/dist/check/producer-code.d.ts +53 -0
- package/dist/check/producer-code.js +379 -0
- package/dist/check/producer-dom.d.ts +21 -0
- package/dist/check/producer-dom.js +52 -0
- package/dist/check/project.d.ts +20 -0
- package/dist/check/project.js +51 -0
- package/dist/check/rules.d.ts +7 -0
- package/dist/check/rules.js +517 -0
- package/dist/check/types.d.ts +160 -0
- package/dist/check/types.js +22 -0
- package/dist/components/WrAltcha.d.ts +5 -0
- package/dist/components/WrAltcha.js +8 -0
- package/dist/components/WrButton.d.ts +8 -0
- package/dist/components/WrButton.js +34 -0
- package/dist/components/WrForm.d.ts +6 -0
- package/dist/components/WrForm.js +29 -0
- package/dist/components/WrFormPrivacy.d.ts +4 -0
- package/dist/components/WrFormPrivacy.js +7 -0
- package/dist/components/WrFormSuccess.d.ts +4 -0
- package/dist/components/WrFormSuccess.js +12 -0
- package/dist/components/WrIframe.d.ts +12 -0
- package/dist/components/WrIframe.js +30 -0
- package/dist/components/WrImage.d.ts +9 -0
- package/dist/components/WrImage.js +28 -0
- package/dist/components/WrInput.d.ts +8 -0
- package/dist/components/WrInput.js +29 -0
- package/dist/components/WrLink.d.ts +10 -0
- package/dist/components/WrLink.js +38 -0
- package/dist/components/WrNav.d.ts +26 -0
- package/dist/components/WrNav.js +30 -0
- package/dist/components/WrSection.d.ts +11 -0
- package/dist/components/WrSection.js +5 -0
- package/dist/components/WrShared.d.ts +12 -0
- package/dist/components/WrShared.js +20 -0
- package/dist/components/WrText.d.ts +11 -0
- package/dist/components/WrText.js +41 -0
- package/dist/components/WrVideo.d.ts +9 -0
- package/dist/components/WrVideo.js +73 -0
- package/dist/components/text-editor/CodeThemeContext.d.ts +8 -0
- package/dist/components/text-editor/CodeThemeContext.js +10 -0
- package/dist/components/text-editor/ExampleTheme.d.ts +77 -0
- package/dist/components/text-editor/ExampleTheme.js +76 -0
- package/dist/components/text-editor/ToolbarContext.d.ts +17 -0
- package/dist/components/text-editor/ToolbarContext.js +32 -0
- package/dist/components/text-editor/WrTextEditor.d.ts +13 -0
- package/dist/components/text-editor/WrTextEditor.js +48 -0
- package/dist/components/text-editor/common/constants.d.ts +109 -0
- package/dist/components/text-editor/common/constants.js +58 -0
- package/dist/components/text-editor/common/surface.d.ts +3 -0
- package/dist/components/text-editor/common/surface.js +8 -0
- package/dist/components/text-editor/common/utils.d.ts +7 -0
- package/dist/components/text-editor/common/utils.js +78 -0
- package/dist/components/text-editor/hooks/useLexicalCommitOnBlur.d.ts +5 -0
- package/dist/components/text-editor/hooks/useLexicalCommitOnBlur.js +41 -0
- package/dist/components/text-editor/hooks/useLexicalMarkdown.d.ts +33 -0
- package/dist/components/text-editor/hooks/useLexicalMarkdown.js +284 -0
- package/dist/components/text-editor/nodes/CodeBlockNode.d.ts +23 -0
- package/dist/components/text-editor/nodes/CodeBlockNode.js +138 -0
- package/dist/components/text-editor/nodes/HorizontalRuleNode.d.ts +13 -0
- package/dist/components/text-editor/nodes/HorizontalRuleNode.js +22 -0
- package/dist/components/text-editor/plugins/CodeBlockPlugin.d.ts +2 -0
- package/dist/components/text-editor/plugins/CodeBlockPlugin.js +38 -0
- package/dist/components/text-editor/plugins/FontPlugin.d.ts +12 -0
- package/dist/components/text-editor/plugins/FontPlugin.js +114 -0
- package/dist/components/text-editor/plugins/FontSizePlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/FontSizePlugin.js +105 -0
- package/dist/components/text-editor/plugins/FontWeightPlugin.d.ts +6 -0
- package/dist/components/text-editor/plugins/FontWeightPlugin.js +103 -0
- package/dist/components/text-editor/plugins/HTMLTagPlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/HTMLTagPlugin.js +82 -0
- package/dist/components/text-editor/plugins/HorizontalRulePlugin.d.ts +2 -0
- package/dist/components/text-editor/plugins/HorizontalRulePlugin.js +35 -0
- package/dist/components/text-editor/plugins/LineSpacingPlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/LineSpacingPlugin.js +83 -0
- package/dist/components/text-editor/plugins/LinkPlugin.d.ts +8 -0
- package/dist/components/text-editor/plugins/LinkPlugin.js +183 -0
- package/dist/components/text-editor/plugins/ListToolbarPlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/ListToolbarPlugin.js +40 -0
- package/dist/components/text-editor/plugins/LoadContentPlugin.d.ts +4 -0
- package/dist/components/text-editor/plugins/LoadContentPlugin.js +63 -0
- package/dist/components/text-editor/plugins/MarkdownExportPlugin.d.ts +4 -0
- package/dist/components/text-editor/plugins/MarkdownExportPlugin.js +17 -0
- package/dist/components/text-editor/plugins/MarkdownPastePlugin.d.ts +2 -0
- package/dist/components/text-editor/plugins/MarkdownPastePlugin.js +45 -0
- package/dist/components/text-editor/plugins/SanitizePastePlugin.d.ts +3 -0
- package/dist/components/text-editor/plugins/SanitizePastePlugin.js +40 -0
- package/dist/components/text-editor/plugins/TextAlignPlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/TextAlignPlugin.js +65 -0
- package/dist/components/text-editor/plugins/TextColorPlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/TextColorPlugin.js +117 -0
- package/dist/components/text-editor/plugins/ToolbarPlugin.d.ts +10 -0
- package/dist/components/text-editor/plugins/ToolbarPlugin.js +73 -0
- package/dist/components/text-editor/theme-colors.d.ts +11 -0
- package/dist/components/text-editor/theme-colors.js +28 -0
- package/dist/context/WrAppContext.d.ts +35 -0
- package/dist/context/WrAppContext.js +113 -0
- package/dist/context/WrLayout.d.ts +15 -0
- package/dist/context/WrLayout.js +22 -0
- package/dist/context/WrPageContext.d.ts +28 -0
- package/dist/context/WrPageContext.js +282 -0
- package/dist/context/restoreValues.d.ts +2 -0
- package/dist/context/restoreValues.js +6 -0
- package/dist/contrast.d.ts +45 -0
- package/dist/contrast.js +122 -0
- package/dist/drop.d.ts +7 -0
- package/dist/drop.js +51 -0
- package/dist/gaps.d.ts +14 -0
- package/dist/gaps.js +50 -0
- package/dist/hooks/useWrMessage.d.ts +5 -0
- package/dist/hooks/useWrMessage.js +24 -0
- package/dist/hooks/useWrSave.d.ts +2 -0
- package/dist/hooks/useWrSave.js +11 -0
- package/dist/index.css +467 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/interactive.d.ts +8 -0
- package/dist/interactive.js +13 -0
- package/dist/measure-rules.d.ts +34 -0
- package/dist/measure-rules.js +54 -0
- package/dist/measure.d.ts +11 -0
- package/dist/measure.js +139 -0
- package/dist/messages.d.ts +860 -0
- package/dist/messages.js +2 -0
- package/dist/nav.d.ts +5 -0
- package/dist/nav.js +10 -0
- package/dist/operations/animations-store.d.ts +6 -0
- package/dist/operations/animations-store.js +63 -0
- package/dist/operations/cli.d.ts +3 -0
- package/dist/operations/cli.js +52 -0
- package/dist/operations/comments-store.d.ts +17 -0
- package/dist/operations/comments-store.js +62 -0
- package/dist/operations/context.d.ts +12 -0
- package/dist/operations/context.js +35 -0
- package/dist/operations/define.d.ts +81 -0
- package/dist/operations/define.js +86 -0
- package/dist/operations/index.d.ts +6 -0
- package/dist/operations/index.js +6 -0
- package/dist/operations/ops/check.d.ts +5 -0
- package/dist/operations/ops/check.js +53 -0
- package/dist/operations/ops/comments.d.ts +6 -0
- package/dist/operations/ops/comments.js +133 -0
- package/dist/operations/ops/content.d.ts +16 -0
- package/dist/operations/ops/content.js +73 -0
- package/dist/operations/ops/json.d.ts +4 -0
- package/dist/operations/ops/json.js +27 -0
- package/dist/operations/ops/langs.d.ts +4 -0
- package/dist/operations/ops/langs.js +32 -0
- package/dist/operations/ops/nodes.d.ts +8 -0
- package/dist/operations/ops/nodes.js +270 -0
- package/dist/operations/ops/pages.d.ts +8 -0
- package/dist/operations/ops/pages.js +154 -0
- package/dist/operations/ops/project.d.ts +36 -0
- package/dist/operations/ops/project.js +97 -0
- package/dist/operations/ops/seo.d.ts +17 -0
- package/dist/operations/ops/seo.js +358 -0
- package/dist/operations/ops/style.d.ts +76 -0
- package/dist/operations/ops/style.js +268 -0
- package/dist/operations/ops/stylesets.d.ts +9 -0
- package/dist/operations/ops/stylesets.js +145 -0
- package/dist/operations/ops/theme.d.ts +6 -0
- package/dist/operations/ops/theme.js +304 -0
- package/dist/operations/registry.d.ts +8 -0
- package/dist/operations/registry.js +19 -0
- package/dist/operations/stylesets-store.d.ts +50 -0
- package/dist/operations/stylesets-store.js +216 -0
- package/dist/registry.d.ts +5 -0
- package/dist/registry.js +24 -0
- package/dist/save.d.ts +65 -0
- package/dist/save.js +86 -0
- package/dist/schema.d.ts +69 -0
- package/dist/schema.js +110 -0
- package/dist/scope.d.ts +41 -0
- package/dist/scope.js +44 -0
- package/dist/vite-plugins/ast.d.ts +3 -0
- package/dist/vite-plugins/ast.js +238 -0
- package/dist/vite-plugins/core-swap.d.ts +3 -0
- package/dist/vite-plugins/core-swap.js +57 -0
- package/dist/vite-plugins/create-page.d.ts +4 -0
- package/dist/vite-plugins/create-page.js +59 -0
- package/dist/vite-plugins/guard.d.ts +15 -0
- package/dist/vite-plugins/guard.js +35 -0
- package/dist/vite-plugins/head.d.ts +13 -0
- package/dist/vite-plugins/head.js +53 -0
- package/dist/vite-plugins/index.d.ts +7 -0
- package/dist/vite-plugins/index.js +7 -0
- package/dist/vite-plugins/jsx-tree.d.ts +118 -0
- package/dist/vite-plugins/jsx-tree.js +686 -0
- package/dist/vite-plugins/langs.d.ts +3 -0
- package/dist/vite-plugins/langs.js +44 -0
- package/dist/vite-plugins/operations.d.ts +10 -0
- package/dist/vite-plugins/operations.js +66 -0
- package/dist/vite-plugins/pages-api.d.ts +3 -0
- package/dist/vite-plugins/pages-api.js +112 -0
- package/dist/vite-plugins/pages.d.ts +3 -0
- package/dist/vite-plugins/pages.js +64 -0
- package/dist/vite-plugins/read.d.ts +3 -0
- package/dist/vite-plugins/read.js +45 -0
- package/dist/vite-plugins/seo-files.d.ts +8 -0
- package/dist/vite-plugins/seo-files.js +42 -0
- package/dist/vite-plugins/theme.d.ts +3 -0
- package/dist/vite-plugins/theme.js +207 -0
- package/dist/vite-plugins/update.d.ts +3 -0
- package/dist/vite-plugins/update.js +46 -0
- package/dist/vite-plugins/wr-plugins.d.ts +15 -0
- package/dist/vite-plugins/wr-plugins.js +34 -0
- package/dist/vite-plugins.d.ts +11 -0
- package/dist/vite-plugins.js +518 -0
- package/package.json +96 -5
- package/README.md +0 -3
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
// packages/editor/src/bridge.ts
|
|
2
|
+
import { wrBoxGaps } from './gaps';
|
|
3
|
+
import { isWrInteractive, WR_INTERACTIVE_ATTR } from './interactive';
|
|
4
|
+
import { scopeOfId, scopeOfAnchor, pageEdgeOf, WR_SCOPE_NAME_ATTR, scopeOfElement, getPageScope, setPageScope } from './scope';
|
|
5
|
+
import { WR_STYLE_PREVIEW_CSS } from '@webround/core/style';
|
|
6
|
+
import { getWrValue } from './registry';
|
|
7
|
+
import { measurePage } from './measure';
|
|
8
|
+
import { navigationTo, pathOf } from './nav';
|
|
9
|
+
import { wrDropTargetAt } from './drop';
|
|
10
|
+
import { wrOp, wrCreatePage, wrDeletePage, wrListPages, wrUpdatePage, wrSaveLangs, wrListLangs, wrRemoveThemeItem, wrSetThemeItem, wrGetTheme, wrSetThemeDark, wrGetSeo, wrSetSeo, wrSetSeoSite, wrSuggestSeo, wrCheckProject, wrPruneJson, wrMuteIssue, wrGetComments, wrAddComment, wrResolveComment, wrDeleteComment, } from './save';
|
|
11
|
+
function getRect(el) {
|
|
12
|
+
const r = el.getBoundingClientRect();
|
|
13
|
+
return { top: r.top, left: r.left, width: r.width, height: r.height };
|
|
14
|
+
}
|
|
15
|
+
/** The room the parent leaves an element (its content box; none in a grid) and the size of 1rem: what a dragged width can snap to. */
|
|
16
|
+
function getParentSize(el) {
|
|
17
|
+
const parent = el.parentElement;
|
|
18
|
+
if (!parent)
|
|
19
|
+
return null;
|
|
20
|
+
const style = getComputedStyle(parent);
|
|
21
|
+
const px = (value) => parseFloat(value) || 0;
|
|
22
|
+
// A percentage width of a grid item is a part of its grid area, not of the grid: no room is claimed there (0), so a width is never snapped to a fraction that would come out smaller
|
|
23
|
+
const inGrid = /grid/.test(style.display);
|
|
24
|
+
return {
|
|
25
|
+
width: inGrid ? 0 : Math.max(0, parent.clientWidth - px(style.paddingLeft) - px(style.paddingRight)),
|
|
26
|
+
height: Math.max(0, parent.clientHeight - px(style.paddingTop) - px(style.paddingBottom)),
|
|
27
|
+
rem: px(getComputedStyle(document.documentElement).fontSize) || 16,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** The room an element has inside (padding) and around (margin), side by side, as the page shows it now (see `WrBoxSpace`): what the handles on the page start from. */
|
|
31
|
+
function getBoxSpace(el) {
|
|
32
|
+
const style = getComputedStyle(el);
|
|
33
|
+
const px = (value) => parseFloat(value) || 0;
|
|
34
|
+
return {
|
|
35
|
+
padding: [px(style.paddingTop), px(style.paddingRight), px(style.paddingBottom), px(style.paddingLeft)],
|
|
36
|
+
margin: [px(style.marginTop), px(style.marginRight), px(style.marginBottom), px(style.marginLeft)],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Where the gap between the children of a flex or grid box is on the page (see `WrBoxGaps`): the box and its children are measured here, what is a band is `wrBoxGaps`'s. */
|
|
40
|
+
function getBoxGaps(el) {
|
|
41
|
+
const style = getComputedStyle(el);
|
|
42
|
+
if (!/(flex|grid)/.test(style.display))
|
|
43
|
+
return null;
|
|
44
|
+
const px = (value) => parseFloat(value) || 0;
|
|
45
|
+
const box = el.getBoundingClientRect();
|
|
46
|
+
const padL = px(style.paddingLeft);
|
|
47
|
+
const padT = px(style.paddingTop);
|
|
48
|
+
const kids = Array.from(el.children).filter(k => { const c = getComputedStyle(k); return c.display !== 'none' && c.display !== 'contents' && c.position !== 'absolute' && c.position !== 'fixed'; }).map(getRect);
|
|
49
|
+
return wrBoxGaps({
|
|
50
|
+
display: style.display, columnGap: px(style.columnGap), rowGap: px(style.rowGap), kids,
|
|
51
|
+
content: { top: box.top + el.clientTop + padT, left: box.left + el.clientLeft + padL, width: Math.max(0, el.clientWidth - padL - px(style.paddingRight)), height: Math.max(0, el.clientHeight - padT - px(style.paddingBottom)) },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** How the children of a box fill the room the box leaves them (see `WrBoxFit`): the children that take part in the layout, against the content box. */
|
|
55
|
+
function getBoxFit(el) {
|
|
56
|
+
const style = getComputedStyle(el);
|
|
57
|
+
const px = (value) => parseFloat(value) || 0;
|
|
58
|
+
const padX = [px(style.paddingLeft), px(style.paddingRight)];
|
|
59
|
+
const padY = [px(style.paddingTop), px(style.paddingBottom)];
|
|
60
|
+
const kids = Array.from(el.children).filter(k => { const c = getComputedStyle(k); return c.display !== 'none' && c.position !== 'absolute' && c.position !== 'fixed'; }).map(k => k.getBoundingClientRect());
|
|
61
|
+
if (kids.length === 0)
|
|
62
|
+
return null;
|
|
63
|
+
const span = (low, high) => Math.max(...high) - Math.min(...low);
|
|
64
|
+
return {
|
|
65
|
+
contentWidth: Math.max(0, el.clientWidth - padX[0] - padX[1]), contentHeight: Math.max(0, el.clientHeight - padY[0] - padY[1]),
|
|
66
|
+
kidsWidth: span(kids.map(k => k.left), kids.map(k => k.right)), kidsHeight: span(kids.map(k => k.top), kids.map(k => k.bottom)), padX, padY,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The smallest the element can be (see `WrBoxFloor`), measured on the page itself: it is asked to be nothing for a moment and what it comes to is read. Nothing is drawn in between:
|
|
71
|
+
* the transitions are off while it happens and the element is given back, and the page is laid out once more, before they are back, so that none starts.
|
|
72
|
+
*/
|
|
73
|
+
function getBoxFloor(el) {
|
|
74
|
+
if (!(el instanceof HTMLElement))
|
|
75
|
+
return null;
|
|
76
|
+
const saved = (target, property) => [target.style.getPropertyValue(property), target.style.getPropertyPriority(property)];
|
|
77
|
+
const give = (target, property, [value, priority]) => { if (value)
|
|
78
|
+
target.style.setProperty(property, value, priority);
|
|
79
|
+
else
|
|
80
|
+
target.style.removeProperty(property); };
|
|
81
|
+
// The page keeps its size while the element is nothing: a shorter document would pull the scroll position with it (and cut a scroll that is going on)
|
|
82
|
+
const root = document.documentElement;
|
|
83
|
+
const hold = [saved(root, 'min-height'), saved(root, 'min-width')];
|
|
84
|
+
root.style.setProperty('min-height', `${root.scrollHeight}px`);
|
|
85
|
+
root.style.setProperty('min-width', `${root.scrollWidth}px`);
|
|
86
|
+
const transition = saved(el, 'transition');
|
|
87
|
+
el.style.setProperty('transition', 'none', 'important');
|
|
88
|
+
const floor = { width: 0, height: 0 };
|
|
89
|
+
for (const property of ['width', 'height']) {
|
|
90
|
+
const was = saved(el, property);
|
|
91
|
+
el.style.setProperty(property, '0px', 'important');
|
|
92
|
+
floor[property] = Math.ceil(el.getBoundingClientRect()[property]);
|
|
93
|
+
give(el, property, was);
|
|
94
|
+
}
|
|
95
|
+
el.getBoundingClientRect();
|
|
96
|
+
give(el, 'transition', transition);
|
|
97
|
+
give(root, 'min-height', hold[0]);
|
|
98
|
+
give(root, 'min-width', hold[1]);
|
|
99
|
+
return floor;
|
|
100
|
+
}
|
|
101
|
+
// The floor depends on the element and its size, not on where it is on the page: it is measured again when either changes, not on every scroll
|
|
102
|
+
let floorOf = null;
|
|
103
|
+
function boxFloor(el, rect) {
|
|
104
|
+
const size = rect ? `${Math.round(rect.width)}x${Math.round(rect.height)}` : '';
|
|
105
|
+
if (floorOf && floorOf.el === el && floorOf.size === size)
|
|
106
|
+
return floorOf.floor;
|
|
107
|
+
floorOf = { el, size, floor: getBoxFloor(el) };
|
|
108
|
+
return floorOf.floor;
|
|
109
|
+
}
|
|
110
|
+
function parseEl(el) {
|
|
111
|
+
const scope = scopeOfElement(el);
|
|
112
|
+
return {
|
|
113
|
+
...(scope && scope.name !== getPageScope()?.name ? { scope: scope.name } : {}),
|
|
114
|
+
type: el.getAttribute('data-wr-type') ?? 'unknown',
|
|
115
|
+
id: el.getAttribute('data-wr-id'),
|
|
116
|
+
parentId: el.parentElement?.closest('[data-wr-id]')?.getAttribute('data-wr-id') ?? null,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
let bridgeOrigin = null;
|
|
120
|
+
let selectedEl = null;
|
|
121
|
+
let wrNavigate = null;
|
|
122
|
+
export function setWrNavigate(navigate) {
|
|
123
|
+
wrNavigate = navigate;
|
|
124
|
+
}
|
|
125
|
+
let wrUndo = null;
|
|
126
|
+
let wrRedo = null;
|
|
127
|
+
export function setWrHistory(undo, redo) {
|
|
128
|
+
wrUndo = undo;
|
|
129
|
+
wrRedo = redo;
|
|
130
|
+
}
|
|
131
|
+
// La pagina corrente: il suo file e il suo JSON. Gli elementi di header e footer hanno un ambito loro (vedi scope.ts).
|
|
132
|
+
let wrPageFile = null;
|
|
133
|
+
let wrPageName = null;
|
|
134
|
+
const syncPageScope = () => { if (wrPageFile && wrPageName)
|
|
135
|
+
setPageScope({ file: wrPageFile, name: wrPageName }); };
|
|
136
|
+
export function setWrPageFile(file) { wrPageFile = file; syncPageScope(); }
|
|
137
|
+
export function setWrPageName(pageName) { wrPageName = pageName; syncPageScope(); }
|
|
138
|
+
let wrAstHistoryPush = null;
|
|
139
|
+
export function setWrAstHistoryPush(fn) {
|
|
140
|
+
wrAstHistoryPush = fn;
|
|
141
|
+
}
|
|
142
|
+
// Una modifica di stile: le classi di prima e di dopo, perché l'undo le rimette ESATTAMENTE così (cosa una modifica ha sostituito non si dice come un'altra modifica).
|
|
143
|
+
let wrStyleHistoryPush = null;
|
|
144
|
+
export function setWrStyleHistoryPush(fn) {
|
|
145
|
+
wrStyleHistoryPush = fn;
|
|
146
|
+
}
|
|
147
|
+
let wrMoveHistoryPush = null;
|
|
148
|
+
export function setWrMoveHistoryPush(fn) {
|
|
149
|
+
wrMoveHistoryPush = fn;
|
|
150
|
+
}
|
|
151
|
+
let wrNodeHistoryPush = null;
|
|
152
|
+
export function setWrNodeHistoryPush(fn) {
|
|
153
|
+
wrNodeHistoryPush = fn;
|
|
154
|
+
}
|
|
155
|
+
export async function fetchNode(elementId, scope = scopeOfId(elementId)) {
|
|
156
|
+
if (!scope)
|
|
157
|
+
return { fields: [], values: {} };
|
|
158
|
+
const result = await wrOp('node-get', { file: scope.file, id: elementId });
|
|
159
|
+
return result.ok && result.data ? { fields: result.data.fields, values: result.data.values } : { fields: [], values: {} };
|
|
160
|
+
}
|
|
161
|
+
/** Come è stilato un elemento (le sue classi, per proprietà e breakpoint), nella forma che l'editor mostra. */
|
|
162
|
+
export async function fetchStyle(elementId, scope = scopeOfId(elementId)) {
|
|
163
|
+
const empty = { elementId, editable: false, className: null, styles: [], unmanaged: [] };
|
|
164
|
+
if (!scope)
|
|
165
|
+
return { ...empty, error: 'element not found' };
|
|
166
|
+
const result = await wrOp('style-get', { file: scope.file, id: elementId });
|
|
167
|
+
return result.ok && result.data ? { elementId, ...result.data } : { ...empty, error: result.error };
|
|
168
|
+
}
|
|
169
|
+
/** Rimette le classi di un elemento esattamente come dette (l'undo e il redo di una modifica di stile). */
|
|
170
|
+
export async function restoreStyle(elementId, className, scope = scopeOfId(elementId)) {
|
|
171
|
+
if (!scope)
|
|
172
|
+
return false;
|
|
173
|
+
return (await wrOp('style-restore', { file: scope.file, id: elementId, className })).ok;
|
|
174
|
+
}
|
|
175
|
+
/** value === null rimuove l'attributo (usato solo internamente, dall'undo). */
|
|
176
|
+
export async function putNode(elementId, key, value, scope = scopeOfId(elementId)) {
|
|
177
|
+
if (!scope)
|
|
178
|
+
return false;
|
|
179
|
+
return (await wrOp('node-set', { file: scope.file, id: elementId, key, value })).ok;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* `{ error }` = l'operazione non l'ha cancellato e dice perché (es. la radice di un header); `null` = niente da fare (nessun file).
|
|
183
|
+
* Insieme al nodo se ne vanno i valori JSON suoi e dei discendenti: lo fa l'operazione, per tutti.
|
|
184
|
+
*/
|
|
185
|
+
export async function deleteNode(elementId, scope = scopeOfId(elementId)) {
|
|
186
|
+
if (!scope)
|
|
187
|
+
return null;
|
|
188
|
+
const result = await wrOp('node-delete', { file: scope.file, id: elementId });
|
|
189
|
+
if (result.ok && result.data)
|
|
190
|
+
return result.data;
|
|
191
|
+
return result.error ? { error: result.error } : null;
|
|
192
|
+
}
|
|
193
|
+
export async function moveNode(elementId, anchor, anchorMode, scope = scopeOfId(elementId)) {
|
|
194
|
+
if (!scope)
|
|
195
|
+
return { ok: false, error: 'no active file' };
|
|
196
|
+
const result = await wrOp('node-move', { file: scope.file, id: elementId, anchor, mode: anchorMode });
|
|
197
|
+
return result.ok && result.data ? { ok: true, from: result.data.from } : { ok: false, code: result.code, error: result.error ?? `HTTP ${result.status}` };
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Inserisce markup JSX (un inserimento di sottoalbero, e l'undo di una delete). L'operazione lo valida e sceglie gli id; i valori JSON
|
|
201
|
+
* iniziali (`jsonSeed`) li scrive lei, con gli id che ha scelto.
|
|
202
|
+
*/
|
|
203
|
+
// A copy or a block is written in the code and the dev server redraws the page. Now and then a write is not taken (the server read the file while it was being written, or the
|
|
204
|
+
// message to the browser was lost): the code has the element and the page does not, and the editor would show a page that is not the code. So the page checks that what was added is
|
|
205
|
+
// there, and if it is not after a while it is loaded again from the code.
|
|
206
|
+
const RENDER_WAIT_MS = 5000;
|
|
207
|
+
const RENDER_CHECK_MS = 250;
|
|
208
|
+
function ensureRendered(ids) {
|
|
209
|
+
if (ids.length === 0)
|
|
210
|
+
return;
|
|
211
|
+
const started = Date.now();
|
|
212
|
+
const check = () => {
|
|
213
|
+
if (ids.every(id => document.querySelector(`[data-wr-id="${CSS.escape(id)}"]`)))
|
|
214
|
+
return;
|
|
215
|
+
if (Date.now() - started > RENDER_WAIT_MS) {
|
|
216
|
+
window.location.reload();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
setTimeout(check, RENDER_CHECK_MS);
|
|
220
|
+
};
|
|
221
|
+
setTimeout(check, RENDER_CHECK_MS * 2);
|
|
222
|
+
}
|
|
223
|
+
/** An insert asked for by the editor: written, put in the history (when it can be undone by id) and answered. Shared by the blocks of the catalog and by duplicating. */
|
|
224
|
+
function runInsert(data) {
|
|
225
|
+
const { jsx, jsonSeed, uniqueIds } = data;
|
|
226
|
+
const { anchor, mode: anchorMode } = pageEdgeOf(data.anchor, data.anchorMode);
|
|
227
|
+
const scope = scopeOfAnchor(anchor);
|
|
228
|
+
insertRawNode(anchor, anchorMode, jsx, uniqueIds !== false, scope, jsonSeed)
|
|
229
|
+
.then(async (result) => {
|
|
230
|
+
const { ok, ids, rootIds, undoable, idMap, code, error } = result;
|
|
231
|
+
if (!ok) {
|
|
232
|
+
post({ type: 'wr:node:insert:response', ok: false, ids: [], rootIds: [], undoable: false, idMap: {}, code, error });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// I valori iniziali erano per gli id chiesti: l'operazione li ha scritti con gli id che ha scelto
|
|
236
|
+
const seed = jsonSeed ? Object.fromEntries(Object.entries(jsonSeed).map(([id, value]) => [idMap[id] ?? id, value])) : undefined;
|
|
237
|
+
// Solo ciò che si può rimuovere per id entra nella cronologia: un <div> di primo livello non ha id da cui annullarlo
|
|
238
|
+
if (undoable && scope)
|
|
239
|
+
wrNodeHistoryPush?.({ op: 'insert', scope, elementIds: rootIds, jsx: result.jsx, anchor, anchorMode, jsonSeed: seed });
|
|
240
|
+
post({ type: 'wr:node:insert:response', ok: true, ids, rootIds, undoable, idMap });
|
|
241
|
+
ensureRendered(rootIds);
|
|
242
|
+
})
|
|
243
|
+
.catch(err => post({ type: 'wr:node:insert:response', ok: false, ids: [], rootIds: [], undoable: false, idMap: {}, error: String(err) }));
|
|
244
|
+
}
|
|
245
|
+
export async function insertRawNode(anchor, anchorMode, rawJsx, uniqueIds = true, scope = scopeOfAnchor(anchor), jsonSeed) {
|
|
246
|
+
const failed = (error, code) => ({ ok: false, ids: [], rootIds: [], undoable: false, idMap: {}, jsx: rawJsx, error, code });
|
|
247
|
+
if (!scope)
|
|
248
|
+
return failed('no active file');
|
|
249
|
+
const result = await wrOp('node-insert', {
|
|
250
|
+
file: scope.file, anchor, mode: anchorMode, jsx: rawJsx, uniqueIds, ...(jsonSeed ? { jsonSeed } : {}),
|
|
251
|
+
});
|
|
252
|
+
if (!result.ok || !result.data)
|
|
253
|
+
return failed(result.error ?? `HTTP ${result.status}`, result.code);
|
|
254
|
+
const { ids, rootIds, undoable, idMap, jsx } = result.data;
|
|
255
|
+
return { ok: true, ids: ids ?? [], rootIds: rootIds ?? [], undoable: undoable === true, idMap: idMap ?? {}, jsx: jsx ?? rawJsx };
|
|
256
|
+
}
|
|
257
|
+
function post(msg) {
|
|
258
|
+
if (!bridgeOrigin)
|
|
259
|
+
return;
|
|
260
|
+
window.parent.postMessage(msg, bridgeOrigin);
|
|
261
|
+
}
|
|
262
|
+
/** The shared styles of the site, told to the editor. Said again after anything that can change them or where they are used. */
|
|
263
|
+
function postStyleSets() {
|
|
264
|
+
wrOp('styleset-list', {})
|
|
265
|
+
.then(res => post({ type: 'wr:styleset:list:response', sets: res.ok && res.data ? res.data.sets : [], ...(res.ok ? {} : { error: res.error }) }))
|
|
266
|
+
.catch(err => post({ type: 'wr:styleset:list:response', sets: [], error: String(err) }));
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Something that changes the classes of an ELEMENT (a value, a copy between screens, a shared style put on it or taken off): one operation, one answer (`wr:style:set:response`, with what
|
|
270
|
+
* the classes are now) and one step of undo (what they were and what they are). `action`: it is also a shared-styles action, so it says how it went and the list is told again.
|
|
271
|
+
*/
|
|
272
|
+
function runElementStyle(elementId, operation, params, action) {
|
|
273
|
+
const scope = scopeOfId(elementId);
|
|
274
|
+
if (!scope) {
|
|
275
|
+
post({ type: 'wr:style:set:response', elementId, ok: false, error: 'element not found' });
|
|
276
|
+
if (action)
|
|
277
|
+
post({ type: 'wr:styleset:result', ...action, ok: false, error: 'element not found' });
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
wrOp(operation, { file: scope.file, id: elementId, ...params })
|
|
281
|
+
.then((res) => {
|
|
282
|
+
if (!res.ok || !res.data) {
|
|
283
|
+
post({ type: 'wr:style:set:response', elementId, ok: false, error: res.error, code: res.code });
|
|
284
|
+
if (action)
|
|
285
|
+
post({ type: 'wr:styleset:result', ...action, ok: false, error: res.error, code: res.code });
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const { changed, previous, className, styles, unmanaged, states, sets } = res.data;
|
|
289
|
+
if (changed)
|
|
290
|
+
wrStyleHistoryPush?.(elementId, previous, className, scope);
|
|
291
|
+
post({ type: 'wr:style:set:response', elementId, ok: true, changed, className, styles, unmanaged, ...(states ? { states } : {}), ...(sets ? { sets } : {}) });
|
|
292
|
+
if (action) {
|
|
293
|
+
post({ type: 'wr:styleset:result', ...action, ok: true });
|
|
294
|
+
postStyleSets();
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
.catch((err) => {
|
|
298
|
+
post({ type: 'wr:style:set:response', elementId, ok: false, error: String(err) });
|
|
299
|
+
if (action)
|
|
300
|
+
post({ type: 'wr:styleset:result', ...action, ok: false, error: String(err) });
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
/** An action on a shared style itself (make an empty one, take it off every element, rename it, delete it): it says how it went, then the list. Not in the undo history: it can touch many files. */
|
|
304
|
+
function runStyleSetOp(action, operation, params, name) {
|
|
305
|
+
wrOp(operation, params)
|
|
306
|
+
.then((res) => {
|
|
307
|
+
post({ type: 'wr:styleset:result', action, name, ok: res.ok, ...(res.ok ? {} : { error: res.error, code: res.code }) });
|
|
308
|
+
postStyleSets();
|
|
309
|
+
})
|
|
310
|
+
.catch(err => post({ type: 'wr:styleset:result', action, name, ok: false, error: String(err) }));
|
|
311
|
+
}
|
|
312
|
+
/** How a shared style looks and what it holds, read or changed: the same operations as for an element, by its name. A change says how many elements move with it. Not in the undo history. */
|
|
313
|
+
function runStyleSetStyle(name, operation, params) {
|
|
314
|
+
wrOp(operation, { set: name, ...params })
|
|
315
|
+
.then((res) => {
|
|
316
|
+
if (!res.ok || !res.data) {
|
|
317
|
+
post({ type: 'wr:styleset:style:response', name, ok: false, error: res.error, code: res.code });
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const { className, styles, unmanaged, states, usedBy } = res.data;
|
|
321
|
+
post({ type: 'wr:styleset:style:response', name, ok: true, className, styles, unmanaged, ...(states ? { states } : {}), usedBy });
|
|
322
|
+
if (operation !== 'style-get')
|
|
323
|
+
postStyleSets();
|
|
324
|
+
})
|
|
325
|
+
.catch(err => post({ type: 'wr:styleset:style:response', name, ok: false, error: String(err) }));
|
|
326
|
+
}
|
|
327
|
+
export function postToEditor(msg, transfer) {
|
|
328
|
+
const origin = bridgeOrigin ?? '*';
|
|
329
|
+
window.parent.postMessage(msg, origin, transfer);
|
|
330
|
+
}
|
|
331
|
+
function scan() {
|
|
332
|
+
const elements = Array.from(document.querySelectorAll('[data-wr-id]')).map(parseEl);
|
|
333
|
+
post({ type: 'wr:scan', elements });
|
|
334
|
+
}
|
|
335
|
+
// The page is measured (contrast, targets, text size…) a moment after the page stops changing, and again when its theme changes (the class on <html>): a burst of changes
|
|
336
|
+
// measures once. What is measured is what the visitor sees now.
|
|
337
|
+
const MEASURE_DELAY_MS = 900;
|
|
338
|
+
let measureTimer;
|
|
339
|
+
function scheduleMeasure() {
|
|
340
|
+
clearTimeout(measureTimer);
|
|
341
|
+
measureTimer = setTimeout(() => {
|
|
342
|
+
const pathname = pathOf(window.location.pathname);
|
|
343
|
+
post({ type: 'wr:measure:report', pathname, findings: measurePage(document) });
|
|
344
|
+
}, MEASURE_DELAY_MS);
|
|
345
|
+
}
|
|
346
|
+
let hoveredEl = null;
|
|
347
|
+
function postSelect(el) {
|
|
348
|
+
const id = el.getAttribute('data-wr-id');
|
|
349
|
+
lastGeometry = '';
|
|
350
|
+
post({
|
|
351
|
+
type: 'wr:select',
|
|
352
|
+
element: parseEl(el),
|
|
353
|
+
rect: getRect(el),
|
|
354
|
+
parent: getParentSize(el),
|
|
355
|
+
fit: getBoxFit(el),
|
|
356
|
+
space: getBoxSpace(el),
|
|
357
|
+
gaps: getBoxGaps(el),
|
|
358
|
+
value: getWrValue(id ?? ''),
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
function postDeselect() {
|
|
362
|
+
post({ type: 'wr:select', element: null, rect: null, value: null });
|
|
363
|
+
}
|
|
364
|
+
function clearHighlight() {
|
|
365
|
+
document.querySelectorAll('[data-wr-highlighted]')
|
|
366
|
+
.forEach(el => el.removeAttribute('data-wr-highlighted'));
|
|
367
|
+
}
|
|
368
|
+
export function refreshSelection(id) {
|
|
369
|
+
if (selectedEl?.getAttribute('data-wr-id') === id) {
|
|
370
|
+
postSelect(selectedEl);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// Browsing as a visitor (the editor's preview mode): the page is not selected or hovered by clicking and pointing
|
|
374
|
+
let browsing = false;
|
|
375
|
+
function onClick(e) {
|
|
376
|
+
if (browsing)
|
|
377
|
+
return;
|
|
378
|
+
// A control the editor puts on the page itself (`data-wr-editor-ui`, e.g. "Back to the form" in a confirmation) is neither a selection nor a deselection
|
|
379
|
+
if (e.target.closest('[data-wr-editor-ui]'))
|
|
380
|
+
return;
|
|
381
|
+
const el = e.target.closest('[data-wr-id]');
|
|
382
|
+
// Un elemento interattivo non si seleziona cliccandolo (vedi interactive.ts): un click altrove deseleziona
|
|
383
|
+
if (!el || isWrInteractive(el)) {
|
|
384
|
+
if (selectedEl) {
|
|
385
|
+
selectedEl = null;
|
|
386
|
+
clearHighlight();
|
|
387
|
+
postDeselect();
|
|
388
|
+
}
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
selectedEl = el;
|
|
392
|
+
postSelect(el);
|
|
393
|
+
}
|
|
394
|
+
// An element drawn INSIDE a link or a button (the text beside an icon, a picture in a button) is selected by clicking it, and the click must not also follow the link or press the button: the page
|
|
395
|
+
// would go away (or a product would be added to the cart) while someone is only trying to change the text. The link or the button itself (its padding, its icon) still acts, and is selected by its
|
|
396
|
+
// handle. This runs while the click goes down (capture), before the page's own handlers, and stops it there.
|
|
397
|
+
//
|
|
398
|
+
// An element inside a `<label>` (the text of a field) is another thing: it is edited where it is, so its own click must go on. Only what the label does with it is stopped: the label would send the
|
|
399
|
+
// focus to its field, and the text being edited would lose it (and be closed).
|
|
400
|
+
function onClickInsideInteractive(e) {
|
|
401
|
+
if (browsing)
|
|
402
|
+
return;
|
|
403
|
+
const target = e.target;
|
|
404
|
+
if (target.closest('[data-wr-editor-ui]'))
|
|
405
|
+
return;
|
|
406
|
+
const el = target.closest('[data-wr-id]');
|
|
407
|
+
// (A text being edited is drawn without its own `data-wr-id`, inside `data-wr-editing`: it counts as the text it is)
|
|
408
|
+
const label = target.closest('label');
|
|
409
|
+
if (label && !target.closest('input, select, textarea, button') && (target.closest('[data-wr-editing]') || (el && label.contains(el) && !isWrInteractive(el))))
|
|
410
|
+
e.preventDefault();
|
|
411
|
+
if (!el || isWrInteractive(el) || !el.parentElement?.closest(`[${WR_INTERACTIVE_ATTR}]`))
|
|
412
|
+
return;
|
|
413
|
+
e.preventDefault();
|
|
414
|
+
e.stopPropagation();
|
|
415
|
+
selectedEl = el;
|
|
416
|
+
postSelect(el);
|
|
417
|
+
}
|
|
418
|
+
const MOVE_KEYS = { ArrowUp: 'up', ArrowDown: 'down', ArrowLeft: 'out', ArrowRight: 'in' };
|
|
419
|
+
/** Alt + frecce con un elemento selezionato: lo spostamento lo decide l'editor (conosce l'albero), qui si inoltra soltanto il tasto. */
|
|
420
|
+
function onKeyDown(e) {
|
|
421
|
+
const direction = MOVE_KEYS[e.key];
|
|
422
|
+
if (!direction || !e.altKey || e.ctrlKey || e.metaKey || !selectedEl)
|
|
423
|
+
return;
|
|
424
|
+
const target = e.target;
|
|
425
|
+
if (target?.closest('input, textarea, select, [contenteditable="true"]'))
|
|
426
|
+
return; // lì le frecce sono del testo
|
|
427
|
+
e.preventDefault();
|
|
428
|
+
post({ type: 'wr:move:key', direction });
|
|
429
|
+
}
|
|
430
|
+
function onScroll() {
|
|
431
|
+
postCommentRects();
|
|
432
|
+
syncGeometry();
|
|
433
|
+
}
|
|
434
|
+
// Where the selected and the hovered element are. The editor draws their outlines over the preview, so it must be told whenever they move or change shape, whatever the reason
|
|
435
|
+
// (scroll, resize, a change of style, an image that loads, an animation, the code being edited): rather than listening for each reason, the rectangles are compared at a
|
|
436
|
+
// short interval and when the page scrolls, and said again only when they changed. An element that was replaced (the page redrew) is found again by its id.
|
|
437
|
+
const GEOMETRY_EVERY_MS = 50;
|
|
438
|
+
let lastGeometry = '';
|
|
439
|
+
function stillThere(el) {
|
|
440
|
+
if (!el || el.isConnected)
|
|
441
|
+
return el;
|
|
442
|
+
const id = el.getAttribute('data-wr-id');
|
|
443
|
+
return id ? document.querySelector(`[data-wr-id="${CSS.escape(id)}"]`) : null;
|
|
444
|
+
}
|
|
445
|
+
function syncGeometry() {
|
|
446
|
+
if (!selectedEl && !hoveredEl)
|
|
447
|
+
return;
|
|
448
|
+
// An element that is not on the page right now (a text turns into its editor while it is written, and comes back) has no rectangle, and keeps being the selected one: it is
|
|
449
|
+
// found again by its id when it returns. Whether it was deleted is the editor's to say, not this loop's.
|
|
450
|
+
const selectedNow = stillThere(selectedEl);
|
|
451
|
+
if (selectedNow)
|
|
452
|
+
selectedEl = selectedNow;
|
|
453
|
+
const hoveredNow = stillThere(hoveredEl);
|
|
454
|
+
if (hoveredNow)
|
|
455
|
+
hoveredEl = hoveredNow;
|
|
456
|
+
const selected = selectedNow ? getRect(selectedNow) : null;
|
|
457
|
+
const hovered = hoveredNow ? getRect(hoveredNow) : null;
|
|
458
|
+
const parent = selectedNow ? getParentSize(selectedNow) : null;
|
|
459
|
+
const fit = selectedNow ? getBoxFit(selectedNow) : null;
|
|
460
|
+
const floor = selectedNow ? boxFloor(selectedNow, selected) : null;
|
|
461
|
+
const space = selectedNow ? getBoxSpace(selectedNow) : null;
|
|
462
|
+
const gaps = selectedNow ? getBoxGaps(selectedNow) : null;
|
|
463
|
+
const said = JSON.stringify([selected, hovered, parent, fit, floor, space, gaps]);
|
|
464
|
+
if (said === lastGeometry)
|
|
465
|
+
return;
|
|
466
|
+
lastGeometry = said;
|
|
467
|
+
post({ type: 'wr:rects', selected, hovered, parent, fit, floor, space, gaps });
|
|
468
|
+
}
|
|
469
|
+
// The elements the editor wants a dot on (they have an open comment): where they are now, said again when the page scrolls, resizes or changes
|
|
470
|
+
const RECTS_DELAY_MS = 30;
|
|
471
|
+
let commentMarks = [];
|
|
472
|
+
let rectsTimer;
|
|
473
|
+
function postCommentRects() {
|
|
474
|
+
if (commentMarks.length === 0 || rectsTimer)
|
|
475
|
+
return;
|
|
476
|
+
// A timer, not an animation frame: a page in a tab that is not shown gets no frames, and the dots would not follow it
|
|
477
|
+
rectsTimer = setTimeout(() => {
|
|
478
|
+
rectsTimer = undefined;
|
|
479
|
+
const rects = [];
|
|
480
|
+
for (const { scope, id } of commentMarks) {
|
|
481
|
+
const el = Array.from(document.querySelectorAll(`[data-wr-id="${CSS.escape(id)}"]`)).find(candidate => (scopeOfElement(candidate)?.name ?? getPageScope()?.name) === scope);
|
|
482
|
+
if (el)
|
|
483
|
+
rects.push({ scope, id, rect: getRect(el) });
|
|
484
|
+
}
|
|
485
|
+
post({ type: 'wr:comments:rects', rects });
|
|
486
|
+
}, RECTS_DELAY_MS);
|
|
487
|
+
}
|
|
488
|
+
/** Every comment, as the editor lists them (after a change too). */
|
|
489
|
+
function postComments() {
|
|
490
|
+
wrGetComments().then((response) => post({ type: 'wr:comments:response', ...response }));
|
|
491
|
+
}
|
|
492
|
+
// The element the pointer is over now (to say a hover once), and the one the editor still has a handle on (`hoveredEl`): the handle is in the editor, outside the page, so the
|
|
493
|
+
// pointer leaves the page to reach it and the editor keeps the outline until the pointer leaves the handle too. The outline follows its element all that time (scroll included).
|
|
494
|
+
let pointerOverEl = null;
|
|
495
|
+
function onPointerOver(e) {
|
|
496
|
+
if (browsing)
|
|
497
|
+
return;
|
|
498
|
+
const el = e.target.closest('[data-wr-id]');
|
|
499
|
+
// Solo gli elementi interattivi segnalano l'hover: la maniglia è il loro modo di essere selezionati
|
|
500
|
+
if (!el || !isWrInteractive(el))
|
|
501
|
+
return;
|
|
502
|
+
if (pointerOverEl === el)
|
|
503
|
+
return;
|
|
504
|
+
pointerOverEl = el;
|
|
505
|
+
hoveredEl = el;
|
|
506
|
+
lastGeometry = '';
|
|
507
|
+
post({ type: 'wr:hover', element: parseEl(el), rect: getRect(el) });
|
|
508
|
+
}
|
|
509
|
+
function onPointerOut(e) {
|
|
510
|
+
const el = e.target.closest('[data-wr-id]');
|
|
511
|
+
if (!el || el !== pointerOverEl)
|
|
512
|
+
return;
|
|
513
|
+
const related = e.relatedTarget?.closest('[data-wr-id]');
|
|
514
|
+
if (related === pointerOverEl)
|
|
515
|
+
return;
|
|
516
|
+
pointerOverEl = null;
|
|
517
|
+
}
|
|
518
|
+
// The path the editor was last told. Only another path is a navigation: the query and the hash change under a page that does not (a shop's filters), and then nothing is deselected
|
|
519
|
+
let lastNavPath = null;
|
|
520
|
+
function sendNav() {
|
|
521
|
+
const pathname = navigationTo(lastNavPath, window.location.pathname);
|
|
522
|
+
if (pathname === null)
|
|
523
|
+
return;
|
|
524
|
+
lastNavPath = pathname;
|
|
525
|
+
selectedEl = null;
|
|
526
|
+
hoveredEl = null;
|
|
527
|
+
pointerOverEl = null;
|
|
528
|
+
clearHighlight();
|
|
529
|
+
postDeselect();
|
|
530
|
+
post({ type: 'wr:navigate', pathname });
|
|
531
|
+
}
|
|
532
|
+
// An element shown at a value that is not written yet (dragging a handle, or a number): the CSS is applied for the moment, and what the element had is kept to give back. Only the
|
|
533
|
+
// properties of `WR_STYLE_PREVIEW_CSS` are touched, whatever the editor asks.
|
|
534
|
+
const PREVIEW_SETTLE_MS = 2000;
|
|
535
|
+
/** How far the page scrolls with each probe of a drag held at its top or bottom edge. */
|
|
536
|
+
const DROP_SCROLL_PX = 24;
|
|
537
|
+
let preview = null;
|
|
538
|
+
/** How often a written size that has not reached the page yet is looked for, and how much a rectangle may differ for the page to count as showing it. */
|
|
539
|
+
const PREVIEW_CHECK_MS = 50;
|
|
540
|
+
const PREVIEW_SAME_PX = 1;
|
|
541
|
+
function endPreview(settle) {
|
|
542
|
+
const current = preview;
|
|
543
|
+
if (!current)
|
|
544
|
+
return;
|
|
545
|
+
const restore = () => {
|
|
546
|
+
current.stop?.();
|
|
547
|
+
if (preview === current)
|
|
548
|
+
preview = null;
|
|
549
|
+
for (const [property, value] of current.original)
|
|
550
|
+
current.el.style.setProperty(property, value);
|
|
551
|
+
};
|
|
552
|
+
if (!settle) {
|
|
553
|
+
restore();
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
// What was just written reaches the page a moment later: the code is saved, the classes change, and the stylesheet that makes them mean something comes after (the CSS is
|
|
557
|
+
// generated from the classes). Until the page itself shows the size, what was shown stays: the classes changing is not enough, or the element would flash back to its old size.
|
|
558
|
+
const arrived = () => {
|
|
559
|
+
const { el } = current;
|
|
560
|
+
const was = el.getBoundingClientRect();
|
|
561
|
+
for (const [property, value] of current.original)
|
|
562
|
+
el.style.setProperty(property, value);
|
|
563
|
+
const now = el.getBoundingClientRect();
|
|
564
|
+
if (Math.abs(now.width - was.width) <= PREVIEW_SAME_PX && Math.abs(now.height - was.height) <= PREVIEW_SAME_PX)
|
|
565
|
+
return true;
|
|
566
|
+
for (const [property, value] of current.shown)
|
|
567
|
+
el.style.setProperty(property, value);
|
|
568
|
+
return false;
|
|
569
|
+
};
|
|
570
|
+
let written = false;
|
|
571
|
+
const check = () => { if (written && arrived()) {
|
|
572
|
+
current.stop?.();
|
|
573
|
+
if (preview === current)
|
|
574
|
+
preview = null;
|
|
575
|
+
} };
|
|
576
|
+
const observer = new MutationObserver(() => { written = true; check(); });
|
|
577
|
+
observer.observe(current.el, { attributes: true, attributeFilter: ['class'] });
|
|
578
|
+
const poll = setInterval(check, PREVIEW_CHECK_MS);
|
|
579
|
+
const timer = setTimeout(restore, PREVIEW_SETTLE_MS);
|
|
580
|
+
current.stop = () => { observer.disconnect(); clearInterval(poll); clearTimeout(timer); };
|
|
581
|
+
}
|
|
582
|
+
function showPreview(elementId, styles) {
|
|
583
|
+
const el = document.querySelector(`[data-wr-id="${CSS.escape(elementId)}"]`);
|
|
584
|
+
if (!el)
|
|
585
|
+
return;
|
|
586
|
+
if (preview && preview.el !== el)
|
|
587
|
+
endPreview(false);
|
|
588
|
+
if (preview) {
|
|
589
|
+
preview.stop?.();
|
|
590
|
+
preview.stop = undefined;
|
|
591
|
+
}
|
|
592
|
+
else
|
|
593
|
+
preview = { el, original: new Map(), shown: new Map() };
|
|
594
|
+
for (const [property, value] of Object.entries(styles)) {
|
|
595
|
+
if (!WR_STYLE_PREVIEW_CSS.has(property) || typeof value !== 'string')
|
|
596
|
+
continue;
|
|
597
|
+
if (!preview.original.has(property))
|
|
598
|
+
preview.original.set(property, el.style.getPropertyValue(property));
|
|
599
|
+
el.style.setProperty(property, value);
|
|
600
|
+
preview.shown.set(property, value);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function onMessage(e) {
|
|
604
|
+
if (bridgeOrigin !== '*' && e.origin !== bridgeOrigin)
|
|
605
|
+
return;
|
|
606
|
+
const data = e.data;
|
|
607
|
+
switch (data?.type) {
|
|
608
|
+
case 'wr:highlight': {
|
|
609
|
+
clearHighlight();
|
|
610
|
+
const target = document.querySelector(`[data-wr-id="${data.element.id}"]`);
|
|
611
|
+
if (target) {
|
|
612
|
+
selectedEl = target;
|
|
613
|
+
target.setAttribute('data-wr-highlighted', 'true');
|
|
614
|
+
// chi seleziona dall'editor (un nuovo blocco appena aggiunto, un elemento dall'elenco) deve vederlo
|
|
615
|
+
target.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'smooth' });
|
|
616
|
+
postSelect(target);
|
|
617
|
+
}
|
|
618
|
+
break;
|
|
619
|
+
}
|
|
620
|
+
case 'wr:drop:probe': {
|
|
621
|
+
const dragged = data.elementId ? document.querySelector(`[data-wr-id="${CSS.escape(data.elementId)}"]`) : null;
|
|
622
|
+
if (data.scroll)
|
|
623
|
+
window.scrollBy({ top: data.scroll * DROP_SCROLL_PX, behavior: 'instant' });
|
|
624
|
+
post({ type: 'wr:drop:target', target: data.elementId && !dragged ? null : wrDropTargetAt(dragged, data.x, data.y) });
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
case 'wr:style:preview':
|
|
628
|
+
if (data.styles)
|
|
629
|
+
showPreview(data.elementId, data.styles);
|
|
630
|
+
else
|
|
631
|
+
endPreview(data.settle === true);
|
|
632
|
+
break;
|
|
633
|
+
case 'wr:browse':
|
|
634
|
+
browsing = data.on === true;
|
|
635
|
+
if (browsing) {
|
|
636
|
+
selectedEl = null;
|
|
637
|
+
hoveredEl = null;
|
|
638
|
+
pointerOverEl = null;
|
|
639
|
+
clearHighlight();
|
|
640
|
+
postDeselect();
|
|
641
|
+
post({ type: 'wr:hover:clear' });
|
|
642
|
+
}
|
|
643
|
+
break;
|
|
644
|
+
case 'wr:highlight:clear':
|
|
645
|
+
selectedEl = null;
|
|
646
|
+
clearHighlight();
|
|
647
|
+
break;
|
|
648
|
+
case 'wr:navigate:push':
|
|
649
|
+
if (wrNavigate) {
|
|
650
|
+
wrNavigate(data.pathname);
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
history.pushState({}, '', data.pathname);
|
|
654
|
+
}
|
|
655
|
+
break;
|
|
656
|
+
case 'wr:undo':
|
|
657
|
+
wrUndo?.();
|
|
658
|
+
break;
|
|
659
|
+
case 'wr:redo':
|
|
660
|
+
wrRedo?.();
|
|
661
|
+
break;
|
|
662
|
+
case 'wr:page:create': {
|
|
663
|
+
const { pageName, component, lazy } = data;
|
|
664
|
+
wrCreatePage(pageName, component, lazy)
|
|
665
|
+
.then((res) => post(res.ok ? { type: 'wr:page:created', pageName } : { type: 'wr:page:created', pageName, error: res.error ?? `HTTP ${res.status}` }));
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
case 'wr:page:list': {
|
|
669
|
+
wrListPages().then((res) => post({ type: 'wr:page:list:response', pages: res.ok ? res.data?.pages ?? [] : [] }));
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
case 'wr:page:delete': {
|
|
673
|
+
const { pageName } = data;
|
|
674
|
+
wrDeletePage(pageName)
|
|
675
|
+
.then((res) => post(res.ok ? { type: 'wr:page:deleted', pageName } : { type: 'wr:page:deleted', pageName, error: res.error ?? `HTTP ${res.status}` }));
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
case 'wr:page:update': {
|
|
679
|
+
const { pageName, newPageName, newComponent, lazy, header, footer } = data;
|
|
680
|
+
wrUpdatePage(pageName, { newPageName, newComponent, lazy, header, footer })
|
|
681
|
+
.then((res) => post(res.ok ? { type: 'wr:page:updated', pageName } : { type: 'wr:page:updated', pageName, error: res.error ?? `HTTP ${res.status}` }));
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
case 'wr:seo:get': {
|
|
685
|
+
// The page asked for, or the one shown
|
|
686
|
+
wrGetSeo(data.pageName).then((response) => post({ type: 'wr:seo:get:response', ...response }));
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
case 'wr:seo:suggest': {
|
|
690
|
+
wrSuggestSeo(data.pageName, data.lang).then((response) => post({ type: 'wr:seo:suggest:response', ...response }));
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
case 'wr:check:run': {
|
|
694
|
+
wrCheckProject().then((response) => post({ type: 'wr:check:response', ...response }));
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
case 'wr:issue:mute': {
|
|
698
|
+
wrMuteIssue(data.key, data.muted).then((res) => post(res.ok ? { type: 'wr:issue:muted' } : { type: 'wr:issue:muted', error: res.error ?? `HTTP ${res.status}` }));
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
case 'wr:json:prune': {
|
|
702
|
+
wrPruneJson(data.page).then((response) => post({ type: 'wr:json:pruned', ...response }));
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
case 'wr:comments:get': {
|
|
706
|
+
postComments();
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
case 'wr:comment:add':
|
|
710
|
+
case 'wr:comment:resolve':
|
|
711
|
+
case 'wr:comment:delete': {
|
|
712
|
+
// One answer for all three: the list as it is now (or why it did not change)
|
|
713
|
+
const change = data.type === 'wr:comment:add' ? wrAddComment(data) : data.type === 'wr:comment:resolve' ? wrResolveComment(data.id, data.resolved) : wrDeleteComment(data.id);
|
|
714
|
+
change.then((res) => {
|
|
715
|
+
if (res.ok)
|
|
716
|
+
postComments();
|
|
717
|
+
else
|
|
718
|
+
post({ type: 'wr:comments:response', comments: [], open: 0, error: res.error ?? `HTTP ${res.status}` });
|
|
719
|
+
});
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
case 'wr:comments:marks': {
|
|
723
|
+
commentMarks = data.marks;
|
|
724
|
+
postCommentRects();
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
case 'wr:seo:set': {
|
|
728
|
+
const { type: _type, ...fields } = data;
|
|
729
|
+
wrSetSeo(fields).then((res) => post(res.ok ? { type: 'wr:seo:updated' } : { type: 'wr:seo:updated', error: res.error ?? `HTTP ${res.status}`, code: res.code }));
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
case 'wr:seo:site:set': {
|
|
733
|
+
const { type: _type, ...fields } = data;
|
|
734
|
+
wrSetSeoSite(fields).then((res) => post(res.ok ? { type: 'wr:seo:updated' } : { type: 'wr:seo:updated', error: res.error ?? `HTTP ${res.status}`, code: res.code }));
|
|
735
|
+
break;
|
|
736
|
+
}
|
|
737
|
+
case 'wr:langs:list': {
|
|
738
|
+
wrListLangs()
|
|
739
|
+
.then((langs) => post({ type: 'wr:langs:list:response', langs }))
|
|
740
|
+
.catch(() => post({ type: 'wr:langs:list:response', langs: null }));
|
|
741
|
+
break;
|
|
742
|
+
}
|
|
743
|
+
case 'wr:langs:update': {
|
|
744
|
+
const { langs } = data;
|
|
745
|
+
wrSaveLangs(langs)
|
|
746
|
+
.then((res) => post(res.ok ? { type: 'wr:langs:updated' } : { type: 'wr:langs:updated', error: res.error ?? `HTTP ${res.status}` }));
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
case 'wr:node:get': {
|
|
750
|
+
const { elementId } = data;
|
|
751
|
+
fetchNode(elementId).then(({ fields, values }) => post({ type: 'wr:node:get:response', elementId, fields, values }));
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
case 'wr:node:set': {
|
|
755
|
+
const { elementId, key, value, prevValue } = data;
|
|
756
|
+
const scope = scopeOfId(elementId);
|
|
757
|
+
putNode(elementId, key, value, scope)
|
|
758
|
+
.then(ok => {
|
|
759
|
+
if (ok && scope)
|
|
760
|
+
wrAstHistoryPush?.(elementId, key, prevValue, value, scope);
|
|
761
|
+
post({ type: 'wr:node:set:response', elementId, key, ok });
|
|
762
|
+
})
|
|
763
|
+
.catch(err => post({ type: 'wr:node:set:response', elementId, key, ok: false, error: String(err) }));
|
|
764
|
+
break;
|
|
765
|
+
}
|
|
766
|
+
case 'wr:style:catalog': {
|
|
767
|
+
wrOp('style-catalog', {})
|
|
768
|
+
.then(res => post({ type: 'wr:style:catalog:response', catalog: res.ok ? (res.data ?? null) : null, ...(res.ok ? {} : { error: res.error }) }))
|
|
769
|
+
.catch(err => post({ type: 'wr:style:catalog:response', catalog: null, error: String(err) }));
|
|
770
|
+
break;
|
|
771
|
+
}
|
|
772
|
+
case 'wr:style:get': {
|
|
773
|
+
fetchStyle(data.elementId).then(res => post({ type: 'wr:style:get:response', ...res }));
|
|
774
|
+
break;
|
|
775
|
+
}
|
|
776
|
+
case 'wr:style:set':
|
|
777
|
+
runElementStyle(data.elementId, 'style-set', { changes: data.changes, ...(data.everywhere ? { everywhere: true } : {}) });
|
|
778
|
+
break;
|
|
779
|
+
case 'wr:style:copy':
|
|
780
|
+
runElementStyle(data.elementId, 'style-copy', { from: data.from, to: data.to });
|
|
781
|
+
break;
|
|
782
|
+
case 'wr:styleset:list':
|
|
783
|
+
postStyleSets();
|
|
784
|
+
break;
|
|
785
|
+
case 'wr:styleset:create':
|
|
786
|
+
if (data.elementId)
|
|
787
|
+
runElementStyle(data.elementId, 'styleset-create', { name: data.name }, { action: 'create', name: data.name });
|
|
788
|
+
else
|
|
789
|
+
runStyleSetOp('create', 'styleset-create', { name: data.name }, data.name);
|
|
790
|
+
break;
|
|
791
|
+
case 'wr:styleset:apply':
|
|
792
|
+
runElementStyle(data.elementId, 'styleset-apply', { name: data.name }, { action: 'apply', name: data.name });
|
|
793
|
+
break;
|
|
794
|
+
case 'wr:styleset:detach':
|
|
795
|
+
runElementStyle(data.elementId, 'styleset-detach', {}, { action: 'detach' });
|
|
796
|
+
break;
|
|
797
|
+
case 'wr:styleset:detach-all':
|
|
798
|
+
runStyleSetOp('detach-all', 'styleset-detach-all', { name: data.name }, data.name);
|
|
799
|
+
break;
|
|
800
|
+
case 'wr:styleset:rename':
|
|
801
|
+
runStyleSetOp('rename', 'styleset-rename', { name: data.name, to: data.to }, data.to);
|
|
802
|
+
break;
|
|
803
|
+
case 'wr:styleset:delete':
|
|
804
|
+
runStyleSetOp('delete', 'styleset-delete', { name: data.name }, data.name);
|
|
805
|
+
break;
|
|
806
|
+
case 'wr:styleset:get':
|
|
807
|
+
runStyleSetStyle(data.name, 'style-get', {});
|
|
808
|
+
break;
|
|
809
|
+
case 'wr:styleset:set':
|
|
810
|
+
runStyleSetStyle(data.name, 'style-set', { changes: data.changes, ...(data.everywhere ? { everywhere: true } : {}) });
|
|
811
|
+
break;
|
|
812
|
+
case 'wr:styleset:copy':
|
|
813
|
+
runStyleSetStyle(data.name, 'style-copy', { from: data.from, to: data.to });
|
|
814
|
+
break;
|
|
815
|
+
case 'wr:node:delete': {
|
|
816
|
+
const { elementId } = data;
|
|
817
|
+
const scope = scopeOfId(elementId); // PRIMA: dopo la delete l'elemento non c'è più per chiederglielo
|
|
818
|
+
deleteNode(elementId, scope)
|
|
819
|
+
.then(async (result) => {
|
|
820
|
+
if (!result || !scope || 'error' in result) {
|
|
821
|
+
post({ type: 'wr:node:delete:response', elementId, removedIds: [], ok: false, error: result && 'error' in result ? result.error : 'no active file' });
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
const { removedIds, removedJsx, anchor, anchorMode } = result;
|
|
825
|
+
// I valori JSON dei nodi cancellati li ha già tolti l'operazione; la cronologia li legge dal React state
|
|
826
|
+
// (chiuso sopra, non da qui). Senza un posto da cui reinserirlo l'undo non può esistere: nella cronologia non entra (non la blocca)
|
|
827
|
+
const undoable = anchor !== null;
|
|
828
|
+
if (undoable)
|
|
829
|
+
wrNodeHistoryPush?.({ op: 'delete', scope, elementIds: [elementId], jsx: removedJsx, removedIds, anchor, anchorMode });
|
|
830
|
+
post({ type: 'wr:node:delete:response', elementId, removedIds, ok: true, undoable });
|
|
831
|
+
})
|
|
832
|
+
.catch(err => post({ type: 'wr:node:delete:response', elementId, removedIds: [], ok: false, error: String(err) }));
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
case 'wr:node:insert': {
|
|
836
|
+
runInsert(data);
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
case 'wr:node:duplicate': {
|
|
840
|
+
// The copy goes right after the original, with what it contains and the values it has: it is read from the code (node-source) and inserted like any block
|
|
841
|
+
const scope = scopeOfId(data.elementId);
|
|
842
|
+
const fail = (error, code) => post({ type: 'wr:node:insert:response', ok: false, ids: [], rootIds: [], undoable: false, idMap: {}, code, error });
|
|
843
|
+
if (!scope) {
|
|
844
|
+
fail('no active file');
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
// The header and the footer are one element each (the root of their component): a copy of it would have nowhere to go
|
|
848
|
+
if (document.querySelector(`[data-wr-id="${CSS.escape(data.elementId)}"]`)?.parentElement?.hasAttribute(WR_SCOPE_NAME_ATTR)) {
|
|
849
|
+
fail('the root of the header or the footer cannot be duplicated', 'component-root');
|
|
850
|
+
break;
|
|
851
|
+
}
|
|
852
|
+
wrOp('node-source', { file: scope.file, id: data.elementId })
|
|
853
|
+
.then(source => {
|
|
854
|
+
if (!source.ok || !source.data) {
|
|
855
|
+
fail(source.error ?? `HTTP ${source.status}`, source.code);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
runInsert({ jsx: source.data.jsx, anchor: { id: data.elementId }, anchorMode: 'after', jsonSeed: source.data.json });
|
|
859
|
+
})
|
|
860
|
+
.catch(err => fail(String(err)));
|
|
861
|
+
break;
|
|
862
|
+
}
|
|
863
|
+
case 'wr:node:move': {
|
|
864
|
+
const { elementId, anchor, anchorMode } = data;
|
|
865
|
+
const scope = scopeOfId(elementId);
|
|
866
|
+
const target = scopeOfAnchor(anchor);
|
|
867
|
+
const reply = (ok, code, error) => post({ type: 'wr:node:move:response', elementId, ok, code, error });
|
|
868
|
+
// Un elemento non cambia file: uscire dall'header per entrare nella pagina vorrebbe dire spostare anche il suo JSON
|
|
869
|
+
if (!scope || !target || scope.name !== target.name) {
|
|
870
|
+
reply(false, 'other-scope', 'an element can only be moved inside its own page, header or footer');
|
|
871
|
+
break;
|
|
872
|
+
}
|
|
873
|
+
moveNode(elementId, anchor, anchorMode, scope)
|
|
874
|
+
.then(result => {
|
|
875
|
+
if (!result.ok) {
|
|
876
|
+
reply(false, result.code, result.error);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
wrMoveHistoryPush?.({ scope, elementId, from: result.from, to: { anchor, mode: anchorMode } });
|
|
880
|
+
reply(true);
|
|
881
|
+
})
|
|
882
|
+
.catch(err => reply(false, undefined, String(err)));
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
case 'wr:theme:get': {
|
|
886
|
+
wrGetTheme()
|
|
887
|
+
.then(({ items, dark }) => post({ type: 'wr:theme:get:response', items, dark }))
|
|
888
|
+
.catch(err => post({ type: 'wr:theme:get:response', items: [], error: String(err) }));
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
891
|
+
case 'wr:theme:set-item': {
|
|
892
|
+
const { item } = data;
|
|
893
|
+
wrSetThemeItem(item)
|
|
894
|
+
.then((res) => post({ type: 'wr:theme:set-item:response', kind: item.kind, key: item.key, ok: res.ok, ...(res.ok ? {} : { error: res.error ?? `HTTP ${res.status}` }) }));
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
case 'wr:theme:dark': {
|
|
898
|
+
const { enabled } = data;
|
|
899
|
+
wrSetThemeDark(enabled)
|
|
900
|
+
.then((res) => post({ type: 'wr:theme:dark:response', enabled, ok: res.ok, ...(res.ok ? {} : { error: res.error ?? `HTTP ${res.status}` }) }));
|
|
901
|
+
break;
|
|
902
|
+
}
|
|
903
|
+
case 'wr:theme:remove-item': {
|
|
904
|
+
const { kind, key } = data;
|
|
905
|
+
wrRemoveThemeItem(kind, key)
|
|
906
|
+
.then((res) => post({ type: 'wr:theme:remove-item:response', kind, key, ok: res.ok, ...(res.ok ? {} : { error: res.error ?? `HTTP ${res.status}` }) }));
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
export function postHistoryState(canUndo, canRedo) {
|
|
912
|
+
post({ type: 'wr:history:state', canUndo, canRedo });
|
|
913
|
+
}
|
|
914
|
+
export function initWrBridge(editorOrigin) {
|
|
915
|
+
bridgeOrigin = editorOrigin;
|
|
916
|
+
if (window === window.parent)
|
|
917
|
+
return () => { };
|
|
918
|
+
selectedEl = null;
|
|
919
|
+
clearHighlight();
|
|
920
|
+
postDeselect();
|
|
921
|
+
scan();
|
|
922
|
+
const observer = new MutationObserver(() => { scan(); scheduleMeasure(); postCommentRects(); });
|
|
923
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
924
|
+
// The site's own theme (light/dark) is a class or an attribute on <html>
|
|
925
|
+
const themeObserver = new MutationObserver(scheduleMeasure);
|
|
926
|
+
themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'data-theme', 'style'] });
|
|
927
|
+
// The site's theme can also follow the system (`prefers-color-scheme`), and colors that transition settle a little after the class changes
|
|
928
|
+
const colorScheme = window.matchMedia?.('(prefers-color-scheme: dark)');
|
|
929
|
+
colorScheme?.addEventListener('change', scheduleMeasure);
|
|
930
|
+
const onTransitionEnd = (e) => { if (/color/.test(e.propertyName))
|
|
931
|
+
scheduleMeasure(); };
|
|
932
|
+
document.documentElement.addEventListener('transitionend', onTransitionEnd);
|
|
933
|
+
scheduleMeasure();
|
|
934
|
+
document.addEventListener('click', onClickInsideInteractive, true);
|
|
935
|
+
document.addEventListener('click', onClick);
|
|
936
|
+
document.addEventListener('keydown', onKeyDown);
|
|
937
|
+
document.addEventListener('pointerover', onPointerOver);
|
|
938
|
+
document.addEventListener('pointerout', onPointerOut);
|
|
939
|
+
window.addEventListener('scroll', onScroll, { passive: true });
|
|
940
|
+
window.addEventListener('resize', postCommentRects);
|
|
941
|
+
window.addEventListener('message', onMessage);
|
|
942
|
+
const geometryTimer = setInterval(syncGeometry, GEOMETRY_EVERY_MS);
|
|
943
|
+
const _push = history.pushState.bind(history);
|
|
944
|
+
const _replace = history.replaceState.bind(history);
|
|
945
|
+
history.pushState = (...args) => { _push(...args); sendNav(); };
|
|
946
|
+
history.replaceState = (...args) => { _replace(...args); sendNav(); };
|
|
947
|
+
window.addEventListener('popstate', sendNav);
|
|
948
|
+
sendNav();
|
|
949
|
+
return () => {
|
|
950
|
+
observer.disconnect();
|
|
951
|
+
themeObserver.disconnect();
|
|
952
|
+
colorScheme?.removeEventListener('change', scheduleMeasure);
|
|
953
|
+
document.documentElement.removeEventListener('transitionend', onTransitionEnd);
|
|
954
|
+
clearTimeout(measureTimer);
|
|
955
|
+
clearInterval(geometryTimer);
|
|
956
|
+
document.removeEventListener('click', onClickInsideInteractive, true);
|
|
957
|
+
document.removeEventListener('click', onClick);
|
|
958
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
959
|
+
document.removeEventListener('pointerover', onPointerOver);
|
|
960
|
+
document.removeEventListener('pointerout', onPointerOut);
|
|
961
|
+
window.removeEventListener('scroll', onScroll);
|
|
962
|
+
window.removeEventListener('resize', postCommentRects);
|
|
963
|
+
window.removeEventListener('message', onMessage);
|
|
964
|
+
window.removeEventListener('popstate', sendNav);
|
|
965
|
+
history.pushState = _push;
|
|
966
|
+
history.replaceState = _replace;
|
|
967
|
+
lastNavPath = null;
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
//# sourceMappingURL=bridge.js.map
|