perf: Remove markdown serialize from editor render path (#3567)

* perf: Remove markdown serialize from editor render path

* fix: Simplify heading equality check

* perf: Add cache for slugified headings

* tsc
This commit is contained in:
Tom Moor
2022-05-21 12:50:27 -07:00
committed by GitHub
parent 2a6d6f5804
commit c4006cef7b
11 changed files with 115 additions and 57 deletions

View File

@@ -14,6 +14,7 @@ import {
} from "react-router";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { Heading } from "@shared/editor/lib/getHeadings";
import getTasks from "@shared/utils/getTasks";
import RootStore from "~/stores/RootStore";
import Document from "~/models/Document";
@@ -29,6 +30,7 @@ import PageTitle from "~/components/PageTitle";
import PlaceholderDocument from "~/components/PlaceholderDocument";
import RegisterKeyDown from "~/components/RegisterKeyDown";
import withStores from "~/components/withStores";
import type { Editor as TEditor } from "~/editor";
import { NavigationNode } from "~/types";
import { client } from "~/utils/ApiClient";
import { isCustomDomain } from "~/utils/domains";
@@ -73,7 +75,7 @@ type Props = WithTranslation &
@observer
class DocumentScene extends React.Component<Props> {
@observable
editor = React.createRef<typeof Editor>();
editor = React.createRef<TEditor>();
@observable
isUploading = false;
@@ -96,6 +98,9 @@ class DocumentScene extends React.Component<Props> {
@observable
title: string = this.props.document.title;
@observable
headings: Heading[] = [];
getEditorText: () => string = () => this.props.document.text;
componentDidMount() {
@@ -158,7 +163,6 @@ class DocumentScene extends React.Component<Props> {
return;
}
// @ts-expect-error ts-migrate(2339) FIXME: Property 'view' does not exist on type 'unknown'.
const { view, parser } = editorRef;
view.dispatch(
view.state.tr
@@ -375,13 +379,24 @@ class DocumentScene extends React.Component<Props> {
const { document, auth } = this.props;
this.getEditorText = getEditorText;
// If the multiplayer editor is enabled then we still want to keep the local
// text value in sync as it is used as a cache.
// Keep headings in sync for table of contents
const headings = this.editor.current?.getHeadings() ?? [];
if (
headings.map((h) => h.level + h.title).join("") !==
this.headings.map((h) => h.level + h.title).join("")
) {
this.headings = headings;
}
// Keep derived task list in sync
const tasks = this.editor.current?.getTasks();
const total = tasks?.length ?? 0;
const completed = tasks?.filter((t) => t.completed).length ?? 0;
document.updateTasks(total, completed);
// If the multiplayer editor is enabled we're done here as changes are saved
// through the persistence protocol. The rest of this method is legacy.
if (auth.team?.collaborativeEditing) {
action(() => {
document.text = this.getEditorText();
document.tasks = getTasks(document.text);
})();
return;
}
@@ -429,12 +444,7 @@ class DocumentScene extends React.Component<Props> {
const embedsDisabled =
(team && team.documentEmbeds === false) || document.embedsDisabled;
const headings = this.editor.current
? // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.
this.editor.current.getHeadings()
: [];
const hasHeadings = headings.length > 0;
const hasHeadings = this.headings.length > 0;
const showContents =
ui.tocVisible &&
((readOnly && hasHeadings) || team?.collaborativeEditing);
@@ -549,7 +559,7 @@ class DocumentScene extends React.Component<Props> {
sharedTree={this.props.sharedTree}
onSelectTemplate={this.replaceDocument}
onSave={this.onSave}
headings={headings}
headings={this.headings}
/>
<MaxWidth
archived={document.isArchived}
@@ -564,7 +574,7 @@ class DocumentScene extends React.Component<Props> {
<Flex auto={!readOnly}>
{showContents && (
<Contents
headings={headings}
headings={this.headings}
isFullWidth={document.fullWidth}
/>
)}

View File

@@ -139,9 +139,6 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
});
if (debug) {
provider.on("status", (ev: ConnectionStatusEvent) =>
Logger.debug("collaboration", "status", ev)
);
provider.on("message", (ev: MessageEvent) =>
Logger.debug("collaboration", "incoming", {
message: ev.message,