feat: Backlinks (#979)
* feat: backlinks * feat: add backlinkDocumentId to documents.list * chore: refactor fix: create and delete backlink handling * fix: guard against self links * feat: basic frontend fix: race condition * styling * test: fix parse ids * self review * linting * feat: Improved link styling * fix: Increase clickable area at bottom of doc / between references * perf: global styles are SLOW
This commit is contained in:
@@ -9,10 +9,19 @@ import {
|
||||
presentCollection,
|
||||
presentRevision,
|
||||
} from '../presenters';
|
||||
import { Document, Collection, Share, Star, View, Revision } from '../models';
|
||||
import {
|
||||
Document,
|
||||
Collection,
|
||||
Share,
|
||||
Star,
|
||||
View,
|
||||
Revision,
|
||||
Backlink,
|
||||
} from '../models';
|
||||
import { InvalidRequestError } from '../errors';
|
||||
import events from '../events';
|
||||
import policy from '../policies';
|
||||
import { sequelize } from '../sequelize';
|
||||
|
||||
const Op = Sequelize.Op;
|
||||
const { authorize, cannot } = policy;
|
||||
@@ -22,6 +31,7 @@ router.post('documents.list', auth(), pagination(), async ctx => {
|
||||
const { sort = 'updatedAt' } = ctx.body;
|
||||
const collectionId = ctx.body.collection;
|
||||
const createdById = ctx.body.user;
|
||||
const backlinkDocumentId = ctx.body.backlinkDocumentId;
|
||||
let direction = ctx.body.direction;
|
||||
if (direction !== 'ASC') direction = 'DESC';
|
||||
|
||||
@@ -50,6 +60,20 @@ router.post('documents.list', auth(), pagination(), async ctx => {
|
||||
where = { ...where, collectionId: collectionIds };
|
||||
}
|
||||
|
||||
if (backlinkDocumentId) {
|
||||
const backlinks = await Backlink.findAll({
|
||||
attributes: ['reverseDocumentId'],
|
||||
where: {
|
||||
documentId: backlinkDocumentId,
|
||||
},
|
||||
});
|
||||
|
||||
where = {
|
||||
...where,
|
||||
id: backlinks.map(backlink => backlink.reverseDocumentId),
|
||||
};
|
||||
}
|
||||
|
||||
// add the users starred state to the response by default
|
||||
const starredScope = { method: ['withStarred', user.id] };
|
||||
const documents = await Document.scope('defaultScope', starredScope).findAll({
|
||||
@@ -620,7 +644,7 @@ router.post('documents.update', auth(), async ctx => {
|
||||
|
||||
// Update document
|
||||
if (title) document.title = title;
|
||||
//append to document
|
||||
|
||||
if (append) {
|
||||
document.text += text;
|
||||
} else if (text) {
|
||||
@@ -628,28 +652,40 @@ router.post('documents.update', auth(), async ctx => {
|
||||
}
|
||||
document.lastModifiedById = user.id;
|
||||
|
||||
if (publish) {
|
||||
await document.publish();
|
||||
let transaction;
|
||||
try {
|
||||
transaction = await sequelize.transaction();
|
||||
|
||||
events.add({
|
||||
name: 'documents.publish',
|
||||
modelId: document.id,
|
||||
collectionId: document.collectionId,
|
||||
teamId: document.teamId,
|
||||
actorId: user.id,
|
||||
});
|
||||
} else {
|
||||
await document.save({ autosave });
|
||||
if (publish) {
|
||||
await document.publish({ transaction });
|
||||
await transaction.commit();
|
||||
|
||||
events.add({
|
||||
name: 'documents.update',
|
||||
modelId: document.id,
|
||||
collectionId: document.collectionId,
|
||||
teamId: document.teamId,
|
||||
actorId: user.id,
|
||||
autosave,
|
||||
done,
|
||||
});
|
||||
events.add({
|
||||
name: 'documents.publish',
|
||||
modelId: document.id,
|
||||
collectionId: document.collectionId,
|
||||
teamId: document.teamId,
|
||||
actorId: user.id,
|
||||
});
|
||||
} else {
|
||||
await document.save({ autosave, transaction });
|
||||
await transaction.commit();
|
||||
|
||||
events.add({
|
||||
name: 'documents.update',
|
||||
modelId: document.id,
|
||||
collectionId: document.collectionId,
|
||||
teamId: document.teamId,
|
||||
actorId: user.id,
|
||||
autosave,
|
||||
done,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
if (transaction) {
|
||||
await transaction.rollback();
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
ctx.body = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable flowtype/require-valid-file-annotation */
|
||||
import TestServer from 'fetch-test-server';
|
||||
import app from '../app';
|
||||
import { Document, View, Star, Revision } from '../models';
|
||||
import { Document, View, Star, Revision, Backlink } from '../models';
|
||||
import { flushdb, seed } from '../test/support';
|
||||
import {
|
||||
buildShare,
|
||||
@@ -252,6 +252,31 @@ describe('#documents.list', async () => {
|
||||
expect(body.data.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should return backlinks', async () => {
|
||||
const { user, document } = await seed();
|
||||
const anotherDoc = await buildDocument({
|
||||
title: 'another document',
|
||||
text: 'random text',
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
});
|
||||
|
||||
await Backlink.create({
|
||||
reverseDocumentId: anotherDoc.id,
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
const res = await server.post('/api/documents.list', {
|
||||
body: { token: user.getJwtToken(), backlinkDocumentId: document.id },
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.data.length).toEqual(1);
|
||||
expect(body.data[0].id).toEqual(anotherDoc.id);
|
||||
});
|
||||
|
||||
it('should require authentication', async () => {
|
||||
const res = await server.post('/api/documents.list');
|
||||
const body = await res.json();
|
||||
|
||||
Reference in New Issue
Block a user