feat: Templates (#1399)

* Migrations
* New from template
* fix: Don't allow public share of template
* chore: Template badges
* fix: Collection active
* feat: New doc button on template list item
* feat: New template menu
* fix: Sorting
* feat: Templates onboarding notice
* fix: New doc button showing on archived/deleted templates
This commit is contained in:
Tom Moor
2020-08-08 15:18:37 -07:00
committed by GitHub
parent 59c24aba7c
commit 869fc086d6
51 changed files with 1007 additions and 327 deletions

View File

@@ -2,7 +2,6 @@
import { action, set, observable, computed } from "mobx";
import addDays from "date-fns/add_days";
import invariant from "invariant";
import { client } from "utils/ApiClient";
import parseTitle from "shared/utils/parseTitle";
import unescape from "shared/utils/unescape";
import BaseModel from "models/BaseModel";
@@ -20,6 +19,7 @@ type SaveOptions = {
export default class Document extends BaseModel {
@observable isSaving: boolean = false;
@observable embedsDisabled: boolean = false;
@observable injectTemplate: boolean = false;
store: DocumentsStore;
collaborators: User[];
@@ -35,6 +35,8 @@ export default class Document extends BaseModel {
text: string;
title: string;
emoji: string;
template: boolean;
templateId: ?string;
parentDocumentId: ?string;
publishedAt: ?string;
archivedAt: string;
@@ -43,11 +45,24 @@ export default class Document extends BaseModel {
urlId: string;
revision: number;
constructor(fields: Object, store: DocumentsStore) {
super(fields, store);
if (this.isNew && this.isFromTemplate) {
this.title = "";
}
}
get emoji() {
const { emoji } = parseTitle(this.title);
return emoji;
}
@computed
get noun(): string {
return this.template ? "template" : "document";
}
@computed
get isOnlyTitle(): boolean {
return !this.text.trim();
@@ -73,11 +88,21 @@ export default class Document extends BaseModel {
return !!this.deletedAt;
}
@computed
get isTemplate(): boolean {
return !!this.template;
}
@computed
get isDraft(): boolean {
return !this.publishedAt;
}
@computed
get titleWithDefault(): string {
return this.title || "Untitled";
}
@computed
get permanentlyDeletedAt(): ?string {
if (!this.deletedAt) {
@@ -87,6 +112,21 @@ export default class Document extends BaseModel {
return addDays(new Date(this.deletedAt), 30).toString();
}
@computed
get isNew(): boolean {
return this.createdAt === this.updatedAt;
}
@computed
get isFromTemplate(): boolean {
return !!this.templateId;
}
@computed
get placeholder(): ?string {
return this.isTemplate ? "Start your template…" : "Start with a title…";
}
@action
share = async () => {
return this.store.rootStore.shares.create({ documentId: this.id });
@@ -157,10 +197,16 @@ export default class Document extends BaseModel {
};
@action
fetch = async () => {
const res = await client.post("/documents.info", { id: this.id });
invariant(res && res.data, "Data should be available");
this.updateFromJson(res.data);
templatize = async () => {
return this.store.templatize(this.id);
};
@action
updateFromTemplate = async (template: Document) => {
this.templateId = template.id;
this.title = template.title;
this.text = template.text;
this.injectTemplate = true;
};
@action
@@ -186,6 +232,7 @@ export default class Document extends BaseModel {
id: this.id,
title: this.title,
text: this.text,
templateId: this.templateId,
lastRevision: options.lastRevision,
...options,
});
@@ -229,7 +276,7 @@ export default class Document extends BaseModel {
// Firefox support requires the anchor tag be in the DOM to trigger the dl
if (document.body) document.body.appendChild(a);
a.href = url;
a.download = `${this.title || "Untitled"}.md`;
a.download = `${this.titleWithDefault}.md`;
a.click();
};
}

View File

@@ -18,6 +18,7 @@ class Event extends BaseModel {
email: string,
title: string,
published: boolean,
templateId: string,
};
get model() {