@nextblock-cms/editor 0.15.10 → 0.16.2
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/index.css +1 -1
- package/index.d.ts +3 -0
- package/index.js +81 -90
- package/index.mjs +15499 -17968
- package/lib/NotionEditor.d.ts +40 -2
- package/lib/components/ui/AdvancedColorMenu.d.ts +1 -1
- package/lib/components/ui/AdvancedFontSizeMenu.d.ts +1 -2
- package/lib/components/widgets/AlertWidgetComponent.d.ts +1 -1
- package/lib/components/widgets/CtaWidgetComponent.d.ts +1 -1
- package/lib/extensions/LowContrastTextHint.d.ts +25 -0
- package/lib/extensions/ResizableImageView.d.ts +1 -2
- package/lib/extensions/components/AlertWidgetComponent.d.ts +1 -1
- package/lib/extensions/components/CtaWidgetComponent.d.ts +1 -1
- package/lib/extensions/low-contrast.d.ts +147 -0
- package/package.json +1 -1
- package/styles/advanced-features.css +33 -33
- package/styles/drag-handle.css +162 -162
- package/styles/editor.css +252 -252
package/lib/NotionEditor.d.ts
CHANGED
|
@@ -1,17 +1,55 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
2
|
import { JSONContent } from '@tiptap/react';
|
|
3
|
-
import { Extensions } from '@tiptap/core';
|
|
3
|
+
import { Editor, Extensions } from '@tiptap/core';
|
|
4
4
|
import { OpenImagePicker } from './utils/mediaPicker';
|
|
5
|
-
interface NotionEditorProps {
|
|
5
|
+
export interface NotionEditorProps {
|
|
6
6
|
content?: string | JSONContent;
|
|
7
7
|
initialContent?: string | JSONContent;
|
|
8
8
|
onChange?: (content: string) => void;
|
|
9
|
+
/**
|
|
10
|
+
* Hands the live Tiptap instance to the host application.
|
|
11
|
+
*
|
|
12
|
+
* The editor is otherwise completely sealed: `useEditor` owns the instance,
|
|
13
|
+
* nothing forwards a ref, and there is no context provider, so a sibling of
|
|
14
|
+
* this component has historically had exactly one way to reach the editor —
|
|
15
|
+
* the `window.__nextblockEditor` global assigned further down this file. That
|
|
16
|
+
* global is a genuine race as soon as two editors are mounted at once (a
|
|
17
|
+
* block editor modal opened over a page that already renders one), because
|
|
18
|
+
* the last mount silently wins and the loser's unmount can delete the
|
|
19
|
+
* winner's entry. Anything that needs *this* editor rather than *an* editor
|
|
20
|
+
* should take it from here instead; the global stays in place because other
|
|
21
|
+
* code still reads it.
|
|
22
|
+
*
|
|
23
|
+
* Called with the instance once `useEditor` has produced one, and with `null`
|
|
24
|
+
* on unmount so the host can drop its reference rather than hold a detached
|
|
25
|
+
* ProseMirror view alive. The callback is read through a ref internally, so
|
|
26
|
+
* an inline arrow function is safe to pass: a new identity on every parent
|
|
27
|
+
* render will not re-fire the effect and produce a null/instance flapping
|
|
28
|
+
* sequence.
|
|
29
|
+
*/
|
|
30
|
+
onEditorReady?: (editor: Editor | null) => void;
|
|
9
31
|
onUpdate?: (content: JSONContent) => void;
|
|
10
32
|
placeholder?: string;
|
|
11
33
|
editable?: boolean;
|
|
12
34
|
showToolbar?: boolean;
|
|
13
35
|
showAiPrompt?: boolean;
|
|
14
36
|
showCharacterCount?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Optional companion UI rendered beside the writing surface — the CMS uses it
|
|
39
|
+
* for the live SEO audit, which has to sit next to the prose it is grading
|
|
40
|
+
* rather than in a dialog the author has to leave the document to open.
|
|
41
|
+
*
|
|
42
|
+
* This library cannot import anything application-specific (it declares no
|
|
43
|
+
* dependencies, ships as a client bundle, and must not reach into
|
|
44
|
+
* `apps/nextblock`), so the panel is injected as a node rather than
|
|
45
|
+
* configured through a flag — the same idiom as `openImagePicker`.
|
|
46
|
+
*
|
|
47
|
+
* When omitted the component renders exactly the tree it always has: no extra
|
|
48
|
+
* wrapper element, no changed class list on the root, and therefore no layout
|
|
49
|
+
* shift for the callers that never pass it. The two-column layout only comes
|
|
50
|
+
* into existence when there is something to put in the second column.
|
|
51
|
+
*/
|
|
52
|
+
sidePanel?: React.ReactNode;
|
|
15
53
|
className?: string;
|
|
16
54
|
onFocus?: () => void;
|
|
17
55
|
onBlur?: () => void;
|
|
@@ -5,5 +5,5 @@ interface AdvancedColorMenuProps {
|
|
|
5
5
|
className?: string;
|
|
6
6
|
initialMode?: Mode;
|
|
7
7
|
}
|
|
8
|
-
export declare function AdvancedColorMenu({ editor, className, initialMode }: AdvancedColorMenuProps): import("react").JSX.Element;
|
|
8
|
+
export declare function AdvancedColorMenu({ editor, className, initialMode }: AdvancedColorMenuProps): import("react/jsx-runtime").JSX.Element;
|
|
9
9
|
export {};
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { default as React } from 'react';
|
|
2
1
|
import { Editor } from '@tiptap/core';
|
|
3
2
|
interface AdvancedFontSizeMenuProps {
|
|
4
3
|
editor: Editor;
|
|
5
4
|
className?: string;
|
|
6
5
|
}
|
|
7
|
-
export declare function AdvancedFontSizeMenu({ editor, className }: AdvancedFontSizeMenuProps):
|
|
6
|
+
export declare function AdvancedFontSizeMenu({ editor, className }: AdvancedFontSizeMenuProps): import("react/jsx-runtime").JSX.Element;
|
|
8
7
|
export default AdvancedFontSizeMenu;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { NodeViewProps } from '@tiptap/react';
|
|
2
|
-
declare const AlertWidgetComponent: ({ node, updateAttributes, editor }: NodeViewProps) => import("react").JSX.Element;
|
|
2
|
+
declare const AlertWidgetComponent: ({ node, updateAttributes, editor }: NodeViewProps) => import("react/jsx-runtime").JSX.Element;
|
|
3
3
|
export default AlertWidgetComponent;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { NodeViewProps } from '@tiptap/react';
|
|
2
|
-
declare const CtaWidgetComponent: ({ node, updateAttributes, editor }: NodeViewProps) => import("react").JSX.Element;
|
|
2
|
+
declare const CtaWidgetComponent: ({ node, updateAttributes, editor }: NodeViewProps) => import("react/jsx-runtime").JSX.Element;
|
|
3
3
|
export default CtaWidgetComponent;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core';
|
|
2
|
+
import { PluginKey } from '@tiptap/pm/state';
|
|
3
|
+
import { DecorationSet } from '@tiptap/pm/view';
|
|
4
|
+
import { Rgba } from '../../../../utils/src/index.ts';
|
|
5
|
+
export interface LowContrastTextHintOptions {
|
|
6
|
+
/** Set to `false` to disable the hint entirely without unregistering it. */
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Documents larger than this (in ProseMirror content units) skip the walk.
|
|
10
|
+
* The editor's own `CharacterCount` caps authored content at 50,000
|
|
11
|
+
* characters, so this only ever fires for pathological pasted markup, where
|
|
12
|
+
* freezing the tab would be far worse than losing the hint.
|
|
13
|
+
*/
|
|
14
|
+
maxDocumentSize: number;
|
|
15
|
+
/** Minimum contrast ratio for body copy. Large text is scaled from this. */
|
|
16
|
+
threshold: number;
|
|
17
|
+
}
|
|
18
|
+
export declare const lowContrastTextHintKey: PluginKey<LowContrastPluginState>;
|
|
19
|
+
interface LowContrastPluginState {
|
|
20
|
+
decorations: DecorationSet;
|
|
21
|
+
/** The resolved editing surface the current `decorations` were measured against. */
|
|
22
|
+
surface: Rgba | null;
|
|
23
|
+
}
|
|
24
|
+
export declare const LowContrastTextHint: Extension<LowContrastTextHintOptions, any>;
|
|
25
|
+
export default LowContrastTextHint;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import { NodeViewProps } from '@tiptap/react';
|
|
2
|
-
|
|
3
|
-
export default function ResizableImageView(props: NodeViewProps): React.JSX.Element;
|
|
2
|
+
export default function ResizableImageView(props: NodeViewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { NodeViewProps } from '@tiptap/react';
|
|
2
|
-
declare const AlertWidgetComponent: ({ node, updateAttributes, editor }: NodeViewProps) => import("react").JSX.Element;
|
|
2
|
+
declare const AlertWidgetComponent: ({ node, updateAttributes, editor }: NodeViewProps) => import("react/jsx-runtime").JSX.Element;
|
|
3
3
|
export default AlertWidgetComponent;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { NodeViewProps } from '@tiptap/react';
|
|
2
|
-
declare const CtaWidgetComponent: ({ node, updateAttributes, editor }: NodeViewProps) => import("react").JSX.Element;
|
|
2
|
+
declare const CtaWidgetComponent: ({ node, updateAttributes, editor }: NodeViewProps) => import("react/jsx-runtime").JSX.Element;
|
|
3
3
|
export default CtaWidgetComponent;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Rgba } from '../../../../utils/src/index.ts';
|
|
2
|
+
/** WCAG 2.1 AA minimum contrast for body copy. */
|
|
3
|
+
export declare const BODY_TEXT_CONTRAST_THRESHOLD = 4.5;
|
|
4
|
+
/** WCAG 2.1 AA minimum contrast for large text. */
|
|
5
|
+
export declare const LARGE_TEXT_CONTRAST_THRESHOLD = 3;
|
|
6
|
+
/** WCAG's "large text" floor for regular weights: 18pt === 24px. */
|
|
7
|
+
export declare const LARGE_TEXT_MIN_PX = 24;
|
|
8
|
+
/** WCAG's "large text" floor for bold weights: 14pt === 18.66px. */
|
|
9
|
+
export declare const LARGE_TEXT_BOLD_MIN_PX = 18.66;
|
|
10
|
+
/**
|
|
11
|
+
* Heading levels we are willing to treat as large text when the document does
|
|
12
|
+
* not carry an explicit font size.
|
|
13
|
+
*
|
|
14
|
+
* This is a deliberate approximation and we would rather say so than pretend
|
|
15
|
+
* otherwise: the only way to know a run's *rendered* size is to measure it in
|
|
16
|
+
* the DOM, which would force a layout for every coloured run on every rebuild.
|
|
17
|
+
* So an explicit `font-size` on the run wins when it exists, and otherwise we
|
|
18
|
+
* assume h1-h3 clear 24px in the editor's typography while h4-h6 do not and are
|
|
19
|
+
* held to the stricter body threshold.
|
|
20
|
+
*/
|
|
21
|
+
export declare const LARGE_TEXT_MAX_HEADING_LEVEL = 3;
|
|
22
|
+
/** Stable class name on the hint decoration, so integrators can restyle it. */
|
|
23
|
+
export declare const LOW_CONTRAST_HINT_CLASS = "nb-low-contrast-hint";
|
|
24
|
+
export interface LowContrastVerdict {
|
|
25
|
+
flag: boolean;
|
|
26
|
+
ratio: number;
|
|
27
|
+
}
|
|
28
|
+
export interface LargeTextInput {
|
|
29
|
+
bold: boolean;
|
|
30
|
+
fontSize: string | null;
|
|
31
|
+
headingLevel: number | null;
|
|
32
|
+
}
|
|
33
|
+
export interface LowContrastHintAttrsInput {
|
|
34
|
+
backdrop: string;
|
|
35
|
+
foreground: string;
|
|
36
|
+
ratio: number;
|
|
37
|
+
threshold: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The index signature is what makes this structurally assignable to
|
|
41
|
+
* ProseMirror's `DecorationAttrs`, which is an open bag of DOM attributes.
|
|
42
|
+
*/
|
|
43
|
+
export interface LowContrastHintAttrs {
|
|
44
|
+
[attribute: string]: string;
|
|
45
|
+
'aria-label': string;
|
|
46
|
+
class: string;
|
|
47
|
+
style: string;
|
|
48
|
+
title: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The contrast bar a run has to clear.
|
|
52
|
+
*
|
|
53
|
+
* WCAG does not define a formula linking the body and large-text minimums — it
|
|
54
|
+
* just publishes the pairs (4.5, 3) for AA and (7, 4.5) for AAA. Rather than
|
|
55
|
+
* hardcode a table we scale a caller-supplied body threshold by the AA ratio,
|
|
56
|
+
* which reproduces 3 exactly for the default 4.5 and stays proportional if an
|
|
57
|
+
* integrator raises the bar.
|
|
58
|
+
*/
|
|
59
|
+
export declare function contrastThresholdFor(isLargeText: boolean, bodyThreshold?: number): number;
|
|
60
|
+
/**
|
|
61
|
+
* Decide whether a run of text is unreadable against the editing surface.
|
|
62
|
+
*
|
|
63
|
+
* `background` is expected to be opaque because the caller has already
|
|
64
|
+
* flattened the DOM's background stack; any alpha on it is ignored rather than
|
|
65
|
+
* guessed at. A colour we cannot parse is never flagged — a false positive on a
|
|
66
|
+
* value we do not understand would put a black chip behind perfectly readable
|
|
67
|
+
* text — and this function never throws, because it runs inside a ProseMirror
|
|
68
|
+
* transaction handler where an exception would break editing entirely.
|
|
69
|
+
*/
|
|
70
|
+
export declare function shouldFlagLowContrast(foreground: string, background: string, isLargeText: boolean, bodyThreshold?: number): LowContrastVerdict;
|
|
71
|
+
/**
|
|
72
|
+
* Flatten a translucent colour onto the surface it is painted over.
|
|
73
|
+
*
|
|
74
|
+
* Contrast is a property of what the eye actually receives, so a 10%-opacity
|
|
75
|
+
* black on white is effectively a pale grey and needs a *dark* chip, not the
|
|
76
|
+
* light one its raw RGB would suggest.
|
|
77
|
+
*/
|
|
78
|
+
export declare function flattenColorOver(color: Rgba, background: Rgba): Rgba;
|
|
79
|
+
/**
|
|
80
|
+
* Pick the chip colour to paint behind an unreadable run.
|
|
81
|
+
*
|
|
82
|
+
* Contrast is symmetric, so the colour that would be readable *on* this text is
|
|
83
|
+
* also the colour this text is readable *on*: `readableTextColor` therefore
|
|
84
|
+
* doubles as a backdrop picker, giving pale text a black chip and dark text a
|
|
85
|
+
* white one. Pass the flattened colour from {@link flattenColorOver} — the raw
|
|
86
|
+
* value would mis-classify translucent text.
|
|
87
|
+
*
|
|
88
|
+
* The chip is fully opaque on purpose. Softening it with alpha would blend it
|
|
89
|
+
* back towards the surface and re-introduce exactly the contrast loss we are
|
|
90
|
+
* fixing; the rounded shape and dashed outline are what mark it as an
|
|
91
|
+
* affordance, not transparency.
|
|
92
|
+
*/
|
|
93
|
+
export declare function pickBackdropColor(foreground: Rgba): string;
|
|
94
|
+
/**
|
|
95
|
+
* Flatten a stack of background layers, nearest-to-the-text first, onto a
|
|
96
|
+
* fallback surface.
|
|
97
|
+
*
|
|
98
|
+
* The DOM walk collects `background-color` from the text's element and its
|
|
99
|
+
* ancestors; the first opaque layer terminates the stack and everything nearer
|
|
100
|
+
* is composited over it. When nothing opaque is found the caller's fallback
|
|
101
|
+
* stands in.
|
|
102
|
+
*/
|
|
103
|
+
export declare function flattenBackgroundLayers(layers: readonly Rgba[], fallback: Rgba): Rgba;
|
|
104
|
+
/**
|
|
105
|
+
* Last-resort guess at the editing surface when every ancestor is transparent
|
|
106
|
+
* all the way up (which happens inside portals and some print stylesheets).
|
|
107
|
+
*
|
|
108
|
+
* Hardcoding white here would break the dark and 'vibrant' CMS themes, where
|
|
109
|
+
* the *dark* author colours are the invisible ones. The default text colour is
|
|
110
|
+
* a reliable proxy for the theme: light default text means a dark page.
|
|
111
|
+
*/
|
|
112
|
+
export declare function inferSurfaceFromTextColor(textColor: Rgba): Rgba;
|
|
113
|
+
/**
|
|
114
|
+
* Pull one declaration out of an inline `style` attribute.
|
|
115
|
+
*
|
|
116
|
+
* The editor's `PreserveAllAttributesExtension` and `SpanNode` keep raw `style`
|
|
117
|
+
* strings on nodes and on the `textStyle` mark, so pasted markup can carry a
|
|
118
|
+
* colour that never reaches a typed Tiptap attribute. Later declarations win,
|
|
119
|
+
* matching the cascade. This is a pragmatic split rather than a real CSS parser:
|
|
120
|
+
* a value containing a semicolon (a `url(data:...;base64,...)` background, say)
|
|
121
|
+
* will not be extracted, which for our purposes means "no colour found" and is
|
|
122
|
+
* safely ignored.
|
|
123
|
+
*/
|
|
124
|
+
export declare function extractStyleDeclaration(style: string | null | undefined, property: string): string | null;
|
|
125
|
+
/**
|
|
126
|
+
* Convert a CSS font size to pixels.
|
|
127
|
+
*
|
|
128
|
+
* `rem` and `em` are resolved against a 16px root, which is the browser default
|
|
129
|
+
* and what this workspace's Tailwind config assumes; we accept that this is an
|
|
130
|
+
* approximation rather than measuring the cascade, because it only ever moves a
|
|
131
|
+
* run between the 4.5:1 and 3:1 bars.
|
|
132
|
+
*/
|
|
133
|
+
export declare function parseFontSizePx(value: string | null | undefined): number | null;
|
|
134
|
+
/** Whether a run qualifies for WCAG's relaxed large-text threshold. */
|
|
135
|
+
export declare function isLargeTextRun(input: LargeTextInput): boolean;
|
|
136
|
+
/** Trim a contrast ratio to the shortest exact decimal, for display. */
|
|
137
|
+
export declare function formatContrastRatio(ratio: number): string;
|
|
138
|
+
/**
|
|
139
|
+
* Build the DOM attributes for a hint decoration.
|
|
140
|
+
*
|
|
141
|
+
* The chip deliberately keeps the author's real colour on top: we are making
|
|
142
|
+
* their text legible, not overriding their choice, and an author who cannot see
|
|
143
|
+
* the text also cannot guess why it suddenly looks different — hence the
|
|
144
|
+
* `title`/`aria-label` quoting the measured ratio and saying, in as many words,
|
|
145
|
+
* that this is an editing aid and is not saved.
|
|
146
|
+
*/
|
|
147
|
+
export declare function buildLowContrastHintAttrs(input: LowContrastHintAttrsInput): LowContrastHintAttrs;
|
package/package.json
CHANGED
|
@@ -7,17 +7,17 @@
|
|
|
7
7
|
position: relative;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
.is-empty::before {
|
|
11
|
-
color: var(--muted-foreground);
|
|
12
|
-
content: attr(data-placeholder);
|
|
13
|
-
position: absolute;
|
|
14
|
-
left: 0;
|
|
15
|
-
float: none;
|
|
16
|
-
height: auto;
|
|
17
|
-
pointer-events: none;
|
|
18
|
-
font-style: italic;
|
|
19
|
-
opacity: 0.6;
|
|
20
|
-
}
|
|
10
|
+
.is-empty::before {
|
|
11
|
+
color: var(--muted-foreground);
|
|
12
|
+
content: attr(data-placeholder);
|
|
13
|
+
position: absolute;
|
|
14
|
+
left: 0;
|
|
15
|
+
float: none;
|
|
16
|
+
height: auto;
|
|
17
|
+
pointer-events: none;
|
|
18
|
+
font-style: italic;
|
|
19
|
+
opacity: 0.6;
|
|
20
|
+
}
|
|
21
21
|
|
|
22
22
|
/* Specific placeholder styles for different node types */
|
|
23
23
|
.is-empty-paragraph::before {
|
|
@@ -452,26 +452,26 @@
|
|
|
452
452
|
.mobile-toolbar {
|
|
453
453
|
border-width: 2px;
|
|
454
454
|
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
/* Suppress placeholder rendering within table cells */
|
|
458
|
-
.ProseMirror td .is-empty::before,
|
|
459
|
-
.ProseMirror th .is-empty::before,
|
|
460
|
-
.ProseMirror td.is-empty::before,
|
|
461
|
-
.ProseMirror th.is-empty::before {
|
|
462
|
-
content: none;
|
|
463
|
-
display: none;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
.ProseMirror table .is-empty::before {
|
|
467
|
-
content: none !important;
|
|
468
|
-
display: none !important;
|
|
469
|
-
float: none !important;
|
|
470
|
-
height: auto !important;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
.ProseMirror table .is-empty {
|
|
475
|
-
position: static !important;
|
|
476
|
-
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/* Suppress placeholder rendering within table cells */
|
|
458
|
+
.ProseMirror td .is-empty::before,
|
|
459
|
+
.ProseMirror th .is-empty::before,
|
|
460
|
+
.ProseMirror td.is-empty::before,
|
|
461
|
+
.ProseMirror th.is-empty::before {
|
|
462
|
+
content: none;
|
|
463
|
+
display: none;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.ProseMirror table .is-empty::before {
|
|
467
|
+
content: none !important;
|
|
468
|
+
display: none !important;
|
|
469
|
+
float: none !important;
|
|
470
|
+
height: auto !important;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
.ProseMirror table .is-empty {
|
|
475
|
+
position: static !important;
|
|
476
|
+
}
|
|
477
477
|
|