mirror of
https://github.com/outline/outline.git
synced 2026-06-14 03:45:00 +03:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 38fa3ed903 | |||
| c269d9f1a3 |
@@ -23,6 +23,7 @@ import ExtensionManager from "@shared/editor/lib/ExtensionManager";
|
|||||||
import getHeadings from "@shared/editor/lib/getHeadings";
|
import getHeadings from "@shared/editor/lib/getHeadings";
|
||||||
import getTasks from "@shared/editor/lib/getTasks";
|
import getTasks from "@shared/editor/lib/getTasks";
|
||||||
import { MarkdownSerializer } from "@shared/editor/lib/markdown/serializer";
|
import { MarkdownSerializer } from "@shared/editor/lib/markdown/serializer";
|
||||||
|
import textBetween from "@shared/editor/lib/textBetween";
|
||||||
import Mark from "@shared/editor/marks/Mark";
|
import Mark from "@shared/editor/marks/Mark";
|
||||||
import Node from "@shared/editor/nodes/Node";
|
import Node from "@shared/editor/nodes/Node";
|
||||||
import ReactNode from "@shared/editor/nodes/ReactNode";
|
import ReactNode from "@shared/editor/nodes/ReactNode";
|
||||||
@@ -571,6 +572,9 @@ export class Editor extends React.PureComponent<
|
|||||||
this.setState({ blockMenuOpen: false });
|
this.setState({ blockMenuOpen: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Focus the editor at the start of the content.
|
||||||
|
*/
|
||||||
public focusAtStart = () => {
|
public focusAtStart = () => {
|
||||||
const selection = Selection.atStart(this.view.state.doc);
|
const selection = Selection.atStart(this.view.state.doc);
|
||||||
const transaction = this.view.state.tr.setSelection(selection);
|
const transaction = this.view.state.tr.setSelection(selection);
|
||||||
@@ -578,6 +582,9 @@ export class Editor extends React.PureComponent<
|
|||||||
this.view.focus();
|
this.view.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Focus the editor at the end of the content.
|
||||||
|
*/
|
||||||
public focusAtEnd = () => {
|
public focusAtEnd = () => {
|
||||||
const selection = Selection.atEnd(this.view.state.doc);
|
const selection = Selection.atEnd(this.view.state.doc);
|
||||||
const transaction = this.view.state.tr.setSelection(selection);
|
const transaction = this.view.state.tr.setSelection(selection);
|
||||||
@@ -585,14 +592,40 @@ export class Editor extends React.PureComponent<
|
|||||||
this.view.focus();
|
this.view.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the headings in the current editor.
|
||||||
|
*
|
||||||
|
* @returns A list of headings in the document
|
||||||
|
*/
|
||||||
public getHeadings = () => {
|
public getHeadings = () => {
|
||||||
return getHeadings(this.view.state.doc);
|
return getHeadings(this.view.state.doc);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the tasks/checkmarks in the current editor.
|
||||||
|
*
|
||||||
|
* @returns A list of tasks in the document
|
||||||
|
*/
|
||||||
public getTasks = () => {
|
public getTasks = () => {
|
||||||
return getTasks(this.view.state.doc);
|
return getTasks(this.view.state.doc);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the plain text content of the current editor.
|
||||||
|
*
|
||||||
|
* @returns A string of text
|
||||||
|
*/
|
||||||
|
public getPlainText = () => {
|
||||||
|
const { doc } = this.view.state;
|
||||||
|
const textSerializers = Object.fromEntries(
|
||||||
|
Object.entries(this.schema.nodes)
|
||||||
|
.filter(([, node]) => node.spec.toPlainText)
|
||||||
|
.map(([name, node]) => [name, node.spec.toPlainText])
|
||||||
|
);
|
||||||
|
|
||||||
|
return textBetween(doc, 0, doc.content.size, textSerializers);
|
||||||
|
};
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {
|
const {
|
||||||
dir,
|
dir,
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ import {
|
|||||||
} from "~/utils/routeHelpers";
|
} from "~/utils/routeHelpers";
|
||||||
import Container from "./Container";
|
import Container from "./Container";
|
||||||
import Contents from "./Contents";
|
import Contents from "./Contents";
|
||||||
|
import DocumentContext from "./DocumentContext";
|
||||||
|
import type { DocumentContextValue } from "./DocumentContext";
|
||||||
import Editor from "./Editor";
|
import Editor from "./Editor";
|
||||||
import Header from "./Header";
|
import Header from "./Header";
|
||||||
import KeyboardShortcutsButton from "./KeyboardShortcutsButton";
|
import KeyboardShortcutsButton from "./KeyboardShortcutsButton";
|
||||||
@@ -110,6 +112,14 @@ class DocumentScene extends React.Component<Props> {
|
|||||||
@observable
|
@observable
|
||||||
headings: Heading[] = [];
|
headings: Heading[] = [];
|
||||||
|
|
||||||
|
@observable
|
||||||
|
documentContext: DocumentContextValue = {
|
||||||
|
editor: null,
|
||||||
|
setEditor: action((editor: TEditor) => {
|
||||||
|
this.documentContext.editor = editor;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
getEditorText: () => string = () => this.props.document.text;
|
getEditorText: () => string = () => this.props.document.text;
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -452,196 +462,203 @@ class DocumentScene extends React.Component<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
{this.props.location.pathname !== canonicalUrl && (
|
<DocumentContext.Provider value={this.documentContext}>
|
||||||
<Redirect
|
{this.props.location.pathname !== canonicalUrl && (
|
||||||
to={{
|
<Redirect
|
||||||
pathname: canonicalUrl,
|
to={{
|
||||||
state: this.props.location.state,
|
pathname: canonicalUrl,
|
||||||
hash: this.props.location.hash,
|
state: this.props.location.state,
|
||||||
|
hash: this.props.location.hash,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<RegisterKeyDown trigger="m" handler={this.goToMove} />
|
||||||
|
<RegisterKeyDown trigger="e" handler={this.goToEdit} />
|
||||||
|
<RegisterKeyDown trigger="Escape" handler={this.goBack} />
|
||||||
|
<RegisterKeyDown trigger="h" handler={this.goToHistory} />
|
||||||
|
<RegisterKeyDown
|
||||||
|
trigger="p"
|
||||||
|
handler={(event) => {
|
||||||
|
if (isModKey(event) && event.shiftKey) {
|
||||||
|
this.onPublish(event);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
<RegisterKeyDown
|
||||||
<RegisterKeyDown trigger="m" handler={this.goToMove} />
|
trigger="h"
|
||||||
<RegisterKeyDown trigger="e" handler={this.goToEdit} />
|
handler={(event) => {
|
||||||
<RegisterKeyDown trigger="Escape" handler={this.goBack} />
|
if (event.ctrlKey && event.altKey) {
|
||||||
<RegisterKeyDown trigger="h" handler={this.goToHistory} />
|
this.onToggleTableOfContents(event);
|
||||||
<RegisterKeyDown
|
|
||||||
trigger="p"
|
|
||||||
handler={(event) => {
|
|
||||||
if (isModKey(event) && event.shiftKey) {
|
|
||||||
this.onPublish(event);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<RegisterKeyDown
|
|
||||||
trigger="h"
|
|
||||||
handler={(event) => {
|
|
||||||
if (event.ctrlKey && event.altKey) {
|
|
||||||
this.onToggleTableOfContents(event);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Background key={revision ? revision.id : document.id} column auto>
|
|
||||||
<Route
|
|
||||||
path={`${document.url}/move`}
|
|
||||||
component={() => (
|
|
||||||
<Modal
|
|
||||||
title={`Move ${document.noun}`}
|
|
||||||
onRequestClose={this.goBack}
|
|
||||||
isOpen
|
|
||||||
>
|
|
||||||
<DocumentMove
|
|
||||||
document={document}
|
|
||||||
onRequestClose={this.goBack}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<PageTitle
|
|
||||||
title={document.titleWithDefault.replace(document.emoji || "", "")}
|
|
||||||
favicon={document.emoji ? emojiToUrl(document.emoji) : undefined}
|
|
||||||
/>
|
|
||||||
{(this.isUploading || this.isSaving) && <LoadingIndicator />}
|
|
||||||
<Container justify="center" column auto>
|
|
||||||
{!readOnly && (
|
|
||||||
<>
|
|
||||||
<Prompt
|
|
||||||
when={
|
|
||||||
this.isEditorDirty &&
|
|
||||||
!this.isUploading &&
|
|
||||||
!team?.collaborativeEditing
|
|
||||||
}
|
|
||||||
message={(location, action) => {
|
|
||||||
if (
|
|
||||||
// a URL replace matching the current document indicates a title change
|
|
||||||
// no guard is needed for this transition
|
|
||||||
action === "REPLACE" &&
|
|
||||||
location.pathname === editDocumentUrl(document)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return t(
|
|
||||||
`You have unsaved changes.\nAre you sure you want to discard them?`
|
|
||||||
) as string;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Prompt
|
|
||||||
when={this.isUploading && !this.isEditorDirty}
|
|
||||||
message={t(
|
|
||||||
`Images are still uploading.\nAre you sure you want to discard them?`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Header
|
|
||||||
document={document}
|
|
||||||
documentHasHeadings={hasHeadings}
|
|
||||||
shareId={shareId}
|
|
||||||
isRevision={!!revision}
|
|
||||||
isDraft={document.isDraft}
|
|
||||||
isEditing={!readOnly && !team?.seamlessEditing}
|
|
||||||
isSaving={this.isSaving}
|
|
||||||
isPublishing={this.isPublishing}
|
|
||||||
publishingIsDisabled={
|
|
||||||
document.isSaving || this.isPublishing || this.isEmpty
|
|
||||||
}
|
}
|
||||||
savingIsDisabled={document.isSaving || this.isEmpty}
|
}}
|
||||||
sharedTree={this.props.sharedTree}
|
/>
|
||||||
onSelectTemplate={this.replaceDocument}
|
<Background key={revision ? revision.id : document.id} column auto>
|
||||||
onSave={this.onSave}
|
<Route
|
||||||
headings={this.headings}
|
path={`${document.url}/move`}
|
||||||
|
component={() => (
|
||||||
|
<Modal
|
||||||
|
title={`Move ${document.noun}`}
|
||||||
|
onRequestClose={this.goBack}
|
||||||
|
isOpen
|
||||||
|
>
|
||||||
|
<DocumentMove
|
||||||
|
document={document}
|
||||||
|
onRequestClose={this.goBack}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<MaxWidth
|
<PageTitle
|
||||||
archived={document.isArchived}
|
title={document.titleWithDefault.replace(
|
||||||
showContents={showContents}
|
document.emoji || "",
|
||||||
isEditing={!readOnly}
|
""
|
||||||
isFullWidth={document.fullWidth}
|
)}
|
||||||
column
|
favicon={document.emoji ? emojiToUrl(document.emoji) : undefined}
|
||||||
auto
|
/>
|
||||||
>
|
{(this.isUploading || this.isSaving) && <LoadingIndicator />}
|
||||||
<Notices document={document} readOnly={readOnly} />
|
<Container justify="center" column auto>
|
||||||
<React.Suspense fallback={<PlaceholderDocument />}>
|
{!readOnly && (
|
||||||
<Flex auto={!readOnly}>
|
<>
|
||||||
{revision ? (
|
<Prompt
|
||||||
<RevisionViewer
|
when={
|
||||||
isDraft={document.isDraft}
|
this.isEditorDirty &&
|
||||||
document={document}
|
!this.isUploading &&
|
||||||
revision={revision}
|
!team?.collaborativeEditing
|
||||||
id={revision.id}
|
}
|
||||||
/>
|
message={(location, action) => {
|
||||||
) : (
|
if (
|
||||||
<>
|
// a URL replace matching the current document indicates a title change
|
||||||
{showContents && (
|
// no guard is needed for this transition
|
||||||
<Contents
|
action === "REPLACE" &&
|
||||||
headings={this.headings}
|
location.pathname === editDocumentUrl(document)
|
||||||
isFullWidth={document.fullWidth}
|
) {
|
||||||
/>
|
return true;
|
||||||
)}
|
}
|
||||||
<Editor
|
|
||||||
id={document.id}
|
return t(
|
||||||
key={embedsDisabled ? "disabled" : "enabled"}
|
`You have unsaved changes.\nAre you sure you want to discard them?`
|
||||||
ref={this.editor}
|
) as string;
|
||||||
multiplayer={collaborativeEditing}
|
}}
|
||||||
shareId={shareId}
|
/>
|
||||||
|
<Prompt
|
||||||
|
when={this.isUploading && !this.isEditorDirty}
|
||||||
|
message={t(
|
||||||
|
`Images are still uploading.\nAre you sure you want to discard them?`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Header
|
||||||
|
document={document}
|
||||||
|
documentHasHeadings={hasHeadings}
|
||||||
|
shareId={shareId}
|
||||||
|
isRevision={!!revision}
|
||||||
|
isDraft={document.isDraft}
|
||||||
|
isEditing={!readOnly && !team?.seamlessEditing}
|
||||||
|
isSaving={this.isSaving}
|
||||||
|
isPublishing={this.isPublishing}
|
||||||
|
publishingIsDisabled={
|
||||||
|
document.isSaving || this.isPublishing || this.isEmpty
|
||||||
|
}
|
||||||
|
savingIsDisabled={document.isSaving || this.isEmpty}
|
||||||
|
sharedTree={this.props.sharedTree}
|
||||||
|
onSelectTemplate={this.replaceDocument}
|
||||||
|
onSave={this.onSave}
|
||||||
|
headings={this.headings}
|
||||||
|
/>
|
||||||
|
<MaxWidth
|
||||||
|
archived={document.isArchived}
|
||||||
|
showContents={showContents}
|
||||||
|
isEditing={!readOnly}
|
||||||
|
isFullWidth={document.fullWidth}
|
||||||
|
column
|
||||||
|
auto
|
||||||
|
>
|
||||||
|
<Notices document={document} readOnly={readOnly} />
|
||||||
|
<React.Suspense fallback={<PlaceholderDocument />}>
|
||||||
|
<Flex auto={!readOnly}>
|
||||||
|
{revision ? (
|
||||||
|
<RevisionViewer
|
||||||
isDraft={document.isDraft}
|
isDraft={document.isDraft}
|
||||||
template={document.isTemplate}
|
|
||||||
document={document}
|
document={document}
|
||||||
value={readOnly ? document.text : undefined}
|
revision={revision}
|
||||||
defaultValue={document.text}
|
id={revision.id}
|
||||||
embedsDisabled={embedsDisabled}
|
/>
|
||||||
onSynced={this.onSynced}
|
) : (
|
||||||
onFileUploadStart={this.onFileUploadStart}
|
<>
|
||||||
onFileUploadStop={this.onFileUploadStop}
|
{showContents && (
|
||||||
onSearchLink={this.props.onSearchLink}
|
<Contents
|
||||||
onCreateLink={this.props.onCreateLink}
|
headings={this.headings}
|
||||||
onChangeTitle={this.onChangeTitle}
|
isFullWidth={document.fullWidth}
|
||||||
onChange={this.onChange}
|
/>
|
||||||
onHeadingsChange={this.onHeadingsChange}
|
|
||||||
onSave={this.onSave}
|
|
||||||
onPublish={this.onPublish}
|
|
||||||
onCancel={this.goBack}
|
|
||||||
readOnly={readOnly}
|
|
||||||
readOnlyWriteCheckboxes={readOnly && abilities.update}
|
|
||||||
>
|
|
||||||
{shareId && (
|
|
||||||
<ReferencesWrapper isOnlyTitle={document.isOnlyTitle}>
|
|
||||||
<PublicReferences
|
|
||||||
shareId={shareId}
|
|
||||||
documentId={document.id}
|
|
||||||
sharedTree={this.props.sharedTree}
|
|
||||||
/>
|
|
||||||
</ReferencesWrapper>
|
|
||||||
)}
|
)}
|
||||||
{!isShare && !revision && (
|
<Editor
|
||||||
<>
|
id={document.id}
|
||||||
<MarkAsViewed document={document} />
|
key={embedsDisabled ? "disabled" : "enabled"}
|
||||||
|
ref={this.editor}
|
||||||
|
multiplayer={collaborativeEditing}
|
||||||
|
shareId={shareId}
|
||||||
|
isDraft={document.isDraft}
|
||||||
|
template={document.isTemplate}
|
||||||
|
document={document}
|
||||||
|
value={readOnly ? document.text : undefined}
|
||||||
|
defaultValue={document.text}
|
||||||
|
embedsDisabled={embedsDisabled}
|
||||||
|
onSynced={this.onSynced}
|
||||||
|
onFileUploadStart={this.onFileUploadStart}
|
||||||
|
onFileUploadStop={this.onFileUploadStop}
|
||||||
|
onSearchLink={this.props.onSearchLink}
|
||||||
|
onCreateLink={this.props.onCreateLink}
|
||||||
|
onChangeTitle={this.onChangeTitle}
|
||||||
|
onChange={this.onChange}
|
||||||
|
onHeadingsChange={this.onHeadingsChange}
|
||||||
|
onSave={this.onSave}
|
||||||
|
onPublish={this.onPublish}
|
||||||
|
onCancel={this.goBack}
|
||||||
|
readOnly={readOnly}
|
||||||
|
readOnlyWriteCheckboxes={readOnly && abilities.update}
|
||||||
|
>
|
||||||
|
{shareId && (
|
||||||
<ReferencesWrapper
|
<ReferencesWrapper
|
||||||
isOnlyTitle={document.isOnlyTitle}
|
isOnlyTitle={document.isOnlyTitle}
|
||||||
>
|
>
|
||||||
<References document={document} />
|
<PublicReferences
|
||||||
|
shareId={shareId}
|
||||||
|
documentId={document.id}
|
||||||
|
sharedTree={this.props.sharedTree}
|
||||||
|
/>
|
||||||
</ReferencesWrapper>
|
</ReferencesWrapper>
|
||||||
</>
|
)}
|
||||||
)}
|
{!isShare && !revision && (
|
||||||
</Editor>
|
<>
|
||||||
</>
|
<MarkAsViewed document={document} />
|
||||||
)}
|
<ReferencesWrapper
|
||||||
</Flex>
|
isOnlyTitle={document.isOnlyTitle}
|
||||||
</React.Suspense>
|
>
|
||||||
</MaxWidth>
|
<References document={document} />
|
||||||
{isShare &&
|
</ReferencesWrapper>
|
||||||
!parseDomain(window.location.origin).custom &&
|
</>
|
||||||
!auth.user && (
|
)}
|
||||||
<Branding href="//www.getoutline.com?ref=sharelink" />
|
</Editor>
|
||||||
)}
|
</>
|
||||||
</Container>
|
)}
|
||||||
</Background>
|
</Flex>
|
||||||
{!isShare && (
|
</React.Suspense>
|
||||||
<>
|
</MaxWidth>
|
||||||
<KeyboardShortcutsButton />
|
{isShare &&
|
||||||
<ConnectionStatus />
|
!parseDomain(window.location.origin).custom &&
|
||||||
</>
|
!auth.user && (
|
||||||
)}
|
<Branding href="//www.getoutline.com?ref=sharelink" />
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
</Background>
|
||||||
|
{!isShare && (
|
||||||
|
<>
|
||||||
|
<KeyboardShortcutsButton />
|
||||||
|
<ConnectionStatus />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DocumentContext.Provider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { Editor } from "~/editor";
|
||||||
|
|
||||||
|
export type DocumentContextValue = {
|
||||||
|
/** The current editor instance for this document. */
|
||||||
|
editor: Editor | null;
|
||||||
|
/** Set the current editor instance for this document. */
|
||||||
|
setEditor: (editor: Editor) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DocumentContext = React.createContext<DocumentContextValue>({
|
||||||
|
editor: null,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
setEditor() {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useDocumentContext = () => React.useContext(DocumentContext);
|
||||||
|
|
||||||
|
export default DocumentContext;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { mergeRefs } from "react-merge-refs";
|
||||||
import { useRouteMatch } from "react-router-dom";
|
import { useRouteMatch } from "react-router-dom";
|
||||||
import fullPackage from "@shared/editor/packages/full";
|
import fullPackage from "@shared/editor/packages/full";
|
||||||
import Document from "~/models/Document";
|
import Document from "~/models/Document";
|
||||||
@@ -14,6 +15,7 @@ import {
|
|||||||
matchDocumentHistory,
|
matchDocumentHistory,
|
||||||
} from "~/utils/routeHelpers";
|
} from "~/utils/routeHelpers";
|
||||||
import MultiplayerEditor from "./AsyncMultiplayerEditor";
|
import MultiplayerEditor from "./AsyncMultiplayerEditor";
|
||||||
|
import { useDocumentContext } from "./DocumentContext";
|
||||||
import EditableTitle from "./EditableTitle";
|
import EditableTitle from "./EditableTitle";
|
||||||
|
|
||||||
type Props = Omit<EditorProps, "extensions"> & {
|
type Props = Omit<EditorProps, "extensions"> & {
|
||||||
@@ -74,6 +76,9 @@ function DocumentEditor(props: Props, ref: React.RefObject<any>) {
|
|||||||
[focusAtStart, ref]
|
[focusAtStart, ref]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { setEditor } = useDocumentContext();
|
||||||
|
const handleRefChanged = React.useCallback(setEditor, [setEditor]);
|
||||||
|
|
||||||
const EditorComponent = multiplayer ? MultiplayerEditor : Editor;
|
const EditorComponent = multiplayer ? MultiplayerEditor : Editor;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -103,7 +108,7 @@ function DocumentEditor(props: Props, ref: React.RefObject<any>) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<EditorComponent
|
<EditorComponent
|
||||||
ref={ref}
|
ref={mergeRefs([ref, handleRefChanged])}
|
||||||
autoFocus={!!document.title && !props.defaultValue}
|
autoFocus={!!document.title && !props.defaultValue}
|
||||||
placeholder={t("Type '/' to insert, or start writing…")}
|
placeholder={t("Type '/' to insert, or start writing…")}
|
||||||
scrollTo={decodeURIComponent(window.location.hash)}
|
scrollTo={decodeURIComponent(window.location.hash)}
|
||||||
|
|||||||
Reference in New Issue
Block a user