mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
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:
@@ -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}
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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 won’t 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}
|
||||
|
||||
@@ -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")}…`,
|
||||
},
|
||||
]),
|
||||
];
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
@@ -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" },
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
|
||||
@@ -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 />,
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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,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 />,
|
||||
|
||||
@@ -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 />,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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 />,
|
||||
},
|
||||
|
||||
@@ -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 won’t 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>;
|
||||
@@ -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++;
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 || ""),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -184,7 +184,6 @@ export default class SimpleImage extends Node {
|
||||
onFileUploadStart,
|
||||
onFileUploadStop,
|
||||
onFileUploadProgress,
|
||||
dictionary: this.options.dictionary,
|
||||
replaceExisting: true,
|
||||
attrs: {
|
||||
width: node.attrs.width,
|
||||
|
||||
@@ -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")}…`,
|
||||
},
|
||||
]),
|
||||
];
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 won’t work for this embed type": "Sorry, that link won’t 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 won’t work for this embed type": "Sorry, that link won’t 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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user