feat: Separate title from body (#1216)

* first pass at updating all Time components each second

* fix a couple date variable typos

* use class style state management instead of hooks

* wip: Separate title from body

* address feedback

* test: Remove unused test

* feat: You in publishing info language
fix: Removal of secondary headings

* After much deliberation… a migration is needed for this to be reliable

* fix: Export to work with new title structure

* fix: Untitled

* fix: Consistent spacing of first editor node

* fix: Emoji in title handling

* fix: Time component not updating for new props

* chore: Add createdAt case

* fix: Conflict after merging new TOC

* PR feedback

* lint

* fix: Heading level adjustment

Co-authored-by: Taylor Lapeyre <taylorlapeyre@gmail.com>
This commit is contained in:
Tom Moor
2020-04-05 15:07:34 -07:00
committed by GitHub
parent a0e73bf4c2
commit 9338a54fe0
19 changed files with 241 additions and 145 deletions

View File

@@ -425,7 +425,7 @@ router.post('documents.revision', auth(), async ctx => {
ctx.body = {
pagination: ctx.state.pagination,
data: presentRevision(revision),
data: await presentRevision(revision),
};
});
@@ -445,9 +445,13 @@ router.post('documents.revisions', auth(), pagination(), async ctx => {
limit: ctx.state.pagination.limit,
});
const data = await Promise.all(
revisions.map(revision => presentRevision(revision))
);
ctx.body = {
pagination: ctx.state.pagination,
data: revisions.map(presentRevision),
data,
};
});

View File

@@ -0,0 +1,19 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('documents', 'version', {
type: Sequelize.SMALLINT,
allowNull: true,
});
await queryInterface.addColumn('revisions', 'version', {
type: Sequelize.SMALLINT,
allowNull: true,
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn('documents', 'version');
await queryInterface.removeColumn('revisions', 'version');
}
};

View File

@@ -17,7 +17,8 @@ import Revision from './Revision';
const Op = Sequelize.Op;
const Markdown = new MarkdownSerializer();
const URL_REGEX = /^[0-9a-zA-Z-_~]*-([a-zA-Z0-9]{10,15})$/;
const DEFAULT_TITLE = 'Untitled';
export const DOCUMENT_VERSION = 1;
slug.defaults.mode = 'rfc3986';
const slugify = text =>
@@ -38,6 +39,7 @@ const createRevision = (doc, options = {}) => {
text: doc.text,
userId: doc.lastModifiedById,
editorVersion: doc.editorVersion,
version: doc.version,
documentId: doc.id,
},
{
@@ -50,16 +52,19 @@ const createUrlId = doc => {
return (doc.urlId = doc.urlId || randomstring.generate(10));
};
const beforeCreate = async doc => {
doc.version = DOCUMENT_VERSION;
return beforeSave(doc);
};
const beforeSave = async doc => {
const { emoji, title } = parseTitle(doc.text);
const { emoji } = parseTitle(doc.text);
// emoji in the title is split out for easier display
doc.emoji = emoji;
// ensure documents have a title
if (!title) {
doc.title = DEFAULT_TITLE;
}
doc.title = doc.title || '';
// add the current user as a collaborator on this doc
if (!doc.collaboratorIds) doc.collaboratorIds = [];
@@ -92,6 +97,7 @@ const Document = sequelize.define(
},
},
},
version: DataTypes.SMALLINT,
editorVersion: DataTypes.STRING,
text: DataTypes.TEXT,
isWelcome: { type: DataTypes.BOOLEAN, defaultValue: false },
@@ -105,7 +111,7 @@ const Document = sequelize.define(
paranoid: true,
hooks: {
beforeValidate: createUrlId,
beforeCreate: beforeSave,
beforeCreate: beforeCreate,
beforeUpdate: beforeSave,
afterCreate: createRevision,
afterUpdate: createRevision,
@@ -427,6 +433,26 @@ Document.addHook('afterCreate', async model => {
// Instance methods
Document.prototype.toMarkdown = function() {
const text = unescape(this.text);
if (this.version) {
return `# ${this.title}\n\n${text}`;
}
return text;
};
Document.prototype.migrateVersion = function() {
// migrate from document version 0 -> 1 means removing the title from the
// document text attribute.
if (!this.version) {
this.text = this.text.replace(/^#\s(.*)\n/, '');
this.version = 1;
return this.save({ silent: true, hooks: false });
}
};
// Note: This method marks the document and it's children as deleted
// in the database, it does not permanantly delete them OR remove
// from the collection structure.

View File

@@ -7,6 +7,7 @@ const Revision = sequelize.define('revision', {
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
version: DataTypes.SMALLINT,
editorVersion: DataTypes.STRING,
title: DataTypes.STRING,
text: DataTypes.TEXT,
@@ -31,4 +32,14 @@ Revision.associate = models => {
);
};
Revision.prototype.migrateVersion = function() {
// migrate from revision version 0 -> 1 means removing the title from the
// revision text attribute.
if (!this.version) {
this.text = this.text.replace(/^#\s(.*)\n/, '');
this.version = 1;
return this.save({ silent: true, hooks: false });
}
};
export default Revision;

View File

@@ -31,7 +31,9 @@ export default async function present(document: Document, options: ?Options) {
...options,
};
const text = options.isPublic
await document.migrateVersion();
let text = options.isPublic
? await replaceImageAttachments(document.text)
: document.text;

View File

@@ -2,7 +2,9 @@
import { Revision } from '../models';
import presentUser from './user';
export default function present(revision: Revision) {
export default async function present(revision: Revision) {
await revision.migrateVersion();
return {
id: revision.id,
documentId: revision.documentId,

View File

@@ -3,14 +3,13 @@ import fs from 'fs';
import JSZip from 'jszip';
import tmp from 'tmp';
import * as Sentry from '@sentry/node';
import unescape from '../../shared/utils/unescape';
import { Attachment, Collection, Document } from '../models';
import { getImageByKey } from './s3';
async function addToArchive(zip, documents) {
for (const doc of documents) {
const document = await Document.findByPk(doc.id);
let text = unescape(document.text);
let text = document.toMarkdown();
const attachments = await Attachment.findAll({
where: { documentId: document.id },
@@ -21,7 +20,7 @@ async function addToArchive(zip, documents) {
text = text.replace(attachment.redirectUrl, encodeURI(attachment.key));
}
zip.file(`${document.title}.md`, text);
zip.file(`${document.title || 'Untitled'}.md`, text);
if (doc.children && doc.children.length) {
const folder = zip.folder(document.title);