Added the ability to rearrange Docs
This commit is contained in:
@@ -9,8 +9,9 @@ import { storedIconsSchema } from '/imports/api/icons/Icons.js';
|
||||
import '/imports/api/library/methods/index.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import { restore } from '/imports/api/parenting/softRemove.js';
|
||||
import { reorderDocs } from '/imports/api/parenting/order.js';
|
||||
import { getAncestry } from '/imports/api/parenting/parenting.js';
|
||||
import { getAncestry, updateParent } from '/imports/api/parenting/parenting.js';
|
||||
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree';
|
||||
import { getDocsInDepthFirstOrder } from '/imports/api/parenting/getDescendantsInDepthFirstOrder';
|
||||
|
||||
const Docs = new Mongo.Collection('docs');
|
||||
|
||||
@@ -184,10 +185,7 @@ const insertDoc = new ValidatedMethod({
|
||||
}
|
||||
|
||||
const docId = Docs.insert(doc);
|
||||
reorderDocs({
|
||||
collection: Docs,
|
||||
ancestorId: 'root',
|
||||
});
|
||||
reorderDocs();
|
||||
return docId;
|
||||
},
|
||||
});
|
||||
@@ -231,10 +229,7 @@ const updateDoc = new ValidatedMethod({
|
||||
if (pathString === 'name' || pathString === 'urlName') {
|
||||
rebuildDocAncestors(_id);
|
||||
}
|
||||
reorderDocs({
|
||||
collection: Docs,
|
||||
ancestorId: 'root',
|
||||
});
|
||||
reorderDocs();
|
||||
return updates;
|
||||
},
|
||||
});
|
||||
@@ -284,10 +279,7 @@ const softRemoveDoc = new ValidatedMethod({
|
||||
run({ _id }) {
|
||||
assertDocsEditPermission(this.userId);
|
||||
softRemove({ _id, collection: Docs });
|
||||
reorderDocs({
|
||||
collection: Docs,
|
||||
ancestorId: 'root',
|
||||
});
|
||||
reorderDocs();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -304,13 +296,105 @@ const restoreDoc = new ValidatedMethod({
|
||||
run({ _id }) {
|
||||
assertDocsEditPermission(this.userId);
|
||||
restore({ _id, collection: Docs });
|
||||
reorderDocs({
|
||||
collection: Docs,
|
||||
ancestorId: 'root',
|
||||
});
|
||||
reorderDocs();
|
||||
}
|
||||
});
|
||||
|
||||
const organizeDoc = new ValidatedMethod({
|
||||
name: 'docs.organizeDoc',
|
||||
validate: new SimpleSchema({
|
||||
docId: String,
|
||||
parentId: String,
|
||||
order: {
|
||||
type: Number,
|
||||
// Should end in 0.5 to place it reliably between two existing documents
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ docId, parentId, order }) {
|
||||
let doc = Docs.findOne(docId);
|
||||
// The user must be able to edit both the doc and its parent to move it
|
||||
// successfully
|
||||
assertDocsEditPermission(this.userId);
|
||||
|
||||
// Change the doc's parent
|
||||
updateParent({ docRef: { id: docId, collection: 'docs' }, parentRef: { id: parentId, collection: 'docs' } });
|
||||
// Change the doc's order to be a half step ahead of its target location
|
||||
Docs.update(doc._id, { $set: { order } });
|
||||
|
||||
reorderDocs();
|
||||
},
|
||||
});
|
||||
|
||||
const reorderDoc = new ValidatedMethod({
|
||||
name: 'docs.reorderDoc',
|
||||
validate: new SimpleSchema({
|
||||
docId: String,
|
||||
order: {
|
||||
type: Number,
|
||||
// Should end in 0.5 to place it reliably between two existing documents
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ docId, order }) {
|
||||
assertDocsEditPermission(this.userId);
|
||||
Docs.update(docId, {
|
||||
$set: { order }
|
||||
});
|
||||
reorderDocs();
|
||||
},
|
||||
});
|
||||
|
||||
function reorderDocs() {
|
||||
const docs = Docs.find({ removed: { $ne: true } }, { sort: { order: 1 } }).fetch();
|
||||
const forest = nodeArrayToTree(docs);
|
||||
const orderedDocs = getDocsInDepthFirstOrder(forest);
|
||||
const bulkWrite = [];
|
||||
orderedDocs.forEach((doc, index) => {
|
||||
if (doc.order !== index) {
|
||||
bulkWrite.push({
|
||||
updateOne: {
|
||||
filter: { _id: doc._id },
|
||||
update: { $set: { order: index } },
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
if (Meteor.isServer && bulkWrite.length) {
|
||||
Docs.rawCollection().bulkWrite(
|
||||
bulkWrite,
|
||||
{ ordered: false },
|
||||
function (e) {
|
||||
if (e) {
|
||||
console.error('Bulk write failed: ');
|
||||
console.error(e);
|
||||
}
|
||||
// Rebuild the ancestors of all the docs
|
||||
// This is a pretty slow way to do anything, but docs hardly ever get rearranged
|
||||
docs.forEach(doc => {
|
||||
rebuildDocAncestors(doc._id);
|
||||
});
|
||||
}
|
||||
);
|
||||
} else {
|
||||
bulkWrite.forEach(op => {
|
||||
Docs.update(
|
||||
op.updateOne.filter,
|
||||
op.updateOne.update,
|
||||
{ selector: { type: 'any' } }
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
DocSchema,
|
||||
insertDoc,
|
||||
@@ -319,6 +403,8 @@ export {
|
||||
pullFromDoc,
|
||||
softRemoveDoc,
|
||||
restoreDoc,
|
||||
organizeDoc,
|
||||
reorderDoc,
|
||||
};
|
||||
|
||||
export default Docs;
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
Documentation
|
||||
</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-app-bar-nav-icon
|
||||
v-if="editing"
|
||||
@click="toggleRightDrawer"
|
||||
>
|
||||
<v-icon>mdi-file-tree</v-icon>
|
||||
</v-app-bar-nav-icon>
|
||||
<v-btn
|
||||
v-if="canEdit"
|
||||
icon
|
||||
@@ -45,6 +51,7 @@ export default {
|
||||
methods: {
|
||||
...mapMutations([
|
||||
'toggleDrawer',
|
||||
'toggleRightDrawer',
|
||||
]),
|
||||
toggleEdit() {
|
||||
if (!this.canEdit) return;
|
||||
|
||||
84
app/imports/client/ui/docs/DocsRightDrawer.vue
Normal file
84
app/imports/client/ui/docs/DocsRightDrawer.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template lang="html">
|
||||
<v-navigation-drawer
|
||||
v-if="editing"
|
||||
v-model="drawer"
|
||||
app
|
||||
right
|
||||
>
|
||||
<tree-node-list
|
||||
:key="refreshTree"
|
||||
:children="docs"
|
||||
:organize="true"
|
||||
:selected-node="undefined"
|
||||
group="docs"
|
||||
@selected="selected"
|
||||
@reordered="reordered"
|
||||
@reorganized="reorganized"
|
||||
/>
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js';
|
||||
import TreeNodeList from '/imports/client/ui/components/tree/TreeNodeList.vue';
|
||||
import { organizeDoc, reorderDoc } from '/imports/api/docs/Docs.js';
|
||||
import Docs from '/imports/api/docs/Docs.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TreeNodeList,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
refreshTree: 0,
|
||||
}},
|
||||
computed: {
|
||||
drawer: {
|
||||
get () {
|
||||
return this.$store.state.rightDrawer;
|
||||
},
|
||||
set (value) {
|
||||
this.$store.commit('setRightDrawer', value);
|
||||
},
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
editing() {
|
||||
return Session.get('editingDocs');
|
||||
},
|
||||
docs() {
|
||||
const docs = Docs.find({ removed: {$ne: true} }, { sort: {order: 1} }).fetch();
|
||||
return nodeArrayToTree(docs);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
selected(docId) {
|
||||
const doc = Docs.findOne(docId);
|
||||
if (!doc) return;
|
||||
console.log(doc.href);
|
||||
this.$router.push(doc.href);
|
||||
},
|
||||
reordered({ doc, newIndex }) {
|
||||
reorderDoc.call({
|
||||
docId: doc._id,
|
||||
order: newIndex,
|
||||
});
|
||||
},
|
||||
reorganized({ doc, parent, newIndex }) {
|
||||
if (!parent) {
|
||||
this.refreshTree += 1;
|
||||
console.error('Moving docs to root level isn\'t implemented');
|
||||
return;
|
||||
}
|
||||
organizeDoc.call({
|
||||
docId: doc._id,
|
||||
parentId: parent?._id,
|
||||
order: newIndex,
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -112,10 +112,14 @@ export default {
|
||||
if (!this.doc) return Docs.find({
|
||||
'parent': undefined,
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
});
|
||||
return Docs.find({
|
||||
'parent.id': this.doc._id,
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
})
|
||||
},
|
||||
siblingDocs() {
|
||||
@@ -123,6 +127,8 @@ export default {
|
||||
return Docs.find({
|
||||
'parent.id': this.doc.parent?.id,
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
});
|
||||
},
|
||||
editing() {
|
||||
|
||||
@@ -40,6 +40,7 @@ const Maintenance = () => import('/imports/client/ui/pages/Maintenance.vue');
|
||||
const Files = () => import('/imports/client/ui/pages/Files.vue');
|
||||
const DocsPage = () => import('/imports/client/ui/pages/DocsPage.vue');
|
||||
const DocToolbar = () => import('/imports/client/ui/docs/DocToolbar.vue');
|
||||
const DocsRightDrawer = () => import('/imports/client/ui/docs/DocsRightDrawer.vue');
|
||||
|
||||
// Not found
|
||||
const NotFound = () => import('/imports/client/ui/pages/NotFound.vue');
|
||||
@@ -285,6 +286,7 @@ RouterFactory.configure(router => {
|
||||
components: {
|
||||
default: DocsPage,
|
||||
toolbar: DocToolbar,
|
||||
rightDrawer: DocsRightDrawer,
|
||||
},
|
||||
meta: {
|
||||
title: 'Documentation',
|
||||
|
||||
Reference in New Issue
Block a user