diff --git a/docs/package.json b/docs/package.json index e04c37faf5..b0ddb1d1b8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -31,7 +31,7 @@ "react": "^18", "react-dom": "^18", "react-github-btn": "^1.4.0", - "react-icons": "^4.3.1", + "react-icons": "^5.2.1", "y-partykit": "^0.0.25", "yjs": "^13.6.1" }, diff --git a/docs/pages/docs/advanced/vanilla-js.mdx b/docs/pages/docs/advanced/vanilla-js.mdx index 938dbda970..743ee478dc 100644 --- a/docs/pages/docs/advanced/vanilla-js.mdx +++ b/docs/pages/docs/advanced/vanilla-js.mdx @@ -49,7 +49,7 @@ While it's up to you to decide how you want the elements to be rendered, BlockNo type UIElement = | "formattingToolbar" | "linkToolbar" - | "imageToolbar" + | "filePanel" | "sideMenu" | "suggestionMenu" | "tableHandles" diff --git a/docs/pages/docs/editor-basics/setup.mdx b/docs/pages/docs/editor-basics/setup.mdx index e8675c6c84..e16960f469 100644 --- a/docs/pages/docs/editor-basics/setup.mdx +++ b/docs/pages/docs/editor-basics/setup.mdx @@ -96,7 +96,7 @@ export type BlockNoteViewProps = { linkToolbar?: boolean; sideMenu?: boolean; slashMenu?: boolean; - imageToolbar?: boolean; + filePanel?: boolean; tableHandles?: boolean; children?: } & HTMLAttributes; @@ -120,7 +120,7 @@ export type BlockNoteViewProps = { `slashMenu`: Whether the [Slash Menu](/docs/ui-components/suggestion-menus#slash-menu) should be enabled. -`imageToolbar`: Whether the Image Toolbar should be enabled. +`filePanel`: Whether the File Toolbar should be enabled. `tableHandles`: Whether the Table Handles should be enabled. diff --git a/examples/01-basic/03-all-blocks/App.tsx b/examples/01-basic/03-all-blocks/App.tsx index ff6e4d05bd..f8debbcfdc 100644 --- a/examples/01-basic/03-all-blocks/App.tsx +++ b/examples/01-basic/03-all-blocks/App.tsx @@ -40,9 +40,6 @@ export default function App() { type: "numberedListItem", content: "Numbered List Item", }, - { - type: "image", - }, { type: "table", content: { @@ -60,6 +57,33 @@ export default function App() { ], }, }, + { + type: "file", + }, + { + type: "image", + props: { + url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + caption: + "From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + }, + }, + { + type: "video", + props: { + url: "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", + caption: + "From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", + }, + }, + { + type: "audio", + props: { + url: "https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", + caption: + "From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", + }, + }, { type: "paragraph", }, diff --git a/examples/01-basic/testing/App.tsx b/examples/01-basic/testing/App.tsx index de439b5928..353e445e91 100644 --- a/examples/01-basic/testing/App.tsx +++ b/examples/01-basic/testing/App.tsx @@ -1,8 +1,8 @@ import { uploadToTmpFilesDotOrg_DEV_ONLY } from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; export default function App() { // Creates a new editor instance. diff --git a/examples/02-ui-components/01-ui-elements-remove/App.tsx b/examples/02-ui-components/01-ui-elements-remove/App.tsx index b2f9038ab6..bb4c1176da 100644 --- a/examples/02-ui-components/01-ui-elements-remove/App.tsx +++ b/examples/02-ui-components/01-ui-elements-remove/App.tsx @@ -1,7 +1,7 @@ import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; export default function App() { // Creates a new editor instance. @@ -34,7 +34,7 @@ export default function App() { // Removes all menus and toolbars. formattingToolbar={false} linkToolbar={false} - imageToolbar={false} + filePanel={false} sideMenu={false} slashMenu={false} tableHandles={false} diff --git a/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx b/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx index 779452273d..92c4b48073 100644 --- a/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx +++ b/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx @@ -1,20 +1,20 @@ import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; import { BasicTextStyleButton, BlockTypeSelect, ColorStyleButton, CreateLinkButton, + FileCaptionButton, + FileReplaceButton, FormattingToolbar, FormattingToolbarController, - ImageCaptionButton, NestBlockButton, - ReplaceImageButton, TextAlignButton, UnnestBlockButton, useCreateBlockNote, } from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; import { BlueButton } from "./BlueButton"; @@ -77,8 +77,8 @@ export default function App() { {/* Extra button to toggle blue text & background */} - - + + { const block = editor.getTextCursorPosition().block; const activeStyles = editor.getActiveStyles(); @@ -44,7 +45,9 @@ export function CustomFormattingToolbar() { italic: (activeStyles.italic as boolean) || false, underline: (activeStyles.underline as boolean) || false, - textAlignment: block.props.textAlignment, + textAlignment: checkBlockHasDefaultProp("textAlignment", block, editor) + ? block.props.textAlignment + : undefined, textColor: (activeStyles.textColor as string) || "default", backgroundColor: (activeStyles.backgroundColor as string) || "default", @@ -80,8 +83,7 @@ export function CustomFormattingToolbar() { const [linkMenuOpen, setLinkMenuOpen] = useState(false); // Updates toolbar state when the editor content or selection changes. - useEditorChange(() => setState(getState()), editor); - useEditorSelectionChange(() => setState(getState()), editor); + useEditorContentOrSelectionChange(() => setState(getState()), editor); return (
@@ -111,40 +113,42 @@ export function CustomFormattingToolbar() {
{/* Button group for text alignment */} -
- {/*Left align button*/} - - {/* Center align button */} - - {/* Right align button */} - - {/* Justify text button */} - -
+ {state.textAlignment && ( +
+ {/*Left align button*/} + + {/* Center align button */} + + {/* Right align button */} + + {/* Justify text button */} + +
+ )} {/* Button group for color menu */}
diff --git a/examples/02-ui-components/08-custom-ui/package.json b/examples/02-ui-components/08-custom-ui/package.json index 367401093c..ead2a0ecfc 100644 --- a/examples/02-ui-components/08-custom-ui/package.json +++ b/examples/02-ui-components/08-custom-ui/package.json @@ -18,7 +18,7 @@ "@blocknote/shadcn": "^0.13.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-icons": "^4.3.1" + "react-icons": "^5.2.1" }, "devDependencies": { "@types/react": "^18.0.25", diff --git a/examples/05-custom-schema/01-alert-block/.bnexample.json b/examples/05-custom-schema/01-alert-block/.bnexample.json index a768adacd1..e066ae8fb6 100644 --- a/examples/05-custom-schema/01-alert-block/.bnexample.json +++ b/examples/05-custom-schema/01-alert-block/.bnexample.json @@ -5,6 +5,6 @@ "tags": ["Intermediate", "Blocks", "Custom Schemas", "Suggestion Menus", "Slash Menu"], "dependencies": { "@mantine/core": "^7.7.1", - "react-icons": "^4.3.1" + "react-icons": "^5.2.1" } } diff --git a/examples/05-custom-schema/01-alert-block/package.json b/examples/05-custom-schema/01-alert-block/package.json index e4190f6a4f..44a0beb0bf 100644 --- a/examples/05-custom-schema/01-alert-block/package.json +++ b/examples/05-custom-schema/01-alert-block/package.json @@ -19,7 +19,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "@mantine/core": "^7.7.1", - "react-icons": "^4.3.1" + "react-icons": "^5.2.1" }, "devDependencies": { "@types/react": "^18.0.25", diff --git a/examples/05-custom-schema/03-font-style/App.tsx b/examples/05-custom-schema/03-font-style/App.tsx index c64743552d..7c881fbf11 100644 --- a/examples/05-custom-schema/03-font-style/App.tsx +++ b/examples/05-custom-schema/03-font-style/App.tsx @@ -7,11 +7,11 @@ import { BlockTypeSelect, ColorStyleButton, CreateLinkButton, + FileCaptionButton, + FileReplaceButton, FormattingToolbar, FormattingToolbarController, - ImageCaptionButton, NestBlockButton, - ReplaceImageButton, TextAlignButton, UnnestBlockButton, useBlockNoteEditor, @@ -104,8 +104,8 @@ export default function App() { - - + + ({ + title: "PDF", + onItemClick: () => { + insertOrUpdateBlock(editor, { + type: "pdf", + }); + }, + aliases: ["pdf", "document", "embed", "file"], + group: "Other", + icon: , +}); + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + schema, + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "pdf", + props: { + url: "https://pdfobject.com/pdf/sample.pdf", + }, + }, + { + type: "paragraph", + content: "Press the '/' key to open the Slash Menu and add another PDF", + }, + { + type: "paragraph", + }, + ], + }); + + // Renders the editor instance. + return ( + + {/* Replaces the default Slash Menu. */} + + // Gets all default slash menu items and `insertPDF` item. + filterSuggestionItems( + [...getDefaultReactSlashMenuItems(editor), insertPDF(editor)], + query + ) + } + /> + + ); +} diff --git a/examples/05-custom-schema/04-pdf-file-block/PDF.tsx b/examples/05-custom-schema/04-pdf-file-block/PDF.tsx new file mode 100644 index 0000000000..1cd116ff8d --- /dev/null +++ b/examples/05-custom-schema/04-pdf-file-block/PDF.tsx @@ -0,0 +1,84 @@ +import { defaultProps, FileBlockConfig } from "@blocknote/core"; +import { + AddFileButton, + createReactBlockSpec, + DefaultFilePreview, + FileAndCaptionWrapper, + ReactCustomBlockRenderProps, +} from "@blocknote/react"; + +import { RiFilePdfFill } from "react-icons/ri"; + +export const PDFPreview = ( + props: Omit< + ReactCustomBlockRenderProps, + "contentRef" + > +) => { + return ( + props.editor.setTextCursorPosition(props.block)} + /> + ); +}; + +export const PDF = createReactBlockSpec( + { + type: "pdf", + propSchema: { + name: { + default: "" as const, + }, + url: { + default: "" as const, + }, + caption: { + default: "" as const, + }, + showPreview: { + default: true, + }, + previewWidth: { + default: 512, + }, + }, + content: "none", + isFileBlock: true, + isFileBlockPlaceholder: (block: any) => !block.props.url, + }, + { + render: (props) => ( +
+ {props.block.props.url === "" ? ( + } + /> + ) : !props.block.props.showPreview ? ( + + + + ) : ( + + + + )} +
+ ), + } +); diff --git a/examples/05-custom-schema/04-pdf-file-block/README.md b/examples/05-custom-schema/04-pdf-file-block/README.md new file mode 100644 index 0000000000..797ec9b9f8 --- /dev/null +++ b/examples/05-custom-schema/04-pdf-file-block/README.md @@ -0,0 +1,11 @@ +# PDF Block + +In this example, we create a custom `PDF` block which extends the built-in `File` block. In addition, we create a Slash Menu item which inserts a `PDF` block. + +**Try it out:** Press the "/" key to open the Slash Menu and insert an `PDF` block! + +**Relevant Docs:** + +- [Custom Blocks](/docs/custom-schemas/custom-blocks) +- [Changing Slash Menu Items](/docs/ui-components/suggestion-menus#changing-slash-menu-items) +- [Editor Setup](/docs/editor-basics/setup) \ No newline at end of file diff --git a/examples/05-custom-schema/04-pdf-file-block/index.html b/examples/05-custom-schema/04-pdf-file-block/index.html new file mode 100644 index 0000000000..7c645810ab --- /dev/null +++ b/examples/05-custom-schema/04-pdf-file-block/index.html @@ -0,0 +1,14 @@ + + + + + + PDF Block + + +
+ + + diff --git a/examples/05-custom-schema/04-pdf-file-block/main.tsx b/examples/05-custom-schema/04-pdf-file-block/main.tsx new file mode 100644 index 0000000000..f88b490fbd --- /dev/null +++ b/examples/05-custom-schema/04-pdf-file-block/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + +); diff --git a/examples/05-custom-schema/04-pdf-file-block/package.json b/examples/05-custom-schema/04-pdf-file-block/package.json new file mode 100644 index 0000000000..536a031b9f --- /dev/null +++ b/examples/05-custom-schema/04-pdf-file-block/package.json @@ -0,0 +1,39 @@ +{ + "name": "@blocknote/example-pdf-file-block", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --max-warnings 0" + }, + "dependencies": { + "@blocknote/core": "^0.13.0", + "@blocknote/react": "^0.13.0", + "@blocknote/ariakit": "^0.13.0", + "@blocknote/mantine": "^0.13.0", + "@blocknote/shadcn": "^0.13.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "@mantine/core": "^7.7.1", + "react-icons": "^5.2.1" + }, + "devDependencies": { + "@types/react": "^18.0.25", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react": "^4.0.4", + "eslint": "^8.10.0", + "vite": "^4.4.8" + }, + "eslintConfig": { + "extends": [ + "../../../.eslintrc.js" + ] + }, + "eslintIgnore": [ + "dist" + ] +} \ No newline at end of file diff --git a/examples/05-custom-schema/04-pdf-file-block/tsconfig.json b/examples/05-custom-schema/04-pdf-file-block/tsconfig.json new file mode 100644 index 0000000000..1bd8ab3c57 --- /dev/null +++ b/examples/05-custom-schema/04-pdf-file-block/tsconfig.json @@ -0,0 +1,36 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": [ + "." + ], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} \ No newline at end of file diff --git a/examples/05-custom-schema/04-pdf-file-block/vite.config.ts b/examples/05-custom-schema/04-pdf-file-block/vite.config.ts new file mode 100644 index 0000000000..f62ab20bc2 --- /dev/null +++ b/examples/05-custom-schema/04-pdf-file-block/vite.config.ts @@ -0,0 +1,32 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite"; +// import eslintPlugin from "vite-plugin-eslint"; +// https://vitejs.dev/config/ +export default defineConfig((conf) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/" + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/" + ), + } as any), + }, +})); diff --git a/package-lock.json b/package-lock.json index 55eb310525..d6e8d7cb5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,7 +48,7 @@ "react": "^18", "react-dom": "^18", "react-github-btn": "^1.4.0", - "react-icons": "^4.3.1", + "react-icons": "^5.2.1", "y-partykit": "^0.0.25", "yjs": "^13.6.1" }, @@ -21201,9 +21201,9 @@ } }, "node_modules/react-icons": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", - "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz", + "integrity": "sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==", "peerDependencies": { "react": "*" } @@ -26852,7 +26852,7 @@ "@mantine/utils": "^6.0.21", "react": "^18", "react-dom": "^18", - "react-icons": "^4.3.1", + "react-icons": "^5.2.1", "use-prefers-color-scheme": "^1.1.3" }, "devDependencies": { @@ -26903,7 +26903,7 @@ "lodash.merge": "^4.6.2", "react": "^18", "react-dom": "^18", - "react-icons": "^4.3.1", + "react-icons": "^5.2.1", "use-prefers-color-scheme": "^1.1.3" }, "devDependencies": { @@ -27027,6 +27027,7 @@ "@mantine/core": "^7.7.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-icons": "^5.2.1", "react-router-dom": "^6.20.0", "y-partykit": "^0.0.25", "yjs": "^13.6.10" @@ -27076,7 +27077,7 @@ "@playwright/experimental-ct-react": "^1.42.1", "@playwright/test": "^1.42.1", "eslint": "^8.10.0", - "react-icons": "^4.3.1", + "react-icons": "^5.2.1", "rimraf": "^5.0.5" } }, diff --git a/packages/ariakit/src/index.tsx b/packages/ariakit/src/index.tsx index e19cb08d4e..5afaab6c91 100644 --- a/packages/ariakit/src/index.tsx +++ b/packages/ariakit/src/index.tsx @@ -42,7 +42,7 @@ export const components: Components = { Button: ToolbarButton, Select: ToolbarSelect, }, - ImagePanel: { + FilePanel: { Root: Panel, Button: PanelButton, FileInput: PanelFileInput, diff --git a/packages/ariakit/src/panel/Panel.tsx b/packages/ariakit/src/panel/Panel.tsx index 4bfda485b6..efb3e47209 100644 --- a/packages/ariakit/src/panel/Panel.tsx +++ b/packages/ariakit/src/panel/Panel.tsx @@ -6,7 +6,7 @@ import { forwardRef } from "react"; export const Panel = forwardRef< HTMLDivElement, - ComponentProps["ImagePanel"]["Root"] + ComponentProps["FilePanel"]["Root"] >((props, ref) => { const { className, diff --git a/packages/ariakit/src/panel/PanelButton.tsx b/packages/ariakit/src/panel/PanelButton.tsx index c11948bd94..dbda13f469 100644 --- a/packages/ariakit/src/panel/PanelButton.tsx +++ b/packages/ariakit/src/panel/PanelButton.tsx @@ -6,7 +6,7 @@ import { forwardRef } from "react"; export const PanelButton = forwardRef< HTMLButtonElement, - ComponentProps["ImagePanel"]["Button"] + ComponentProps["FilePanel"]["Button"] >((props, ref) => { const { className, children, onClick, label, ...rest } = props; diff --git a/packages/ariakit/src/panel/PanelFileInput.tsx b/packages/ariakit/src/panel/PanelFileInput.tsx index ca9b622222..a7ddab7be4 100644 --- a/packages/ariakit/src/panel/PanelFileInput.tsx +++ b/packages/ariakit/src/panel/PanelFileInput.tsx @@ -6,9 +6,9 @@ import { forwardRef } from "react"; export const PanelFileInput = forwardRef< HTMLInputElement, - ComponentProps["ImagePanel"]["FileInput"] + ComponentProps["FilePanel"]["FileInput"] >((props, ref) => { - const { className, value, placeholder, onChange, ...rest } = props; + const { className, accept, value, placeholder, onChange, ...rest } = props; assertEmpty(rest); @@ -19,6 +19,7 @@ export const PanelFileInput = forwardRef< ref={ref} name={"panel-input"} type={"file"} + accept={accept} value={value ? value.name : undefined} onChange={async (e) => onChange?.(e.target.files![0])} placeholder={placeholder} diff --git a/packages/ariakit/src/panel/PanelTab.tsx b/packages/ariakit/src/panel/PanelTab.tsx index 224d299cea..6f5a83b12b 100644 --- a/packages/ariakit/src/panel/PanelTab.tsx +++ b/packages/ariakit/src/panel/PanelTab.tsx @@ -4,7 +4,7 @@ import { forwardRef } from "react"; export const PanelTab = forwardRef< HTMLDivElement, - ComponentProps["ImagePanel"]["TabPanel"] + ComponentProps["FilePanel"]["TabPanel"] >((props, ref) => { const { className, children, ...rest } = props; diff --git a/packages/ariakit/src/panel/PanelTextInput.tsx b/packages/ariakit/src/panel/PanelTextInput.tsx index 91f13050b7..20fdf3d7df 100644 --- a/packages/ariakit/src/panel/PanelTextInput.tsx +++ b/packages/ariakit/src/panel/PanelTextInput.tsx @@ -6,7 +6,7 @@ import { forwardRef } from "react"; export const PanelTextInput = forwardRef< HTMLInputElement, - ComponentProps["ImagePanel"]["TextInput"] + ComponentProps["FilePanel"]["TextInput"] >((props, ref) => { const { className, value, placeholder, onKeyDown, onChange, ...rest } = props; diff --git a/packages/core/src/api/exporters/html/__snapshots__/file/basic/external.html b/packages/core/src/api/exporters/html/__snapshots__/file/basic/external.html new file mode 100644 index 0000000000..3c59ab678d --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/file/basic/external.html @@ -0,0 +1 @@ +
example

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/file/basic/internal.html b/packages/core/src/api/exporters/html/__snapshots__/file/basic/internal.html new file mode 100644 index 0000000000..da6935c5ea --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/file/basic/internal.html @@ -0,0 +1 @@ +

example

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/file/button/external.html b/packages/core/src/api/exporters/html/__snapshots__/file/button/external.html new file mode 100644 index 0000000000..cc675c57a7 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/file/button/external.html @@ -0,0 +1 @@ +

Add file

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/file/button/internal.html b/packages/core/src/api/exporters/html/__snapshots__/file/button/internal.html new file mode 100644 index 0000000000..8b639920d1 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/file/button/internal.html @@ -0,0 +1 @@ +

Add file

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/file/nested/external.html b/packages/core/src/api/exporters/html/__snapshots__/file/nested/external.html new file mode 100644 index 0000000000..b108f11ead --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/file/nested/external.html @@ -0,0 +1 @@ +
example

Caption

example

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/file/nested/internal.html b/packages/core/src/api/exporters/html/__snapshots__/file/nested/internal.html new file mode 100644 index 0000000000..1983bdaba2 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/file/nested/internal.html @@ -0,0 +1 @@ +

example

Caption

example

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/file/noCaption/external.html b/packages/core/src/api/exporters/html/__snapshots__/file/noCaption/external.html new file mode 100644 index 0000000000..fdda900023 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/file/noCaption/external.html @@ -0,0 +1 @@ +example \ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/file/noCaption/internal.html b/packages/core/src/api/exporters/html/__snapshots__/file/noCaption/internal.html new file mode 100644 index 0000000000..b51c375d9f --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/file/noCaption/internal.html @@ -0,0 +1 @@ +

example

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/file/noName/external.html b/packages/core/src/api/exporters/html/__snapshots__/file/noName/external.html new file mode 100644 index 0000000000..10ae0e8aac --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/file/noName/external.html @@ -0,0 +1 @@ +
exampleURL

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/file/noName/internal.html b/packages/core/src/api/exporters/html/__snapshots__/file/noName/internal.html new file mode 100644 index 0000000000..1558ee2a2c --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/file/noName/internal.html @@ -0,0 +1 @@ +

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/image/basic/external.html b/packages/core/src/api/exporters/html/__snapshots__/image/basic/external.html index f214a9a441..bdd29c83d4 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/image/basic/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/image/basic/external.html @@ -1 +1 @@ -
Caption
\ No newline at end of file +
example
Caption
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/image/basic/internal.html b/packages/core/src/api/exporters/html/__snapshots__/image/basic/internal.html index 218d0a5897..fbc60f28dd 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/image/basic/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/image/basic/internal.html @@ -1 +1 @@ -
placeholder

\ No newline at end of file +
example

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/image/button/external.html b/packages/core/src/api/exporters/html/__snapshots__/image/button/external.html index de77120ebf..8553433aff 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/image/button/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/image/button/external.html @@ -1 +1 @@ -

Add Image

\ No newline at end of file +

Add image

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/image/button/internal.html b/packages/core/src/api/exporters/html/__snapshots__/image/button/internal.html index d646f2cc67..1af0abaabd 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/image/button/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/image/button/internal.html @@ -1 +1 @@ -

\ No newline at end of file +

Add image

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/image/nested/external.html b/packages/core/src/api/exporters/html/__snapshots__/image/nested/external.html index 1a4a0986a2..e06baeed6d 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/image/nested/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/image/nested/external.html @@ -1 +1 @@ -
Caption
Caption
\ No newline at end of file +
Caption
Caption
Caption
Caption
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/image/nested/internal.html b/packages/core/src/api/exporters/html/__snapshots__/image/nested/internal.html index b03368d31a..dbcc89e0a3 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/image/nested/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/image/nested/internal.html @@ -1 +1 @@ -
placeholder

placeholder

\ No newline at end of file +
Caption

Caption

Caption

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/image/noCaption/external.html b/packages/core/src/api/exporters/html/__snapshots__/image/noCaption/external.html new file mode 100644 index 0000000000..01db39dbf1 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/image/noCaption/external.html @@ -0,0 +1 @@ +example \ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/image/noCaption/internal.html b/packages/core/src/api/exporters/html/__snapshots__/image/noCaption/internal.html new file mode 100644 index 0000000000..3c9df2890b --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/image/noCaption/internal.html @@ -0,0 +1 @@ +
example

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/image/noName/external.html b/packages/core/src/api/exporters/html/__snapshots__/image/noName/external.html new file mode 100644 index 0000000000..8e7317b66d --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/image/noName/external.html @@ -0,0 +1 @@ +
Caption
Caption
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/image/noName/internal.html b/packages/core/src/api/exporters/html/__snapshots__/image/noName/internal.html new file mode 100644 index 0000000000..a26e95ffc5 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/image/noName/internal.html @@ -0,0 +1 @@ +
Caption

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/image/noPreview/external.html b/packages/core/src/api/exporters/html/__snapshots__/image/noPreview/external.html new file mode 100644 index 0000000000..3c59ab678d --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/image/noPreview/external.html @@ -0,0 +1 @@ +
example

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/image/noPreview/internal.html b/packages/core/src/api/exporters/html/__snapshots__/image/noPreview/internal.html new file mode 100644 index 0000000000..b62dc979a5 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/image/noPreview/internal.html @@ -0,0 +1 @@ +

example

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleFile/basic/external.html b/packages/core/src/api/exporters/html/__snapshots__/simpleFile/basic/external.html new file mode 100644 index 0000000000..a7d4b8d146 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleFile/basic/external.html @@ -0,0 +1 @@ +
exampleURL

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleFile/basic/internal.html b/packages/core/src/api/exporters/html/__snapshots__/simpleFile/basic/internal.html new file mode 100644 index 0000000000..7f5bec940c --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleFile/basic/internal.html @@ -0,0 +1 @@ +
exampleURL

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleFile/button/external.html b/packages/core/src/api/exporters/html/__snapshots__/simpleFile/button/external.html new file mode 100644 index 0000000000..26f6f1a3ea --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleFile/button/external.html @@ -0,0 +1 @@ +

Add file

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleFile/button/internal.html b/packages/core/src/api/exporters/html/__snapshots__/simpleFile/button/internal.html new file mode 100644 index 0000000000..3933e1ccf3 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleFile/button/internal.html @@ -0,0 +1 @@ +

Add file

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleFile/nested/external.html b/packages/core/src/api/exporters/html/__snapshots__/simpleFile/nested/external.html new file mode 100644 index 0000000000..e0cffe581c --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleFile/nested/external.html @@ -0,0 +1 @@ +
exampleURL

Caption

exampleURL

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleFile/nested/internal.html b/packages/core/src/api/exporters/html/__snapshots__/simpleFile/nested/internal.html new file mode 100644 index 0000000000..079d157fe0 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleFile/nested/internal.html @@ -0,0 +1 @@ +
exampleURL

Caption

exampleURL

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/basic/external.html b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/basic/external.html index 4619adfa9c..a4050713e3 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/basic/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/basic/external.html @@ -1 +1 @@ -
placeholder

\ No newline at end of file +
example

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/basic/internal.html b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/basic/internal.html index 7c3cd70a32..67dbed1f99 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/basic/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/basic/internal.html @@ -1 +1 @@ -
placeholder

\ No newline at end of file +
example

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/button/external.html b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/button/external.html index f2e492d987..5628e8c3fb 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/button/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/button/external.html @@ -1 +1 @@ -

\ No newline at end of file +

Add image

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/button/internal.html b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/button/internal.html index 375ac4f5b7..89600026f7 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/button/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/button/internal.html @@ -1 +1 @@ -

\ No newline at end of file +

Add image

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/nested/external.html b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/nested/external.html index f8e0c810de..b7888fa509 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/nested/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/nested/external.html @@ -1 +1 @@ -
placeholder

placeholder

\ No newline at end of file +
example

Caption

example

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/nested/internal.html b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/nested/internal.html index c85a76f475..adfb3125bc 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/nested/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/nested/internal.html @@ -1 +1 @@ -
placeholder

placeholder

\ No newline at end of file +
example

Caption

example

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noCaption/external.html b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noCaption/external.html new file mode 100644 index 0000000000..a34250b159 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noCaption/external.html @@ -0,0 +1 @@ +
example

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noCaption/internal.html b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noCaption/internal.html new file mode 100644 index 0000000000..0647411f8c --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noCaption/internal.html @@ -0,0 +1 @@ +
example

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noName/external.html b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noName/external.html new file mode 100644 index 0000000000..834a423aa8 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noName/external.html @@ -0,0 +1 @@ +
Caption

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noName/internal.html b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noName/internal.html new file mode 100644 index 0000000000..9963522a0f --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noName/internal.html @@ -0,0 +1 @@ +
Caption

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noPreview/external.html b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noPreview/external.html new file mode 100644 index 0000000000..6aa85bf79d --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noPreview/external.html @@ -0,0 +1 @@ +

example

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noPreview/internal.html b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noPreview/internal.html new file mode 100644 index 0000000000..f38804d8a9 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleImage/noPreview/internal.html @@ -0,0 +1 @@ +

example

Caption

\ No newline at end of file diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/file/basic/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/file/basic/markdown.md new file mode 100644 index 0000000000..3acd9e1ad0 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/file/basic/markdown.md @@ -0,0 +1,3 @@ +[example](exampleURL) + +Caption diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/file/button/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/file/button/markdown.md new file mode 100644 index 0000000000..8d3fa6a207 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/file/button/markdown.md @@ -0,0 +1 @@ +Add file diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/file/nested/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/file/nested/markdown.md new file mode 100644 index 0000000000..93a61da7b4 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/file/nested/markdown.md @@ -0,0 +1,7 @@ +[example](exampleURL) + +Caption + +[example](exampleURL) + +Caption diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/file/noCaption/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/file/noCaption/markdown.md new file mode 100644 index 0000000000..f525eb2143 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/file/noCaption/markdown.md @@ -0,0 +1 @@ +[example](exampleURL) diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/file/noName/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/file/noName/markdown.md new file mode 100644 index 0000000000..c7fefc547f --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/file/noName/markdown.md @@ -0,0 +1,3 @@ +[exampleURL](exampleURL) + +Caption diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/image/basic/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/image/basic/markdown.md index dda13c76fa..b350ae21e0 100644 --- a/packages/core/src/api/exporters/markdown/__snapshots__/image/basic/markdown.md +++ b/packages/core/src/api/exporters/markdown/__snapshots__/image/basic/markdown.md @@ -1,3 +1,3 @@ -![](exampleURL) +![example](exampleURL) Caption diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/image/button/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/image/button/markdown.md index 4f8610b831..02184caf8a 100644 --- a/packages/core/src/api/exporters/markdown/__snapshots__/image/button/markdown.md +++ b/packages/core/src/api/exporters/markdown/__snapshots__/image/button/markdown.md @@ -1 +1 @@ -Add Image +Add image diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/image/nested/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/image/nested/markdown.md index d2d1ce4de4..7a13551364 100644 --- a/packages/core/src/api/exporters/markdown/__snapshots__/image/nested/markdown.md +++ b/packages/core/src/api/exporters/markdown/__snapshots__/image/nested/markdown.md @@ -1,7 +1,7 @@ -![](exampleURL) +![Caption](exampleURL) Caption -![](exampleURL) +![Caption](exampleURL) Caption diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/image/noCaption/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/image/noCaption/markdown.md new file mode 100644 index 0000000000..2c05062cd4 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/image/noCaption/markdown.md @@ -0,0 +1 @@ +![example](exampleURL) diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/image/noName/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/image/noName/markdown.md new file mode 100644 index 0000000000..c6b5864d90 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/image/noName/markdown.md @@ -0,0 +1,3 @@ +![Caption](exampleURL) + +Caption diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/image/noPreview/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/image/noPreview/markdown.md new file mode 100644 index 0000000000..3acd9e1ad0 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/image/noPreview/markdown.md @@ -0,0 +1,3 @@ +[example](exampleURL) + +Caption diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/simpleFile/basic/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/simpleFile/basic/markdown.md new file mode 100644 index 0000000000..4cca42f87d --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/simpleFile/basic/markdown.md @@ -0,0 +1,3 @@ +exampleURL + +Caption diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/simpleFile/button/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/simpleFile/button/markdown.md new file mode 100644 index 0000000000..8d3fa6a207 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/simpleFile/button/markdown.md @@ -0,0 +1 @@ +Add file diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/simpleFile/nested/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/simpleFile/nested/markdown.md new file mode 100644 index 0000000000..c170d952c5 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/simpleFile/nested/markdown.md @@ -0,0 +1,7 @@ +exampleURL + +Caption + +exampleURL + +Caption diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/basic/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/basic/markdown.md index e90136ab90..b350ae21e0 100644 --- a/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/basic/markdown.md +++ b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/basic/markdown.md @@ -1 +1,3 @@ -![placeholder](exampleURL) +![example](exampleURL) + +Caption diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/button/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/button/markdown.md index e69de29bb2..02184caf8a 100644 --- a/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/button/markdown.md +++ b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/button/markdown.md @@ -0,0 +1 @@ +Add image diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/nested/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/nested/markdown.md index 7d84311ed4..27ac3f5776 100644 --- a/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/nested/markdown.md +++ b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/nested/markdown.md @@ -1,3 +1,7 @@ -![placeholder](exampleURL) +![example](exampleURL) -![placeholder](exampleURL) +Caption + +![example](exampleURL) + +Caption diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/noCaption/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/noCaption/markdown.md new file mode 100644 index 0000000000..2c05062cd4 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/noCaption/markdown.md @@ -0,0 +1 @@ +![example](exampleURL) diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/noName/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/noName/markdown.md new file mode 100644 index 0000000000..c6b5864d90 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/noName/markdown.md @@ -0,0 +1,3 @@ +![Caption](exampleURL) + +Caption diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/noPreview/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/noPreview/markdown.md new file mode 100644 index 0000000000..07b71c0ea6 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/noPreview/markdown.md @@ -0,0 +1,3 @@ +example + +Caption diff --git a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap index fdb3e743a1..d97a75e0db 100644 --- a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +++ b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap @@ -217,6 +217,134 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert } `; +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert file/basic to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "attrs": { + "caption": "Caption", + "name": "example", + "url": "exampleURL", + }, + "type": "file", + }, + ], + "type": "blockContainer", +} +`; + +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert file/button to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "attrs": { + "caption": "", + "name": "", + "url": "", + }, + "type": "file", + }, + ], + "type": "blockContainer", +} +`; + +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert file/nested to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "attrs": { + "caption": "Caption", + "name": "example", + "url": "exampleURL", + }, + "type": "file", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "id": "2", + "textColor": "default", + }, + "content": [ + { + "attrs": { + "caption": "Caption", + "name": "example", + "url": "exampleURL", + }, + "type": "file", + }, + ], + "type": "blockContainer", + }, + ], + "type": "blockGroup", + }, + ], + "type": "blockContainer", +} +`; + +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert file/noCaption to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "attrs": { + "caption": "", + "name": "example", + "url": "exampleURL", + }, + "type": "file", + }, + ], + "type": "blockContainer", +} +`; + +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert file/noName to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "attrs": { + "caption": "Caption", + "name": "", + "url": "exampleURL", + }, + "type": "file", + }, + ], + "type": "blockContainer", +} +`; + exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert hardbreak/basic to/from prosemirror 1`] = ` { "attrs": { @@ -524,9 +652,11 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert { "attrs": { "caption": "Caption", + "name": "example", + "previewWidth": 256, + "showPreview": true, "textAlignment": "left", "url": "exampleURL", - "width": 256, }, "type": "image", }, @@ -546,9 +676,11 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert { "attrs": { "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, "textAlignment": "left", "url": "", - "width": 512, }, "type": "image", }, @@ -568,9 +700,11 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert { "attrs": { "caption": "Caption", + "name": "", + "previewWidth": 256, + "showPreview": true, "textAlignment": "left", "url": "exampleURL", - "width": 256, }, "type": "image", }, @@ -586,9 +720,11 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert { "attrs": { "caption": "Caption", + "name": "", + "previewWidth": 256, + "showPreview": true, "textAlignment": "left", "url": "exampleURL", - "width": 256, }, "type": "image", }, @@ -603,6 +739,78 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert } `; +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert image/noCaption to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "attrs": { + "caption": "", + "name": "example", + "previewWidth": 256, + "showPreview": true, + "textAlignment": "left", + "url": "exampleURL", + }, + "type": "image", + }, + ], + "type": "blockContainer", +} +`; + +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert image/noName to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "attrs": { + "caption": "Caption", + "name": "", + "previewWidth": 256, + "showPreview": true, + "textAlignment": "left", + "url": "exampleURL", + }, + "type": "image", + }, + ], + "type": "blockContainer", +} +`; + +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert image/noPreview to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "attrs": { + "caption": "Caption", + "name": "example", + "previewWidth": 256, + "showPreview": false, + "textAlignment": "left", + "url": "exampleURL", + }, + "type": "image", + }, + ], + "type": "blockContainer", +} +`; + exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert link/adjacent to/from prosemirror 1`] = ` { "attrs": { diff --git a/packages/core/src/api/parsers/html/__snapshots__/paste/parse-basic-block-types.json b/packages/core/src/api/parsers/html/__snapshots__/paste/parse-basic-block-types.json index 2d11e081f6..66c01ba90c 100644 --- a/packages/core/src/api/parsers/html/__snapshots__/paste/parse-basic-block-types.json +++ b/packages/core/src/api/parsers/html/__snapshots__/paste/parse-basic-block-types.json @@ -76,9 +76,11 @@ "props": { "backgroundColor": "default", "textAlignment": "left", + "name": "", "url": "exampleURL", "caption": "Image Caption", - "width": 512 + "showPreview": true, + "previewWidth": 512 }, "children": [] }, diff --git a/packages/core/src/api/parsers/html/__snapshots__/paste/parse-fake-image-caption.json b/packages/core/src/api/parsers/html/__snapshots__/paste/parse-fake-image-caption.json index 86a0cb8168..319d16a003 100644 --- a/packages/core/src/api/parsers/html/__snapshots__/paste/parse-fake-image-caption.json +++ b/packages/core/src/api/parsers/html/__snapshots__/paste/parse-fake-image-caption.json @@ -5,9 +5,11 @@ "props": { "backgroundColor": "default", "textAlignment": "left", + "name": "", "url": "exampleURL", "caption": "", - "width": 512 + "showPreview": true, + "previewWidth": 512 }, "children": [] }, diff --git a/packages/core/src/api/testUtil/cases/customBlocks.ts b/packages/core/src/api/testUtil/cases/customBlocks.ts index bd7e96cce1..6b2d91a7b1 100644 --- a/packages/core/src/api/testUtil/cases/customBlocks.ts +++ b/packages/core/src/api/testUtil/cases/customBlocks.ts @@ -1,10 +1,6 @@ import { EditorTestCases } from "../index"; -import { - imagePropSchema, - renderImage, -} from "../../../blocks/ImageBlockContent/ImageBlockContent"; -import { uploadToTmpFilesDotOrg_DEV_ONLY } from "../../../blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY"; +import { uploadToTmpFilesDotOrg_DEV_ONLY } from "../../../blocks/FileBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY"; import { DefaultInlineContentSchema, DefaultStyleSchema, @@ -13,23 +9,29 @@ import { import { defaultProps } from "../../../blocks/defaultProps"; import { BlockNoteEditor } from "../../../editor/BlockNoteEditor"; import { BlockNoteSchema } from "../../../editor/BlockNoteSchema"; -import { createBlockSpec } from "../../../schema/blocks/createSpec"; +import { createBlockSpec } from "../../../schema"; +import { + imagePropSchema, + imageRender, +} from "../../../blocks/ImageBlockContent/ImageBlockContent"; // This is a modified version of the default image block that does not implement -// a `serialize` function. It's used to test if the custom serializer by default -// serializes custom blocks using their `render` function. +// a `toExternalHTML` function. It's used to test if the custom serializer by +// default serializes custom blocks using their `render` function. const SimpleImage = createBlockSpec( { - type: "simpleImage" as const, + type: "simpleImage", propSchema: imagePropSchema, content: "none", }, - { render: renderImage as any } + { + render: (block, editor) => imageRender(block as any, editor as any), + } ); const CustomParagraph = createBlockSpec( { - type: "customParagraph" as const, + type: "customParagraph", propSchema: defaultProps, content: "inline", }, @@ -57,7 +59,7 @@ const CustomParagraph = createBlockSpec( const SimpleCustomParagraph = createBlockSpec( { - type: "simpleCustomParagraph" as const, + type: "simpleCustomParagraph", propSchema: defaultProps, content: "inline", }, @@ -108,12 +110,54 @@ export const customBlocksTestCases: EditorTestCases< name: "simpleImage/basic", blocks: [ { - type: "simpleImage" as const, + type: "simpleImage", + props: { + name: "example", + url: "exampleURL", + caption: "Caption", + previewWidth: 256, + }, + }, + ], + }, + { + name: "simpleImage/noName", + blocks: [ + { + type: "simpleImage", + props: { + url: "exampleURL", + caption: "Caption", + previewWidth: 256, + }, + }, + ], + }, + { + name: "simpleImage/noCaption", + blocks: [ + { + type: "simpleImage", + props: { + name: "example", + url: "exampleURL", + previewWidth: 256, + }, + }, + ], + }, + { + name: "simpleImage/noPreview", + blocks: [ + { + type: "simpleImage", props: { + name: "example", url: "exampleURL", caption: "Caption", - width: 256, - } as const, + showPreview: false, + previewWidth: 256, + }, }, ], }, @@ -121,20 +165,22 @@ export const customBlocksTestCases: EditorTestCases< name: "simpleImage/nested", blocks: [ { - type: "simpleImage" as const, + type: "simpleImage", props: { + name: "example", url: "exampleURL", caption: "Caption", - width: 256, - } as const, + previewWidth: 256, + }, children: [ { - type: "simpleImage" as const, + type: "simpleImage", props: { + name: "example", url: "exampleURL", caption: "Caption", - width: 256, - } as const, + previewWidth: 256, + }, }, ], }, @@ -144,7 +190,7 @@ export const customBlocksTestCases: EditorTestCases< name: "customParagraph/basic", blocks: [ { - type: "customParagraph" as const, + type: "customParagraph", content: "Custom Paragraph", }, ], @@ -153,12 +199,12 @@ export const customBlocksTestCases: EditorTestCases< name: "customParagraph/styled", blocks: [ { - type: "customParagraph" as const, + type: "customParagraph", props: { textAlignment: "center", textColor: "orange", backgroundColor: "pink", - } as const, + }, content: [ { type: "text", @@ -195,15 +241,15 @@ export const customBlocksTestCases: EditorTestCases< name: "customParagraph/nested", blocks: [ { - type: "customParagraph" as const, + type: "customParagraph", content: "Custom Paragraph", children: [ { - type: "customParagraph" as const, + type: "customParagraph", content: "Nested Custom Paragraph 1", }, { - type: "customParagraph" as const, + type: "customParagraph", content: "Nested Custom Paragraph 2", }, ], @@ -214,7 +260,7 @@ export const customBlocksTestCases: EditorTestCases< name: "simpleCustomParagraph/basic", blocks: [ { - type: "simpleCustomParagraph" as const, + type: "simpleCustomParagraph", content: "Custom Paragraph", }, ], @@ -223,12 +269,12 @@ export const customBlocksTestCases: EditorTestCases< name: "simpleCustomParagraph/styled", blocks: [ { - type: "simpleCustomParagraph" as const, + type: "simpleCustomParagraph", props: { textAlignment: "center", textColor: "orange", backgroundColor: "pink", - } as const, + }, content: [ { type: "text", @@ -265,15 +311,15 @@ export const customBlocksTestCases: EditorTestCases< name: "simpleCustomParagraph/nested", blocks: [ { - type: "simpleCustomParagraph" as const, + type: "simpleCustomParagraph", content: "Custom Paragraph", children: [ { - type: "simpleCustomParagraph" as const, + type: "simpleCustomParagraph", content: "Nested Custom Paragraph 1", }, { - type: "simpleCustomParagraph" as const, + type: "simpleCustomParagraph", content: "Nested Custom Paragraph 2", }, ], diff --git a/packages/core/src/api/testUtil/cases/customInlineContent.ts b/packages/core/src/api/testUtil/cases/customInlineContent.ts index 290edb0b8b..204e63b42d 100644 --- a/packages/core/src/api/testUtil/cases/customInlineContent.ts +++ b/packages/core/src/api/testUtil/cases/customInlineContent.ts @@ -1,6 +1,6 @@ import { EditorTestCases } from "../index"; -import { uploadToTmpFilesDotOrg_DEV_ONLY } from "../../../blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY"; +import { uploadToTmpFilesDotOrg_DEV_ONLY } from "../../../blocks/FileBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY"; import { DefaultBlockSchema, DefaultStyleSchema, diff --git a/packages/core/src/api/testUtil/cases/customStyles.ts b/packages/core/src/api/testUtil/cases/customStyles.ts index de6556deb4..567ca5cf18 100644 --- a/packages/core/src/api/testUtil/cases/customStyles.ts +++ b/packages/core/src/api/testUtil/cases/customStyles.ts @@ -1,6 +1,6 @@ import { EditorTestCases } from "../index"; -import { uploadToTmpFilesDotOrg_DEV_ONLY } from "../../../blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY"; +import { uploadToTmpFilesDotOrg_DEV_ONLY } from "../../../blocks/FileBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY"; import { DefaultBlockSchema, DefaultInlineContentSchema, diff --git a/packages/core/src/api/testUtil/cases/defaultSchema.ts b/packages/core/src/api/testUtil/cases/defaultSchema.ts index 0b9f1f2f5c..70564450a3 100644 --- a/packages/core/src/api/testUtil/cases/defaultSchema.ts +++ b/packages/core/src/api/testUtil/cases/defaultSchema.ts @@ -6,7 +6,7 @@ import { DefaultInlineContentSchema, DefaultStyleSchema, } from "../../../blocks/defaultBlocks"; -import { uploadToTmpFilesDotOrg_DEV_ONLY } from "../../../blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY"; +import { uploadToTmpFilesDotOrg_DEV_ONLY } from "../../../blocks/FileBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY"; export const defaultSchemaTestCases: EditorTestCases< DefaultBlockSchema, @@ -98,6 +98,74 @@ export const defaultSchemaTestCases: EditorTestCases< }, ], }, + { + name: "file/button", + blocks: [ + { + type: "file", + }, + ], + }, + { + name: "file/basic", + blocks: [ + { + type: "file", + props: { + name: "example", + url: "exampleURL", + caption: "Caption", + }, + }, + ], + }, + { + name: "file/noName", + blocks: [ + { + type: "file", + props: { + url: "exampleURL", + caption: "Caption", + }, + }, + ], + }, + { + name: "file/noCaption", + blocks: [ + { + type: "file", + props: { + name: "example", + url: "exampleURL", + }, + }, + ], + }, + { + name: "file/nested", + blocks: [ + { + type: "file", + props: { + name: "example", + url: "exampleURL", + caption: "Caption", + }, + children: [ + { + type: "file", + props: { + name: "example", + url: "exampleURL", + caption: "Caption", + }, + }, + ], + }, + ], + }, { name: "image/button", blocks: [ @@ -112,9 +180,51 @@ export const defaultSchemaTestCases: EditorTestCases< { type: "image", props: { + name: "example", + url: "exampleURL", + caption: "Caption", + previewWidth: 256, + }, + }, + ], + }, + { + name: "image/noName", + blocks: [ + { + type: "image", + props: { + url: "exampleURL", + caption: "Caption", + previewWidth: 256, + }, + }, + ], + }, + { + name: "image/noCaption", + blocks: [ + { + type: "image", + props: { + name: "example", + url: "exampleURL", + previewWidth: 256, + }, + }, + ], + }, + { + name: "image/noPreview", + blocks: [ + { + type: "image", + props: { + name: "example", url: "exampleURL", caption: "Caption", - width: 256, + showPreview: false, + previewWidth: 256, }, }, ], @@ -127,7 +237,7 @@ export const defaultSchemaTestCases: EditorTestCases< props: { url: "exampleURL", caption: "Caption", - width: 256, + previewWidth: 256, }, children: [ { @@ -135,7 +245,7 @@ export const defaultSchemaTestCases: EditorTestCases< props: { url: "exampleURL", caption: "Caption", - width: 256, + previewWidth: 256, }, }, ], diff --git a/packages/core/src/blocks/AudioBlockContent/AudioBlockContent.ts b/packages/core/src/blocks/AudioBlockContent/AudioBlockContent.ts new file mode 100644 index 0000000000..576b17492a --- /dev/null +++ b/packages/core/src/blocks/AudioBlockContent/AudioBlockContent.ts @@ -0,0 +1,162 @@ +import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; +import { + BlockFromConfig, + createBlockSpec, + FileBlockConfig, + Props, + PropSchema, +} from "../../schema"; +import { defaultProps } from "../defaultProps"; + +import { + createAddFileButton, + createDefaultFilePreview, + createFigureWithCaption, + createFileAndCaptionWrapper, + createLinkWithCaption, + parseFigureElement, +} from "../FileBlockContent/fileBlockHelpers"; +import { parseAudioElement } from "./audioBlockHelpers"; + +export const audioPropSchema = { + backgroundColor: defaultProps.backgroundColor, + // File name. + name: { + default: "" as const, + }, + // File url. + url: { + default: "" as const, + }, + // File caption. + caption: { + default: "" as const, + }, + + showPreview: { + default: true, + }, +} satisfies PropSchema; + +export const audioBlockConfig = { + type: "audio" as const, + propSchema: audioPropSchema, + content: "none", + isFileBlock: true, + isFileBlockPlaceholder: (block: any) => !block.props.url, + fileBlockAcceptMimeTypes: ["audio/*"], +} satisfies FileBlockConfig; + +export const audioRender = ( + block: BlockFromConfig, + editor: BlockNoteEditor +) => { + const wrapper = document.createElement("div"); + wrapper.className = "bn-file-block-content-wrapper"; + + if (block.props.url === "") { + const fileBlockAudioIcon = document.createElement("div"); + fileBlockAudioIcon.innerHTML = + ''; + const addAudioButton = createAddFileButton( + block, + editor, + editor.dictionary.file_blocks.audio.add_button_text, + fileBlockAudioIcon.firstElementChild as HTMLElement + ); + wrapper.appendChild(addAudioButton.dom); + + return { + dom: wrapper, + destroy: () => { + addAudioButton?.destroy?.(); + }, + }; + } else if (!block.props.showPreview) { + const file = createDefaultFilePreview(block).dom; + const element = createFileAndCaptionWrapper(block, file); + + return { + dom: element.dom, + }; + } else { + const audio = document.createElement("audio"); + audio.className = "bn-audio"; + audio.src = block.props.url; + audio.controls = true; + audio.contentEditable = "false"; + audio.draggable = false; + + const element = createFileAndCaptionWrapper(block, audio); + wrapper.appendChild(element.dom); + + return { + dom: wrapper, + }; + } +}; + +export const audioParse = ( + element: HTMLElement +): Partial> | undefined => { + if (element.tagName === "AUDIO") { + return parseAudioElement(element as HTMLAudioElement); + } + + if (element.tagName === "FIGURE") { + const parsedFigure = parseFigureElement(element, "audio"); + if (!parsedFigure) { + return undefined; + } + + const { targetElement, caption } = parsedFigure; + + return { + ...parseAudioElement(targetElement as HTMLAudioElement), + caption, + }; + } + + return undefined; +}; + +export const audioToExternalHTML = ( + block: BlockFromConfig +) => { + if (!block.props.url) { + const div = document.createElement("p"); + div.textContent = "Add audio"; + + return { + dom: div, + }; + } + + let audio; + if (block.props.showPreview) { + audio = document.createElement("audio"); + audio.src = block.props.url; + } else { + audio = document.createElement("a"); + audio.href = block.props.url; + audio.textContent = block.props.name || block.props.url; + } + + if (block.props.caption) { + if (block.props.showPreview) { + return createFigureWithCaption(audio, block.props.caption); + } else { + return createLinkWithCaption(audio, block.props.caption); + } + } + + return { + dom: audio, + }; +}; + +export const AudioBlock = createBlockSpec(audioBlockConfig, { + render: audioRender, + parse: audioParse, + toExternalHTML: audioToExternalHTML, +}); diff --git a/packages/core/src/blocks/AudioBlockContent/audioBlockHelpers.ts b/packages/core/src/blocks/AudioBlockContent/audioBlockHelpers.ts new file mode 100644 index 0000000000..2a5bec4403 --- /dev/null +++ b/packages/core/src/blocks/AudioBlockContent/audioBlockHelpers.ts @@ -0,0 +1,5 @@ +export const parseAudioElement = (audioElement: HTMLAudioElement) => { + const url = audioElement.src || undefined; + + return { url }; +}; diff --git a/packages/core/src/blocks/FileBlockContent/FileBlockContent.ts b/packages/core/src/blocks/FileBlockContent/FileBlockContent.ts new file mode 100644 index 0000000000..f569808e24 --- /dev/null +++ b/packages/core/src/blocks/FileBlockContent/FileBlockContent.ts @@ -0,0 +1,121 @@ +import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; +import { + BlockFromConfig, + FileBlockConfig, + PropSchema, + createBlockSpec, +} from "../../schema"; +import { defaultProps } from "../defaultProps"; +import { + createAddFileButton, + createDefaultFilePreview, + createFileAndCaptionWrapper, + parseEmbedElement, + parseFigureElement, + createLinkWithCaption, +} from "./fileBlockHelpers"; + +export const filePropSchema = { + backgroundColor: defaultProps.backgroundColor, + // File name. + name: { + default: "" as const, + }, + // File url. + url: { + default: "" as const, + }, + // File caption. + caption: { + default: "" as const, + }, +} satisfies PropSchema; + +export const fileBlockConfig = { + type: "file" as const, + propSchema: filePropSchema, + content: "none", + isFileBlock: true, + isFileBlockPlaceholder: (block: any) => !block.props.url, +} satisfies FileBlockConfig; + +export const fileRender = ( + block: BlockFromConfig, + editor: BlockNoteEditor +) => { + // Wrapper element to set the file alignment, contains both file/file + // upload dashboard and caption. + const wrapper = document.createElement("div"); + wrapper.className = "bn-file-block-content-wrapper"; + + if (block.props.url === "") { + const addFileButton = createAddFileButton(block, editor); + wrapper.appendChild(addFileButton.dom); + + return { + dom: wrapper, + destroy: addFileButton.destroy, + }; + } else { + const file = createDefaultFilePreview(block).dom; + const element = createFileAndCaptionWrapper(block, file); + wrapper.appendChild(element.dom); + + return { + dom: wrapper, + }; + } +}; + +export const fileParse = (element: HTMLElement) => { + if (element.tagName === "EMBED") { + return parseEmbedElement(element as HTMLEmbedElement); + } + + if (element.tagName === "FIGURE") { + const parsedFigure = parseFigureElement(element, "embed"); + if (!parsedFigure) { + return undefined; + } + + const { targetElement, caption } = parsedFigure; + + return { + ...parseEmbedElement(targetElement as HTMLEmbedElement), + caption, + }; + } + + return undefined; +}; + +export const fileToExternalHTML = ( + block: BlockFromConfig +) => { + if (!block.props.url) { + const div = document.createElement("p"); + div.textContent = "Add file"; + + return { + dom: div, + }; + } + + const fileSrcLink = document.createElement("a"); + fileSrcLink.href = block.props.url; + fileSrcLink.textContent = block.props.name || block.props.url; + + if (block.props.caption) { + return createLinkWithCaption(fileSrcLink, block.props.caption); + } + + return { + dom: fileSrcLink, + }; +}; + +export const FileBlock = createBlockSpec(fileBlockConfig, { + render: fileRender, + parse: fileParse, + toExternalHTML: fileToExternalHTML, +}); diff --git a/packages/core/src/blocks/FileBlockContent/fileBlockHelpers.ts b/packages/core/src/blocks/FileBlockContent/fileBlockHelpers.ts new file mode 100644 index 0000000000..3800418aa1 --- /dev/null +++ b/packages/core/src/blocks/FileBlockContent/fileBlockHelpers.ts @@ -0,0 +1,377 @@ +import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; +import { BlockFromConfig, FileBlockConfig } from "../../schema"; + +// Default file preview, displaying a file icon and file name. +export const createDefaultFilePreview = ( + block: BlockFromConfig +): { dom: HTMLElement; destroy?: () => void } => { + const file = document.createElement("div"); + file.className = "bn-file-default-preview"; + + const icon = document.createElement("div"); + icon.className = "bn-file-default-preview-icon"; + icon.innerHTML = + ''; + + const fileName = document.createElement("p"); + fileName.className = "bn-file-default-preview-name"; + fileName.textContent = block.props.name || ""; + + file.appendChild(icon); + file.appendChild(fileName); + + return { + dom: file, + }; +}; + +// Wrapper element containing file preview and caption. +export const createFileAndCaptionWrapper = ( + block: BlockFromConfig, + file: HTMLElement +) => { + const fileAndCaptionWrapper = document.createElement("div"); + fileAndCaptionWrapper.className = "bn-file-and-caption-wrapper"; + + const caption = document.createElement("p"); + caption.className = "bn-file-caption"; + caption.textContent = block.props.caption; + + fileAndCaptionWrapper.appendChild(file); + fileAndCaptionWrapper.appendChild(caption); + + return { + dom: fileAndCaptionWrapper, + }; +}; + +// Button element that acts as a placeholder for files with no src. +export const createAddFileButton = ( + block: BlockFromConfig, + editor: BlockNoteEditor, + buttonText?: string, + buttonIcon?: HTMLElement +) => { + const addFileButton = document.createElement("div"); + addFileButton.className = "bn-add-file-button"; + + const addFileButtonIcon = document.createElement("div"); + addFileButtonIcon.className = "bn-add-file-button-icon"; + if (buttonIcon) { + addFileButtonIcon.appendChild(buttonIcon); + } else { + addFileButtonIcon.innerHTML = + ''; + } + + const addFileButtonText = document.createElement("p"); + addFileButtonText.className = "bn-add-file-button-text"; + addFileButtonText.innerHTML = + buttonText || editor.dictionary.file_blocks.file.add_button_text; + + // Prevents focus from moving to the button. + const addFileButtonMouseDownHandler = (event: MouseEvent) => { + event.preventDefault(); + }; + // Opens the file toolbar. + const addFileButtonClickHandler = () => { + editor._tiptapEditor.view.dispatch( + editor._tiptapEditor.state.tr.setMeta(editor.filePanel!.plugin, { + block: block, + }) + ); + }; + + addFileButton.appendChild(addFileButtonIcon); + addFileButton.appendChild(addFileButtonText); + + addFileButton.addEventListener( + "mousedown", + addFileButtonMouseDownHandler, + true + ); + addFileButton.addEventListener("click", addFileButtonClickHandler, true); + + return { + dom: addFileButton, + destroy: () => { + addFileButton.removeEventListener( + "mousedown", + addFileButtonMouseDownHandler, + true + ); + addFileButton.removeEventListener( + "click", + addFileButtonClickHandler, + true + ); + }, + }; +}; + +export const parseEmbedElement = (embedElement: HTMLEmbedElement) => { + const url = embedElement.src || undefined; + + return { url }; +}; + +export const parseFigureElement = ( + figureElement: HTMLElement, + targetTag: string +) => { + const targetElement = figureElement.querySelector( + targetTag + ) as HTMLElement | null; + if (!targetElement) { + return undefined; + } + + const captionElement = figureElement.querySelector("figcaption"); + const caption = captionElement?.textContent ?? undefined; + + return { targetElement, caption }; +}; + +// Wrapper figure element to display file link with caption. Used for external +// HTML +export const createLinkWithCaption = ( + element: HTMLElement, + caption: string +) => { + const wrapper = document.createElement("div"); + const fileCaption = document.createElement("p"); + fileCaption.textContent = caption; + + wrapper.appendChild(element); + wrapper.appendChild(fileCaption); + + return { + dom: wrapper, + }; +}; + +// Wrapper figure element to display file preview with caption. Used for +// external HTML. +export const createFigureWithCaption = ( + element: HTMLElement, + caption: string +) => { + const figure = document.createElement("figure"); + const captionElement = document.createElement("figcaption"); + captionElement.textContent = caption; + + figure.appendChild(element); + figure.appendChild(captionElement); + + return { dom: figure }; +}; + +// Wrapper element which adds resize handles & logic for visual media file +// previews. +export const createResizeHandlesWrapper = ( + block: BlockFromConfig, + editor: BlockNoteEditor, + element: HTMLElement, + getWidth: () => number, + setWidth: (width: number) => void +): { dom: HTMLElement; destroy: () => void } => { + if (!block.props.previewWidth) { + throw new Error("Block must have a `previewWidth` prop."); + } + + // Wrapper element for rendered element and resize handles. + const wrapper = document.createElement("div"); + wrapper.className = "bn-visual-media-wrapper"; + + // Resize handle elements. + const leftResizeHandle = document.createElement("div"); + leftResizeHandle.className = "bn-visual-media-resize-handle"; + leftResizeHandle.style.left = "4px"; + const rightResizeHandle = document.createElement("div"); + rightResizeHandle.className = "bn-visual-media-resize-handle"; + rightResizeHandle.style.right = "4px"; + + // Temporary parameters set when the user begins resizing the element, used to + // calculate the new width of the element. + let resizeParams: + | { + handleUsed: "left" | "right"; + initialWidth: number; + initialClientX: number; + } + | undefined; + + // Updates the element width with an updated width depending on the cursor X + // offset from when the resize began, and which resize handle is being used. + const windowMouseMoveHandler = (event: MouseEvent) => { + if (!resizeParams) { + if ( + !editor.isEditable && + wrapper.contains(leftResizeHandle) && + wrapper.contains(rightResizeHandle) + ) { + wrapper.removeChild(leftResizeHandle); + wrapper.removeChild(rightResizeHandle); + } + + return; + } + + let newWidth: number; + + if (block.props.textAlignment === "center") { + if (resizeParams.handleUsed === "left") { + newWidth = + resizeParams.initialWidth + + (resizeParams.initialClientX - event.clientX) * 2; + } else { + newWidth = + resizeParams.initialWidth + + (event.clientX - resizeParams.initialClientX) * 2; + } + } else { + if (resizeParams.handleUsed === "left") { + newWidth = + resizeParams.initialWidth + + resizeParams.initialClientX - + event.clientX; + } else { + newWidth = + resizeParams.initialWidth + + event.clientX - + resizeParams.initialClientX; + } + } + + // Min element width in px. + const minWidth = 64; + + // Ensures the element is not wider than the editor and not smaller than a + // predetermined minimum width. + if (newWidth < minWidth) { + setWidth(minWidth); + } else if (newWidth > editor.domElement.firstElementChild!.clientWidth) { + setWidth(editor.domElement.firstElementChild!.clientWidth); + } else { + setWidth(newWidth); + } + }; + // Stops mouse movements from resizing the element and updates the block's + // `width` prop to the new value. + const windowMouseUpHandler = (event: MouseEvent) => { + // Hides the drag handles if the cursor is no longer over the element. + if ( + (!event.target || + !wrapper.contains(event.target as Node) || + !editor.isEditable) && + wrapper.contains(leftResizeHandle) && + wrapper.contains(rightResizeHandle) + ) { + wrapper.removeChild(leftResizeHandle); + wrapper.removeChild(rightResizeHandle); + } + + if (!resizeParams) { + return; + } + + resizeParams = undefined; + + editor.updateBlock(block, { + props: { + previewWidth: getWidth(), + }, + }); + }; + + // Shows the resize handles when hovering over the element with the cursor. + const elementMouseEnterHandler = () => { + if (editor.isEditable) { + wrapper.appendChild(leftResizeHandle); + wrapper.appendChild(rightResizeHandle); + } + }; + // Hides the resize handles when the cursor leaves the element, unless the + // cursor moves to one of the resize handles. + const elementMouseLeaveHandler = (event: MouseEvent) => { + if ( + event.relatedTarget === leftResizeHandle || + event.relatedTarget === rightResizeHandle + ) { + return; + } + + if (resizeParams) { + return; + } + + if ( + editor.isEditable && + wrapper.contains(leftResizeHandle) && + wrapper.contains(rightResizeHandle) + ) { + wrapper.removeChild(leftResizeHandle); + wrapper.removeChild(rightResizeHandle); + } + }; + + // Sets the resize params, allowing the user to begin resizing the element by + // moving the cursor left or right. + const leftResizeHandleMouseDownHandler = (event: MouseEvent) => { + event.preventDefault(); + + wrapper.appendChild(leftResizeHandle); + wrapper.appendChild(rightResizeHandle); + + resizeParams = { + handleUsed: "left", + initialWidth: block.props.previewWidth!, + initialClientX: event.clientX, + }; + }; + const rightResizeHandleMouseDownHandler = (event: MouseEvent) => { + event.preventDefault(); + + wrapper.appendChild(leftResizeHandle); + wrapper.appendChild(rightResizeHandle); + + resizeParams = { + handleUsed: "right", + initialWidth: block.props.previewWidth!, + initialClientX: event.clientX, + }; + }; + + wrapper.appendChild(element); + + window.addEventListener("mousemove", windowMouseMoveHandler); + window.addEventListener("mouseup", windowMouseUpHandler); + element.addEventListener("mouseenter", elementMouseEnterHandler); + element.addEventListener("mouseleave", elementMouseLeaveHandler); + leftResizeHandle.addEventListener( + "mousedown", + leftResizeHandleMouseDownHandler + ); + rightResizeHandle.addEventListener( + "mousedown", + rightResizeHandleMouseDownHandler + ); + + return { + dom: wrapper, + destroy: () => { + window.removeEventListener("mousemove", windowMouseMoveHandler); + window.removeEventListener("mouseup", windowMouseUpHandler); + element.removeEventListener("mouseenter", elementMouseEnterHandler); + element.removeEventListener("mouseleave", elementMouseLeaveHandler); + leftResizeHandle.removeEventListener( + "mousedown", + leftResizeHandleMouseDownHandler + ); + rightResizeHandle.removeEventListener( + "mousedown", + rightResizeHandleMouseDownHandler + ); + }, + }; +}; diff --git a/packages/core/src/blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts b/packages/core/src/blocks/FileBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts similarity index 100% rename from packages/core/src/blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts rename to packages/core/src/blocks/FileBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts diff --git a/packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts b/packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts index af1e444a99..8d1dfe1f22 100644 --- a/packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts +++ b/packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts @@ -1,404 +1,183 @@ import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; - import { BlockFromConfig, - BlockSchemaWithBlock, - CustomBlockConfig, - InlineContentSchema, - PropSchema, - StyleSchema, createBlockSpec, + FileBlockConfig, + Props, + PropSchema, } from "../../schema"; import { defaultProps } from "../defaultProps"; +import { + createAddFileButton, + createDefaultFilePreview, + createFigureWithCaption, + createFileAndCaptionWrapper, + createLinkWithCaption, + createResizeHandlesWrapper, + parseFigureElement, +} from "../FileBlockContent/fileBlockHelpers"; +import { parseImageElement } from "./imageBlockHelpers"; + export const imagePropSchema = { textAlignment: defaultProps.textAlignment, backgroundColor: defaultProps.backgroundColor, - // Image url. + // File name. + name: { + default: "" as const, + }, + // File url. url: { default: "" as const, }, - // Image caption. + // File caption. caption: { default: "" as const, }, - // Image width in px. - width: { - default: 512 as const, + + showPreview: { + default: true, + }, + // File preview width in px. + previewWidth: { + default: 512, }, } satisfies PropSchema; -// Converts text alignment prop values to the flexbox `align-items` values. -const textAlignmentToAlignItems = ( - textAlignment: "left" | "center" | "right" | "justify" -): "flex-start" | "center" | "flex-end" => { - switch (textAlignment) { - case "left": - return "flex-start"; - case "center": - return "center"; - case "right": - return "flex-end"; - default: - return "flex-start"; - } -}; - -// Min image width in px. -const minWidth = 64; - -const blockConfig = { +export const imageBlockConfig = { type: "image" as const, propSchema: imagePropSchema, content: "none", -} satisfies CustomBlockConfig; - -export const renderImage = ( - block: BlockFromConfig, - editor: BlockNoteEditor> + isFileBlock: true, + isFileBlockPlaceholder: (block: any) => !block.props.url, + fileBlockAcceptMimeTypes: ["image/*"], +} satisfies FileBlockConfig; + +export const imageRender = ( + block: BlockFromConfig, + editor: BlockNoteEditor ) => { - // Wrapper element to set the image alignment, contains both image/image - // upload dashboard and caption. const wrapper = document.createElement("div"); - wrapper.className = "bn-image-block-content-wrapper"; - wrapper.style.alignItems = textAlignmentToAlignItems( - block.props.textAlignment - ); - - // Button element that acts as a placeholder for images with no src. - const addImageButton = document.createElement("div"); - addImageButton.className = "bn-add-image-button"; - - // Icon for the add image button. - const addImageButtonIcon = document.createElement("div"); - addImageButtonIcon.className = "bn-add-image-button-icon"; - - // Text for the add image button. - const addImageButtonText = document.createElement("p"); - addImageButtonText.className = "bn-add-image-button-text"; - addImageButtonText.innerText = editor.dictionary.image.add_button; - - // Wrapper element for the image, resize handles and caption. - const imageAndCaptionWrapper = document.createElement("div"); - imageAndCaptionWrapper.className = "bn-image-and-caption-wrapper"; - - // Wrapper element for the image and resize handles. - const imageWrapper = document.createElement("div"); - imageWrapper.className = "bn-image-wrapper"; - - // Image element. - const image = document.createElement("img"); - image.className = "bn-image"; - image.src = block.props.url; - image.alt = "placeholder"; - image.contentEditable = "false"; - image.draggable = false; - image.style.width = `${Math.min( - block.props.width, - editor.domElement.firstElementChild!.clientWidth - )}px`; - - // Resize handle elements. - const leftResizeHandle = document.createElement("div"); - leftResizeHandle.className = "bn-image-resize-handle"; - leftResizeHandle.style.left = "4px"; - const rightResizeHandle = document.createElement("div"); - rightResizeHandle.className = "bn-image-resize-handle"; - rightResizeHandle.style.right = "4px"; - - // Caption element. - const caption = document.createElement("p"); - caption.className = "bn-image-caption"; - caption.innerText = block.props.caption; - caption.style.padding = block.props.caption ? "4px" : ""; + wrapper.className = "bn-file-block-content-wrapper"; - // Adds a light blue outline to selected image blocks. - const handleEditorUpdate = () => { - const selection = editor.getSelection()?.blocks || []; - const currentBlock = editor.getTextCursorPosition().block; - - const isSelected = - [currentBlock, ...selection].find( - (selectedBlock) => selectedBlock.id === block.id - ) !== undefined; - - if (isSelected) { - addImageButton.style.outline = "4px solid rgb(100, 160, 255)"; - imageAndCaptionWrapper.style.outline = "4px solid rgb(100, 160, 255)"; - } else { - addImageButton.style.outline = ""; - imageAndCaptionWrapper.style.outline = ""; - } - }; - editor.onEditorContentChange(handleEditorUpdate); - editor.onEditorSelectionChange(handleEditorUpdate); - - // Temporary parameters set when the user begins resizing the image, used to - // calculate the new width of the image. - let resizeParams: - | { - handleUsed: "left" | "right"; - initialWidth: number; - initialClientX: number; - } - | undefined; - - // Updates the image width with an updated width depending on the cursor X - // offset from when the resize began, and which resize handle is being used. - const windowMouseMoveHandler = (event: MouseEvent) => { - if (!resizeParams) { - if ( - !editor.isEditable && - imageWrapper.contains(leftResizeHandle) && - imageWrapper.contains(rightResizeHandle) - ) { - imageWrapper.removeChild(leftResizeHandle); - imageWrapper.removeChild(rightResizeHandle); - } - - return; - } - - let newWidth: number; - - if (textAlignmentToAlignItems(block.props.textAlignment) === "center") { - if (resizeParams.handleUsed === "left") { - newWidth = - resizeParams.initialWidth + - (resizeParams.initialClientX - event.clientX) * 2; - } else { - newWidth = - resizeParams.initialWidth + - (event.clientX - resizeParams.initialClientX) * 2; - } - } else { - if (resizeParams.handleUsed === "left") { - newWidth = - resizeParams.initialWidth + - resizeParams.initialClientX - - event.clientX; - } else { - newWidth = - resizeParams.initialWidth + - event.clientX - - resizeParams.initialClientX; - } - } - - // Ensures the image is not wider than the editor and not smaller than a - // predetermined minimum width. - if (newWidth < minWidth) { - image.style.width = `${minWidth}px`; - } else if (newWidth > editor.domElement.firstElementChild!.clientWidth) { - image.style.width = `${ - editor.domElement.firstElementChild!.clientWidth - }px`; - } else { - image.style.width = `${newWidth}px`; - } - }; - // Stops mouse movements from resizing the image and updates the block's - // `width` prop to the new value. - const windowMouseUpHandler = (event: MouseEvent) => { - // Hides the drag handles if the cursor is no longer over the image. - if ( - (!event.target || - !imageWrapper.contains(event.target as Node) || - !editor.isEditable) && - imageWrapper.contains(leftResizeHandle) && - imageWrapper.contains(rightResizeHandle) - ) { - imageWrapper.removeChild(leftResizeHandle); - imageWrapper.removeChild(rightResizeHandle); - } - - if (!resizeParams) { - return; - } - - resizeParams = undefined; + if (block.props.url === "") { + const fileBlockImageIcon = document.createElement("div"); + fileBlockImageIcon.innerHTML = + ''; + const addImageButton = createAddFileButton( + block, + editor, + editor.dictionary.file_blocks.image.add_button_text, + fileBlockImageIcon.firstElementChild as HTMLElement + ); + wrapper.appendChild(addImageButton.dom); - editor.updateBlock(block, { - type: "image", - props: { - // Removes "px" from the end of the width string and converts to float. - width: parseFloat(image.style.width.slice(0, -2)) as any, + return { + dom: wrapper, + destroy: () => { + addImageButton?.destroy?.(); }, - }); - }; + }; + } else if (!block.props.showPreview) { + const file = createDefaultFilePreview(block).dom; + const element = createFileAndCaptionWrapper(block, file); - // Prevents focus from moving to the button. - const addImageButtonMouseDownHandler = (event: MouseEvent) => { - event.preventDefault(); - }; - // Opens the image toolbar. - const addImageButtonClickHandler = () => { - editor._tiptapEditor.view.dispatch( - editor._tiptapEditor.state.tr.setMeta(editor.imagePanel!.plugin, { - block: block, - }) + return { + dom: element.dom, + }; + } else { + const image = document.createElement("img"); + image.className = "bn-visual-media"; + image.src = block.props.url; + image.alt = block.props.name || block.props.caption || "BlockNote image"; + image.contentEditable = "false"; + image.draggable = false; + image.width = Math.min( + block.props.previewWidth, + editor.domElement.firstElementChild!.clientWidth ); - }; - - // Shows the resize handles when hovering over the image with the cursor. - const imageMouseEnterHandler = () => { - if (editor.isEditable) { - imageWrapper.appendChild(leftResizeHandle); - imageWrapper.appendChild(rightResizeHandle); - } - }; - // Hides the resize handles when the cursor leaves the image, unless the - // cursor moves to one of the resize handles. - const imageMouseLeaveHandler = (event: MouseEvent) => { - if ( - event.relatedTarget === leftResizeHandle || - event.relatedTarget === rightResizeHandle - ) { - return; - } - if (resizeParams) { - return; - } + const file = createResizeHandlesWrapper( + block, + editor, + image, + () => image.width, + (width) => (image.width = width) + ); - if ( - editor.isEditable && - imageWrapper.contains(leftResizeHandle) && - imageWrapper.contains(rightResizeHandle) - ) { - imageWrapper.removeChild(leftResizeHandle); - imageWrapper.removeChild(rightResizeHandle); - } - }; + const element = createFileAndCaptionWrapper(block, file.dom); + wrapper.appendChild(element.dom); - // Sets the resize params, allowing the user to begin resizing the image by - // moving the cursor left or right. - const leftResizeHandleMouseDownHandler = (event: MouseEvent) => { - event.preventDefault(); + return { + dom: wrapper, + destroy: file.destroy, + }; + } +}; - imageWrapper.appendChild(leftResizeHandle); - imageWrapper.appendChild(rightResizeHandle); +export const imageParse = ( + element: HTMLElement +): Partial> | undefined => { + if (element.tagName === "IMG") { + return parseImageElement(element as HTMLImageElement); + } - resizeParams = { - handleUsed: "left", - initialWidth: block.props.width, - initialClientX: event.clientX, - }; - }; - const rightResizeHandleMouseDownHandler = (event: MouseEvent) => { - event.preventDefault(); + if (element.tagName === "FIGURE") { + const parsedFigure = parseFigureElement(element, "img"); + if (!parsedFigure) { + return undefined; + } - imageWrapper.appendChild(leftResizeHandle); - imageWrapper.appendChild(rightResizeHandle); + const { targetElement, caption } = parsedFigure; - resizeParams = { - handleUsed: "right", - initialWidth: block.props.width, - initialClientX: event.clientX, + return { + ...parseImageElement(targetElement as HTMLImageElement), + caption, }; - }; + } - addImageButton.appendChild(addImageButtonIcon); - addImageButton.appendChild(addImageButtonText); + return undefined; +}; - imageAndCaptionWrapper.appendChild(imageWrapper); - imageWrapper.appendChild(image); - imageAndCaptionWrapper.appendChild(caption); +export const imageToExternalHTML = ( + block: BlockFromConfig +) => { + if (!block.props.url) { + const div = document.createElement("p"); + div.textContent = "Add image"; - if (block.props.url === "") { - wrapper.appendChild(addImageButton); + return { + dom: div, + }; + } + + let image; + if (block.props.showPreview) { + image = document.createElement("img"); + image.src = block.props.url; + image.alt = block.props.name || block.props.caption || "BlockNote image"; + image.width = block.props.previewWidth; } else { - wrapper.appendChild(imageAndCaptionWrapper); + image = document.createElement("a"); + image.href = block.props.url; + image.textContent = block.props.name || block.props.url; } - window.addEventListener("mousemove", windowMouseMoveHandler); - window.addEventListener("mouseup", windowMouseUpHandler); - addImageButton.addEventListener("mousedown", addImageButtonMouseDownHandler); - addImageButton.addEventListener("click", addImageButtonClickHandler); - image.addEventListener("mouseenter", imageMouseEnterHandler); - image.addEventListener("mouseleave", imageMouseLeaveHandler); - leftResizeHandle.addEventListener( - "mousedown", - leftResizeHandleMouseDownHandler - ); - rightResizeHandle.addEventListener( - "mousedown", - rightResizeHandleMouseDownHandler - ); + if (block.props.caption) { + if (block.props.showPreview) { + return createFigureWithCaption(image, block.props.caption); + } else { + return createLinkWithCaption(image, block.props.caption); + } + } return { - dom: wrapper, - destroy: () => { - window.removeEventListener("mousemove", windowMouseMoveHandler); - window.removeEventListener("mouseup", windowMouseUpHandler); - addImageButton.removeEventListener( - "mousedown", - addImageButtonMouseDownHandler - ); - addImageButton.removeEventListener("click", addImageButtonClickHandler); - leftResizeHandle.removeEventListener( - "mousedown", - leftResizeHandleMouseDownHandler - ); - rightResizeHandle.removeEventListener( - "mousedown", - rightResizeHandleMouseDownHandler - ); - }, + dom: image, }; }; -export const Image = createBlockSpec( - { - type: "image" as const, - propSchema: imagePropSchema, - content: "none", - }, - { - render: renderImage, - toExternalHTML: (block) => { - if (block.props.url === "") { - const div = document.createElement("p"); - div.innerHTML = "Add Image"; - - return { - dom: div, - }; - } - - const figure = document.createElement("figure"); - - const img = document.createElement("img"); - img.src = block.props.url; - figure.appendChild(img); - - if (block.props.caption !== "") { - const figcaption = document.createElement("figcaption"); - figcaption.innerHTML = block.props.caption; - figure.appendChild(figcaption); - } - - return { - dom: figure, - }; - }, - parse: (element: HTMLElement) => { - if (element.tagName === "FIGURE") { - const img = element.querySelector("img"); - const caption = element.querySelector("figcaption"); - return { - url: img?.getAttribute("src") || "", - caption: - caption?.textContent || img?.getAttribute("alt") || undefined, - }; - } else if (element.tagName === "IMG") { - return { - url: element.getAttribute("src") || "", - caption: element.getAttribute("alt") || undefined, - }; - } - - return undefined; - }, - } -); +export const ImageBlock = createBlockSpec(imageBlockConfig, { + render: imageRender, + parse: imageParse, + toExternalHTML: imageToExternalHTML, +}); diff --git a/packages/core/src/blocks/ImageBlockContent/imageBlockHelpers.ts b/packages/core/src/blocks/ImageBlockContent/imageBlockHelpers.ts new file mode 100644 index 0000000000..d225b9daa3 --- /dev/null +++ b/packages/core/src/blocks/ImageBlockContent/imageBlockHelpers.ts @@ -0,0 +1,6 @@ +export const parseImageElement = (imageElement: HTMLImageElement) => { + const url = imageElement.src || undefined; + const previewWidth = imageElement.width || undefined; + + return { url, previewWidth }; +}; diff --git a/packages/core/src/blocks/VideoBlockContent/VideoBlockContent.ts b/packages/core/src/blocks/VideoBlockContent/VideoBlockContent.ts new file mode 100644 index 0000000000..8501e0cd91 --- /dev/null +++ b/packages/core/src/blocks/VideoBlockContent/VideoBlockContent.ts @@ -0,0 +1,182 @@ +import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; +import { + BlockFromConfig, + createBlockSpec, + FileBlockConfig, + Props, + PropSchema, +} from "../../schema"; +import { defaultProps } from "../defaultProps"; + +import { + createAddFileButton, + createDefaultFilePreview, + createFigureWithCaption, + createFileAndCaptionWrapper, + createLinkWithCaption, + createResizeHandlesWrapper, + parseFigureElement, +} from "../FileBlockContent/fileBlockHelpers"; +import { parseVideoElement } from "./videoBlockHelpers"; + +export const videoPropSchema = { + textAlignment: defaultProps.textAlignment, + backgroundColor: defaultProps.backgroundColor, + // File name. + name: { + default: "" as const, + }, + // File url. + url: { + default: "" as const, + }, + // File caption. + caption: { + default: "" as const, + }, + + showPreview: { + default: true, + }, + // File preview width in px. + previewWidth: { + default: 512, + }, +} satisfies PropSchema; + +export const videoBlockConfig = { + type: "video" as const, + propSchema: videoPropSchema, + content: "none", + isFileBlock: true, + isFileBlockPlaceholder: (block: any) => !block.props.url, + fileBlockAcceptMimeTypes: ["video/*"], +} satisfies FileBlockConfig; + +export const videoRender = ( + block: BlockFromConfig, + editor: BlockNoteEditor +) => { + const wrapper = document.createElement("div"); + wrapper.className = "bn-file-block-content-wrapper"; + + if (block.props.url === "") { + const fileBlockVideoIcon = document.createElement("div"); + fileBlockVideoIcon.innerHTML = + ''; + const addVideoButton = createAddFileButton( + block, + editor, + editor.dictionary.file_blocks.video.add_button_text, + fileBlockVideoIcon.firstElementChild as HTMLElement + ); + wrapper.appendChild(addVideoButton.dom); + + return { + dom: wrapper, + destroy: () => { + addVideoButton?.destroy?.(); + }, + }; + } else if (!block.props.showPreview) { + const file = createDefaultFilePreview(block).dom; + const element = createFileAndCaptionWrapper(block, file); + + return { + dom: element.dom, + }; + } else { + const video = document.createElement("video"); + video.className = "bn-visual-media"; + video.src = block.props.url; + video.controls = true; + video.contentEditable = "false"; + video.draggable = false; + video.width = Math.min( + block.props.previewWidth, + editor.domElement.firstElementChild!.clientWidth + ); + + const file = createResizeHandlesWrapper( + block, + editor, + video, + () => video.width, + (width) => (video.width = width) + ); + + const element = createFileAndCaptionWrapper(block, file.dom); + wrapper.appendChild(element.dom); + + return { + dom: wrapper, + destroy: file.destroy, + }; + } +}; + +export const videoParse = ( + element: HTMLElement +): Partial> | undefined => { + if (element.tagName === "VIDEO") { + return parseVideoElement(element as HTMLVideoElement); + } + + if (element.tagName === "FIGURE") { + const parsedFigure = parseFigureElement(element, "video"); + if (!parsedFigure) { + return undefined; + } + + const { targetElement, caption } = parsedFigure; + + return { + ...parseVideoElement(targetElement as HTMLVideoElement), + caption, + }; + } + + return undefined; +}; + +export const videoToExternalHTML = ( + block: BlockFromConfig +) => { + if (!block.props.url) { + const div = document.createElement("p"); + div.textContent = "Add video"; + + return { + dom: div, + }; + } + + let video; + if (block.props.showPreview) { + video = document.createElement("video"); + video.src = block.props.url; + video.width = block.props.previewWidth; + } else { + video = document.createElement("a"); + video.href = block.props.url; + video.textContent = block.props.name || block.props.url; + } + + if (block.props.caption) { + if (block.props.showPreview) { + return createFigureWithCaption(video, block.props.caption); + } else { + return createLinkWithCaption(video, block.props.caption); + } + } + + return { + dom: video, + }; +}; + +export const VideoBlock = createBlockSpec(videoBlockConfig, { + render: videoRender, + parse: videoParse, + toExternalHTML: videoToExternalHTML, +}); diff --git a/packages/core/src/blocks/VideoBlockContent/videoBlockHelpers.ts b/packages/core/src/blocks/VideoBlockContent/videoBlockHelpers.ts new file mode 100644 index 0000000000..4b11481d48 --- /dev/null +++ b/packages/core/src/blocks/VideoBlockContent/videoBlockHelpers.ts @@ -0,0 +1,6 @@ +export const parseVideoElement = (videoElement: HTMLVideoElement) => { + const url = videoElement.src || undefined; + const previewWidth = videoElement.width || undefined; + + return { url, previewWidth }; +}; diff --git a/packages/core/src/blocks/defaultBlockTypeGuards.ts b/packages/core/src/blocks/defaultBlockTypeGuards.ts index 175aabca2b..c753effce6 100644 --- a/packages/core/src/blocks/defaultBlockTypeGuards.ts +++ b/packages/core/src/blocks/defaultBlockTypeGuards.ts @@ -1,5 +1,11 @@ import type { BlockNoteEditor } from "../editor/BlockNoteEditor"; -import { BlockFromConfig, InlineContentSchema, StyleSchema } from "../schema"; +import { + BlockFromConfig, + BlockSchema, + FileBlockConfig, + InlineContentSchema, + StyleSchema, +} from "../schema"; import { Block, DefaultBlockSchema, defaultBlockSchema } from "./defaultBlocks"; import { defaultProps } from "./defaultProps"; @@ -33,6 +39,52 @@ export function checkBlockIsDefaultType< ); } +export function checkBlockIsFileBlock< + B extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + block: Block, + editor: BlockNoteEditor +): block is BlockFromConfig { + return ( + (block.type in editor.schema.blockSchema && + editor.schema.blockSchema[block.type].isFileBlock) || + false + ); +} + +export function checkBlockIsFileBlockWithPreview< + B extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + block: Block, + editor: BlockNoteEditor +): block is BlockFromConfig< + FileBlockConfig & { + propSchema: Required; + }, + I, + S +> { + return ( + (block.type in editor.schema.blockSchema && + editor.schema.blockSchema[block.type].isFileBlock && + "showPreview" in editor.schema.blockSchema[block.type].propSchema) || + false + ); +} + +export function checkBlockIsFileBlockWithPlaceholder< + B extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>(block: Block, editor: BlockNoteEditor) { + const config = editor.schema.blockSchema[block.type]; + return config.isFileBlock && config.isFileBlockPlaceholder(block); +} + export function checkBlockTypeHasDefaultProp< Prop extends keyof typeof defaultProps, I extends InlineContentSchema, diff --git a/packages/core/src/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts index a6fbb88316..8bde3a6077 100644 --- a/packages/core/src/blocks/defaultBlocks.ts +++ b/packages/core/src/blocks/defaultBlocks.ts @@ -19,19 +19,25 @@ import { getInlineContentSchemaFromSpecs, getStyleSchemaFromSpecs, } from "../schema"; +import { FileBlock } from "./FileBlockContent/FileBlockContent"; +import { ImageBlock } from "./ImageBlockContent/ImageBlockContent"; import { Heading } from "./HeadingBlockContent/HeadingBlockContent"; -import { Image } from "./ImageBlockContent/ImageBlockContent"; import { BulletListItem } from "./ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent"; import { NumberedListItem } from "./ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent"; import { Paragraph } from "./ParagraphBlockContent/ParagraphBlockContent"; import { Table } from "./TableBlockContent/TableBlockContent"; +import { VideoBlock } from "./VideoBlockContent/VideoBlockContent"; +import { AudioBlock } from "./AudioBlockContent/AudioBlockContent"; export const defaultBlockSpecs = { paragraph: Paragraph, heading: Heading, bulletListItem: BulletListItem, numberedListItem: NumberedListItem, - image: Image, + file: FileBlock, + image: ImageBlock, + video: VideoBlock, + audio: AudioBlock, table: Table, } satisfies BlockSpecs; diff --git a/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css index 85d61fadd4..b8ee3d578d 100644 --- a/packages/core/src/editor/Block.css +++ b/packages/core/src/editor/Block.css @@ -13,15 +13,9 @@ BASIC STYLES flex-direction: column; } -/*Ensures block content inside React node views spans editor width*/ -.bn-react-node-view-renderer { - display: flex; - flex-grow: 1; -} - .bn-block-content { + display: flex; padding: 3px 0; - flex-grow: 1; transition: font-size 0.2s; width: 100%; /* @@ -36,6 +30,13 @@ BASIC STYLES /*margin: 0px;*/ } +.bn-block-content.ProseMirror-selectednode > *, +/* Case for node view renderers */ +.ProseMirror-selectednode > .bn-block-content > * { + border-radius: 4px; + outline: 4px solid rgb(100, 160, 255); +} + /* NESTED BLOCKS */ @@ -235,49 +236,75 @@ NESTED BLOCKS content: "▪"; } -/* IMAGES */ +/* FILES */ + +/* Add block button & default preview */ +[data-file-block] .bn-file-block-content-wrapper:has(.bn-add-file-button), +[data-file-block] .bn-file-block-content-wrapper:has(.bn-file-default-preview) { + width: 100%; +} -[data-content-type="image"] .bn-image-block-content-wrapper { +[data-file-block] .bn-file-block-content-wrapper { + cursor: pointer; display: flex; flex-direction: column; - justify-content: center; + justify-content: stretch; user-select: none; - width: 100%; } -[data-content-type="image"] .bn-add-image-button { - display: flex; - flex-direction: row; +[data-file-block] .bn-add-file-button { align-items: center; - gap: 8px; - background-color: whitesmoke; + background-color: rgb(242, 241, 238); border-radius: 4px; + color: rgb(125, 121, 122); cursor: pointer; + display: flex; + flex-direction: row; + gap: 10px; padding: 12px; width: 100%; } -[data-content-type="image"] .bn-add-image-button:hover { - background-color: gainsboro; +[data-file-block] .bn-add-file-button:hover { + background-color: rgb(225, 225, 225); } -[data-content-type="image"] .bn-add-image-button-icon { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M20 5H4V19L13.2923 9.70649C13.6828 9.31595 14.3159 9.31591 14.7065 9.70641L20 15.0104V5ZM2 3.9934C2 3.44476 2.45531 3 2.9918 3H21.0082C21.556 3 22 3.44495 22 3.9934V20.0066C22 20.5552 21.5447 21 21.0082 21H2.9918C2.44405 21 2 20.5551 2 20.0066V3.9934ZM8 11C6.89543 11 6 10.1046 6 9C6 7.89543 6.89543 7 8 7C9.10457 7 10 7.89543 10 9C10 10.1046 9.10457 11 8 11Z'%3E%3C/path%3E%3C/svg%3E"); +[data-file-block] .bn-add-file-button-icon { width: 24px; height: 24px; } -[data-content-type="image"] .bn-add-image-button-text { - color: black; +[data-file-block] .bn-add-file-button .bn-add-file-button-text { + font-size: 0.9rem; } -[data-content-type="image"] .bn-image-and-caption-wrapper { +[data-file-block] .bn-file-and-caption-wrapper { display: flex; flex-direction: column; border-radius: 4px; } -[data-content-type="image"] .bn-image-wrapper { +[data-file-block] .bn-file-default-preview { + align-items: center; + border-radius: 4px; + display: flex; + flex-direction: row; + gap: 4px; + padding: 4px; + width: 100%; +} + +[data-file-block] .bn-file-default-preview:hover, +.ProseMirror-selectednode .bn-file-default-preview { + background-color: rgb(225, 225, 225); +} + +[data-file-block] .bn-file-default-preview-icon { + width: 24px; + height: 24px; +} + +[data-file-block] .bn-visual-media-wrapper { display: flex; flex-direction: row; align-items: center; @@ -285,12 +312,12 @@ NESTED BLOCKS width: fit-content; } -[data-content-type="image"] .bn-image { +[data-file-block] .bn-visual-media { border-radius: 4px; max-width: 100%; } -[data-content-type="image"] .bn-image-resize-handle { +[data-file-block] .bn-visual-media-resize-handle { position: absolute; width: 8px; height: 30px; @@ -300,8 +327,17 @@ NESTED BLOCKS cursor: ew-resize; } -[data-content-type="image"] .caption { +[data-content-type="audio"] > .bn-file-block-content-wrapper, .bn-audio { + width: 100%; +} + +[data-file-block] .bn-file-caption { font-size: 0.8em; + padding-block: 4px; +} + +[data-file-block] .bn-file-caption:empty { + padding-block: 0; } /* PLACEHOLDERS*/ @@ -392,17 +428,21 @@ NESTED BLOCKS /* TEXT ALIGNMENT */ [data-text-alignment="left"] { + justify-content: flex-start; text-align: left; } [data-text-alignment="center"] { + justify-content: center; text-align: center; } [data-text-alignment="right"] { + justify-content: flex-end; text-align: right; } [data-text-alignment="justify"] { + justify-content: flex-start; text-align: justify; } diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 7e2396040d..78d7e23df7 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -26,8 +26,8 @@ import { DefaultStyleSchema, PartialBlock, } from "../blocks/defaultBlocks"; +import { FilePanelProsemirrorPlugin } from "../extensions/FilePanel/FilePanelPlugin"; import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin"; -import { ImagePanelProsemirrorPlugin } from "../extensions/ImagePanel/ImageToolbarPlugin"; import { LinkToolbarProsemirrorPlugin } from "../extensions/LinkToolbar/LinkToolbarPlugin"; import { SideMenuProsemirrorPlugin } from "../extensions/SideMenu/SideMenuPlugin"; import { SuggestionMenuProseMirrorPlugin } from "../extensions/SuggestionMenu/SuggestionPlugin"; @@ -113,9 +113,9 @@ export type BlockNoteEditorOptions< /** * A custom function to handle file uploads. * @param file The file that should be uploaded. - * @returns The URL of the uploaded file. + * @returns The URL of the uploaded file OR an object containing props that should be set on the file block (such as an id) */ - uploadFile: (file: File) => Promise; + uploadFile: (file: File) => Promise>; /** * When enabled, allows for collaboration between multiple users. @@ -187,13 +187,19 @@ export class BlockNoteEditor< ISchema, SSchema >; - public readonly imagePanel?: ImagePanelProsemirrorPlugin; + public readonly filePanel?: FilePanelProsemirrorPlugin< + BSchema, + ISchema, + SSchema + >; public readonly tableHandles?: TableHandlesProsemirrorPlugin< ISchema, SSchema >; - public readonly uploadFile: ((file: File) => Promise) | undefined; + public readonly uploadFile: + | ((file: File) => Promise>) + | undefined; public static create< BSchema extends BlockSchema = DefaultBlockSchema, @@ -254,10 +260,8 @@ export class BlockNoteEditor< this.linkToolbar = new LinkToolbarProsemirrorPlugin(this); this.sideMenu = new SideMenuProsemirrorPlugin(this); this.suggestionMenus = new SuggestionMenuProseMirrorPlugin(this); - if (checkDefaultBlockTypeInSchema("image", this)) { - // Type guards only work on `const`s? Not working for `this` - this.imagePanel = new ImagePanelProsemirrorPlugin(this as any); - } + this.filePanel = new FilePanelProsemirrorPlugin(this as any); + if (checkDefaultBlockTypeInSchema("table", this)) { this.tableHandles = new TableHandlesProsemirrorPlugin(this as any); } @@ -282,7 +286,7 @@ export class BlockNoteEditor< this.linkToolbar.plugin, this.sideMenu.plugin, this.suggestionMenus.plugin, - ...(this.imagePanel ? [this.imagePanel.plugin] : []), + ...(this.filePanel ? [this.filePanel.plugin] : []), ...(this.tableHandles ? [this.tableHandles.plugin] : []), PlaceholderPlugin(this, newOptions.placeholders), ]; diff --git a/packages/core/src/editor/BlockNoteSchema.ts b/packages/core/src/editor/BlockNoteSchema.ts index 90ebd3748d..8f420c9e3f 100644 --- a/packages/core/src/editor/BlockNoteSchema.ts +++ b/packages/core/src/editor/BlockNoteSchema.ts @@ -23,6 +23,15 @@ import type { } from "../schema/blocks/types"; import type { BlockNoteEditor } from "./BlockNoteEditor"; +function removeUndefined | undefined>(obj: T): T { + if (!obj) { + return obj; + } + return Object.fromEntries( + Object.entries(obj).filter(([, value]) => value !== undefined) + ) as T; +} + export class BlockNoteSchema< BSchema extends BlockSchema, ISchema extends InlineContentSchema, @@ -84,10 +93,10 @@ export class BlockNoteSchema< inlineContentSpecs?: InlineContentSpecs; styleSpecs?: StyleSpecs; }) { - this.blockSpecs = opts?.blockSpecs || defaultBlockSpecs; + this.blockSpecs = removeUndefined(opts?.blockSpecs) || defaultBlockSpecs; this.inlineContentSpecs = - opts?.inlineContentSpecs || defaultInlineContentSpecs; - this.styleSpecs = opts?.styleSpecs || defaultStyleSpecs; + removeUndefined(opts?.inlineContentSpecs) || defaultInlineContentSpecs; + this.styleSpecs = removeUndefined(opts?.styleSpecs) || defaultStyleSpecs; this.blockSchema = getBlockSchemaFromSpecs(this.blockSpecs) as any; this.inlineContentSchema = getInlineContentSchemaFromSpecs( diff --git a/packages/core/src/extensions/ImagePanel/ImageToolbarPlugin.ts b/packages/core/src/extensions/FilePanel/FilePanelPlugin.ts similarity index 76% rename from packages/core/src/extensions/ImagePanel/ImageToolbarPlugin.ts rename to packages/core/src/extensions/FilePanel/FilePanelPlugin.ts index d83c7879f3..35a4c1d640 100644 --- a/packages/core/src/extensions/ImagePanel/ImageToolbarPlugin.ts +++ b/packages/core/src/extensions/FilePanel/FilePanelPlugin.ts @@ -1,30 +1,29 @@ import { EditorState, Plugin, PluginKey, PluginView } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; -import { DefaultBlockSchema } from "../../blocks/defaultBlocks"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; import { UiElementPosition } from "../../extensions-shared/UiElementPosition"; import type { BlockFromConfig, + BlockSchema, + FileBlockConfig, InlineContentSchema, StyleSchema, } from "../../schema"; import { EventEmitter } from "../../util/EventEmitter"; -export type ImagePanelState< +export type FilePanelState< I extends InlineContentSchema, S extends StyleSchema > = UiElementPosition & { // TODO: This typing is not quite right (children should be from BSchema) - block: BlockFromConfig; + block: BlockFromConfig; }; -export class ImagePanelView< - I extends InlineContentSchema, - S extends StyleSchema -> implements PluginView +export class FilePanelView + implements PluginView { - public state?: ImagePanelState; + public state?: FilePanelState; public emitUpdate: () => void; public prevWasEditable: boolean | null = null; @@ -32,11 +31,11 @@ export class ImagePanelView< constructor( private readonly pluginKey: PluginKey, private readonly pmView: EditorView, - emitUpdate: (state: ImagePanelState) => void + emitUpdate: (state: FilePanelState) => void ) { this.emitUpdate = () => { if (!this.state) { - throw new Error("Attempting to update uninitialized image panel"); + throw new Error("Attempting to update uninitialized file panel"); } emitUpdate(this.state); @@ -77,7 +76,7 @@ export class ImagePanelView< update(view: EditorView, prevState: EditorState) { const pluginState: { - block: BlockFromConfig; + block: BlockFromConfig; } = this.pluginKey.getState(view.state); if (!this.state?.show && pluginState.block) { @@ -124,27 +123,26 @@ export class ImagePanelView< } } -const imagePanelPluginKey = new PluginKey("ImagePanelPlugin"); +const filePanelPluginKey = new PluginKey("FilePanelPlugin"); -export class ImagePanelProsemirrorPlugin< +export class FilePanelProsemirrorPlugin< + B extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema > extends EventEmitter { - private view: ImagePanelView | undefined; + private view: FilePanelView | undefined; public readonly plugin: Plugin; - constructor( - _editor: BlockNoteEditor<{ image: DefaultBlockSchema["image"] }, I, S> - ) { + constructor(_editor: BlockNoteEditor) { super(); this.plugin = new Plugin<{ - block: BlockFromConfig | undefined; + block: BlockFromConfig | undefined; }>({ - key: imagePanelPluginKey, + key: filePanelPluginKey, view: (editorView) => { - this.view = new ImagePanelView( + this.view = new FilePanelView( // editor, - imagePanelPluginKey, + filePanelPluginKey, editorView, (state) => { this.emit("update", state); @@ -168,9 +166,8 @@ export class ImagePanelProsemirrorPlugin< }; }, apply: (transaction) => { - const block: - | BlockFromConfig - | undefined = transaction.getMeta(imagePanelPluginKey)?.block; + const block: BlockFromConfig | undefined = + transaction.getMeta(filePanelPluginKey)?.block; return { block, @@ -184,7 +181,7 @@ export class ImagePanelProsemirrorPlugin< return this.view?.state?.show || false; } - public onUpdate(callback: (state: ImagePanelState) => void) { + public onUpdate(callback: (state: FilePanelState) => void) { return this.on("update", callback); } diff --git a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts index 65836ec1f9..eb41dddcdd 100644 --- a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +++ b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts @@ -188,9 +188,9 @@ export function getDefaultSlashMenuItems< type: "image", }); - // Immediately open the image toolbar + // Immediately open the file toolbar editor.prosemirrorView.dispatch( - editor._tiptapEditor.state.tr.setMeta(editor.imagePanel!.plugin, { + editor._tiptapEditor.state.tr.setMeta(editor.filePanel!.plugin, { block: insertedBlock, }) ); @@ -200,6 +200,63 @@ export function getDefaultSlashMenuItems< }); } + if (checkDefaultBlockTypeInSchema("video", editor)) { + items.push({ + onItemClick: () => { + const insertedBlock = insertOrUpdateBlock(editor, { + type: "video", + }); + + // Immediately open the file toolbar + editor.prosemirrorView.dispatch( + editor._tiptapEditor.state.tr.setMeta(editor.filePanel!.plugin, { + block: insertedBlock, + }) + ); + }, + key: "video", + ...editor.dictionary.slash_menu.video, + }); + } + + if (checkDefaultBlockTypeInSchema("audio", editor)) { + items.push({ + onItemClick: () => { + const insertedBlock = insertOrUpdateBlock(editor, { + type: "audio", + }); + + // Immediately open the file toolbar + editor.prosemirrorView.dispatch( + editor._tiptapEditor.state.tr.setMeta(editor.filePanel!.plugin, { + block: insertedBlock, + }) + ); + }, + key: "audio", + ...editor.dictionary.slash_menu.audio, + }); + } + + if (checkDefaultBlockTypeInSchema("file", editor)) { + items.push({ + onItemClick: () => { + const insertedBlock = insertOrUpdateBlock(editor, { + type: "file", + }); + + // Immediately open the file toolbar + editor.prosemirrorView.dispatch( + editor._tiptapEditor.state.tr.setMeta(editor.filePanel!.plugin, { + block: insertedBlock, + }) + ); + }, + key: "image", + ...editor.dictionary.slash_menu.file, + }); + } + return items; } diff --git a/packages/core/src/i18n/locales/en.ts b/packages/core/src/i18n/locales/en.ts index f6b68f07ad..4613fb7081 100644 --- a/packages/core/src/i18n/locales/en.ts +++ b/packages/core/src/i18n/locales/en.ts @@ -53,11 +53,43 @@ export const en = { "picture", "media", "url", - "drive", - "dropbox", ], group: "Media", }, + video: { + title: "Video", + subtext: "Insert a video", + aliases: [ + "video", + "videoUpload", + "upload", + "mp4", + "film", + "media", + "url", + ], + group: "Media", + }, + audio: { + title: "Audio", + subtext: "Insert audio", + aliases: [ + "audio", + "audioUpload", + "upload", + "mp3", + "sound", + "media", + "url", + ], + group: "Media", + }, + file: { + title: "File", + subtext: "Insert a file", + aliases: ["file", "upload", "embed", "media", "url"], + group: "Media", + }, }, placeholders: { default: "Enter text or type '/' for commands", @@ -65,8 +97,19 @@ export const en = { bulletListItem: "List", numberedListItem: "List", }, - image: { - add_button: "Add Image", + file_blocks: { + image: { + add_button_text: "Add image", + }, + video: { + add_button_text: "Add video", + }, + audio: { + add_button_text: "Add audio", + }, + file: { + add_button_text: "Add file", + }, }, // from react package: side_menu: { @@ -134,12 +177,50 @@ export const en = { tooltip: "Create link", secondary_tooltip: "Mod+K", }, - image_caption: { + file_caption: { tooltip: "Edit caption", input_placeholder: "Edit caption", }, - image_replace: { - tooltip: "Replace image", + file_replace: { + tooltip: { + image: "Replace image", + video: "Replace video", + audio: "Replace audio", + file: "Replace file", + } as Record, + }, + file_rename: { + tooltip: { + image: "Rename image", + video: "Rename video", + audio: "Rename audio", + file: "Rename file", + } as Record, + input_placeholder: { + image: "Rename image", + video: "Rename video", + audio: "Rename audio", + file: "Rename file", + } as Record, + }, + file_download: { + tooltip: { + image: "Download image", + video: "Download video", + audio: "Download audio", + file: "Download file", + } as Record, + }, + file_delete: { + tooltip: { + image: "Delete image", + video: "Delete video", + audio: "Delete audio", + file: "Delete file", + } as Record, + }, + file_preview_toggle: { + tooltip: "Toggle preview", }, nest: { tooltip: "Nest block", @@ -162,15 +243,25 @@ export const en = { tooltip: "Justify text", }, }, - image_panel: { + file_panel: { upload: { title: "Upload", - file_placeholder: "Upload image", + file_placeholder: { + image: "Upload image", + video: "Upload video", + audio: "Upload audio", + file: "Upload file", + } as Record, upload_error: "Error: Upload failed", }, embed: { title: "Embed", - embed_button: "Embed image", + embed_button: { + image: "Embed image", + video: "Embed video", + audio: "Embed audio", + file: "Embed file", + } as Record, url_placeholder: "Enter URL", }, }, diff --git a/packages/core/src/i18n/locales/fr.ts b/packages/core/src/i18n/locales/fr.ts index b220c0c051..57a31e95ed 100644 --- a/packages/core/src/i18n/locales/fr.ts +++ b/packages/core/src/i18n/locales/fr.ts @@ -1,4 +1,6 @@ -export const fr = { +import { Dictionary } from "../dictionary"; + +export const fr: Dictionary = { slash_menu: { heading: { title: "Titre 1", @@ -53,11 +55,43 @@ export const fr = { "photo", "média", "url", - "drive", - "dropbox", ], group: "Médias", }, + video: { + title: "Vidéo", + subtext: "Insérer une vidéo", + aliases: [ + "vidéo", + "téléchargerVidéo", + "téléverser", + "mp4", + "film", + "média", + "url", + ], + group: "Média", + }, + audio: { + title: "Audio", + subtext: "Insérer un audio", + aliases: [ + "audio", + "téléchargerAudio", + "téléverser", + "mp3", + "son", + "média", + "url", + ], + group: "Média", + }, + file: { + title: "Fichier", + subtext: "Insérer un fichier", + aliases: ["fichier", "téléverser", "intégrer", "média", "url"], + group: "Média", + }, }, placeholders: { default: "Entrez du texte ou tapez '/' pour les commandes", @@ -65,8 +99,19 @@ export const fr = { bulletListItem: "Liste", numberedListItem: "Liste", }, - image: { - add_button: "Ajouter une Image", + file_blocks: { + image: { + add_button_text: "Ajouter une image", + }, + video: { + add_button_text: "Ajouter une vidéo", + }, + audio: { + add_button_text: "Ajouter un audio", + }, + file: { + add_button_text: "Ajouter un fichier", + }, }, // from react package: side_menu: { @@ -134,12 +179,50 @@ export const fr = { tooltip: "Créer un lien", secondary_tooltip: "Mod+K", }, - image_caption: { + file_caption: { tooltip: "Modifier la légende", input_placeholder: "Modifier la légende", }, - image_replace: { - tooltip: "Remplacer l'image", + file_replace: { + tooltip: { + image: "Remplacer l'image", + video: "Remplacer la vidéo", + audio: "Remplacer l'audio", + file: "Remplacer le fichier", + }, + }, + file_rename: { + tooltip: { + image: "Renommer l'image", + video: "Renommer la vidéo", + audio: "Renommer l'audio", + file: "Renommer le fichier", + }, + input_placeholder: { + image: "Renommer l'image", + video: "Renommer la vidéo", + audio: "Renommer l'audio", + file: "Renommer le fichier", + }, + }, + file_download: { + tooltip: { + image: "Télécharger l'image", + video: "Télécharger la vidéo", + audio: "Télécharger l'audio", + file: "Télécharger le fichier", + }, + }, + file_delete: { + tooltip: { + image: "Supprimer l'image", + video: "Supprimer la vidéo", + audio: "Supprimer l'audio", + file: "Supprimer le fichier", + }, + }, + file_preview_toggle: { + tooltip: "Basculer l'aperçu", }, nest: { tooltip: "Emboîter le bloc", @@ -162,15 +245,25 @@ export const fr = { tooltip: "Justifier le texte", }, }, - image_panel: { + file_panel: { upload: { title: "Télécharger", - file_placeholder: "Télécharger l'image", + file_placeholder: { + image: "Télécharger une image", + video: "Télécharger une vidéo", + audio: "Télécharger un fichier audio", + file: "Télécharger un fichier", + }, upload_error: "Erreur : Échec du téléchargement", }, embed: { title: "Intégrer", - embed_button: "Intégrer l'image", + embed_button: { + image: "Intégrer une image", + video: "Intégrer une vidéo", + audio: "Intégrer un fichier audio", + file: "Intégrer un fichier", + }, url_placeholder: "Entrez l'URL", }, }, diff --git a/packages/core/src/i18n/locales/is.ts b/packages/core/src/i18n/locales/is.ts index ea33cad57e..f61198b6f1 100644 --- a/packages/core/src/i18n/locales/is.ts +++ b/packages/core/src/i18n/locales/is.ts @@ -1,197 +1,288 @@ -import type { Dictionary } from "../dictionary"; - -export const is: Dictionary = { - slash_menu: { - heading: { - title: "Fyrirsögn 1", - subtext: "Notað fyrir efstu fyrirsögn", - aliases: ["h", "fyrirsogn1", "h1"], - group: "Fyrirsagnir", - }, - heading_2: { - title: "Fyrirsögn 2", - subtext: "Notað fyrir lykilhluta", - aliases: ["h2", "fyrirsogn2", "undirfyrirsogn"], - group: "Fyrirsagnir", - }, - heading_3: { - title: "Fyrirsögn 3", - subtext: "Notað fyrir undirhluta og hópfyrirsagnir", - aliases: ["h3", "fyrirsogn3", "undirfyrirsogn"], - group: "Fyrirsagnir", - }, - numbered_list: { - title: "Númeruð listi", - subtext: "Notað til að birta númeraðan lista", - aliases: ["ol", "li", "listi", "numeradurlisti"], - group: "Grunnblokkar", - }, - bullet_list: { - title: "Punktalisti", - subtext: "Notað til að birta óraðaðan lista", - aliases: ["ul", "li", "listi", "punktalisti"], - group: "Grunnblokkar", - }, - paragraph: { - title: "Málsgrein", - subtext: "Notað fyrir meginmál skjalsins", - aliases: ["p", "malsgrein"], - group: "Grunnblokkar", - }, - table: { - title: "Tafla", - subtext: "Notað fyrir töflur", - aliases: ["tafla"], - group: "Ítarlegt", - }, - image: { - title: "Mynd", - subtext: "Settu inn mynd", - aliases: [ - "mynd", - "myndaupphlaed", - "upphlaed", - "img", - "mynd", - "media", - "url", - "drive", - "dropbox", - ], - group: "Miðlar", - }, - }, - placeholders: { - default: "Sláðu inn texta eða skrifaðu '/' fyrir skipanir", - heading: "Fyrirsögn", - bulletListItem: "Listi", - numberedListItem: "Listi", - }, - image: { - add_button: "Bæta við mynd", - }, - side_menu: { - add_block_label: "Bæta við blokki", - drag_handle_label: "Opna blokkarvalmynd", - }, - drag_handle: { - delete_menuitem: "Eyða", - colors_menuitem: "Litir", - }, - table_handle: { - delete_column_menuitem: "Eyða dálki", - delete_row_menuitem: "Eyða röð", - add_left_menuitem: "Bæta dálki við til vinstri", - add_right_menuitem: "Bæta dálki við til hægri", - add_above_menuitem: "Bæta röð við fyrir ofan", - add_below_menuitem: "Bæta röð við fyrir neðan", - }, - suggestion_menu: { - no_items_title: "Engir hlutir fundust", - loading: "Hleður…", - }, - color_picker: { - text_title: "Texti", - background_title: "Bakgrunnur", - colors: { - default: "Sjálfgefið", - gray: "Grár", - brown: "Brúnn", - red: "Rauður", - orange: "Appelsínugulur", - yellow: "Gulur", - green: "Grænn", - blue: "Blár", - purple: "Fjólublár", - pink: "Bleikur", - }, - }, - - formatting_toolbar: { - bold: { - tooltip: "Feitletrað", - secondary_tooltip: "Mod+B", - }, - italic: { - tooltip: "Skáletrað", - secondary_tooltip: "Mod+I", - }, - underline: { - tooltip: "Undirstrikað", - secondary_tooltip: "Mod+U", - }, - strike: { - tooltip: "Yfirstrikað", - secondary_tooltip: "Mod+Shift+X", - }, - code: { - tooltip: "Kóði", - secondary_tooltip: "", - }, - colors: { - tooltip: "Litir", - }, - link: { - tooltip: "Búa til tengil", - secondary_tooltip: "Mod+K", - }, - image_caption: { - tooltip: "Breyta myndatexta", - input_placeholder: "Breyta myndatexta", - }, - image_replace: { - tooltip: "Skipta um mynd", - }, - nest: { - tooltip: "Fella blokk saman", - secondary_tooltip: "Tab", - }, - unnest: { - tooltip: "Afþýða blokk", - secondary_tooltip: "Shift+Tab", - }, - align_left: { - tooltip: "Vinstrijafna texta", - }, - align_center: { - tooltip: "Miðjustilla texta", - }, - align_right: { - tooltip: "Hægrijafna texta", - }, - align_justify: { - tooltip: "Jafna texta", - }, - }, - image_panel: { - upload: { - title: "Hlaða upp", - file_placeholder: "Hlaða upp mynd", - upload_error: "Villa: Upphleðsla mistókst", - }, - embed: { - title: "Innsetja", - embed_button: "Innsetja mynd", - url_placeholder: "Sláðu inn URL", - }, - }, - link_toolbar: { - delete: { - tooltip: "Fjarlægja tengil", - }, - edit: { - text: "Breyta tengli", - tooltip: "Breyta", - }, - open: { - tooltip: "Opna í nýjum flipa", - }, - form: { - title_placeholder: "Breyta titli", - url_placeholder: "Breyta URL", - }, - }, - generic: { - ctrl_shortcut: "Ctrl", - }, -}; +import type { Dictionary } from "../dictionary"; + +export const is: Dictionary = { + slash_menu: { + heading: { + title: "Fyrirsögn 1", + subtext: "Notað fyrir efstu fyrirsögn", + aliases: ["h", "fyrirsogn1", "h1"], + group: "Fyrirsagnir", + }, + heading_2: { + title: "Fyrirsögn 2", + subtext: "Notað fyrir lykilhluta", + aliases: ["h2", "fyrirsogn2", "undirfyrirsogn"], + group: "Fyrirsagnir", + }, + heading_3: { + title: "Fyrirsögn 3", + subtext: "Notað fyrir undirhluta og hópfyrirsagnir", + aliases: ["h3", "fyrirsogn3", "undirfyrirsogn"], + group: "Fyrirsagnir", + }, + numbered_list: { + title: "Númeruð listi", + subtext: "Notað til að birta númeraðan lista", + aliases: ["ol", "li", "listi", "numeradurlisti"], + group: "Grunnblokkar", + }, + bullet_list: { + title: "Punktalisti", + subtext: "Notað til að birta óraðaðan lista", + aliases: ["ul", "li", "listi", "punktalisti"], + group: "Grunnblokkar", + }, + paragraph: { + title: "Málsgrein", + subtext: "Notað fyrir meginmál skjalsins", + aliases: ["p", "malsgrein"], + group: "Grunnblokkar", + }, + table: { + title: "Tafla", + subtext: "Notað fyrir töflur", + aliases: ["tafla"], + group: "Ítarlegt", + }, + image: { + title: "Mynd", + subtext: "Settu inn mynd", + aliases: [ + "mynd", + "myndaupphlaed", + "upphlaed", + "img", + "mynd", + "media", + "url", + ], + group: "Miðlar", + }, + video: { + title: "Myndband", + subtext: "Setja inn myndband", + aliases: [ + "myndband", + "videoUpphala", + "hlaða upp", + "mp4", + "kvikmynd", + "miðill", + "url", + ], + group: "Miðill", + }, + audio: { + title: "Hljóð", + subtext: "Setja inn hljóð", + aliases: [ + "hljóð", + "audioUpphala", + "hlaða upp", + "mp3", + "hljóð", + "miðill", + "url", + ], + group: "Miðlar", + }, + file: { + title: "Skrá", + subtext: "Setja inn skrá", + aliases: ["skrá", "hlaða upp", "fella inn", "miðill", "url"], + group: "Miðlar", + }, + }, + placeholders: { + default: "Sláðu inn texta eða skrifaðu '/' fyrir skipanir", + heading: "Fyrirsögn", + bulletListItem: "Listi", + numberedListItem: "Listi", + }, + file_blocks: { + image: { + add_button_text: "Bæta við mynd", + }, + video: { + add_button_text: "Bæta við myndbandi", + }, + audio: { + add_button_text: "Bæta við hljóði", + }, + file: { + add_button_text: "Bæta við skrá", + }, + }, + side_menu: { + add_block_label: "Bæta við blokki", + drag_handle_label: "Opna blokkarvalmynd", + }, + drag_handle: { + delete_menuitem: "Eyða", + colors_menuitem: "Litir", + }, + table_handle: { + delete_column_menuitem: "Eyða dálki", + delete_row_menuitem: "Eyða röð", + add_left_menuitem: "Bæta dálki við til vinstri", + add_right_menuitem: "Bæta dálki við til hægri", + add_above_menuitem: "Bæta röð við fyrir ofan", + add_below_menuitem: "Bæta röð við fyrir neðan", + }, + suggestion_menu: { + no_items_title: "Engir hlutir fundust", + loading: "Hleður…", + }, + color_picker: { + text_title: "Texti", + background_title: "Bakgrunnur", + colors: { + default: "Sjálfgefið", + gray: "Grár", + brown: "Brúnn", + red: "Rauður", + orange: "Appelsínugulur", + yellow: "Gulur", + green: "Grænn", + blue: "Blár", + purple: "Fjólublár", + pink: "Bleikur", + }, + }, + + formatting_toolbar: { + bold: { + tooltip: "Feitletrað", + secondary_tooltip: "Mod+B", + }, + italic: { + tooltip: "Skáletrað", + secondary_tooltip: "Mod+I", + }, + underline: { + tooltip: "Undirstrikað", + secondary_tooltip: "Mod+U", + }, + strike: { + tooltip: "Yfirstrikað", + secondary_tooltip: "Mod+Shift+X", + }, + code: { + tooltip: "Kóði", + secondary_tooltip: "", + }, + colors: { + tooltip: "Litir", + }, + link: { + tooltip: "Búa til tengil", + secondary_tooltip: "Mod+K", + }, + file_caption: { + tooltip: "Breyta myndatexta", + input_placeholder: "Breyta myndatexta", + }, + file_replace: { + tooltip: { + image: "Skipta um mynd", + video: "Skipta um myndband", + audio: "Skipta um hljóð", + file: "Skipta um skrá", + }, + }, + file_rename: { + tooltip: { + image: "Endurnefna mynd", + video: "Endurnefna myndband", + audio: "Endurnefna hljóð", + file: "Endurnefna skrá", + }, + input_placeholder: { + image: "Endurnefna mynd", + video: "Endurnefna myndband", + audio: "Endurnefna hljóð", + file: "Endurnefna skrá", + }, + }, + file_download: { + tooltip: { + image: "Sækja mynd", + video: "Sækja myndband", + audio: "Sækja hljóð", + file: "Sækja skrá", + }, + }, + file_delete: { + tooltip: { + image: "Eyða mynd", + video: "Eyða myndbandi", + audio: "Eyða hljóði", + file: "Eyða skrá", + }, + }, + file_preview_toggle: { + tooltip: "Skipta um forskoðun", + }, + nest: { + tooltip: "Fella blokk saman", + secondary_tooltip: "Tab", + }, + unnest: { + tooltip: "Afþýða blokk", + secondary_tooltip: "Shift+Tab", + }, + align_left: { + tooltip: "Vinstrijafna texta", + }, + align_center: { + tooltip: "Miðjustilla texta", + }, + align_right: { + tooltip: "Hægrijafna texta", + }, + align_justify: { + tooltip: "Jafna texta", + }, + }, + file_panel: { + upload: { + title: "Hlaða upp", + file_placeholder: { + image: "Hlaða upp mynd", + video: "Hlaða upp myndband", + audio: "Hlaða upp hljóð", + file: "Hlaða upp skrá", + }, + upload_error: "Villa: Upphleðsla mistókst", + }, + embed: { + title: "Innsetja", + embed_button: { + image: "Innsetja mynd", + video: "Innsetja myndband", + audio: "Innsetja hljóð", + file: "Innsetja skrá", + }, + url_placeholder: "Sláðu inn URL", + }, + }, + link_toolbar: { + delete: { + tooltip: "Fjarlægja tengil", + }, + edit: { + text: "Breyta tengli", + tooltip: "Breyta", + }, + open: { + tooltip: "Opna í nýjum flipa", + }, + form: { + title_placeholder: "Breyta titli", + url_placeholder: "Breyta URL", + }, + }, + generic: { + ctrl_shortcut: "Ctrl", + }, +}; diff --git a/packages/core/src/i18n/locales/ja.ts b/packages/core/src/i18n/locales/ja.ts index 6fadfb882b..e77fe831fd 100644 --- a/packages/core/src/i18n/locales/ja.ts +++ b/packages/core/src/i18n/locales/ja.ts @@ -63,12 +63,46 @@ export const ja: Dictionary = { "picture", "media", "url", - "drive", - "dropbox", "画像", ], group: "メディア", }, + video: { + title: "ビデオ", + subtext: "ビデオを挿入", + aliases: [ + "video", + "videoUpload", + "upload", + "mp4", + "film", + "media", + "url", + "ビデオ", + ], + group: "メディア", + }, + audio: { + title: "オーディオ", + subtext: "オーディオを挿入", + aliases: [ + "audio", + "audioUpload", + "upload", + "mp3", + "sound", + "media", + "url", + "オーディオ", + ], + group: "メディア", + }, + file: { + title: "ファイル", + subtext: "ファイルを挿入", + aliases: ["file", "upload", "embed", "media", "url", "ファイル"], + group: "メディア", + }, }, placeholders: { default: "テキストを入力するか'/' を入力してコマンド選択", @@ -76,8 +110,19 @@ export const ja: Dictionary = { bulletListItem: "リストを追加", numberedListItem: "リストを追加", }, - image: { - add_button: "画像を追加", + file_blocks: { + image: { + add_button_text: "画像を追加", + }, + video: { + add_button_text: "ビデオを追加", + }, + audio: { + add_button_text: "オーディオを追加", + }, + file: { + add_button_text: "ファイルを追加", + }, }, // from react package: side_menu: { @@ -145,12 +190,50 @@ export const ja: Dictionary = { tooltip: "リンク", secondary_tooltip: "Mod+K", }, - image_caption: { + file_caption: { tooltip: "キャプションを編集", input_placeholder: "キャプションを編集", }, - image_replace: { - tooltip: "画像の置き換え", + file_replace: { + tooltip: { + image: "画像を置換", + video: "ビデオを置換", + audio: "オーディオを置換", + file: "ファイルを置換", + }, + }, + file_rename: { + tooltip: { + image: "画像の名前を変更", + video: "ビデオの名前を変更", + audio: "オーディオの名前を変更", + file: "ファイルの名前を変更", + }, + input_placeholder: { + image: "画像の名前を変更", + video: "ビデオの名前を変更", + audio: "オーディオの名前を変更", + file: "ファイルの名前を変更", + }, + }, + file_download: { + tooltip: { + image: "画像をダウンロード", + video: "ビデオをダウンロード", + audio: "オーディオをダウンロード", + file: "ファイルをダウンロード", + }, + }, + file_delete: { + tooltip: { + image: "画像を削除", + video: "ビデオを削除", + audio: "オーディオを削除", + file: "ファイルを削除", + }, + }, + file_preview_toggle: { + tooltip: "プレビューの切り替え", }, nest: { tooltip: "インデント増", @@ -173,15 +256,25 @@ export const ja: Dictionary = { tooltip: "両端揃え", }, }, - image_panel: { + file_panel: { upload: { title: "アップロード", - file_placeholder: "画像アップロード", + file_placeholder: { + image: "画像をアップロード", + video: "ビデオをアップロード", + audio: "オーディオをアップロード", + file: "ファイルをアップロード", + }, upload_error: "エラー: アップロードが失敗しました", }, embed: { title: "埋め込み", - embed_button: "画像を埋め込み", + embed_button: { + image: "画像を埋め込む", + video: "ビデオを埋め込む", + audio: "オーディオを埋め込む", + file: "ファイルを埋め込む", + }, url_placeholder: "URLを入力", }, }, diff --git a/packages/core/src/i18n/locales/ko.ts b/packages/core/src/i18n/locales/ko.ts index 5bed6db5fe..888a062f33 100644 --- a/packages/core/src/i18n/locales/ko.ts +++ b/packages/core/src/i18n/locales/ko.ts @@ -1,4 +1,6 @@ -export const ko = { +import { Dictionary } from "../dictionary"; + +export const ko: Dictionary = { slash_menu: { heading: { title: "제목1", @@ -54,11 +56,45 @@ export const ko = { "media", "이미지", "url", - "drive", - "dropbox", ], group: "미디어", }, + video: { + title: "비디오", + subtext: "비디오 삽입", + aliases: [ + "video", + "videoUpload", + "upload", + "mp4", + "film", + "media", + "동영상", + "url", + ], + group: "미디어", + }, + audio: { + title: "오디오", + subtext: "오디오 삽입", + aliases: [ + "audio", + "audioUpload", + "upload", + "mp3", + "sound", + "media", + "오디오", + "url", + ], + group: "미디어", + }, + file: { + title: "파일", + subtext: "파일 삽입", + aliases: ["file", "upload", "embed", "media", "파일", "url"], + group: "미디어", + }, }, placeholders: { default: "텍스트를 입력하거나 /를 입력하여 명령을 입력하세요.", @@ -66,8 +102,19 @@ export const ko = { bulletListItem: "목록", numberedListItem: "목록", }, - image: { - add_button: "이미지 추가", + file_blocks: { + image: { + add_button_text: "이미지 추가", + }, + video: { + add_button_text: "비디오 추가", + }, + audio: { + add_button_text: "오디오 추가", + }, + file: { + add_button_text: "파일 추가", + }, }, // from react package: side_menu: { @@ -135,12 +182,50 @@ export const ko = { tooltip: "링크 만들기", secondary_tooltip: "Mod+K", }, - image_caption: { + file_caption: { tooltip: "이미지 캡션 수정", input_placeholder: "이미지 캡션 수정", }, - image_replace: { - tooltip: "이미지 교체", + file_replace: { + tooltip: { + image: "이미지 교체", + video: "비디오 교체", + audio: "오디오 교체", + file: "파일 교체", + }, + }, + file_rename: { + tooltip: { + image: "이미지 이름 변경", + video: "비디오 이름 변경", + audio: "오디오 이름 변경", + file: "파일 이름 변경", + }, + input_placeholder: { + image: "이미지 이름 변경", + video: "비디오 이름 변경", + audio: "오디오 이름 변경", + file: "파일 이름 변경", + }, + }, + file_download: { + tooltip: { + image: "이미지 다운로드", + video: "비디오 다운로드", + audio: "오디오 다운로드", + file: "파일 다운로드", + }, + }, + file_delete: { + tooltip: { + image: "이미지 삭제", + video: "비디오 삭제", + audio: "오디오 삭제", + file: "파일 삭제", + }, + }, + file_preview_toggle: { + tooltip: "미리보기 전환", }, nest: { tooltip: "중첩 블록", @@ -163,15 +248,25 @@ export const ko = { tooltip: "텍스트 양쪽 맞춤", }, }, - image_panel: { + file_panel: { upload: { title: "업로드", - file_placeholder: "이미지 업로드", + file_placeholder: { + image: "이미지 업로드", + video: "비디오 업로드", + audio: "오디오 업로드", + file: "파일 업로드", + }, upload_error: "오류: 업로드 실패", }, embed: { title: "임베드", - embed_button: "이미지 임베드", + embed_button: { + image: "이미지 삽입", + video: "비디오 삽입", + audio: "오디오 삽입", + file: "파일 삽입", + }, url_placeholder: "URL을 입력하세요.", }, }, diff --git a/packages/core/src/i18n/locales/nl.ts b/packages/core/src/i18n/locales/nl.ts index d61bf2530f..0d9e5f3628 100644 --- a/packages/core/src/i18n/locales/nl.ts +++ b/packages/core/src/i18n/locales/nl.ts @@ -55,11 +55,45 @@ export const nl: Dictionary = { "foto", "media", "url", + ], + group: "Media", + }, + video: { + title: "Video", + subtext: "Voeg een video in", + aliases: [ + "video", + "videoUploaden", + "upload", + "mp4", + "film", + "media", + "url", "drive", "dropbox", ], group: "Media", }, + audio: { + title: "Audio", + subtext: "Voeg audio in", + aliases: [ + "audio", + "audioUploaden", + "upload", + "mp3", + "geluid", + "media", + "url", + ], + group: "Media", + }, + file: { + title: "Bestand", + subtext: "Voeg een bestand in", + aliases: ["bestand", "upload", "insluiten", "media", "url"], + group: "Media", + }, }, placeholders: { default: "Voer tekst in of type '/' voor commando's", @@ -67,8 +101,19 @@ export const nl: Dictionary = { bulletListItem: "Lijst", numberedListItem: "Lijst", }, - image: { - add_button: "Afbeelding toevoegen", + file_blocks: { + image: { + add_button_text: "Afbeelding toevoegen", + }, + video: { + add_button_text: "Video toevoegen", + }, + audio: { + add_button_text: "Audio toevoegen", + }, + file: { + add_button_text: "Bestand toevoegen", + }, }, // from react package: side_menu: { @@ -135,12 +180,50 @@ export const nl: Dictionary = { tooltip: "Maak link", secondary_tooltip: "Mod+K", }, - image_caption: { + file_caption: { tooltip: "Bewerk onderschrift", input_placeholder: "Bewerk onderschrift", }, - image_replace: { - tooltip: "Vervang afbeelding", + file_replace: { + tooltip: { + image: "Afbeelding vervangen", + video: "Video vervangen", + audio: "Audio vervangen", + file: "Bestand vervangen", + }, + }, + file_rename: { + tooltip: { + image: "Afbeelding hernoemen", + video: "Video hernoemen", + audio: "Audio hernoemen", + file: "Bestand hernoemen", + }, + input_placeholder: { + image: "Afbeelding hernoemen", + video: "Video hernoemen", + audio: "Audio hernoemen", + file: "Bestand hernoemen", + }, + }, + file_download: { + tooltip: { + image: "Afbeelding downloaden", + video: "Video downloaden", + audio: "Audio downloaden", + file: "Bestand downloaden", + }, + }, + file_delete: { + tooltip: { + image: "Afbeelding verwijderen", + video: "Video verwijderen", + audio: "Audio verwijderen", + file: "Bestand verwijderen", + }, + }, + file_preview_toggle: { + tooltip: "Voorbeeldschakelaar", }, nest: { tooltip: "Nest blok", @@ -163,15 +246,25 @@ export const nl: Dictionary = { tooltip: "Tekst uitvullen", }, }, - image_panel: { + file_panel: { upload: { title: "Upload", - file_placeholder: "Upload afbeelding", + file_placeholder: { + image: "Afbeelding uploaden", + video: "Video uploaden", + audio: "Audio uploaden", + file: "Bestand uploaden", + }, upload_error: "Fout: Upload mislukt", }, embed: { title: "Insluiten", - embed_button: "Insluiten afbeelding", + embed_button: { + image: "Afbeelding insluiten", + video: "Video insluiten", + audio: "Audio insluiten", + file: "Bestand insluiten", + }, url_placeholder: "Voer URL in", }, }, diff --git a/packages/core/src/i18n/locales/pl.ts b/packages/core/src/i18n/locales/pl.ts index 54b1585c33..d5d6cba04b 100644 --- a/packages/core/src/i18n/locales/pl.ts +++ b/packages/core/src/i18n/locales/pl.ts @@ -1,197 +1,280 @@ -import type { Dictionary } from "../dictionary"; - -export const pl: Dictionary = { - slash_menu: { - heading: { - title: "Nagłówek 1", - subtext: "Używany dla nagłówka najwyższego poziomu", - aliases: ["h", "naglowek1", "h1"], - group: "Nagłówki", - }, - heading_2: { - title: "Nagłówek 2", - subtext: "Używany dla kluczowych sekcji", - aliases: ["h2", "naglowek2", "podnaglowek"], - group: "Nagłówki", - }, - heading_3: { - title: "Nagłówek 3", - subtext: "Używany dla podsekcji i grup nagłówków", - aliases: ["h3", "naglowek3", "podnaglowek"], - group: "Nagłówki", - }, - numbered_list: { - title: "Lista numerowana", - subtext: "Używana do wyświetlania listy numerowanej", - aliases: ["ol", "li", "lista", "numerowana lista"], - group: "Podstawowe bloki", - }, - bullet_list: { - title: "Lista punktowana", - subtext: "Używana do wyświetlania listy bez numeracji", - aliases: ["ul", "li", "lista", "punktowana lista"], - group: "Podstawowe bloki", - }, - paragraph: { - title: "Akapit", - subtext: "Używany dla treści dokumentu", - aliases: ["p", "akapit"], - group: "Podstawowe bloki", - }, - table: { - title: "Tabela", - subtext: "Używana do tworzenia tabel", - aliases: ["tabela"], - group: "Zaawansowane", - }, - image: { - title: "Obraz", - subtext: "Wstaw obraz", - aliases: [ - "obraz", - "przeslijObraz", - "przeslij", - "img", - "zdjecie", - "media", - "url", - "dysk", - "dropbox", - ], - group: "Media", - }, - }, - placeholders: { - default: "Wprowadź tekst lub wpisz '/' aby użyć poleceń", - heading: "Nagłówek", - bulletListItem: "Lista", - numberedListItem: "Lista", - }, - image: { - add_button: "Dodaj obraz", - }, - side_menu: { - add_block_label: "Dodaj blok", - drag_handle_label: "Otwórz menu bloków", - }, - drag_handle: { - delete_menuitem: "Usuń", - colors_menuitem: "Kolory", - }, - table_handle: { - delete_column_menuitem: "Usuń kolumnę", - delete_row_menuitem: "Usuń wiersz", - add_left_menuitem: "Dodaj kolumnę po lewej", - add_right_menuitem: "Dodaj kolumnę po prawej", - add_above_menuitem: "Dodaj wiersz powyżej", - add_below_menuitem: "Dodaj wiersz poniżej", - }, - suggestion_menu: { - no_items_title: "Nie znaleziono elementów", - loading: "Ładowanie…", - }, - color_picker: { - text_title: "Tekst", - background_title: "Tło", - colors: { - default: "Domyślny", - gray: "Szary", - brown: "Brązowy", - red: "Czerwony", - orange: "Pomarańczowy", - yellow: "Żółty", - green: "Zielony", - blue: "Niebieski", - purple: "Fioletowy", - pink: "Różowy", - }, - }, - - formatting_toolbar: { - bold: { - tooltip: "Pogrubienie", - secondary_tooltip: "Mod+B", - }, - italic: { - tooltip: "Kursywa", - secondary_tooltip: "Mod+I", - }, - underline: { - tooltip: "Podkreślenie", - secondary_tooltip: "Mod+U", - }, - strike: { - tooltip: "Przekreślenie", - secondary_tooltip: "Mod+Shift+X", - }, - code: { - tooltip: "Kod", - secondary_tooltip: "", - }, - colors: { - tooltip: "Kolory", - }, - link: { - tooltip: "Utwórz link", - secondary_tooltip: "Mod+K", - }, - image_caption: { - tooltip: "Edytuj podpis", - input_placeholder: "Edytuj podpis", - }, - image_replace: { - tooltip: "Zamień obraz", - }, - nest: { - tooltip: "Zagnieźdź blok", - secondary_tooltip: "Tab", - }, - unnest: { - tooltip: "Odgagnieźdź blok", - secondary_tooltip: "Shift+Tab", - }, - align_left: { - tooltip: "Wyrównaj tekst do lewej", - }, - align_center: { - tooltip: "Wyśrodkuj tekst", - }, - align_right: { - tooltip: "Wyrównaj tekst do prawej", - }, - align_justify: { - tooltip: "Wyjustuj tekst", - }, - }, - image_panel: { - upload: { - title: "Przesyłanie", - file_placeholder: "Prześlij obraz", - upload_error: "Błąd: Przesyłanie nie powiodło się", - }, - embed: { - title: "Osadź", - embed_button: "Osadź obraz", - url_placeholder: "Wprowadź URL", - }, - }, - link_toolbar: { - delete: { - tooltip: "Usuń link", - }, - edit: { - text: "Edytuj link", - tooltip: "Edytuj", - }, - open: { - tooltip: "Otwórz w nowej karcie", - }, - form: { - title_placeholder: "Edytuj tytuł", - url_placeholder: "Edytuj URL", - }, - }, - generic: { - ctrl_shortcut: "Ctrl", - }, -}; +import type { Dictionary } from "../dictionary"; + +export const pl: Dictionary = { + slash_menu: { + heading: { + title: "Nagłówek 1", + subtext: "Używany dla nagłówka najwyższego poziomu", + aliases: ["h", "naglowek1", "h1"], + group: "Nagłówki", + }, + heading_2: { + title: "Nagłówek 2", + subtext: "Używany dla kluczowych sekcji", + aliases: ["h2", "naglowek2", "podnaglowek"], + group: "Nagłówki", + }, + heading_3: { + title: "Nagłówek 3", + subtext: "Używany dla podsekcji i grup nagłówków", + aliases: ["h3", "naglowek3", "podnaglowek"], + group: "Nagłówki", + }, + numbered_list: { + title: "Lista numerowana", + subtext: "Używana do wyświetlania listy numerowanej", + aliases: ["ol", "li", "lista", "numerowana lista"], + group: "Podstawowe bloki", + }, + bullet_list: { + title: "Lista punktowana", + subtext: "Używana do wyświetlania listy bez numeracji", + aliases: ["ul", "li", "lista", "punktowana lista"], + group: "Podstawowe bloki", + }, + paragraph: { + title: "Akapit", + subtext: "Używany dla treści dokumentu", + aliases: ["p", "akapit"], + group: "Podstawowe bloki", + }, + table: { + title: "Tabela", + subtext: "Używana do tworzenia tabel", + aliases: ["tabela"], + group: "Zaawansowane", + }, + image: { + title: "Zdjęcie", + subtext: "Wstaw zdjęcie", + aliases: [ + "obraz", + "wrzućZdjęcie", + "wrzuć", + "img", + "zdjęcie", + "media", + "url", + ], + group: "Media", + }, + video: { + title: "Wideo", + subtext: "Wstaw wideo", + aliases: ["wideo", "wrzućWideo", "wrzuć", "mp4", "film", "media", "url"], + group: "Media", + }, + audio: { + title: "Audio", + subtext: "Wstaw audio", + aliases: [ + "audio", + "wrzućAudio", + "wrzuć", + "mp3", + "dźwięk", + "media", + "url", + ], + group: "Media", + }, + file: { + title: "Plik", + subtext: "Wstaw plik", + aliases: ["plik", "wrzuć", "wstaw", "media", "url"], + group: "Media", + }, + }, + placeholders: { + default: "Wprowadź tekst lub wpisz '/' aby użyć poleceń", + heading: "Nagłówek", + bulletListItem: "Lista", + numberedListItem: "Lista", + }, + file_blocks: { + image: { + add_button_text: "Dodaj zdjęcie", + }, + video: { + add_button_text: "Dodaj wideo", + }, + audio: { + add_button_text: "Dodaj audio", + }, + file: { + add_button_text: "Dodaj plik", + }, + }, + side_menu: { + add_block_label: "Dodaj blok", + drag_handle_label: "Otwórz menu bloków", + }, + drag_handle: { + delete_menuitem: "Usuń", + colors_menuitem: "Kolory", + }, + table_handle: { + delete_column_menuitem: "Usuń kolumnę", + delete_row_menuitem: "Usuń wiersz", + add_left_menuitem: "Dodaj kolumnę po lewej", + add_right_menuitem: "Dodaj kolumnę po prawej", + add_above_menuitem: "Dodaj wiersz powyżej", + add_below_menuitem: "Dodaj wiersz poniżej", + }, + suggestion_menu: { + no_items_title: "Nie znaleziono elementów", + loading: "Ładowanie…", + }, + color_picker: { + text_title: "Tekst", + background_title: "Tło", + colors: { + default: "Domyślny", + gray: "Szary", + brown: "Brązowy", + red: "Czerwony", + orange: "Pomarańczowy", + yellow: "Żółty", + green: "Zielony", + blue: "Niebieski", + purple: "Fioletowy", + pink: "Różowy", + }, + }, + + formatting_toolbar: { + bold: { + tooltip: "Pogrubienie", + secondary_tooltip: "Mod+B", + }, + italic: { + tooltip: "Kursywa", + secondary_tooltip: "Mod+I", + }, + underline: { + tooltip: "Podkreślenie", + secondary_tooltip: "Mod+U", + }, + strike: { + tooltip: "Przekreślenie", + secondary_tooltip: "Mod+Shift+X", + }, + code: { + tooltip: "Kod", + secondary_tooltip: "", + }, + colors: { + tooltip: "Kolory", + }, + link: { + tooltip: "Utwórz link", + secondary_tooltip: "Mod+K", + }, + file_caption: { + tooltip: "Zmień podpis", + input_placeholder: "Zmień podpis", + }, + file_replace: { + tooltip: { + image: "Zmień obraz", + video: "Zmień wideo", + audio: "Zmień audio", + file: "Zmień plik", + }, + }, + file_rename: { + tooltip: { + image: "Zmień nazwę zdjęcia", + video: "Zmień nazwę wideo", + audio: "Zmień nazwę audio", + file: "Zmień nazwę pliku", + }, + input_placeholder: { + image: "Zmień nazwę zdjęcia", + video: "Zmień nazwę wideo", + audio: "Zmień nazwę audio", + file: "Zmień nazwę pliku", + }, + }, + file_download: { + tooltip: { + image: "Pobierz zdjęcie", + video: "Pobierz wideo", + audio: "Pobierz audio", + file: "Pobierz plik", + }, + }, + file_delete: { + tooltip: { + image: "Usuń zdjęcie", + video: "Usuń wideo", + audio: "Usuń audio", + file: "Usuń plik", + }, + }, + file_preview_toggle: { + tooltip: "Przełącz podgląd", + }, + nest: { + tooltip: "Zagnieźdź blok", + secondary_tooltip: "Tab", + }, + unnest: { + tooltip: "Odgagnieźdź blok", + secondary_tooltip: "Shift+Tab", + }, + align_left: { + tooltip: "Wyrównaj tekst do lewej", + }, + align_center: { + tooltip: "Wyśrodkuj tekst", + }, + align_right: { + tooltip: "Wyrównaj tekst do prawej", + }, + align_justify: { + tooltip: "Wyjustuj tekst", + }, + }, + file_panel: { + upload: { + title: "Wrzuć", + file_placeholder: { + image: "Wrzuć zdjęcie", + video: "Wrzuć wideo", + audio: "Wrzuć audio", + file: "Wrzuć plik", + }, + upload_error: "Błąd: Przesyłanie nie powiodło się", + }, + embed: { + title: "Wstaw", + embed_button: { + image: "Wstaw zdjęice", + video: "Wstaw wideo", + audio: "Wstaw audio", + file: "Wstaw plik", + }, + url_placeholder: "Wprowadź URL", + }, + }, + link_toolbar: { + delete: { + tooltip: "Usuń link", + }, + edit: { + text: "Edytuj link", + tooltip: "Edytuj", + }, + open: { + tooltip: "Otwórz w nowej karcie", + }, + form: { + title_placeholder: "Edytuj tytuł", + url_placeholder: "Edytuj URL", + }, + }, + generic: { + ctrl_shortcut: "Ctrl", + }, +}; diff --git a/packages/core/src/i18n/locales/pt.ts b/packages/core/src/i18n/locales/pt.ts index 540fbc7d8b..bfd374231b 100644 --- a/packages/core/src/i18n/locales/pt.ts +++ b/packages/core/src/i18n/locales/pt.ts @@ -55,11 +55,35 @@ export const pt: Dictionary = { "foto", "media", "url", - "drive", - "dropbox", ], group: "Mídia", }, + video: { + title: "Vídeo", + subtext: "Inserir um vídeo", + aliases: [ + "vídeo", + "uploadVídeo", + "upload", + "mp4", + "filme", + "mídia", + "url", + ], + group: "Mídia", + }, + audio: { + title: "Áudio", + subtext: "Inserir um áudio", + aliases: ["áudio", "uploadÁudio", "upload", "mp3", "som", "mídia", "url"], + group: "Mídia", + }, + file: { + title: "Arquivo", + subtext: "Inserir um arquivo", + aliases: ["arquivo", "upload", "incorporar", "mídia", "url"], + group: "Mídia", + }, }, placeholders: { default: "Digite texto ou use '/' para comandos", @@ -67,8 +91,19 @@ export const pt: Dictionary = { bulletListItem: "Lista", numberedListItem: "Lista", }, - image: { - add_button: "Adicionar Imagem", + file_blocks: { + image: { + add_button_text: "Adicionar imagem", + }, + video: { + add_button_text: "Adicionar vídeo", + }, + audio: { + add_button_text: "Adicionar áudio", + }, + file: { + add_button_text: "Adicionar arquivo", + }, }, // from react package: side_menu: { @@ -136,12 +171,50 @@ export const pt: Dictionary = { tooltip: "Criar link", secondary_tooltip: "Mod+K", }, - image_caption: { + file_caption: { tooltip: "Editar legenda", input_placeholder: "Editar legenda", }, - image_replace: { - tooltip: "Substituir imagem", + file_replace: { + tooltip: { + image: "Substituir imagem", + video: "Substituir vídeo", + audio: "Substituir áudio", + file: "Substituir arquivo", + }, + }, + file_rename: { + tooltip: { + image: "Renomear imagem", + video: "Renomear vídeo", + audio: "Renomear áudio", + file: "Renomear arquivo", + }, + input_placeholder: { + image: "Renomear imagem", + video: "Renomear vídeo", + audio: "Renomear áudio", + file: "Renomear arquivo", + }, + }, + file_download: { + tooltip: { + image: "Baixar imagem", + video: "Baixar vídeo", + audio: "Baixar áudio", + file: "Baixar arquivo", + }, + }, + file_delete: { + tooltip: { + image: "Excluir imagem", + video: "Excluir vídeo", + audio: "Excluir áudio", + file: "Excluir arquivo", + }, + }, + file_preview_toggle: { + tooltip: "Alternar visualização", }, nest: { tooltip: "Aninhar bloco", @@ -164,15 +237,25 @@ export const pt: Dictionary = { tooltip: "Justificar texto", }, }, - image_panel: { + file_panel: { upload: { title: "Upload", - file_placeholder: "Upload de imagem", + file_placeholder: { + image: "Upload de imagem", + video: "Upload de vídeo", + audio: "Upload de áudio", + file: "Upload de arquivo", + }, upload_error: "Erro: Falha no upload", }, embed: { title: "Incorporar", - embed_button: "Incorporar imagem", + embed_button: { + image: "Incorporar imagem", + video: "Incorporar vídeo", + audio: "Incorporar áudio", + file: "Incorporar arquivo", + }, url_placeholder: "Insira a URL", }, }, diff --git a/packages/core/src/i18n/locales/vi.ts b/packages/core/src/i18n/locales/vi.ts index 48cd484c9d..6e58aa206b 100644 --- a/packages/core/src/i18n/locales/vi.ts +++ b/packages/core/src/i18n/locales/vi.ts @@ -47,19 +47,43 @@ export const vi: Dictionary = { image: { title: "Hình ảnh", subtext: "Chèn hình ảnh", + aliases: ["anh", "tai-len-anh", "tai-len", "img", "hinh", "media", "url"], + group: "Phương tiện", + }, + video: { + title: "Video", + subtext: "Chèn video", + aliases: [ + "video", + "tai-len-video", + "tai-len", + "mp4", + "phim", + "media", + "url", + ], + group: "Phương tiện", + }, + audio: { + title: "Âm thanh", + subtext: "Chèn âm thanh", aliases: [ - "anh", - "tai-len-anh", + "âm thanh", + "tai-len-am-thanh", "tai-len", - "img", - "hinh", + "mp3", + "am thanh", "media", "url", - "drive", - "dropbox", ], group: "Phương tiện", }, + file: { + title: "Tệp", + subtext: "Chèn tệp", + aliases: ["tep", "tai-len", "nhung", "media", "url"], + group: "Phương tiện", + }, }, placeholders: { default: "Nhập văn bản hoặc gõ '/' để thêm định dạng", @@ -67,8 +91,19 @@ export const vi: Dictionary = { bulletListItem: "Danh sách", numberedListItem: "Danh sách", }, - image: { - add_button: "Thêm ảnh", + file_blocks: { + image: { + add_button_text: "Thêm ảnh", + }, + video: { + add_button_text: "Thêm video", + }, + audio: { + add_button_text: "Thêm âm thanh", + }, + file: { + add_button_text: "Thêm tệp", + }, }, // từ gói phản ứng: side_menu: { @@ -136,12 +171,50 @@ export const vi: Dictionary = { tooltip: "Tạo liên kết", secondary_tooltip: "Mod+K", }, - image_caption: { + file_caption: { tooltip: "Chỉnh sửa chú thích", input_placeholder: "Chỉnh sửa chú thích", }, - image_replace: { - tooltip: "Thay thế hình ảnh", + file_replace: { + tooltip: { + image: "Thay thế hình ảnh", + video: "Thay thế video", + audio: "Thay thế âm thanh", + file: "Thay thế tệp", + }, + }, + file_rename: { + tooltip: { + image: "Đổi tên hình ảnh", + video: "Đổi tên video", + audio: "Đổi tên âm thanh", + file: "Đổi tên tệp", + }, + input_placeholder: { + image: "Đổi tên hình ảnh", + video: "Đổi tên video", + audio: "Đổi tên âm thanh", + file: "Đổi tên tệp", + }, + }, + file_download: { + tooltip: { + image: "Tải xuống hình ảnh", + video: "Tải xuống video", + audio: "Tải xuống âm thanh", + file: "Tải xuống tệp", + }, + }, + file_delete: { + tooltip: { + image: "Xóa hình ảnh", + video: "Xóa video", + audio: "Xóa âm thanh", + file: "Xóa tệp", + }, + }, + file_preview_toggle: { + tooltip: "Chuyển đổi xem trước", }, nest: { tooltip: "Lồng khối", @@ -164,15 +237,25 @@ export const vi: Dictionary = { tooltip: "Căn đều văn bản", }, }, - image_panel: { + file_panel: { upload: { title: "Tải lên", - file_placeholder: "Tải lên hình ảnh", + file_placeholder: { + image: "Tải lên hình ảnh", + video: "Tải lên video", + audio: "Tải lên âm thanh", + file: "Tải lên tệp", + }, upload_error: "Lỗi: Tải lên thất bại", }, embed: { title: "Nhúng", - embed_button: "Nhúng hình ảnh", + embed_button: { + image: "Nhúng hình ảnh", + video: "Nhúng video", + audio: "Nhúng âm thanh", + file: "Nhúng tệp", + }, url_placeholder: "Nhập URL", }, }, @@ -195,4 +278,4 @@ export const vi: Dictionary = { generic: { ctrl_shortcut: "Ctrl", }, -}; \ No newline at end of file +}; diff --git a/packages/core/src/i18n/locales/zh.ts b/packages/core/src/i18n/locales/zh.ts index f987ced0d5..7e173a3828 100644 --- a/packages/core/src/i18n/locales/zh.ts +++ b/packages/core/src/i18n/locales/zh.ts @@ -75,6 +75,46 @@ export const zh: Dictionary = { ], group: "媒体", }, + video: { + title: "视频", + subtext: "插入视频", + aliases: [ + "视频", + "视频上传", + "上传", + "video", + "mp4", + "电影", + "媒体", + "url", + "驱动", + "dropbox", + ], + group: "媒体", + }, + audio: { + title: "音频", + subtext: "插入音频", + aliases: [ + "音频", + "音频上传", + "上传", + "audio", + "mp3", + "声音", + "媒体", + "url", + "驱动", + "dropbox", + ], + group: "媒体", + }, + file: { + title: "文件", + subtext: "插入文件", + aliases: ["文件", "上传", "file", "嵌入", "媒体", "url"], + group: "媒体", + }, }, placeholders: { default: "输入 '/' 以使用命令", @@ -82,8 +122,19 @@ export const zh: Dictionary = { bulletListItem: "列表", numberedListItem: "列表", }, - image: { - add_button: "添加图片", + file_blocks: { + image: { + add_button_text: "添加图片", + }, + video: { + add_button_text: "添加视频", + }, + audio: { + add_button_text: "添加音频", + }, + file: { + add_button_text: "添加文件", + }, }, // from react package: side_menu: { @@ -151,12 +202,50 @@ export const zh: Dictionary = { tooltip: "添加链接", secondary_tooltip: "Mod+K", }, - image_caption: { + file_caption: { tooltip: "编辑标题", input_placeholder: "编辑标题", }, - image_replace: { - tooltip: "替换图片", + file_replace: { + tooltip: { + image: "替换图片", + video: "替换视频", + audio: "替换音频", + file: "替换文件", + }, + }, + file_rename: { + tooltip: { + image: "重命名图片", + video: "重命名视频", + audio: "重命名音频", + file: "重命名文件", + }, + input_placeholder: { + image: "重命名图片", + video: "重命名视频", + audio: "重命名音频", + file: "重命名文件", + }, + }, + file_download: { + tooltip: { + image: "下载图片", + video: "下载视频", + audio: "下载音频", + file: "下载文件", + }, + }, + file_delete: { + tooltip: { + image: "删除图片", + video: "删除视频", + audio: "删除音频", + file: "删除文件", + }, + }, + file_preview_toggle: { + tooltip: "切换预览", }, nest: { tooltip: "嵌套", @@ -179,15 +268,25 @@ export const zh: Dictionary = { tooltip: "文本对齐", }, }, - image_panel: { + file_panel: { upload: { title: "上传", - file_placeholder: "上传图片", + file_placeholder: { + image: "上传图片", + video: "上传视频", + audio: "上传音频", + file: "上传文件", + }, upload_error: "Error:上传失败", }, embed: { title: "嵌入", - embed_button: "嵌入图片", + embed_button: { + image: "嵌入图片", + video: "嵌入视频", + audio: "嵌入音频", + file: "嵌入文件", + }, url_placeholder: "输入图片地址", }, }, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 34cbae5a64..88b0f6186a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -2,7 +2,13 @@ import * as locales from "./i18n/locales"; export * from "./api/exporters/html/externalHTMLExporter"; export * from "./api/exporters/html/internalHTMLSerializer"; export * from "./api/testUtil"; -export * from "./blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY"; +export * from "./blocks/FileBlockContent/FileBlockContent"; +export * from "./blocks/ImageBlockContent/ImageBlockContent"; +export * from "./blocks/VideoBlockContent/VideoBlockContent"; +export * from "./blocks/AudioBlockContent/AudioBlockContent"; + +export * from "./blocks/FileBlockContent/fileBlockHelpers"; +export * from "./blocks/FileBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY"; export * from "./blocks/defaultBlockTypeGuards"; export * from "./blocks/defaultBlocks"; export * from "./blocks/defaultProps"; @@ -11,8 +17,8 @@ export * from "./editor/BlockNoteExtensions"; export * from "./editor/BlockNoteSchema"; export * from "./editor/selectionTypes"; export * from "./extensions-shared/UiElementPosition"; +export * from "./extensions/FilePanel/FilePanelPlugin"; export * from "./extensions/FormattingToolbar/FormattingToolbarPlugin"; -export * from "./extensions/ImagePanel/ImageToolbarPlugin"; export * from "./extensions/LinkToolbar/LinkToolbarPlugin"; export * from "./extensions/SideMenu/SideMenuPlugin"; export * from "./extensions/SuggestionMenu/DefaultSuggestionItem"; @@ -29,3 +35,4 @@ export * from "./extensions/UniqueID/UniqueID"; export * from "./i18n/dictionary"; export { UnreachableCaseError, assertEmpty } from "./util/typescript"; export { locales }; +export { parseImageElement } from "./blocks/ImageBlockContent/imageBlockHelpers"; diff --git a/packages/core/src/pm-nodes/BlockContainer.ts b/packages/core/src/pm-nodes/BlockContainer.ts index 7498f16e15..fa1e7ee66b 100644 --- a/packages/core/src/pm-nodes/BlockContainer.ts +++ b/packages/core/src/pm-nodes/BlockContainer.ts @@ -691,7 +691,7 @@ export const BlockContainer = Node.create<{ if ( this.options.editor.formattingToolbar?.shown || this.options.editor.linkToolbar?.shown || - this.options.editor.imagePanel?.shown + this.options.editor.filePanel?.shown ) { // don't handle tabs if a toolbar is shown, so we can tab into / out of it return false; @@ -703,7 +703,7 @@ export const BlockContainer = Node.create<{ if ( this.options.editor.formattingToolbar?.shown || this.options.editor.linkToolbar?.shown || - this.options.editor.imagePanel?.shown + this.options.editor.filePanel?.shown ) { // don't handle tabs if a toolbar is shown, so we can tab into / out of it return false; diff --git a/packages/core/src/schema/blocks/createSpec.ts b/packages/core/src/schema/blocks/createSpec.ts index eee2ac6788..248445a4c2 100644 --- a/packages/core/src/schema/blocks/createSpec.ts +++ b/packages/core/src/schema/blocks/createSpec.ts @@ -193,6 +193,7 @@ export function createBlockSpec< block.type, block.props, blockConfig.propSchema, + blockConfig.isFileBlock, blockContentDOMAttributes ); }, diff --git a/packages/core/src/schema/blocks/internal.ts b/packages/core/src/schema/blocks/internal.ts index 7ab8b1d4c5..3c7e5d90e3 100644 --- a/packages/core/src/schema/blocks/internal.ts +++ b/packages/core/src/schema/blocks/internal.ts @@ -109,6 +109,11 @@ export function getBlockFromPos< const blockContainer = tipTapEditor.state.doc.resolve(pos!).node(); // Gets block identifier const blockIdentifier = blockContainer.attrs.id; + + if (!blockIdentifier) { + throw new Error("Block doesn't have id"); + } + // Gets the block const block = editor.getBlock(blockIdentifier)! as SpecificBlock< BSchema, @@ -139,6 +144,7 @@ export function wrapInBlockStructure< blockType: BType, blockProps: Props, propSchema: PSchema, + isFileBlock = false, domAttributes?: Record ): { dom: HTMLElement; @@ -171,6 +177,10 @@ export function wrapInBlockStructure< blockContent.setAttribute(camelToDataKebab(prop), value); } } + // Adds file block attribute + if (isFileBlock) { + blockContent.setAttribute("data-file-block", ""); + } blockContent.appendChild(element.dom); diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index 66d073655d..423715d4aa 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -21,14 +21,50 @@ export type BlockNoteDOMAttributes = Partial<{ [DOMElement in BlockNoteDOMElement]: Record; }>; -// BlockConfig contains the "schema" info about a Block type -// i.e. what props it supports, what content it supports, etc. -export type BlockConfig = { +export type FileBlockConfig = { type: string; - readonly propSchema: PropSchema; - content: "inline" | "none" | "table"; + readonly propSchema: PropSchema & { + caption: { + default: ""; + }; + name: { + default: ""; + }; + + // URL is optional, as we also want to accept files with no URL, but for example ids + // (ids can be used for files that are resolved on the backend) + url?: { + default: ""; + }; + + // Whether to show the file preview or the name only. + // This is useful for some file blocks, but not all + // (e.g.: not relevant for default "file" block which doesn;'t show previews) + showPreview?: { + default: boolean; + }; + // File preview width in px. + previewWidth?: { + default: number; + }; + }; + content: "none"; + isFileBlock: true; + isFileBlockPlaceholder: (block: any) => boolean; + fileBlockAcceptMimeTypes?: string[]; }; +// BlockConfig contains the "schema" info about a Block type +// i.e. what props it supports, what content it supports, etc. +export type BlockConfig = + | { + type: string; + readonly propSchema: PropSchema; + content: "inline" | "none" | "table"; + isFileBlock?: false; + } + | FileBlockConfig; + // Block implementation contains the "implementation" info about a Block // such as the functions / Nodes required to render and / or serialize it export type TiptapBlockImplementation< diff --git a/packages/core/src/util/string.ts b/packages/core/src/util/string.ts index d08881ca37..a2bbc6822d 100644 --- a/packages/core/src/util/string.ts +++ b/packages/core/src/util/string.ts @@ -1,3 +1,15 @@ export function camelToDataKebab(str: string): string { return "data-" + str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); } + +export function filenameFromURL(url: string): string { + const parts = url.split("/"); + if ( + !parts.length || // invalid? + parts[parts.length - 1] === "" // for example, URL ends in a directory-like trailing slash + ) { + // in this case just return the original url + return url; + } + return parts[parts.length - 1]; +} diff --git a/packages/mantine/package.json b/packages/mantine/package.json index f6a27d5f86..05646b3968 100644 --- a/packages/mantine/package.json +++ b/packages/mantine/package.json @@ -55,7 +55,7 @@ "@mantine/utils": "^6.0.21", "react": "^18", "react-dom": "^18", - "react-icons": "^4.3.1", + "react-icons": "^5.2.1", "use-prefers-color-scheme": "^1.1.3" }, "devDependencies": { diff --git a/packages/mantine/src/index.tsx b/packages/mantine/src/index.tsx index f0ade60fd2..6a1c7e8c14 100644 --- a/packages/mantine/src/index.tsx +++ b/packages/mantine/src/index.tsx @@ -52,7 +52,7 @@ export const components: Components = { Button: ToolbarButton, Select: ToolbarSelect, }, - ImagePanel: { + FilePanel: { Root: Panel, Button: PanelButton, FileInput: PanelFileInput, diff --git a/packages/mantine/src/panel/Panel.tsx b/packages/mantine/src/panel/Panel.tsx index 6f15c3dba6..e0f71267d0 100644 --- a/packages/mantine/src/panel/Panel.tsx +++ b/packages/mantine/src/panel/Panel.tsx @@ -6,7 +6,7 @@ import { forwardRef } from "react"; export const Panel = forwardRef< HTMLDivElement, - ComponentProps["ImagePanel"]["Root"] + ComponentProps["FilePanel"]["Root"] >((props, ref) => { const { className, diff --git a/packages/mantine/src/panel/PanelButton.tsx b/packages/mantine/src/panel/PanelButton.tsx index 3db527ab05..55eae4acbc 100644 --- a/packages/mantine/src/panel/PanelButton.tsx +++ b/packages/mantine/src/panel/PanelButton.tsx @@ -6,7 +6,7 @@ import { forwardRef } from "react"; export const PanelButton = forwardRef< HTMLButtonElement, - ComponentProps["ImagePanel"]["Button"] + ComponentProps["FilePanel"]["Button"] >((props, ref) => { const { className, children, onClick, label, ...rest } = props; diff --git a/packages/mantine/src/panel/PanelFileInput.tsx b/packages/mantine/src/panel/PanelFileInput.tsx index a71206da29..c90221035e 100644 --- a/packages/mantine/src/panel/PanelFileInput.tsx +++ b/packages/mantine/src/panel/PanelFileInput.tsx @@ -6,9 +6,9 @@ import { forwardRef } from "react"; export const PanelFileInput = forwardRef< HTMLButtonElement, - ComponentProps["ImagePanel"]["FileInput"] + ComponentProps["FilePanel"]["FileInput"] >((props, ref) => { - const { className, value, placeholder, onChange, ...rest } = props; + const { className, accept, value, placeholder, onChange, ...rest } = props; assertEmpty(rest); @@ -17,6 +17,7 @@ export const PanelFileInput = forwardRef< size={"xs"} className={className} ref={ref} + accept={accept} value={value} placeholder={placeholder} onChange={onChange} diff --git a/packages/mantine/src/panel/PanelTab.tsx b/packages/mantine/src/panel/PanelTab.tsx index 224d299cea..6f5a83b12b 100644 --- a/packages/mantine/src/panel/PanelTab.tsx +++ b/packages/mantine/src/panel/PanelTab.tsx @@ -4,7 +4,7 @@ import { forwardRef } from "react"; export const PanelTab = forwardRef< HTMLDivElement, - ComponentProps["ImagePanel"]["TabPanel"] + ComponentProps["FilePanel"]["TabPanel"] >((props, ref) => { const { className, children, ...rest } = props; diff --git a/packages/mantine/src/panel/PanelTextInput.tsx b/packages/mantine/src/panel/PanelTextInput.tsx index e68efaf18e..ee90a60fa1 100644 --- a/packages/mantine/src/panel/PanelTextInput.tsx +++ b/packages/mantine/src/panel/PanelTextInput.tsx @@ -6,7 +6,7 @@ import { forwardRef } from "react"; export const PanelTextInput = forwardRef< HTMLInputElement, - ComponentProps["ImagePanel"]["TextInput"] + ComponentProps["FilePanel"]["TextInput"] >((props, ref) => { const { className, value, placeholder, onKeyDown, onChange, ...rest } = props; diff --git a/packages/react/package.json b/packages/react/package.json index 7890c5a149..df19f05bbd 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -57,7 +57,7 @@ "lodash.merge": "^4.6.2", "react": "^18", "react-dom": "^18", - "react-icons": "^4.3.1", + "react-icons": "^5.2.1", "use-prefers-color-scheme": "^1.1.3" }, "devDependencies": { diff --git a/packages/react/src/blocks/AudioBlockContent/AudioBlockContent.tsx b/packages/react/src/blocks/AudioBlockContent/AudioBlockContent.tsx new file mode 100644 index 0000000000..153dd44fc0 --- /dev/null +++ b/packages/react/src/blocks/AudioBlockContent/AudioBlockContent.tsx @@ -0,0 +1,90 @@ +import { FileBlockConfig, audioBlockConfig, audioParse } from "@blocknote/core"; +import { RiVolumeUpFill } from "react-icons/ri"; + +import { + ReactCustomBlockRenderProps, + createReactBlockSpec, +} from "../../schema/ReactBlockSpec"; +import { + AddFileButton, + DefaultFilePreview, + FigureWithCaption, + FileAndCaptionWrapper, + LinkWithCaption, +} from "../FileBlockContent/fileBlockHelpers"; + +export const AudioPreview = ( + props: Omit< + ReactCustomBlockRenderProps, + "contentRef" + > +) => ( +