Skip to content

Commit

Permalink
add: paste plaintext markdown into note editor
Browse files Browse the repository at this point in the history
resolve: #1185
  • Loading branch information
windingwind committed Nov 10, 2024
1 parent 136b15a commit ec1674c
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 31 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"unist-util-visit": "^5.0.0",
"unist-util-visit-parents": "^6.0.1",
"yamljs": "^0.3.0",
"zotero-plugin-toolkit": "^4.0.8"
"zotero-plugin-toolkit": "^4.0.9"
},
"devDependencies": {
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
Expand Down
132 changes: 132 additions & 0 deletions src/extras/editor/pasteMarkdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { Plugin, PluginKey } from "prosemirror-state";
import { md2html } from "../../utils/convert";

export { initPasteMarkdownPlugin };

declare const _currentEditorInstance: {
_editorCore: EditorCore;
};

function initPasteMarkdownPlugin() {
const core = _currentEditorInstance._editorCore;
console.log("Init BN Paste Markdown Plugin");
const key = new PluginKey("pasteDropPlugin");
const oldPlugins = core.view.state.plugins;
const oldPastePluginIndex = oldPlugins.findIndex(
(plugin) => plugin.props.handlePaste && plugin.props.handleDrop,
);
if (oldPastePluginIndex === -1) {
console.error("Paste plugin not found");
return;
}
const oldPastePlugin = oldPlugins[oldPastePluginIndex];
const newState = core.view.state.reconfigure({
plugins: [
...oldPlugins.slice(0, oldPastePluginIndex),
new Plugin({
key,
props: {
handlePaste: (view, event, slice) => {
if (!event.clipboardData) {
return false;
}
const markdown = getMarkdown(event.clipboardData);

if (!markdown) {
// Try the old paste plugin
return oldPastePlugin.props.handlePaste?.apply(oldPastePlugin, [
view,
event,
slice,
]);
}

md2html(markdown).then((html: string) => {
const slice = window.BetterNotesEditorAPI.getSliceFromHTML(
view.state,
html,
);
const tr = view.state.tr.replaceSelection(slice);
view.dispatch(tr);
});
return true;
},
handleDrop: (view, event, slice, moved) => {
if (!event.dataTransfer) {
return false;
}

const markdown = getMarkdown(event.dataTransfer);
if (!markdown) {
// Try the old drop plugin first
return oldPastePlugin.props.handleDrop?.apply(oldPastePlugin, [
view,
event,
slice,
moved,
]);
}

md2html(markdown).then((html: string) => {
const slice = window.BetterNotesEditorAPI.getSliceFromHTML(
view.state,
html,
);
const pos = view.posAtCoords({
left: event.clientX,
top: event.clientY,
});
if (!pos) {
return;
}
// Insert the slice to the current position
const tr = view.state.tr.insert(pos.pos, slice);
view.dispatch(tr);
});

return true;
},
},
}),
...oldPlugins.slice(oldPastePluginIndex + 1),
],
});
core.view.updateState(newState);
}

function getMarkdown(clipboardData: DataTransfer) {
// If the clipboard contains HTML, don't handle it
if (clipboardData.types.includes("text/html")) {
return false;
}

if (clipboardData.types.includes("text/markdown")) {
return clipboardData.getData("text/markdown");
}

// For Typora
if (clipboardData.types.includes("text/x-markdown")) {
return clipboardData.getData("text/x-markdown");
}

// Match markdown patterns
if (clipboardData.types.includes("text/plain")) {
const text = clipboardData.getData("text/plain");
const markdownPatterns = [
/^#/m, // Headers: Lines starting with #
/^\s*[-+*]\s/m, // Unordered lists: Lines starting with -, +, or *
/^\d+\.\s/m, // Ordered lists: Lines starting with numbers followed by a dot
/\[.*\]\(.*\)/, // Links: [text](url)
/`[^`]+`/, // Inline code: `code`
/^> /m, // Blockquotes: Lines starting with >
/```/, // Code blocks: Triple backticks
];

for (const pattern of markdownPatterns) {
if (pattern.test(text)) {
return text;
}
}
}
return false;
}
2 changes: 2 additions & 0 deletions src/extras/editorScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { EditorState, TextSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { initLinkPreviewPlugin } from "./editor/linkPreview";
import { initPasteMarkdownPlugin } from "./editor/pasteMarkdown";

declare const _currentEditorInstance: {
_editorCore: EditorCore;
Expand Down Expand Up @@ -378,6 +379,7 @@ export const BetterNotesEditorAPI = {
getNodeFromHTML,
setSelection,
initLinkPreviewPlugin,
initPasteMarkdownPlugin,
};

// @ts-ignore
Expand Down
65 changes: 39 additions & 26 deletions src/utils/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,33 +452,46 @@ function initLinkPreview(editor: Zotero.EditorInstance) {
return;
}
const EditorAPI = getEditorAPI(editor);
EditorAPI.initLinkPreviewPlugin(
Components.utils.cloneInto(
{
setPreviewContent: (
link: string,
setContent: (content: string) => void,
) => {
const note = addon.api.convert.link2note(link);
if (!note) {
setContent(`<p style="color: red;">Invalid note link: ${link}</p>`);
return;
}
addon.api.convert
.link2html(link, {
noteItem: note,
dryRun: true,
usePosition: true,
})
.then((content) => setContent(content));
safeCall(() =>
EditorAPI.initLinkPreviewPlugin(
Components.utils.cloneInto(
{
setPreviewContent: (
link: string,
setContent: (content: string) => void,
) => {
const note = addon.api.convert.link2note(link);
if (!note) {
setContent(
`<p style="color: red;">Invalid note link: ${link}</p>`,
);
return;
}
addon.api.convert
.link2html(link, {
noteItem: note,
dryRun: true,
usePosition: true,
})
.then((content) => setContent(content));
},
openURL: (url: string) => {
Zotero.getActiveZoteroPane().loadURI(url);
},
requireCtrl: previewType === "ctrl",
},
openURL: (url: string) => {
Zotero.getActiveZoteroPane().loadURI(url);
},
requireCtrl: previewType === "ctrl",
},
editor._iframeWindow,
{ wrapReflectors: true, cloneFunctions: true },
editor._iframeWindow,
{ wrapReflectors: true, cloneFunctions: true },
),
),
);
safeCall(() => EditorAPI.initPasteMarkdownPlugin());
}

function safeCall(callback: () => void) {
try {
callback();
} catch (e) {
ztoolkit.log(e as Error);
}
}

0 comments on commit ec1674c

Please sign in to comment.