Continued implementing sharing
This commit is contained in:
@@ -3,18 +3,64 @@ import { assertOwnership } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||
import getCollectionByName from '/imports/api/parenting/getCollectionByName.js';
|
||||
import { RefSchema } from '/imports/api/parenting/ChildSchema.js';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
|
||||
const setPublic = new ValidatedMethod({
|
||||
name: 'sharing.methods.setPublic',
|
||||
validate: new SimpleSchema({
|
||||
docRef: RefSchema,
|
||||
public: { type: Boolean },
|
||||
isPublic: { type: Boolean },
|
||||
}).validator(),
|
||||
run({docRef, public}){
|
||||
run({docRef, isPublic}){
|
||||
let doc = fetchDocByRef(docRef);
|
||||
assertOwnership(doc, this.userId);
|
||||
getCollectionByName(docRef.collection).update(docRef.id, {$set: {public}});
|
||||
return getCollectionByName(docRef.collection).update(docRef.id, {
|
||||
$set: {public: isPublic},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export { setPublic };
|
||||
const updateUserSharePermissions = new ValidatedMethod({
|
||||
name: 'sharing.methods.updateUserSharePermissions',
|
||||
validate: new SimpleSchema({
|
||||
docRef: RefSchema,
|
||||
userId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
allowedValues: ['reader', 'writer', 'none'],
|
||||
},
|
||||
}).validator(),
|
||||
run({docRef, userId, role}){
|
||||
let doc = fetchDocByRef(docRef);
|
||||
if (role === 'none'){
|
||||
// only asser ownership if you aren't removing yourself
|
||||
if (this.userId !== userId){
|
||||
assertOwnership(doc, this.userId);
|
||||
}
|
||||
return getCollectionByName(docRef.collection).update(docRef.id, {
|
||||
$pullAll: { readers: userId, writers: userId },
|
||||
});
|
||||
}
|
||||
if (doc.owner === userId){
|
||||
throw new Meteor.Error('Sharing update failed',
|
||||
'User is already the owner of this document');
|
||||
}
|
||||
assertOwnership(doc, this.userId);
|
||||
if (role === 'reader'){
|
||||
return getCollectionByName(docRef.collection).update(docRef.id, {
|
||||
$addToSet: { readers: userId },
|
||||
$pullAll: { writers: userId },
|
||||
});
|
||||
} else if (role === 'writer'){
|
||||
return getCollectionByName(docRef.collection).update(docRef.id, {
|
||||
$addToSet: { writers: userId },
|
||||
$pullAll: { readers: userId },
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export { setPublic, updateUserSharePermissions };
|
||||
|
||||
@@ -66,6 +66,16 @@ const userSchema = new SimpleSchema({
|
||||
},
|
||||
'subscribedLibraries.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
subscribedCharacters: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
max: 100,
|
||||
},
|
||||
'subscribedCharacters.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -151,6 +161,31 @@ Meteor.users.setUsername = new ValidatedMethod({
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.users.subscribeToLibrary = new ValidatedMethod({
|
||||
name: 'Users.methods.subscribeToLibrary',
|
||||
validate: new SimpleSchema({
|
||||
libraryId:{
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
subscribe: {
|
||||
type: Boolean,
|
||||
},
|
||||
}).validator(),
|
||||
run({libraryId, subscribe}){
|
||||
if (!this.userId) throw 'Can only subscribe if logged in';
|
||||
if (subscribe){
|
||||
return Meteor.users.update(this.userId, {
|
||||
$addToSet: {subscribedLibraries: libraryId},
|
||||
});
|
||||
} else {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$pullAll: {subscribedLibraries: libraryId},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.users.findUserByUsernameOrEmail = new ValidatedMethod({
|
||||
name: 'Users.methods.findUserByUsernameOrEmail',
|
||||
validate: new SimpleSchema({
|
||||
|
||||
@@ -8,6 +8,7 @@ Meteor.publish('user', function(){
|
||||
username: 1,
|
||||
apiKey: 1,
|
||||
darkMode: 1,
|
||||
subscribedLibraries: 1,
|
||||
'services.patreon.id': 1,
|
||||
'services.patreon.entitledCents': 1,
|
||||
'services.patreon.entitledCentsOverride': 1,
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
<template lang="html">
|
||||
<!--use value for immutable, list for auto-updating children -->
|
||||
<draggable
|
||||
class="drag-area"
|
||||
:value="children"
|
||||
:group="group"
|
||||
:animation="200"
|
||||
:move="move"
|
||||
@change="change"
|
||||
ghost-class="ghost"
|
||||
draggable=".item"
|
||||
handle=".handle"
|
||||
>
|
||||
<tree-node
|
||||
class="item"
|
||||
v-for="child in children"
|
||||
:node="child.node"
|
||||
:children="child.children"
|
||||
:group="group"
|
||||
:key="child.node._id"
|
||||
:selected-node-id="selectedNodeId"
|
||||
:selected="selectedNodeId === child.node._id"
|
||||
:organize="organize"
|
||||
:lazy="lazy"
|
||||
@selected="e => $emit('selected', e)"
|
||||
@reordered="e => $emit('reordered', e)"
|
||||
@reorganized="e => $emit('reorganized', e)"
|
||||
@dragstart.native="e => e.dataTransfer.setData('cow', child.node && child.node.name)"
|
||||
/>
|
||||
</draggable>
|
||||
<!--use value for immutable, list for auto-updating children -->
|
||||
<draggable
|
||||
class="drag-area"
|
||||
:value="children"
|
||||
:group="group"
|
||||
:animation="200"
|
||||
:move="move"
|
||||
ghost-class="ghost"
|
||||
draggable=".item"
|
||||
handle=".handle"
|
||||
@change="change"
|
||||
>
|
||||
<tree-node
|
||||
v-for="child in children"
|
||||
:key="child.node._id"
|
||||
class="item"
|
||||
:node="child.node"
|
||||
:children="child.children"
|
||||
:group="group"
|
||||
:selected-node-id="selectedNodeId"
|
||||
:selected="selectedNodeId === child.node._id"
|
||||
:organize="organize"
|
||||
:lazy="lazy"
|
||||
@selected="e => $emit('selected', e)"
|
||||
@reordered="e => $emit('reordered', e)"
|
||||
@reorganized="e => $emit('reorganized', e)"
|
||||
@dragstart.native="e => e.dataTransfer.setData('cow', child.node && child.node.name)"
|
||||
/>
|
||||
</draggable>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -40,9 +40,6 @@
|
||||
draggable,
|
||||
TreeNode,
|
||||
},
|
||||
data(){ return {
|
||||
expanded: false,
|
||||
}},
|
||||
props: {
|
||||
node: Object,
|
||||
group: String,
|
||||
@@ -50,10 +47,13 @@
|
||||
lazy: Boolean,
|
||||
children: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
selectedNodeId: String,
|
||||
},
|
||||
data(){ return {
|
||||
expanded: false,
|
||||
}},
|
||||
computed: {
|
||||
hasChildren(){
|
||||
return this.children && this.children.length;
|
||||
@@ -63,7 +63,7 @@
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
change({added, moved, removed}){
|
||||
change({added, moved}){
|
||||
let event = moved || added;
|
||||
if (event){
|
||||
let doc = event.element.node;
|
||||
@@ -71,7 +71,7 @@
|
||||
if (event.newIndex === 0){
|
||||
newIndex = 0;
|
||||
} else {
|
||||
childBeforeNewIndex = this.children[event.newIndex - 1];
|
||||
let childBeforeNewIndex = this.children[event.newIndex - 1];
|
||||
newIndex = childBeforeNewIndex.node.order + 1;
|
||||
}
|
||||
if (moved){
|
||||
|
||||
121
app/imports/ui/library/SingleLibrary.vue
Normal file
121
app/imports/ui/library/SingleLibrary.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
class="layout row"
|
||||
style="background-color: inherit;"
|
||||
>
|
||||
<div
|
||||
class="layout column"
|
||||
style="
|
||||
background-color: inherit;
|
||||
width: initial;
|
||||
max-width: 100%;
|
||||
min-width: 320px;
|
||||
"
|
||||
>
|
||||
<v-toolbar
|
||||
dense
|
||||
flat
|
||||
>
|
||||
<v-spacer />
|
||||
<v-switch
|
||||
v-model="organize"
|
||||
label="Organize"
|
||||
class="mx-3"
|
||||
style="flex-grow: 0; height: 32px;"
|
||||
/>
|
||||
</v-toolbar>
|
||||
<library-contents-container
|
||||
:library-id="$route.params.id"
|
||||
:organize-mode="organize"
|
||||
:selected-node-id="selected"
|
||||
@selected="e => selected = e"
|
||||
/>
|
||||
</div>
|
||||
<v-divider vertical />
|
||||
<div
|
||||
style="width: 100%; background-color: inherit;"
|
||||
data-id="selected-node-card"
|
||||
>
|
||||
<v-toolbar
|
||||
dense
|
||||
flat
|
||||
>
|
||||
<property-icon
|
||||
:type="selectedNode && selectedNode.type"
|
||||
class="mr-2"
|
||||
/>
|
||||
<div class="title">
|
||||
{{ getPropertyName(selectedNode && selectedNode.type) }}
|
||||
</div>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
v-if="selectedNode"
|
||||
flat
|
||||
icon
|
||||
@click="editLibraryNode"
|
||||
>
|
||||
<v-icon>create</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
<v-card-text style="overflow-y: auto;">
|
||||
<property-viewer :model="selectedNode" />
|
||||
</v-card-text>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PropertyViewer from '/imports/ui/properties/shared/PropertyViewer.vue';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
|
||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||
import LibraryContentsContainer from '/imports/ui/library/LibraryContentsContainer.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LibraryContentsContainer,
|
||||
PropertyViewer,
|
||||
PropertyIcon,
|
||||
},
|
||||
data(){ return {
|
||||
organize: false,
|
||||
selected: undefined,
|
||||
};},
|
||||
watch:{
|
||||
selectedNode(val){
|
||||
this.$emit('selected', val)
|
||||
},
|
||||
'library.name'(value){
|
||||
this.$store.commit('setPageTitle', value || 'Library');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
editLibraryNode(){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'library-node-edit-dialog',
|
||||
elementId: 'selected-node-card',
|
||||
data: {_id: this.selected},
|
||||
});
|
||||
},
|
||||
getPropertyName,
|
||||
},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'libraries': [],
|
||||
},
|
||||
library(){
|
||||
return Libraries.findOne(this.$route.params.id);
|
||||
},
|
||||
selectedNode(){
|
||||
return LibraryNodes.findOne({
|
||||
_id: this.selected,
|
||||
removed: {$ne: true}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
88
app/imports/ui/library/SingleLibraryToolbarItems.vue
Normal file
88
app/imports/ui/library/SingleLibraryToolbarItems.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<template lang="html">
|
||||
<v-toolbar-items>
|
||||
<v-btn
|
||||
v-if="showSubscribeButton"
|
||||
flat
|
||||
:loading="loading"
|
||||
@click="subscribe(!subscribed)"
|
||||
>
|
||||
{{ subscribed ? 'Unsubscribe' : 'Subscribe' }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="canEdit"
|
||||
flat
|
||||
icon
|
||||
data-id="library-edit-button"
|
||||
@click="editLibrary(library._id)"
|
||||
>
|
||||
<v-icon>create</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar-items>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
export default {
|
||||
data(){ return {
|
||||
loading: false,
|
||||
}},
|
||||
meteor: {
|
||||
library(){
|
||||
console.log(this.$route);
|
||||
return Libraries.findOne(this.$route.params.id);
|
||||
},
|
||||
subscribed(){
|
||||
let libraryId = this.$route.params.id;
|
||||
let subs = Meteor.user().subscribedLibraries;
|
||||
return subs.includes(libraryId);
|
||||
},
|
||||
showSubscribeButton(){
|
||||
let userId = Meteor.userId();
|
||||
let library = this.library;
|
||||
if (!library) return;
|
||||
console.log({library, userId});
|
||||
if (
|
||||
library.readers.includes(userId) ||
|
||||
library.writers.includes(userId) ||
|
||||
library.owner === userId
|
||||
) {
|
||||
return false
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
canEdit(){
|
||||
try {
|
||||
assertDocEditPermission(this.library, Meteor.userId());
|
||||
console.log('can edit');
|
||||
return true
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
subscribe(value){
|
||||
this.loading = true;
|
||||
Meteor.users.subscribeToLibrary.call({
|
||||
libraryId: this.$route.params.id,
|
||||
subscribe: value,
|
||||
}, () => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
editLibrary(){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'library-edit-dialog',
|
||||
elementId: 'library-edit-button',
|
||||
data: {_id: this.$route.params.id},
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
16
app/imports/ui/pages/SingleLibraryPage.vue
Normal file
16
app/imports/ui/pages/SingleLibraryPage.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<template lang="html">
|
||||
<div>
|
||||
<v-card class="ma-4">
|
||||
<single-library />
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SingleLibrary from '/imports/ui/library/SingleLibrary.vue';
|
||||
export default {
|
||||
components: {
|
||||
SingleLibrary,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -6,6 +6,8 @@ import LAUNCH_DATE from '/imports/constants/LAUNCH_DATE.js';
|
||||
import Home from '/imports/ui/pages/Home.vue';
|
||||
import CharacterList from '/imports/ui/pages/CharacterList.vue';
|
||||
import Library from '/imports/ui/pages/Library.vue';
|
||||
import SingleLibraryPage from '/imports/ui/pages/SingleLibraryPage.vue'
|
||||
import SingleLibraryToolbarItems from '/imports/ui/library/SingleLibraryToolbarItems.vue'
|
||||
import CharacterSheetPage from '/imports/ui/pages/CharacterSheetPage.vue';
|
||||
import CharacterSheetToolbarItems from '/imports/ui/creature/character/CharacterSheetToolbarItems.vue';
|
||||
import CharacterSheetToolbarExtension from '/imports/ui/creature/character/CharacterSheetToolbarExtension.vue';
|
||||
@@ -100,6 +102,17 @@ RouterFactory.configure(factory => {
|
||||
title: 'Library',
|
||||
},
|
||||
beforeEnter: ensurePatronTier5,
|
||||
},{
|
||||
name: 'singleLibrary',
|
||||
path: '/library/:id',
|
||||
components: {
|
||||
default: SingleLibraryPage,
|
||||
toolbarItems: SingleLibraryToolbarItems,
|
||||
},
|
||||
meta: {
|
||||
title: 'Library',
|
||||
},
|
||||
beforeEnter: ensurePatronTier5,
|
||||
},{
|
||||
path: '/character/:id/:urlName',
|
||||
components: {
|
||||
|
||||
@@ -13,6 +13,15 @@
|
||||
:value="!!model.public + ''"
|
||||
@change="(value, ack) => setSheetPublic({value, ack})"
|
||||
/>
|
||||
<text-field
|
||||
v-if="model.public && docRef.collection === 'libraries'"
|
||||
disabled
|
||||
label="Link"
|
||||
:value="'https://beta.dicecloud.com' + this.$router.resolve({
|
||||
name: 'singleLibrary',
|
||||
params: { id: model._id },
|
||||
}).href"
|
||||
/>
|
||||
<div class="layout row">
|
||||
<text-field
|
||||
label="Username or email"
|
||||
@@ -20,42 +29,78 @@
|
||||
:debounce-time="300"
|
||||
@change="(value, ack) => getUser({value, ack})"
|
||||
/>
|
||||
<v-btn :disabled="userFoundState !== 'found'">
|
||||
<v-btn
|
||||
:disabled="userFoundState !== 'found'"
|
||||
@click="updateSharing(userId, 'reader')"
|
||||
>
|
||||
Share
|
||||
</v-btn>
|
||||
</div>
|
||||
<div class="sharedWith">
|
||||
<h3 v-if="writers.length">
|
||||
Can Edit
|
||||
</h3>
|
||||
<div
|
||||
v-for="user in writers"
|
||||
<v-list
|
||||
two-lines
|
||||
class="sharedWith"
|
||||
>
|
||||
<v-list-tile
|
||||
v-for="user in sharedUsers"
|
||||
:key="user._id"
|
||||
>
|
||||
{{ user }}
|
||||
<v-btn
|
||||
flat
|
||||
icon
|
||||
>
|
||||
<v-icon>delete</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<h3 v-if="readers.length">
|
||||
Can View
|
||||
</h3>
|
||||
<div
|
||||
v-for="user in readers"
|
||||
:key="user._id"
|
||||
>
|
||||
{{ user }}
|
||||
<v-btn
|
||||
flat
|
||||
icon
|
||||
>
|
||||
<v-icon>delete</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
{{ user.username || user._id }}
|
||||
</v-list-tile-title>
|
||||
<v-list-tile-sub-title>
|
||||
{{ user.permission === 'writer' ? 'Can edit' : 'Can view' }}
|
||||
</v-list-tile-sub-title>
|
||||
</v-list-tile-content>
|
||||
<v-list-tile-action>
|
||||
<v-menu
|
||||
bottom
|
||||
left
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
icon
|
||||
v-on="on"
|
||||
>
|
||||
<v-icon>more_vert</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-tile
|
||||
v-if="user.permission === 'reader'"
|
||||
@click="updateSharing(user._id, 'writer')"
|
||||
>
|
||||
<v-list-tile-action>
|
||||
<v-icon>create</v-icon>
|
||||
</v-list-tile-action>
|
||||
<v-list-tile-title>Can edit</v-list-tile-title>
|
||||
</v-list-tile>
|
||||
<v-list-tile
|
||||
v-if="user.permission === 'writer'"
|
||||
@click="updateSharing(user._id, 'reader')"
|
||||
>
|
||||
<v-list-tile-action>
|
||||
<v-icon>remove_red_eye</v-icon>
|
||||
</v-list-tile-action>
|
||||
<v-list-tile-title>View only</v-list-tile-title>
|
||||
</v-list-tile>
|
||||
<v-list-tile @click="updateSharing(user._id, 'none')">
|
||||
<v-list-tile-action>
|
||||
<v-icon>delete</v-icon>
|
||||
</v-list-tile-action>
|
||||
<v-list-tile-title>Remove</v-list-tile-title>
|
||||
</v-list-tile>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-list-tile-action>
|
||||
</v-list-tile>
|
||||
</v-list>
|
||||
<v-fade-transition>
|
||||
<v-progress-circular
|
||||
v-if="!$subReady.userPublicProfiles"
|
||||
indeterminate
|
||||
/>
|
||||
</v-fade-transition>
|
||||
</div>
|
||||
<v-spacer slot="actions" />
|
||||
<v-btn
|
||||
@@ -69,7 +114,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { setPublic } from '/imports/api/sharing/sharing.js';
|
||||
import {
|
||||
setPublic,
|
||||
updateUserSharePermissions
|
||||
} from '/imports/api/sharing/sharing.js';
|
||||
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||
|
||||
@@ -92,7 +140,7 @@ export default {
|
||||
setSheetPublic({value, ack}){
|
||||
setPublic.call({
|
||||
docRef: this.docRef,
|
||||
public: value === 'true',
|
||||
isPublic: value === 'true',
|
||||
}, (error) => {
|
||||
ack(error && error.reason || error);
|
||||
});
|
||||
@@ -127,6 +175,13 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
updateSharing(userId, role){
|
||||
updateUserSharePermissions.call({
|
||||
docRef: this.docRef,
|
||||
userId,
|
||||
role,
|
||||
});
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
model(){
|
||||
@@ -134,21 +189,28 @@ export default {
|
||||
let model = fetchDocByRef(this.docRef);
|
||||
return model;
|
||||
},
|
||||
readers(){
|
||||
if (this.model){
|
||||
return Meteor.users.find({_id: {$in: this.model.readers}})
|
||||
}
|
||||
},
|
||||
writers(){
|
||||
if (this.model){
|
||||
return Meteor.users.find({_id: {$in: this.model.writers}})
|
||||
}
|
||||
},
|
||||
sharedUsers(){
|
||||
let users = [];
|
||||
Meteor.users.find({_id: {$in: this.model.readers}}).forEach(user => {
|
||||
user.permission = 'reader';
|
||||
users.push(user);
|
||||
});
|
||||
Meteor.users.find({_id: {$in: this.model.writers}}).forEach(user => {
|
||||
user.permission = 'writer';
|
||||
users.push(user);
|
||||
});
|
||||
users.sort(function(a, b){
|
||||
if (a.username < b.username) return -1;
|
||||
if (a.username > b.username) return 1;
|
||||
return 0;
|
||||
});
|
||||
return users;
|
||||
},
|
||||
$subscribe: {
|
||||
'userPublicProfiles'(){
|
||||
let model = this.model;
|
||||
if (!model) return false;
|
||||
return [model.owner, ...model.writers, ...model.readers];
|
||||
return [[model.owner, ...model.writers, ...model.readers]];
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user