mirror of
https://gitlab.com/ArkHost/HelixNotes.git
synced 2026-09-22 10:57:28 +02:00
Wrap selected text with typed pairs (#119)
Adds modular selection-pair handling for the rich editor and source mode so typing (, {, [, ', " or ` around a selection wraps it instead of replacing it.
Reviewed-on: https://codeberg.org/ArkHost/HelixNotes/pulls/119
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import { Extension } from '@tiptap/core';
|
||||
import { Plugin, PluginKey, TextSelection } from '@tiptap/pm/state';
|
||||
import { getSelectionPair } from '../selectionPairs';
|
||||
|
||||
// TipTap/ProseMirror behavior for wrapping a non-empty rich-editor selection
|
||||
// without flattening selected inline marks or content to plain text.
|
||||
export const WrapSelectedText = Extension.create({
|
||||
name: 'wrapSelectedText',
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
new Plugin({
|
||||
key: new PluginKey('wrapSelectedText'),
|
||||
props: {
|
||||
// Intercept only supported single-character text input over a range.
|
||||
// Everything else falls back to ProseMirror's normal input handling.
|
||||
handleTextInput(view, from, to, text) {
|
||||
const close = getSelectionPair(text);
|
||||
if (!close || from === to) return false;
|
||||
|
||||
const tr = view.state.tr.insertText(close, to).insertText(text, from);
|
||||
tr.setSelection(TextSelection.create(tr.doc, from + text.length, to + text.length));
|
||||
view.dispatch(tr);
|
||||
return true;
|
||||
},
|
||||
},
|
||||
}),
|
||||
];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
// Shared source of truth for editor behaviors that wrap an active selection.
|
||||
// Keep this small: only characters users reasonably expect to form pairs.
|
||||
const selectionPairs: Record<string, string> = {
|
||||
'(': ')',
|
||||
'{': '}',
|
||||
'[': ']',
|
||||
"'": "'",
|
||||
'"': '"',
|
||||
'`': '`',
|
||||
};
|
||||
|
||||
// Returns the closing character for a typed wrapper key, or null to pass through.
|
||||
export function getSelectionPair(key: string) {
|
||||
return selectionPairs[key] ?? null;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { getSelectionPair } from '../selectionPairs';
|
||||
|
||||
// Pure source-mode helper: computes the wrapped textarea value and preserved
|
||||
// inner selection so Svelte event handlers stay as glue code.
|
||||
export function wrapTextareaSelection(value: string, start: number, end: number, key: string) {
|
||||
const close = getSelectionPair(key);
|
||||
if (!close || start === end) return null;
|
||||
|
||||
return {
|
||||
value: value.slice(0, start) + key + value.slice(start, end) + close + value.slice(end),
|
||||
selectionStart: start + key.length,
|
||||
selectionEnd: end + key.length,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user