Skip to content

Commit 0616ea6

Browse files
committed
Show soft-hyphens in editor
1 parent 4663024 commit 0616ea6

File tree

2 files changed

+68
-41
lines changed

2 files changed

+68
-41
lines changed

resources/js/form/components/fields/editor/Editor.vue

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,15 @@
107107
}),
108108
getTextInputReplacementsExtension(field, locale),
109109
DecorateHiddenCharacters.configure({
110-
class: cn(
111-
`relative pl-[.125em] cursor-text after:block after:absolute after:top-1/2 after:-translate-y-1/2 after:left-1/2 after:-translate-x-1/2 after:opacity-25`,
112-
`data-[key=nbsp]:after:content-['°']`,
113-
),
110+
class: `relative cursor-text selection:bg-transparent data-selected:bg-[Highlight] after:block after:opacity-25 after:absolute after:top-1/2 after:-translate-y-1/2 after:left-1/2 after:-translate-x-1/2 py-0.5`,
111+
characters: {
112+
nbsp: {
113+
class: `after:content-['°'] pl-[.125em]`,
114+
},
115+
shy: {
116+
class: `after:content-['-'] pl-[.25em]`,
117+
},
118+
}
114119
}),
115120
props.field.uploads && Upload.configure({
116121
uploadManager,
Lines changed: 59 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,90 @@
11
import { Extension } from "@tiptap/core";
22
import { Decoration, DecorationSet } from "@tiptap/pm/view";
3-
import { Plugin } from "@tiptap/pm/state";
4-
import { cn } from "@/utils/cn";
3+
import { Plugin, EditorState } from "@tiptap/pm/state";
54

6-
export const DecorateHiddenCharacters = Extension.create({
7-
name: 'decorateHiddenCharacters',
5+
type Key = "nbsp" | "shy";
6+
7+
type Options = {
8+
class?: string;
9+
characters?: {
10+
[key in Key]: { class: string };
11+
};
12+
};
13+
14+
export const DecorateHiddenCharacters = Extension.create<Options>({
15+
name: "decorateHiddenCharacters",
816

917
addOptions() {
10-
return {
11-
class: '',
12-
}
18+
return {};
1319
},
1420

15-
1621
addProseMirrorPlugins() {
17-
const buildDecorations = (doc) => {
18-
const decorations = []
22+
const buildCharDecorations = (
23+
state: EditorState,
24+
{ char, key }: { char: string, key: Key }
25+
) => {
26+
const decorations: Decoration[] = [];
1927

28+
state.doc.descendants((node, pos) => {
29+
if (!node.isText) return true;
2030

21-
doc.descendants((node, pos) => {
22-
if (!node.isText) return true
31+
const text = node.text;
32+
if (!text) return true;
2333

34+
let idx = text.indexOf(char);
35+
while (idx !== -1) {
36+
const from = pos + idx;
37+
const to = from + 1;
2438

25-
const text = node.text
26-
if (!text) return true
39+
const isSelected =
40+
(from >= state.selection.from && from < state.selection.to) ||
41+
(to > state.selection.from && to <= state.selection.to);
2742

28-
let idx = text.indexOf('\u00A0')
29-
while (idx !== -1) {
30-
const from = pos + idx
31-
const to = from + 1
3243
const deco = Decoration.inline(from, to, {
33-
'data-key': 'nbsp',
34-
class: this.options.class,
35-
})
36-
decorations.push(deco)
37-
idx = text.indexOf('\u00A0', idx + 1)
38-
}
44+
'data-key': key,
45+
'data-selected': isSelected ? 'true' : null,
46+
class: [
47+
this.options.class,
48+
this.options.characters?.[key]?.class,
49+
]
50+
.filter(Boolean)
51+
.join(' '),
52+
});
53+
decorations.push(deco);
3954

55+
idx = text.indexOf(char, idx + 1);
56+
}
4057

41-
return true
42-
})
43-
58+
return true;
59+
});
4460

45-
return DecorationSet.create(doc, decorations)
46-
}
61+
return decorations;
62+
};
4763

64+
const buildDecorations = (state: EditorState) => {
65+
return DecorationSet.create(state.doc, [
66+
...buildCharDecorations(state, { char: '\u00A0', key: 'nbsp' }),
67+
...buildCharDecorations(state, { char: '\u00AD', key: 'shy' }),
68+
]);
69+
};
4870

4971
return [
5072
new Plugin({
5173
state: {
52-
init(_, { doc }) {
53-
return buildDecorations(doc)
74+
init(_, state) {
75+
return buildDecorations(state);
5476
},
55-
apply(tr, old) {
56-
if (!tr.docChanged) return old
57-
return buildDecorations(tr.doc)
77+
apply(tr, old, oldState, newState) {
78+
if (!tr.docChanged && !tr.selectionSet) return old;
79+
return buildDecorations(newState);
5880
},
5981
},
6082
props: {
6183
decorations(state) {
62-
return this.getState(state)
84+
return this.getState(state);
6385
},
6486
},
6587
}),
66-
]
88+
];
6789
},
68-
})
90+
});

0 commit comments

Comments
 (0)