Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add paste support #178

Merged
merged 3 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"success": "Comment successful",
"title": "Comment"
},
"comparison": "Comparison",
"confirm": "Confirm",
"content": {
"empty": "Content cannot be empty"
Expand Down
1 change: 1 addition & 0 deletions client/public/locales/ja/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"success": "コメントに成功しました",
"title": "コメント"
},
"comparison": "対比",
"confirm": "確認",
"content": {
"empty": "内容は空にできません"
Expand Down
1 change: 1 addition & 0 deletions client/public/locales/zh/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"success": "评论成功",
"title": "评论"
},
"comparison": "对比",
"confirm": "确认",
"content": {
"empty": "内容不能为空"
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/toc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export function TableOfContents({ selector, maxDepth = 3 }: { selector: string,
}

function TOCItem({ item, maxDepth, intersecting, lastIntersecting, enabled = true }: { item: TOCItem, maxDepth?: number, intersecting: string[], lastIntersecting?: string, enabled?: boolean }) {
const isIntersecting = intersecting.includes(item.id) || (intersecting.length === 0 && lastIntersecting === item.id)
const isIntersecting = intersecting[0] === item.id || (intersecting.length === 0 && lastIntersecting === item.id)
return (
<li>
<a className={isIntersecting && enabled ? "text-theme" : ""} href={`#${item.id}`}
Expand Down
156 changes: 81 additions & 75 deletions client/src/page/writing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,19 +142,6 @@ function uploadImage(file: File, onSuccess: (url: string) => void) {
});
}

const handlePaste = async (event: React.ClipboardEvent<HTMLDivElement>) => {
// Access the clipboard data using event.clipboardData
const clipboardData = event.clipboardData;
// only if clipboard payload is file
if (clipboardData.files.length === 1) {
const myfile = clipboardData.files[0] as File;
uploadImage(myfile, (url) => {
document.execCommand("insertText", false, `![${myfile.name}](${url})\n`);
});
event.preventDefault();
}
};



// 写作页面
Expand All @@ -171,7 +158,7 @@ export function WritingPage({ id }: { id?: number }) {
const [listed, setListed] = useState(true);
const [content, setContent] = useState<string>(cache.get("content") ?? "");
const [createdAt, setCreatedAt] = useState<Date | undefined>(new Date());
const [preview, setPreview] = useState(false);
const [preview, setPreview] = useState<boolean | 'comparison'>(false);
const [uploading, setUploading] = useState(false)
const [publishing, setPublishing] = useState(false)
function publishButton() {
Expand Down Expand Up @@ -223,6 +210,29 @@ export function WritingPage({ id }: { id?: number }) {
}
}


const handlePaste = async (event: React.ClipboardEvent<HTMLDivElement>) => {
// Access the clipboard data using event.clipboardData
const clipboardData = event.clipboardData;
// only if clipboard payload is file
if (clipboardData.files.length === 1) {
const editor = editorRef.current;
if (!editor) return;
editor.trigger(undefined, "undo", undefined);
setUploading(true)
const myfile = clipboardData.files[0] as File;
uploadImage(myfile, (url) => {
const selection = editor.getSelection();
if (!selection) return;
editor.executeEdits(undefined, [{
range: selection,
text: `![${myfile.name}](${url})\n`,
}]);
setUploading(false)
});
}
};

function UploadImageButton() {
const uploadRef = useRef<HTMLInputElement>(null);
const t = i18n.t
Expand Down Expand Up @@ -283,7 +293,6 @@ export function WritingPage({ id }: { id?: number }) {
});
}
}, []);
const [drag, setDrag] = useState(false);
function MetaInput({ className }: { className?: string }) {
return (
<>
Expand Down Expand Up @@ -370,8 +379,9 @@ export function WritingPage({ id }: { id?: number }) {
{MetaInput({ className: "visible md:hidden mb-8" })}
<div className="flex flex-col mx-4 my-2 md:mx-0 md:my-0 gap-2">
<div className="flex flex-row space-x-2">
<button className={`${preview ? "" : "text-theme"}`} onClick={() => setPreview(false)}> {t("edit")} </button>
<button className={`${preview ? "text-theme" : ""}`} onClick={() => setPreview(true)}> {t("preview")} </button>
<button className={`${preview === false ? "text-theme" : ""}`} onClick={() => setPreview(false)}> {t("edit")} </button>
<button className={`${preview === true ? "text-theme" : ""}`} onClick={() => setPreview(true)}> {t("preview")} </button>
<button className={`${preview === 'comparison' ? "text-theme" : ""}`} onClick={() => setPreview('comparison')}> {t("comparison")} </button>
<div className="flex-grow" />
{uploading &&
<div className="flex flex-row space-x-2 items-center">
Expand All @@ -380,68 +390,64 @@ export function WritingPage({ id }: { id?: number }) {
</div>
}
</div>
<div className={"flex flex-col " + (preview ? "hidden" : "")}>
<div className="flex flex-row justify-start mb-2">
<UploadImageButton />
</div>
<div
className={"relative"}
onDrop={(e) => {
e.preventDefault();
setDrag(false);
const editor = editorRef.current;
if (!editor) return;
for (let i = 0; i < e.dataTransfer.files.length; i++) {
const selection = editor.getSelection();
if (!selection) return;
const file = e.dataTransfer.files[i];
setUploading(true)
uploadImage(file, (url) => {
setUploading(false)
editor.executeEdits(undefined, [{
range: selection,
text: `![${file.name}](${url})\n`,
}]);
});
}
}}
onPaste={handlePaste}
>
<Editor
onMount={(editor, _) => {
editorRef.current = editor
}}
height="600px"
defaultLanguage="markdown"
className=""
value={content}
// onPaste={handlePaste}
onChange={(data, e) => {
console.log(e)
cache.set("content", data ?? "");
setContent(data ?? "");
}}
theme={colorMode === "dark" ? "vs-dark" : "light"}
options={{
wordWrap: "on",
fontSize: 14,
fontFamily: "Fira Code",
lineNumbers: "off",
dragAndDrop: true,
}}
/>
<div className={`grid grid-cols-1 ${preview === 'comparison' ? "sm:grid-cols-2" : ""}`}>
<div className={"flex flex-col " + (preview === true ? "hidden" : "")}>
<div className="flex flex-row justify-start mb-2">
<UploadImageButton />
</div>
<div
className={`absolute bg-theme/10 t-secondary text-xl top-0 left-0 right-0 bottom-0 flex flex-col justify-center items-center ${drag ? "" : "hidden"
}`}
className={"relative"}
onDrop={(e) => {
e.preventDefault();
const editor = editorRef.current;
if (!editor) return;
for (let i = 0; i < e.dataTransfer.files.length; i++) {
const selection = editor.getSelection();
if (!selection) return;
const file = e.dataTransfer.files[i];
setUploading(true)
uploadImage(file, (url) => {
setUploading(false)
editor.executeEdits(undefined, [{
range: selection,
text: `![${file.name}](${url})\n`,
}]);
});
}
}}
onPaste={handlePaste}
>
{t('drop.image')}
<Editor
onMount={(editor, _) => {
editorRef.current = editor
}}
height="600px"
defaultLanguage="markdown"
className=""
value={content}
// onPaste={handlePaste}
onChange={(data, e) => {
console.log(e)
cache.set("content", data ?? "");
setContent(data ?? "");
}}
theme={colorMode === "dark" ? "vs-dark" : "light"}
options={{
wordWrap: "on",
fontSize: 14,
fontFamily: "Fira Code",
lineNumbers: "off",
dragAndDrop: true,
pasteAs: { enabled: false }
}}
/>
</div>
</div>
</div>
<div
className={"px-4 h-[600px] overflow-y-scroll " + (preview ? "" : "hidden")}
>
<Markdown content={content ? content : "> No content now. Write on the left side."} />
<div
className={"px-4 h-[600px] overflow-y-scroll " + (preview != false ? "" : "hidden")}
>
<Markdown content={content ? content : "> No content now. Write on the left side."} />
</div>
</div>
</div>
</div>
Expand Down