From dbf72551a8ad03b594610f47ebc9d7bfb7be7720 Mon Sep 17 00:00:00 2001 From: NGPixel Date: Fri, 28 Aug 2026 19:31:17 -0400 Subject: [PATCH] feat: add insert abbr button in markdown editor --- backend/locales/en.json | 3 ++ frontend/src/assets/icons.generated.js | 3 +- frontend/src/components/EditorMarkdown.vue | 48 +++++++++++++++++++--- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/backend/locales/en.json b/backend/locales/en.json index 34fe7beda..3e2d99809 100644 --- a/backend/locales/en.json +++ b/backend/locales/en.json @@ -1736,6 +1736,8 @@ "editor.localeRel.selectPage": "Select Page", "editor.localeRel.thisPage": "This page", "editor.localeRel.title": "Locale Relations", + "editor.markup.abbreviationDefinition": "Definition", + "editor.markup.abbreviationTerm": "TERM", "editor.markup.admonitionDanger": "Caution Admonition", "editor.markup.admonitionImportant": "Important Admonition", "editor.markup.admonitionInfo": "Note Admonition", @@ -1758,6 +1760,7 @@ "editor.markup.heading": "Heading {level}", "editor.markup.highlight": "Highlight", "editor.markup.inlineCode": "Inline Code", + "editor.markup.insertAbbreviation": "Insert Abbreviation", "editor.markup.insertAssets": "Insert Assets", "editor.markup.insertBlock": "Insert Block", "editor.markup.insertCodeBlock": "Insert Code Block", diff --git a/frontend/src/assets/icons.generated.js b/frontend/src/assets/icons.generated.js index e14a5a8ad..dd0dfeb4c 100644 --- a/frontend/src/assets/icons.generated.js +++ b/frontend/src/assets/icons.generated.js @@ -5,7 +5,7 @@ never waits on (or depends on) the icon service. Regenerate with `npm run icons` after adding or removing an icon; `check-icons.mjs` fails the build if this drifts. - 269 icons. + 270 icons. */ export const BUNDLED_ICONS = { "la:angle-right": {"body":"","width":32,"height":32}, @@ -271,6 +271,7 @@ export const BUNDLED_ICONS = { "mdi:table-split-cell": {"body":"","width":24,"height":24}, "mdi:tag": {"body":"","width":24,"height":24}, "mdi:text-box-outline": {"body":"","width":24,"height":24}, + "mdi:tooltip-plus-outline": {"body":"","width":24,"height":24}, "mdi:toy-brick-plus": {"body":"","width":24,"height":24}, "mdi:transfer-down": {"body":"","width":24,"height":24}, "mdi:transfer-up": {"body":"","width":24,"height":24}, diff --git a/frontend/src/components/EditorMarkdown.vue b/frontend/src/components/EditorMarkdown.vue index 0b40047aa..f670f5331 100644 --- a/frontend/src/components/EditorMarkdown.vue +++ b/frontend/src/components/EditorMarkdown.vue @@ -44,11 +44,6 @@ t('editor.markup.insertDefinitionList') }} - - {{ - t('editor.markup.insertFootnote') - }} - {{ @@ -65,6 +60,16 @@ t('editor.markup.insertIcon') }} + + {{ + t('editor.markup.insertFootnote') + }} + + + {{ + t('editor.markup.insertAbbreviation') + }} + {{ t('editor.markup.insertHorizontalBar') @@ -959,6 +964,39 @@ function insertBeforeEachLine({ content, before, focus = true }) { } } +/** + * An abbreviation definition, on a line of its own. + * + * `*[TERM]: definition` is a block rule, so it can follow a paragraph line directly but cannot sit + * inside one — the same reason the definition list breaks out of a line the cursor is mid-way + * through. + * + * Inserted as a Monaco snippet rather than as plain text, so the two placeholders are tab stops: the + * term starts selected and Tab moves to the definition. Both are typed over, never cleaned up. + */ +function insertAbbreviation() { + const model = editor.getModel() + const position = editor.getPosition() + const line = model.getLineContent(position.lineNumber) + const before = line.slice(0, position.column - 1).trim().length > 0 ? '\n' : '' + const after = line.slice(position.column - 1).trim().length > 0 ? '\n' : '' + const term = snippetEscape(t('editor.markup.abbreviationTerm')) + const definition = snippetEscape(t('editor.markup.abbreviationDefinition')) + + editor + .getContribution('snippetController2') + .insert(`${before}*[\${1:${term}}]: \${2:${definition}}${after}`) + editor.focus() +} + +/** + * Escape the characters Monaco's snippet parser reads as syntax, so a translated placeholder is + * inserted as the words it is + */ +function snippetEscape(text) { + return text.replace(/[\\$}]/g, '\\$&') +} + /** * Insert an Horizontal Bar */