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
*/