edytor 0.1.0-next.29 → 0.1.0-next.31
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/README.md +1 -1
- package/dist/plugins/blockHandles/BlockHandleController.svelte.js +2 -27
- package/dist/plugins/richtext/RichTextPlugin.svelte +14 -0
- package/dist/plugins/richtext/richTextOperations.d.ts +7 -0
- package/dist/plugins/richtext/richTextOperations.js +19 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
Edytor aims to be for Svelte what Slate.js is for React: a heavily customizable editor with an API to build any kind of collaborative rich text editor.
|
|
17
17
|
|
|
18
|
-
> **Work in progress.** Edytor is a pre-release (`0.1.0-next.
|
|
18
|
+
> **Work in progress.** Edytor is a pre-release (`0.1.0-next.31`, `edytor@next` on npm) and not ready for production; the API changes between releases without a compatibility layer. The untagged `edytor@0.0.11` on npm predates the current API. Issues and PRs are welcome: when you report a bug, include the document's JSON value.
|
|
19
19
|
|
|
20
20
|
## Features
|
|
21
21
|
|
|
@@ -120,27 +120,6 @@ const backdrop = (document) => {
|
|
|
120
120
|
node.setAttribute('aria-hidden', 'true');
|
|
121
121
|
return node;
|
|
122
122
|
};
|
|
123
|
-
/** How many blocks move: the badge beside the drop indicator. */
|
|
124
|
-
const countBadge = (document, count) => {
|
|
125
|
-
const badge = document.createElement('span');
|
|
126
|
-
badge.dataset.edytorDragCount = 'true';
|
|
127
|
-
badge.textContent = String(count);
|
|
128
|
-
Object.assign(badge.style, {
|
|
129
|
-
position: 'absolute',
|
|
130
|
-
top: '-8px',
|
|
131
|
-
right: '-8px',
|
|
132
|
-
minWidth: '18px',
|
|
133
|
-
height: '18px',
|
|
134
|
-
padding: '0 5px',
|
|
135
|
-
boxSizing: 'border-box',
|
|
136
|
-
borderRadius: '9px',
|
|
137
|
-
background: 'rgb(35, 131, 226)',
|
|
138
|
-
color: 'white',
|
|
139
|
-
font: '600 11px/18px system-ui, sans-serif',
|
|
140
|
-
textAlign: 'center'
|
|
141
|
-
});
|
|
142
|
-
return badge;
|
|
143
|
-
};
|
|
144
123
|
export class BlockHandleController {
|
|
145
124
|
edytor;
|
|
146
125
|
options;
|
|
@@ -1045,13 +1024,9 @@ export class BlockHandleController {
|
|
|
1045
1024
|
overlay.dataset.blocked = 'true';
|
|
1046
1025
|
overlay.setAttribute('aria-hidden', 'true');
|
|
1047
1026
|
const count = this.group.length;
|
|
1048
|
-
|
|
1049
|
-
|
|
1027
|
+
// How many blocks move, as data only: the bar shows no badge.
|
|
1028
|
+
if (count > 1)
|
|
1050
1029
|
overlay.dataset.count = String(count);
|
|
1051
|
-
const badge = countBadge(document, count);
|
|
1052
|
-
Object.assign(badge.style, { top: `${BAR / 2 - 9}px`, right: 'auto', left: '-26px' });
|
|
1053
|
-
overlay.append(badge);
|
|
1054
|
-
}
|
|
1055
1030
|
const color = document.defaultView
|
|
1056
1031
|
?.getComputedStyle(placement.node)
|
|
1057
1032
|
.getPropertyValue('--edytor-drop-indicator-color')
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import type { SerializableContent } from '../../utils/json.js';
|
|
5
5
|
import type { HotKey } from '../../session/keymap.js';
|
|
6
6
|
import {
|
|
7
|
+
pastedLink,
|
|
7
8
|
richTextOperations,
|
|
8
9
|
sanitizeLinkHref,
|
|
9
10
|
sanitizeCssColorValue,
|
|
@@ -141,6 +142,19 @@
|
|
|
141
142
|
])
|
|
142
143
|
)
|
|
143
144
|
},
|
|
145
|
+
// Pasting a link over selected text links it (Notion), where the toolbar
|
|
146
|
+
// offers a link: a text range outside code blocks and voids.
|
|
147
|
+
onPaste: ({ e, prevent }) => {
|
|
148
|
+
const { selection } = edytor;
|
|
149
|
+
const { value, projection } = selection;
|
|
150
|
+
if (value.kind !== 'text' || selection.state.isCollapsed) return;
|
|
151
|
+
if (projection.islandRoot !== null || projection.voidRoot !== null) return;
|
|
152
|
+
const href = pastedLink(e.clipboardData?.getData('text/plain'));
|
|
153
|
+
if (href)
|
|
154
|
+
prevent(() =>
|
|
155
|
+
edytor.dispatcher.run('insertFromPaste', () => operations.setLinkAtRange({ href }))
|
|
156
|
+
);
|
|
157
|
+
},
|
|
144
158
|
onBeforeInput: ({ e, prevent }) => {
|
|
145
159
|
const { inputType, data } = e;
|
|
146
160
|
if (isNativeFormatInputType(inputType)) {
|
|
@@ -24,6 +24,13 @@ export declare const sanitizeCssColorValue: (value: unknown) => string | null;
|
|
|
24
24
|
* pass through.
|
|
25
25
|
*/
|
|
26
26
|
export declare const sanitizeLinkHref: (href: unknown) => string | null;
|
|
27
|
+
/**
|
|
28
|
+
* The URL a paste carries when its plain text is one link and nothing else
|
|
29
|
+
* (surrounding whitespace aside): `http:`/`https:` with a host, or
|
|
30
|
+
* `mailto:`. `null` for anything else — words, several URLs, a bare
|
|
31
|
+
* domain, a scriptable scheme.
|
|
32
|
+
*/
|
|
33
|
+
export declare const pastedLink: (plain: string | undefined | null) => string | null;
|
|
27
34
|
export declare const richTextOperations: (edytor: Edytor) => {
|
|
28
35
|
/**
|
|
29
36
|
* Insert a `divider` void block at the caret — the native
|
|
@@ -52,6 +52,25 @@ export const sanitizeLinkHref = (href) => {
|
|
|
52
52
|
return null;
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
|
+
/**
|
|
56
|
+
* The URL a paste carries when its plain text is one link and nothing else
|
|
57
|
+
* (surrounding whitespace aside): `http:`/`https:` with a host, or
|
|
58
|
+
* `mailto:`. `null` for anything else — words, several URLs, a bare
|
|
59
|
+
* domain, a scriptable scheme.
|
|
60
|
+
*/
|
|
61
|
+
export const pastedLink = (plain) => {
|
|
62
|
+
const text = plain?.trim();
|
|
63
|
+
if (!text || /\s/.test(text))
|
|
64
|
+
return null;
|
|
65
|
+
try {
|
|
66
|
+
const url = new URL(text);
|
|
67
|
+
const web = (url.protocol === 'http:' || url.protocol === 'https:') && url.host;
|
|
68
|
+
return web || (url.protocol === 'mailto:' && url.pathname) ? sanitizeLinkHref(text) : null;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
55
74
|
const formatSelectedTextRange = (edytor, mark, value, toggle) => {
|
|
56
75
|
const { yStart, yEnd, startText, endText, isCollapsed, isReversed } = edytor.selection.state;
|
|
57
76
|
if (isCollapsed || !startText || !endText)
|