@jvs-milkdown/plugin-clipboard 1.1.8 → 1.2.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/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { editorViewOptionsCtx, parserCtx, schemaCtx, serializerCtx } from "@jvs-milkdown/core";
2
2
  import { getNodeFromSchema, isTextOnlySlice } from "@jvs-milkdown/prose";
3
3
  import { DOMParser, DOMSerializer } from "@jvs-milkdown/prose/model";
4
- import { Plugin, PluginKey, TextSelection } from "@jvs-milkdown/prose/state";
4
+ import { Plugin, PluginKey } from "@jvs-milkdown/prose/state";
5
5
  import { $prose } from "@jvs-milkdown/utils";
6
6
  //#region src/__internal__/is-pure-text.ts
7
7
  function isPureText(content) {
@@ -67,10 +67,8 @@ var clipboard = $prose((ctx) => {
67
67
  if (vscodeData) {
68
68
  const language = JSON.parse(vscodeData)?.mode;
69
69
  if (text && language) {
70
- const { tr } = view.state;
71
- const codeBlock = getNodeFromSchema("code_block", schema);
72
- tr.replaceSelectionWith(codeBlock.create({ language })).setSelection(TextSelection.near(tr.doc.resolve(Math.max(0, tr.selection.from - 2)))).insertText(text.replace(/\r\n?/g, "\n"));
73
- view.dispatch(tr);
70
+ const node = getNodeFromSchema("code_block", schema).create({ language }, schema.text(text.replace(/\r\n?/g, "\n")));
71
+ view.dispatch(view.state.tr.replaceSelectionWith(node));
74
72
  return true;
75
73
  }
76
74
  }
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/__internal__/is-pure-text.ts","../src/__internal__/with-meta.ts","../src/index.ts"],"sourcesContent":["type UnknownRecord = Record<string, unknown>\nexport function isPureText(\n content: UnknownRecord | UnknownRecord[] | undefined | null\n): boolean {\n if (!content) return false\n if (Array.isArray(content)) {\n if (content.length > 1) return false\n return isPureText(content[0])\n }\n\n const child = content.content\n if (child) return isPureText(child as UnknownRecord[])\n\n return content.type === 'text'\n}\n","import type { Meta, MilkdownPlugin } from '@jvs-milkdown/ctx'\n\nexport function withMeta<T extends MilkdownPlugin>(\n plugin: T,\n meta: Partial<Meta> & Pick<Meta, 'displayName'>\n): T {\n Object.assign(plugin, {\n meta: {\n package: '@jvs-milkdown/plugin-clipboard',\n ...meta,\n },\n })\n\n return plugin\n}\n","import type { EditorView } from '@jvs-milkdown/prose/view'\n\nimport {\n editorViewOptionsCtx,\n parserCtx,\n schemaCtx,\n serializerCtx,\n} from '@jvs-milkdown/core'\nimport { getNodeFromSchema, isTextOnlySlice } from '@jvs-milkdown/prose'\nimport {\n DOMParser,\n DOMSerializer,\n type Node as ProsemirrorNode,\n type Slice,\n} from '@jvs-milkdown/prose/model'\nimport { Plugin, PluginKey, TextSelection } from '@jvs-milkdown/prose/state'\nimport { $prose } from '@jvs-milkdown/utils'\n\nimport { isPureText } from './__internal__/is-pure-text'\nimport { withMeta } from './__internal__/with-meta'\n\nfunction dispatchPasteSlice(view: EditorView, slice: Slice): boolean {\n const node = isTextOnlySlice(slice)\n if (node) {\n view.dispatch(view.state.tr.replaceSelectionWith(node, true))\n return true\n }\n\n try {\n view.dispatch(view.state.tr.replaceSelection(slice))\n return true\n } catch {\n return false\n }\n}\n\n/// The prosemirror plugin for clipboard.\nexport const clipboard = $prose((ctx) => {\n const schema = ctx.get(schemaCtx)\n\n // Set editable props for https://github.com/Milkdown/milkdown/issues/190\n ctx.update(editorViewOptionsCtx, (prev) => ({\n ...prev,\n editable: prev.editable ?? (() => true),\n transformPastedHTML: (html: string, view: EditorView) => {\n const prevTransform = prev.transformPastedHTML\n if (prevTransform) html = prevTransform(html, view)\n\n // Google Docs wraps pasted content in <b style=\"font-weight:normal;\" id=\"docs-internal-guid-...\">\n // This wrapper causes ProseMirror's parser to fail when parsing multiple tables.\n // Strip it so block content is at the top level.\n if (html.includes('docs-internal-guid')) {\n html = html.replace(\n /<b[^>]*id=\"docs-internal-guid[^\"]*\"[^>]*>([\\s\\S]*)<\\/b>/,\n '$1'\n )\n // Also unwrap <div> elements that wrap tables.\n // Google Docs wraps each table in <div dir=\"ltr\" ...><table>...</table></div>\n // These wrappers interfere with ProseMirror's parseSlice for multiple tables.\n html = html.replace(/<div[^>]*>(<table[\\s\\S]*?<\\/table>)<\\/div>/g, '$1')\n }\n return html\n },\n }))\n\n const key = new PluginKey('MILKDOWN_CLIPBOARD')\n const plugin = new Plugin({\n key,\n props: {\n handlePaste: (view, event, preProcessedSlice) => {\n const parser = ctx.get(parserCtx)\n const editable = view.props.editable?.(view.state)\n const { clipboardData } = event\n if (!editable || !clipboardData) return false\n\n const currentNode = view.state.selection.$from.node()\n if (currentNode.type.spec.code) return false\n\n const text = clipboardData.getData('text/plain')\n\n // if is copied from vscode, try to create a code block\n const vscodeData = clipboardData.getData('vscode-editor-data')\n if (vscodeData) {\n const data = JSON.parse(vscodeData)\n const language = data?.mode\n if (text && language) {\n const { tr } = view.state\n const codeBlock = getNodeFromSchema('code_block', schema)\n\n tr.replaceSelectionWith(codeBlock.create({ language }))\n .setSelection(\n TextSelection.near(\n tr.doc.resolve(Math.max(0, tr.selection.from - 2))\n )\n )\n .insertText(text.replace(/\\r\\n?/g, '\\n'))\n\n view.dispatch(tr)\n return true\n }\n }\n\n const html = clipboardData.getData('text/html')\n if (html.length === 0 && text.length === 0) return false\n\n // When HTML is present, use the pre-processed Slice from ProseMirror.\n // ProseMirror's parseFromClipboard already ran transformPastedHTML\n // (e.g. Google Docs wrapper stripping) and transformPasted (paste rules\n // like table header fix), producing a better Slice than re-parsing here.\n if (html.length > 0 && preProcessedSlice) {\n return dispatchPasteSlice(view, preProcessedSlice)\n }\n\n const domParser = DOMParser.fromSchema(schema)\n let dom: Node\n if (html.length === 0) {\n const slice = parser(text)\n if (!slice || typeof slice === 'string') return false\n\n dom = DOMSerializer.fromSchema(schema).serializeFragment(\n slice.content\n )\n } else {\n const template = document.createElement('template')\n template.innerHTML = html\n dom = template.content.cloneNode(true)\n template.remove()\n }\n\n let slice = domParser.parseSlice(dom)\n const transformPasted = view.someProp('transformPasted')\n if (transformPasted) {\n slice = transformPasted(slice, view, false)\n }\n return dispatchPasteSlice(view, slice)\n },\n clipboardTextSerializer: (slice) => {\n const serializer = ctx.get(serializerCtx)\n const isText = isPureText(slice.content.toJSON())\n if (isText)\n return (slice.content as unknown as ProsemirrorNode).textBetween(\n 0,\n slice.content.size,\n '\\n\\n'\n )\n\n const doc = schema.topNodeType.createAndFill(undefined, slice.content)\n if (!doc) return ''\n const value = serializer(doc)\n return value\n },\n },\n appendTransaction(transactions, _oldState, newState) {\n if (!transactions.some((tr) => tr.docChanged)) return null\n\n const imageType = schema.nodes['image']\n const imageBlockType = schema.nodes['image-block']\n const paragraphType = schema.nodes['paragraph']\n if (!imageType || !imageBlockType || !paragraphType) return null\n\n const replacements: {\n from: number\n to: number\n blocks: ProsemirrorNode[]\n }[] = []\n\n const isInlineNodesEmpty = (nodes: ProsemirrorNode[]) => {\n return nodes.every(\n (n) =>\n n.type.name === 'hardbreak' || (n.isText && n.text?.trim() === '')\n )\n }\n\n newState.doc.descendants((node, pos) => {\n if (node.type !== paragraphType) return true\n\n let hasImage = false\n for (let i = 0; i < node.childCount; i++) {\n if (node.child(i).type === imageType) {\n hasImage = true\n break\n }\n }\n\n if (!hasImage) return false\n\n const blocks: ProsemirrorNode[] = []\n let currentInlines: ProsemirrorNode[] = []\n\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i)\n if (child.type === imageType) {\n if (!isInlineNodesEmpty(currentInlines)) {\n blocks.push(paragraphType.create(node.attrs, currentInlines))\n }\n currentInlines = []\n\n blocks.push(\n imageBlockType.create({\n src: child.attrs.src,\n caption: child.attrs.alt || child.attrs.title || '',\n ratio: 1,\n })\n )\n } else {\n currentInlines.push(child)\n }\n }\n\n if (!isInlineNodesEmpty(currentInlines)) {\n blocks.push(paragraphType.create(node.attrs, currentInlines))\n }\n\n if (blocks.length > 0) {\n replacements.push({ from: pos, to: pos + node.nodeSize, blocks })\n }\n\n return false\n })\n\n if (replacements.length === 0) return null\n\n const { tr } = newState\n for (let i = replacements.length - 1; i >= 0; i--) {\n const r = replacements[i]!\n tr.replaceWith(r.from, r.to, r.blocks)\n }\n\n return tr\n },\n })\n\n return plugin\n})\n\nwithMeta(clipboard, { displayName: 'Prose<clipboard>' })\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/__internal__/is-pure-text.ts","../src/__internal__/with-meta.ts","../src/index.ts"],"sourcesContent":["type UnknownRecord = Record<string, unknown>\nexport function isPureText(\n content: UnknownRecord | UnknownRecord[] | undefined | null\n): boolean {\n if (!content) return false\n if (Array.isArray(content)) {\n if (content.length > 1) return false\n return isPureText(content[0])\n }\n\n const child = content.content\n if (child) return isPureText(child as UnknownRecord[])\n\n return content.type === 'text'\n}\n","import type { Meta, MilkdownPlugin } from '@jvs-milkdown/ctx'\n\nexport function withMeta<T extends MilkdownPlugin>(\n plugin: T,\n meta: Partial<Meta> & Pick<Meta, 'displayName'>\n): T {\n Object.assign(plugin, {\n meta: {\n package: '@jvs-milkdown/plugin-clipboard',\n ...meta,\n },\n })\n\n return plugin\n}\n","import type { EditorView } from '@jvs-milkdown/prose/view'\n\nimport {\n editorViewOptionsCtx,\n parserCtx,\n schemaCtx,\n serializerCtx,\n} from '@jvs-milkdown/core'\nimport { getNodeFromSchema, isTextOnlySlice } from '@jvs-milkdown/prose'\nimport {\n DOMParser,\n DOMSerializer,\n type Node as ProsemirrorNode,\n type Slice,\n} from '@jvs-milkdown/prose/model'\nimport { Plugin, PluginKey } from '@jvs-milkdown/prose/state'\nimport { $prose } from '@jvs-milkdown/utils'\n\nimport { isPureText } from './__internal__/is-pure-text'\nimport { withMeta } from './__internal__/with-meta'\n\nfunction dispatchPasteSlice(view: EditorView, slice: Slice): boolean {\n const node = isTextOnlySlice(slice)\n if (node) {\n view.dispatch(view.state.tr.replaceSelectionWith(node, true))\n return true\n }\n\n try {\n view.dispatch(view.state.tr.replaceSelection(slice))\n return true\n } catch {\n return false\n }\n}\n\n/// The prosemirror plugin for clipboard.\nexport const clipboard = $prose((ctx) => {\n const schema = ctx.get(schemaCtx)\n\n // Set editable props for https://github.com/Milkdown/milkdown/issues/190\n ctx.update(editorViewOptionsCtx, (prev) => ({\n ...prev,\n editable: prev.editable ?? (() => true),\n transformPastedHTML: (html: string, view: EditorView) => {\n const prevTransform = prev.transformPastedHTML\n if (prevTransform) html = prevTransform(html, view)\n\n // Google Docs wraps pasted content in <b style=\"font-weight:normal;\" id=\"docs-internal-guid-...\">\n // This wrapper causes ProseMirror's parser to fail when parsing multiple tables.\n // Strip it so block content is at the top level.\n if (html.includes('docs-internal-guid')) {\n html = html.replace(\n /<b[^>]*id=\"docs-internal-guid[^\"]*\"[^>]*>([\\s\\S]*)<\\/b>/,\n '$1'\n )\n // Also unwrap <div> elements that wrap tables.\n // Google Docs wraps each table in <div dir=\"ltr\" ...><table>...</table></div>\n // These wrappers interfere with ProseMirror's parseSlice for multiple tables.\n html = html.replace(/<div[^>]*>(<table[\\s\\S]*?<\\/table>)<\\/div>/g, '$1')\n }\n return html\n },\n }))\n\n const key = new PluginKey('MILKDOWN_CLIPBOARD')\n const plugin = new Plugin({\n key,\n props: {\n handlePaste: (view, event, preProcessedSlice) => {\n const parser = ctx.get(parserCtx)\n const editable = view.props.editable?.(view.state)\n const { clipboardData } = event\n if (!editable || !clipboardData) return false\n\n const currentNode = view.state.selection.$from.node()\n if (currentNode.type.spec.code) return false\n\n const text = clipboardData.getData('text/plain')\n\n // if is copied from vscode, try to create a code block\n const vscodeData = clipboardData.getData('vscode-editor-data')\n if (vscodeData) {\n const data = JSON.parse(vscodeData)\n const language = data?.mode\n if (text && language) {\n const codeBlock = getNodeFromSchema('code_block', schema)\n const node = codeBlock.create(\n { language },\n schema.text(text.replace(/\\r\\n?/g, '\\n'))\n )\n view.dispatch(view.state.tr.replaceSelectionWith(node))\n return true\n }\n }\n\n const html = clipboardData.getData('text/html')\n if (html.length === 0 && text.length === 0) return false\n\n // When HTML is present, use the pre-processed Slice from ProseMirror.\n // ProseMirror's parseFromClipboard already ran transformPastedHTML\n // (e.g. Google Docs wrapper stripping) and transformPasted (paste rules\n // like table header fix), producing a better Slice than re-parsing here.\n if (html.length > 0 && preProcessedSlice) {\n return dispatchPasteSlice(view, preProcessedSlice)\n }\n\n const domParser = DOMParser.fromSchema(schema)\n let dom: Node\n if (html.length === 0) {\n const slice = parser(text)\n if (!slice || typeof slice === 'string') return false\n\n dom = DOMSerializer.fromSchema(schema).serializeFragment(\n slice.content\n )\n } else {\n const template = document.createElement('template')\n template.innerHTML = html\n dom = template.content.cloneNode(true)\n template.remove()\n }\n\n let slice = domParser.parseSlice(dom)\n const transformPasted = view.someProp('transformPasted')\n if (transformPasted) {\n slice = transformPasted(slice, view, false)\n }\n return dispatchPasteSlice(view, slice)\n },\n clipboardTextSerializer: (slice) => {\n const serializer = ctx.get(serializerCtx)\n const isText = isPureText(slice.content.toJSON())\n if (isText)\n return (slice.content as unknown as ProsemirrorNode).textBetween(\n 0,\n slice.content.size,\n '\\n\\n'\n )\n\n const doc = schema.topNodeType.createAndFill(undefined, slice.content)\n if (!doc) return ''\n const value = serializer(doc)\n return value\n },\n },\n appendTransaction(transactions, _oldState, newState) {\n if (!transactions.some((tr) => tr.docChanged)) return null\n\n const imageType = schema.nodes['image']\n const imageBlockType = schema.nodes['image-block']\n const paragraphType = schema.nodes['paragraph']\n if (!imageType || !imageBlockType || !paragraphType) return null\n\n const replacements: {\n from: number\n to: number\n blocks: ProsemirrorNode[]\n }[] = []\n\n const isInlineNodesEmpty = (nodes: ProsemirrorNode[]) => {\n return nodes.every(\n (n) =>\n n.type.name === 'hardbreak' || (n.isText && n.text?.trim() === '')\n )\n }\n\n newState.doc.descendants((node, pos) => {\n if (node.type !== paragraphType) return true\n\n let hasImage = false\n for (let i = 0; i < node.childCount; i++) {\n if (node.child(i).type === imageType) {\n hasImage = true\n break\n }\n }\n\n if (!hasImage) return false\n\n const blocks: ProsemirrorNode[] = []\n let currentInlines: ProsemirrorNode[] = []\n\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i)\n if (child.type === imageType) {\n if (!isInlineNodesEmpty(currentInlines)) {\n blocks.push(paragraphType.create(node.attrs, currentInlines))\n }\n currentInlines = []\n\n blocks.push(\n imageBlockType.create({\n src: child.attrs.src,\n caption: child.attrs.alt || child.attrs.title || '',\n ratio: 1,\n })\n )\n } else {\n currentInlines.push(child)\n }\n }\n\n if (!isInlineNodesEmpty(currentInlines)) {\n blocks.push(paragraphType.create(node.attrs, currentInlines))\n }\n\n if (blocks.length > 0) {\n replacements.push({ from: pos, to: pos + node.nodeSize, blocks })\n }\n\n return false\n })\n\n if (replacements.length === 0) return null\n\n const { tr } = newState\n for (let i = replacements.length - 1; i >= 0; i--) {\n const r = replacements[i]!\n tr.replaceWith(r.from, r.to, r.blocks)\n }\n\n return tr\n },\n })\n\n return plugin\n})\n\nwithMeta(clipboard, { displayName: 'Prose<clipboard>' })\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jvs-milkdown/plugin-clipboard",
3
- "version": "1.1.8",
3
+ "version": "1.2.0",
4
4
  "keywords": [
5
5
  "milkdown",
6
6
  "milkdown plugin"
@@ -25,10 +25,10 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "@jvs-milkdown/core": "^1.1.8",
29
- "@jvs-milkdown/ctx": "^1.1.8",
30
- "@jvs-milkdown/prose": "^1.1.8",
31
- "@jvs-milkdown/utils": "^1.1.8"
28
+ "@jvs-milkdown/core": "^1.2.0",
29
+ "@jvs-milkdown/ctx": "^1.2.0",
30
+ "@jvs-milkdown/prose": "^1.2.0",
31
+ "@jvs-milkdown/utils": "^1.2.0"
32
32
  },
33
33
  "scripts": {
34
34
  "build": "vite build"
package/src/index.ts CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  type Node as ProsemirrorNode,
14
14
  type Slice,
15
15
  } from '@jvs-milkdown/prose/model'
16
- import { Plugin, PluginKey, TextSelection } from '@jvs-milkdown/prose/state'
16
+ import { Plugin, PluginKey } from '@jvs-milkdown/prose/state'
17
17
  import { $prose } from '@jvs-milkdown/utils'
18
18
 
19
19
  import { isPureText } from './__internal__/is-pure-text'
@@ -84,18 +84,12 @@ export const clipboard = $prose((ctx) => {
84
84
  const data = JSON.parse(vscodeData)
85
85
  const language = data?.mode
86
86
  if (text && language) {
87
- const { tr } = view.state
88
87
  const codeBlock = getNodeFromSchema('code_block', schema)
89
-
90
- tr.replaceSelectionWith(codeBlock.create({ language }))
91
- .setSelection(
92
- TextSelection.near(
93
- tr.doc.resolve(Math.max(0, tr.selection.from - 2))
94
- )
95
- )
96
- .insertText(text.replace(/\r\n?/g, '\n'))
97
-
98
- view.dispatch(tr)
88
+ const node = codeBlock.create(
89
+ { language },
90
+ schema.text(text.replace(/\r\n?/g, '\n'))
91
+ )
92
+ view.dispatch(view.state.tr.replaceSelectionWith(node))
99
93
  return true
100
94
  }
101
95
  }