@colixsystems/widget-sdk 0.275.2 → 0.277.0
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 +13 -3
- package/dist/bundled-assets.cjs +6 -2
- package/dist/bundled-assets.js +0 -1
- package/dist/contract.cjs +13 -9
- package/dist/contract.js +10 -7
- package/dist/define-widget.js +8 -1
- package/dist/index.d.ts +15 -16
- package/dist/linter.cjs +279 -210
- package/dist/lucideIconName.cjs +14 -5
- package/dist/manifest.cjs +7 -15
- package/dist/manifest.js +10 -69
- package/dist/markdown-doc.js +209 -0
- package/dist/markdown-editor-bridge.js +162 -0
- package/dist/markdown-editor-core.js +184 -0
- package/dist/markdown-editor-css.js +50 -0
- package/dist/markdown-editor-field.js +111 -0
- package/dist/markdown-editor-field.native.js +9 -0
- package/dist/markdown-editor-html.js +4 -0
- package/dist/markdown-editor-webview.js +70 -0
- package/dist/markdown-input-view.js +43 -101
- package/dist/markdown-input.js +4 -6
- package/dist/markdown-input.native.js +3 -5
- package/dist/markdown.js +1 -112
- package/dist/navigation.cjs +31 -11
- package/dist/richtext-tokens.js +1 -25
- package/dist/source-mask.cjs +169 -0
- package/dist/theme-appearance.cjs +7 -5
- package/dist/theme-appearance.js +1 -1
- package/dist/theme-background.cjs +3 -8
- package/dist/theme-components.cjs +13 -10
- package/package.json +63 -3
- package/src/contract.js +10 -7
- package/dist/markdown-edit.js +0 -164
- package/dist/markdown-live-field.js +0 -312
- package/dist/markdown-live-field.native.js +0 -36
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// GENERATED from source-mask.js by scripts/generate-cjs.js — DO NOT EDIT.
|
|
2
|
+
// Change source-mask.js, then run `npm run generate:cjs` in packages/widget-sdk.
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
// sc-6086: the shared source masker, extracted verbatim from linter.js so the
|
|
6
|
+
// linter, the packer's import scanner, and the dev-server guard all agree on
|
|
7
|
+
// what is code and what is a comment or string literal (CLAUDE.md §3).
|
|
8
|
+
//
|
|
9
|
+
// Length-preserving: every blanked character becomes a space and newlines are
|
|
10
|
+
// kept, so an index into the mask is the SAME index in the original source.
|
|
11
|
+
// That is what lets a caller match against the mask and splice the original.
|
|
12
|
+
|
|
13
|
+
// Replace the *content* of comments and string / template literals with
|
|
14
|
+
// spaces so the banned-identifier scan only ever sees executable code. A
|
|
15
|
+
// banned host-escape identifier (`window`, `document`, `eval`, `process`, …)
|
|
16
|
+
// is only dangerous as a real identifier reference — never as prose in a
|
|
17
|
+
// `//` comment or as character data inside a string — so matching the bare
|
|
18
|
+
// word there is a false positive that blocks an otherwise-clean widget (a
|
|
19
|
+
// comment that reads "the hour window the grid renders" must not trip
|
|
20
|
+
// `no-window`).
|
|
21
|
+
//
|
|
22
|
+
// Newlines are preserved verbatim so reported line numbers still line up
|
|
23
|
+
// with the original source. Template-literal `${ … }` expression holes are
|
|
24
|
+
// left intact: real code lives there and must still be scanned (`${window}`
|
|
25
|
+
// is a genuine escape). Backslash escapes inside strings/templates are
|
|
26
|
+
// consumed so an escaped quote (`"\""`) doesn't end the literal early.
|
|
27
|
+
function stripNonCode(
|
|
28
|
+
source,
|
|
29
|
+
{ keepStrings = false, keepTemplates = keepStrings } = {},
|
|
30
|
+
) {
|
|
31
|
+
let out = "";
|
|
32
|
+
const n = source.length;
|
|
33
|
+
let mode = "code"; // code | line | block | sq | dq | tmpl
|
|
34
|
+
// Brace depth, plus a stack of the depths at which an enclosing template
|
|
35
|
+
// literal resumes — lets a `${ … }` hole (which may itself contain `{}`,
|
|
36
|
+
// strings, or nested templates) be told apart from the literal text.
|
|
37
|
+
let braceDepth = 0;
|
|
38
|
+
const tmplStack = [];
|
|
39
|
+
const keep = (ch) => {
|
|
40
|
+
out += ch;
|
|
41
|
+
};
|
|
42
|
+
const blank = (ch) => {
|
|
43
|
+
out += ch === "\n" || ch === "\r" ? ch : " ";
|
|
44
|
+
};
|
|
45
|
+
// String / template CONTENT: blanked for the banned-identifier scan
|
|
46
|
+
// (prose must not trip `no-window`), kept for the host-API-URL scan,
|
|
47
|
+
// whose whole job is to find a URL literal.
|
|
48
|
+
const str = keepStrings ? keep : blank;
|
|
49
|
+
// An import specifier is always a quoted string, never a template literal —
|
|
50
|
+
// so the import scanner keeps quoted content and blanks templates, which is
|
|
51
|
+
// what stops an `import … from "./x"` line inside a backtick block from
|
|
52
|
+
// reading as a real statement.
|
|
53
|
+
const tmplStr = keepTemplates ? keep : blank;
|
|
54
|
+
let i = 0;
|
|
55
|
+
while (i < n) {
|
|
56
|
+
const ch = source[i];
|
|
57
|
+
const nx = source[i + 1];
|
|
58
|
+
if (mode === "code") {
|
|
59
|
+
if (ch === "/" && nx === "/") {
|
|
60
|
+
mode = "line";
|
|
61
|
+
blank(ch);
|
|
62
|
+
blank(nx);
|
|
63
|
+
i += 2;
|
|
64
|
+
} else if (ch === "/" && nx === "*") {
|
|
65
|
+
mode = "block";
|
|
66
|
+
blank(ch);
|
|
67
|
+
blank(nx);
|
|
68
|
+
i += 2;
|
|
69
|
+
} else if (ch === "'") {
|
|
70
|
+
mode = "sq";
|
|
71
|
+
str(ch);
|
|
72
|
+
i += 1;
|
|
73
|
+
} else if (ch === '"') {
|
|
74
|
+
mode = "dq";
|
|
75
|
+
str(ch);
|
|
76
|
+
i += 1;
|
|
77
|
+
} else if (ch === "`") {
|
|
78
|
+
mode = "tmpl";
|
|
79
|
+
tmplStr(ch);
|
|
80
|
+
i += 1;
|
|
81
|
+
} else if (ch === "{") {
|
|
82
|
+
braceDepth += 1;
|
|
83
|
+
keep(ch);
|
|
84
|
+
i += 1;
|
|
85
|
+
} else if (ch === "}") {
|
|
86
|
+
braceDepth -= 1;
|
|
87
|
+
if (
|
|
88
|
+
tmplStack.length > 0 &&
|
|
89
|
+
tmplStack[tmplStack.length - 1] === braceDepth
|
|
90
|
+
) {
|
|
91
|
+
tmplStack.pop();
|
|
92
|
+
mode = "tmpl";
|
|
93
|
+
str(ch);
|
|
94
|
+
} else {
|
|
95
|
+
keep(ch);
|
|
96
|
+
}
|
|
97
|
+
i += 1;
|
|
98
|
+
} else {
|
|
99
|
+
keep(ch);
|
|
100
|
+
i += 1;
|
|
101
|
+
}
|
|
102
|
+
} else if (mode === "line") {
|
|
103
|
+
if (ch === "\n") {
|
|
104
|
+
mode = "code";
|
|
105
|
+
keep(ch);
|
|
106
|
+
} else {
|
|
107
|
+
blank(ch);
|
|
108
|
+
}
|
|
109
|
+
i += 1;
|
|
110
|
+
} else if (mode === "block") {
|
|
111
|
+
if (ch === "*" && nx === "/") {
|
|
112
|
+
mode = "code";
|
|
113
|
+
blank(ch);
|
|
114
|
+
blank(nx);
|
|
115
|
+
i += 2;
|
|
116
|
+
} else {
|
|
117
|
+
blank(ch);
|
|
118
|
+
i += 1;
|
|
119
|
+
}
|
|
120
|
+
} else if (mode === "sq" || mode === "dq") {
|
|
121
|
+
const quote = mode === "sq" ? "'" : '"';
|
|
122
|
+
if (ch === "\\") {
|
|
123
|
+
str(ch);
|
|
124
|
+
if (i + 1 < n) str(nx);
|
|
125
|
+
i += 2;
|
|
126
|
+
} else if (ch === quote) {
|
|
127
|
+
mode = "code";
|
|
128
|
+
str(ch);
|
|
129
|
+
i += 1;
|
|
130
|
+
} else if (ch === "\n") {
|
|
131
|
+
// A bare newline terminates an unterminated string in JS; bail back
|
|
132
|
+
// to code so malformed input can't blank the rest of the file.
|
|
133
|
+
mode = "code";
|
|
134
|
+
keep(ch);
|
|
135
|
+
i += 1;
|
|
136
|
+
} else {
|
|
137
|
+
str(ch);
|
|
138
|
+
i += 1;
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
// mode === "tmpl"
|
|
142
|
+
if (ch === "\\") {
|
|
143
|
+
tmplStr(ch);
|
|
144
|
+
if (i + 1 < n) tmplStr(nx);
|
|
145
|
+
i += 2;
|
|
146
|
+
} else if (ch === "`") {
|
|
147
|
+
mode = "code";
|
|
148
|
+
tmplStr(ch);
|
|
149
|
+
i += 1;
|
|
150
|
+
} else if (ch === "$" && nx === "{") {
|
|
151
|
+
// Enter an expression hole. Remember the brace depth the template
|
|
152
|
+
// resumes at, then count the `{` so its matching `}` is recognised.
|
|
153
|
+
tmplStack.push(braceDepth);
|
|
154
|
+
braceDepth += 1;
|
|
155
|
+
mode = "code";
|
|
156
|
+
keep(ch);
|
|
157
|
+
keep(nx);
|
|
158
|
+
i += 2;
|
|
159
|
+
} else {
|
|
160
|
+
tmplStr(ch);
|
|
161
|
+
i += 1;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
module.exports = {
|
|
168
|
+
stripNonCode,
|
|
169
|
+
};
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
1
|
+
// GENERATED from theme-appearance.js by scripts/generate-cjs.js — DO NOT EDIT.
|
|
2
|
+
// Change theme-appearance.js, then run `npm run generate:cjs` in packages/widget-sdk.
|
|
3
|
+
"use strict";
|
|
4
4
|
|
|
5
5
|
const { isHexColor, APPEARANCE_PREFERENCES } = require("./contract.cjs");
|
|
6
6
|
|
|
7
|
+
// APPEARANCE_PREFERENCES: what an app opens on and a user may choose.
|
|
8
|
+
|
|
9
|
+
|
|
7
10
|
// REQ-THEME-APPEARANCE (sc-12335) — the ONE implementation of an app's light and
|
|
8
11
|
// dark palettes, and of which one is showing. Host surface, not author API.
|
|
9
12
|
|
|
@@ -181,10 +184,9 @@ function themeInBothModes(theme, mode, twin) {
|
|
|
181
184
|
if (mode !== "dark") return withDarkPalette(base, twinPalette);
|
|
182
185
|
return withDarkPalette(overlayPalette(base, twinPalette), recolourablePalette(base));
|
|
183
186
|
}
|
|
184
|
-
|
|
185
187
|
module.exports = {
|
|
186
|
-
APPEARANCE_MODES,
|
|
187
188
|
APPEARANCE_PREFERENCES,
|
|
189
|
+
APPEARANCE_MODES,
|
|
188
190
|
themePaletteOf,
|
|
189
191
|
normaliseAppearance,
|
|
190
192
|
resolveAppearancePreference,
|
package/dist/theme-appearance.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
// APPEARANCE_PREFERENCES: what an app opens on and a user may choose.
|
|
1
2
|
import { isHexColor, APPEARANCE_PREFERENCES } from "./contract.js";
|
|
2
3
|
|
|
3
|
-
// What an app opens on and a user may choose; one list, in the contract.
|
|
4
4
|
export { APPEARANCE_PREFERENCES };
|
|
5
5
|
|
|
6
6
|
// REQ-THEME-APPEARANCE (sc-12335) — the ONE implementation of an app's light and
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
// The BODY below is copied VERBATIM from theme-background.js; only the module
|
|
6
|
-
// syntax differs. theme-background-parity.test.js pins both facts — identical
|
|
7
|
-
// bodies AND identical behaviour over a shared case table — so drift fails CI.
|
|
1
|
+
// GENERATED from theme-background.js by scripts/generate-cjs.js — DO NOT EDIT.
|
|
2
|
+
// Change theme-background.js, then run `npm run generate:cjs` in packages/widget-sdk.
|
|
3
|
+
"use strict";
|
|
8
4
|
|
|
9
5
|
// REQ-THEME-08 / REQ-PLR-05-CUSTOM — the ONE implementation of the app's
|
|
10
6
|
// background gradient and of the sign-in screen's design, shared by both hosts.
|
|
@@ -169,7 +165,6 @@ function resolveLoginTextVisibility(login) {
|
|
|
169
165
|
subheading: own.hideSubheading !== true,
|
|
170
166
|
};
|
|
171
167
|
}
|
|
172
|
-
|
|
173
168
|
module.exports = {
|
|
174
169
|
LOGIN_TEXT_MAX_LENGTH,
|
|
175
170
|
normaliseThemeGradient,
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// syntax differs. theme-components-parity.test.js pins both facts — identical
|
|
7
|
-
// bodies AND identical behaviour over a shared case table — so drift fails CI.
|
|
1
|
+
// GENERATED from theme-components.js by scripts/generate-cjs.js — DO NOT EDIT.
|
|
2
|
+
// Change theme-components.js, then run `npm run generate:cjs` in packages/widget-sdk.
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const { CONTRACT, normaliseComponentGradient } = require("./contract.cjs");
|
|
8
6
|
|
|
9
7
|
// REQ-THEME-15 (sc-1497) — the host side of per-component style tokens.
|
|
10
8
|
//
|
|
@@ -23,7 +21,6 @@
|
|
|
23
21
|
// widget reads. The Mason build runner validates `set_theme` against the SAME
|
|
24
22
|
// literal, so what a planner may emit and what a host applies cannot diverge.
|
|
25
23
|
|
|
26
|
-
const { CONTRACT, normaliseComponentGradient } = require("./contract.cjs");
|
|
27
24
|
|
|
28
25
|
// 3-, 6- and 8-digit hex, matching what the build runner persists — the host
|
|
29
26
|
// must never drop a colour the runner already accepted.
|
|
@@ -402,5 +399,11 @@ function applyThemeComponentStyle(manifestId, theme, props, styleSchema) {
|
|
|
402
399
|
const authored = isPlainObject(base.style) ? base.style : null;
|
|
403
400
|
return { ...base, style: { ...themed, ...authored } };
|
|
404
401
|
}
|
|
405
|
-
|
|
406
|
-
|
|
402
|
+
module.exports = {
|
|
403
|
+
normaliseThemeComponents,
|
|
404
|
+
MAX_STYLE_STRING_LENGTH,
|
|
405
|
+
styleValueBytes,
|
|
406
|
+
normaliseWidgetStyleFields,
|
|
407
|
+
normaliseWidgetStyles,
|
|
408
|
+
applyThemeComponentStyle,
|
|
409
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.277.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"homepage": "https://github.com/Colix-AB/AppStudio",
|
|
6
6
|
"type": "module",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
],
|
|
62
62
|
"scripts": {
|
|
63
63
|
"build": "node scripts/build.js",
|
|
64
|
-
"generate:
|
|
64
|
+
"generate:cjs": "node scripts/generate-cjs.js",
|
|
65
65
|
"test": "node --test \"src/__tests__/*.test.js\""
|
|
66
66
|
},
|
|
67
67
|
"engines": {
|
|
@@ -75,7 +75,22 @@
|
|
|
75
75
|
"react": ">=18.0.0",
|
|
76
76
|
"react-native": "*",
|
|
77
77
|
"react-dom": ">=18.0.0",
|
|
78
|
-
"react-native-web": ">=0.19.0"
|
|
78
|
+
"react-native-web": ">=0.19.0",
|
|
79
|
+
"@tiptap/core": "^3.32.2",
|
|
80
|
+
"@tiptap/extension-bold": "^3.32.2",
|
|
81
|
+
"@tiptap/extension-code": "^3.32.2",
|
|
82
|
+
"@tiptap/extension-code-block": "^3.32.2",
|
|
83
|
+
"@tiptap/extension-document": "^3.32.2",
|
|
84
|
+
"@tiptap/extension-heading": "^3.32.2",
|
|
85
|
+
"@tiptap/extension-image": "^3.32.2",
|
|
86
|
+
"@tiptap/extension-italic": "^3.32.2",
|
|
87
|
+
"@tiptap/extension-link": "^3.32.2",
|
|
88
|
+
"@tiptap/extension-list": "^3.32.2",
|
|
89
|
+
"@tiptap/extension-paragraph": "^3.32.2",
|
|
90
|
+
"@tiptap/extension-table": "^3.32.2",
|
|
91
|
+
"@tiptap/extension-text": "^3.32.2",
|
|
92
|
+
"@tiptap/extensions": "^3.32.2",
|
|
93
|
+
"react-native-webview": ">=13.7.0"
|
|
79
94
|
},
|
|
80
95
|
"peerDependenciesMeta": {
|
|
81
96
|
"react": {
|
|
@@ -89,6 +104,51 @@
|
|
|
89
104
|
},
|
|
90
105
|
"react-dom": {
|
|
91
106
|
"optional": true
|
|
107
|
+
},
|
|
108
|
+
"@tiptap/core": {
|
|
109
|
+
"optional": true
|
|
110
|
+
},
|
|
111
|
+
"@tiptap/extension-bold": {
|
|
112
|
+
"optional": true
|
|
113
|
+
},
|
|
114
|
+
"@tiptap/extension-code": {
|
|
115
|
+
"optional": true
|
|
116
|
+
},
|
|
117
|
+
"@tiptap/extension-code-block": {
|
|
118
|
+
"optional": true
|
|
119
|
+
},
|
|
120
|
+
"@tiptap/extension-document": {
|
|
121
|
+
"optional": true
|
|
122
|
+
},
|
|
123
|
+
"@tiptap/extension-heading": {
|
|
124
|
+
"optional": true
|
|
125
|
+
},
|
|
126
|
+
"@tiptap/extension-image": {
|
|
127
|
+
"optional": true
|
|
128
|
+
},
|
|
129
|
+
"@tiptap/extension-italic": {
|
|
130
|
+
"optional": true
|
|
131
|
+
},
|
|
132
|
+
"@tiptap/extension-link": {
|
|
133
|
+
"optional": true
|
|
134
|
+
},
|
|
135
|
+
"@tiptap/extension-list": {
|
|
136
|
+
"optional": true
|
|
137
|
+
},
|
|
138
|
+
"@tiptap/extension-paragraph": {
|
|
139
|
+
"optional": true
|
|
140
|
+
},
|
|
141
|
+
"@tiptap/extension-table": {
|
|
142
|
+
"optional": true
|
|
143
|
+
},
|
|
144
|
+
"@tiptap/extension-text": {
|
|
145
|
+
"optional": true
|
|
146
|
+
},
|
|
147
|
+
"@tiptap/extensions": {
|
|
148
|
+
"optional": true
|
|
149
|
+
},
|
|
150
|
+
"react-native-webview": {
|
|
151
|
+
"optional": true
|
|
92
152
|
}
|
|
93
153
|
},
|
|
94
154
|
"optionalDependencies": {
|
package/src/contract.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The widget SDK contract: the ONE source the linter, the AI agents, the
|
|
2
2
|
// compiler and both hosts read. `contract.cjs` is GENERATED from `contract.js`
|
|
3
|
-
// (`npm run generate:
|
|
4
|
-
//
|
|
3
|
+
// (`npm run generate:cjs`) for CommonJS consumers — never edit it by hand;
|
|
4
|
+
// cjs-generated.test.js fails when it is stale.
|
|
5
5
|
|
|
6
6
|
// REQ-AI-AGENT-DESIGN (sc-3696): the ONE cross-platform elevation vocabulary.
|
|
7
7
|
// `shadow*` applies on iOS + react-native-web, `elevation` drives Android.
|
|
@@ -3464,13 +3464,13 @@ const PRIMITIVES = [
|
|
|
3464
3464
|
rnComponent: null,
|
|
3465
3465
|
docsUrl: null,
|
|
3466
3466
|
},
|
|
3467
|
-
// sc-6970 — formatted content, authored. The authoring half of `<RichText
|
|
3468
|
-
// a
|
|
3469
|
-
//
|
|
3467
|
+
// sc-6970 — formatted content, authored. The authoring half of `<RichText>`,
|
|
3468
|
+
// so a generated widget never has to splice HTML tags into a plain TextInput
|
|
3469
|
+
// again. sc-15080: the field edits the formatted result directly.
|
|
3470
3470
|
{
|
|
3471
3471
|
name: "MarkdownInput",
|
|
3472
3472
|
description:
|
|
3473
|
-
|
|
3473
|
+
"Formatted-content editor. `<MarkdownInput value={draft} onChange={setDraft} />`. The default way to let an app USER write formatted text: a rich-text field where the user edits the FORMATTED RESULT directly — headings, bold, italic, code, links, bulleted and numbered lists and real tables are shown as they will read, with no markdown markers in view — plus a toolbar (bold, italic, code, H2, H3, bullets, numbered list), a Table button that inserts a real table, with buttons to add or remove rows and columns or delete the table while the caret is in one, and a Link button that opens a form for the link text plus its target. Typing `## `, `- `, `1. `, `**bold**` or `*italic*` formats as you type. Use it rather than a bare `TextInput` plus formatting buttons of your own, and do NOT add a `<RichText>` preview under it: the field already shows the result; an editor you do build (one that only bolds, a checklist editor) still stores markdown markers and previews with `<RichText>` — buttons that splice `<strong>`/`<br>`/`<table>` tags into the string leave the author reading tag soup. The stored value is markdown text in the `<RichText>` grammar; loading a value never rewrites it, and an edit writes each construct in its canonical spelling. On iOS and Android the field runs in a WebView bundled into the SDK (no network). Props: `value`, `onChange(next)`, `placeholder`, `pages` (optional `[{ id, name }]` — supply it and the Link form offers those pages to link to by name; without it the form offers only an external address, because a widget cannot know the app's pages on its own), `minHeight` (default 150) and `maxHeight` (default 280) — the field grows with its content between them, then scrolls — `accessibilityLabel`, `style`, `testID`. `showPreview`, `previewLabel` and `renderImage` are still accepted and ignored. Images show inside the field when their source is an http(s) URL and as a labelled placeholder when it is a filestore id. Links in the field are inert, so a tap while editing cannot navigate away from the draft.",
|
|
3474
3474
|
rnComponent: null,
|
|
3475
3475
|
docsUrl: null,
|
|
3476
3476
|
},
|
|
@@ -6570,7 +6570,10 @@ const CONTRACT = deepFreeze({
|
|
|
6570
6570
|
// ships a hand-written `sv` value beside `en`.
|
|
6571
6571
|
// 1.225.0: behaviour (sc-14974) — `<MarkdownInput>` styles its own field
|
|
6572
6572
|
// live; `showPreview` now defaults to false.
|
|
6573
|
-
|
|
6573
|
+
// 1.226.0: behaviour (sc-15080) — `<MarkdownInput>` is a rich-text editor
|
|
6574
|
+
// showing the formatted result; `showPreview`, `previewLabel` and
|
|
6575
|
+
// `renderImage` are accepted and ignored.
|
|
6576
|
+
version: "1.226.0",
|
|
6574
6577
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6575
6578
|
hooks: HOOKS,
|
|
6576
6579
|
primitives: PRIMITIVES,
|
package/dist/markdown-edit.js
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
// sc-6970 — the selection edits `<MarkdownInput>`'s toolbar performs.
|
|
2
|
-
//
|
|
3
|
-
// Pure string maths, kept out of the component the way `richtext-tokens.js`
|
|
4
|
-
// keeps the theme maths out: a toolbar button is only correct if it wraps the
|
|
5
|
-
// author's SELECTION rather than appending at the end, and that is a thing
|
|
6
|
-
// tests can pin without a renderer.
|
|
7
|
-
|
|
8
|
-
import { escapeMarkdownLabel, formatMarkdownTable } from "./markdown.js";
|
|
9
|
-
|
|
10
|
-
const LINE_RE = /^(\s*)((?:#{1,3}\s)|(?:[-*]\s)|(?:\d+[.)]\s))?([\s\S]*)$/;
|
|
11
|
-
|
|
12
|
-
// The literal the inserted table skeleton opens with; the caret lands on it.
|
|
13
|
-
const FIRST_HEADING = "Column 1";
|
|
14
|
-
|
|
15
|
-
const LINE_KINDS = Object.freeze({
|
|
16
|
-
heading2: { marker: "## ", test: /^##\s/ },
|
|
17
|
-
heading3: { marker: "### ", test: /^###\s/ },
|
|
18
|
-
bullet: { marker: "- ", test: /^[-*]\s/ },
|
|
19
|
-
ordered: { marker: null, test: /^\d+[.)]\s/ },
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
function clamp(index, max) {
|
|
23
|
-
const n = Number.isFinite(index) ? Math.trunc(index) : 0;
|
|
24
|
-
return Math.min(Math.max(n, 0), max);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function readRange(text, selection) {
|
|
28
|
-
const src = typeof text === "string" ? text : "";
|
|
29
|
-
const raw = selection && typeof selection === "object" ? selection : {};
|
|
30
|
-
const a = clamp(raw.start, src.length);
|
|
31
|
-
const b = clamp(raw.end, src.length);
|
|
32
|
-
return { src, start: Math.min(a, b), end: Math.max(a, b) };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function wrap(marker) {
|
|
36
|
-
return (text, selection) => {
|
|
37
|
-
const { src, start, end } = readRange(text, selection);
|
|
38
|
-
const selected = src.slice(start, end);
|
|
39
|
-
const pair = marker.length * 2;
|
|
40
|
-
|
|
41
|
-
// Already wrapped — the button toggles the emphasis back off.
|
|
42
|
-
if (selected.length >= pair && selected.startsWith(marker) && selected.endsWith(marker)) {
|
|
43
|
-
const inner = selected.slice(marker.length, selected.length - marker.length);
|
|
44
|
-
return {
|
|
45
|
-
value: src.slice(0, start) + inner + src.slice(end),
|
|
46
|
-
selection: { start, end: start + inner.length },
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
value: src.slice(0, start) + marker + selected + marker + src.slice(end),
|
|
52
|
-
// Leaves the author's own text selected (or the caret between an empty
|
|
53
|
-
// pair), so typing continues inside the emphasis they just asked for.
|
|
54
|
-
selection: { start: start + marker.length, end: end + marker.length },
|
|
55
|
-
};
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function linePrefix(kind) {
|
|
60
|
-
const spec = LINE_KINDS[kind];
|
|
61
|
-
return (text, selection) => {
|
|
62
|
-
const { src, start, end } = readRange(text, selection);
|
|
63
|
-
const blockStart = src.lastIndexOf("\n", Math.max(start - 1, 0)) + 1;
|
|
64
|
-
const newlineAfter = src.indexOf("\n", end);
|
|
65
|
-
const blockEnd = newlineAfter === -1 ? src.length : newlineAfter;
|
|
66
|
-
|
|
67
|
-
const lines = src.slice(blockStart, blockEnd).split("\n");
|
|
68
|
-
const filled = lines.filter((line) => line.trim());
|
|
69
|
-
// Toggled off only when EVERY line already carries this kind, so a mixed
|
|
70
|
-
// selection becomes uniformly marked rather than half-stripped.
|
|
71
|
-
const removing =
|
|
72
|
-
filled.length > 0 &&
|
|
73
|
-
filled.every((line) => spec.test.test(line.replace(/^\s*/, "")));
|
|
74
|
-
|
|
75
|
-
let ordinal = 0;
|
|
76
|
-
const rebuilt = lines.map((line) => {
|
|
77
|
-
const match = LINE_RE.exec(line);
|
|
78
|
-
const indent = match[1];
|
|
79
|
-
const body = match[3];
|
|
80
|
-
if (!line.trim()) return line;
|
|
81
|
-
if (removing) return indent + body;
|
|
82
|
-
ordinal += 1;
|
|
83
|
-
return indent + (spec.marker || `${ordinal}. `) + body;
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const block = rebuilt.join("\n");
|
|
87
|
-
return {
|
|
88
|
-
value: src.slice(0, blockStart) + block + src.slice(blockEnd),
|
|
89
|
-
selection: { start: blockStart, end: blockStart + block.length },
|
|
90
|
-
};
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// sc-7349 — a link needs a TARGET, which no selection can supply, so this is
|
|
95
|
-
// the one op that takes a third argument. The target is stored verbatim: the
|
|
96
|
-
// host's `navigation.openLink` is what decides page / external / refuse, so
|
|
97
|
-
// this must not pre-judge it (see markdown.js).
|
|
98
|
-
function linkOp() {
|
|
99
|
-
return (text, selection, options) => {
|
|
100
|
-
const { src, start, end } = readRange(text, selection);
|
|
101
|
-
// A bare string is accepted as the target, so the common case stays terse.
|
|
102
|
-
const opts =
|
|
103
|
-
options && typeof options === "object" ? options : { target: options };
|
|
104
|
-
const href = typeof opts.target === "string" ? opts.target.trim() : "";
|
|
105
|
-
// Nothing to point at — leave the text exactly as the author left it.
|
|
106
|
-
if (!href) return { value: src, selection: { start, end } };
|
|
107
|
-
|
|
108
|
-
// An explicit label wins, so a dialog can name the link when the author
|
|
109
|
-
// had nothing selected; otherwise the selection IS the label.
|
|
110
|
-
const chosen =
|
|
111
|
-
typeof opts.label === "string" && opts.label.trim()
|
|
112
|
-
? opts.label
|
|
113
|
-
: src.slice(start, end);
|
|
114
|
-
const label = escapeMarkdownLabel(chosen) || href;
|
|
115
|
-
const markup = `[${label}](${href})`;
|
|
116
|
-
return {
|
|
117
|
-
value: src.slice(0, start) + markup + src.slice(end),
|
|
118
|
-
// Leaves the LABEL selected, so typing replaces the link text rather
|
|
119
|
-
// than the markup around it.
|
|
120
|
-
selection: { start: start + 1, end: start + 1 + label.length },
|
|
121
|
-
};
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// A table is a block, so it needs its own lines: a blank line before keeps it
|
|
126
|
-
// from fusing with the paragraph the caret was in.
|
|
127
|
-
function tableOp() {
|
|
128
|
-
return (text, selection) => {
|
|
129
|
-
const { src, start, end } = readRange(text, selection);
|
|
130
|
-
const before = src.slice(0, start);
|
|
131
|
-
const after = src.slice(end);
|
|
132
|
-
const lead =
|
|
133
|
-
before.length === 0 || before.endsWith("\n\n")
|
|
134
|
-
? ""
|
|
135
|
-
: before.endsWith("\n")
|
|
136
|
-
? "\n"
|
|
137
|
-
: "\n\n";
|
|
138
|
-
const trail = after.startsWith("\n") ? "" : "\n";
|
|
139
|
-
|
|
140
|
-
const skeleton = formatMarkdownTable();
|
|
141
|
-
const offset = before.length + lead.length;
|
|
142
|
-
return {
|
|
143
|
-
value: before + lead + skeleton + trail + after,
|
|
144
|
-
// Selects the first header cell, so the author types over "Column 1"
|
|
145
|
-
// instead of hunting for the caret inside the pipes.
|
|
146
|
-
selection: {
|
|
147
|
-
start: offset + 2,
|
|
148
|
-
end: offset + 2 + FIRST_HEADING.length,
|
|
149
|
-
},
|
|
150
|
-
};
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export const markdownEditOps = Object.freeze({
|
|
155
|
-
bold: wrap("**"),
|
|
156
|
-
italic: wrap("*"),
|
|
157
|
-
code: wrap("`"),
|
|
158
|
-
heading2: linePrefix("heading2"),
|
|
159
|
-
heading3: linePrefix("heading3"),
|
|
160
|
-
bullet: linePrefix("bullet"),
|
|
161
|
-
ordered: linePrefix("ordered"),
|
|
162
|
-
link: linkOp(),
|
|
163
|
-
table: tableOp(),
|
|
164
|
-
});
|