Added the ability to rearrange Docs

This commit is contained in:
ThaumRystra
2023-11-12 14:49:15 +02:00
parent d578268e99
commit d50ad58526
5 changed files with 203 additions and 18 deletions

View File

@@ -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;

View File

@@ -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;

View 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>

View File

@@ -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() {

View File

@@ -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',