Skip to content
This repository has been archived by the owner on Jan 21, 2024. It is now read-only.

Commit

Permalink
feat: default editor supports pasting or dragging pictures to upload (#…
Browse files Browse the repository at this point in the history
…825)

#### What type of PR is this?

/kind feature

#### What this PR does / why we need it:

添加复制或者拖拽图片到编辑器上传的支持。

#### Which issue(s) this PR fixes:

Fixes halo-dev/halo#3109
Fixes halo-dev/halo#2946

#### Screenshots:


#### Special notes for your reviewer:

#### Does this PR introduce a user-facing change?


```release-note
Console 端的默认编辑器支持拖拽或者粘贴图片上传
```
  • Loading branch information
ruibaby authored Feb 1, 2023
1 parent 658a8ce commit 8f6d543
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 3 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"colorjs.io": "^0.4.2",
"dayjs": "^1.11.6",
"emoji-mart": "^5.3.3",
"fastq": "^1.15.0",
"floating-vue": "2.0.0-beta.20",
"fuse.js": "^6.6.2",
"lodash.clonedeep": "^4.5.0",
Expand Down
8 changes: 5 additions & 3 deletions pnpm-lock.yaml

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

143 changes: 143 additions & 0 deletions src/components/editor/DefaultEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import {
IconCharacterRecognition,
IconLink,
IconUserFollow,
Toast,
VTabItem,
VTabs,
} from "@halo-dev/components";
Expand All @@ -104,6 +105,11 @@ import {
} from "vue";
import { formatDatetime } from "@/utils/date";
import { useAttachmentSelect } from "@/modules/contents/attachments/composables/use-attachment";
import { apiClient } from "@/utils/api-client";
import * as fastq from "fastq";
import type { queueAsPromised } from "fastq";
import type { Attachment } from "@halo-dev/api-client";
import { useFetchAttachmentPolicy } from "@/modules/contents/attachments/composables/use-attachment-policy";
const props = withDefaults(
defineProps<{
Expand Down Expand Up @@ -168,6 +174,7 @@ const editor = useEditor({
ExtensionText,
ExtensionImage.configure({
inline: true,
allowBase64: false,
HTMLAttributes: {
loading: "lazy",
},
Expand Down Expand Up @@ -250,8 +257,144 @@ const editor = useEditor({
handleGenerateTableOfContent();
});
},
editorProps: {
handleDrop: (view, event: DragEvent, _, moved) => {
if (!moved && event.dataTransfer && event.dataTransfer.files) {
const images = Array.from(event.dataTransfer.files).filter((file) =>
file.type.startsWith("image/")
) as File[];
if (images.length === 0) {
return;
}
event.preventDefault();
images.forEach((file, index) => {
uploadQueue.push({
file,
process: (url: string) => {
const { schema } = view.state;
const coordinates = view.posAtCoords({
left: event.clientX,
top: event.clientY,
});
if (!coordinates) return;
const node = schema.nodes.image.create({
src: url,
});
const transaction = view.state.tr.insert(
coordinates.pos + index,
node
);
editor.value?.view.dispatch(transaction);
},
});
});
return true;
}
return false;
},
handlePaste: (view, event: ClipboardEvent, slice) => {
const images = Array.from(event.clipboardData?.items || [])
.map((item) => {
return item.getAsFile();
})
.filter((file) => {
return file && file.type.startsWith("image/");
}) as File[];
if (images.length === 0) {
return;
}
event.preventDefault();
images.forEach((file) => {
uploadQueue.push({
file,
process: (url: string) => {
editor.value
?.chain()
.focus()
.insertContent([
{
type: "image",
attrs: {
src: url,
},
},
])
.run();
},
});
});
},
},
});
// image drag and paste upload
const { policies } = useFetchAttachmentPolicy({ fetchOnMounted: true });
type Task = {
file: File;
process: (permalink: string) => void;
};
const uploadQueue: queueAsPromised<Task> = fastq.promise(asyncWorker, 1);
async function asyncWorker(arg: Task): Promise<void> {
if (!policies.value.length) {
Toast.warning("目前没有可用的存储策略");
return;
}
const { data: attachmentData } = await apiClient.attachment.uploadAttachment({
file: arg.file,
policyName: policies.value[0].metadata.name,
});
const permalink = await handleFetchPermalink(attachmentData, 3);
if (permalink) {
arg.process(permalink);
}
}
const handleFetchPermalink = async (
attachment: Attachment,
maxRetry: number
): Promise<string | undefined> => {
if (maxRetry === 0) {
Toast.error(`获取附件永久链接失败:${attachment.spec.displayName}`);
return undefined;
}
const { data } =
await apiClient.extension.storage.attachment.getstorageHaloRunV1alpha1Attachment(
{
name: attachment.metadata.name,
}
);
if (data.status?.permalink) {
return data.status.permalink;
}
return await new Promise((resolve) => {
const timer = setTimeout(() => {
const permalink = handleFetchPermalink(attachment, maxRetry - 1);
clearTimeout(timer);
resolve(permalink);
}, 300);
});
};
const toolbarMenuItems = computed(() => {
if (!editor.value) return [];
return [
Expand Down

1 comment on commit 8f6d543

@vercel
Copy link

@vercel vercel bot commented on 8f6d543 Feb 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

ui – ./

halo-admin-ui.vercel.app
ui-git-main-halo-dev.vercel.app
ui-halo-dev.vercel.app

Please sign in to comment.