@templatical/editor 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AiFeatureMenu-vatvCD-m.js → AiFeatureMenu-D7Od4cI2.js} +1 -1
- package/dist/{CloudEditor-Bxe8Jt9u.js → CloudEditor-DG8fWnXI.js} +4 -4
- package/dist/{CollaboratorBar-Dv7VT09N.js → CollaboratorBar-BdXfCYAr.js} +1 -1
- package/dist/{ModuleBrowserModal-DAnqdlyq.js → ModuleBrowserModal-DpMInJoP.js} +4 -4
- package/dist/{ModulePreviewCanvas-CXApEVEG.js → ModulePreviewCanvas-MjIGlxVI.js} +1 -1
- package/dist/{ParagraphEditor-Bjdakcoz.js → ParagraphEditor-B5qZL5c2.js} +7 -7
- package/dist/{SaveModuleDialog-CZXltahx.js → SaveModuleDialog-BGFYMJby.js} +2 -2
- package/dist/{SnapshotHistory-OqEkaA0W.js → SnapshotHistory-4aWE_-GM.js} +1 -1
- package/dist/{TestEmailModal-CvEK6MTy.js → TestEmailModal-p-iH28wl.js} +2 -2
- package/dist/{TitleEditor-CxCD70Bs.js → TitleEditor-BHxJ6Oxd.js} +3 -3
- package/dist/{TplModal-C2Hoz1it.js → TplModal-BiUCSfou.js} +1 -1
- package/dist/{blockTypeIcons-DpJa9awv.js → blockTypeIcons-8GJD96Tw.js} +1 -1
- package/dist/bundle-stats.json +6 -6
- package/dist/cdn/chunks/{AiFeatureMenu-BggCm6wN.js → AiFeatureMenu-DSXAYiMk.js} +2 -2
- package/dist/cdn/chunks/{AiFeatureMenu-BggCm6wN.js.map → AiFeatureMenu-DSXAYiMk.js.map} +1 -1
- package/dist/cdn/chunks/{CloudEditor-CTNhzoZ1.js → CloudEditor-CRYF-bR3.js} +6 -6
- package/dist/cdn/chunks/{CloudEditor-CTNhzoZ1.js.map → CloudEditor-CRYF-bR3.js.map} +1 -1
- package/dist/cdn/chunks/{CollaboratorBar-De-3lvDy.js → CollaboratorBar-CTFgLtUt.js} +3 -3
- package/dist/cdn/chunks/{CollaboratorBar-De-3lvDy.js.map → CollaboratorBar-CTFgLtUt.js.map} +1 -1
- package/dist/cdn/chunks/{CountdownBlock-CUAl8R-c.js → CountdownBlock-BKg-e3Uo.js} +2 -2
- package/dist/cdn/chunks/{CountdownBlock-CUAl8R-c.js.map → CountdownBlock-BKg-e3Uo.js.map} +1 -1
- package/dist/cdn/chunks/{CountdownToolbar-3zLcGAcb.js → CountdownToolbar-CPAOXOwI.js} +3 -3
- package/dist/cdn/chunks/{CountdownToolbar-3zLcGAcb.js.map → CountdownToolbar-CPAOXOwI.js.map} +1 -1
- package/dist/cdn/chunks/{ModuleBrowserModal-CxICY31A.js → ModuleBrowserModal-B70ZM4KG.js} +5 -5
- package/dist/cdn/chunks/{ModuleBrowserModal-CxICY31A.js.map → ModuleBrowserModal-B70ZM4KG.js.map} +1 -1
- package/dist/cdn/chunks/{ModulePreviewCanvas-Cv0zciNS.js → ModulePreviewCanvas-VHhQwuFP.js} +2 -2
- package/dist/cdn/chunks/{ModulePreviewCanvas-Cv0zciNS.js.map → ModulePreviewCanvas-VHhQwuFP.js.map} +1 -1
- package/dist/cdn/chunks/{NumberWithSuffix-BaVxnNI4.js → NumberWithSuffix-DnqYqSKs.js} +2 -2
- package/dist/cdn/chunks/{NumberWithSuffix-BaVxnNI4.js.map → NumberWithSuffix-DnqYqSKs.js.map} +1 -1
- package/dist/cdn/chunks/{ParagraphEditor-BrzlI8vp.js → ParagraphEditor-oVEaeerJ.js} +21 -21
- package/dist/cdn/chunks/ParagraphEditor-oVEaeerJ.js.map +1 -0
- package/dist/cdn/chunks/{RichTextEditorContent-9vq4MRDp.js → RichTextEditorContent-CmOwLOeY.js} +2 -2
- package/dist/cdn/chunks/{RichTextEditorContent-9vq4MRDp.js.map → RichTextEditorContent-CmOwLOeY.js.map} +1 -1
- package/dist/cdn/chunks/{SaveModuleDialog-Lco3k1xX.js → SaveModuleDialog-B6kNizps.js} +2 -2
- package/dist/cdn/chunks/{SaveModuleDialog-Lco3k1xX.js.map → SaveModuleDialog-B6kNizps.js.map} +1 -1
- package/dist/cdn/chunks/{TitleEditor-BiiEtH7x.js → TitleEditor-CvZe3neh.js} +9 -9
- package/dist/cdn/chunks/TitleEditor-CvZe3neh.js.map +1 -0
- package/dist/cdn/chunks/{blockTypeIcons-DWhn7E7O.js → blockTypeIcons-Bs9B-yft.js} +3 -3
- package/dist/cdn/chunks/{blockTypeIcons-DWhn7E7O.js.map → blockTypeIcons-Bs9B-yft.js.map} +1 -1
- package/dist/cdn/chunks/{de-DEg-6TsA.js → de-BSWt7lYu.js} +4 -4
- package/dist/cdn/chunks/de-BSWt7lYu.js.map +1 -0
- package/dist/cdn/chunks/{dist-DFfcnJJB.js → dist-MGrg9tLo.js} +2 -2
- package/dist/cdn/chunks/{dist-DFfcnJJB.js.map → dist-MGrg9tLo.js.map} +1 -1
- package/dist/cdn/chunks/{en-DpHxxBnH.js → en-9-vIGlc7.js} +4 -4
- package/dist/cdn/chunks/en-9-vIGlc7.js.map +1 -0
- package/dist/cdn/chunks/{extensions-C6oPJqKD.js → extensions-D13MqxlW.js} +16 -7
- package/dist/cdn/chunks/{extensions-C6oPJqKD.js.map → extensions-D13MqxlW.js.map} +1 -1
- package/dist/cdn/chunks/{features-DV4PhoBs.js → features-BnG-dhzy.js} +18 -15
- package/dist/cdn/chunks/{features-DV4PhoBs.js.map → features-BnG-dhzy.js.map} +1 -1
- package/dist/cdn/chunks/{icons-LJ8U8lWI.js → icons-B_185qj_.js} +2 -2
- package/dist/cdn/chunks/{icons-LJ8U8lWI.js.map → icons-B_185qj_.js.map} +1 -1
- package/dist/cdn/chunks/{media-library-CWQAvfov.js → media-library-DxpFw4Tt.js} +3 -3
- package/dist/cdn/chunks/{media-library-CWQAvfov.js.map → media-library-DxpFw4Tt.js.map} +1 -1
- package/dist/cdn/chunks/{src-CVBDXDBH.js → src-Cw7KAyui.js} +4 -4
- package/dist/cdn/chunks/{src-CVBDXDBH.js.map → src-Cw7KAyui.js.map} +1 -1
- package/dist/cdn/chunks/{styles-CFnn-xE6.js → styles-DYDpAUsJ.js} +12 -12
- package/dist/cdn/chunks/styles-DYDpAUsJ.js.map +1 -0
- package/dist/cdn/chunks/{tiptap-kGWe8qnA.js → tiptap-CPAwPMop.js} +2 -2
- package/dist/cdn/chunks/{tiptap-kGWe8qnA.js.map → tiptap-CPAwPMop.js.map} +1 -1
- package/dist/cdn/editor.css +1 -1
- package/dist/cdn/editor.js +7 -7
- package/dist/{de-tDioEErp.js → de-DwLWZn6p.js} +3 -3
- package/dist/{en-W2p7oPaa.js → en-BrlGVpmd.js} +3 -3
- package/dist/{extensions-BQjnrlGT.js → extensions-Cmf6XVn9.js} +14 -5
- package/dist/index.d.ts +8 -8
- package/dist/style.css +1 -1
- package/dist/{styles-CdrFC7-K.js → styles-C-FrkTA9.js} +8 -8
- package/dist/templatical-editor.js +5 -5
- package/dist/{useEditorCore-JdLcaPeJ.js → useEditorCore-D0uZbPKG.js} +1717 -1714
- package/dist/{useMergeTag-BfFykpYl.js → useMergeTag-CEE4CG-D.js} +1 -1
- package/package.json +5 -5
- package/dist/cdn/chunks/ParagraphEditor-BrzlI8vp.js.map +0 -1
- package/dist/cdn/chunks/TitleEditor-BiiEtH7x.js.map +0 -1
- package/dist/cdn/chunks/de-DEg-6TsA.js.map +0 -1
- package/dist/cdn/chunks/en-DpHxxBnH.js.map +0 -1
- package/dist/cdn/chunks/styles-CFnn-xE6.js.map +0 -1
|
@@ -143,7 +143,7 @@ var e = {
|
|
|
143
143
|
openInNewTab: "Open in new tab",
|
|
144
144
|
placeholderUrl: "Placeholder Image",
|
|
145
145
|
placeholderUrlPlaceholder: "https://... (design-time only)",
|
|
146
|
-
placeholderUrlTooltip: "Since the image URL uses a
|
|
146
|
+
placeholderUrlTooltip: "Since the image URL uses a merge tag, you can provide a real image here to preview the layout while designing. This is not included in the final output.",
|
|
147
147
|
clickToAdd: "Click to add image URL",
|
|
148
148
|
browseMedia: "Browse Media"
|
|
149
149
|
},
|
|
@@ -163,7 +163,7 @@ var e = {
|
|
|
163
163
|
fullWidth: "Full Width",
|
|
164
164
|
placeholderUrl: "Placeholder Thumbnail",
|
|
165
165
|
placeholderUrlPlaceholder: "https://... (design-time only)",
|
|
166
|
-
placeholderUrlTooltip: "Since the video URL uses a
|
|
166
|
+
placeholderUrlTooltip: "Since the video URL uses a merge tag, you can provide a real thumbnail here to preview the layout while designing. This is not included in the final output.",
|
|
167
167
|
addVideo: "Add a video URL"
|
|
168
168
|
},
|
|
169
169
|
button: {
|
|
@@ -353,7 +353,7 @@ var e = {
|
|
|
353
353
|
fontFamily: "Font Family",
|
|
354
354
|
preheaderText: "Preheader Text",
|
|
355
355
|
preheaderTextPlaceholder: "Preview text shown after subject line in inbox...",
|
|
356
|
-
preheaderTextHint: "This text appears after the subject line in email client previews. Supports
|
|
356
|
+
preheaderTextHint: "This text appears after the subject line in email client previews. Supports merge tags.",
|
|
357
357
|
tips: "Tips",
|
|
358
358
|
tip1: "600px is the standard width for email templates",
|
|
359
359
|
tip2: "Use web-safe fonts for best compatibility",
|
|
@@ -502,4 +502,4 @@ var e = {
|
|
|
502
502
|
//#endregion
|
|
503
503
|
export { e as default };
|
|
504
504
|
|
|
505
|
-
//# sourceMappingURL=en-
|
|
505
|
+
//# sourceMappingURL=en-9-vIGlc7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"en-9-vIGlc7.js","names":[],"sources":["../../../src/i18n/locales/en.ts"],"sourcesContent":["export default {\n // Footer (OSS only)\n footer: {\n poweredBy: \"Powered by\",\n openSource: \"Open Source\",\n },\n\n // History (undo/redo)\n history: {\n undo: \"Undo\",\n redo: \"Redo\",\n collabWarning: \"Undo may affect collaborators' recent changes\",\n },\n\n // Viewport toggle\n viewport: {\n label: \"Viewport\",\n desktop: \"Desktop\",\n tablet: \"Tablet\",\n mobile: \"Mobile\",\n },\n\n // Dark mode preview\n darkMode: {\n enable: \"Dark Mode Preview\",\n disable: \"Light Mode Preview\",\n },\n\n // Preview mode\n previewMode: {\n enable: \"Preview Mode\",\n disable: \"Exit Preview\",\n },\n\n // Sidebar - Block types\n blocks: {\n section: \"Section\",\n image: \"Image\",\n title: \"Title\",\n paragraph: \"Paragraph\",\n button: \"Button\",\n divider: \"Divider\",\n video: \"Video\",\n social: \"Social\",\n spacer: \"Spacer\",\n html: \"HTML\",\n menu: \"Menu\",\n table: \"Table\",\n countdown: \"Countdown\",\n },\n\n // Right sidebar\n sidebar: {\n content: \"Content\",\n settings: \"Settings\",\n noSelection: \"No element selected\",\n noSelectionHint: \"Select a block on the canvas to edit it\",\n },\n\n // Toolbar - Common\n toolbar: {\n duplicate: \"Duplicate\",\n delete: \"Delete\",\n },\n\n // Title editor toolbar\n titleEditor: {\n toolbar: \"Title formatting\",\n bold: \"Bold (Ctrl+B)\",\n italic: \"Italic (Ctrl+I)\",\n addLink: \"Add Link\",\n },\n\n // Paragraph editor toolbar\n paragraphEditor: {\n toolbar: \"Text formatting\",\n bold: \"Bold (Ctrl+B)\",\n italic: \"Italic (Ctrl+I)\",\n underline: \"Underline (Ctrl+U)\",\n strikethrough: \"Strikethrough\",\n subscript: \"Subscript\",\n superscript: \"Superscript\",\n addLink: \"Add Link\",\n bulletList: \"Bullet List\",\n numberedList: \"Numbered List\",\n alignLeft: \"Align Left\",\n alignCenter: \"Align Center\",\n alignRight: \"Align Right\",\n clearFormatting: \"Clear Formatting\",\n insertEmoji: \"Insert Emoji\",\n fontFamily: \"Font Family\",\n defaultFont: \"Default\",\n fontSize: \"Font Size\",\n defaultSize: \"Default\",\n textColor: \"Text Color\",\n highlightColor: \"Highlight Color\",\n lineHeight: \"Line Height\",\n letterSpacing: \"Letter Spacing\",\n emojiItemLabel: \"Insert emoji {emoji}\",\n closeEmojiPicker: \"Close emoji picker\",\n },\n\n // Block actions (BlockWrapper)\n blockActions: {\n drag: \"Drag to reorder, or press Space to move with keyboard\",\n dragLifted:\n \"Moving {block}. Use up and down arrow keys to reposition, Space or Enter to drop, Escape to cancel.\",\n duplicate: \"Duplicate block\",\n delete: \"Delete block\",\n hiddenOnViewport: \"Hidden on {viewport}\",\n saveAsModule: \"Save as Module\",\n conditionToggle: \"Toggle display condition\",\n comments: \"Comments ({count})\",\n lifted: \"{block} lifted. Position {position} of {total}.\",\n moved: \"{block} moved to position {position} of {total}.\",\n dropped: \"{block} dropped at position {position} of {total}.\",\n cancelled: \"Move cancelled. {block} returned to position {position}.\",\n },\n\n // Toolbar - Section\n section: {\n dropHere: \"Drop blocks here\",\n columns: \"Columns\",\n column1: \"1 Column\",\n column2: \"2 Columns\",\n column3: \"3 Columns\",\n ratio12: \"1:2 Ratio\",\n ratio21: \"2:1 Ratio\",\n },\n\n // Text editor link dialog\n linkDialog: {\n editLink: \"Edit Link\",\n insertLink: \"Insert Link\",\n updateLink: \"Update Link\",\n removeLink: \"Remove Link\",\n cancel: \"Cancel\",\n urlPlaceholder: \"https://example.com\",\n urlLabel: \"URL\",\n },\n\n // Toolbar - Title\n title: {\n level: \"Heading Level\",\n heading1: \"Heading 1 (36px)\",\n heading2: \"Heading 2 (28px)\",\n heading3: \"Heading 3 (22px)\",\n heading4: \"Heading 4 (18px)\",\n fontFamily: \"Font Family\",\n inheritFont: \"Use template font\",\n color: \"Color\",\n align: \"Align\",\n alignLeft: \"Left\",\n alignCenter: \"Center\",\n alignRight: \"Right\",\n },\n\n // Emoji picker\n emoji: {\n smileys: \"Smileys\",\n gestures: \"Gestures\",\n objects: \"Objects\",\n },\n\n // Toolbar - Image\n image: {\n imageUrl: \"Image URL\",\n imageUrlPlaceholder: \"https://...\",\n altText: \"Alt Text\",\n altTextPlaceholder: \"Image description\",\n width: \"Width\",\n fullWidth: \"Full Width\",\n linkUrl: \"Link URL\",\n openInNewTab: \"Open in new tab\",\n placeholderUrl: \"Placeholder Image\",\n placeholderUrlPlaceholder: \"https://... (design-time only)\",\n placeholderUrlTooltip:\n \"Since the image URL uses a merge tag, you can provide a real image here to preview the layout while designing. This is not included in the final output.\",\n clickToAdd: \"Click to add image URL\",\n browseMedia: \"Browse Media\",\n },\n\n // Toolbar - Video\n video: {\n videoUrl: \"Video URL\",\n videoUrlPlaceholder: \"https://youtube.com/...\",\n youtube: \"YouTube\",\n vimeo: \"Vimeo\",\n detected: \"Video detected — thumbnail will be generated automatically\",\n openInNewTab: \"Open in new tab\",\n customThumbnail: \"Custom Thumbnail\",\n optional: \"(optional)\",\n thumbnailPlaceholder: \"Auto-generated from video URL\",\n altText: \"Alt Text\",\n altTextPlaceholder: \"Video description\",\n width: \"Width\",\n fullWidth: \"Full Width\",\n placeholderUrl: \"Placeholder Thumbnail\",\n placeholderUrlPlaceholder: \"https://... (design-time only)\",\n placeholderUrlTooltip:\n \"Since the video URL uses a merge tag, you can provide a real thumbnail here to preview the layout while designing. This is not included in the final output.\",\n addVideo: \"Add a video URL\",\n },\n\n // Toolbar - Button\n button: {\n fontFamily: \"Font Family\",\n inheritFont: \"Use template font\",\n text: \"Text\",\n url: \"URL\",\n urlPlaceholder: \"https://...\",\n openInNewTab: \"Open in new tab\",\n background: \"Background\",\n textColor: \"Text Color\",\n borderRadius: \"Border Radius\",\n fontSize: \"Font Size\",\n },\n\n // Toolbar - Divider\n divider: {\n style: \"Style\",\n solid: \"Solid\",\n dashed: \"Dashed\",\n dotted: \"Dotted\",\n color: \"Color\",\n thickness: \"Thickness\",\n },\n\n // Toolbar - Social Icons\n social: {\n icons: \"Icons\",\n addIcon: \"Add Icon\",\n addIcons: \"Add social icons\",\n removeIcon: \"Remove\",\n platform: \"Platform\",\n url: \"URL\",\n urlPlaceholder: \"https://...\",\n style: \"Style\",\n styleSolid: \"Solid\",\n styleOutlined: \"Outlined\",\n styleRounded: \"Rounded\",\n styleSquare: \"Square\",\n styleCircle: \"Circle\",\n size: \"Size\",\n sizeSmall: \"S\",\n sizeMedium: \"M\",\n sizeLarge: \"L\",\n spacing: \"Spacing\",\n align: \"Alignment\",\n platforms: {\n facebook: \"Facebook\",\n twitter: \"X (Twitter)\",\n instagram: \"Instagram\",\n linkedin: \"LinkedIn\",\n youtube: \"YouTube\",\n tiktok: \"TikTok\",\n pinterest: \"Pinterest\",\n email: \"Email\",\n whatsapp: \"WhatsApp\",\n telegram: \"Telegram\",\n discord: \"Discord\",\n snapchat: \"Snapchat\",\n reddit: \"Reddit\",\n github: \"GitHub\",\n dribbble: \"Dribbble\",\n behance: \"Behance\",\n },\n },\n\n // Toolbar - Menu\n menu: {\n items: \"Menu Items\",\n addItem: \"Add Item\",\n removeItem: \"Remove\",\n text: \"Text\",\n url: \"URL\",\n urlPlaceholder: \"https://example.com\",\n openInNewTab: \"Open in new tab\",\n bold: \"Bold\",\n underline: \"Underline\",\n color: \"Color\",\n linkColor: \"Link Color\",\n fontSize: \"Font Size\",\n fontFamily: \"Font Family\",\n separator: \"Separator\",\n separatorColor: \"Separator Color\",\n spacing: \"Spacing\",\n textAlign: \"Alignment\",\n addLinks: \"Add menu links\",\n },\n\n // Toolbar - Table\n table: {\n dimensions: \"Dimensions\",\n rows: \"Rows\",\n columns: \"Columns\",\n addRow: \"Add Row\",\n removeRow: \"Remove Row\",\n addColumn: \"Add Column\",\n removeColumn: \"Remove Column\",\n hasHeaderRow: \"Header row\",\n headerBackgroundColor: \"Header Background\",\n noHeaderBg: \"No background\",\n borderColor: \"Border Color\",\n borderWidth: \"Border Width\",\n cellPadding: \"Cell Padding\",\n fontFamily: \"Font Family\",\n fontSize: \"Font Size\",\n color: \"Text Color\",\n textAlign: \"Alignment\",\n cellPlaceholder: \"Enter text...\",\n empty: \"Add a table\",\n },\n\n // Toolbar - Spacer\n spacer: {\n height: \"Height\",\n },\n\n // Toolbar - Countdown\n countdown: {\n targetDate: \"Target Date\",\n timezone: \"Timezone\",\n display: \"Display\",\n days: \"Days\",\n hours: \"Hours\",\n minutes: \"Minutes\",\n seconds: \"Seconds\",\n separator: \"Separator\",\n fontFamily: \"Font Family\",\n inheritFont: \"Default\",\n digitFontSize: \"Digit Size\",\n digitColor: \"Digit Color\",\n labelColor: \"Label Color\",\n labelFontSize: \"Label Size\",\n background: \"Background\",\n labels: \"Labels\",\n expiry: \"Expired Message\",\n expiredMessagePlaceholder: \"This offer has expired\",\n expiredImageUrl: \"Expired Image URL\",\n hideOnExpiry: \"Hide when expired\",\n setDate: \"Set a target date in the settings panel\",\n hidden: \"Hidden (expired)\",\n },\n\n // Custom Blocks\n customBlocks: {\n definitionNotFound: \"Unknown block type — this block is not registered\",\n renderError:\n \"This block could not be rendered. Check the block template for errors.\",\n fields: {\n required: \"Required\",\n addItem: \"Add item\",\n removeItem: \"Remove\",\n maxItemsReached: \"Maximum items reached\",\n minItemsRequired: \"Minimum {count} items required\",\n },\n toolbar: {\n noDefinition:\n \"Register this block type in your SDK configuration to edit its properties\",\n },\n dataSource: {\n fetchButton: \"Load content\",\n changeButton: \"Change\",\n fetching: \"Loading...\",\n readOnlyTooltip: \"This value is loaded from your data source\",\n fetchError: \"Failed to load content\",\n },\n },\n\n // Toolbar - HTML\n html: {\n content: \"HTML Content\",\n preview: \"Custom HTML block\",\n empty: \"Add HTML content in the panel\",\n sanitizationHint: \"Scripts and unsafe elements are removed on export.\",\n },\n\n // Toolbar - Common block settings\n blockSettings: {\n spacing: \"Spacing\",\n padding: \"Padding\",\n margin: \"Margin\",\n background: \"Background\",\n color: \"Color\",\n display: \"Display\",\n showOnDesktop: \"Show on desktop\",\n showOnTablet: \"Show on tablet\",\n showOnMobile: \"Show on mobile\",\n hiddenOnDevice: \"Hidden on {device}\",\n customCss: \"Custom CSS\",\n css: \"CSS\",\n cssPlaceholder: \"/* Custom styles */\",\n displayCondition: \"Display Condition\",\n selectCondition: \"Select condition\",\n removeCondition: \"Remove condition\",\n noCondition: \"Always visible\",\n conditionApplied: \"Condition applied\",\n customCondition: \"Custom condition\",\n customConditionLabel: \"Condition name\",\n customConditionBefore: \"Before (opening logic)\",\n customConditionAfter: \"After (closing logic)\",\n applyCondition: \"Apply\",\n cancelCondition: \"Cancel\",\n customBadge: \"Custom\",\n restoreHiddenBlocks: \"Show all hidden blocks\",\n },\n\n // Template settings\n templateSettings: {\n layout: \"Layout\",\n widthPreset: \"Width Preset\",\n customWidth: \"Custom Width\",\n appearance: \"Appearance\",\n backgroundColor: \"Background Color\",\n fontFamily: \"Font Family\",\n preheaderText: \"Preheader Text\",\n preheaderTextPlaceholder:\n \"Preview text shown after subject line in inbox...\",\n preheaderTextHint:\n \"This text appears after the subject line in email client previews. Supports merge tags.\",\n tips: \"Tips\",\n tip1: \"600px is the standard width for email templates\",\n tip2: \"Use web-safe fonts for best compatibility\",\n tip3: \"Light backgrounds work best for readability\",\n },\n\n // Spacing control\n spacingControl: {\n lockAll: \"Lock all sides\",\n unlock: \"Unlock sides\",\n top: \"Top\",\n right: \"Right\",\n bottom: \"Bottom\",\n left: \"Left\",\n decreaseTop: \"Decrease top\",\n increaseTop: \"Increase top\",\n decreaseLeft: \"Decrease left\",\n increaseLeft: \"Increase left\",\n decreaseRight: \"Decrease right\",\n increaseRight: \"Increase right\",\n decreaseBottom: \"Decrease bottom\",\n increaseBottom: \"Increase bottom\",\n },\n\n // Color Picker\n colorPicker: {\n pickColor: \"Pick a color\",\n hexValue: \"Hex color value\",\n },\n\n // Merge Tag\n mergeTag: {\n clickToEdit: \"Click to edit\",\n remove: \"Remove merge tag\",\n insert: \"Insert merge tag\",\n add: \"Add merge tag\",\n editValue: \"Edit merge tag value\",\n deleteMergeTag: \"Delete merge tag\",\n suggestionEmpty: \"No matching merge tags\",\n },\n\n // Canvas\n canvas: {\n noBlocks: \"No blocks yet\",\n dragHint: \"Start from scratch by dragging blocks from the sidebar\",\n dropHere: \"Drop here\",\n aiHintChat: \"or let\",\n aiHintChatSuffix: \"generate a complete template for you in seconds\",\n aiHintDesign:\n \"Have an existing design? Upload a screenshot, image, or PDF and\",\n aiHintDesignSuffix: \"will instantly recreate it\",\n },\n\n // Media Library (cloud)\n mediaLibrary: {\n title: \"Media Library\",\n searchPlaceholder: \"Search files...\",\n allFiles: \"All Files\",\n filterAll: \"All Types\",\n filterImages: \"Images\",\n filterDocuments: \"Documents\",\n filterVideos: \"Videos\",\n filterAudio: \"Audio\",\n newFolder: \"New Folder\",\n folderName: \"Folder name\",\n noFiles: \"No files found\",\n dropOrClick: \"Drop files here or click to upload\",\n acceptedFormats: \"Images, PDF, Video, Audio, Documents (max 10MB)\",\n uploading: \"Uploading...\",\n uploadingProgress: \"Uploading {current} of {total}...\",\n selectImage: \"Select Image\",\n selectFile: \"Select File\",\n deleteSelected: \"Delete\",\n copyUrl: \"Copy URL\",\n copied: \"Copied!\",\n browseMedia: \"Browse Media Library\",\n renameFolder: \"Rename folder\",\n addSubfolder: \"Add subfolder\",\n subfolderName: \"Subfolder name\",\n sortNewest: \"Newest First\",\n sortOldest: \"Oldest First\",\n sortNameAsc: \"Name A-Z\",\n sortNameDesc: \"Name Z-A\",\n sortSizeAsc: \"Smallest First\",\n sortSizeDesc: \"Largest First\",\n moveSelected: \"Move\",\n moveToRoot: \"All Files\",\n currentFolder: \"(current)\",\n confirmDelete: \"Delete this file?\",\n renameFile: \"Rename\",\n editFile: \"Edit File\",\n fileName: \"Filename\",\n altText: \"Alt Text\",\n altTextPlaceholder: \"Describe this image for accessibility\",\n saveChanges: \"Save\",\n cancel: \"Cancel\",\n frequentlyUsed: \"Frequently Used\",\n deleteWarningTitle: \"Delete File\",\n deleteWarningMessage:\n \"This file will be permanently deleted and cannot be recovered.\",\n deleteWarningUsageNote:\n \"The following files are used in templates. Deleting them may break those templates.\",\n deleteAnyway: \"Delete file\",\n usedInTemplates: \"Used in {count} template(s)\",\n viewGrid: \"Grid view\",\n viewList: \"List view\",\n showFolders: \"Show folders\",\n hideFolders: \"Hide folders\",\n importFromUrl: \"Import from URL\",\n importUrlPlaceholder: \"https://example.com/image.jpg\",\n import: \"Import\",\n importing: \"Importing...\",\n importError: \"Failed to import from URL\",\n conversionLabel: \"Size\",\n conversionOriginal: \"Original\",\n conversionSmall: \"Small (150px)\",\n conversionMedium: \"Medium (600px)\",\n conversionLarge: \"Large (1200px)\",\n replaceFile: \"Replace File\",\n replaceWarningTitle: \"Replace File\",\n replaceWarningMessage:\n \"You are about to replace this file. The replacement must have the same file extension ({extension}).\",\n replaceWarningUsageNote:\n \"This file is used in {count} template(s). Replacing it will update all references.\",\n replaceSelectFile: \"Select replacement file\",\n replace: \"Replace\",\n replacing: \"Replacing...\",\n replaceError: \"Failed to replace file\",\n saving: \"Saving...\",\n cropAspectRatio: \"Aspect Ratio\",\n cropFree: \"Free\",\n cropSquare: \"1:1\",\n cropLandscape43: \"4:3\",\n cropLandscape169: \"16:9\",\n cropOriginal: \"Original\",\n cropMaxWidth: \"Max Width\",\n cropMaxHeight: \"Max Height\",\n cropOutputSize: \"Output Size\",\n cropPixels: \"px\",\n cropOptional: \"(optional)\",\n storageTooltip: \"{used} of {total} used ({remaining} remaining)\",\n },\n\n // Sidebar\n sidebarNav: {\n browseModules: \"Browse saved modules\",\n expandSidebar: \"Expand block sidebar\",\n palette: \"Block palette\",\n insertBlock: \"Insert {block} block\",\n },\n\n // Landmark region labels for assistive technology\n landmarks: {\n canvas: \"Email canvas\",\n blockToolbar: \"Block properties\",\n rightSidebar: \"Block properties and template settings\",\n reorderAnnouncements: \"Block reorder announcements\",\n },\n\n // Design Reference (cloud)\n errors: {\n editorLoading: \"Loading editor...\",\n editorLoadFailed: \"Failed to load editor.\",\n retry: \"Retry\",\n },\n};\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $ as e, F as t, H as n, N as r, S as i, U as a, _ as o, at as s, ct as c, d as l, g as u, h as d, k as f, l as p, lt as m, m as h, p as g, r as _, s as v, st as y, u as b, w as x } from "./draggable-Bcb86AsV.js";
|
|
2
|
-
import { Mt as S, Z as C, fn as w, mn as T, pn as E, un as D } from "./features-
|
|
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-
|
|
2
|
+
import { Mt as S, Z as C, fn as w, mn as T, pn as E, un as D } from "./features-BnG-dhzy.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-CPAwPMop.js";
|
|
4
4
|
//#region src/extensions/FontSize.ts
|
|
5
5
|
var I = A.create({
|
|
6
6
|
name: "fontSize",
|
|
@@ -439,7 +439,7 @@ var H = O.create({
|
|
|
439
439
|
}
|
|
440
440
|
return (n, a) => (r(), o("div", {
|
|
441
441
|
id: e.listId,
|
|
442
|
-
class: "tpl:min-w-[200px] tpl:max-w-[320px] tpl:max-h-[50vh] tpl:overflow-y-auto tpl:rounded-[var(--tpl-radius
|
|
442
|
+
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
443
|
role: "listbox",
|
|
444
444
|
"data-testid": "merge-tag-suggestion-list"
|
|
445
445
|
}, [e.items.length === 0 ? (r(), o("div", J, m(e.emptyText), 1)) : u("", !0), (r(!0), o(l, null, t(e.items, (t, a) => (r(), o("button", {
|
|
@@ -525,8 +525,17 @@ var oe = A.create({
|
|
|
525
525
|
r.style.top = `${n}px`;
|
|
526
526
|
}
|
|
527
527
|
}
|
|
528
|
-
function g(e) {
|
|
529
|
-
|
|
528
|
+
function g(e, t) {
|
|
529
|
+
let n = t?.closest("[data-tpl-theme]");
|
|
530
|
+
if (!n) return;
|
|
531
|
+
let r = n.getAttribute("data-tpl-theme");
|
|
532
|
+
r && e.setAttribute("data-tpl-theme", r);
|
|
533
|
+
let i = window.getComputedStyle(n);
|
|
534
|
+
for (let t = 0; t < i.length; t++) {
|
|
535
|
+
let n = i[t];
|
|
536
|
+
n.startsWith("--tpl-") && e.style.setProperty(n, i.getPropertyValue(n));
|
|
537
|
+
}
|
|
538
|
+
e.style.fontFamily = i.fontFamily, e.style.fontSize = i.fontSize, e.style.lineHeight = i.lineHeight;
|
|
530
539
|
}
|
|
531
540
|
function v(e) {
|
|
532
541
|
i && (e ? (i.setAttribute("role", "combobox"), i.setAttribute("aria-haspopup", "listbox"), i.setAttribute("aria-expanded", "true"), i.setAttribute("aria-controls", c)) : (i.removeAttribute("aria-expanded"), i.removeAttribute("aria-controls"), i.removeAttribute("aria-activedescendant"), i.removeAttribute("aria-haspopup"), i.removeAttribute("role")));
|
|
@@ -547,7 +556,7 @@ var oe = A.create({
|
|
|
547
556
|
onStart: (e) => {
|
|
548
557
|
a.value = e.items, o.value = 0, s = (t) => e.command(t), r = document.createElement("div"), r.setAttribute("data-testid", "merge-tag-suggestion-popup");
|
|
549
558
|
let u = e.editor.view?.dom;
|
|
550
|
-
i = u ?? null, g(u ?? null).appendChild(r), t = _({ render() {
|
|
559
|
+
i = u ?? null, g(r, u ?? null), document.body.appendChild(r), t = _({ render() {
|
|
551
560
|
return x(ie, {
|
|
552
561
|
items: a.value,
|
|
553
562
|
selectedIndex: o.value,
|
|
@@ -583,4 +592,4 @@ var oe = A.create({
|
|
|
583
592
|
//#endregion
|
|
584
593
|
export { I as FontSize, L as LetterSpacing, R as LineHeight, H as LogicMergeTagNode, K as MergeTagNode, oe as MergeTagSuggestion, Q as filterMergeTags, $ as handleSuggestionKeyDown };
|
|
585
594
|
|
|
586
|
-
//# sourceMappingURL=extensions-
|
|
595
|
+
//# sourceMappingURL=extensions-D13MqxlW.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extensions-C6oPJqKD.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 * Checks whether a TipTap node of the given type is selected, or if the cursor\n * is immediately adjacent to one (for Backspace/Delete handling).\n *\n * Shared by MergeTagNode and LogicMergeTagNode keyboard shortcuts.\n */\nexport function isNodeSelected(editor: Editor, nodeTypeName: string): boolean {\n const { selection } = editor.state;\n const { $from, $to } = selection;\n\n // Check if selection contains a node of this type\n let found = false;\n editor.state.doc.nodesBetween($from.pos, $to.pos, (node) => {\n if (node.type.name === nodeTypeName) {\n found = true;\n return false;\n }\n });\n\n if (found) {\n return true;\n }\n\n // Check if cursor is right after the node (for Backspace)\n if ($from.pos > 0 && $from.nodeBefore?.type.name === nodeTypeName) {\n return true;\n }\n\n // Check if cursor is right before the node (for Delete)\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-md)] 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-md)] 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\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 };\n },\n\n addProseMirrorPlugins() {\n const tags = this.options.mergeTags;\n const emptyText = this.options.emptyText;\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 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 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 findMountTarget(\n editorEl: HTMLElement | null | undefined,\n ): HTMLElement {\n // Prefer the theme root (outside Canvas). Canvas.vue applies\n // `filter: invert(1) hue-rotate(180deg)` in dark mode, which\n // creates a containing block for `position: fixed` descendants.\n // Mounting inside the canvas would offset the popup. The theme\n // root has the CSS vars and no transform/filter — best of both.\n //\n // Click-outside is handled by mousedown.prevent.stop on options;\n // mount location no longer affects that.\n const root =\n editorEl?.closest<HTMLElement>(\"[data-tpl-theme]\") ??\n editorEl?.closest<HTMLElement>(\".tpl-text-editor-wrapper\") ??\n null;\n return root ?? document.body;\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 findMountTarget(viewDom ?? null).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 position(latestClientRect?.() ?? null);\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 position(latestClientRect?.() ?? null);\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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"}
|
|
1
|
+
{"version":3,"file":"extensions-D13MqxlW.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 * Checks whether a TipTap node of the given type is selected, or if the cursor\n * is immediately adjacent to one (for Backspace/Delete handling).\n *\n * Shared by MergeTagNode and LogicMergeTagNode keyboard shortcuts.\n */\nexport function isNodeSelected(editor: Editor, nodeTypeName: string): boolean {\n const { selection } = editor.state;\n const { $from, $to } = selection;\n\n // Check if selection contains a node of this type\n let found = false;\n editor.state.doc.nodesBetween($from.pos, $to.pos, (node) => {\n if (node.type.name === nodeTypeName) {\n found = true;\n return false;\n }\n });\n\n if (found) {\n return true;\n }\n\n // Check if cursor is right after the node (for Backspace)\n if ($from.pos > 0 && $from.nodeBefore?.type.name === nodeTypeName) {\n return true;\n }\n\n // Check if cursor is right before the node (for Delete)\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\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 };\n },\n\n addProseMirrorPlugins() {\n const tags = this.options.mergeTags;\n const emptyText = this.options.emptyText;\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 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 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 // The popup mounts to document.body so 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 // 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 document.body.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 position(latestClientRect?.() ?? null);\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 position(latestClientRect?.() ?? null);\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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"}
|
|
@@ -2276,7 +2276,7 @@ function wi() {
|
|
|
2276
2276
|
return {
|
|
2277
2277
|
mergeTags: e,
|
|
2278
2278
|
isRequesting: a,
|
|
2279
|
-
|
|
2279
|
+
canRequestMergeTag: !!r,
|
|
2280
2280
|
autocomplete: i,
|
|
2281
2281
|
syntax: t,
|
|
2282
2282
|
isMergeTagValue: s,
|
|
@@ -2287,24 +2287,27 @@ function wi() {
|
|
|
2287
2287
|
//#endregion
|
|
2288
2288
|
//#region src/composables/useEmoji.ts
|
|
2289
2289
|
function Ti() {
|
|
2290
|
-
let [e, t] = rn(!1), n = R([]);
|
|
2291
|
-
|
|
2290
|
+
let [e, t] = rn(!1), n = R([]), r = !1;
|
|
2291
|
+
x(() => {
|
|
2292
|
+
r = !0;
|
|
2293
|
+
}), i(e, async (e) => {
|
|
2292
2294
|
if (e && n.value.length === 0) {
|
|
2293
2295
|
let { emojiCategories: e } = await import("./emojiData-Vv_m5TSB.js");
|
|
2296
|
+
if (r) return;
|
|
2294
2297
|
n.value = e;
|
|
2295
2298
|
}
|
|
2296
2299
|
});
|
|
2297
|
-
function
|
|
2300
|
+
function a() {
|
|
2298
2301
|
t();
|
|
2299
2302
|
}
|
|
2300
|
-
function
|
|
2303
|
+
function o() {
|
|
2301
2304
|
e.value = !1;
|
|
2302
2305
|
}
|
|
2303
2306
|
return {
|
|
2304
2307
|
categories: n,
|
|
2305
2308
|
isOpen: e,
|
|
2306
|
-
toggle:
|
|
2307
|
-
close:
|
|
2309
|
+
toggle: a,
|
|
2310
|
+
close: o
|
|
2308
2311
|
};
|
|
2309
2312
|
}
|
|
2310
2313
|
//#endregion
|
|
@@ -2512,7 +2515,7 @@ function ji(e) {
|
|
|
2512
2515
|
//#endregion
|
|
2513
2516
|
//#region src/composables/useRichTextEditor.ts
|
|
2514
2517
|
function Mi(e) {
|
|
2515
|
-
let t = o(Dn, null), { mergeTags: a,
|
|
2518
|
+
let t = o(Dn, null), { mergeTags: a, canRequestMergeTag: s, isRequesting: c, requestMergeTag: l, syntax: d, autocomplete: f } = wi(), p = o(En, null), m = (u(p) ? p.value : p)?.mergeTag?.suggestionEmpty ?? "No matching merge tags", h = ht(d), g = R(null), { showLinkDialog: _, linkUrl: v, linkDialogRef: y, openLinkDialog: b, insertLink: x, removeLink: S, closeLinkDialog: C, handleLinkKeydown: w } = ji(g), { start: T, stop: E } = nn(() => g.value?.commands.focus("end"), 0, { immediate: !1 }), D = R(null), O = n(!0), k = n(null), A = !1;
|
|
2516
2519
|
async function j() {
|
|
2517
2520
|
k.value = null, O.value = !0;
|
|
2518
2521
|
try {
|
|
@@ -2562,10 +2565,10 @@ function Mi(e) {
|
|
|
2562
2565
|
});
|
|
2563
2566
|
async function ne() {
|
|
2564
2567
|
let e = await l();
|
|
2565
|
-
e && g.value ? g.value.chain().focus().insertMergeTag({
|
|
2568
|
+
A || (e && g.value ? g.value.chain().focus().insertMergeTag({
|
|
2566
2569
|
label: e.label,
|
|
2567
2570
|
value: e.value
|
|
2568
|
-
}).run() : g.value?.commands.focus();
|
|
2571
|
+
}).run() : g.value?.commands.focus());
|
|
2569
2572
|
}
|
|
2570
2573
|
return {
|
|
2571
2574
|
editor: g,
|
|
@@ -2577,7 +2580,7 @@ function Mi(e) {
|
|
|
2577
2580
|
linkUrl: v,
|
|
2578
2581
|
linkDialogRef: y,
|
|
2579
2582
|
mergeTags: a,
|
|
2580
|
-
|
|
2583
|
+
canRequestMergeTag: s,
|
|
2581
2584
|
isRequestingMergeTag: c,
|
|
2582
2585
|
syntax: d,
|
|
2583
2586
|
openLinkDialog: b,
|
|
@@ -2854,7 +2857,7 @@ var da = ["innerHTML"], fa = /* @__PURE__ */ g({
|
|
|
2854
2857
|
viewport: {}
|
|
2855
2858
|
},
|
|
2856
2859
|
setup(e) {
|
|
2857
|
-
let t = e, n = pe(() => import("./ParagraphEditor-
|
|
2860
|
+
let t = e, n = pe(() => import("./ParagraphEditor-oVEaeerJ.js")), { isEditing: r, blockRef: i, toolbarPosition: a, resolvedContent: o, handleDoubleClick: s, handleEditorDone: c } = ua(() => t.block.content);
|
|
2858
2861
|
return (t, l) => (f(), C("div", {
|
|
2859
2862
|
ref_key: "blockRef",
|
|
2860
2863
|
ref: i,
|
|
@@ -3052,7 +3055,7 @@ var ha = ["data-block-id", "data-block-type"], ga = ["aria-label"], _a = [
|
|
|
3052
3055
|
viewport: {}
|
|
3053
3056
|
},
|
|
3054
3057
|
setup(e) {
|
|
3055
|
-
let t = e, n = pe(() => import("./TitleEditor-
|
|
3058
|
+
let t = e, n = pe(() => import("./TitleEditor-CvZe3neh.js")), { isEditing: r, blockRef: i, toolbarPosition: a, resolvedContent: o, handleDoubleClick: s, handleEditorDone: c } = ua(() => t.block.content), l = F(() => {
|
|
3056
3059
|
let e = {
|
|
3057
3060
|
fontSize: `${_e[t.block.level]}px`,
|
|
3058
3061
|
color: t.block.color,
|
|
@@ -3607,7 +3610,7 @@ var ro = ["src", "alt"], io = {
|
|
|
3607
3610
|
table: $a,
|
|
3608
3611
|
spacer: qa,
|
|
3609
3612
|
html: Ji,
|
|
3610
|
-
countdown: pe(() => import("./CountdownBlock-
|
|
3613
|
+
countdown: pe(() => import("./CountdownBlock-BKg-e3Uo.js").then((e) => e.n))
|
|
3611
3614
|
};
|
|
3612
3615
|
function fo(e) {
|
|
3613
3616
|
let { editor: t, config: r, translations: a, fontsManager: o } = e, { t: s, format: c } = J(a);
|
|
@@ -6709,4 +6712,4 @@ var jl = {
|
|
|
6709
6712
|
//#endregion
|
|
6710
6713
|
export { Ci as $, un as $t, lo as A, ye as An, ar as At, pa as B, Jn as Bt, bo as C, xe as Cn, Mr as Ct, po as D, Te as Dn, Tr as Dt, X as E, ve as En, Er as Et, La as F, me as Fn, Kn as Ft, Ui as G, jn as Gt, la as H, In as Ht, Pa as I, Pn as It, Ai as J, Yn as Jt, Vi as K, Rn as Kt, ka as L, Fn as Lt, qa as M, _e as Mn, J as Mt, Wa as N, he as Nn, qn as Nt, mo as O, Ee as On, Y as Ot, Fa as P, ge as Pn, Un as Pt, Si as Q, q as Qt, Da as R, Wn as Rt, Co as S, De as Sn, Nr as St, $ as T, be as Tn, Or as Tt, aa as U, Hn as Ut, fa as V, An as Vt, Ji as W, Dn as Wt, Ti as X, Mn as Xt, Ei as Y, Xn as Yt, wi as Z, Nn as Zt, Po as _, ut as _n, Hr as _t, cl as a, $t as an, ii as at, Oo as b, Ae as bn, Fr as bt, qo as c, Pt as cn, ei as ct, Bo as d, xt as dn, Xr as dt, vn as en, bi as et, zo as f, Ct as fn, qr as ft, Fo as g, dt as gn, Ur as gt, Io as h, ft as hn, Wr as ht, dl as i, Sn as in, si as it, $a as j, we as jn, Qn as jt, fo as k, ke as kn, pr as kt, Ko as l, Et as ln, $r as lt, Lo as m, St as mn, Gr as mt, cu as n, K as nn, pi as nt, Nc as o, tn as on, ri as ot, Ro as p, yt as pn, Kr as pt, Mi as q, Vn as qt, Tl as r, Tn as rn, li as rt, ys as s, nn as sn, ni as st, wu as t, yn as tn, mi as tt, Vo as u, pt as un, Qr as ut, jo as v, H as vn, Vr as vt, yo as w, Oe as wn, Ar as wt, Do as x, Ce as xn, Pr as xt, Ao as y, Se as yn, Ir as yt, ma as z, Zn as zt };
|
|
6711
6714
|
|
|
6712
|
-
//# sourceMappingURL=features-
|
|
6715
|
+
//# sourceMappingURL=features-BnG-dhzy.js.map
|