JSON to client (#5553)
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user