JSON to client (#5553)

This commit is contained in:
Tom Moor
2024-05-24 08:29:00 -04:00
committed by GitHub
parent e1e8257df7
commit d51267b8bc
71 changed files with 651 additions and 378 deletions

View File

@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import { PAGINATION_SYMBOL } from "~/stores/base/Store";
import Collection from "~/models/Collection";
import Avatar from "~/components/Avatar";
import { AvatarSize } from "~/components/Avatar/Avatar";
import Facepile from "~/components/Facepile";
import Fade from "~/components/Fade";
import NudeButton from "~/components/NudeButton";
@@ -66,7 +67,7 @@ const MembershipPreview = ({ collection, limit = 8 }: Props) => {
return null;
}
const overflow = usersCount - groupsCount - collectionUsers.length;
const overflow = usersCount + groupsCount - collectionUsers.length;
return (
<NudeButton
@@ -107,7 +108,9 @@ const MembershipPreview = ({ collection, limit = 8 }: Props) => {
users={sortBy(collectionUsers, "lastActiveAt")}
overflow={overflow}
limit={limit}
renderAvatar={(user) => <Avatar model={user} size={32} />}
renderAvatar={(item) => (
<Avatar model={item} size={AvatarSize.Large} />
)}
/>
</Fade>
</NudeButton>

View File

@@ -123,10 +123,10 @@ function SharedDocumentScene(props: Props) {
React.useEffect(() => {
async function fetchData() {
try {
const response = await documents.fetchWithSharedTree(documentSlug, {
const res = await documents.fetchWithSharedTree(documentSlug, {
shareId,
});
setResponse(response);
setResponse(res);
} catch (err) {
setError(err);
}

View File

@@ -8,7 +8,7 @@ import { toast } from "sonner";
import styled, { css } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { s } from "@shared/styles";
import { JSONObject } from "@shared/types";
import { ProsemirrorData } from "@shared/types";
import { dateToRelative } from "@shared/utils/date";
import { Minute } from "@shared/utils/time";
import Comment from "~/models/Comment";
@@ -100,7 +100,7 @@ function CommentThreadItem({
const [isEditing, setEditing, setReadOnly] = useBoolean();
const formRef = React.useRef<HTMLFormElement>(null);
const handleChange = (value: (asString: boolean) => JSONObject) => {
const handleChange = (value: (asString: boolean) => ProsemirrorData) => {
setData(value(false));
};

View File

@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useLocation, RouteComponentProps, StaticContext } from "react-router";
import { NavigationNode, TeamPreference } from "@shared/types";
import { ProsemirrorHelper } from "@shared/utils/ProsemirrorHelper";
import { RevisionHelper } from "@shared/utils/RevisionHelper";
import Document from "~/models/Document";
import Revision from "~/models/Revision";
@@ -92,7 +93,7 @@ function DataLoader({ match, children }: Props) {
}
}
void fetchDocument();
}, [ui, documents, document, shareId, documentSlug]);
}, [ui, documents, shareId, documentSlug]);
React.useEffect(() => {
async function fetchRevision() {
@@ -161,7 +162,7 @@ function DataLoader({ match, children }: Props) {
collectionId: document.collectionId,
parentDocumentId: nested ? document.id : document.parentDocumentId,
title,
text: "",
data: ProsemirrorHelper.getEmptyDocument(),
});
return newDocument.url;

View File

@@ -1,6 +1,9 @@
import cloneDeep from "lodash/cloneDeep";
import debounce from "lodash/debounce";
import isEqual from "lodash/isEqual";
import { action, observable } from "mobx";
import { observer } from "mobx-react";
import { Node } from "prosemirror-model";
import { AllSelection } from "prosemirror-state";
import * as React from "react";
import { WithTranslation, withTranslation } from "react-i18next";
@@ -16,9 +19,8 @@ import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { s } from "@shared/styles";
import { NavigationNode } from "@shared/types";
import { Heading } from "@shared/utils/ProsemirrorHelper";
import { ProsemirrorHelper, Heading } from "@shared/utils/ProsemirrorHelper";
import { parseDomain } from "@shared/utils/domains";
import getTasks from "@shared/utils/getTasks";
import RootStore from "~/stores/RootStore";
import Document from "~/models/Document";
import Revision from "~/models/Revision";
@@ -34,6 +36,7 @@ import PlaceholderDocument from "~/components/PlaceholderDocument";
import RegisterKeyDown from "~/components/RegisterKeyDown";
import withStores from "~/components/withStores";
import type { Editor as TEditor } from "~/editor";
import { SearchResult } from "~/editor/components/LinkEditor";
import { client } from "~/utils/ApiClient";
import { replaceTitleVariables } from "~/utils/date";
import { emojiToUrl } from "~/utils/emoji";
@@ -73,13 +76,13 @@ type Props = WithTranslation &
RootStore &
RouteComponentProps<Params, StaticContext, LocationState> & {
sharedTree?: NavigationNode;
abilities: Record<string, any>;
abilities: Record<string, boolean>;
document: Document;
revision?: Revision;
readOnly: boolean;
shareId?: string;
onCreateLink?: (title: string, nested?: boolean) => Promise<string>;
onSearchLink?: (term: string) => any;
onSearchLink?: (term: string) => Promise<SearchResult[]>;
};
@observer
@@ -108,8 +111,6 @@ class DocumentScene extends React.Component<Props> {
@observable
headings: Heading[] = [];
getEditorText: () => string = () => this.props.document.text;
componentDidMount() {
this.updateIsDirty();
}
@@ -140,8 +141,8 @@ class DocumentScene extends React.Component<Props> {
return;
}
const { view, parser } = editorRef;
const doc = parser.parse(template.text);
const { view, schema } = editorRef;
const doc = Node.fromJSON(schema, template.data);
if (doc) {
view.dispatch(
@@ -168,10 +169,8 @@ class DocumentScene extends React.Component<Props> {
if (template.emoji) {
this.props.document.emoji = template.emoji;
}
if (template.text) {
this.props.document.text = template.text;
}
this.props.document.data = cloneDeep(template.data);
this.updateIsDirty();
return this.onSave({
@@ -292,15 +291,18 @@ class DocumentScene extends React.Component<Props> {
}
// get the latest version of the editor text value
const text = this.getEditorText ? this.getEditorText() : document.text;
// prevent save before anything has been written (single hash is empty doc)
if (text.trim() === "" && document.title.trim() === "") {
const doc = this.editor.current?.view.state.doc;
if (!doc) {
return;
}
document.text = text;
document.tasks = getTasks(document.text);
// prevent save before anything has been written (single hash is empty doc)
if (ProsemirrorHelper.isEmpty(doc) && document.title.trim() === "") {
return;
}
document.data = doc.toJSON();
document.tasks = ProsemirrorHelper.getTasksSummary(doc);
// prevent autosave if nothing has changed
if (options.autosave && !this.isEditorDirty && !document.isDirty()) {
@@ -340,12 +342,11 @@ class DocumentScene extends React.Component<Props> {
updateIsDirty = () => {
const { document } = this.props;
const editorText = this.getEditorText().trim();
this.isEditorDirty = editorText !== document.text.trim();
const doc = this.editor.current?.view.state.doc;
this.isEditorDirty = !isEqual(doc?.toJSON(), document.data);
// a single hash is a doc with just an empty title
this.isEmpty =
(!editorText || editorText === "#" || editorText === "\\") && !this.title;
this.isEmpty = (!doc || ProsemirrorHelper.isEmpty(doc)) && !this.title;
};
updateIsDirtyDebounced = debounce(this.updateIsDirty, 500);
@@ -358,9 +359,8 @@ class DocumentScene extends React.Component<Props> {
this.isUploading = false;
};
handleChange = (getEditorText: () => string) => {
handleChange = () => {
const { document } = this.props;
this.getEditorText = getEditorText;
// Keep derived task list in sync
const tasks = this.editor.current?.getTasks();
@@ -503,8 +503,8 @@ class DocumentScene extends React.Component<Props> {
isDraft={document.isDraft}
template={document.isTemplate}
document={document}
value={readOnly ? document.text : undefined}
defaultValue={document.text}
value={readOnly ? document.data : undefined}
defaultValue={document.data}
embedsDisabled={embedsDisabled}
onSynced={this.onSynced}
onFileUploadStart={this.onFileUploadStart}

View File

@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import { useHistory, useLocation, useRouteMatch } from "react-router-dom";
import { toast } from "sonner";
import { UserPreference } from "@shared/types";
import { ProsemirrorHelper } from "@shared/utils/ProsemirrorHelper";
import CenteredContent from "~/components/CenteredContent";
import Flex from "~/components/Flex";
import PlaceholderDocument from "~/components/PlaceholderDocument";
@@ -49,7 +50,7 @@ function DocumentNew({ template }: Props) {
templateId: query.get("templateId") ?? undefined,
template,
title: "",
text: "",
data: ProsemirrorHelper.getEmptyDocument(),
});
history.replace(
template || !user.separateEditMode