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 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.29`, `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.
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
- if (count > 1) {
1049
- // The count sits in the gutter at the bar's start, where the handles are.
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edytor",
3
- "version": "0.1.0-next.29",
3
+ "version": "0.1.0-next.31",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/beynar/edytor.git"