@templatical/editor 0.6.7 → 0.7.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 +26 -1
- package/dist/{AccessibilityPanel-B2MT0M58.js → AccessibilityPanel-D-PqmHdH.js} +2 -2
- package/dist/{AiChatSidebar-w5ek3Z76.js → AiChatSidebar-BwLECwsO.js} +1 -1
- package/dist/{AiFeatureMenu-ChlAWywJ.js → AiFeatureMenu-CVHKharv.js} +3 -3
- package/dist/{BlockA11yBadge-C0S6kPC4.js → BlockA11yBadge-BFIw0h1m.js} +2 -2
- package/dist/{CloudEditor-DYYaScFe.js → CloudEditor-DdZatjUe.js} +30 -21
- package/dist/{CollaboratorBar-Bo8vtPId.js → CollaboratorBar-VZKOv_Zn.js} +2 -2
- package/dist/{CommentsSidebar-BQROg36f.js → CommentsSidebar-BiRtaXYD.js} +3 -3
- package/dist/{CountdownBlock-Bxqe7zwL.js → CountdownBlock-Cq8A8WrM.js} +1 -1
- package/dist/{CountdownToolbar-CpFAnjSo.js → CountdownToolbar-CojjlZet.js} +2 -2
- package/dist/{DesignReferenceSidebar-CfqpcWX6.js → DesignReferenceSidebar-Ci9HIGbf.js} +2 -2
- package/dist/ModuleBrowserModal-D7IYx1Nh.js +206 -0
- package/dist/{ModulePreviewCanvas-Cw9GeGus.js → ModulePreviewCanvas-I-uMcK5G.js} +2 -2
- package/dist/{NumberWithSuffix-Dw8dN1Pt.js → NumberWithSuffix-CLTBb2Rj.js} +1 -1
- package/dist/{ParagraphEditor-DjDiUzmv.js → ParagraphEditor-BRQTNDFO.js} +156 -151
- package/dist/{RichTextEditorContent-CK3Om7ES.js → RichTextEditorContent-C3QSg7gd.js} +32 -28
- package/dist/{SaveModuleDialog-CSUPmfRP.js → SaveModuleDialog-DGGGNZfm.js} +6 -6
- package/dist/{SnapshotHistory-DwX2fj6N.js → SnapshotHistory-C97Iw6xw.js} +3 -3
- package/dist/{TemplateScoringPanel-BIAeCAEP.js → TemplateScoringPanel-V79yrEPC.js} +3 -3
- package/dist/{TestEmailModal-CIlBvWWn.js → TestEmailModal-B7S8H-VG.js} +3 -3
- package/dist/{TitleEditor-BCV5k6wj.js → TitleEditor-DhFTYzrw.js} +65 -60
- package/dist/{TplModal-C3Hq9b58.js → TplModal-BgABm6ju.js} +21 -17
- package/dist/{blockTypeIcons-iUurP50H.js → blockTypeIcons-BSf-3RJ-.js} +1 -1
- package/dist/bundle-stats.json +7 -7
- package/dist/cdn/chunks/{AccessibilityPanel-DnNB30b0.js → AccessibilityPanel-kgNBRaZV.js} +28 -28
- package/dist/cdn/chunks/{AccessibilityPanel-DnNB30b0.js.map → AccessibilityPanel-kgNBRaZV.js.map} +1 -1
- package/dist/cdn/chunks/{AiFeatureMenu-BKBh_ueF.js → AiFeatureMenu-CRkzwdMg.js} +15 -15
- package/dist/cdn/chunks/{AiFeatureMenu-BKBh_ueF.js.map → AiFeatureMenu-CRkzwdMg.js.map} +1 -1
- package/dist/cdn/chunks/{BlockA11yBadge-CnBu14Fj.js → BlockA11yBadge-BfuH2Hrg.js} +9 -9
- package/dist/cdn/chunks/{BlockA11yBadge-CnBu14Fj.js.map → BlockA11yBadge-BfuH2Hrg.js.map} +1 -1
- package/dist/cdn/chunks/{CloudEditor-CmR17piA.js → CloudEditor-h5JXI8WQ.js} +252 -243
- package/dist/cdn/chunks/CloudEditor-h5JXI8WQ.js.map +1 -0
- package/dist/cdn/chunks/{CollaboratorBar-BejZaFtY.js → CollaboratorBar-CMl3lAZU.js} +15 -15
- package/dist/cdn/chunks/{CollaboratorBar-BejZaFtY.js.map → CollaboratorBar-CMl3lAZU.js.map} +1 -1
- package/dist/cdn/chunks/{CountdownBlock-CjvUEHhE.js → CountdownBlock-D1RoaOgF.js} +18 -18
- package/dist/cdn/chunks/{CountdownBlock-CjvUEHhE.js.map → CountdownBlock-D1RoaOgF.js.map} +1 -1
- package/dist/cdn/chunks/{CountdownToolbar-Caokkqsg.js → CountdownToolbar-DltwuOer.js} +42 -42
- package/dist/cdn/chunks/{CountdownToolbar-Caokkqsg.js.map → CountdownToolbar-DltwuOer.js.map} +1 -1
- package/dist/cdn/chunks/{ModuleBrowserModal-Tb9a7L-K.js → ModuleBrowserModal-3uu2mXR9.js} +36 -36
- package/dist/cdn/chunks/{ModuleBrowserModal-Tb9a7L-K.js.map → ModuleBrowserModal-3uu2mXR9.js.map} +1 -1
- package/dist/cdn/chunks/{ModulePreviewCanvas-JWIEv5oS.js → ModulePreviewCanvas-K9CQFr7p.js} +37 -37
- package/dist/cdn/chunks/{ModulePreviewCanvas-JWIEv5oS.js.map → ModulePreviewCanvas-K9CQFr7p.js.map} +1 -1
- package/dist/cdn/chunks/{NumberWithSuffix-DrV8eumz.js → NumberWithSuffix-BIFgtqGs.js} +74 -74
- package/dist/cdn/chunks/{NumberWithSuffix-DrV8eumz.js.map → NumberWithSuffix-BIFgtqGs.js.map} +1 -1
- package/dist/cdn/chunks/{ParagraphEditor-CSfShBAO.js → ParagraphEditor-DlpZX_x4.js} +180 -176
- package/dist/cdn/chunks/ParagraphEditor-DlpZX_x4.js.map +1 -0
- package/dist/cdn/chunks/{RichTextEditorContent-DgqZzl8n.js → RichTextEditorContent-CPGT8h-O.js} +40 -37
- package/dist/cdn/chunks/RichTextEditorContent-CPGT8h-O.js.map +1 -0
- package/dist/cdn/chunks/{SaveModuleDialog-CxTwrIgx.js → SaveModuleDialog-_uaJscj1.js} +24 -24
- package/dist/cdn/chunks/{SaveModuleDialog-CxTwrIgx.js.map → SaveModuleDialog-_uaJscj1.js.map} +1 -1
- package/dist/cdn/chunks/TitleEditor-Kerz6US8.js +175 -0
- package/dist/cdn/chunks/TitleEditor-Kerz6US8.js.map +1 -0
- package/dist/cdn/chunks/{blockTypeIcons-DMt-2qR6.js → blockTypeIcons-BeOhGtoo.js} +7 -7
- package/dist/cdn/chunks/{blockTypeIcons-DMt-2qR6.js.map → blockTypeIcons-BeOhGtoo.js.map} +1 -1
- package/dist/cdn/chunks/{draggable-CNhyCGIO.js → draggable-P6QWzy4g.js} +1182 -1171
- package/dist/cdn/chunks/{draggable-CNhyCGIO.js.map → draggable-P6QWzy4g.js.map} +1 -1
- package/dist/cdn/chunks/{extensions-CUvwrffu.js → extensions-BZYkPlKr.js} +107 -108
- package/dist/cdn/chunks/extensions-BZYkPlKr.js.map +1 -0
- package/dist/cdn/chunks/{features-U3nzKc-R.js → features-DzIN-Nua.js} +925 -896
- package/dist/cdn/chunks/features-DzIN-Nua.js.map +1 -0
- package/dist/cdn/chunks/{icons-QcjADKIW.js → icons-Nvr6w13E.js} +2 -2
- package/dist/cdn/chunks/{icons-QcjADKIW.js.map → icons-Nvr6w13E.js.map} +1 -1
- package/dist/cdn/chunks/{media-library-Zcd_GInj.js → media-library-BzT7BoGU.js} +1189 -1183
- package/dist/cdn/chunks/media-library-BzT7BoGU.js.map +1 -0
- package/dist/cdn/chunks/{quality-8eo6DM3p.js → quality-C328caFm.js} +45 -45
- package/dist/cdn/chunks/{quality-8eo6DM3p.js.map → quality-C328caFm.js.map} +1 -1
- package/dist/cdn/chunks/{renderer-eHJyPiJH.js → renderer-DYpHh_uV.js} +20 -20
- package/dist/cdn/chunks/{renderer-eHJyPiJH.js.map → renderer-DYpHh_uV.js.map} +1 -1
- package/dist/cdn/chunks/{src-CthVYW_o.js → src-DKNTQJVO.js} +144 -144
- package/dist/cdn/chunks/{src-CthVYW_o.js.map → src-DKNTQJVO.js.map} +1 -1
- package/dist/cdn/chunks/{styles-CV5w3kjq.js → styles-DaTht5d7.js} +924 -899
- package/dist/cdn/chunks/styles-DaTht5d7.js.map +1 -0
- package/dist/cdn/chunks/{tiptap-IyIsncxY.js → tiptap-BwTCLVWl.js} +7 -7
- package/dist/cdn/chunks/{tiptap-IyIsncxY.js.map → tiptap-BwTCLVWl.js.map} +1 -1
- package/dist/cdn/editor.css +1 -1
- package/dist/cdn/editor.js +197 -146
- package/dist/cdn/editor.js.map +1 -1
- package/dist/{extensions-BF39Siqk.js → extensions-D-J02CiP.js} +50 -51
- package/dist/index.d.ts +56 -11
- package/dist/{keys-Bqs_0du9.js → keys-B5SJtPWf.js} +3 -3
- package/dist/style.css +1 -1
- package/dist/{styles-BgmKdc2x.js → styles-D_fEz49o.js} +497 -472
- package/dist/templatical-editor.js +182 -131
- package/dist/{useCloudI18n-CL_AwWwi.js → useCloudI18n-ByEMykjO.js} +1 -1
- package/dist/{useEditorCore-CnXrv71D.js → useEditorCore-uCU9Ny0M.js} +637 -619
- package/dist/{useI18n-CgmQftNf.js → useI18n-PEB8ioi_.js} +1 -1
- package/dist/{useMergeTag-vpwrZ9eQ.js → useMergeTag-C47xwU7X.js} +2 -2
- package/dist/usePopoverRoot-BxJrqnMD.js +8 -0
- package/package.json +7 -7
- package/dist/ModuleBrowserModal-CKhsaPnA.js +0 -206
- package/dist/cdn/chunks/CloudEditor-CmR17piA.js.map +0 -1
- package/dist/cdn/chunks/ParagraphEditor-CSfShBAO.js.map +0 -1
- package/dist/cdn/chunks/RichTextEditorContent-DgqZzl8n.js.map +0 -1
- package/dist/cdn/chunks/TitleEditor-DlqV7ODD.js +0 -171
- package/dist/cdn/chunks/TitleEditor-DlqV7ODD.js.map +0 -1
- package/dist/cdn/chunks/extensions-CUvwrffu.js.map +0 -1
- package/dist/cdn/chunks/features-U3nzKc-R.js.map +0 -1
- package/dist/cdn/chunks/media-library-Zcd_GInj.js.map +0 -1
- package/dist/cdn/chunks/styles-CV5w3kjq.js.map +0 -1
- /package/dist/{de-DcVOh9Fp.js → de-BhIWu_bO.js} +0 -0
- /package/dist/{de-DCaaCE5s.js → de-Di4MEjjx.js} +0 -0
- /package/dist/{emojiData-PQyVa4bU.js → emojiData-DX3E0XT-.js} +0 -0
- /package/dist/{en-DXCyK4-X.js → en-BSuzi-Pd.js} +0 -0
- /package/dist/{en-TZVJ_f6v.js → en-D7HRbYah.js} +0 -0
- /package/dist/{pt-BR-Vq7D7c11.js → pt-BR-CCVBRais.js} +0 -0
- /package/dist/{pt-BR-BMGasLBa.js → pt-BR-K32lt6YP.js} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { C as e, H as t, M as n, O as r, P as i, V as a, Z as o,
|
|
2
|
-
import {
|
|
3
|
-
import { _ as O, g as k, h as A, n as j, r as M, t as N, v as P, y as F } from "./tiptap-
|
|
1
|
+
import { C as e, H as t, M as n, O as r, P as i, V as a, Z as o, c as s, ct as c, f as l, g as u, h as d, it as f, l as p, m, o as h, ot as g, p as _, r as v, st as y, u as b, x } from "./draggable-P6QWzy4g.js";
|
|
2
|
+
import { Pt as S, Q as C, _n as w, gn as T, hn as E, pn as D } from "./features-DzIN-Nua.js";
|
|
3
|
+
import { _ as O, g as k, h as A, n as j, r as M, t as N, v as P, y as F } from "./tiptap-BwTCLVWl.js";
|
|
4
4
|
//#region src/extensions/FontSize.ts
|
|
5
5
|
var I = A.create({
|
|
6
6
|
name: "fontSize",
|
|
@@ -82,9 +82,9 @@ var I = A.create({
|
|
|
82
82
|
updateAttributes: { type: Function }
|
|
83
83
|
},
|
|
84
84
|
setup(e) {
|
|
85
|
-
let i = e, { syntax:
|
|
85
|
+
let i = e, { syntax: v } = C(), { t: b } = S(), x = l(() => w(i.node.attrs.value, v)), T = l(() => E(i.node.attrs.value, v)), D = o(!1), O = o(""), k = o(null), A = !1;
|
|
86
86
|
function M() {
|
|
87
|
-
O.value = i.node.attrs.value, A = !1,
|
|
87
|
+
O.value = i.node.attrs.value, A = !1, D.value = !0, r(() => {
|
|
88
88
|
k.value?.focus(), k.value?.select();
|
|
89
89
|
});
|
|
90
90
|
}
|
|
@@ -93,24 +93,24 @@ var I = A.create({
|
|
|
93
93
|
A = !0;
|
|
94
94
|
let e = O.value.trim();
|
|
95
95
|
if (!e) {
|
|
96
|
-
|
|
96
|
+
D.value = !1;
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
99
99
|
e !== i.node.attrs.value && i.updateAttributes({
|
|
100
100
|
value: e,
|
|
101
|
-
keyword:
|
|
102
|
-
}),
|
|
101
|
+
keyword: w(e, v) ? E(e, v) : ""
|
|
102
|
+
}), D.value = !1;
|
|
103
103
|
}
|
|
104
104
|
function P(e) {
|
|
105
|
-
e.key === "Enter" ? (e.preventDefault(), N()) : e.key === "Escape" && (
|
|
105
|
+
e.key === "Enter" ? (e.preventDefault(), N()) : e.key === "Escape" && (D.value = !1);
|
|
106
106
|
}
|
|
107
|
-
return (r, i) => (n(),
|
|
107
|
+
return (r, i) => (n(), m(f(j), {
|
|
108
108
|
as: "span",
|
|
109
|
-
class:
|
|
110
|
-
style:
|
|
109
|
+
class: g(x.value ? "tpl-logic-merge-tag-node tpl:group tpl:mx-0.5 tpl:inline-flex tpl:items-center tpl:gap-1 tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[0.8em] tpl:font-bold tpl:tracking-wide tpl:uppercase tpl:select-none" : ""),
|
|
110
|
+
style: y(x.value ? "background-color: transparent; border: 1.5px solid color-mix(in srgb, var(--tpl-primary) 50%, transparent); color: var(--tpl-primary);" : ""),
|
|
111
111
|
contenteditable: "false"
|
|
112
112
|
}, {
|
|
113
|
-
default: a(() => [
|
|
113
|
+
default: a(() => [D.value ? t((n(), u("input", {
|
|
114
114
|
key: 0,
|
|
115
115
|
ref_key: "inputRef",
|
|
116
116
|
ref: k,
|
|
@@ -119,30 +119,30 @@ var I = A.create({
|
|
|
119
119
|
class: "tpl:w-40 tpl:rounded tpl:border-none tpl:bg-transparent tpl:px-0.5 tpl:py-0 tpl:text-[1em] tpl:font-medium tpl:normal-case tpl:outline-none tpl:text-[var(--tpl-primary)]",
|
|
120
120
|
onBlur: N,
|
|
121
121
|
onKeydown: P
|
|
122
|
-
}, null, 544)), [[
|
|
122
|
+
}, null, 544)), [[h, O.value]]) : x.value ? (n(), u("span", {
|
|
123
123
|
key: 1,
|
|
124
124
|
role: "button",
|
|
125
125
|
tabindex: "0",
|
|
126
|
-
"aria-label":
|
|
126
|
+
"aria-label": f(b).mergeTag.editValue,
|
|
127
127
|
class: "tpl-tooltip tpl:cursor-pointer",
|
|
128
128
|
"data-tooltip": e.node.attrs.value,
|
|
129
|
-
onClick:
|
|
130
|
-
onKeydown: [
|
|
131
|
-
},
|
|
129
|
+
onClick: p(M, ["stop"]),
|
|
130
|
+
onKeydown: [s(p(M, ["stop"]), ["enter"]), s(p(M, ["prevent", "stop"]), ["space"])]
|
|
131
|
+
}, c(T.value), 41, ee)) : (n(), u("span", {
|
|
132
132
|
key: 2,
|
|
133
133
|
role: "button",
|
|
134
134
|
tabindex: "0",
|
|
135
|
-
"aria-label":
|
|
136
|
-
onClick:
|
|
137
|
-
onKeydown: [
|
|
138
|
-
},
|
|
135
|
+
"aria-label": f(b).mergeTag.editValue,
|
|
136
|
+
onClick: p(M, ["stop"]),
|
|
137
|
+
onKeydown: [s(p(M, ["stop"]), ["enter"]), s(p(M, ["prevent", "stop"]), ["space"])]
|
|
138
|
+
}, c(e.node.attrs.value), 41, te)), x.value ? (n(), u("button", {
|
|
139
139
|
key: 3,
|
|
140
140
|
type: "button",
|
|
141
|
-
"aria-label":
|
|
141
|
+
"aria-label": f(b).mergeTag.deleteMergeTag,
|
|
142
142
|
class: "tpl-merge-tag-delete tpl:flex tpl:size-5 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-full tpl:border-none tpl:bg-transparent tpl:p-0 tpl:opacity-60 tpl:transition-all hover:tpl:opacity-100 tpl:text-[var(--tpl-primary)]",
|
|
143
143
|
contenteditable: "false",
|
|
144
|
-
onClick: i[1] ||=
|
|
145
|
-
}, [...i[2] ||= [
|
|
144
|
+
onClick: i[1] ||= p((...t) => e.deleteNode && e.deleteNode(...t), ["stop", "prevent"])
|
|
145
|
+
}, [...i[2] ||= [_("svg", {
|
|
146
146
|
width: "10",
|
|
147
147
|
height: "10",
|
|
148
148
|
viewBox: "0 0 24 24",
|
|
@@ -150,12 +150,12 @@ var I = A.create({
|
|
|
150
150
|
stroke: "currentColor",
|
|
151
151
|
"stroke-width": "3",
|
|
152
152
|
"aria-hidden": "true"
|
|
153
|
-
}, [
|
|
153
|
+
}, [_("line", {
|
|
154
154
|
x1: "18",
|
|
155
155
|
y1: "6",
|
|
156
156
|
x2: "6",
|
|
157
157
|
y2: "18"
|
|
158
|
-
}),
|
|
158
|
+
}), _("line", {
|
|
159
159
|
x1: "6",
|
|
160
160
|
y1: "6",
|
|
161
161
|
x2: "18",
|
|
@@ -168,10 +168,8 @@ var I = A.create({
|
|
|
168
168
|
//#endregion
|
|
169
169
|
//#region src/extensions/isNodeSelected.ts
|
|
170
170
|
function B(e, t) {
|
|
171
|
-
let {
|
|
172
|
-
return
|
|
173
|
-
if (e.type.name === t) return a = !0, !1;
|
|
174
|
-
}), !!(a || r.pos > 0 && r.nodeBefore?.type.name === t || r.nodeAfter?.type.name === t);
|
|
171
|
+
let { $from: n, $to: r } = e.state.selection;
|
|
172
|
+
return n.pos === r.pos ? n.pos > 0 && n.nodeBefore?.type.name === t || n.nodeAfter?.type.name === t : !1;
|
|
175
173
|
}
|
|
176
174
|
//#endregion
|
|
177
175
|
//#region src/extensions/renderVueNodeView.ts
|
|
@@ -186,7 +184,7 @@ var H = O.create({
|
|
|
186
184
|
inline: !0,
|
|
187
185
|
atom: !0,
|
|
188
186
|
addOptions() {
|
|
189
|
-
return { syntax:
|
|
187
|
+
return { syntax: D.liquid };
|
|
190
188
|
},
|
|
191
189
|
addAttributes() {
|
|
192
190
|
return {
|
|
@@ -204,12 +202,12 @@ var H = O.create({
|
|
|
204
202
|
return [{ tag: "span[data-logic-merge-tag]" }];
|
|
205
203
|
},
|
|
206
204
|
renderHTML({ node: e, HTMLAttributes: t }) {
|
|
207
|
-
if (!
|
|
205
|
+
if (!w(e.attrs.value, this.options.syntax)) return [
|
|
208
206
|
"span",
|
|
209
207
|
{},
|
|
210
208
|
e.attrs.value
|
|
211
209
|
];
|
|
212
|
-
let n =
|
|
210
|
+
let n = E(e.attrs.value, this.options.syntax);
|
|
213
211
|
return [
|
|
214
212
|
"span",
|
|
215
213
|
F(t, {
|
|
@@ -233,8 +231,8 @@ var H = O.create({
|
|
|
233
231
|
find: RegExp(this.options.syntax.logic.source + "$", ""),
|
|
234
232
|
handler: ({ state: e, range: t, match: n }) => {
|
|
235
233
|
let r = n[0];
|
|
236
|
-
if (!
|
|
237
|
-
let i =
|
|
234
|
+
if (!w(r, this.options.syntax)) return;
|
|
235
|
+
let i = E(r, this.options.syntax), a = this.type.create({
|
|
238
236
|
value: r,
|
|
239
237
|
keyword: i
|
|
240
238
|
});
|
|
@@ -247,8 +245,8 @@ var H = O.create({
|
|
|
247
245
|
find: new RegExp(this.options.syntax.logic.source, "g"),
|
|
248
246
|
handler: ({ state: e, range: t, match: n }) => {
|
|
249
247
|
let r = n[0];
|
|
250
|
-
if (!
|
|
251
|
-
let i =
|
|
248
|
+
if (!w(r, this.options.syntax)) return;
|
|
249
|
+
let i = E(r, this.options.syntax), a = this.type.create({
|
|
252
250
|
value: r,
|
|
253
251
|
keyword: i
|
|
254
252
|
});
|
|
@@ -268,9 +266,9 @@ var H = O.create({
|
|
|
268
266
|
updateAttributes: { type: Function }
|
|
269
267
|
},
|
|
270
268
|
setup(e) {
|
|
271
|
-
let i = e, { getMergeTagLabel:
|
|
269
|
+
let i = e, { getMergeTagLabel: d } = C(), { t: g } = S(), v = l(() => d(i.node.attrs.value)), y = o(!1), b = o(""), x = o(null);
|
|
272
270
|
function w() {
|
|
273
|
-
b.value = i.node.attrs.value,
|
|
271
|
+
b.value = i.node.attrs.value, y.value = !0, r(() => {
|
|
274
272
|
x.value?.focus(), x.value?.select();
|
|
275
273
|
});
|
|
276
274
|
}
|
|
@@ -278,19 +276,19 @@ var H = O.create({
|
|
|
278
276
|
let e = b.value.trim();
|
|
279
277
|
e && e !== i.node.attrs.value && i.updateAttributes({
|
|
280
278
|
value: e,
|
|
281
|
-
label:
|
|
282
|
-
}),
|
|
279
|
+
label: d(e)
|
|
280
|
+
}), y.value = !1;
|
|
283
281
|
}
|
|
284
282
|
function E(e) {
|
|
285
|
-
e.key === "Enter" ? (e.preventDefault(), T()) : e.key === "Escape" && (
|
|
283
|
+
e.key === "Enter" ? (e.preventDefault(), T()) : e.key === "Escape" && (y.value = !1);
|
|
286
284
|
}
|
|
287
|
-
return (r, i) => (n(),
|
|
285
|
+
return (r, i) => (n(), m(f(j), {
|
|
288
286
|
as: "span",
|
|
289
287
|
class: "tpl-merge-tag-node tpl:group tpl:mx-0.5 tpl:inline-flex tpl:items-center tpl:gap-1 tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[0.9em] tpl:font-medium tpl:select-none tpl:text-[var(--tpl-primary)]",
|
|
290
288
|
style: { "background-color": "color-mix(in srgb, var(--tpl-primary) 20%, transparent)" },
|
|
291
289
|
contenteditable: "false"
|
|
292
290
|
}, {
|
|
293
|
-
default: a(() => [
|
|
291
|
+
default: a(() => [y.value ? t((n(), u("input", {
|
|
294
292
|
key: 0,
|
|
295
293
|
ref_key: "inputRef",
|
|
296
294
|
ref: x,
|
|
@@ -299,22 +297,22 @@ var H = O.create({
|
|
|
299
297
|
class: "tpl:w-32 tpl:rounded tpl:border-none tpl:bg-transparent tpl:px-0.5 tpl:py-0 tpl:text-[1em] tpl:font-medium tpl:outline-none tpl:text-[var(--tpl-primary)]",
|
|
300
298
|
onBlur: T,
|
|
301
299
|
onKeydown: E
|
|
302
|
-
}, null, 544)), [[
|
|
300
|
+
}, null, 544)), [[h, b.value]]) : (n(), u("span", {
|
|
303
301
|
key: 1,
|
|
304
302
|
role: "button",
|
|
305
303
|
tabindex: "0",
|
|
306
|
-
"aria-label":
|
|
304
|
+
"aria-label": f(g).mergeTag.editValue,
|
|
307
305
|
class: "tpl-tooltip tpl:cursor-pointer",
|
|
308
306
|
"data-tooltip": e.node.attrs.value,
|
|
309
|
-
onClick:
|
|
310
|
-
onKeydown: [
|
|
311
|
-
},
|
|
307
|
+
onClick: p(w, ["stop"]),
|
|
308
|
+
onKeydown: [s(p(w, ["stop"]), ["enter"]), s(p(w, ["prevent", "stop"]), ["space"])]
|
|
309
|
+
}, c(v.value), 41, U)), _("button", {
|
|
312
310
|
type: "button",
|
|
313
|
-
"aria-label":
|
|
311
|
+
"aria-label": f(g).mergeTag.deleteMergeTag,
|
|
314
312
|
class: "tpl-merge-tag-delete tpl:flex tpl:size-5 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-full tpl:border-none tpl:bg-transparent tpl:p-0 tpl:opacity-60 tpl:transition-all hover:tpl:opacity-100 tpl:text-[var(--tpl-primary)]",
|
|
315
313
|
contenteditable: "false",
|
|
316
|
-
onClick: i[1] ||=
|
|
317
|
-
}, [...i[2] ||= [
|
|
314
|
+
onClick: i[1] ||= p((...t) => e.deleteNode && e.deleteNode(...t), ["stop", "prevent"])
|
|
315
|
+
}, [...i[2] ||= [_("svg", {
|
|
318
316
|
width: "10",
|
|
319
317
|
height: "10",
|
|
320
318
|
viewBox: "0 0 24 24",
|
|
@@ -322,12 +320,12 @@ var H = O.create({
|
|
|
322
320
|
stroke: "currentColor",
|
|
323
321
|
"stroke-width": "3",
|
|
324
322
|
"aria-hidden": "true"
|
|
325
|
-
}, [
|
|
323
|
+
}, [_("line", {
|
|
326
324
|
x1: "18",
|
|
327
325
|
y1: "6",
|
|
328
326
|
x2: "6",
|
|
329
327
|
y2: "18"
|
|
330
|
-
}),
|
|
328
|
+
}), _("line", {
|
|
331
329
|
x1: "6",
|
|
332
330
|
y1: "6",
|
|
333
331
|
x2: "18",
|
|
@@ -344,7 +342,7 @@ var H = O.create({
|
|
|
344
342
|
addOptions() {
|
|
345
343
|
return {
|
|
346
344
|
mergeTags: [],
|
|
347
|
-
syntax:
|
|
345
|
+
syntax: D.liquid
|
|
348
346
|
};
|
|
349
347
|
},
|
|
350
348
|
addAttributes() {
|
|
@@ -363,7 +361,7 @@ var H = O.create({
|
|
|
363
361
|
return [{ tag: "span[data-merge-tag]" }];
|
|
364
362
|
},
|
|
365
363
|
renderHTML({ node: e, HTMLAttributes: t }) {
|
|
366
|
-
let n =
|
|
364
|
+
let n = T(e.attrs.value, this.options.mergeTags);
|
|
367
365
|
return [
|
|
368
366
|
"span",
|
|
369
367
|
F(t, {
|
|
@@ -392,7 +390,7 @@ var H = O.create({
|
|
|
392
390
|
return [new k({
|
|
393
391
|
find: RegExp(this.options.syntax.value.source + "$", ""),
|
|
394
392
|
handler: ({ state: e, range: t, match: n }) => {
|
|
395
|
-
let r = n[0], i =
|
|
393
|
+
let r = n[0], i = T(r, this.options.mergeTags), a = this.type.create({
|
|
396
394
|
label: i,
|
|
397
395
|
value: r
|
|
398
396
|
});
|
|
@@ -404,7 +402,7 @@ var H = O.create({
|
|
|
404
402
|
return [new P({
|
|
405
403
|
find: new RegExp(this.options.syntax.value.source, "g"),
|
|
406
404
|
handler: ({ state: e, range: t, match: n }) => {
|
|
407
|
-
let r = n[0], i =
|
|
405
|
+
let r = n[0], i = T(r, this.options.mergeTags), a = this.type.create({
|
|
408
406
|
label: i,
|
|
409
407
|
value: r
|
|
410
408
|
});
|
|
@@ -442,7 +440,7 @@ var H = O.create({
|
|
|
442
440
|
class: "tpl:min-w-[200px] tpl:max-w-[320px] tpl:max-h-[50vh] tpl:overflow-y-auto tpl:rounded-[var(--tpl-radius)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:py-1 tpl:shadow-lg",
|
|
443
441
|
role: "listbox",
|
|
444
442
|
"data-testid": "merge-tag-suggestion-list"
|
|
445
|
-
}, [e.items.length === 0 ? (n(), u("div", J,
|
|
443
|
+
}, [e.items.length === 0 ? (n(), u("div", J, c(e.emptyText), 1)) : d("", !0), (n(!0), u(b, null, i(e.items, (i, a) => (n(), u("button", {
|
|
446
444
|
key: i.value,
|
|
447
445
|
id: r(a),
|
|
448
446
|
type: "button",
|
|
@@ -450,10 +448,10 @@ var H = O.create({
|
|
|
450
448
|
"aria-selected": a === e.selectedIndex,
|
|
451
449
|
"data-selected": a === e.selectedIndex ? "true" : "false",
|
|
452
450
|
"data-merge-tag-value": i.value,
|
|
453
|
-
class:
|
|
454
|
-
onMousedown:
|
|
451
|
+
class: g(["tpl:flex tpl:w-full tpl:flex-col tpl:items-start tpl:gap-0.5 tpl:px-3 tpl:py-1.5 tpl:text-left tpl:text-xs tpl:transition-colors", a === e.selectedIndex ? "tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-primary)]" : "tpl:text-[var(--tpl-text)] hover:tpl:bg-[var(--tpl-bg-hover)]"]),
|
|
452
|
+
onMousedown: p((e) => t.$emit("select", i), ["prevent", "stop"]),
|
|
455
453
|
onMousemove: (n) => a !== e.selectedIndex && t.$emit("hover", a)
|
|
456
|
-
}, [
|
|
454
|
+
}, [_("span", X, c(i.label), 1), _("span", re, c(i.value), 1)], 42, Y))), 128))], 8, q));
|
|
457
455
|
}
|
|
458
456
|
}), Z = 10, ae = 0;
|
|
459
457
|
function Q(e, t) {
|
|
@@ -472,11 +470,12 @@ var oe = A.create({
|
|
|
472
470
|
return {
|
|
473
471
|
mergeTags: [],
|
|
474
472
|
char: "{{",
|
|
475
|
-
emptyText: "No matching merge tags"
|
|
473
|
+
emptyText: "No matching merge tags",
|
|
474
|
+
popoverRoot: null
|
|
476
475
|
};
|
|
477
476
|
},
|
|
478
477
|
addProseMirrorPlugins() {
|
|
479
|
-
let t = this.options.mergeTags, n = this.options.emptyText, r = {
|
|
478
|
+
let t = this.options.mergeTags, n = this.options.emptyText, r = this.options.popoverRoot, i = {
|
|
480
479
|
char: this.options.char,
|
|
481
480
|
allowSpaces: !1,
|
|
482
481
|
startOfLine: !1,
|
|
@@ -492,14 +491,14 @@ var oe = A.create({
|
|
|
492
491
|
}).run();
|
|
493
492
|
},
|
|
494
493
|
render: () => {
|
|
495
|
-
let t = null,
|
|
496
|
-
function f() {
|
|
497
|
-
v(u?.() ?? null);
|
|
498
|
-
}
|
|
494
|
+
let t = null, i = null, a = null, s = o([]), c = o(0), l = null, u = `tpl-merge-tag-suggestion-${++ae}`, d = null, f = [];
|
|
499
495
|
function p() {
|
|
500
|
-
|
|
496
|
+
y(d?.() ?? null);
|
|
497
|
+
}
|
|
498
|
+
function m() {
|
|
499
|
+
p(), requestAnimationFrame(p);
|
|
501
500
|
}
|
|
502
|
-
function
|
|
501
|
+
function h(e) {
|
|
503
502
|
let t = [], n = e?.parentElement ?? null;
|
|
504
503
|
for (; n && n !== document.body && n !== document.documentElement;) {
|
|
505
504
|
let e = window.getComputedStyle(n), r = e.overflow + e.overflowX + e.overflowY;
|
|
@@ -507,28 +506,28 @@ var oe = A.create({
|
|
|
507
506
|
}
|
|
508
507
|
return t;
|
|
509
508
|
}
|
|
510
|
-
function
|
|
511
|
-
|
|
512
|
-
for (let e of
|
|
509
|
+
function g(e) {
|
|
510
|
+
f = [window, ...h(e)];
|
|
511
|
+
for (let e of f) e.addEventListener("scroll", p, {
|
|
513
512
|
passive: !0,
|
|
514
513
|
capture: !0
|
|
515
514
|
});
|
|
516
|
-
window.addEventListener("resize",
|
|
515
|
+
window.addEventListener("resize", p, { passive: !0 });
|
|
517
516
|
}
|
|
518
|
-
function
|
|
519
|
-
for (let e of
|
|
520
|
-
window.removeEventListener("resize",
|
|
517
|
+
function _() {
|
|
518
|
+
for (let e of f) e.removeEventListener("scroll", p, { capture: !0 });
|
|
519
|
+
window.removeEventListener("resize", p), f = [];
|
|
521
520
|
}
|
|
522
|
-
function
|
|
523
|
-
if (!
|
|
524
|
-
|
|
525
|
-
let t =
|
|
521
|
+
function y(e) {
|
|
522
|
+
if (!i || !e || e.bottom < 0 || e.top > window.innerHeight) return;
|
|
523
|
+
i.style.position = "fixed", i.style.left = `${e.left}px`, i.style.zIndex = "9999", i.style.top = `${e.bottom}px`;
|
|
524
|
+
let t = i.offsetHeight;
|
|
526
525
|
if (t !== 0 && window.innerHeight - e.bottom < t) {
|
|
527
526
|
let n = Math.max(0, e.top - t);
|
|
528
|
-
|
|
527
|
+
i.style.top = `${n}px`;
|
|
529
528
|
}
|
|
530
529
|
}
|
|
531
|
-
function
|
|
530
|
+
function b(e, t) {
|
|
532
531
|
let n = t?.closest("[data-tpl-theme]");
|
|
533
532
|
if (!n) return;
|
|
534
533
|
let r = n.getAttribute("data-tpl-theme");
|
|
@@ -540,59 +539,59 @@ var oe = A.create({
|
|
|
540
539
|
}
|
|
541
540
|
e.style.fontFamily = i.fontFamily, e.style.fontSize = i.fontSize, e.style.lineHeight = i.lineHeight;
|
|
542
541
|
}
|
|
543
|
-
function
|
|
544
|
-
|
|
542
|
+
function x(e) {
|
|
543
|
+
a && (e ? (a.setAttribute("role", "combobox"), a.setAttribute("aria-haspopup", "listbox"), a.setAttribute("aria-expanded", "true"), a.setAttribute("aria-controls", u)) : (a.removeAttribute("aria-expanded"), a.removeAttribute("aria-controls"), a.removeAttribute("aria-activedescendant"), a.removeAttribute("aria-haspopup"), a.removeAttribute("role")));
|
|
545
544
|
}
|
|
546
|
-
function
|
|
547
|
-
if (
|
|
548
|
-
if (
|
|
549
|
-
|
|
545
|
+
function S() {
|
|
546
|
+
if (a) {
|
|
547
|
+
if (s.value.length === 0) {
|
|
548
|
+
a.removeAttribute("aria-activedescendant");
|
|
550
549
|
return;
|
|
551
550
|
}
|
|
552
|
-
|
|
551
|
+
a.setAttribute("aria-activedescendant", `${u}-opt-${c.value}`);
|
|
553
552
|
}
|
|
554
553
|
}
|
|
555
|
-
function
|
|
556
|
-
|
|
554
|
+
function C(e) {
|
|
555
|
+
l?.(e);
|
|
557
556
|
}
|
|
558
557
|
return {
|
|
559
558
|
onStart: (o) => {
|
|
560
|
-
|
|
561
|
-
let
|
|
562
|
-
|
|
559
|
+
s.value = o.items, c.value = 0, l = (e) => o.command(e), i = document.createElement("div"), i.setAttribute("data-testid", "merge-tag-suggestion-popup");
|
|
560
|
+
let f = o.editor.view?.dom;
|
|
561
|
+
a = f ?? null, b(i, f ?? null), (r?.value ?? document.body).appendChild(i), t = v({ render() {
|
|
563
562
|
return e(ie, {
|
|
564
|
-
items:
|
|
565
|
-
selectedIndex:
|
|
563
|
+
items: s.value,
|
|
564
|
+
selectedIndex: c.value,
|
|
566
565
|
emptyText: n,
|
|
567
|
-
listId:
|
|
568
|
-
onSelect: (e) =>
|
|
566
|
+
listId: u,
|
|
567
|
+
onSelect: (e) => C(e),
|
|
569
568
|
onHover: (e) => {
|
|
570
|
-
|
|
569
|
+
c.value = e, S();
|
|
571
570
|
}
|
|
572
571
|
});
|
|
573
|
-
} }), t.mount(
|
|
572
|
+
} }), t.mount(i), x(!0), S(), d = o.clientRect ?? null, m(), g(f ?? null);
|
|
574
573
|
},
|
|
575
574
|
onUpdate: (e) => {
|
|
576
|
-
|
|
575
|
+
s.value = e.items, c.value >= e.items.length && (c.value = 0), l = (t) => e.command(t), S(), d = e.clientRect ?? null, m();
|
|
577
576
|
},
|
|
578
577
|
onKeyDown: (e) => {
|
|
579
578
|
if (e.event.key === "Escape") return !0;
|
|
580
|
-
let t = $(e.event,
|
|
581
|
-
return t &&
|
|
579
|
+
let t = $(e.event, s.value, c, C);
|
|
580
|
+
return t && S(), t;
|
|
582
581
|
},
|
|
583
582
|
onExit: () => {
|
|
584
|
-
|
|
583
|
+
_(), x(!1), t?.unmount(), i?.remove(), t = null, i = null, a = null, l = null, d = null;
|
|
585
584
|
}
|
|
586
585
|
};
|
|
587
586
|
}
|
|
588
587
|
};
|
|
589
588
|
return [N({
|
|
590
589
|
editor: this.editor,
|
|
591
|
-
...
|
|
590
|
+
...i
|
|
592
591
|
})];
|
|
593
592
|
}
|
|
594
593
|
});
|
|
595
594
|
//#endregion
|
|
596
595
|
export { I as FontSize, L as LetterSpacing, R as LineHeight, H as LogicMergeTagNode, K as MergeTagNode, oe as MergeTagSuggestion, Q as filterMergeTags, $ as handleSuggestionKeyDown };
|
|
597
596
|
|
|
598
|
-
//# sourceMappingURL=extensions-
|
|
597
|
+
//# sourceMappingURL=extensions-BZYkPlKr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions-BZYkPlKr.js","names":["$emit"],"sources":["../../../src/extensions/FontSize.ts","../../../src/extensions/LetterSpacing.ts","../../../src/extensions/LineHeight.ts","../../../src/extensions/LogicMergeTagNodeView.vue","../../../src/extensions/LogicMergeTagNodeView.vue","../../../src/extensions/isNodeSelected.ts","../../../src/extensions/renderVueNodeView.ts","../../../src/extensions/LogicMergeTagNode.ts","../../../src/extensions/MergeTagNodeView.vue","../../../src/extensions/MergeTagNodeView.vue","../../../src/extensions/MergeTagNode.ts","../../../src/components/MergeTagSuggestionList.vue","../../../src/components/MergeTagSuggestionList.vue","../../../src/extensions/MergeTagSuggestion.ts"],"sourcesContent":["import { Extension } from \"@tiptap/core\";\n\nexport interface FontSizeOptions {\n types: string[];\n}\n\ndeclare module \"@tiptap/core\" {\n interface Commands<ReturnType> {\n fontSize: {\n setFontSize: (size: string) => ReturnType;\n unsetFontSize: () => ReturnType;\n };\n }\n}\n\nexport const FontSize = Extension.create<FontSizeOptions>({\n name: \"fontSize\",\n\n addOptions() {\n return {\n types: [\"textStyle\"],\n };\n },\n\n addGlobalAttributes() {\n return [\n {\n types: this.options.types,\n attributes: {\n fontSize: {\n default: null,\n parseHTML: (element) =>\n element.style.fontSize?.replace(/['\"]+/g, \"\") || null,\n renderHTML: (attributes) => {\n if (!attributes.fontSize) {\n return {};\n }\n return {\n style: `font-size: ${attributes.fontSize}`,\n };\n },\n },\n },\n },\n ];\n },\n\n addCommands() {\n return {\n setFontSize:\n (size: string) =>\n ({ chain }) => {\n return chain().setMark(\"textStyle\", { fontSize: size }).run();\n },\n unsetFontSize:\n () =>\n ({ chain }) => {\n return chain()\n .setMark(\"textStyle\", { fontSize: null })\n .removeEmptyTextStyle()\n .run();\n },\n };\n },\n});\n","import { Extension } from \"@tiptap/core\";\n\nexport interface LetterSpacingOptions {\n types: string[];\n}\n\ndeclare module \"@tiptap/core\" {\n interface Commands<ReturnType> {\n letterSpacing: {\n setLetterSpacing: (spacing: string) => ReturnType;\n unsetLetterSpacing: () => ReturnType;\n };\n }\n}\n\nexport const LetterSpacing = Extension.create<LetterSpacingOptions>({\n name: \"letterSpacing\",\n\n addOptions() {\n return {\n types: [\"textStyle\"],\n };\n },\n\n addGlobalAttributes() {\n return [\n {\n types: this.options.types,\n attributes: {\n letterSpacing: {\n default: null,\n parseHTML: (element) =>\n element.style.letterSpacing?.replace(/['\"]+/g, \"\") || null,\n renderHTML: (attributes) => {\n if (!attributes.letterSpacing) {\n return {};\n }\n return {\n style: `letter-spacing: ${attributes.letterSpacing}`,\n };\n },\n },\n },\n },\n ];\n },\n\n addCommands() {\n return {\n setLetterSpacing:\n (spacing: string) =>\n ({ chain }) => {\n return chain().setMark(\"textStyle\", { letterSpacing: spacing }).run();\n },\n unsetLetterSpacing:\n () =>\n ({ chain }) => {\n return chain()\n .setMark(\"textStyle\", { letterSpacing: null })\n .removeEmptyTextStyle()\n .run();\n },\n };\n },\n});\n","import { Extension } from \"@tiptap/core\";\n\nexport interface LineHeightOptions {\n types: string[];\n defaultLineHeight: string;\n}\n\ndeclare module \"@tiptap/core\" {\n interface Commands<ReturnType> {\n lineHeight: {\n setLineHeight: (lineHeight: string) => ReturnType;\n unsetLineHeight: () => ReturnType;\n };\n }\n}\n\nexport const LineHeight = Extension.create<LineHeightOptions>({\n name: \"lineHeight\",\n\n addOptions() {\n return {\n types: [\"paragraph\"],\n defaultLineHeight: \"1.5\",\n };\n },\n\n addGlobalAttributes() {\n return [\n {\n types: this.options.types,\n attributes: {\n lineHeight: {\n default: null,\n parseHTML: (element) => element.style.lineHeight || null,\n renderHTML: (attributes) => {\n if (!attributes.lineHeight) {\n return {};\n }\n return {\n style: `line-height: ${attributes.lineHeight}`,\n };\n },\n },\n },\n },\n ];\n },\n\n addCommands() {\n return {\n setLineHeight:\n (lineHeight: string) =>\n ({ commands }) => {\n return this.options.types.every((type) =>\n commands.updateAttributes(type, { lineHeight }),\n );\n },\n unsetLineHeight:\n () =>\n ({ commands }) => {\n return this.options.types.every((type) =>\n commands.resetAttributes(type, \"lineHeight\"),\n );\n },\n };\n },\n});\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\nimport { useMergeTag } from \"../composables/useMergeTag\";\nimport {\n getLogicMergeTagKeyword,\n isLogicMergeTagValue,\n} from \"@templatical/types\";\nimport type { Editor } from \"@tiptap/core\";\nimport { NodeViewWrapper } from \"@tiptap/vue-3\";\nimport { computed, nextTick, ref } from \"vue\";\n\nconst props = defineProps<{\n node: {\n attrs: {\n value: string;\n keyword: string;\n };\n };\n editor: Editor;\n getPos: () => number;\n deleteNode: () => void;\n updateAttributes: (attrs: Record<string, unknown>) => void;\n}>();\n\nconst { syntax } = useMergeTag();\nconst { t } = useI18n();\n\nconst isValid = computed(() =>\n isLogicMergeTagValue(props.node.attrs.value, syntax),\n);\nconst displayKeyword = computed(() =>\n getLogicMergeTagKeyword(props.node.attrs.value, syntax),\n);\n\nconst isEditing = ref(false);\nconst editValue = ref(\"\");\nconst inputRef = ref<HTMLInputElement | null>(null);\nlet handled = false;\n\nfunction startEditing(): void {\n editValue.value = props.node.attrs.value;\n handled = false;\n isEditing.value = true;\n nextTick(() => {\n inputRef.value?.focus();\n inputRef.value?.select();\n });\n}\n\nfunction finishEditing(): void {\n if (handled) {\n return;\n }\n handled = true;\n\n const newValue = editValue.value.trim();\n if (!newValue) {\n isEditing.value = false;\n return;\n }\n\n if (newValue !== props.node.attrs.value) {\n props.updateAttributes({\n value: newValue,\n keyword: isLogicMergeTagValue(newValue, syntax)\n ? getLogicMergeTagKeyword(newValue, syntax)\n : \"\",\n });\n }\n isEditing.value = false;\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key === \"Enter\") {\n event.preventDefault();\n finishEditing();\n } else if (event.key === \"Escape\") {\n isEditing.value = false;\n }\n}\n</script>\n\n<template>\n <NodeViewWrapper\n as=\"span\"\n :class=\"\n isValid\n ? 'tpl-logic-merge-tag-node tpl:group tpl:mx-0.5 tpl:inline-flex tpl:items-center tpl:gap-1 tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[0.8em] tpl:font-bold tpl:tracking-wide tpl:uppercase tpl:select-none'\n : ''\n \"\n :style=\"\n isValid\n ? 'background-color: transparent; border: 1.5px solid color-mix(in srgb, var(--tpl-primary) 50%, transparent); color: var(--tpl-primary);'\n : ''\n \"\n contenteditable=\"false\"\n >\n <!-- Edit mode -->\n <input\n v-if=\"isEditing\"\n ref=\"inputRef\"\n v-model=\"editValue\"\n type=\"text\"\n class=\"tpl:w-40 tpl:rounded tpl:border-none tpl:bg-transparent tpl:px-0.5 tpl:py-0 tpl:text-[1em] tpl:font-medium tpl:normal-case tpl:outline-none tpl:text-[var(--tpl-primary)]\"\n @blur=\"finishEditing\"\n @keydown=\"handleKeydown\"\n />\n <!-- Display mode: valid merge tag -->\n <span\n v-else-if=\"isValid\"\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"t.mergeTag.editValue\"\n class=\"tpl-tooltip tpl:cursor-pointer\"\n :data-tooltip=\"node.attrs.value\"\n @click.stop=\"startEditing\"\n @keydown.enter.stop=\"startEditing\"\n @keydown.space.prevent.stop=\"startEditing\"\n >\n {{ displayKeyword }}\n </span>\n <!-- Display mode: invalid (plain text) -->\n <span\n v-else\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"t.mergeTag.editValue\"\n @click.stop=\"startEditing\"\n @keydown.enter.stop=\"startEditing\"\n @keydown.space.prevent.stop=\"startEditing\"\n >\n {{ node.attrs.value }}\n </span>\n <button\n v-if=\"isValid\"\n type=\"button\"\n :aria-label=\"t.mergeTag.deleteMergeTag\"\n class=\"tpl-merge-tag-delete tpl:flex tpl:size-5 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-full tpl:border-none tpl:bg-transparent tpl:p-0 tpl:opacity-60 tpl:transition-all hover:tpl:opacity-100 tpl:text-[var(--tpl-primary)]\"\n contenteditable=\"false\"\n @click.stop.prevent=\"deleteNode\"\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"3\"\n aria-hidden=\"true\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n </NodeViewWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\nimport { useMergeTag } from \"../composables/useMergeTag\";\nimport {\n getLogicMergeTagKeyword,\n isLogicMergeTagValue,\n} from \"@templatical/types\";\nimport type { Editor } from \"@tiptap/core\";\nimport { NodeViewWrapper } from \"@tiptap/vue-3\";\nimport { computed, nextTick, ref } from \"vue\";\n\nconst props = defineProps<{\n node: {\n attrs: {\n value: string;\n keyword: string;\n };\n };\n editor: Editor;\n getPos: () => number;\n deleteNode: () => void;\n updateAttributes: (attrs: Record<string, unknown>) => void;\n}>();\n\nconst { syntax } = useMergeTag();\nconst { t } = useI18n();\n\nconst isValid = computed(() =>\n isLogicMergeTagValue(props.node.attrs.value, syntax),\n);\nconst displayKeyword = computed(() =>\n getLogicMergeTagKeyword(props.node.attrs.value, syntax),\n);\n\nconst isEditing = ref(false);\nconst editValue = ref(\"\");\nconst inputRef = ref<HTMLInputElement | null>(null);\nlet handled = false;\n\nfunction startEditing(): void {\n editValue.value = props.node.attrs.value;\n handled = false;\n isEditing.value = true;\n nextTick(() => {\n inputRef.value?.focus();\n inputRef.value?.select();\n });\n}\n\nfunction finishEditing(): void {\n if (handled) {\n return;\n }\n handled = true;\n\n const newValue = editValue.value.trim();\n if (!newValue) {\n isEditing.value = false;\n return;\n }\n\n if (newValue !== props.node.attrs.value) {\n props.updateAttributes({\n value: newValue,\n keyword: isLogicMergeTagValue(newValue, syntax)\n ? getLogicMergeTagKeyword(newValue, syntax)\n : \"\",\n });\n }\n isEditing.value = false;\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key === \"Enter\") {\n event.preventDefault();\n finishEditing();\n } else if (event.key === \"Escape\") {\n isEditing.value = false;\n }\n}\n</script>\n\n<template>\n <NodeViewWrapper\n as=\"span\"\n :class=\"\n isValid\n ? 'tpl-logic-merge-tag-node tpl:group tpl:mx-0.5 tpl:inline-flex tpl:items-center tpl:gap-1 tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[0.8em] tpl:font-bold tpl:tracking-wide tpl:uppercase tpl:select-none'\n : ''\n \"\n :style=\"\n isValid\n ? 'background-color: transparent; border: 1.5px solid color-mix(in srgb, var(--tpl-primary) 50%, transparent); color: var(--tpl-primary);'\n : ''\n \"\n contenteditable=\"false\"\n >\n <!-- Edit mode -->\n <input\n v-if=\"isEditing\"\n ref=\"inputRef\"\n v-model=\"editValue\"\n type=\"text\"\n class=\"tpl:w-40 tpl:rounded tpl:border-none tpl:bg-transparent tpl:px-0.5 tpl:py-0 tpl:text-[1em] tpl:font-medium tpl:normal-case tpl:outline-none tpl:text-[var(--tpl-primary)]\"\n @blur=\"finishEditing\"\n @keydown=\"handleKeydown\"\n />\n <!-- Display mode: valid merge tag -->\n <span\n v-else-if=\"isValid\"\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"t.mergeTag.editValue\"\n class=\"tpl-tooltip tpl:cursor-pointer\"\n :data-tooltip=\"node.attrs.value\"\n @click.stop=\"startEditing\"\n @keydown.enter.stop=\"startEditing\"\n @keydown.space.prevent.stop=\"startEditing\"\n >\n {{ displayKeyword }}\n </span>\n <!-- Display mode: invalid (plain text) -->\n <span\n v-else\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"t.mergeTag.editValue\"\n @click.stop=\"startEditing\"\n @keydown.enter.stop=\"startEditing\"\n @keydown.space.prevent.stop=\"startEditing\"\n >\n {{ node.attrs.value }}\n </span>\n <button\n v-if=\"isValid\"\n type=\"button\"\n :aria-label=\"t.mergeTag.deleteMergeTag\"\n class=\"tpl-merge-tag-delete tpl:flex tpl:size-5 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-full tpl:border-none tpl:bg-transparent tpl:p-0 tpl:opacity-60 tpl:transition-all hover:tpl:opacity-100 tpl:text-[var(--tpl-primary)]\"\n contenteditable=\"false\"\n @click.stop.prevent=\"deleteNode\"\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"3\"\n aria-hidden=\"true\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n </NodeViewWrapper>\n</template>\n","import type { Editor } from \"@tiptap/core\";\n\n/**\n * Decides whether MergeTagNode / LogicMergeTagNode should swallow a\n * Backspace/Delete keystroke (returning `true` from a TipTap keymap\n * suppresses the default behavior).\n *\n * Only suppresses in the cursor-adjacent case so the \"first press\n * selects the atom, second press deletes it\" UX works. Range selections\n * (Cmd+A, drag-select, double-click-word) explicitly fall through —\n * default `replaceSelection` then deletes the entire range including\n * any merge-tag atoms inside it.\n *\n * Earlier revisions also returned `true` whenever `nodesBetween($from,\n * $to)` found a merge tag anywhere in the selection range — which\n * silently broke Cmd+A + Backspace for any paragraph containing a\n * merge tag (entire deletion was cancelled, nothing happened). The\n * cursor-adjacent protection is the only piece that should survive.\n */\nexport function isNodeSelected(editor: Editor, nodeTypeName: string): boolean {\n const { $from, $to } = editor.state.selection;\n\n // Range selection: let TipTap's default range-delete run. Even when\n // the range contains atom nodes, ProseMirror's replaceSelection\n // handles atomic deletion correctly.\n if ($from.pos !== $to.pos) return false;\n\n // Cursor-adjacent atom: protect from single-keystroke deletion.\n // (`$from.pos > 0` guards against reading `nodeBefore` at doc start\n // where ProseMirror's index would be undefined for nodeBefore.)\n if ($from.pos > 0 && $from.nodeBefore?.type.name === nodeTypeName) {\n return true;\n }\n if ($from.nodeAfter?.type.name === nodeTypeName) {\n return true;\n }\n\n return false;\n}\n","import type { Component } from \"vue\";\nimport { VueNodeViewRenderer } from \"@tiptap/vue-3\";\n\n/**\n * Typed wrapper for VueNodeViewRenderer that handles the known type mismatch\n * between Vue SFC default exports and TipTap's expected component type.\n */\nexport function renderVueNodeView(component: Component) {\n return VueNodeViewRenderer(\n component as Parameters<typeof VueNodeViewRenderer>[0],\n );\n}\n","import LogicMergeTagNodeView from \"./LogicMergeTagNodeView.vue\";\nimport type { SyntaxPreset } from \"@templatical/types\";\nimport {\n getLogicMergeTagKeyword,\n isLogicMergeTagValue,\n SYNTAX_PRESETS,\n} from \"@templatical/types\";\nimport { InputRule, mergeAttributes, Node, PasteRule } from \"@tiptap/core\";\nimport { isNodeSelected } from \"./isNodeSelected\";\nimport { renderVueNodeView } from \"./renderVueNodeView\";\n\nexport interface LogicMergeTagNodeOptions {\n syntax: SyntaxPreset;\n}\n\nexport const LogicMergeTagNode = Node.create<LogicMergeTagNodeOptions>({\n name: \"logicMergeTagNode\",\n\n group: \"inline\",\n\n inline: true,\n\n atom: true,\n\n addOptions() {\n return {\n syntax: SYNTAX_PRESETS.liquid,\n };\n },\n\n addAttributes() {\n return {\n value: {\n default: \"\",\n parseHTML: (element) =>\n element.getAttribute(\"data-logic-merge-tag\") || \"\",\n },\n keyword: {\n default: \"\",\n parseHTML: (element) =>\n element.getAttribute(\"data-keyword\") || element.textContent || \"\",\n },\n };\n },\n\n parseHTML() {\n return [\n {\n tag: \"span[data-logic-merge-tag]\",\n },\n ];\n },\n\n renderHTML({ node, HTMLAttributes }) {\n if (!isLogicMergeTagValue(node.attrs.value, this.options.syntax)) {\n return [\"span\", {}, node.attrs.value];\n }\n\n const keyword = getLogicMergeTagKeyword(\n node.attrs.value,\n this.options.syntax,\n );\n\n return [\n \"span\",\n mergeAttributes(HTMLAttributes, {\n \"data-logic-merge-tag\": node.attrs.value,\n \"data-keyword\": keyword,\n }),\n keyword,\n ];\n },\n\n addNodeView() {\n return renderVueNodeView(LogicMergeTagNodeView);\n },\n\n addKeyboardShortcuts() {\n return {\n Backspace: () => isNodeSelected(this.editor, this.name),\n Delete: () => isNodeSelected(this.editor, this.name),\n };\n },\n\n addInputRules() {\n const inputRegex = new RegExp(this.options.syntax.logic.source + \"$\", \"\");\n\n return [\n new InputRule({\n find: inputRegex,\n handler: ({ state, range, match }) => {\n const fullValue = match[0];\n if (!isLogicMergeTagValue(fullValue, this.options.syntax)) {\n return;\n }\n\n const keyword = getLogicMergeTagKeyword(\n fullValue,\n this.options.syntax,\n );\n\n const node = this.type.create({\n value: fullValue,\n keyword,\n });\n\n state.tr.replaceWith(range.from, range.to, node);\n },\n }),\n ];\n },\n\n addPasteRules() {\n const pasteRegex = new RegExp(this.options.syntax.logic.source, \"g\");\n\n return [\n new PasteRule({\n find: pasteRegex,\n handler: ({ state, range, match }) => {\n const fullValue = match[0];\n if (!isLogicMergeTagValue(fullValue, this.options.syntax)) {\n return;\n }\n\n const keyword = getLogicMergeTagKeyword(\n fullValue,\n this.options.syntax,\n );\n\n const node = this.type.create({\n value: fullValue,\n keyword,\n });\n\n state.tr.replaceWith(range.from, range.to, node);\n },\n }),\n ];\n },\n});\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\nimport { useMergeTag } from \"../composables/useMergeTag\";\nimport { NodeViewWrapper } from \"@tiptap/vue-3\";\nimport { computed, nextTick, ref } from \"vue\";\n\nconst props = defineProps<{\n node: {\n attrs: {\n label: string;\n value: string;\n };\n };\n deleteNode: () => void;\n updateAttributes: (attrs: Record<string, unknown>) => void;\n}>();\n\nconst { getMergeTagLabel } = useMergeTag();\nconst { t } = useI18n();\n\nconst displayLabel = computed(() => getMergeTagLabel(props.node.attrs.value));\n\nconst isEditing = ref(false);\nconst editValue = ref(\"\");\nconst inputRef = ref<HTMLInputElement | null>(null);\n\nfunction startEditing(): void {\n editValue.value = props.node.attrs.value;\n isEditing.value = true;\n nextTick(() => {\n inputRef.value?.focus();\n inputRef.value?.select();\n });\n}\n\nfunction finishEditing(): void {\n const newValue = editValue.value.trim();\n if (newValue && newValue !== props.node.attrs.value) {\n // Update with new value and derive label from it\n props.updateAttributes({\n value: newValue,\n label: getMergeTagLabel(newValue),\n });\n }\n isEditing.value = false;\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key === \"Enter\") {\n event.preventDefault();\n finishEditing();\n } else if (event.key === \"Escape\") {\n isEditing.value = false;\n }\n}\n</script>\n\n<template>\n <NodeViewWrapper\n as=\"span\"\n class=\"tpl-merge-tag-node tpl:group tpl:mx-0.5 tpl:inline-flex tpl:items-center tpl:gap-1 tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[0.9em] tpl:font-medium tpl:select-none tpl:text-[var(--tpl-primary)]\"\n style=\"\n background-color: color-mix(in srgb, var(--tpl-primary) 20%, transparent);\n \"\n contenteditable=\"false\"\n >\n <!-- Edit mode -->\n <input\n v-if=\"isEditing\"\n ref=\"inputRef\"\n v-model=\"editValue\"\n type=\"text\"\n class=\"tpl:w-32 tpl:rounded tpl:border-none tpl:bg-transparent tpl:px-0.5 tpl:py-0 tpl:text-[1em] tpl:font-medium tpl:outline-none tpl:text-[var(--tpl-primary)]\"\n @blur=\"finishEditing\"\n @keydown=\"handleKeydown\"\n />\n <!-- Display mode -->\n <span\n v-else\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"t.mergeTag.editValue\"\n class=\"tpl-tooltip tpl:cursor-pointer\"\n :data-tooltip=\"node.attrs.value\"\n @click.stop=\"startEditing\"\n @keydown.enter.stop=\"startEditing\"\n @keydown.space.prevent.stop=\"startEditing\"\n >\n {{ displayLabel }}\n </span>\n <button\n type=\"button\"\n :aria-label=\"t.mergeTag.deleteMergeTag\"\n class=\"tpl-merge-tag-delete tpl:flex tpl:size-5 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-full tpl:border-none tpl:bg-transparent tpl:p-0 tpl:opacity-60 tpl:transition-all hover:tpl:opacity-100 tpl:text-[var(--tpl-primary)]\"\n contenteditable=\"false\"\n @click.stop.prevent=\"deleteNode\"\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"3\"\n aria-hidden=\"true\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n </NodeViewWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../composables/useI18n\";\nimport { useMergeTag } from \"../composables/useMergeTag\";\nimport { NodeViewWrapper } from \"@tiptap/vue-3\";\nimport { computed, nextTick, ref } from \"vue\";\n\nconst props = defineProps<{\n node: {\n attrs: {\n label: string;\n value: string;\n };\n };\n deleteNode: () => void;\n updateAttributes: (attrs: Record<string, unknown>) => void;\n}>();\n\nconst { getMergeTagLabel } = useMergeTag();\nconst { t } = useI18n();\n\nconst displayLabel = computed(() => getMergeTagLabel(props.node.attrs.value));\n\nconst isEditing = ref(false);\nconst editValue = ref(\"\");\nconst inputRef = ref<HTMLInputElement | null>(null);\n\nfunction startEditing(): void {\n editValue.value = props.node.attrs.value;\n isEditing.value = true;\n nextTick(() => {\n inputRef.value?.focus();\n inputRef.value?.select();\n });\n}\n\nfunction finishEditing(): void {\n const newValue = editValue.value.trim();\n if (newValue && newValue !== props.node.attrs.value) {\n // Update with new value and derive label from it\n props.updateAttributes({\n value: newValue,\n label: getMergeTagLabel(newValue),\n });\n }\n isEditing.value = false;\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key === \"Enter\") {\n event.preventDefault();\n finishEditing();\n } else if (event.key === \"Escape\") {\n isEditing.value = false;\n }\n}\n</script>\n\n<template>\n <NodeViewWrapper\n as=\"span\"\n class=\"tpl-merge-tag-node tpl:group tpl:mx-0.5 tpl:inline-flex tpl:items-center tpl:gap-1 tpl:rounded tpl:px-1.5 tpl:py-0.5 tpl:text-[0.9em] tpl:font-medium tpl:select-none tpl:text-[var(--tpl-primary)]\"\n style=\"\n background-color: color-mix(in srgb, var(--tpl-primary) 20%, transparent);\n \"\n contenteditable=\"false\"\n >\n <!-- Edit mode -->\n <input\n v-if=\"isEditing\"\n ref=\"inputRef\"\n v-model=\"editValue\"\n type=\"text\"\n class=\"tpl:w-32 tpl:rounded tpl:border-none tpl:bg-transparent tpl:px-0.5 tpl:py-0 tpl:text-[1em] tpl:font-medium tpl:outline-none tpl:text-[var(--tpl-primary)]\"\n @blur=\"finishEditing\"\n @keydown=\"handleKeydown\"\n />\n <!-- Display mode -->\n <span\n v-else\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"t.mergeTag.editValue\"\n class=\"tpl-tooltip tpl:cursor-pointer\"\n :data-tooltip=\"node.attrs.value\"\n @click.stop=\"startEditing\"\n @keydown.enter.stop=\"startEditing\"\n @keydown.space.prevent.stop=\"startEditing\"\n >\n {{ displayLabel }}\n </span>\n <button\n type=\"button\"\n :aria-label=\"t.mergeTag.deleteMergeTag\"\n class=\"tpl-merge-tag-delete tpl:flex tpl:size-5 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-full tpl:border-none tpl:bg-transparent tpl:p-0 tpl:opacity-60 tpl:transition-all hover:tpl:opacity-100 tpl:text-[var(--tpl-primary)]\"\n contenteditable=\"false\"\n @click.stop.prevent=\"deleteNode\"\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"3\"\n aria-hidden=\"true\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n </NodeViewWrapper>\n</template>\n","import MergeTagNodeView from \"./MergeTagNodeView.vue\";\nimport type { MergeTag, SyntaxPreset } from \"@templatical/types\";\nimport { getMergeTagLabel, SYNTAX_PRESETS } from \"@templatical/types\";\nimport { InputRule, mergeAttributes, Node, PasteRule } from \"@tiptap/core\";\nimport { isNodeSelected } from \"./isNodeSelected\";\nimport { renderVueNodeView } from \"./renderVueNodeView\";\n\nexport interface MergeTagNodeOptions {\n mergeTags: MergeTag[];\n syntax: SyntaxPreset;\n}\n\ndeclare module \"@tiptap/core\" {\n interface Commands<ReturnType> {\n mergeTagNode: {\n insertMergeTag: (attrs: MergeTag) => ReturnType;\n };\n }\n}\n\nexport const MergeTagNode = Node.create<MergeTagNodeOptions>({\n name: \"mergeTagNode\",\n\n group: \"inline\",\n\n inline: true,\n\n atom: true,\n\n addOptions() {\n return {\n mergeTags: [],\n syntax: SYNTAX_PRESETS.liquid,\n };\n },\n\n addAttributes() {\n return {\n label: {\n default: \"\",\n parseHTML: (element) =>\n element.getAttribute(\"data-label\") || element.textContent || \"\",\n },\n value: {\n default: \"\",\n parseHTML: (element) => element.getAttribute(\"data-merge-tag\") || \"\",\n },\n };\n },\n\n parseHTML() {\n return [\n {\n tag: \"span[data-merge-tag]\",\n },\n ];\n },\n\n renderHTML({ node, HTMLAttributes }) {\n const label = getMergeTagLabel(node.attrs.value, this.options.mergeTags);\n\n return [\n \"span\",\n mergeAttributes(HTMLAttributes, {\n \"data-merge-tag\": node.attrs.value,\n \"data-label\": label,\n }),\n label,\n ];\n },\n\n addNodeView() {\n return renderVueNodeView(MergeTagNodeView);\n },\n\n addCommands() {\n return {\n insertMergeTag:\n (attrs: MergeTag) =>\n ({ commands }) => {\n return commands.insertContent({\n type: this.name,\n attrs,\n });\n },\n };\n },\n\n addKeyboardShortcuts() {\n return {\n Backspace: () => isNodeSelected(this.editor, this.name),\n Delete: () => isNodeSelected(this.editor, this.name),\n };\n },\n\n addInputRules() {\n const inputRegex = new RegExp(this.options.syntax.value.source + \"$\", \"\");\n\n return [\n new InputRule({\n find: inputRegex,\n handler: ({ state, range, match }) => {\n const fullValue = match[0];\n const label = getMergeTagLabel(fullValue, this.options.mergeTags);\n\n const node = this.type.create({\n label,\n value: fullValue,\n });\n\n state.tr.replaceWith(range.from, range.to, node);\n },\n }),\n ];\n },\n\n addPasteRules() {\n const pasteRegex = new RegExp(this.options.syntax.value.source, \"g\");\n\n return [\n new PasteRule({\n find: pasteRegex,\n handler: ({ state, range, match }) => {\n const fullValue = match[0];\n const label = getMergeTagLabel(fullValue, this.options.mergeTags);\n\n const node = this.type.create({\n label,\n value: fullValue,\n });\n\n state.tr.replaceWith(range.from, range.to, node);\n },\n }),\n ];\n },\n});\n","<script setup lang=\"ts\">\nimport type { MergeTag } from \"@templatical/types\";\n\nconst props = defineProps<{\n items: MergeTag[];\n selectedIndex: number;\n emptyText: string;\n /** Stable id used for aria-controls + per-option id derivation. */\n listId?: string;\n}>();\n\ndefineEmits<{\n (e: \"select\", item: MergeTag): void;\n (e: \"hover\", index: number): void;\n}>();\n\nfunction optionId(index: number): string | undefined {\n return props.listId ? `${props.listId}-opt-${index}` : undefined;\n}\n</script>\n\n<template>\n <div\n :id=\"listId\"\n class=\"tpl:min-w-[200px] tpl:max-w-[320px] tpl:max-h-[50vh] tpl:overflow-y-auto tpl:rounded-[var(--tpl-radius)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:py-1 tpl:shadow-lg\"\n role=\"listbox\"\n data-testid=\"merge-tag-suggestion-list\"\n >\n <div\n v-if=\"items.length === 0\"\n class=\"tpl:px-3 tpl:py-2 tpl:text-xs tpl:text-[var(--tpl-text-dim)]\"\n data-testid=\"merge-tag-suggestion-empty\"\n >\n {{ emptyText }}\n </div>\n <button\n v-for=\"(item, index) in items\"\n :key=\"item.value\"\n :id=\"optionId(index)\"\n type=\"button\"\n role=\"option\"\n :aria-selected=\"index === selectedIndex\"\n :data-selected=\"index === selectedIndex ? 'true' : 'false'\"\n :data-merge-tag-value=\"item.value\"\n class=\"tpl:flex tpl:w-full tpl:flex-col tpl:items-start tpl:gap-0.5 tpl:px-3 tpl:py-1.5 tpl:text-left tpl:text-xs tpl:transition-colors\"\n :class=\"\n index === selectedIndex\n ? 'tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-primary)]'\n : 'tpl:text-[var(--tpl-text)] hover:tpl:bg-[var(--tpl-bg-hover)]'\n \"\n @mousedown.prevent.stop=\"$emit('select', item)\"\n @mousemove=\"index !== selectedIndex && $emit('hover', index)\"\n >\n <span class=\"tpl:font-medium\">{{ item.label }}</span>\n <span class=\"tpl:text-[var(--tpl-text-dim)] tpl:font-mono\">{{\n item.value\n }}</span>\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport type { MergeTag } from \"@templatical/types\";\n\nconst props = defineProps<{\n items: MergeTag[];\n selectedIndex: number;\n emptyText: string;\n /** Stable id used for aria-controls + per-option id derivation. */\n listId?: string;\n}>();\n\ndefineEmits<{\n (e: \"select\", item: MergeTag): void;\n (e: \"hover\", index: number): void;\n}>();\n\nfunction optionId(index: number): string | undefined {\n return props.listId ? `${props.listId}-opt-${index}` : undefined;\n}\n</script>\n\n<template>\n <div\n :id=\"listId\"\n class=\"tpl:min-w-[200px] tpl:max-w-[320px] tpl:max-h-[50vh] tpl:overflow-y-auto tpl:rounded-[var(--tpl-radius)] tpl:border tpl:border-[var(--tpl-border)] tpl:bg-[var(--tpl-bg-elevated)] tpl:py-1 tpl:shadow-lg\"\n role=\"listbox\"\n data-testid=\"merge-tag-suggestion-list\"\n >\n <div\n v-if=\"items.length === 0\"\n class=\"tpl:px-3 tpl:py-2 tpl:text-xs tpl:text-[var(--tpl-text-dim)]\"\n data-testid=\"merge-tag-suggestion-empty\"\n >\n {{ emptyText }}\n </div>\n <button\n v-for=\"(item, index) in items\"\n :key=\"item.value\"\n :id=\"optionId(index)\"\n type=\"button\"\n role=\"option\"\n :aria-selected=\"index === selectedIndex\"\n :data-selected=\"index === selectedIndex ? 'true' : 'false'\"\n :data-merge-tag-value=\"item.value\"\n class=\"tpl:flex tpl:w-full tpl:flex-col tpl:items-start tpl:gap-0.5 tpl:px-3 tpl:py-1.5 tpl:text-left tpl:text-xs tpl:transition-colors\"\n :class=\"\n index === selectedIndex\n ? 'tpl:bg-[var(--tpl-primary-light)] tpl:text-[var(--tpl-primary)]'\n : 'tpl:text-[var(--tpl-text)] hover:tpl:bg-[var(--tpl-bg-hover)]'\n \"\n @mousedown.prevent.stop=\"$emit('select', item)\"\n @mousemove=\"index !== selectedIndex && $emit('hover', index)\"\n >\n <span class=\"tpl:font-medium\">{{ item.label }}</span>\n <span class=\"tpl:text-[var(--tpl-text-dim)] tpl:font-mono\">{{\n item.value\n }}</span>\n </button>\n </div>\n</template>\n","import type { MergeTag } from \"@templatical/types\";\nimport { Extension } from \"@tiptap/core\";\nimport Suggestion, {\n type SuggestionOptions,\n type SuggestionProps,\n type SuggestionKeyDownProps,\n} from \"@tiptap/suggestion\";\nimport { type App, createApp, h, ref, type Ref } from \"vue\";\nimport MergeTagSuggestionList from \"../components/MergeTagSuggestionList.vue\";\n\nconst MAX_RESULTS = 10;\n\n// Monotonic counter for unique listbox IDs across multiple popup instances.\nlet POPUP_ID_SEQ = 0;\n\nexport interface MergeTagSuggestionOptions {\n /** Available merge tags */\n mergeTags: MergeTag[];\n /** Trigger string (e.g. \"{{\", \"*|\", \"%%=\") */\n char: string;\n /** Localized empty-state label */\n emptyText: string;\n /**\n * Mount target for the suggestion popup. When provided with a non-null\n * `.value`, the popup attaches into that element instead of\n * `document.body` — keeping it inside the editor's effective DOM root\n * (shadow-aware). Pass the ref returned by `usePopoverRoot()`.\n *\n * Falls back to `document.body` when omitted or when the ref's value is\n * null at popup-open time (e.g. headless/test editors without an editor\n * shell). Preserves pre-Phase-3 behavior for callers that don't migrate.\n */\n popoverRoot?: Ref<HTMLElement | null> | null;\n}\n\n/**\n * Filter merge tags by query against label and value (case-insensitive).\n * Capped at MAX_RESULTS. Exported for testing.\n */\nexport function filterMergeTags(tags: MergeTag[], query: string): MergeTag[] {\n const trimmed = query.trim().toLowerCase();\n if (trimmed === \"\") {\n return tags.slice(0, MAX_RESULTS);\n }\n return tags\n .filter((tag) => {\n const label = tag.label.toLowerCase();\n const value = tag.value.toLowerCase();\n return label.includes(trimmed) || value.includes(trimmed);\n })\n .slice(0, MAX_RESULTS);\n}\n\n/**\n * Handle a keydown event against a list of items. Returns whether the\n * event was handled (so the suggestion plugin can stop propagation).\n * Exported for testing.\n */\nexport function handleSuggestionKeyDown(\n event: KeyboardEvent,\n items: MergeTag[],\n selectedIndex: Ref<number>,\n onSelect: (item: MergeTag) => void,\n): boolean {\n if (items.length === 0) {\n if (event.key === \"Enter\" || event.key === \"Tab\") return true;\n return false;\n }\n\n if (event.key === \"ArrowDown\") {\n selectedIndex.value = (selectedIndex.value + 1) % items.length;\n return true;\n }\n if (event.key === \"ArrowUp\") {\n selectedIndex.value =\n (selectedIndex.value - 1 + items.length) % items.length;\n return true;\n }\n if (event.key === \"Enter\" || event.key === \"Tab\") {\n onSelect(items[selectedIndex.value]);\n return true;\n }\n return false;\n}\n\nexport const MergeTagSuggestion = Extension.create<MergeTagSuggestionOptions>({\n name: \"mergeTagSuggestion\",\n\n addOptions() {\n return {\n mergeTags: [],\n char: \"{{\",\n emptyText: \"No matching merge tags\",\n popoverRoot: null,\n };\n },\n\n addProseMirrorPlugins() {\n const tags = this.options.mergeTags;\n const emptyText = this.options.emptyText;\n const popoverRootRef = this.options.popoverRoot;\n\n const config: Omit<SuggestionOptions<MergeTag>, \"editor\"> = {\n char: this.options.char,\n allowSpaces: false,\n startOfLine: false,\n // Default is [\" \"] which requires whitespace/line-start before the\n // trigger char — so `.{{` would not fire. Allow any preceding char.\n allowedPrefixes: null,\n items: ({ query }: { query: string }) => filterMergeTags(tags, query),\n command: ({\n editor,\n range,\n props,\n }: {\n editor: SuggestionProps<MergeTag>[\"editor\"];\n range: { from: number; to: number };\n props: MergeTag;\n }) => {\n // Use insertContentAt for atomic replace (matches the canonical\n // @tiptap/suggestion + Mention pattern). Avoids edge cases where\n // chained deleteRange + insertMergeTag fails to insert when the\n // selection state shifts mid-chain.\n editor\n .chain()\n .focus()\n .insertContentAt(range, {\n type: \"mergeTagNode\",\n attrs: { label: props.label, value: props.value },\n })\n .run();\n },\n render: () => {\n let app: App | null = null;\n let container: HTMLElement | null = null;\n let editableEl: HTMLElement | null = null;\n const itemsRef = ref<MergeTag[]>([]);\n const selectedIndex = ref(0);\n let currentCommand: ((item: MergeTag) => void) | null = null;\n const listId = `tpl-merge-tag-suggestion-${++POPUP_ID_SEQ}`;\n let latestClientRect: (() => DOMRect | null) | null = null;\n let scrollTargets: Array<EventTarget> = [];\n\n function reposition(): void {\n position(latestClientRect?.() ?? null);\n }\n\n /**\n * Reposition immediately and on the next animation frame.\n * After a keystroke triggers the suggestion, TipTap may run its\n * own `scrollIntoView` to keep the caret visible. That scroll\n * lands AFTER the current task's `position()` call but BEFORE\n * the browser's next paint, so we re-measure on rAF to catch\n * the post-scroll caret rect. Without this, the popup pins to\n * the pre-scroll caret position and ends up offset on slower\n * runners.\n */\n function repositionAfterPaint(): void {\n reposition();\n requestAnimationFrame(reposition);\n }\n\n function collectScrollAncestors(el: HTMLElement | null): HTMLElement[] {\n // Walk up the DOM finding scrollable ancestors. ProseMirror's\n // scrollIntoView fires on whichever ancestor scrolls — listening\n // to all of them ensures we reposition regardless of which one\n // moves.\n const result: HTMLElement[] = [];\n let node: HTMLElement | null = el?.parentElement ?? null;\n while (\n node &&\n // shadow-ok: ancestor walk terminator; not a mount target\n node !== document.body &&\n node !== document.documentElement\n ) {\n const style = window.getComputedStyle(node);\n const overflow = style.overflow + style.overflowX + style.overflowY;\n if (/(auto|scroll|overlay)/.test(overflow)) {\n result.push(node);\n }\n node = node.parentElement;\n }\n return result;\n }\n\n function attachScrollListeners(viewDom: HTMLElement | null): void {\n scrollTargets = [window, ...collectScrollAncestors(viewDom)];\n for (const target of scrollTargets) {\n target.addEventListener(\"scroll\", reposition, {\n passive: true,\n capture: true,\n });\n }\n window.addEventListener(\"resize\", reposition, { passive: true });\n }\n\n function detachScrollListeners(): void {\n for (const target of scrollTargets) {\n target.removeEventListener(\"scroll\", reposition, {\n capture: true,\n } as EventListenerOptions);\n }\n window.removeEventListener(\"resize\", reposition);\n scrollTargets = [];\n }\n\n function position(rect: DOMRect | null): void {\n if (!container || !rect) return;\n // If the caret has scrolled out of the viewport, freeze the\n // popup at its last on-screen position. Following the caret\n // off-screen produces an invisible popup the user can't reach,\n // and lets pathological scroll loops drag the popup further\n // each tick.\n if (rect.bottom < 0 || rect.top > window.innerHeight) return;\n container.style.position = \"fixed\";\n container.style.left = `${rect.left}px`;\n container.style.zIndex = \"9999\";\n // Place below caret first; offsetHeight is sync-readable after\n // the Vue app has mounted (or after onUpdate's reactive flush).\n container.style.top = `${rect.bottom}px`;\n const popupHeight = container.offsetHeight;\n if (popupHeight === 0) return;\n const spaceBelow = window.innerHeight - rect.bottom;\n if (spaceBelow < popupHeight) {\n // Not enough room below — flip above. Clamp to 0 so the\n // popup never positions off the top of the viewport.\n const flippedTop = Math.max(0, rect.top - popupHeight);\n container.style.top = `${flippedTop}px`;\n }\n }\n\n function applyThemeContext(\n target: HTMLElement,\n editorEl: HTMLElement | null | undefined,\n ): void {\n // When the popup mounts to document.body its `position: fixed`\n // resolves against the viewport — any transform/filter on a\n // consumer-page ancestor (route transitions, reveal animations,\n // dark canvas inversion) creates a containing block and moves\n // fixed descendants with it. Body ancestors don't transform.\n //\n // When mounting inside the editor's popover root, the popup is a\n // descendant of the `.tpl[data-tpl-theme]` root so CSS vars + font\n // would inherit — but the snapshot below is still emitted inline\n // because inline declarations win and the cost is negligible. This\n // keeps a single behavior across both mount paths.\n //\n // CSS vars (--tpl-bg-elevated, --tpl-border, etc.) are scoped to\n // `.tpl` and `.tpl[data-tpl-theme=\"dark\"]` in the editor's\n // stylesheet, so the popup wouldn't inherit them at body root.\n // Adding `class=\"tpl\"` would also pull base rules (min-height,\n // flex, full-page bg) we don't want on a popup. Instead, snapshot\n // every --tpl-* custom property from the editor's theme root and\n // re-emit them inline on the popup wrapper.\n const themeRoot = editorEl?.closest<HTMLElement>(\"[data-tpl-theme]\");\n if (!themeRoot) return;\n const themeValue = themeRoot.getAttribute(\"data-tpl-theme\");\n if (themeValue) target.setAttribute(\"data-tpl-theme\", themeValue);\n const computed = window.getComputedStyle(themeRoot);\n for (let i = 0; i < computed.length; i++) {\n const prop = computed[i];\n if (prop.startsWith(\"--tpl-\")) {\n target.style.setProperty(prop, computed.getPropertyValue(prop));\n }\n }\n // The popup no longer inherits font from the editor wrapper, so\n // its content would render in the page's default font and end up\n // at a different height — which changes the flip-above decision\n // and shifts the popup off the caret. Copy typography too.\n target.style.fontFamily = computed.fontFamily;\n target.style.fontSize = computed.fontSize;\n target.style.lineHeight = computed.lineHeight;\n }\n\n function setEditableAria(active: boolean): void {\n if (!editableEl) return;\n if (active) {\n editableEl.setAttribute(\"role\", \"combobox\");\n editableEl.setAttribute(\"aria-haspopup\", \"listbox\");\n editableEl.setAttribute(\"aria-expanded\", \"true\");\n editableEl.setAttribute(\"aria-controls\", listId);\n } else {\n editableEl.removeAttribute(\"aria-expanded\");\n editableEl.removeAttribute(\"aria-controls\");\n editableEl.removeAttribute(\"aria-activedescendant\");\n editableEl.removeAttribute(\"aria-haspopup\");\n editableEl.removeAttribute(\"role\");\n }\n }\n\n function setActiveDescendant(): void {\n if (!editableEl) return;\n if (itemsRef.value.length === 0) {\n editableEl.removeAttribute(\"aria-activedescendant\");\n return;\n }\n editableEl.setAttribute(\n \"aria-activedescendant\",\n `${listId}-opt-${selectedIndex.value}`,\n );\n }\n\n function select(item: MergeTag): void {\n currentCommand?.(item);\n }\n\n return {\n onStart: (props: SuggestionProps<MergeTag>) => {\n itemsRef.value = props.items;\n selectedIndex.value = 0;\n currentCommand = (item) => props.command(item);\n\n container = document.createElement(\"div\");\n container.setAttribute(\"data-testid\", \"merge-tag-suggestion-popup\");\n // Use view.dom (ProseMirror contenteditable, actually\n // attached to the DOM) rather than options.element, which may\n // be a detached div when no `element` is passed to the editor\n // constructor (as is the case with @tiptap/vue-3 EditorContent).\n const viewDom = props.editor.view?.dom as HTMLElement | undefined;\n editableEl = viewDom ?? null;\n applyThemeContext(container, viewDom ?? null);\n // Prefer the editor's popover root (shadow-aware) when wired by\n // the consumer. Falls back to document.body for headless callers\n // that don't pass `popoverRoot` to configure().\n // shadow-ok: fallback when popoverRoot wasn't provided (e.g., headless caller); editor mounts pass the shadow-aware root\n const mountTarget = popoverRootRef?.value ?? document.body;\n mountTarget.appendChild(container);\n\n app = createApp({\n render() {\n return h(MergeTagSuggestionList, {\n items: itemsRef.value,\n selectedIndex: selectedIndex.value,\n emptyText,\n listId,\n onSelect: (item: MergeTag) => select(item),\n onHover: (index: number) => {\n selectedIndex.value = index;\n setActiveDescendant();\n },\n });\n },\n });\n app.mount(container);\n setEditableAria(true);\n setActiveDescendant();\n latestClientRect = props.clientRect ?? null;\n repositionAfterPaint();\n attachScrollListeners(viewDom ?? null);\n },\n onUpdate: (props: SuggestionProps<MergeTag>) => {\n itemsRef.value = props.items;\n // Reset selection when item set changes (query changed).\n if (selectedIndex.value >= props.items.length) {\n selectedIndex.value = 0;\n }\n currentCommand = (item) => props.command(item);\n setActiveDescendant();\n latestClientRect = props.clientRect ?? null;\n repositionAfterPaint();\n },\n onKeyDown: (props: SuggestionKeyDownProps): boolean => {\n if (props.event.key === \"Escape\") {\n return true;\n }\n const handled = handleSuggestionKeyDown(\n props.event,\n itemsRef.value,\n selectedIndex,\n select,\n );\n if (handled) setActiveDescendant();\n return handled;\n },\n onExit: () => {\n detachScrollListeners();\n setEditableAria(false);\n app?.unmount();\n container?.remove();\n app = null;\n container = null;\n editableEl = null;\n currentCommand = null;\n latestClientRect = null;\n },\n };\n },\n };\n\n return [\n Suggestion({\n editor: this.editor,\n ...config,\n }),\n ];\n },\n});\n"],"mappings":"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"}
|