refactor: Remove useDictionary hook in favor of i18next t directly (#12282)

Plumbed `dictionary` props through editor components, menus, extensions,
and nodes. Replaces with `useTranslation()` in React contexts and direct
`t` imports from i18next elsewhere.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Tom Moor
2026-05-06 20:24:50 -04:00
committed by GitHub
parent 4883071059
commit 0f3f7b8da7
35 changed files with 307 additions and 530 deletions
+7 -15
View File
@@ -3,6 +3,7 @@ import { observer } from "mobx-react";
import { DOMParser as ProsemirrorDOMParser } from "prosemirror-model";
import { TextSelection } from "prosemirror-state";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { mergeRefs } from "react-merge-refs";
import type { Optional } from "utility-types";
@@ -16,7 +17,6 @@ import ClickablePadding from "~/components/ClickablePadding";
import ErrorBoundary from "~/components/ErrorBoundary";
import type { Props as EditorProps, Editor as SharedEditor } from "~/editor";
import useCurrentUser from "~/hooks/useCurrentUser";
import useDictionary from "~/hooks/useDictionary";
import useEditorClickHandlers from "~/hooks/useEditorClickHandlers";
import useEmbeds from "~/hooks/useEmbeds";
import useStores from "~/hooks/useStores";
@@ -28,12 +28,7 @@ const LazyLoadedEditor = lazyWithRetry(() => import("~/editor"));
export type Props = Optional<
EditorProps,
| "placeholder"
| "defaultValue"
| "onClickLink"
| "embeds"
| "dictionary"
| "extensions"
"placeholder" | "defaultValue" | "onClickLink" | "embeds" | "extensions"
> & {
embedsDisabled?: boolean;
onSynced?: () => Promise<void>;
@@ -52,7 +47,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
} = props;
const { comments } = useStores();
const { shareId } = useShare();
const dictionary = useDictionary();
const { t } = useTranslation();
const embeds = useEmbeds(!shareId);
const localRef = React.useRef<SharedEditor>();
const preferences = useCurrentUser({ rejectOnEmpty: false })?.preferences;
@@ -95,11 +90,11 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
const handleFileUploadStart = React.useCallback(() => {
uploadState.current.timeoutId = setTimeout(() => {
uploadState.current.toastId = toast.loading(
dictionary.uploadingWithProgress(0)
t("Uploading… {{ progress }}%", { progress: 0 })
);
}, 2000);
onFileUploadStart?.();
}, [onFileUploadStart, dictionary]);
}, [onFileUploadStart, t]);
const handleFileUploadProgress = React.useCallback(
(fileId: string, fractionComplete: number) => {
@@ -113,12 +108,12 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
// Update toast if visible
if (uploadState.current.toastId) {
toast.loading(dictionary.uploadingWithProgress(percent), {
toast.loading(t("Uploading… {{ progress }}%", { progress: percent }), {
id: uploadState.current.toastId,
});
}
},
[dictionary]
[t]
);
const handleFileUploadStop = React.useCallback(() => {
@@ -183,7 +178,6 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
onFileUploadStart: handleFileUploadStart,
onFileUploadStop: handleFileUploadStop,
onFileUploadProgress: handleFileUploadProgress,
dictionary,
isAttachment,
});
},
@@ -192,7 +186,6 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
handleFileUploadStart,
handleFileUploadStop,
handleFileUploadProgress,
dictionary,
handleUploadFile,
]
);
@@ -289,7 +282,6 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
uploadFile={handleUploadFile}
embeds={embeds}
userPreferences={preferences}
dictionary={dictionary}
{...props}
onClickLink={handleClickLink}
onChange={handleChange}
+3 -4
View File
@@ -8,7 +8,6 @@ import type { MenuItem } from "@shared/editor/types";
import { ProsemirrorHelper } from "@shared/utils/ProsemirrorHelper";
import { TextHelper } from "@shared/utils/TextHelper";
import useCurrentUser from "~/hooks/useCurrentUser";
import useDictionary from "~/hooks/useDictionary";
import useStores from "~/hooks/useStores";
import getMenuItems from "../menus/block";
import { useEditor } from "./EditorContext";
@@ -108,19 +107,19 @@ type Props = Omit<SuggestionsMenuProps, "renderMenuItem" | "items"> &
Required<Pick<SuggestionsMenuProps, "embeds">>;
function BlockMenu(props: Props) {
const dictionary = useDictionary();
const { t } = useTranslation();
const { elementRef } = useEditor();
const templateMenuItem = useTemplateMenuItem();
const items = useMemo(() => {
const baseItems = getMenuItems(dictionary, elementRef);
const baseItems = getMenuItems(t, elementRef);
if (!templateMenuItem) {
return baseItems;
}
return [...baseItems, { name: "separator" } as MenuItem, templateMenuItem];
}, [dictionary, elementRef, templateMenuItem]);
}, [t, elementRef, templateMenuItem]);
const renderMenuItem = useCallback(
(item, _index, options) => (
+6 -7
View File
@@ -10,6 +10,7 @@ import type { Mark } from "prosemirror-model";
import type { EditorView } from "prosemirror-view";
import * as React from "react";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import Icon from "@shared/components/Icon";
import { hideScrollbars, s } from "@shared/styles";
@@ -18,7 +19,6 @@ import DocumentBreadcrumb from "~/components/DocumentBreadcrumb";
import Flex from "~/components/Flex";
import { ResizingHeightContainer } from "~/components/ResizingHeightContainer";
import Scrollable from "~/components/Scrollable";
import type { Dictionary } from "~/hooks/useDictionary";
import useRequest from "~/hooks/useRequest";
import useStores from "~/hooks/useStores";
import { client } from "~/utils/ApiClient";
@@ -31,7 +31,6 @@ import { useEditor } from "./EditorContext";
type Props = {
mark?: Mark;
dictionary: Dictionary;
view: EditorView;
autoFocus?: boolean;
onLinkAdd: () => void;
@@ -44,7 +43,6 @@ type Props = {
const LinkEditor: React.FC<Props> = ({
mark,
dictionary,
view,
autoFocus,
onLinkAdd,
@@ -54,6 +52,7 @@ const LinkEditor: React.FC<Props> = ({
onClickOutside,
onClickBack,
}) => {
const { t } = useTranslation();
const getHref = () => sanitizeUrl(mark?.attrs.href) ?? "";
const initialValue = getHref();
const { commands } = useEditor();
@@ -183,21 +182,21 @@ const LinkEditor: React.FC<Props> = ({
const isInternal = isInternalUrl(query);
const actions = [
{
tooltip: isInternal ? dictionary.goToLink : dictionary.openLink,
tooltip: isInternal ? t("Go to link") : t("Open link"),
icon: isInternal ? <ArrowIcon /> : <OpenIcon />,
visible: true,
disabled: !query,
handler: openLink,
},
{
tooltip: dictionary.removeLink,
tooltip: t("Remove link"),
icon: <CloseIcon />,
visible: view.editable,
disabled: false,
handler: removeLink,
},
{
tooltip: dictionary.formattingControls,
tooltip: t("Formatting controls"),
icon: <ReturnIcon />,
visible: view.editable,
disabled: false,
@@ -211,7 +210,7 @@ const LinkEditor: React.FC<Props> = ({
<Input
ref={inputRef}
value={query}
placeholder={dictionary.searchOrPasteLink}
placeholder={`${t("Search or paste a link")}`}
onKeyDown={handleKeyDown}
onChange={handleSearch}
onFocus={handleSearch}
+5 -8
View File
@@ -3,18 +3,17 @@ import type { Node } from "prosemirror-model";
import { Selection, TextSelection } from "prosemirror-state";
import type { EditorView } from "prosemirror-view";
import { useCallback, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import Flex from "~/components/Flex";
import Tooltip from "~/components/Tooltip";
import Input from "~/editor/components/Input";
import type { Dictionary } from "~/hooks/useDictionary";
import ToolbarButton from "./ToolbarButton";
import useOnClickOutside from "~/hooks/useOnClickOutside";
type Props = {
node?: Node;
view: EditorView;
dictionary: Dictionary;
autoFocus?: boolean;
onLinkUpdate: () => void;
onLinkRemove: () => void;
@@ -25,12 +24,12 @@ type Props = {
export function MediaLinkEditor({
node,
view,
dictionary,
onLinkUpdate,
onLinkRemove,
onEscape,
onClickOutside,
}: Props) {
const { t } = useTranslation();
const url = (node?.attrs.href ?? node?.attrs.src) as string;
const [localUrl, setLocalUrl] = useState(url);
const wrapperRef = useRef<HTMLDivElement>(null);
@@ -111,12 +110,12 @@ export function MediaLinkEditor({
<Input
autoFocus={isEditingImgUrl}
value={localUrl}
placeholder={dictionary.pasteLink}
placeholder={`${t("Paste a link")}`}
onChange={(e) => setLocalUrl(e.target.value)}
onKeyDown={handleKeyDown}
readOnly={!view.editable}
/>
<Tooltip content={dictionary.openLink}>
<Tooltip content={t("Open link")}>
<ToolbarButton onClick={openLink} disabled={!localUrl}>
<OpenIcon />
</ToolbarButton>
@@ -124,9 +123,7 @@ export function MediaLinkEditor({
{view.editable && (
<Tooltip
content={
node.type.name === "embed"
? dictionary.deleteEmbed
: dictionary.deleteImage
node.type.name === "embed" ? t("Delete embed") : t("Delete image")
}
>
<ToolbarButton onClick={remove}>
+12 -14
View File
@@ -2,6 +2,7 @@ import type { EditorState, Selection } from "prosemirror-state";
import Suggestion from "~/editor/extensions/Suggestion";
import { NodeSelection, TextSelection } from "prosemirror-state";
import * as React from "react";
import { useTranslation } from "react-i18next";
import filterExcessSeparators from "@shared/editor/lib/filterExcessSeparators";
import {
getMarkRange,
@@ -17,7 +18,6 @@ import {
} from "@shared/editor/queries/table";
import type { MenuItem } from "@shared/editor/types";
import useBoolean from "~/hooks/useBoolean";
import useDictionary from "~/hooks/useDictionary";
import useEventListener from "~/hooks/useEventListener";
import useMobile from "~/hooks/useMobile";
import getAttachmentMenuItems from "../menus/attachment";
@@ -82,7 +82,7 @@ enum Toolbar {
export function SelectionToolbar(props: Props) {
const { readOnly = false } = props;
const { view, extensions, commands } = useEditor();
const dictionary = useDictionary();
const { t } = useTranslation();
const menuRef = React.useRef<HTMLDivElement | null>(null);
const isMobile = useMobile();
const isActive = props.isActive || isMobile;
@@ -244,32 +244,32 @@ export function SelectionToolbar(props: Props) {
isCodeSelection &&
(selection.empty || selection instanceof NodeSelection)
) {
items = getCodeMenuItems(state, readOnly, dictionary);
items = getCodeMenuItems(state, readOnly, t);
align = "end";
} else if (isTableSelected(state)) {
items = getTableMenuItems(state, readOnly, dictionary);
items = getTableMenuItems(state, readOnly, t);
} else if (colIndex !== undefined) {
items = getTableColMenuItems(state, readOnly, dictionary, {
items = getTableColMenuItems(state, readOnly, t, {
index: colIndex,
rtl,
});
} else if (rowIndex !== undefined) {
items = getTableRowMenuItems(state, readOnly, dictionary, {
items = getTableRowMenuItems(state, readOnly, t, {
index: rowIndex,
});
} else if (isImageSelection) {
items = getImageMenuItems(state, readOnly, dictionary);
items = getImageMenuItems(state, readOnly, t);
} else if (isAttachmentSelection) {
items = getAttachmentMenuItems(state, readOnly, dictionary);
items = getAttachmentMenuItems(state, readOnly, t);
} else if (isDividerSelection) {
items = getDividerMenuItems(state, readOnly, dictionary);
items = getDividerMenuItems(state, readOnly, t);
} else if (readOnly) {
items = getReadOnlyMenuItems(state, !!canUpdate, dictionary);
items = getReadOnlyMenuItems(state, !!canUpdate, t);
} else if (isNoticeSelection && selection.empty) {
items = getNoticeMenuItems(state, readOnly, dictionary);
items = getNoticeMenuItems(state, readOnly, t);
align = "end";
} else {
items = getFormattingMenuItems(state, isTemplate, dictionary);
items = getFormattingMenuItems(state, isTemplate, t);
}
// Some extensions may be disabled, remove corresponding items
@@ -332,7 +332,6 @@ export function SelectionToolbar(props: Props) {
{activeToolbar === Toolbar.Link ? (
<LinkEditor
key={`link-${selection.anchor}`}
dictionary={dictionary}
autoFocus={autoFocusLinkInput}
view={view}
mark={linkMark ? linkMark.mark : undefined}
@@ -350,7 +349,6 @@ export function SelectionToolbar(props: Props) {
"node" in selection ? (selection as NodeSelection).node : undefined
}
view={view}
dictionary={dictionary}
onLinkUpdate={() => setActiveToolbar(null)}
onLinkRemove={() => setActiveToolbar(null)}
onEscape={() => setActiveToolbar(Toolbar.Menu)}
+10 -9
View File
@@ -27,7 +27,6 @@ import {
} from "~/components/primitives/Popover";
import { MouseSafeArea } from "~/components/MouseSafeArea";
import Scrollable from "~/components/Scrollable";
import useDictionary from "~/hooks/useDictionary";
import useMobile from "~/hooks/useMobile";
import Logger from "~/utils/Logger";
import { useEditor } from "./EditorContext";
@@ -63,7 +62,6 @@ export type Props<T extends MenuItem = MenuItem> = {
function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
const { view, commands, props: editorProps } = useEditor();
const dictionary = useDictionary();
const { t } = useTranslation();
const isMobile = useMobile();
const pointerRef = React.useRef<{ clientX: number; clientY: number }>({
@@ -301,7 +299,7 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
const matches = "matcher" in insertItem && insertItem.matcher(href);
if (!matches) {
toast.error(dictionary.embedInvalidLink);
toast.error(t("Sorry, that link wont work for this embed type"));
return;
}
@@ -390,7 +388,6 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
onFileUploadStart,
onFileUploadStop,
onFileUploadProgress,
dictionary,
isAttachment: inputRef.current?.accept === "*",
attrs,
});
@@ -901,7 +898,7 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
})}
{items.length === 0 && (
<ListItem>
<Empty>{dictionary.noResults}</Empty>
<Empty>{t("No results")}</Empty>
</ListItem>
)}
</>
@@ -925,8 +922,10 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
"placeholder" in insertItem && !!insertItem.placeholder
? insertItem.placeholder
: insertItem.title
? dictionary.pasteLinkWithTitle(insertItem.title)
: dictionary.pasteLink
? t("Paste a {{service}} link…", {
service: insertItem.title,
})
: `${t("Paste a link")}`
}
onKeyDown={handleLinkInputKeydown}
onPaste={handleLinkInputPaste}
@@ -975,8 +974,10 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
"placeholder" in insertItem && !!insertItem.placeholder
? insertItem.placeholder
: insertItem.title
? dictionary.pasteLinkWithTitle(insertItem.title)
: dictionary.pasteLink
? t("Paste a {{service}} link…", {
service: insertItem.title,
})
: `${t("Paste a link")}`
}
onKeyDown={handleLinkInputKeydown}
onPaste={handleLinkInputPaste}
+4 -13
View File
@@ -1,3 +1,4 @@
import { t } from "i18next";
import { action } from "mobx";
import { PlusIcon } from "outline-icons";
import { Plugin } from "prosemirror-state";
@@ -6,20 +7,10 @@ import ReactDOM from "react-dom";
import type { WidgetProps } from "@shared/editor/lib/Extension";
import { PlaceholderPlugin } from "@shared/editor/plugins/PlaceholderPlugin";
import { findParentNode } from "@shared/editor/queries/findParentNode";
import type { Dictionary } from "~/hooks/useDictionary";
import type { SuggestionOptions } from "~/editor/extensions/Suggestion";
import Suggestion from "~/editor/extensions/Suggestion";
import BlockMenu from "../components/BlockMenu";
/**
* Options for the BlockMenu extension.
*/
type BlockMenuOptions = SuggestionOptions & {
/** A dictionary of translated strings used in the editor. */
dictionary: Dictionary;
};
export default class BlockMenuExtension extends Suggestion<BlockMenuOptions> {
export default class BlockMenuExtension extends Suggestion {
get defaultOptions() {
return {
trigger: "/",
@@ -90,14 +81,14 @@ export default class BlockMenuExtension extends Suggestion<BlockMenuOptions> {
!!textContent &&
node.childCount === 0 &&
node.textContent === "",
text: this.options.dictionary.newLineEmpty,
text: `${t("Type '/' to insert")}`,
},
{
condition: ({ node, $start, state }) =>
$start.depth === 1 &&
state.selection.$from.pos === $start.pos + node.content.size &&
node.textContent === "/",
text: ` ${this.options.dictionary.newLineWithSlash}`,
text: ` ${t("Keep typing to filter")}`,
},
]),
];
-3
View File
@@ -46,7 +46,6 @@ import EventEmitter from "@shared/utils/events";
import type Document from "~/models/Document";
import Flex from "~/components/Flex";
import { PortalContext } from "~/components/Portal";
import type { Dictionary } from "~/hooks/useDictionary";
import type { Properties } from "~/types";
import Logger from "~/utils/Logger";
import ComponentView from "./components/ComponentView";
@@ -89,8 +88,6 @@ export type Props = {
canUpdate?: boolean;
/** If the editor should still allow commenting when it is readOnly */
canComment?: boolean;
/** A dictionary of translated strings used in the editor */
dictionary: Dictionary;
/** The reading direction of the text content, if known */
dir?: "rtl" | "ltr";
/** If the editor should vertically grow to fill available space */
+7 -7
View File
@@ -1,13 +1,13 @@
import type { TFunction } from "i18next";
import { TrashIcon, DownloadIcon, ReplaceIcon, PDFIcon } from "outline-icons";
import type { EditorState } from "prosemirror-state";
import type { MenuItem } from "@shared/editor/types";
import type { Dictionary } from "~/hooks/useDictionary";
import { isNodeActive } from "@shared/editor/queries/isNodeActive";
export default function attachmentMenuItems(
state: EditorState,
readOnly: boolean,
dictionary: Dictionary
t: TFunction
): MenuItem[] {
if (readOnly) {
return [];
@@ -24,17 +24,17 @@ export default function attachmentMenuItems(
return [
{
name: "replaceAttachment",
tooltip: dictionary.replaceAttachment,
tooltip: t("Replace file"),
icon: <ReplaceIcon />,
},
{
name: "deleteAttachment",
tooltip: dictionary.deleteAttachment,
tooltip: t("Delete file"),
icon: <TrashIcon />,
},
{
name: "toggleAttachmentPreview",
tooltip: dictionary.previewAttachment,
tooltip: t("Show preview"),
icon: <PDFIcon />,
active: isAttachmentWithPreview,
visible: isPdfAttachment(state),
@@ -44,7 +44,7 @@ export default function attachmentMenuItems(
},
{
name: "dimensions",
tooltip: dictionary.dimensions,
tooltip: `${t("Width")} × ${t("Height")}`,
visible: isAttachmentWithPreview(state),
skipIcon: true,
},
@@ -53,7 +53,7 @@ export default function attachmentMenuItems(
},
{
name: "downloadAttachment",
label: dictionary.download,
label: t("Download"),
icon: <DownloadIcon />,
visible: !!fetch,
},
+27 -27
View File
@@ -26,10 +26,10 @@ import {
} from "outline-icons";
import * as React from "react";
import styled from "styled-components";
import type { TFunction } from "i18next";
import Image from "@shared/editor/components/Img";
import type { MenuItem } from "@shared/editor/types";
import { metaDisplay } from "@shared/utils/keyboard";
import type { Dictionary } from "~/hooks/useDictionary";
import Desktop from "~/utils/Desktop";
const Img = styled(Image)`
@@ -42,7 +42,7 @@ const Img = styled(Image)`
`;
export default function blockMenuItems(
dictionary: Dictionary,
t: TFunction,
documentRef: React.RefObject<HTMLDivElement>
): MenuItem[] {
const documentWidth = documentRef.current?.clientWidth ?? 0;
@@ -50,7 +50,7 @@ export default function blockMenuItems(
const items = [
{
name: "heading",
title: dictionary.h1,
title: t("Big heading"),
keywords: "h1 heading1 title",
icon: <Heading1Icon />,
shortcut: "^ ⇧ 1",
@@ -58,7 +58,7 @@ export default function blockMenuItems(
},
{
name: "heading",
title: dictionary.h2,
title: t("Medium heading"),
keywords: "h2 heading2",
icon: <Heading2Icon />,
shortcut: "^ ⇧ 2",
@@ -66,7 +66,7 @@ export default function blockMenuItems(
},
{
name: "heading",
title: dictionary.h3,
title: t("Small heading"),
keywords: "h3 heading3",
icon: <Heading3Icon />,
shortcut: "^ ⇧ 3",
@@ -74,7 +74,7 @@ export default function blockMenuItems(
},
{
name: "heading",
title: dictionary.h4,
title: t("Extra small heading"),
keywords: "h4 heading4",
icon: <Heading4Icon />,
shortcut: "^ ⇧ 4",
@@ -85,20 +85,20 @@ export default function blockMenuItems(
},
{
name: "checkbox_list",
title: dictionary.checkboxList,
title: t("Todo list"),
icon: <TodoListIcon />,
keywords: "checklist checkbox task",
shortcut: "^ ⇧ 7",
},
{
name: "bullet_list",
title: dictionary.bulletList,
title: t("Bulleted list"),
icon: <BulletedListIcon />,
shortcut: "^ ⇧ 8",
},
{
name: "ordered_list",
title: dictionary.orderedList,
title: t("Ordered list"),
icon: <OrderedListIcon />,
shortcut: "^ ⇧ 9",
},
@@ -107,19 +107,19 @@ export default function blockMenuItems(
},
{
name: "image",
title: dictionary.image,
title: t("Image"),
icon: <ImageIcon />,
keywords: "picture photo",
},
{
name: "video",
title: dictionary.video,
title: t("Video"),
icon: <EmbedIcon />,
keywords: "mov avi upload player",
},
{
name: "attachment",
title: dictionary.pdf,
title: t("Embed PDF"),
icon: <PDFIcon />,
keywords: "pdf upload attach",
attrs: {
@@ -131,13 +131,13 @@ export default function blockMenuItems(
},
{
name: "attachment",
title: dictionary.file,
title: t("File attachment"),
icon: <AttachmentIcon />,
keywords: "file upload attach",
},
{
name: "table",
title: dictionary.table,
title: t("Table"),
icon: <TableIcon />,
attrs: {
rowsCount: 3,
@@ -147,59 +147,59 @@ export default function blockMenuItems(
},
{
name: "blockquote",
title: dictionary.quote,
title: t("Quote"),
icon: <BlockQuoteIcon />,
keywords: "blockquote pullquote",
shortcut: `${metaDisplay} ]`,
},
{
name: "code_block",
title: dictionary.codeBlock,
title: t("Code block"),
icon: <CodeIcon />,
shortcut: "^ ⇧ c",
keywords: "script",
},
{
name: "math_block",
title: dictionary.mathBlock,
title: t("Math block (LaTeX)"),
icon: <MathIcon />,
keywords: "math katex latex",
},
{
name: "container_toggle",
title: dictionary.toggleBlock,
title: t("Toggle block"),
icon: <CollapseIcon />,
keywords: "toggle collapsible collapse fold",
},
{
name: "hr",
title: dictionary.hr,
title: t("Divider"),
icon: <HorizontalRuleIcon />,
shortcut: `${metaDisplay} _`,
keywords: "horizontal rule break line",
},
{
name: "hr",
title: dictionary.pageBreak,
title: t("Page break"),
icon: <PageBreakIcon />,
keywords: "page print break line",
attrs: { markup: "***" },
},
{
name: "date",
title: dictionary.insertDate,
title: t("Current date"),
keywords: "clock today",
icon: <CalendarIcon />,
},
{
name: "time",
title: dictionary.insertTime,
title: t("Current time"),
keywords: "clock now",
icon: <ClockIcon />,
},
{
name: "datetime",
title: dictionary.insertDateTime,
title: t("Current date and time"),
keywords: "clock today date",
icon: <CalendarIcon />,
},
@@ -208,28 +208,28 @@ export default function blockMenuItems(
},
{
name: "container_notice",
title: dictionary.infoNotice,
title: t("Info notice"),
icon: <InfoIcon />,
keywords: "notice card information",
attrs: { style: "info" },
},
{
name: "container_notice",
title: dictionary.successNotice,
title: t("Success notice"),
icon: <DoneIcon />,
keywords: "notice card success",
attrs: { style: "success" },
},
{
name: "container_notice",
title: dictionary.warningNotice,
title: t("Warning notice"),
icon: <WarningIcon />,
keywords: "notice card error",
attrs: { style: "warning" },
},
{
name: "container_notice",
title: dictionary.tipNotice,
title: t("Tip notice"),
icon: <StarredIcon />,
keywords: "notice card suggestion",
attrs: { style: "tip" },
+5 -5
View File
@@ -12,14 +12,14 @@ import {
getLabelForLanguage,
} from "@shared/editor/lib/code";
import { isMermaid } from "@shared/editor/lib/isCode";
import type { TFunction } from "i18next";
import type { MenuItem } from "@shared/editor/types";
import type { Dictionary } from "~/hooks/useDictionary";
import { metaDisplay } from "@shared/utils/keyboard";
export default function codeMenuItems(
state: EditorState,
readOnly: boolean | undefined,
dictionary: Dictionary
t: TFunction
): MenuItem[] {
const node =
state.selection instanceof NodeSelection
@@ -59,7 +59,7 @@ export default function codeMenuItems(
label: readOnly
? getLabelForLanguage(node.attrs.language ?? "none")
: undefined,
tooltip: dictionary.copy,
tooltip: t("Copy"),
},
{
name: "separator",
@@ -67,7 +67,7 @@ export default function codeMenuItems(
{
name: "edit_mermaid",
icon: <EditIcon />,
tooltip: dictionary.editDiagram,
tooltip: t("Edit diagram"),
shortcut: `${metaDisplay} Enter`,
visible: isMermaid(node) && !isEditingMermaid && !readOnly,
},
@@ -77,7 +77,7 @@ export default function codeMenuItems(
{
name: "toggleCodeBlockWrap",
icon: <TextWrapIcon />,
tooltip: dictionary.wrapText,
tooltip: t("Wrap text"),
active: () => node.attrs.wrap,
visible: !readOnly && (!isMermaid(node) || isEditingMermaid),
},
+4 -4
View File
@@ -1,13 +1,13 @@
import type { TFunction } from "i18next";
import { PageBreakIcon, HorizontalRuleIcon } from "outline-icons";
import type { EditorState } from "prosemirror-state";
import { isNodeActive } from "@shared/editor/queries/isNodeActive";
import type { MenuItem } from "@shared/editor/types";
import type { Dictionary } from "~/hooks/useDictionary";
export default function dividerMenuItems(
state: EditorState,
readOnly: boolean,
dictionary: Dictionary
t: TFunction
): MenuItem[] {
if (readOnly) {
return [];
@@ -17,14 +17,14 @@ export default function dividerMenuItems(
return [
{
name: "hr",
tooltip: dictionary.hr,
tooltip: t("Divider"),
attrs: { markup: "---" },
active: isNodeActive(schema.nodes.hr, { markup: "---" }),
icon: <HorizontalRuleIcon />,
},
{
name: "hr",
tooltip: dictionary.pageBreak,
tooltip: t("Page break"),
attrs: { markup: "***" },
active: isNodeActive(schema.nodes.hr, { markup: "***" }),
icon: <PageBreakIcon />,
+29 -29
View File
@@ -35,8 +35,8 @@ import { isMarkActive } from "@shared/editor/queries/isMarkActive";
import { isNodeActive } from "@shared/editor/queries/isNodeActive";
import type { MenuItem } from "@shared/editor/types";
import { metaDisplay } from "@shared/utils/keyboard";
import type { TFunction } from "i18next";
import CircleIcon from "~/components/Icons/CircleIcon";
import type { Dictionary } from "~/hooks/useDictionary";
import {
isMobile as isMobileDevice,
isTouchDevice,
@@ -57,7 +57,7 @@ import { DottedCircleIcon } from "~/components/Icons/DottedCircleIcon";
export default function formattingMenuItems(
state: EditorState,
isTemplate: boolean,
dictionary: Dictionary
t: TFunction
): MenuItem[] {
const { schema } = state;
const isCode = isInCode(state);
@@ -86,7 +86,7 @@ export default function formattingMenuItems(
return [
{
name: "placeholder",
tooltip: dictionary.placeholder,
tooltip: t("Placeholder"),
icon: <InputIcon />,
active: isMarkActive(schema.marks.placeholder),
visible: isTemplate && (!isMobile || !isEmpty),
@@ -97,7 +97,7 @@ export default function formattingMenuItems(
},
{
name: "strong",
tooltip: dictionary.strong,
tooltip: t("Bold"),
shortcut: `${metaDisplay}+B`,
icon: <BoldIcon />,
active: isMarkActive(schema.marks.strong),
@@ -105,7 +105,7 @@ export default function formattingMenuItems(
},
{
name: "em",
tooltip: dictionary.em,
tooltip: t("Italic"),
shortcut: `${metaDisplay}+I`,
icon: <ItalicIcon />,
active: isMarkActive(schema.marks.em),
@@ -113,14 +113,14 @@ export default function formattingMenuItems(
},
{
name: "strikethrough",
tooltip: dictionary.strikethrough,
tooltip: t("Strikethrough"),
shortcut: `${metaDisplay}+D`,
icon: <StrikethroughIcon />,
active: isMarkActive(schema.marks.strikethrough),
visible: !isCodeBlock && (!isMobile || !isEmpty),
},
{
tooltip: dictionary.background,
tooltip: t("Background color"),
icon:
getColorSetForSelectedCells(state.selection).size > 1 ? (
<CircleIcon color="rainbow" />
@@ -147,7 +147,7 @@ export default function formattingMenuItems(
return [
{
name: "toggleCellSelectionBackgroundAndCollapseSelection",
label: dictionary.none,
label: t("None"),
icon: <DottedCircleIcon retainColor color="transparent" />,
active: () => (cellSelectionHasBackground ? false : true),
attrs: { color: null },
@@ -215,7 +215,7 @@ export default function formattingMenuItems(
},
},
{
tooltip: dictionary.mark,
tooltip: t("Highlight"),
shortcut: `${metaDisplay}+⇧+H`,
icon: highlight ? (
<CircleIcon
@@ -242,7 +242,7 @@ export default function formattingMenuItems(
? [
{
name: "highlight",
label: dictionary.none,
label: t("None"),
icon: <DottedCircleIcon retainColor color="transparent" />,
active: () => false,
attrs: { color: highlight.mark.attrs.color },
@@ -309,7 +309,7 @@ export default function formattingMenuItems(
},
{
name: "code_inline",
tooltip: dictionary.codeInline,
tooltip: t("Code"),
shortcut: `${metaDisplay}+E`,
icon: <CodeIcon />,
active: isMarkActive(schema.marks.code_inline),
@@ -321,7 +321,7 @@ export default function formattingMenuItems(
},
{
name: "heading",
tooltip: dictionary.heading,
tooltip: t("Heading"),
shortcut: `⇧+Ctrl+1`,
icon: <Heading1Icon />,
active: isNodeActive(schema.nodes.heading, { level: 1 }),
@@ -330,7 +330,7 @@ export default function formattingMenuItems(
},
{
name: "heading",
tooltip: dictionary.subheading,
tooltip: t("Subheading"),
shortcut: `⇧+Ctrl+2`,
icon: <Heading2Icon />,
active: isNodeActive(schema.nodes.heading, { level: 2 }),
@@ -339,7 +339,7 @@ export default function formattingMenuItems(
},
{
name: "heading",
tooltip: dictionary.subheading,
tooltip: t("Subheading"),
shortcut: `⇧+Ctrl+3`,
icon: <Heading3Icon />,
active: isNodeActive(schema.nodes.heading, { level: 3 }),
@@ -348,7 +348,7 @@ export default function formattingMenuItems(
},
{
name: "blockquote",
tooltip: dictionary.quote,
tooltip: t("Quote"),
shortcut: `${metaDisplay}+]`,
icon: <BlockQuoteIcon />,
active: isNodeActive(schema.nodes.blockquote),
@@ -360,20 +360,20 @@ export default function formattingMenuItems(
},
{
name: "mergeCells",
tooltip: dictionary.mergeCells,
tooltip: t("Merge cells"),
icon: <TableMergeCellsIcon />,
visible: isMultipleCellSelection(state),
},
{
name: "splitCell",
tooltip: dictionary.splitCell,
tooltip: t("Split cell"),
icon: <TableSplitCellsIcon />,
visible: isMergedCellSelection(state),
},
{
name: "container_toggle",
icon: <CollapseIcon />,
tooltip: dictionary.toggleBlock,
tooltip: t("Toggle block"),
active: isNodeActive(schema.nodes.container_toggle),
attrs: { id: uuidv4() },
visible: !isCodeBlock && (!isMobile || isEmpty),
@@ -383,7 +383,7 @@ export default function formattingMenuItems(
},
{
name: "checkbox_list",
tooltip: dictionary.checkboxList,
tooltip: t("Todo list"),
shortcut: `⇧+Ctrl+7`,
icon: <TodoListIcon />,
keywords: "checklist checkbox task",
@@ -392,7 +392,7 @@ export default function formattingMenuItems(
},
{
name: "bullet_list",
tooltip: dictionary.bulletList,
tooltip: t("Bulleted list"),
shortcut: `⇧+Ctrl+8`,
icon: <BulletedListIcon />,
active: isNodeActive(schema.nodes.bullet_list),
@@ -400,7 +400,7 @@ export default function formattingMenuItems(
},
{
name: "ordered_list",
tooltip: dictionary.orderedList,
tooltip: t("Ordered list"),
shortcut: `⇧+Ctrl+9`,
icon: <OrderedListIcon />,
active: isNodeActive(schema.nodes.ordered_list),
@@ -408,28 +408,28 @@ export default function formattingMenuItems(
},
{
name: "outdentList",
tooltip: dictionary.outdent,
tooltip: t("Outdent"),
shortcut: `⇧+Tab`,
icon: <OutdentIcon />,
visible: isTouch && isList,
},
{
name: "indentList",
tooltip: dictionary.indent,
tooltip: t("Indent"),
shortcut: `Tab`,
icon: <IndentIcon />,
visible: isTouch && isList,
},
{
name: "outdentCheckboxList",
tooltip: dictionary.outdent,
tooltip: t("Outdent"),
shortcut: `⇧+Tab`,
icon: <OutdentIcon />,
visible: isTouch && isInList(state, { types: ["checkbox_list"] }),
},
{
name: "indentCheckboxList",
tooltip: dictionary.indent,
tooltip: t("Indent"),
shortcut: `Tab`,
icon: <IndentIcon />,
visible: isTouch && isInList(state, { types: ["checkbox_list"] }),
@@ -440,7 +440,7 @@ export default function formattingMenuItems(
},
{
name: "addLink",
tooltip: dictionary.createLink,
tooltip: t("Create link"),
shortcut: `${metaDisplay}+K`,
icon: <LinkIcon />,
attrs: { href: "" },
@@ -449,10 +449,10 @@ export default function formattingMenuItems(
},
{
name: "comment",
tooltip: dictionary.comment,
tooltip: t("Comment"),
shortcut: `${metaDisplay}+⌥+M`,
icon: <CommentIcon />,
label: isCodeBlock ? dictionary.comment : undefined,
label: isCodeBlock ? t("Comment") : undefined,
active: isMarkActive(
schema.marks.comment,
{ resolved: false },
@@ -467,7 +467,7 @@ export default function formattingMenuItems(
{
name: "copyToClipboard",
icon: <CopyIcon />,
tooltip: dictionary.copy,
tooltip: t("Copy"),
shortcut: `${metaDisplay}+C`,
visible: isCode && !isCodeBlock && (!isMobile || !isEmpty),
},
+15 -15
View File
@@ -12,8 +12,8 @@ import {
} from "outline-icons";
import type { EditorState } from "prosemirror-state";
import { isNodeActive } from "@shared/editor/queries/isNodeActive";
import type { TFunction } from "i18next";
import type { MenuItem } from "@shared/editor/types";
import type { Dictionary } from "~/hooks/useDictionary";
import { metaDisplay } from "@shared/utils/keyboard";
import { ImageSource } from "@shared/editor/lib/FileHelper";
import Desktop from "~/utils/Desktop";
@@ -22,7 +22,7 @@ import { isMarkActive } from "@shared/editor/queries/isMarkActive";
export default function imageMenuItems(
state: EditorState,
readOnly: boolean,
dictionary: Dictionary
t: TFunction
): MenuItem[] {
if (readOnly) {
return [];
@@ -48,14 +48,14 @@ export default function imageMenuItems(
return [
{
name: "alignLeft",
tooltip: dictionary.alignLeft,
tooltip: t("Align left"),
icon: <AlignImageLeftIcon />,
active: isLeftAligned,
visible: !isEmptyDiagram(state),
},
{
name: "alignCenter",
tooltip: dictionary.alignCenter,
tooltip: t("Align center"),
icon: <AlignImageCenterIcon />,
active: (state) =>
isNodeActive(schema.nodes.image)(state) &&
@@ -66,14 +66,14 @@ export default function imageMenuItems(
},
{
name: "alignRight",
tooltip: dictionary.alignRight,
tooltip: t("Align right"),
icon: <AlignImageRightIcon />,
active: isRightAligned,
visible: !isEmptyDiagram(state),
},
{
name: "alignFullWidth",
tooltip: dictionary.alignFullWidth,
tooltip: t("Full width"),
icon: <AlignFullWidthIcon />,
active: isFullWidthAligned,
visible: !isEmptyDiagram(state),
@@ -83,7 +83,7 @@ export default function imageMenuItems(
},
{
name: "dimensions",
tooltip: dictionary.dimensions,
tooltip: `${t("Width")} × ${t("Height")}`,
visible: !isFullWidthAligned(state) && !isEmptyDiagram(state),
skipIcon: true,
},
@@ -92,34 +92,34 @@ export default function imageMenuItems(
},
{
name: "editDiagram",
tooltip: dictionary.editDiagram,
tooltip: t("Edit diagram"),
icon: <EditIcon />,
visible: isDiagram(state) && !Desktop.isElectron(),
},
{
name: "downloadImage",
tooltip: dictionary.downloadImage,
tooltip: t("Download image"),
icon: <DownloadIcon />,
visible: !!fetch && !isEmptyDiagram(state),
},
{
tooltip: dictionary.replaceImage,
tooltip: t("Replace image"),
icon: <ReplaceIcon />,
visible: !isDiagram(state),
children: [
{
name: "replaceImage",
label: dictionary.uploadImage,
label: t("Upload an image"),
},
{
name: "editImageUrl",
label: dictionary.editImageUrl,
label: t("Edit image URL"),
},
],
},
{
name: "deleteImage",
tooltip: dictionary.deleteImage,
tooltip: t("Delete image"),
icon: <TrashIcon />,
},
{
@@ -127,14 +127,14 @@ export default function imageMenuItems(
},
{
name: "linkOnImage",
tooltip: dictionary.createLink,
tooltip: t("Create link"),
shortcut: `${metaDisplay}+K`,
active: isMarkActive(schema.marks.link),
icon: <LinkIcon />,
},
{
name: "commentOnImage",
tooltip: dictionary.comment,
tooltip: t("Comment"),
shortcut: `${metaDisplay}+⌥+M`,
icon: <CommentIcon />,
},
+10 -10
View File
@@ -1,3 +1,4 @@
import type { TFunction } from "i18next";
import {
DoneIcon,
ExpandedIcon,
@@ -8,21 +9,20 @@ import {
import type { EditorState } from "prosemirror-state";
import { NoticeTypes } from "@shared/editor/nodes/Notice";
import type { MenuItem } from "@shared/editor/types";
import type { Dictionary } from "~/hooks/useDictionary";
export default function noticeMenuItems(
state: EditorState,
readOnly: boolean | undefined,
dictionary: Dictionary
t: TFunction
): MenuItem[] {
const node = state.selection.$from.node(-1);
const currentStyle = node?.attrs.style as NoticeTypes;
const mapping = {
[NoticeTypes.Info]: dictionary.infoNotice,
[NoticeTypes.Warning]: dictionary.warningNotice,
[NoticeTypes.Success]: dictionary.successNotice,
[NoticeTypes.Tip]: dictionary.tipNotice,
[NoticeTypes.Info]: t("Info notice"),
[NoticeTypes.Warning]: t("Warning notice"),
[NoticeTypes.Success]: t("Success notice"),
[NoticeTypes.Tip]: t("Tip notice"),
};
return [
@@ -35,25 +35,25 @@ export default function noticeMenuItems(
{
name: NoticeTypes.Info,
icon: <InfoIcon />,
label: dictionary.infoNotice,
label: t("Info notice"),
active: () => currentStyle === NoticeTypes.Info,
},
{
name: NoticeTypes.Success,
icon: <DoneIcon />,
label: dictionary.successNotice,
label: t("Success notice"),
active: () => currentStyle === NoticeTypes.Success,
},
{
name: NoticeTypes.Warning,
icon: <WarningIcon />,
label: dictionary.warningNotice,
label: t("Warning notice"),
active: () => currentStyle === NoticeTypes.Warning,
},
{
name: NoticeTypes.Tip,
icon: <StarredIcon />,
label: dictionary.tipNotice,
label: t("Tip notice"),
active: () => currentStyle === NoticeTypes.Tip,
},
],
+4 -4
View File
@@ -1,13 +1,13 @@
import type { TFunction } from "i18next";
import { CommentIcon } from "outline-icons";
import type { EditorState } from "prosemirror-state";
import { isMarkActive } from "@shared/editor/queries/isMarkActive";
import type { MenuItem } from "@shared/editor/types";
import type { Dictionary } from "~/hooks/useDictionary";
export default function readOnlyMenuItems(
state: EditorState,
canUpdate: boolean,
dictionary: Dictionary
t: TFunction
): MenuItem[] {
const { schema } = state;
const isEmpty = state.selection.empty;
@@ -16,8 +16,8 @@ export default function readOnlyMenuItems(
{
visible: canUpdate && !isEmpty,
name: "comment",
tooltip: dictionary.comment,
label: dictionary.comment,
tooltip: t("Comment"),
label: t("Comment"),
icon: <CommentIcon />,
active: isMarkActive(schema.marks.comment),
},
+6 -8
View File
@@ -6,14 +6,14 @@ import {
} from "outline-icons";
import type { EditorState } from "prosemirror-state";
import { isNodeActive } from "@shared/editor/queries/isNodeActive";
import type { TFunction } from "i18next";
import type { MenuItem } from "@shared/editor/types";
import { TableLayout } from "@shared/editor/types";
import type { Dictionary } from "~/hooks/useDictionary";
export default function tableMenuItems(
state: EditorState,
readOnly: boolean,
dictionary: Dictionary
t: TFunction
): MenuItem[] {
if (readOnly) {
return [];
@@ -27,16 +27,14 @@ export default function tableMenuItems(
return [
{
name: "setTableAttr",
tooltip: isFullWidth
? dictionary.alignDefaultWidth
: dictionary.alignFullWidth,
tooltip: isFullWidth ? t("Default width") : t("Full width"),
icon: <AlignFullWidthIcon />,
attrs: isFullWidth ? { layout: null } : { layout: TableLayout.fullWidth },
active: () => isFullWidth,
},
{
name: "distributeColumns",
tooltip: dictionary.distributeColumns,
tooltip: t("Distribute columns"),
icon: <TableColumnsDistributeIcon />,
},
{
@@ -44,7 +42,7 @@ export default function tableMenuItems(
},
{
name: "deleteTable",
tooltip: dictionary.deleteTable,
tooltip: t("Delete table"),
icon: <TrashIcon />,
},
{
@@ -52,7 +50,7 @@ export default function tableMenuItems(
},
{
name: "exportTable",
tooltip: dictionary.exportAsCSV,
tooltip: t("Export as CSV"),
label: "CSV",
attrs: { format: "csv", fileName: `${window.document.title}.csv` },
icon: <DownloadIcon />,
+18 -18
View File
@@ -24,8 +24,8 @@ import {
isMultipleCellSelection,
tableHasRowspan,
} from "@shared/editor/queries/table";
import type { TFunction } from "i18next";
import type { MenuItem, NodeAttrMark } from "@shared/editor/types";
import type { Dictionary } from "~/hooks/useDictionary";
import { ArrowLeftIcon, ArrowRightIcon } from "~/components/Icons/ArrowIcon";
import CircleIcon from "~/components/Icons/CircleIcon";
import CellBackgroundColorPicker from "../components/CellBackgroundColorPicker";
@@ -58,7 +58,7 @@ function getColumnColors(state: EditorState, colIndex: number): Set<string> {
export default function tableColMenuItems(
state: EditorState,
readOnly: boolean,
dictionary: Dictionary,
t: TFunction,
options: {
index: number;
rtl: boolean;
@@ -89,7 +89,7 @@ export default function tableColMenuItems(
return [
{
name: "setColumnAttr",
tooltip: dictionary.alignLeft,
tooltip: t("Align left"),
icon: <AlignLeftIcon />,
attrs: { index, alignment: "left" },
active: isNodeActive(schema.nodes.th, {
@@ -100,7 +100,7 @@ export default function tableColMenuItems(
},
{
name: "setColumnAttr",
tooltip: dictionary.alignCenter,
tooltip: t("Align center"),
icon: <AlignCenterIcon />,
attrs: { index, alignment: "center" },
active: isNodeActive(schema.nodes.th, {
@@ -111,7 +111,7 @@ export default function tableColMenuItems(
},
{
name: "setColumnAttr",
tooltip: dictionary.alignRight,
tooltip: t("Align right"),
icon: <AlignRightIcon />,
attrs: { index, alignment: "right" },
active: isNodeActive(schema.nodes.th, {
@@ -125,14 +125,14 @@ export default function tableColMenuItems(
},
{
name: "sortTable",
tooltip: dictionary.sortAsc,
tooltip: t("Sort ascending"),
attrs: { index, direction: "asc" },
icon: <SortAscendingIcon />,
disabled: tableHasRowspan(state),
},
{
name: "sortTable",
tooltip: dictionary.sortDesc,
tooltip: t("Sort descending"),
attrs: { index, direction: "desc" },
icon: <SortDescendingIcon />,
disabled: tableHasRowspan(state),
@@ -141,7 +141,7 @@ export default function tableColMenuItems(
name: "separator",
},
{
tooltip: dictionary.background,
tooltip: t("Background color"),
icon:
colColors.size > 1 ? (
<CircleIcon color="rainbow" />
@@ -154,7 +154,7 @@ export default function tableColMenuItems(
...[
{
name: "toggleColumnBackgroundAndCollapseSelection",
label: dictionary.none,
label: t("None"),
icon: <DottedCircleIcon retainColor color="transparent" />,
active: () => (hasBackground ? false : true),
attrs: { color: null },
@@ -203,32 +203,32 @@ export default function tableColMenuItems(
children: [
{
name: "toggleHeaderColumn",
label: dictionary.toggleHeader,
label: t("Toggle header"),
icon: <TableHeaderColumnIcon />,
visible: index === 0,
},
{
name: rtl ? "addColumnAfter" : "addColumnBefore",
label: rtl ? dictionary.addColumnAfter : dictionary.addColumnBefore,
label: rtl ? t("Insert after") : t("Insert before"),
icon: <InsertLeftIcon />,
attrs: { index },
},
{
name: rtl ? "addColumnBefore" : "addColumnAfter",
label: rtl ? dictionary.addColumnBefore : dictionary.addColumnAfter,
label: rtl ? t("Insert before") : t("Insert after"),
icon: <InsertRightIcon />,
attrs: { index },
},
{
name: "moveTableColumn",
label: dictionary.moveColumnLeft,
label: t("Move left"),
icon: <ArrowLeftIcon />,
attrs: { from: index, to: index - 1 },
visible: index > 0,
},
{
name: "moveTableColumn",
label: dictionary.moveColumnRight,
label: t("Move right"),
icon: <ArrowRightIcon />,
attrs: { from: index, to: index + 1 },
visible: index < tableMap.map.width - 1,
@@ -238,20 +238,20 @@ export default function tableColMenuItems(
},
{
name: "mergeCells",
label: dictionary.mergeCells,
label: t("Merge cells"),
icon: <TableMergeCellsIcon />,
visible: isMultipleCellSelection(state),
},
{
name: "splitCell",
label: dictionary.splitCell,
label: t("Split cell"),
icon: <TableSplitCellsIcon />,
visible: isMergedCellSelection(state),
},
{
name: "distributeColumns",
visible: selectedCols.length > 1,
label: dictionary.distributeColumns,
label: t("Distribute columns"),
icon: <TableColumnsDistributeIcon />,
},
{
@@ -260,7 +260,7 @@ export default function tableColMenuItems(
{
name: "deleteColumn",
dangerous: true,
label: dictionary.deleteColumn,
label: t("Delete"),
icon: <TrashIcon />,
},
],
+12 -12
View File
@@ -15,8 +15,8 @@ import {
isMergedCellSelection,
isMultipleCellSelection,
} from "@shared/editor/queries/table";
import type { TFunction } from "i18next";
import type { MenuItem, NodeAttrMark } from "@shared/editor/types";
import type { Dictionary } from "~/hooks/useDictionary";
import { ArrowDownIcon, ArrowUpIcon } from "~/components/Icons/ArrowIcon";
import CircleIcon from "~/components/Icons/CircleIcon";
import CellBackgroundColorPicker from "../components/CellBackgroundColorPicker";
@@ -49,7 +49,7 @@ function getRowColors(state: EditorState, rowIndex: number): Set<string> {
export default function tableRowMenuItems(
state: EditorState,
readOnly: boolean,
dictionary: Dictionary,
t: TFunction,
options: {
index: number;
}
@@ -77,7 +77,7 @@ export default function tableRowMenuItems(
return [
{
tooltip: dictionary.background,
tooltip: t("Background color"),
icon:
rowColors.size > 1 ? (
<CircleIcon color="rainbow" />
@@ -90,7 +90,7 @@ export default function tableRowMenuItems(
...[
{
name: "toggleRowBackgroundAndCollapseSelection",
label: dictionary.none,
label: t("None"),
icon: <DottedCircleIcon retainColor color="transparent" />,
active: () => (hasBackground ? false : true),
attrs: { color: null },
@@ -139,32 +139,32 @@ export default function tableRowMenuItems(
children: [
{
name: "toggleHeaderRow",
label: dictionary.toggleHeader,
label: t("Toggle header"),
icon: <TableHeaderRowIcon />,
visible: index === 0,
},
{
name: "addRowBefore",
label: dictionary.addRowBefore,
label: t("Insert before"),
icon: <InsertAboveIcon />,
attrs: { index },
},
{
name: "addRowAfter",
label: dictionary.addRowAfter,
label: t("Insert after"),
icon: <InsertBelowIcon />,
attrs: { index },
},
{
name: "moveTableRow",
label: dictionary.moveRowUp,
label: t("Move up"),
icon: <ArrowUpIcon />,
attrs: { from: index, to: index - 1 },
visible: index > 0,
},
{
name: "moveTableRow",
label: dictionary.moveRowDown,
label: t("Move down"),
icon: <ArrowDownIcon />,
attrs: { from: index, to: index + 1 },
visible: index < tableMap.map.height - 1,
@@ -174,13 +174,13 @@ export default function tableRowMenuItems(
},
{
name: "mergeCells",
label: dictionary.mergeCells,
label: t("Merge cells"),
icon: <TableMergeCellsIcon />,
visible: isMultipleCellSelection(state),
},
{
name: "splitCell",
label: dictionary.splitCell,
label: t("Split cell"),
icon: <TableSplitCellsIcon />,
visible: isMergedCellSelection(state),
},
@@ -189,7 +189,7 @@ export default function tableRowMenuItems(
},
{
name: "deleteRow",
label: dictionary.deleteRow,
label: t("Delete"),
dangerous: true,
icon: <TrashIcon />,
},
-135
View File
@@ -1,135 +0,0 @@
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
/**
* Hook that provides a dictionary of translated UI strings.
*
* @returns An object containing all translated UI strings used throughout the application
*/
export default function useDictionary() {
const { t } = useTranslation();
return useMemo(
() => ({
addColumnAfter: t("Insert after"),
addColumnBefore: t("Insert before"),
moveRowUp: t("Move up"),
moveRowDown: t("Move down"),
moveColumnLeft: t("Move left"),
moveColumnRight: t("Move right"),
addRowAfter: t("Insert after"),
addRowBefore: t("Insert before"),
alignCenter: t("Align center"),
alignLeft: t("Align left"),
alignRight: t("Align right"),
alignDefaultWidth: t("Default width"),
alignFullWidth: t("Full width"),
bulletList: t("Bulleted list"),
checkboxList: t("Todo list"),
showCompleted: (count: number) =>
t("Show {{ count }} completed", { count }),
hideCompleted: t("Hide completed"),
codeBlock: t("Code block"),
codeCopied: t("Copied to clipboard"),
codeInline: t("Code"),
comment: t("Comment"),
copy: t("Copy"),
createLink: t("Create link"),
editDiagram: t("Edit diagram"),
editImageUrl: t("Edit image URL"),
createLinkError: t("Sorry, an error occurred creating the link"),
createNewDoc: t("Create a new doc"),
createNewChildDoc: t("Create a new child doc"),
deleteColumn: t("Delete"),
deleteRow: t("Delete"),
deleteTable: t("Delete table"),
deleteAttachment: t("Delete file"),
previewAttachment: t("Show preview"),
dimensions: `${t("Width")} × ${t("Height")}`,
download: t("Download"),
downloadAttachment: t("Download file"),
replaceAttachment: t("Replace file"),
deleteImage: t("Delete image"),
downloadImage: t("Download image"),
replaceImage: t("Replace image"),
em: t("Italic"),
embedInvalidLink: t("Sorry, that link wont work for this embed type"),
file: t("File attachment"),
pdf: t("Embed PDF"),
enterLink: `${t("Enter a link")}`,
h1: t("Big heading"),
h2: t("Medium heading"),
h3: t("Small heading"),
h4: t("Extra small heading"),
heading: t("Heading"),
hr: t("Divider"),
image: t("Image"),
fileUploadError: t("Sorry, an error occurred uploading the file"),
uploadingWithProgress: (progress: number) =>
t("Uploading… {{ progress }}%", { progress }),
imageCaptionPlaceholder: t("Write a caption"),
info: t("Info"),
infoNotice: t("Info notice"),
link: t("Link"),
linkCopied: t("Link copied to clipboard"),
mark: t("Highlight"),
background: t("Background color"),
newLineEmpty: `${t("Type '/' to insert")}`,
newLineWithSlash: `${t("Keep typing to filter")}`,
noResults: t("No results"),
openLink: t("Open link"),
goToLink: t("Go to link"),
openLinkError: t("Sorry, that type of link is not supported"),
orderedList: t("Ordered list"),
pageBreak: t("Page break"),
pasteLink: `${t("Paste a link")}`,
pasteLinkWithTitle: (service: string) =>
t("Paste a {{service}} link…", {
service,
}),
placeholder: t("Placeholder"),
quote: t("Quote"),
removeLink: t("Remove link"),
searchOrPasteLink: `${t("Search or paste a link")}`,
strikethrough: t("Strikethrough"),
strong: t("Bold"),
subheading: t("Subheading"),
sortAsc: t("Sort ascending"),
sortDesc: t("Sort descending"),
table: t("Table"),
exportAsCSV: t("Export as CSV"),
toggleHeader: t("Toggle header"),
mathInline: t("Math inline (LaTeX)"),
mathBlock: t("Math block (LaTeX)"),
mergeCells: t("Merge cells"),
splitCell: t("Split cell"),
tip: t("Tip"),
tipNotice: t("Tip notice"),
warning: t("Warning"),
warningNotice: t("Warning notice"),
success: t("Success"),
successNotice: t("Success notice"),
insertDate: t("Current date"),
insertTime: t("Current time"),
insertDateTime: t("Current date and time"),
indent: t("Indent"),
outdent: t("Outdent"),
video: t("Video"),
untitled: t("Untitled"),
none: t("None"),
toggleBlock: t("Toggle block"),
emptyToggleBlockHead: `${t("Add title")}`,
emptyToggleBlockBody: `${t("Add content")}`,
deleteEmbed: t("Delete embed"),
uploadImage: t("Upload an image"),
formattingControls: t("Formatting controls"),
distributeColumns: t("Distribute columns"),
wrapText: t("Wrap text"),
collapseCode: t("Collapse"),
expandCode: t("Expand"),
}),
[t]
);
}
export type Dictionary = ReturnType<typeof useDictionary>;
+6 -7
View File
@@ -1,16 +1,14 @@
import * as Sentry from "@sentry/react";
import { t } from "i18next";
import { v4 as uuidv4 } from "uuid";
import type { EditorView } from "prosemirror-view";
import { toast } from "sonner";
import type { Dictionary } from "~/hooks/useDictionary";
import FileHelper from "../lib/FileHelper";
import uploadPlaceholderPlugin, {
findPlaceholder,
} from "../lib/uploadPlaceholder";
export type Options = {
/** Dictionary object containing translation strings */
dictionary: Dictionary;
/** Set to true to force images and videos to become file attachments */
isAttachment?: boolean;
/** Set to true to replace any existing image at the users selection */
@@ -55,7 +53,6 @@ const insertFiles = async function (
options: Options
) {
const {
dictionary,
uploadFile,
onFileUploadStart,
onFileUploadStop,
@@ -179,7 +176,7 @@ const insertFiles = async function (
to || from,
schema.nodes.video.create({
src,
title: upload.file.name ?? dictionary.untitled,
title: upload.file.name ?? t("Untitled"),
...upload.dimensions,
...options.attrs,
})
@@ -201,7 +198,7 @@ const insertFiles = async function (
to || from,
schema.nodes.attachment.create({
href: src,
title: upload.file.name ?? dictionary.untitled,
title: upload.file.name ?? t("Untitled"),
size: upload.file.size,
contentType: upload.file.type,
preview: false,
@@ -229,7 +226,9 @@ const insertFiles = async function (
})
);
toast.error(error.message || dictionary.fileUploadError);
toast.error(
error.message || t("Sorry, an error occurred uploading the file")
);
})
.finally(() => {
complete++;
+8 -10
View File
@@ -1,3 +1,4 @@
import { t } from "i18next";
import { chainCommands, toggleMark } from "prosemirror-commands";
import type { Attrs } from "prosemirror-model";
import type { Command } from "prosemirror-state";
@@ -58,8 +59,7 @@ const openLinkTextSelection =
| MouseEvent
| React.MouseEvent<HTMLButtonElement>
) => void)
| undefined,
dictionary: { openLinkError: string }
| undefined
): Command =>
(state) => {
if (!(state.selection instanceof TextSelection)) {
@@ -72,7 +72,7 @@ const openLinkTextSelection =
const event = new KeyboardEvent("keydown", { metaKey: false });
onClickLink(sanitizeUrl(range.mark.attrs.href) ?? "", event);
} catch (_err) {
toast.error(dictionary.openLinkError);
toast.error(t("Sorry, that type of link is not supported"));
}
return true;
}
@@ -89,8 +89,7 @@ const openLinkNodeSelection =
| MouseEvent
| React.MouseEvent<HTMLButtonElement>
) => void)
| undefined,
dictionary: { openLinkError: string }
| undefined
): Command =>
(state) => {
if (!(state.selection instanceof NodeSelection)) {
@@ -111,7 +110,7 @@ const openLinkNodeSelection =
const event = new KeyboardEvent("keydown", { metaKey: false });
onClickLink(sanitizeUrl(linkMark.attrs.href) ?? "", event);
} catch (_err) {
toast.error(dictionary.openLinkError);
toast.error(t("Sorry, that type of link is not supported"));
}
return true;
};
@@ -260,12 +259,11 @@ export const openLink = (
url: string,
event?: KeyboardEvent | MouseEvent | React.MouseEvent<HTMLButtonElement>
) => void)
| undefined,
dictionary: { openLinkError: string }
| undefined
): Command =>
chainCommands(
openLinkTextSelection(onClickLink, dictionary),
openLinkNodeSelection(onClickLink, dictionary)
openLinkTextSelection(onClickLink),
openLinkNodeSelection(onClickLink)
);
export const updateLink = (attrs: Attrs): Command =>
+2 -1
View File
@@ -1,4 +1,5 @@
import copy from "copy-to-clipboard";
import { t } from "i18next";
import type { EditorState } from "prosemirror-state";
import { Plugin, PluginKey } from "prosemirror-state";
import { Decoration, DecorationSet } from "prosemirror-view";
@@ -110,7 +111,7 @@ export default class HexColorPreview extends Extension {
event.preventDefault();
event.stopPropagation();
copy(color);
toast.message(this.editor.props.dictionary.codeCopied);
toast.message(t("Copied to clipboard"));
});
return swatch;
+3 -2
View File
@@ -1,3 +1,4 @@
import { t } from "i18next";
import last from "lodash/last";
import sortBy from "lodash/sortBy";
import { v4 as uuidv4 } from "uuid";
@@ -363,7 +364,7 @@ export default function Mermaid({
isDark: boolean;
editor: Editor;
}) {
const { onClickLink, dictionary } = editor.props;
const { onClickLink } = editor.props;
return new Plugin({
key: pluginKey,
@@ -591,7 +592,7 @@ export default function Mermaid({
onClickLink(sanitizeUrl(href) ?? "");
}
} catch (_err) {
toast.error(dictionary.openLinkError);
toast.error(t("Sorry, that type of link is not supported"));
}
}
+4 -7
View File
@@ -1,3 +1,4 @@
import { t } from "i18next";
import type { Token } from "markdown-it";
import { InputRule } from "prosemirror-inputrules";
import type { MarkdownSerializerState } from "prosemirror-markdown";
@@ -12,7 +13,6 @@ import type { Command, EditorState } from "prosemirror-state";
import { Plugin, TextSelection } from "prosemirror-state";
import type { EditorView } from "prosemirror-view";
import { toast } from "sonner";
import type { Dictionary } from "~/hooks/useDictionary";
import { isUrl, sanitizeUrl } from "../../utils/urls";
import { getMarkRange } from "../queries/getMarkRange";
import Mark from "./Mark";
@@ -58,8 +58,6 @@ function isPlainURL(
* Options for the Link mark.
*/
type LinkOptions = {
/** A dictionary of translated strings used in the editor. */
dictionary: Dictionary;
/** Callback invoked when the user clicks any link in the document. */
onClickLink?: (
href: string,
@@ -128,7 +126,7 @@ export default class Link extends Mark<LinkOptions> {
keys(): Record<string, Command> {
return {
"Mod-Enter": openLink(this.options.onClickLink, this.options.dictionary),
"Mod-Enter": openLink(this.options.onClickLink),
};
}
@@ -137,8 +135,7 @@ export default class Link extends Mark<LinkOptions> {
link: (attrs: Attrs) => toggleLink(attrs),
addLink,
updateLink,
openLink: (): Command =>
openLink(this.options.onClickLink, this.options.dictionary),
openLink: (): Command => openLink(this.options.onClickLink),
removeLink,
};
}
@@ -229,7 +226,7 @@ export default class Link extends Mark<LinkOptions> {
this.options.onClickLink(sanitized, event);
}
} catch (_err) {
toast.error(this.options.dictionary.openLinkError);
toast.error(t("Sorry, that type of link is not supported"));
}
return true;
+1 -11
View File
@@ -9,7 +9,6 @@ import type { Command } from "prosemirror-state";
import { NodeSelection } from "prosemirror-state";
import { Trans } from "react-i18next";
import type { Primitive } from "utility-types";
import type { Dictionary } from "~/hooks/useDictionary";
import { bytesToHumanReadable, getEventFiles } from "../../utils/files";
import { sanitizeUrl } from "../../utils/urls";
import insertFiles from "../commands/insertFiles";
@@ -22,15 +21,7 @@ import type { ComponentProps } from "../types";
import Node from "./Node";
import PdfViewer from "../components/PDF";
/**
* Options for the Attachment node.
*/
type AttachmentOptions = {
/** A dictionary of translated strings used in the editor. */
dictionary: Dictionary;
};
export default class Attachment extends Node<AttachmentOptions> {
export default class Attachment extends Node {
get name() {
return "attachment";
}
@@ -216,7 +207,6 @@ export default class Attachment extends Node<AttachmentOptions> {
onFileUploadStart,
onFileUploadStop,
onFileUploadProgress,
dictionary: this.options.dictionary,
replaceExisting: true,
attrs: {
preview: node.attrs.preview,
+1 -8
View File
@@ -36,7 +36,6 @@ export default class CheckboxList extends Node {
get plugins() {
const userIdentifier = this.editor.props.userId;
const dictionary = this.editor.props.dictionary;
// Plugin to auto-assign IDs to checkbox lists
const assignIdsPlugin = new Plugin({
@@ -67,13 +66,7 @@ export default class CheckboxList extends Node {
props: {
nodeViews: {
[this.name]: (node, view, getPos) =>
new CheckboxListView(
node,
view,
getPos,
userIdentifier || "",
dictionary
),
new CheckboxListView(node, view, getPos, userIdentifier || ""),
},
},
});
+4 -7
View File
@@ -1,6 +1,6 @@
import { t } from "i18next";
import type { Node as ProsemirrorNode } from "prosemirror-model";
import type { EditorView, NodeView } from "prosemirror-view";
import type { Dictionary } from "../../../app/hooks/useDictionary";
import { isBrowser } from "../../utils/browser";
import Storage from "../../utils/Storage";
import { EditorStyleHelper } from "../styles/EditorStyleHelper";
@@ -16,19 +16,16 @@ export class CheckboxListView implements NodeView {
private toggleControl: HTMLButtonElement;
private node: ProsemirrorNode;
private userIdentifier: string;
private dictionary: Dictionary;
private isNested: boolean;
constructor(
node: ProsemirrorNode,
_view: EditorView,
_getPos: () => number | undefined,
userIdentifier: string,
dictionary: Dictionary
userIdentifier: string
) {
this.node = node;
this.userIdentifier = userIdentifier;
this.dictionary = dictionary;
// Detect if this is a nested checkbox list (inside a checkbox_item)
const pos = _getPos();
@@ -118,8 +115,8 @@ export class CheckboxListView implements NodeView {
} else {
this.toggleControl.style.display = "inline-block";
this.toggleControl.textContent = shouldCollapse
? this.dictionary.showCompleted(completedItemsCount)
: this.dictionary.hideCompleted;
? t("Show {{ count }} completed", { count: completedItemsCount })
: t("Hide completed");
if (shouldCollapse) {
this.dom.classList.add(EditorStyleHelper.checklistCompletedHidden);
+4 -14
View File
@@ -1,4 +1,5 @@
import copy from "copy-to-clipboard";
import { t } from "i18next";
import type { Token } from "markdown-it";
import { textblockTypeInputRule } from "prosemirror-inputrules";
import type {
@@ -17,7 +18,6 @@ import {
import { Decoration, DecorationSet, type EditorView } from "prosemirror-view";
import { toast } from "sonner";
import type { Primitive } from "utility-types";
import type { Dictionary } from "~/hooks/useDictionary";
import type { UserPreferences } from "../../types";
import { isBrowser, isMac } from "../../utils/browser";
import backspaceToParagraph from "../commands/backspaceToParagraph";
@@ -149,8 +149,6 @@ function buildCollapseState(
* Options for the CodeFence node.
*/
type CodeFenceOptions = {
/** A dictionary of translated strings used in the editor. */
dictionary: Dictionary;
/** Display preferences for the logged in user, if any. */
userPreferences?: UserPreferences | null;
};
@@ -317,7 +315,7 @@ export default class CodeFence extends Node<CodeFenceOptions> {
if (codeBlock) {
copy(codeBlock.node.textContent);
toast.message(this.options.dictionary.codeCopied);
toast.message(t("Copied to clipboard"));
return true;
}
@@ -338,7 +336,7 @@ export default class CodeFence extends Node<CodeFenceOptions> {
dispatch?.(tr);
copy(tr.doc.textBetween(state.selection.from, state.selection.to));
toast.message(this.options.dictionary.codeCopied);
toast.message(t("Copied to clipboard"));
return true;
}
@@ -380,19 +378,11 @@ export default class CodeFence extends Node<CodeFenceOptions> {
/** Plugins for collapsible code block behavior. */
private collapsePlugins(): Plugin[] {
const collapseKey = CodeFence.collapseKey;
const options = this.options;
const build = (
doc: ProsemirrorNode,
tall: Set<number>,
collapsed: Set<number>
) =>
buildCollapseState(
doc,
tall,
collapsed,
options.dictionary?.expandCode ?? "",
options.dictionary?.collapseCode ?? ""
);
) => buildCollapseState(doc, tall, collapsed, t("Expand"), t("Collapse"));
return [
// Main collapse plugin: manages state and decorations
+2 -4
View File
@@ -1,4 +1,5 @@
import copy from "copy-to-clipboard";
import { t } from "i18next";
import type Token from "markdown-it/lib/token.mjs";
import { textblockTypeInputRule } from "prosemirror-inputrules";
import type {
@@ -13,7 +14,6 @@ import { Decoration, DecorationSet } from "prosemirror-view";
import { toast } from "sonner";
import type { Primitive } from "utility-types";
import { isSafari } from "../../utils/browser";
import type { Dictionary } from "~/hooks/useDictionary";
import Storage from "../../utils/Storage";
import backspaceToParagraph from "../commands/backspaceToParagraph";
import splitHeading from "../commands/splitHeading";
@@ -39,8 +39,6 @@ type HeadingOptions = {
levels: number[];
/** Offset added to the rendered heading level (e.g. 1 renders an `h2` for level 1). */
offset?: number;
/** A dictionary of translated strings used in the editor. */
dictionary: Dictionary;
};
export default class Heading extends Node<HeadingOptions> {
@@ -187,7 +185,7 @@ export default class Heading extends Node<HeadingOptions> {
.replace("/edit", "");
copy(normalizedUrl + hash);
toast.message(this.options.dictionary.linkCopied);
toast.message(t("Link copied to clipboard"));
};
keys({ type, schema }: { type: NodeType; schema: Schema }) {
+2 -1
View File
@@ -1,3 +1,4 @@
import { t } from "i18next";
import type { Token } from "markdown-it";
import { InputRule } from "prosemirror-inputrules";
import type {
@@ -402,7 +403,7 @@ export default class Image extends SimpleImage {
onBlur={this.handleCaptionBlur(props)}
onKeyDown={this.handleCaptionKeyDown(props)}
isSelected={props.isSelected}
placeholder={this.options.dictionary.imageCaptionPlaceholder}
placeholder={t("Write a caption")}
>
{props.node.attrs.alt}
</Caption>
-1
View File
@@ -184,7 +184,6 @@ export default class SimpleImage extends Node {
onFileUploadStart,
onFileUploadStop,
onFileUploadProgress,
dictionary: this.options.dictionary,
replaceExisting: true,
attrs: {
width: node.attrs.width,
+5 -13
View File
@@ -1,3 +1,4 @@
import { t } from "i18next";
import { chainCommands, newlineInCode } from "prosemirror-commands";
import { wrappingInputRule } from "prosemirror-inputrules";
import type { ParseSpec } from "prosemirror-markdown";
@@ -12,7 +13,6 @@ import { Plugin, PluginKey, TextSelection } from "prosemirror-state";
import { findWrapping } from "prosemirror-transform";
import { Decoration, DecorationSet } from "prosemirror-view";
import { v4 } from "uuid";
import type { Dictionary } from "~/hooks/useDictionary";
import Storage from "../../utils/Storage";
import {
deleteSelectionPreservingBody,
@@ -65,15 +65,7 @@ export const toggleEventPluginKey = new PluginKey("toggleBlockEvent");
/** Build the localStorage key used to persist a toggle block's fold state. */
export const toggleStorageKey = (id: string) => `toggle:${id}`;
/**
* Options for the ToggleBlock node.
*/
type ToggleBlockOptions = {
/** A dictionary of translated strings used in the editor. */
dictionary?: Dictionary;
};
export default class ToggleBlock extends Node<ToggleBlockOptions> {
export default class ToggleBlock extends Node {
get name() {
return "container_toggle";
}
@@ -349,7 +341,7 @@ export default class ToggleBlock extends Node<ToggleBlockOptions> {
parent.type.name === "container_toggle" &&
$start.index($start.depth - 1) === 0 &&
node.textContent === "",
text: this.options.dictionary?.emptyToggleBlockHead ?? "",
text: `${t("Add title")}`,
},
{
condition: ({ parent, $start, state }) =>
@@ -359,7 +351,7 @@ export default class ToggleBlock extends Node<ToggleBlockOptions> {
ToggleBlock.isBodyEmpty(parent) &&
(state.selection.$from.pos < $start.pos ||
state.selection.$from.pos > $start.end($start.depth - 1)),
text: this.options.dictionary?.emptyToggleBlockBody ?? "",
text: `${t("Add content")}`,
},
{
condition: ({ node, parent, $start, state }) =>
@@ -368,7 +360,7 @@ export default class ToggleBlock extends Node<ToggleBlockOptions> {
node.isTextblock &&
node.textContent === "" &&
(state.selection as TextSelection).$cursor?.pos === $start.pos,
text: this.options.dictionary?.newLineEmpty ?? "",
text: `${t("Type '/' to insert")}`,
},
]),
];
+3 -11
View File
@@ -1,3 +1,4 @@
import { t } from "i18next";
import type { Token } from "markdown-it";
import type {
NodeSpec,
@@ -7,7 +8,6 @@ import type {
import { NodeSelection, TextSelection } from "prosemirror-state";
import * as React from "react";
import type { Primitive } from "utility-types";
import type { Dictionary } from "~/hooks/useDictionary";
import { sanitizeUrl } from "../../utils/urls";
import toggleWrap from "../commands/toggleWrap";
import Caption from "../components/Caption";
@@ -17,15 +17,7 @@ import attachmentsRule from "../rules/links";
import type { ComponentProps } from "../types";
import Node from "./Node";
/**
* Options for the Video node.
*/
type VideoOptions = {
/** A dictionary of translated strings used in the editor. */
dictionary: Dictionary;
};
export default class Video extends Node<VideoOptions> {
export default class Video extends Node {
get name() {
return "video";
}
@@ -178,7 +170,7 @@ export default class Video extends Node<VideoOptions> {
onBlur={this.handleCaptionBlur(props)}
onKeyDown={this.handleCaptionKeyDown(props)}
isSelected={props.isSelected}
placeholder={this.options.dictionary.imageCaptionPlaceholder}
placeholder={t("Write a caption")}
>
{props.node.attrs.title}
</Caption>
+78 -86
View File
@@ -298,6 +298,7 @@
"Currently editing": "Currently editing",
"Currently viewing": "Currently viewing",
"Viewed {{ timeAgo }}": "Viewed {{ timeAgo }}",
"Uploading… {{ progress }}%": "Uploading… {{ progress }}%",
"File type not supported. Please use PNG, JPG, GIF, or WebP.": "File type not supported. Please use PNG, JPG, GIF, or WebP.",
"File size too large. Maximum size is {{ size }}.": "File size too large. Maximum size is {{ size }}.",
"Click or drag to replace": "Click or drag to replace",
@@ -554,10 +555,18 @@
"Replacement": "Replacement",
"Replace": "Replace",
"Replace all": "Replace all",
"Go to link": "Go to link",
"Open link": "Open link",
"Remove link": "Remove link",
"Formatting controls": "Formatting controls",
"Search or paste a link": "Search or paste a link",
"Image width": "Image width",
"Width": "Width",
"Image height": "Image height",
"Height": "Height",
"Paste a link": "Paste a link",
"Delete embed": "Delete embed",
"Delete image": "Delete image",
"Profile picture": "Profile picture",
"Create a new doc": "Create a new doc",
"Create a nested doc": "Create a nested doc",
@@ -567,106 +576,78 @@
"Mention": "Mention",
"Embed": "Embed",
"Not supported": "Not supported",
"Sorry, that link wont work for this embed type": "Sorry, that link wont work for this embed type",
"Upload file": "Upload file",
"Paste a {{service}} link…": "Paste a {{service}} link…",
"More options": "More options",
"Rename": "Rename",
"Insert after": "Insert after",
"Insert before": "Insert before",
"Move up": "Move up",
"Move down": "Move down",
"Move left": "Move left",
"Move right": "Move right",
"Align center": "Align center",
"Align left": "Align left",
"Align right": "Align right",
"Default width": "Default width",
"Full width": "Full width",
"Bulleted list": "Bulleted list",
"Todo list": "Task list",
"Show {{ count }} completed": "Show {{ count }} completed",
"Show {{ count }} completed_plural": "Show {{ count }} completed",
"Hide completed": "Hide completed",
"Code block": "Code block",
"Copied to clipboard": "Copied to clipboard",
"Code": "Code",
"Comment": "Comment",
"Create link": "Create link",
"Edit image URL": "Edit image URL",
"Sorry, an error occurred creating the link": "Sorry, an error occurred creating the link",
"Create a new child doc": "Create a new child doc",
"Delete table": "Delete table",
"Type '/' to insert": "Type '/' to insert",
"Keep typing to filter": "Keep typing to filter",
"Replace file": "Replace file",
"Delete file": "Delete file",
"Show preview": "Show preview",
"Download file": "Download file",
"Replace file": "Replace file",
"Delete image": "Delete image",
"Download image": "Download image",
"Replace image": "Replace image",
"Italic": "Italic",
"Sorry, that link wont work for this embed type": "Sorry, that link wont work for this embed type",
"File attachment": "File attachment",
"Embed PDF": "Embed PDF",
"Enter a link": "Enter a link",
"Big heading": "Big heading",
"Medium heading": "Medium heading",
"Small heading": "Small heading",
"Extra small heading": "Extra small heading",
"Heading": "Heading",
"Divider": "Divider",
"Image": "Image",
"Sorry, an error occurred uploading the file": "Sorry, an error occurred uploading the file",
"Uploading… {{ progress }}%": "Uploading… {{ progress }}%",
"Write a caption": "Write a caption",
"Info": "Info",
"Info notice": "Info notice",
"Link": "Link",
"Highlight": "Highlight",
"Background color": "Background color",
"Type '/' to insert": "Type '/' to insert",
"Keep typing to filter": "Keep typing to filter",
"Open link": "Open link",
"Go to link": "Go to link",
"Sorry, that type of link is not supported": "Sorry, that type of link is not supported",
"Todo list": "Task list",
"Bulleted list": "Bulleted list",
"Ordered list": "Ordered list",
"Page break": "Page break",
"Paste a link": "Paste a link",
"Paste a {{service}} link…": "Paste a {{service}} link…",
"Placeholder": "Placeholder",
"Quote": "Quote",
"Remove link": "Remove link",
"Search or paste a link": "Search or paste a link",
"Strikethrough": "Strikethrough",
"Bold": "Bold",
"Subheading": "Subheading",
"Sort ascending": "Sort ascending",
"Sort descending": "Sort descending",
"Image": "Image",
"Video": "Video",
"Embed PDF": "Embed PDF",
"File attachment": "File attachment",
"Table": "Table",
"Export as CSV": "Export as CSV",
"Toggle header": "Toggle header",
"Math inline (LaTeX)": "Math inline (LaTeX)",
"Quote": "Quote",
"Code block": "Code block",
"Math block (LaTeX)": "Math block (LaTeX)",
"Merge cells": "Merge cells",
"Split cell": "Split cell",
"Tip": "Tip",
"Tip notice": "Tip notice",
"Warning": "Warning",
"Warning notice": "Warning notice",
"Success": "Success",
"Success notice": "Success notice",
"Toggle block": "Toggle block",
"Divider": "Divider",
"Page break": "Page break",
"Current date": "Current date",
"Current time": "Current time",
"Current date and time": "Current date and time",
"Indent": "Indent",
"Outdent": "Outdent",
"Video": "Video",
"None": "None",
"Toggle block": "Toggle block",
"Add title": "Add title",
"Add content": "Add content",
"Delete embed": "Delete embed",
"Formatting controls": "Formatting controls",
"Distribute columns": "Distribute columns",
"Info notice": "Info notice",
"Success notice": "Success notice",
"Warning notice": "Warning notice",
"Tip notice": "Tip notice",
"Wrap text": "Wrap text",
"Placeholder": "Placeholder",
"Bold": "Bold",
"Italic": "Italic",
"Strikethrough": "Strikethrough",
"Background color": "Background color",
"None": "None",
"Highlight": "Highlight",
"Code": "Code",
"Heading": "Heading",
"Subheading": "Subheading",
"Merge cells": "Merge cells",
"Split cell": "Split cell",
"Outdent": "Outdent",
"Indent": "Indent",
"Create link": "Create link",
"Comment": "Comment",
"Align left": "Align left",
"Align center": "Align center",
"Align right": "Align right",
"Full width": "Full width",
"Download image": "Download image",
"Replace image": "Replace image",
"Edit image URL": "Edit image URL",
"Default width": "Default width",
"Distribute columns": "Distribute columns",
"Delete table": "Delete table",
"Export as CSV": "Export as CSV",
"Sort ascending": "Sort ascending",
"Sort descending": "Sort descending",
"Toggle header": "Toggle header",
"Insert after": "Insert after",
"Insert before": "Insert before",
"Move left": "Move left",
"Move right": "Move right",
"Move up": "Move up",
"Move down": "Move down",
"Rename": "Rename",
"Delete Emoji": "Delete Emoji",
"Emoji deleted": "Emoji deleted",
"I'm sure Delete": "I'm sure Delete",
@@ -876,6 +857,7 @@
"Learn more": "Learn more",
"Backlinks": "Backlinks",
"Clear search highlight": "Clear search highlight",
"Warning": "Warning",
"This document is large which may affect performance": "This document is large which may affect performance",
"Are you sure about that? Deleting the <em>{{ documentTitle }}</em> document will delete all of its history</em>.": "Are you sure about that? Deleting the <em>{{ documentTitle }}</em> document will delete all of its history</em>.",
"Are you sure about that? Deleting the <em>{{ documentTitle }}</em> document will delete all of its history and <em>{{ any }} nested document</em>.": "Are you sure about that? Deleting the <em>{{ documentTitle }}</em> document will delete all of its history and <em>{{ any }} nested document</em>.",
@@ -943,6 +925,7 @@
"Large header": "Large header",
"Medium header": "Medium header",
"Small header": "Small header",
"Link": "Link",
"Underline": "Underline",
"Undo": "Undo",
"Redo": "Redo",
@@ -1099,6 +1082,7 @@
"Credentials": "Credentials",
"OAuth client ID": "OAuth client ID",
"The public identifier for this app": "The public identifier for this app",
"Copied to clipboard": "Copied to clipboard",
"OAuth client secret": "OAuth client secret",
"Store this value securely, do not expose it publicly": "Store this value securely, do not expose it publicly",
"Where users are redirected after authorizing this app": "Where users are redirected after authorizing this app",
@@ -1757,10 +1741,18 @@
"{{ user }} updated {{ timeAgo }}": "{{ user }} updated {{ timeAgo }}",
"You created {{ timeAgo }}": "You created {{ timeAgo }}",
"{{ user }} created {{ timeAgo }}": "{{ user }} created {{ timeAgo }}",
"Sorry, an error occurred uploading the file": "Sorry, an error occurred uploading the file",
"Sorry, that type of link is not supported": "Sorry, that type of link is not supported",
"Caption": "Caption",
"Empty diagram": "Empty diagram",
"Double click to edit": "Double click to edit",
"Open": "Open",
"Loading": "Loading",
"Error loading data": "Error loading data"
"Error loading data": "Error loading data",
"Show {{ count }} completed": "Show {{ count }} completed",
"Show {{ count }} completed_plural": "Show {{ count }} completed",
"Hide completed": "Hide completed",
"Write a caption": "Write a caption",
"Add title": "Add title",
"Add content": "Add content"
}