From d5da91c1fda05d5286b9faf75905edb8d47e3c08 Mon Sep 17 00:00:00 2001 From: Mohammad Moradi Date: Sat, 11 Jul 2026 13:12:11 +0300 Subject: [PATCH 1/7] docs: Create Skills --- .claude/skills/adapter-parity/SKILL.md | 56 ++++++++++++++++++++++ .claude/skills/changeset/SKILL.md | 59 +++++++++++++++++++++++ .claude/skills/new-block-type/SKILL.md | 66 ++++++++++++++++++++++++++ .claude/skills/release-check/SKILL.md | 62 ++++++++++++++++++++++++ .claude/skills/theming/SKILL.md | 63 ++++++++++++++++++++++++ 5 files changed, 306 insertions(+) create mode 100644 .claude/skills/adapter-parity/SKILL.md create mode 100644 .claude/skills/changeset/SKILL.md create mode 100644 .claude/skills/new-block-type/SKILL.md create mode 100644 .claude/skills/release-check/SKILL.md create mode 100644 .claude/skills/theming/SKILL.md diff --git a/.claude/skills/adapter-parity/SKILL.md b/.claude/skills/adapter-parity/SKILL.md new file mode 100644 index 0000000..d9731ac --- /dev/null +++ b/.claude/skills/adapter-parity/SKILL.md @@ -0,0 +1,56 @@ +--- +name: adapter-parity +description: Use whenever you change editor *behavior* (keyboard shortcuts, markdown shortcuts, selection handling, table operations, drag/drop, paste handling) in either the Vue or React adapter, or when auditing whether the two adapters have drifted apart. Ports the change to the sibling adapter and verifies the two state machines still mirror each other function-for-function. +--- + +# Keeping the Vue and React adapters in sync + +xproeditor intentionally keeps two state machines — `BlockEditor.vue`'s +` + + diff --git a/packages/vue/src/components/EditorBlockItem.vue b/packages/vue/src/components/EditorBlockItem.vue index 87e66ab..488e90c 100644 --- a/packages/vue/src/components/EditorBlockItem.vue +++ b/packages/vue/src/components/EditorBlockItem.vue @@ -11,7 +11,9 @@ import { IconEmojiPicker, IconValueDisplay, } from '../ui' +import EditorAudioBlock from './EditorAudioBlock.vue' import EditorCodeBlock from './EditorCodeBlock.vue' +import EditorFileBlock from './EditorFileBlock.vue' import EditorImageBlock from './EditorImageBlock.vue' import EditorSelectionHighlight from './EditorSelectionHighlight.vue' import EditorTableBlock from './EditorTableBlock.vue' @@ -374,6 +376,28 @@ defineExpose({ @select="emit('select')" /> + + + + +import { Download, FileText, FolderOpen, Loader2, Paperclip, Upload } from 'lucide-vue-next'; +import { ref } from 'vue'; +import type { Block } from '@xproeditor/core'; +import { fileToObjectUrl, formatFileSize, mediaPropsFromFile } from '@xproeditor/core'; + +const props = defineProps<{ + block: Block; + selected?: boolean; + readonly?: boolean; + upload?: (file: File) => Promise; + pickMedia?: (options: { + accept: string[]; + title?: string; + }) => Promise<{ url: string; alt?: string; caption?: string } | null>; +}>(); + +const emit = defineEmits<{ + patch: [patch: Record]; + select: []; +}>(); + +const uploading = ref(false); +const picking = ref(false); +const dragOver = ref(false); +const fileInput = ref(null); + +async function uploadFile(file: File) { + uploading.value = true; + + try { + const url = await (props.upload ?? fileToObjectUrl)(file); + emit('patch', mediaPropsFromFile(file, url)); + } finally { + uploading.value = false; + } +} + +async function pickFromLibrary() { + if (!props.pickMedia) { + return; + } + + picking.value = true; + + try { + const result = await props.pickMedia({ accept: ['*/*'], title: 'Choose file' }); + + if (result?.url) { + emit('patch', { + url: result.url, + ...(result.caption ? { name: result.caption } : {}), + }); + } + } finally { + picking.value = false; + } +} + +function onFilePicked(e: Event) { + const file = (e.target as HTMLInputElement).files?.[0]; + + if (file) { + uploadFile(file); + } +} + +function onDrop(e: DragEvent) { + dragOver.value = false; + const file = e.dataTransfer?.files?.[0]; + + if (file) { + uploadFile(file); + } +} + +const busy = () => uploading.value || picking.value; + + + diff --git a/packages/vue/src/components/EditorImageBlock.vue b/packages/vue/src/components/EditorImageBlock.vue index 0124e34..45f99ef 100644 --- a/packages/vue/src/components/EditorImageBlock.vue +++ b/packages/vue/src/components/EditorImageBlock.vue @@ -1,7 +1,8 @@