Merge branch 'master' into inputs
This commit is contained in:
10
README.md
10
README.md
@ -14,6 +14,12 @@
|
|||||||
Sequelize is used to create and run migrations, for example:
|
Sequelize is used to create and run migrations, for example:
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn run sequelize -- migration:create
|
yarn run sequelize migration:create
|
||||||
yarn run sequelize -- db:migrate
|
yarn run sequelize db:migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
Or to run migrations on test database:
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn run sequelize db:migrate -- --env test
|
||||||
```
|
```
|
||||||
|
@ -117,7 +117,7 @@ type KeyData = {
|
|||||||
<Editor
|
<Editor
|
||||||
key={this.props.starred}
|
key={this.props.starred}
|
||||||
ref={ref => (this.editor = ref)}
|
ref={ref => (this.editor = ref)}
|
||||||
placeholder="Start with a title…"
|
placeholder="Start with a title..."
|
||||||
className={cx(styles.editor, { readOnly: this.props.readOnly })}
|
className={cx(styles.editor, { readOnly: this.props.readOnly })}
|
||||||
schema={this.schema}
|
schema={this.schema}
|
||||||
plugins={this.plugins}
|
plugins={this.plugins}
|
||||||
|
@ -59,7 +59,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { user, auth, ui, collections } = this.props;
|
const { user, auth, ui } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container column auto>
|
<Container column auto>
|
||||||
@ -112,7 +112,8 @@ type Props = {
|
|||||||
<LinkSection>
|
<LinkSection>
|
||||||
{ui.activeCollection
|
{ui.activeCollection
|
||||||
? <SidebarCollection
|
? <SidebarCollection
|
||||||
collection={collections.getById(ui.activeCollection)}
|
document={ui.activeDocument}
|
||||||
|
collection={ui.activeCollection}
|
||||||
/>
|
/>
|
||||||
: <SidebarCollectionList />}
|
: <SidebarCollectionList />}
|
||||||
</LinkSection>
|
</LinkSection>
|
||||||
|
@ -7,26 +7,49 @@ import styled from 'styled-components';
|
|||||||
import SidebarLink from '../SidebarLink';
|
import SidebarLink from '../SidebarLink';
|
||||||
|
|
||||||
import Collection from 'models/Collection';
|
import Collection from 'models/Collection';
|
||||||
|
import Document from 'models/Document';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
collection: Collection,
|
collection: ?Collection,
|
||||||
|
document: ?Document,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SidebarCollection = ({ collection }: Props) => {
|
class SidebarCollection extends React.Component {
|
||||||
|
props: Props;
|
||||||
|
|
||||||
|
renderDocuments(documentList) {
|
||||||
|
const { document } = this.props;
|
||||||
|
|
||||||
|
if (document) {
|
||||||
|
return documentList.map(doc => (
|
||||||
|
<Flex column key={doc.id}>
|
||||||
|
<SidebarLink key={doc.id} to={doc.url}>
|
||||||
|
{doc.title}
|
||||||
|
</SidebarLink>
|
||||||
|
{(document.pathToDocument.includes(doc.id) ||
|
||||||
|
document.id === doc.id) &&
|
||||||
|
<Children>
|
||||||
|
{doc.children && this.renderDocuments(doc.children)}
|
||||||
|
</Children>}
|
||||||
|
</Flex>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { collection } = this.props;
|
||||||
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
return (
|
return (
|
||||||
<Flex column>
|
<Flex column>
|
||||||
<Header>{collection.name}</Header>
|
<Header>{collection.name}</Header>
|
||||||
{collection.documents.map(document => (
|
{this.renderDocuments(collection.documents)}
|
||||||
<SidebarLink key={document.id} to={document.url}>
|
|
||||||
{document.title}
|
|
||||||
</SidebarLink>
|
|
||||||
))}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const Header = styled(Flex)`
|
const Header = styled(Flex)`
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@ -36,4 +59,8 @@ const Header = styled(Flex)`
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const Children = styled(Flex)`
|
||||||
|
margin-left: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
export default observer(SidebarCollection);
|
export default observer(SidebarCollection);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink, withRouter } from 'react-router-dom';
|
||||||
import { Flex } from 'reflexbox';
|
import { Flex } from 'reflexbox';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
@ -9,11 +9,20 @@ const activeStyle = {
|
|||||||
color: '#000000',
|
color: '#000000',
|
||||||
};
|
};
|
||||||
|
|
||||||
const SidebarLink = observer(props => (
|
@observer class SidebarLink extends React.Component {
|
||||||
|
shouldComponentUpdate(nextProps) {
|
||||||
|
// Navlink is having issues updating, forcing update on URL changes
|
||||||
|
return this.props.match !== nextProps.match;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
<LinkContainer>
|
<LinkContainer>
|
||||||
<NavLink {...props} activeStyle={activeStyle} />
|
<NavLink exact {...this.props} activeStyle={activeStyle} />
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
));
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const LinkContainer = styled(Flex)`
|
const LinkContainer = styled(Flex)`
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
@ -23,4 +32,4 @@ const LinkContainer = styled(Flex)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default SidebarLink;
|
export default withRouter(SidebarLink);
|
||||||
|
83
frontend/models/Document.js
Normal file
83
frontend/models/Document.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// @flow
|
||||||
|
import { extendObservable, action, runInAction, computed } from 'mobx';
|
||||||
|
import invariant from 'invariant';
|
||||||
|
|
||||||
|
import ApiClient, { client } from 'utils/ApiClient';
|
||||||
|
import stores from 'stores';
|
||||||
|
import ErrorsStore from 'stores/ErrorsStore';
|
||||||
|
|
||||||
|
import type { User } from 'types';
|
||||||
|
import Collection from './Collection';
|
||||||
|
|
||||||
|
class Document {
|
||||||
|
collaborators: Array<User>;
|
||||||
|
collection: Collection;
|
||||||
|
createdAt: string;
|
||||||
|
createdBy: User;
|
||||||
|
html: string;
|
||||||
|
id: string;
|
||||||
|
private: boolean;
|
||||||
|
starred: boolean;
|
||||||
|
team: string;
|
||||||
|
text: string;
|
||||||
|
title: string;
|
||||||
|
updatedAt: string;
|
||||||
|
updatedBy: User;
|
||||||
|
url: string;
|
||||||
|
views: number;
|
||||||
|
|
||||||
|
client: ApiClient;
|
||||||
|
errors: ErrorsStore;
|
||||||
|
|
||||||
|
/* Computed */
|
||||||
|
|
||||||
|
@computed get pathToDocument(): Array<string> {
|
||||||
|
let path;
|
||||||
|
const traveler = (nodes, previousPath) => {
|
||||||
|
nodes.forEach(childNode => {
|
||||||
|
const newPath = [...previousPath, childNode.id];
|
||||||
|
if (childNode.id === this.id) {
|
||||||
|
path = newPath;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
return traveler(childNode.children, newPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.collection.documents) {
|
||||||
|
traveler(this.collection.documents, []);
|
||||||
|
invariant(path, 'Path is not available for collection, abort');
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actions */
|
||||||
|
|
||||||
|
@action update = async () => {
|
||||||
|
try {
|
||||||
|
const res = await this.client.post('/documents.info', { id: this.id });
|
||||||
|
invariant(res && res.data, 'Document API response should be available');
|
||||||
|
const { data } = res;
|
||||||
|
runInAction('Document#update', () => {
|
||||||
|
this.updateData(data);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
this.errors.add('Document failed loading');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateData(data: Document) {
|
||||||
|
extendObservable(this, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(document: Document) {
|
||||||
|
this.updateData(document);
|
||||||
|
this.client = client;
|
||||||
|
this.errors = stores.errors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Document;
|
@ -18,7 +18,7 @@ class CollectionStore {
|
|||||||
invariant(res && res.data, 'Data should be available');
|
invariant(res && res.data, 'Data should be available');
|
||||||
const { data } = res;
|
const { data } = res;
|
||||||
|
|
||||||
if (data.type === 'atlas') this.redirectUrl = data.recentDocuments[0].url;
|
if (data.type === 'atlas') this.redirectUrl = data.documents[0].url;
|
||||||
else throw new Error('TODO code up non-atlas collections');
|
else throw new Error('TODO code up non-atlas collections');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
@ -44,18 +44,27 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.props.newDocument) {
|
this.loadDocument(this.props);
|
||||||
this.store.collectionId = this.props.match.params.id;
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (nextProps.match.params.id !== this.props.match.params.id)
|
||||||
|
this.loadDocument(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadDocument(props) {
|
||||||
|
if (props.newDocument) {
|
||||||
|
this.store.collectionId = props.match.params.id;
|
||||||
this.store.newDocument = true;
|
this.store.newDocument = true;
|
||||||
} else if (this.props.match.params.edit) {
|
} else if (props.match.params.edit) {
|
||||||
this.store.documentId = this.props.match.params.id;
|
this.store.documentId = props.match.params.id;
|
||||||
this.store.fetchDocument();
|
this.store.fetchDocument();
|
||||||
} else if (this.props.newChildDocument) {
|
} else if (props.newChildDocument) {
|
||||||
this.store.documentId = this.props.match.params.id;
|
this.store.documentId = props.match.params.id;
|
||||||
this.store.newChildDocument = true;
|
this.store.newChildDocument = true;
|
||||||
this.store.fetchDocument();
|
this.store.fetchDocument();
|
||||||
} else {
|
} else {
|
||||||
this.store.documentId = this.props.match.params.id;
|
this.store.documentId = props.match.params.id;
|
||||||
this.store.newDocument = false;
|
this.store.newDocument = false;
|
||||||
this.store.fetchDocument();
|
this.store.fetchDocument();
|
||||||
}
|
}
|
||||||
@ -64,7 +73,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.ui.clearActiveCollection();
|
this.props.ui.clearActiveDocument();
|
||||||
}
|
}
|
||||||
|
|
||||||
onEdit = () => {
|
onEdit = () => {
|
||||||
@ -117,20 +126,19 @@ type Props = {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container flex column>
|
<Container column auto>
|
||||||
|
{titleText && <PageTitle title={titleText} />}
|
||||||
|
<Prompt when={this.store.hasPendingChanges} message={DISCARD_CHANGES} />
|
||||||
|
|
||||||
<PagePadding auto justify="center">
|
<PagePadding auto justify="center">
|
||||||
<PageTitle title={titleText} />
|
{this.store.isFetching
|
||||||
<Prompt
|
? <CenteredContent>
|
||||||
when={this.store.hasPendingChanges}
|
|
||||||
message={DISCARD_CHANGES}
|
|
||||||
/>
|
|
||||||
{this.store.isFetching &&
|
|
||||||
<CenteredContent>
|
|
||||||
<PreviewLoading />
|
<PreviewLoading />
|
||||||
</CenteredContent>}
|
</CenteredContent>
|
||||||
{this.store.document &&
|
: this.store.document &&
|
||||||
<DocumentContainer>
|
<DocumentContainer>
|
||||||
<Editor
|
<Editor
|
||||||
|
key={this.store.document.id}
|
||||||
text={this.store.document.text}
|
text={this.store.document.text}
|
||||||
onImageUploadStart={this.onImageUploadStart}
|
onImageUploadStart={this.onImageUploadStart}
|
||||||
onImageUploadStop={this.onImageUploadStop}
|
onImageUploadStop={this.onImageUploadStop}
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
@import '~styles/constants.scss';
|
|
||||||
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
position: fixed;
|
|
||||||
justify-content: center;
|
|
||||||
top: $headerHeight;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
@ -4,7 +4,8 @@ import get from 'lodash/get';
|
|||||||
import invariant from 'invariant';
|
import invariant from 'invariant';
|
||||||
import { client } from 'utils/ApiClient';
|
import { client } from 'utils/ApiClient';
|
||||||
import emojify from 'utils/emojify';
|
import emojify from 'utils/emojify';
|
||||||
import type { Document, NavigationNode } from 'types';
|
import Document from 'models/Document';
|
||||||
|
import UiStore from 'stores/UiStore';
|
||||||
|
|
||||||
type SaveProps = { redirect?: boolean };
|
type SaveProps = { redirect?: boolean };
|
||||||
|
|
||||||
@ -24,13 +25,14 @@ const parseHeader = text => {
|
|||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
history: Object,
|
history: Object,
|
||||||
|
ui: UiStore,
|
||||||
};
|
};
|
||||||
|
|
||||||
class DocumentStore {
|
class DocumentStore {
|
||||||
|
document: Document;
|
||||||
@observable collapsedNodes: string[] = [];
|
@observable collapsedNodes: string[] = [];
|
||||||
@observable documentId = null;
|
@observable documentId = null;
|
||||||
@observable collectionId = null;
|
@observable collectionId = null;
|
||||||
@observable document: Document;
|
|
||||||
@observable parentDocument: Document;
|
@observable parentDocument: Document;
|
||||||
@observable hasPendingChanges = false;
|
@observable hasPendingChanges = false;
|
||||||
@observable newDocument: ?boolean;
|
@observable newDocument: ?boolean;
|
||||||
@ -42,6 +44,7 @@ class DocumentStore {
|
|||||||
@observable isUploading: boolean = false;
|
@observable isUploading: boolean = false;
|
||||||
|
|
||||||
history: Object;
|
history: Object;
|
||||||
|
ui: UiStore;
|
||||||
|
|
||||||
/* Computed */
|
/* Computed */
|
||||||
|
|
||||||
@ -49,29 +52,6 @@ class DocumentStore {
|
|||||||
return !!this.document && this.document.collection.type === 'atlas';
|
return !!this.document && this.document.collection.type === 'atlas';
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get pathToDocument(): Array<NavigationNode> {
|
|
||||||
let path;
|
|
||||||
const traveler = (nodes, previousPath) => {
|
|
||||||
nodes.forEach(childNode => {
|
|
||||||
const newPath = [...previousPath, childNode];
|
|
||||||
if (childNode.id === this.document.id) {
|
|
||||||
path = previousPath;
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
return traveler(childNode.chilren, newPath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.document && this.document.collection.documents) {
|
|
||||||
traveler(this.document.collection.documents, []);
|
|
||||||
invariant(path, 'Path is not available for collection, abort');
|
|
||||||
return path.splice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
@action starDocument = async () => {
|
@action starDocument = async () => {
|
||||||
@ -108,18 +88,15 @@ class DocumentStore {
|
|||||||
this.isFetching = true;
|
this.isFetching = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await client.get(
|
const res = await client.get('/documents.info', {
|
||||||
'/documents.info',
|
|
||||||
{
|
|
||||||
id: this.documentId,
|
id: this.documentId,
|
||||||
},
|
});
|
||||||
{ cache: true }
|
|
||||||
);
|
|
||||||
invariant(res && res.data, 'Data should be available');
|
invariant(res && res.data, 'Data should be available');
|
||||||
if (this.newChildDocument) {
|
if (this.newChildDocument) {
|
||||||
this.parentDocument = res.data;
|
this.parentDocument = res.data;
|
||||||
} else {
|
} else {
|
||||||
this.document = res.data;
|
this.document = new Document(res.data);
|
||||||
|
this.ui.setActiveDocument(this.document);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Something went wrong');
|
console.error('Something went wrong');
|
||||||
@ -133,9 +110,7 @@ class DocumentStore {
|
|||||||
this.isSaving = true;
|
this.isSaving = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await client.post(
|
const res = await client.post('/documents.create', {
|
||||||
'/documents.create',
|
|
||||||
{
|
|
||||||
parentDocument: get(this.parentDocument, 'id'),
|
parentDocument: get(this.parentDocument, 'id'),
|
||||||
collection: get(
|
collection: get(
|
||||||
this.parentDocument,
|
this.parentDocument,
|
||||||
@ -144,9 +119,7 @@ class DocumentStore {
|
|||||||
),
|
),
|
||||||
title: get(this.document, 'title', 'Untitled document'),
|
title: get(this.document, 'title', 'Untitled document'),
|
||||||
text: get(this.document, 'text'),
|
text: get(this.document, 'text'),
|
||||||
},
|
});
|
||||||
{ cache: true }
|
|
||||||
);
|
|
||||||
invariant(res && res.data, 'Data should be available');
|
invariant(res && res.data, 'Data should be available');
|
||||||
const { url } = res.data;
|
const { url } = res.data;
|
||||||
|
|
||||||
@ -164,15 +137,11 @@ class DocumentStore {
|
|||||||
this.isSaving = true;
|
this.isSaving = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await client.post(
|
const res = await client.post('/documents.update', {
|
||||||
'/documents.update',
|
|
||||||
{
|
|
||||||
id: this.documentId,
|
id: this.documentId,
|
||||||
title: get(this.document, 'title', 'Untitled document'),
|
title: get(this.document, 'title', 'Untitled document'),
|
||||||
text: get(this.document, 'text'),
|
text: get(this.document, 'text'),
|
||||||
},
|
});
|
||||||
{ cache: true }
|
|
||||||
);
|
|
||||||
invariant(res && res.data, 'Data should be available');
|
invariant(res && res.data, 'Data should be available');
|
||||||
const { url } = res.data;
|
const { url } = res.data;
|
||||||
|
|
||||||
@ -210,6 +179,7 @@ class DocumentStore {
|
|||||||
|
|
||||||
constructor(options: Options) {
|
constructor(options: Options) {
|
||||||
this.history = options.history;
|
this.history = options.history;
|
||||||
|
this.ui = options.ui;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,26 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import { observable, action } from 'mobx';
|
import { observable, action, computed } from 'mobx';
|
||||||
|
import type { Document } from 'types';
|
||||||
|
import Collection from 'models/Collection';
|
||||||
|
|
||||||
class UiStore {
|
class UiStore {
|
||||||
@observable activeCollection: ?string;
|
@observable activeDocument: ?Document;
|
||||||
@observable editMode: boolean = false;
|
@observable editMode: boolean = false;
|
||||||
|
|
||||||
|
/* Computed */
|
||||||
|
|
||||||
|
@computed get activeCollection(): ?Collection {
|
||||||
|
return this.activeDocument ? this.activeDocument.collection : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
@action setActiveCollection = (id: string): void => {
|
@action setActiveDocument = (document: Document): void => {
|
||||||
this.activeCollection = id;
|
this.activeDocument = document;
|
||||||
};
|
};
|
||||||
|
|
||||||
@action clearActiveCollection = (): void => {
|
@action clearActiveDocument = (): void => {
|
||||||
this.activeCollection = null;
|
this.activeDocument = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
@action enableEditMode() {
|
@action enableEditMode() {
|
||||||
|
@ -9,5 +9,6 @@ const stores = {
|
|||||||
ui: new UiStore(),
|
ui: new UiStore(),
|
||||||
errors: new ErrorsStore(),
|
errors: new ErrorsStore(),
|
||||||
};
|
};
|
||||||
|
window.stores = stores;
|
||||||
|
|
||||||
export default stores;
|
export default stores;
|
||||||
|
@ -3,7 +3,7 @@ import apiError from '../../errors';
|
|||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
|
|
||||||
export default function validation() {
|
export default function validation() {
|
||||||
return function validationMiddleware(ctx, next) {
|
return function validationMiddleware(ctx: Object, next: Function) {
|
||||||
ctx.assertPresent = function assertPresent(value, message) {
|
ctx.assertPresent = function assertPresent(value, message) {
|
||||||
if (value === undefined || value === null || value === '') {
|
if (value === undefined || value === null || value === '') {
|
||||||
throw apiError(400, 'validation_error', message);
|
throw apiError(400, 'validation_error', message);
|
||||||
|
@ -29,7 +29,6 @@ const User = sequelize.define(
|
|||||||
classMethods: {
|
classMethods: {
|
||||||
associate: models => {
|
associate: models => {
|
||||||
User.hasMany(models.ApiKey, { as: 'apiKeys' });
|
User.hasMany(models.ApiKey, { as: 'apiKeys' });
|
||||||
User.hasMany(models.Collection, { as: 'collections' });
|
|
||||||
User.hasMany(models.Document, { as: 'documents' });
|
User.hasMany(models.Document, { as: 'documents' });
|
||||||
User.hasMany(models.View, { as: 'views' });
|
User.hasMany(models.View, { as: 'views' });
|
||||||
},
|
},
|
||||||
|
@ -16,7 +16,7 @@ async function present(ctx, collection, includeRecentDocuments = false) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (collection.type === 'atlas')
|
if (collection.type === 'atlas')
|
||||||
data.navigationTree = collection.navigationTree;
|
data.documents = await collection.getDocumentsStructure();
|
||||||
|
|
||||||
if (includeRecentDocuments) {
|
if (includeRecentDocuments) {
|
||||||
const documents = await Document.findAll({
|
const documents = await Document.findAll({
|
||||||
|
Reference in New Issue
Block a user