diff --git a/app/components/Sidebar/components/CollectionLink.js b/app/components/Sidebar/components/CollectionLink.js index d2ac506f8..457910023 100644 --- a/app/components/Sidebar/components/CollectionLink.js +++ b/app/components/Sidebar/components/CollectionLink.js @@ -10,27 +10,34 @@ import CollectionIcon from "components/CollectionIcon"; import DropToImport from "components/DropToImport"; import Flex from "components/Flex"; import DocumentLink from "./DocumentLink"; +import EditableTitle from "./EditableTitle"; import SidebarLink from "./SidebarLink"; import CollectionMenu from "menus/CollectionMenu"; -type Props = { +type Props = {| collection: Collection, ui: UiStore, + canUpdate: boolean, documents: DocumentsStore, activeDocument: ?Document, prefetchDocument: (id: string) => Promise, -}; +|}; @observer class CollectionLink extends React.Component { @observable menuOpen = false; + handleTitleChange = async (name: string) => { + await this.props.collection.save({ name }); + }; + render() { const { collection, documents, activeDocument, prefetchDocument, + canUpdate, ui, } = this.props; const expanded = collection.id === ui.activeCollectionId; @@ -49,7 +56,13 @@ class CollectionLink extends React.Component { expanded={expanded} hideDisclosure menuOpen={this.menuOpen} - label={collection.name} + label={ + + } exact={false} menu={ { collection={collection} activeDocument={activeDocument} prefetchDocument={prefetchDocument} + canUpdate={canUpdate} depth={1.5} /> ))} diff --git a/app/components/Sidebar/components/Collections.js b/app/components/Sidebar/components/Collections.js index 1d3d6a9af..10af48102 100644 --- a/app/components/Sidebar/components/Collections.js +++ b/app/components/Sidebar/components/Collections.js @@ -52,7 +52,7 @@ class Collections extends React.Component { } render() { - const { collections, ui, documents } = this.props; + const { collections, ui, policies, documents } = this.props; const content = ( <> @@ -63,6 +63,7 @@ class Collections extends React.Component { collection={collection} activeDocument={documents.active} prefetchDocument={documents.prefetchDocument} + canUpdate={policies.abilities(collection.id).update} ui={ui} /> ))} diff --git a/app/components/Sidebar/components/DocumentLink.js b/app/components/Sidebar/components/DocumentLink.js index ad9673c3d..023c8afff 100644 --- a/app/components/Sidebar/components/DocumentLink.js +++ b/app/components/Sidebar/components/DocumentLink.js @@ -9,19 +9,21 @@ import Document from "models/Document"; import DropToImport from "components/DropToImport"; import Fade from "components/Fade"; import Flex from "components/Flex"; +import EditableTitle from "./EditableTitle"; import SidebarLink from "./SidebarLink"; import DocumentMenu from "menus/DocumentMenu"; import { type NavigationNode } from "types"; -type Props = { +type Props = {| node: NavigationNode, documents: DocumentsStore, + canUpdate: boolean, collection?: Collection, activeDocument: ?Document, activeDocumentRef?: (?HTMLElement) => void, prefetchDocument: (documentId: string) => Promise, depth: number, -}; +|}; @observer class DocumentLink extends React.Component { @@ -49,6 +51,18 @@ class DocumentLink extends React.Component { prefetchDocument(node.id); }; + handleTitleChange = async (title: string) => { + const document = this.props.documents.get(this.props.node.id); + if (!document) return; + + await this.props.documents.update({ + id: document.id, + lastRevision: document.revision, + text: document.text, + title, + }); + }; + isActiveDocument = () => { return ( this.props.activeDocument && @@ -69,6 +83,7 @@ class DocumentLink extends React.Component { activeDocumentRef, prefetchDocument, depth, + canUpdate, } = this.props; const showChildren = !!( @@ -81,6 +96,7 @@ class DocumentLink extends React.Component { this.isActiveDocument()) ); const document = documents.get(node.id); + const title = node.title || "Untitled"; return ( { state: { title: node.title }, }} expanded={showChildren ? true : undefined} - label={node.title || "Untitled"} + label={ + + } depth={depth} exact={false} menuOpen={this.menuOpen} @@ -124,6 +146,7 @@ class DocumentLink extends React.Component { activeDocument={activeDocument} prefetchDocument={prefetchDocument} depth={depth + 1} + canUpdate={canUpdate} /> ))} diff --git a/app/components/Sidebar/components/EditableTitle.js b/app/components/Sidebar/components/EditableTitle.js new file mode 100644 index 000000000..6efae95c2 --- /dev/null +++ b/app/components/Sidebar/components/EditableTitle.js @@ -0,0 +1,98 @@ +// @flow +import * as React from "react"; +import styled from "styled-components"; +import useStores from "hooks/useStores"; + +type Props = {| + onSubmit: (title: string) => Promise, + title: string, + canUpdate: boolean, +|}; + +function EditableTitle({ title, onSubmit, canUpdate }: Props) { + const [isEditing, setIsEditing] = React.useState(false); + const [originalValue, setOriginalValue] = React.useState(title); + const [value, setValue] = React.useState(title); + const { ui } = useStores(); + + React.useEffect(() => { + setValue(title); + }, [title]); + + const handleChange = React.useCallback((event) => { + setValue(event.target.value); + }, []); + + const handleDoubleClick = React.useCallback((event) => { + event.preventDefault(); + event.stopPropagation(); + setIsEditing(true); + }, []); + + const handleKeyDown = React.useCallback( + (event) => { + if (event.key === "Escape") { + setIsEditing(false); + setValue(originalValue); + } + }, + [originalValue] + ); + + const handleSave = React.useCallback(async () => { + setIsEditing(false); + + if (value === originalValue) { + return; + } + + if (document) { + try { + await onSubmit(value); + setOriginalValue(value); + } catch (error) { + setValue(originalValue); + ui.showToast(error.message); + throw error; + } + } + }, [ui, originalValue, value, onSubmit]); + + return ( + <> + {isEditing ? ( +
+ +
+ ) : ( + + {value} + + )} + + ); +} + +const Input = styled.input` + margin-left: -4px; + background: ${(props) => props.theme.background}; + width: calc(100% - 10px); + border-radius: 3px; + border: 1px solid ${(props) => props.theme.inputBorderFocused}; + padding: 5px 6px; + margin: -4px; + height: 32px; + + &:focus { + outline-color: ${(props) => props.theme.primary}; + } +`; + +export default EditableTitle; diff --git a/app/scenes/Document/components/Document.js b/app/scenes/Document/components/Document.js index 73812570f..431d8726c 100644 --- a/app/scenes/Document/components/Document.js +++ b/app/scenes/Document/components/Document.js @@ -75,16 +75,10 @@ class DocumentScene extends React.Component { @observable isDirty: boolean = false; @observable isEmpty: boolean = true; @observable moveModalOpen: boolean = false; - @observable lastRevision: number; - @observable title: string; + @observable lastRevision: number = this.props.document.revision; + @observable title: string = this.props.document.title; getEditorText: () => string = () => this.props.document.text; - constructor(props) { - super(); - this.title = props.document.title; - this.lastRevision = props.document.revision; - } - componentDidMount() { this.updateIsDirty(); this.updateBackground(); @@ -112,10 +106,13 @@ class DocumentScene extends React.Component { } } - if (document.injectTemplate) { - this.isDirty = true; + if (!this.isDirty && document.title !== this.title) { this.title = document.title; + } + + if (document.injectTemplate) { document.injectTemplate = false; + this.isDirty = true; } this.updateBackground(); diff --git a/package.json b/package.json index be798b970..76f8dc8eb 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build:webpack": "webpack --config webpack.config.prod.js", "build": "yarn clean && yarn build:webpack && yarn build:server", "start": "node ./build/server/index.js", - "dev": "nodemon --exec \"yarn build:server && node build/server/index.js\" -e js --ignore build/", + "dev": "nodemon --exec \"yarn build:server && node build/server/index.js\" -e js --ignore build/ --ignore app/", "lint": "eslint app server shared", "flow": "flow", "deploy": "git push heroku master", @@ -193,4 +193,4 @@ "js-yaml": "^3.13.1" }, "version": "0.47.1" -} +} \ No newline at end of file