@ai-matrx/rich-editor 0.0.0 → 0.1.1
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/CHANGELOG.md +19 -0
- package/LICENSE +1 -1
- package/README.md +13 -3
- package/dist/core/block-insert.d.ts +27 -0
- package/dist/core/block-insert.js +24 -0
- package/dist/core/caret-context.d.ts +24 -0
- package/dist/core/caret-context.js +73 -0
- package/dist/core/clipboard-text.d.ts +5 -0
- package/dist/core/clipboard-text.js +47 -0
- package/dist/core/commands.d.ts +65 -0
- package/dist/core/commands.js +299 -0
- package/dist/core/extensions.d.ts +29 -0
- package/dist/core/extensions.js +326 -0
- package/dist/core/find-replace.d.ts +34 -0
- package/dist/core/find-replace.js +67 -0
- package/dist/core/history-approval.d.ts +11 -0
- package/dist/core/history-approval.js +43 -0
- package/dist/core/host-value.d.ts +29 -0
- package/dist/core/host-value.js +10 -0
- package/dist/core/html-to-markdown.d.ts +3 -0
- package/dist/core/html-to-markdown.js +12 -0
- package/dist/core/markdown-format.d.ts +18 -0
- package/dist/core/markdown-format.js +354 -0
- package/dist/core/markdown-parse.d.ts +35 -0
- package/dist/core/markdown-parse.js +533 -0
- package/dist/core/markdown-serialize.d.ts +27 -0
- package/dist/core/markdown-serialize.js +394 -0
- package/dist/core/outline.d.ts +17 -0
- package/dist/core/outline.js +41 -0
- package/dist/core/paste-html.d.ts +10 -0
- package/dist/core/paste-html.js +198 -0
- package/dist/core/paste-markdown.d.ts +18 -0
- package/dist/core/paste-markdown.js +70 -0
- package/dist/core/placeholders.d.ts +22 -0
- package/dist/core/placeholders.js +60 -0
- package/dist/core/save-plan.d.ts +47 -0
- package/dist/core/save-plan.js +287 -0
- package/dist/core/shortcuts.d.ts +13 -0
- package/dist/core/shortcuts.js +50 -0
- package/dist/core/source-format.d.ts +28 -0
- package/dist/core/source-format.js +104 -0
- package/dist/core/text-metrics.d.ts +11 -0
- package/dist/core/text-metrics.js +35 -0
- package/dist/core/variable-name.d.ts +13 -0
- package/dist/core/variable-name.js +13 -0
- package/dist/core/variables.d.ts +37 -0
- package/dist/core/variables.js +77 -0
- package/dist/core/visual-document.d.ts +52 -0
- package/dist/core/visual-document.js +142 -0
- package/dist/core/visual-find.d.ts +27 -0
- package/dist/core/visual-find.js +93 -0
- package/dist/format/format-target.d.ts +39 -0
- package/dist/format/format-target.js +79 -0
- package/dist/in-place/caret-handoff.d.ts +3 -0
- package/dist/in-place/caret-handoff.js +14 -0
- package/dist/in-place/in-place-session.d.ts +24 -0
- package/dist/in-place/in-place-session.js +32 -0
- package/package.json +95 -5
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { formatMarkdown, isFormatActive } from "../core/markdown-format.js";
|
|
2
|
+
import { applySourceEdit } from "../core/source-format.js";
|
|
3
|
+
const MARKDOWN_FORMAT_HOST_KEY = "markdownFormat";
|
|
4
|
+
function markdownFormatHost(target) {
|
|
5
|
+
const host = { kind: "markdown-format", target };
|
|
6
|
+
return { [MARKDOWN_FORMAT_HOST_KEY]: host };
|
|
7
|
+
}
|
|
8
|
+
function textFormatTarget(engine, read, apply, subscribe) {
|
|
9
|
+
return {
|
|
10
|
+
engine,
|
|
11
|
+
run: (command) => {
|
|
12
|
+
const state = read();
|
|
13
|
+
if (!state || !state.editable) return false;
|
|
14
|
+
const result = formatMarkdown(state.text, state.from, state.to, command);
|
|
15
|
+
if (result.changes.length === 0) return false;
|
|
16
|
+
apply(result, state.text);
|
|
17
|
+
return true;
|
|
18
|
+
},
|
|
19
|
+
isActive: (command) => {
|
|
20
|
+
const state = read();
|
|
21
|
+
return state ? isFormatActive(state.text, state.from, state.to, command) : false;
|
|
22
|
+
},
|
|
23
|
+
canFormat: () => {
|
|
24
|
+
const state = read();
|
|
25
|
+
return Boolean(state?.editable && state.text.slice(state.from, state.to).trim());
|
|
26
|
+
},
|
|
27
|
+
subscribe
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function applyEditToTextarea(el, result) {
|
|
31
|
+
const text = el.value;
|
|
32
|
+
if (result.changes.length > 0) {
|
|
33
|
+
const from = Math.min(...result.changes.map((c) => c.from));
|
|
34
|
+
const to = Math.max(...result.changes.map((c) => c.to));
|
|
35
|
+
const after = applySourceEdit(text, result);
|
|
36
|
+
const insert = after.slice(from, after.length - (text.length - to));
|
|
37
|
+
el.focus({ preventScroll: true });
|
|
38
|
+
el.setSelectionRange(from, to);
|
|
39
|
+
let done = false;
|
|
40
|
+
try {
|
|
41
|
+
done = typeof document.execCommand === "function" && (insert === "" ? document.execCommand("delete", false) : document.execCommand("insertText", false, insert));
|
|
42
|
+
} catch {
|
|
43
|
+
done = false;
|
|
44
|
+
}
|
|
45
|
+
if (!done || el.value !== after) {
|
|
46
|
+
if (el.value !== text) el.value = text;
|
|
47
|
+
el.setRangeText(insert, from, to, "end");
|
|
48
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
el.setSelectionRange(result.anchor, result.head);
|
|
52
|
+
}
|
|
53
|
+
function textareaFormatTarget(el) {
|
|
54
|
+
return textFormatTarget(
|
|
55
|
+
"textarea",
|
|
56
|
+
() => ({
|
|
57
|
+
text: el.value,
|
|
58
|
+
from: el.selectionStart ?? 0,
|
|
59
|
+
to: el.selectionEnd ?? 0,
|
|
60
|
+
editable: !el.readOnly && !el.disabled && el.isConnected
|
|
61
|
+
}),
|
|
62
|
+
(result) => applyEditToTextarea(el, result),
|
|
63
|
+
(onChange) => {
|
|
64
|
+
el.addEventListener("input", onChange);
|
|
65
|
+
el.addEventListener("select", onChange);
|
|
66
|
+
return () => {
|
|
67
|
+
el.removeEventListener("input", onChange);
|
|
68
|
+
el.removeEventListener("select", onChange);
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
MARKDOWN_FORMAT_HOST_KEY,
|
|
75
|
+
applyEditToTextarea,
|
|
76
|
+
markdownFormatHost,
|
|
77
|
+
textFormatTarget,
|
|
78
|
+
textareaFormatTarget
|
|
79
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const pending = /* @__PURE__ */ new Map();
|
|
2
|
+
function handInPlaceCaret(key, caret) {
|
|
3
|
+
if (caret) pending.set(key, caret);
|
|
4
|
+
else pending.delete(key);
|
|
5
|
+
}
|
|
6
|
+
function takeInPlaceCaret(key) {
|
|
7
|
+
const caret = pending.get(key) ?? null;
|
|
8
|
+
pending.delete(key);
|
|
9
|
+
return caret;
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
handInPlaceCaret,
|
|
13
|
+
takeInPlaceCaret
|
|
14
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type InPlaceSaveMode = "explicit" | "autosave";
|
|
2
|
+
export interface InPlaceSessionOptions {
|
|
3
|
+
/** The stored text the editor opened on. */
|
|
4
|
+
openedOn: string;
|
|
5
|
+
mode: InPlaceSaveMode;
|
|
6
|
+
/** The host's write. Resolve with the text as stored to prove it. */
|
|
7
|
+
write: (text: string) => Promise<string | void> | string | void;
|
|
8
|
+
}
|
|
9
|
+
export interface InPlaceSession {
|
|
10
|
+
/** The editor reported a draft (autosave hosts write it). */
|
|
11
|
+
draft(text: string): Promise<void>;
|
|
12
|
+
/** Save / ⌘Enter. Resolves with whether anything was written. */
|
|
13
|
+
save(text: string): Promise<{
|
|
14
|
+
wrote: boolean;
|
|
15
|
+
stored?: string | void;
|
|
16
|
+
}>;
|
|
17
|
+
/** Escape / Cancel after the person confirmed. Resolves with whether anything was written. */
|
|
18
|
+
cancel(): Promise<{
|
|
19
|
+
wrote: boolean;
|
|
20
|
+
}>;
|
|
21
|
+
/** True when the current draft differs from what was opened. */
|
|
22
|
+
dirty(): boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare function createInPlaceSession({ openedOn, mode, write }: InPlaceSessionOptions): InPlaceSession;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
function createInPlaceSession({ openedOn, mode, write }) {
|
|
2
|
+
let current = openedOn;
|
|
3
|
+
let lastWritten = openedOn;
|
|
4
|
+
const writeIfNew = async (text) => {
|
|
5
|
+
if (text === lastWritten) return { wrote: false };
|
|
6
|
+
const stored = await write(text);
|
|
7
|
+
lastWritten = typeof stored === "string" ? stored : text;
|
|
8
|
+
return { wrote: true, stored };
|
|
9
|
+
};
|
|
10
|
+
return {
|
|
11
|
+
async draft(text) {
|
|
12
|
+
current = text;
|
|
13
|
+
if (mode === "autosave") await writeIfNew(text);
|
|
14
|
+
},
|
|
15
|
+
async save(text) {
|
|
16
|
+
current = text;
|
|
17
|
+
if (mode === "explicit" && text === openedOn) return { wrote: false };
|
|
18
|
+
return writeIfNew(text);
|
|
19
|
+
},
|
|
20
|
+
async cancel() {
|
|
21
|
+
current = openedOn;
|
|
22
|
+
if (mode === "explicit") return { wrote: false };
|
|
23
|
+
return writeIfNew(openedOn);
|
|
24
|
+
},
|
|
25
|
+
dirty() {
|
|
26
|
+
return current !== openedOn;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
createInPlaceSession
|
|
32
|
+
};
|
package/package.json
CHANGED
|
@@ -1,9 +1,99 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/rich-editor",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "The AI Matrx editor: Visual (Tiptap), Source (CodeMirror), Preview, the markdown formatting command layer, and edit-in-place — over @ai-matrx/rich-content. Consumers that only render never install it.",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"license": "MIT",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/AI-Matrix-Engine/aidream.git",
|
|
10
|
+
"directory": "apps/shared/rich-editor"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/AI-Matrix-Engine/aidream/tree/main/apps/shared/rich-editor#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/AI-Matrix-Engine/aidream/issues"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"CHANGELOG.md"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"exports": {
|
|
24
|
+
"./core/*": {
|
|
25
|
+
"types": "./dist/core/*.d.ts",
|
|
26
|
+
"import": "./dist/core/*.js"
|
|
27
|
+
},
|
|
28
|
+
"./in-place/*": {
|
|
29
|
+
"types": "./dist/in-place/*.d.ts",
|
|
30
|
+
"import": "./dist/in-place/*.js"
|
|
31
|
+
},
|
|
32
|
+
"./format/*": {
|
|
33
|
+
"types": "./dist/format/*.d.ts",
|
|
34
|
+
"import": "./dist/format/*.js"
|
|
35
|
+
},
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@tiptap/core": "^3.31.3",
|
|
40
|
+
"@tiptap/extension-blockquote": "^3.31.3",
|
|
41
|
+
"@tiptap/extension-bold": "^3.31.3",
|
|
42
|
+
"@tiptap/extension-code": "^3.31.3",
|
|
43
|
+
"@tiptap/extension-document": "^3.31.3",
|
|
44
|
+
"@tiptap/extension-hard-break": "^3.31.3",
|
|
45
|
+
"@tiptap/extension-heading": "^3.31.3",
|
|
46
|
+
"@tiptap/extension-horizontal-rule": "^3.31.3",
|
|
47
|
+
"@tiptap/extension-italic": "^3.31.3",
|
|
48
|
+
"@tiptap/extension-link": "^3.31.3",
|
|
49
|
+
"@tiptap/extension-list": "^3.31.3",
|
|
50
|
+
"@tiptap/extension-paragraph": "^3.31.3",
|
|
51
|
+
"@tiptap/extension-strike": "^3.31.3",
|
|
52
|
+
"@tiptap/extension-table": "^3.31.3",
|
|
53
|
+
"@tiptap/extension-text": "^3.31.3",
|
|
54
|
+
"@tiptap/extensions": "^3.31.3",
|
|
55
|
+
"@tiptap/pm": "^3.31.3",
|
|
56
|
+
"github-slugger": "^2.0.0",
|
|
57
|
+
"@ai-matrx/content-ir": "latest",
|
|
58
|
+
"@ai-matrx/print": "latest",
|
|
59
|
+
"@ai-matrx/rich-content": "latest"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"react": ">=19.0.0",
|
|
63
|
+
"react-dom": ">=19.0.0"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@testing-library/react": "^16.3.3",
|
|
67
|
+
"@types/jest": "^30.0.0",
|
|
68
|
+
"@types/node": "^26.6.2",
|
|
69
|
+
"@types/react": "19.3.0",
|
|
70
|
+
"@types/react-dom": "19.3.0",
|
|
71
|
+
"@typescript/native": "npm:typescript@^7.0.2",
|
|
72
|
+
"jest": "^30.5.2",
|
|
73
|
+
"jest-environment-jsdom": "^30.5.2",
|
|
74
|
+
"jsdom": "^30.1.1",
|
|
75
|
+
"publint": "^0.3.24",
|
|
76
|
+
"react": "^19.3.0",
|
|
77
|
+
"react-dom": "^19.3.0",
|
|
78
|
+
"ts-jest": "^29.4.14",
|
|
79
|
+
"tsup": "^8.5.1",
|
|
80
|
+
"typescript": "^6.0.3"
|
|
81
|
+
},
|
|
82
|
+
"publishConfig": {
|
|
83
|
+
"access": "public",
|
|
84
|
+
"registry": "https://registry.npmjs.org/"
|
|
85
|
+
},
|
|
86
|
+
"engines": {
|
|
87
|
+
"node": ">=20"
|
|
88
|
+
},
|
|
89
|
+
"scripts": {
|
|
90
|
+
"build": "pnpm --filter @ai-matrx/rich-content build && node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsup && node ./scripts/tsc-own.mjs -p tsconfig.build.json && node ./scripts/finish-build.mjs",
|
|
91
|
+
"check:package": "pnpm build && publint && pnpm verify:modules-tarball && pnpm verify:tarball && pnpm check:host-boundary",
|
|
92
|
+
"check:host-boundary": "node ./scripts/check-host-boundary.mjs && node ./scripts/check-host-boundary.mjs --self-test",
|
|
93
|
+
"deps:write": "node ./scripts/finish-build.mjs --write-deps",
|
|
94
|
+
"typecheck": "node ./scripts/tsc-own.mjs -p tsconfig.json",
|
|
95
|
+
"test": "jest -c jest.config.mjs --passWithNoTests",
|
|
96
|
+
"verify:modules-tarball": "node ./scripts/verify-modules-tarball.mjs",
|
|
97
|
+
"verify:tarball": "node ./scripts/verify-tarball.mjs"
|
|
98
|
+
}
|
|
9
99
|
}
|