1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-20 21:59:42 +02:00

Streamline document meta view and editing experience

Meta data:

1. Condensed layout.
2. Unified editing.

Co-Authored-By: Saul S <sauls8t@users.noreply.github.com>
This commit is contained in:
McMatts 2018-06-15 14:25:05 +01:00
parent f70d4b33a3
commit 27fde0dac8
26 changed files with 2389 additions and 1956 deletions

View file

@ -58,9 +58,9 @@ Space view.
## Latest version ## Latest version
[Community edition: v1.65.1](https://github.com/documize/community/releases) [Community edition: v1.65.2](https://github.com/documize/community/releases)
[Enterprise edition: v1.67.1](https://documize.com/downloads) [Enterprise edition: v1.67.2](https://documize.com/downloads)
## OS support ## OS support

View file

@ -10,36 +10,18 @@
// https://documize.com // https://documize.com
import $ from 'jquery'; import $ from 'jquery';
import { A } from '@ember/array';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import { notEmpty } from '@ember/object/computed'; import { notEmpty } from '@ember/object/computed';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import { A } from '@ember/array'; import Modals from '../../mixins/modal';
import { schedule } from '@ember/runloop'; import Tooltips from '../../mixins/tooltip';
import ModalMixin from '../../mixins/modal';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(ModalMixin, { export default Component.extend(Modals, Tooltips, {
documentService: service('document'), documentService: service('document'),
categoryService: service('category'),
sessionService: service('session'), sessionService: service('session'),
categoryService: service('category'),
categories: A([]),
newCategory: '',
showCategoryModal: false,
hasCategories: computed('categories', function() {
return this.get('categories').length > 0;
}),
canSelectCategory: computed('categories', function() {
return (this.get('categories').length > 0 && this.get('permissions.documentEdit'));
}),
canAddCategory: computed('categories', function() {
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
}),
maxTags: 3,
tagz: A([]),
tagzModal: A([]),
newTag: '',
contributorMsg: '', contributorMsg: '',
approverMsg: '', approverMsg: '',
@ -91,67 +73,56 @@ export default Component.extend(ModalMixin, {
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); this._super(...arguments);
this.load();
this.workflowStatus(); this.workflowStatus();
this.popovers();
this.load();
}, },
didInsertElement() { didInsertElement() {
this._super(...arguments); this._super(...arguments);
$('#document-tags-modal').on('show.bs.modal', (event) => { // eslint-disable-line no-unused-vars this.popovers();
schedule('afterRender', () => { this.renderTooltips();
$("#add-tag-field").focus();
$("#add-tag-field").off("keydown").on("keydown", function(e) {
if (e.shiftKey) {
return false;
}
if (e.which === 13 || e.which === 45 || e.which === 189 || e.which === 8 || e.which === 127 || (e.which >= 65 && e.which <= 90) || (e.which >= 97 && e.which <= 122) || (e.which >= 48 && e.which <= 57)) {
return true;
}
return false;
});
// make copy of tags for editing
this.set('tagzEdit', this.get('tagz'));
});
});
}, },
willDestroyElement() { willDestroyElement() {
this._super(...arguments); this._super(...arguments);
$("#add-tag-field").off("keydown");
$('#document-lifecycle-popover').popover('dispose');
$('#document-protection-popover').popover('dispose');
this.removeTooltips();
}, },
load() { popovers() {
this.get('categoryService').getUserVisible(this.get('folder.id')).then((categories) => { let constants = this.get('constants');
let cats = A(categories);
this.set('categories', cats); $('#document-lifecycle-popover').popover('dispose');
this.get('categoryService').getDocumentCategories(this.get('document.id')).then((selected) => { $('#document-protection-popover').popover('dispose');
this.set('selectedCategories', selected);
selected.forEach((s) => { $('#document-lifecycle-popover').popover({
let cat = cats.findBy('id', s.id); html: true,
if (is.not.undefined(cat)) { title: 'Lifecycle',
cat.set('selected', true); content: "<p>Draft &mdash; restricted visiblity and not searchable</p><p>Live &mdash; document visible to all</p><p>Archived &mdash; not visible or searchable</p>",
this.set('categories', cats); placement: 'top'
}
});
});
}); });
let tagz = []; let ccMsg = `<p>${this.changeControlMsg}</p>`;
if (!_.isUndefined(this.get('document.tags')) && this.get('document.tags').length > 1) {
let tags = this.get('document.tags').split('#'); if (this.get('document.protection') === constants.ProtectionType.Review) {
_.each(tags, function(tag) { ccMsg += '<ul>'
if (tag.length > 0) { ccMsg += `<li>${this.approvalMsg}</li>`;
tagz.pushObject(tag); if (this.get('userChanges')) ccMsg += `<li>Your contributions: ${this.contributorMsg}</li>`;
} if (this.get('isApprover') && this.get('approverMsg.length') > 0) ccMsg += `<li>${this.approverMsg}</li>`;
}); ccMsg += '</ul>'
} }
this.set('tagz', A(tagz)); $('#document-protection-popover').popover({
html: true,
title: 'Change Control',
content: ccMsg,
placement: 'top'
});
}, },
workflowStatus() { workflowStatus() {
@ -184,97 +155,38 @@ export default Component.extend(ModalMixin, {
let label = approverPendingCount === 1 ? 'change' : 'changes'; let label = approverPendingCount === 1 ? 'change' : 'changes';
approverMsg = `${approverPendingCount} ${label} progressing, ${approverReviewCount} awaiting review, ${approverRejectedCount} rejected`; approverMsg = `${approverPendingCount} ${label} progressing, ${approverReviewCount} awaiting review, ${approverRejectedCount} rejected`;
} }
this.set('approverMsg', approverMsg); this.set('approverMsg', approverMsg);
this.set('selectedVersion', this.get('versions').findBy('documentId', this.get('document.id')));
this.popovers();
},
load() {
this.get('categoryService').getDocumentCategories(this.get('document.id')).then((selected) => {
this.set('selectedCategories', selected);
});
let tagz = [];
if (!_.isUndefined(this.get('document.tags')) && this.get('document.tags').length > 1) {
let tags = this.get('document.tags').split('#');
_.each(tags, function(tag) {
if (tag.length > 0) {
tagz.pushObject(tag);
}
});
}
this.set('tagz', A(tagz));
}, },
actions: { actions: {
onShowCategoryModal() { onSelectVersion(version) {
this.set('showCategoryModal', true); let space = this.get('folder');
},
onSaveCategory() { this.get('router').transitionTo('document',
let docId = this.get('document.id'); space.get('id'), space.get('slug'),
let folderId = this.get('folder.id'); version.documentId, this.get('document.slug'));
let link = this.get('categories').filterBy('selected', true);
let unlink = this.get('categories').filterBy('selected', false);
let toLink = [];
let toUnlink = [];
// prepare links associated with document
link.forEach((l) => {
let t = {
folderId: folderId,
documentId: docId,
categoryId: l.get('id')
};
toLink.push(t);
});
// prepare links no longer associated with document
unlink.forEach((l) => {
let t = {
folderId: folderId,
documentId: docId,
categoryId: l.get('id')
};
toUnlink.pushObject(t);
});
this.set('showCategoryModal', false);
this.get('categoryService').setCategoryMembership(toUnlink, 'unlink').then(() => {
this.get('categoryService').setCategoryMembership(toLink, 'link').then(() => {
this.load();
});
});
return true;
},
onAddTag(e) {
e.preventDefault();
let tags = this.get("tagzEdit");
let tag = this.get('newTag');
tag = tag.toLowerCase().trim();
// empty or dupe?
if (tag.length === 0 || _.contains(tags, tag) || tags.length >= this.get('maxTags') || tag.startsWith('-')) {
$('#add-tag-field').addClass('is-invalid');
return;
}
tags.pushObject(tag);
this.set('tagzEdit', tags);
this.set('newTag', '');
$('#add-tag-field').removeClass('is-invalid');
},
onRemoveTag(tagToRemove) {
this.set('tagzEdit', _.without(this.get("tagzEdit"), tagToRemove));
},
onSaveTags() {
let tags = this.get("tagzEdit");
let save = "#";
_.each(tags, function(tag) {
save = save + tag + "#";
});
let doc = this.get('document');
doc.set('tags', save);
let cb = this.get('onSaveDocument');
cb(doc);
this.load();
this.set('newTag', '');
$('#document-tags-modal').modal('hide');
$('#document-tags-modal').modal('dispose');
} }
} }
}); });

View file

@ -9,31 +9,29 @@
// //
// https://documize.com // https://documize.com
import $ from 'jquery';
import { empty } from '@ember/object/computed'; import { empty } from '@ember/object/computed';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import { schedule } from '@ember/runloop';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Notifier from '../../mixins/notifier';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend({ export default Component.extend(Notifier, {
documentService: service('document'), documentSvc: service('document'),
editMode: false,
docName: '', docName: '',
docExcerpt: '', docExcerpt: '',
hasNameError: empty('docName'), hasNameError: empty('docName'),
canEdit: computed('permssions', 'document', function() { noEdits: computed('permssions', 'document', function() {
let constants = this.get('constants'); let constants = this.get('constants');
let permissions = this.get('permissions'); let permissions = this.get('permissions');
if (permissions.get('documentEdit') && this.get('document.protection') === constants.ProtectionType.None) { if (permissions.get('documentEdit') && this.get('document.protection') !== constants.ProtectionType.None) {
return true; return false;
} else if (permissions.get('documentApprove') && this.get('document.protection') === constants.ProtectionType.Review) { } else if (permissions.get('documentApprove') && this.get('document.protection') === constants.ProtectionType.Review) {
return true; return false;
} }
return false; return true;
}), }),
keyUp(e) { keyUp(e) {
@ -42,30 +40,31 @@ export default Component.extend({
} }
}, },
actions: { didReceiveAttrs() {
toggleEdit() { this._super(...arguments);
this.set('docName', this.get('document.name')); this.set('docName', this.get('document.name'));
this.set('docExcerpt', this.get('document.excerpt')); this.set('docExcerpt', this.get('document.excerpt'));
this.set('editMode', true);
schedule('afterRender', () => {
$('#document-name').select();
});
}, },
actions: {
onSave() { onSave() {
if (this.get('hasNameError')) return; if (this.get('hasNameError')) return;
let constants = this.get('constants');
this.set('document.name', this.get('docName')); this.set('document.name', this.get('docName'));
this.set('document.excerpt', this.get('docExcerpt').trim()); this.set('document.excerpt', this.get('docExcerpt').trim());
this.set('editMode', false);
let lifecycle = this.get('lifecycle.selected');
this.set('document.lifecycle', lifecycle);
let cb = this.get('onSaveDocument'); let cb = this.get('onSaveDocument');
cb(this.get('document')); cb(this.get('document'));
},
onCancel() { if (lifecycle === constants.Lifecycle.Draft) {
this.set('editMode', false); this.get('activitySvc').clearChangeHistory(this.get('document.id'));
}
} }
} }
}); });

View file

@ -0,0 +1,173 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import $ from 'jquery';
import { inject as service } from '@ember/service';
import { A } from '@ember/array';
import { computed } from '@ember/object';
import { schedule } from '@ember/runloop';
import Notifier from '../../mixins/notifier';
import Component from '@ember/component';
export default Component.extend(Notifier, {
documentSvc: service('document'),
categoryService: service('category'),
categories: A([]),
newCategory: '',
showCategoryModal: false,
hasCategories: computed('categories', function() {
return this.get('categories').length > 0;
}),
canSelectCategory: computed('categories', function() {
return (this.get('categories').length > 0 && this.get('permissions.documentEdit'));
}),
canAddCategory: computed('categories', function() {
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
}),
maxTags: 3,
tag1: '',
tag2: '',
tag3: '',
didReceiveAttrs() {
this._super(...arguments);
this.load();
},
didInsertElement() {
this._super(...arguments);
schedule('afterRender', () => {
$("#add-tag-field0").focus();
$(".tag-input").off("keydown").on("keydown", function(e) {
if (e.shiftKey && e.which === 9) {
return true;
}
if (e.shiftKey) {
return false;
}
if (e.which === 9 || e.which === 13 || e.which === 16 || e.which === 45 || e.which === 189 || e.which === 8 || e.which === 127 || (e.which >= 65 && e.which <= 90) || (e.which >= 97 && e.which <= 122) || (e.which >= 48 && e.which <= 57)) {
return true;
}
return false;
});
});
},
willDestroyElement() {
this._super(...arguments);
$(".tag-input").off("keydown");
},
load() {
this.get('categoryService').getUserVisible(this.get('space.id')).then((categories) => {
let cats = A(categories);
this.set('categories', cats);
this.get('categoryService').getDocumentCategories(this.get('document.id')).then((selected) => {
this.set('selectedCategories', selected);
selected.forEach((s) => {
let cat = cats.findBy('id', s.id);
if (is.not.undefined(cat)) {
cat.set('selected', true);
this.set('categories', cats);
}
});
});
});
if (!_.isUndefined(this.get('document.tags')) && this.get('document.tags').length > 1) {
let tags = this.get('document.tags').split('#');
let counter = 1;
_.each(tags, (tag) => {
tag = tag.trim();
if (tag.length > 0) {
this.set('tag' + counter, tag);
counter++;
}
});
}
},
actions: {
onSave() {
this.showWait();
let docId = this.get('document.id');
let folderId = this.get('space.id');
let link = this.get('categories').filterBy('selected', true);
let unlink = this.get('categories').filterBy('selected', false);
let toLink = [];
let toUnlink = [];
// prepare links associated with document
link.forEach((l) => {
let t = {
folderId: folderId,
documentId: docId,
categoryId: l.get('id')
};
toLink.push(t);
});
// prepare links no longer associated with document
unlink.forEach((l) => {
let t = {
folderId: folderId,
documentId: docId,
categoryId: l.get('id')
};
toUnlink.pushObject(t);
});
this.get('categoryService').setCategoryMembership(toUnlink, 'unlink').then(() => {
this.get('categoryService').setCategoryMembership(toLink, 'link').then(() => {
this.showDone();
});
});
let tag1 = this.get("tag1").toLowerCase().trim();
let tag2 = this.get("tag2").toLowerCase().trim();
let tag3 = this.get("tag3").toLowerCase().trim();
let save = "#";
if (tag1.startsWith('-')) {
$('#add-tag-field1').addClass('is-invalid');
return;
}
if (tag2.startsWith('-')) {
$('#add-tag-field2').addClass('is-invalid');
return;
}
if (tag3.startsWith('-')) {
$('#add-tag-field3').addClass('is-invalid');
return;
}
(tag1.length > 0 ) ? save += (tag1 + "#") : this.set('tag1', '');
(tag2.length > 0 && tag2 !== tag1) ? save += (tag2 + "#") : this.set('tag2', '');
(tag3.length > 0 && tag3 !== tag1 && tag3 !== tag2) ? save += (tag3 + "#") : this.set('tag3', '');
let doc = this.get('document');
doc.set('tags', save);
this.get('onSaveDocument')(doc);
}
}
});

View file

@ -15,7 +15,6 @@ import { inject as service } from '@ember/service';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend({ export default Component.extend({
classNames: ['row d-print-none'],
documentService: service('document'), documentService: service('document'),
appMeta: service(), appMeta: service(),
hasAttachments: notEmpty('files'), hasAttachments: notEmpty('files'),
@ -26,12 +25,12 @@ export default Component.extend({
init() { init() {
this._super(...arguments); this._super(...arguments);
this.getAttachments(); this.deleteAttachment = { id: '', name: '' };
},
this.deleteAttachment = { didReceiveAttrs() {
id: "", this._super(...arguments);
name: "", this.getAttachments();
};
}, },
didInsertElement() { didInsertElement() {

View file

@ -33,7 +33,6 @@ export default Component.extend(TooltipMixin, Notifier, {
}), }),
voteThanks: false, voteThanks: false,
showLikes: false, showLikes: false,
showDeleteBlockDialog: false, showDeleteBlockDialog: false,
deleteBlockId: '', deleteBlockId: '',
@ -107,6 +106,16 @@ export default Component.extend(TooltipMixin, Notifier, {
} }
}, },
addSection(model) {
let constants = this.get('constants');
if (this.get('document.protection') === constants.ProtectionType.Review) {
model.page.set('status', model.page.get('relativeId') === '' ? constants.ChangeState.PendingNew : constants.ChangeState.Pending);
}
return this.get('onInsertSection')(model);
},
actions: { actions: {
onSavePageAsBlock(block) { onSavePageAsBlock(block) {
let cb = this.get('onSavePageAsBlock'); let cb = this.get('onSavePageAsBlock');

View file

@ -17,7 +17,6 @@ import ModalMixin from '../../mixins/modal';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, { export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
userSvc: service('user'),
store: service(), store: service(),
spaceSvc: service('folder'), spaceSvc: service('folder'),
session: service(), session: service(),
@ -26,6 +25,7 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
init() { init() {
this._super(...arguments); this._super(...arguments);
this.pinState = { this.pinState = {
isPinned: false, isPinned: false,
pinId: '', pinId: '',

View file

@ -35,5 +35,4 @@ export default Mixin.create({
removePopovers() { removePopovers() {
$('[data-toggle="tooltip"]').popover('dispose'); $('[data-toggle="tooltip"]').popover('dispose');
} }
}); });

View file

@ -11,6 +11,7 @@
import { Promise as EmberPromise } from 'rsvp'; import { Promise as EmberPromise } from 'rsvp';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import Tooltips from '../../../mixins/tooltip'; import Tooltips from '../../../mixins/tooltip';
import Notifier from '../../../mixins/notifier'; import Notifier from '../../../mixins/notifier';
import Controller from '@ember/controller'; import Controller from '@ember/controller';
@ -22,6 +23,12 @@ export default Controller.extend(Tooltips, Notifier, {
linkService: service('link'), linkService: service('link'),
tab: 'content', tab: 'content',
queryParams: ['currentPageId'], queryParams: ['currentPageId'],
showRevisions: computed('permissions', 'document.protection', function() {
if (this.get('document.protection') === this.get('constants').ProtectionType.None) return true;
if (this.get('document.protection') === this.get('constants').ProtectionType.Review && this.get('permissions.documentApprove')) return true;
return false;
}),
actions: { actions: {
onTabChange(tab) { onTabChange(tab) {
@ -229,7 +236,7 @@ export default Controller.extend(Tooltips, Notifier, {
}); });
}, },
refresh() { refresh(reloadPage) {
return new EmberPromise((resolve) => { return new EmberPromise((resolve) => {
this.get('documentService').fetchDocumentData(this.get('document.id')).then((data) => { this.get('documentService').fetchDocumentData(this.get('document.id')).then((data) => {
this.set('document', data.document); this.set('document', data.document);
@ -247,7 +254,11 @@ export default Controller.extend(Tooltips, Notifier, {
this.set('blocks', data); this.set('blocks', data);
}); });
if (reloadPage) {
window.location.reload();
} else {
resolve(); resolve();
}
}); });
}); });
}); });

View file

@ -32,16 +32,24 @@
<ul class="tabnav-control"> <ul class="tabnav-control">
<li class="tab {{if (eq tab 'content') 'selected'}}" {{action 'onTabChange' 'content'}}>Content</li> <li class="tab {{if (eq tab 'content') 'selected'}}" {{action 'onTabChange' 'content'}}>Content</li>
{{#if session.authenticated}} {{#if session.authenticated}}
{{#if showRevisions}}
<li class="tab {{if (eq tab 'revision') 'selected'}}" {{action 'onTabChange' 'revision'}}>Revisions</li> <li class="tab {{if (eq tab 'revision') 'selected'}}" {{action 'onTabChange' 'revision'}}>Revisions</li>
{{/if}} {{/if}}
{{/if}}
</ul> </ul>
</div> </div>
{{document/document-heading <div class="view-document">
document=document <div class="document-heading">
versions=versions <h1 class="doc-title">
permissions=permissions {{#if document.template}}
onSaveDocument=(action 'onSaveDocument')}} <span class="bg-warning p-1 pr-2 pl-2">Template</span>&nbsp;&nbsp;
{{/if}}
{{document.name}}
</h1>
<div class="doc-excerpt">{{document.excerpt}}</div>
</div>
</div>
{{document/document-meta {{document/document-meta
pages=pages pages=pages
@ -76,6 +84,7 @@
{{/if}} {{/if}}
{{#if (eq tab 'revision')}} {{#if (eq tab 'revision')}}
{{#if showRevisions}}
{{document/view-revision {{document/view-revision
pages=pages pages=pages
folder=folder folder=folder
@ -83,6 +92,7 @@
permissions=permissions permissions=permissions
onRollback=(action 'onRollback')}} onRollback=(action 'onRollback')}}
{{/if}} {{/if}}
{{/if}}
{{/layout/middle-zone-content}} {{/layout/middle-zone-content}}

View file

@ -0,0 +1,42 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import { inject as service } from '@ember/service';
import Notifier from '../../../mixins/notifier';
import Controller from '@ember/controller';
export default Controller.extend(Notifier, {
router: service(),
folderService: service('folder'),
documentService: service('document'),
localStorage: service('localStorage'),
tab: 'general',
actions: {
onTab(view) {
this.set('tab', view);
},
onSaveDocument(doc) {
this.showWait();
this.get('documentService').save(doc).then(() => {
this.showDone();
});
this.get('browser').setTitle(doc.get('name'));
this.get('browser').setMetaDescription(doc.get('excerpt'));
},
onRefresh() {
this.get('target._routerMicrolib').refresh();
}
}
});

View file

@ -0,0 +1,35 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import { hash } from 'rsvp';
import { inject as service } from '@ember/service';
import Route from '@ember/routing/route';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
export default Route.extend(AuthenticatedRouteMixin, {
documentService: service('document'),
folderService: service('folder'),
userService: service('user'),
model(/*params*/) {
return hash({
folders: this.modelFor('document').folders,
folder: this.modelFor('document').folder,
document: this.modelFor('document').document,
links: this.modelFor('document').links,
sections: this.modelFor('document').sections,
permissions: this.modelFor('document').permissions,
roles: this.modelFor('document').roles,
blocks: this.modelFor('document').blocks,
versions: this.modelFor('document').versions
});
}
});

View file

@ -0,0 +1,49 @@
{{#layout/top-bar}}
<li class="item">
{{#link-to "folder.index" model.folder.id model.folder.slug class='link'}}
{{model.folder.name}}
{{/link-to}}
</li>
<li class="item">
{{#link-to 'document.index' model.folder.id model.folder.slug model.document.id model.document.slug class="link"}}
{{model.document.name}}
{{/link-to}}
</li>
<li class="item">
{{#link-to "document.settings" model.folder.id model.folder.slug class='link selected'}}
Settings
{{/link-to}}
</li>
{{/layout/top-bar}}
{{#layout/middle-zone}}
{{#layout/middle-zone-content}}
{{#if (eq tab 'general')}}
{{document/settings-general
space=model.folder
document=model.document
permissions=model.permissions
onSaveDocument=(action 'onSaveDocument')}}
{{/if}}
{{#if (eq tab 'meta')}}
{{document/settings-meta
space=model.folder
document=model.document
permissions=model.permissions
onSaveDocument=(action 'onSaveDocument')}}
{{/if}}
{{/layout/middle-zone-content}}
{{#layout/middle-zone-sidebar}}
<div id="sidebar" class="sidebar">
<ul class="tabnav-control tabnav-control-centered w-75">
<li class="tab tab-vertical {{if (eq tab 'general') 'selected'}}" {{action 'onTab' 'general'}}>General</li>
<li class="tab tab-vertical {{if (eq tab 'permissions') 'selected'}}" {{action 'onTab' 'meta'}}>Categories & Tags</li>
</ul>
</div>
{{/layout/middle-zone-sidebar}}
{{/layout/middle-zone}}
{{#layout/bottom-bar}}
{{/layout/bottom-bar}}

View file

@ -56,6 +56,9 @@ export default Router.map(function () {
this.route('section', { this.route('section', {
path: 'section/:page_id' path: 'section/:page_id'
}); });
this.route('settings', {
path: 'settings'
});
} }
); );

View file

@ -26,18 +26,19 @@ $light: $color-white;
$dark: $color-off-black; $dark: $color-off-black;
// popover // popover
$popover-bg: $color-off-white; $popover-bg: $color-white;
$popover-header-bg: $color-off-white; $popover-header-bg: $color-dark;
$popover-header-color: $color-off-black; $popover-header-color: $color-white;
// tooltip // tooltip
$tooltip-bg: $color-off-white; $tooltip-bg: $color-dark;
$tooltip-color: $color-off-black; $tooltip-color: $color-white;
// modal // modal
$modal-backdrop-opacity: 0.7; $modal-backdrop-opacity: 0.7;
$modal-header-border-color: $color-white; $modal-header-border-color: $color-white;
$modal-footer-border-color: $color-white; $modal-footer-border-color: $color-white;
.modal-header { .modal-header {
background-color: $color-primary; background-color: $color-primary;
color: $color-off-white; color: $color-off-white;
@ -102,11 +103,9 @@ $link-hover-decoration: none;
@import "node_modules/bootstrap/scss/badge"; @import "node_modules/bootstrap/scss/badge";
// Boostrap overrides // Boostrap overrides
.modal-80 { .modal-80 {
max-width: 80% !important; max-width: 80% !important;
} }
body.modal-open { body.modal-open {
padding-right: 0 !important; padding-right: 0 !important;
@ -114,7 +113,6 @@ body.modal-open {
// See: https://stackoverflow.com/questions/21604674/bootstrap-modal-background-jumps-to-top-on-toggle/21881894 // See: https://stackoverflow.com/questions/21604674/bootstrap-modal-background-jumps-to-top-on-toggle/21881894
overflow: visible; overflow: visible;
} }
.modal-header-white { .modal-header-white {
background-color: $color-white !important; background-color: $color-white !important;
border: none !important; border: none !important;
@ -130,6 +128,25 @@ body.modal-open {
} }
} }
.popover-header {
font-size: 1.2rem;
}
.popover-body {
font-size: 1rem;
> p {
margin: 0.5rem 0;
}
> ul {
margin: 10px 0 10px 25px;
> li {
list-style: square;
}
}
}
// Bootstrap override that removes gutter space on smaller screens // Bootstrap override that removes gutter space on smaller screens
// @media (max-width: 1200px) { // @media (max-width: 1200px) {
// .container { // .container {

View file

@ -9,57 +9,90 @@
.doc-excerpt { .doc-excerpt {
font-size: 1.2rem; font-size: 1.2rem;
color: $color-gray; color: $color-gray;
margin: 0 0 45px; margin: 0 0 20px;
}
} }
} }
> .document-heading-edit { .document-lifecycle-live {
margin-top: 3.5rem;
margin-bottom: 3rem;
}
> .document-customfields {
margin-bottom: 4rem;
background-color: $color-off-white;
border: 1px solid $color-border;
padding: 20px 40px;
@include border-radius(3px); @include border-radius(3px);
@include ease-in();
.row { display: inline-block;
padding: 5px 0; border: 2px solid $color-green;
margin-bottom: 10px; padding: 2px 10px;
color: $color-gray;
background-color: $color-off-white;
font-weight: 800;
font-size: 1rem;
cursor: pointer;
&:hover { &:hover {
.action-button { color: $color-green;
visibility: visible;
} }
} }
.heading { .document-lifecycle-draft {
font-size: 1.1rem; @extend .document-lifecycle-live;
font-weight: 700; border: 2px solid $color-orange;
color: $color-dark;
text-align: left; &:hover {
color: $color-orange;
}
} }
.action-button { .document-protection-unlocked {
visibility: hidden; @include border-radius(3px);
margin-left: 15px; @include ease-in();
} display: inline-block;
padding: 2px 10px;
.value { font-weight: 800;
font-size: 1.1rem; font-size: 1rem;
font-weight: normal;
color: $color-black;
text-align: left;
}
.value-static {
font-size: 1.1rem;
font-weight: bold;
color: $color-gray; color: $color-gray;
text-align: left; background-color: $color-off-white;
border: 2px solid $color-gray;
cursor: pointer;
&:hover {
color: $color-dark;
} }
} }
.document-protection-review {
@extend .document-protection-unlocked;
border: 2px solid $color-orange;
&:hover {
color: $color-orange;
} }
} }
.document-protection-locked {
@extend .document-protection-unlocked;
border: 2px solid $color-red;
&:hover {
color: $color-red;
}
}
.document-category {
display: inline-block;
padding: 2px 10px;
font-weight: 600;
font-size: 1rem;
color: $color-gray;
background-color: $color-off-white;
border: 2px solid $color-gray;
border-left: 13px solid $color-gray;
margin-right: 20px;
}
.document-tag {
display: inline-block;
padding: 2px 0;
font-size: 1.1rem;
font-weight: 600;
font-style: italic;
color: $color-gray;
margin-right: 20px;
}

View file

@ -1,7 +1,7 @@
.view-attachment { .view-attachment {
> .upload-document-files { > .upload-document-files {
margin: 10px 0 0 0;
@include ease-in(); @include ease-in();
margin: 50px 0 10px 0;
> .dz-preview, .dz-processing { > .dz-preview, .dz-processing {
display: none !important; display: none !important;
@ -9,21 +9,29 @@
} }
> .list { > .list {
margin: 0; margin: 10px 0 0 0;
padding: 0; padding: 0;
> .item { > .item {
color: $color-off-black;
margin: 0; margin: 0;
padding: 0; padding: 0;
font-size: 1rem; font-size: 1.1rem;
list-style-type: none; list-style-type: none;
border-left: 6px solid $color-gray-light;
padding-left: 15px;
margin-left: 3px;
> a { > a {
@include ease-in();
display: inline-block; display: inline-block;
font-size: 1rem; font-size: 1.1rem;
vertical-align: text-top; vertical-align: text-top;
margin-right: 10px; margin-right: 10px;
color: $color-gray;
&:hover {
color: $color-link;
}
} }
> .delete { > .delete {

View file

@ -41,3 +41,25 @@
} }
} }
} }
.widget-list-choice {
text-align: left;
margin: 0;
padding: 0;
display: inline-block;
> li {
margin: 0 20px 0 0;
padding: 0;
display: inline-block;
font-size: 1.3rem;
font-weight: 700;
color: $color-gray;
cursor: pointer;
}
> .selected {
color: $color-green;
border-bottom: 1px solid $color-green;
}
}

View file

@ -1,26 +0,0 @@
{{#unless editMode}}
<div class="view-document">
<div class="document-heading {{if canEdit 'cursor-pointer'}}" onclick={{if canEdit (action 'toggleEdit')}}>
<h1 class="doc-title">
{{#if document.template}}
<span class="bg-warning p-1 pr-2 pl-2">Template</span>&nbsp;&nbsp;
{{/if}}
{{document.name}}
</h1>
<div class="doc-excerpt">{{document.excerpt}}</div>
</div>
</div>
{{else}}
<form class="view-document" {{action "onSave" on="submit"}}>
<div class="document-heading-edit">
<div class="form-group">
{{focus-input id="document-name" type="text" value=docName class=(if hasNameError 'form-control mousetrap is-invalid' 'form-control mousetrap') placeholder="Title" autocomplete="off"}}
</div>
<div class="form-group">
{{textarea id="document-excerpt" rows="2" value=docExcerpt class='form-control mousetrap' placeholder="Excerpt" autocomplete="off"}}
</div>
<button type="button" class="btn btn-outline-secondary" {{action "onCancel"}}>Cancel</button>
<button type="submit" class="btn btn-success" {{action "onSave"}}>Save</button>
</div>
</form>
{{/unless}}

View file

@ -1,119 +1,42 @@
<div class="view-document"> {{#if (eq document.lifecycle constants.Lifecycle.Live)}}
<div class="document-customfields"> <div id="document-lifecycle-popover" class="document-lifecycle-live text-uppercase">{{document.lifecycleLabel}}</div>
<div class="row {{if (eq selectedCategories.length 0) 'd-print-none'}}">
<div class="col-12 col-sm-3 heading">Categories</div>
<div class="col-12 col-sm-9 value">
{{#each selectedCategories as |cat|}}
{{#link-to 'folder' folder.id folder.slug (query-params category=cat.id)}}
{{cat.category}}
{{/link-to}}
&nbsp;
{{else}}
{{#if canAddCategory}}
{{#if canSelectCategory}}
<a href="#" class="d-print-none" {{action 'onShowCategoryModal'}}>&lt;select&gt;</a>
{{else}}
{{#link-to 'folder.category' folder.id folder.slug class='d-print-none'}}&lt;manage&gt;{{/link-to}}
{{/if}} {{/if}}
{{#if (eq document.lifecycle constants.Lifecycle.Draft)}}
<div id="document-lifecycle-popover" class="document-lifecycle-draft text-uppercase">{{document.lifecycleLabel}}</div>
{{/if}} {{/if}}
{{/each}}
{{#if canSelectCategory}} <div class="d-block d-sm-none margin-top-20" />
<div class="action-button button-icon-gray button-icon-small align-middle d-print-none" {{action 'onShowCategoryModal'}}> <div class="d-sm-inline-block margin-left-20" />
<i class="material-icons align-middle">edit</i>
</div> {{#if (eq document.protection constants.ProtectionType.None)}}
<div id="document-protection-popover" class="document-protection-unlocked text-uppercase">OPEN</div>
{{/if}} {{/if}}
</div>
</div>
<div class="row {{if (eq tagz.length 0) 'd-print-none'}}">
<div class="col-12 col-sm-3 heading">Tags</div>
<div class="col-12 col-sm-9 value">
{{#each tagz as |t index|}}
{{#link-to 'search' (query-params filter=t matchTag=true matchDoc=false matchContent=false matchFile=false)}}
{{concat '#' t}}
{{/link-to}}&nbsp;&nbsp;
{{/each}}
{{#if permissions.documentEdit}}
<div class="action-button button-icon-gray button-icon-small align-middle d-print-none" data-toggle="modal" data-target="#document-tags-modal" data-backdrop="static">
<i class="material-icons align-middle">edit</i>
</div>
{{/if}}
</div>
</div>
<div class="row d-print-none">
<div class="col-12 col-sm-3 heading">Change Control</div>
<div class="col-12 col-sm-9 value-static">
<span>{{changeControlMsg}}</span>
</div>
</div>
{{#if (eq document.protection constants.ProtectionType.Review)}} {{#if (eq document.protection constants.ProtectionType.Review)}}
<div class="row d-print-none"> <div id="document-protection-popover" class="document-protection-review text-uppercase">PROTECTED</div>
<div class="col-12 col-sm-3 heading">Approval Process</div> {{/if}}
<div class="col-12 col-sm-9 value-static"> {{#if (eq document.protection constants.ProtectionType.Lock)}}
<span>{{approvalMsg}}</span> <div id="document-protection-popover" class="document-protection-locked text-uppercase">LOCKED</div>
</div>
</div>
{{#if userChanges}}
<div class="row d-print-none">
<div class="col-12 col-sm-3 heading">Your Contributions</div>
<div class="col-12 col-sm-9 value-static">
<span>{{contributorMsg}}</span>
</div>
</div>
{{/if}} {{/if}}
{{#if isApprover}} <div class="d-block d-sm-none margin-top-20" />
<div class="row d-print-none"> <div class="d-sm-inline-block margin-left-20" />
<div class="col-12 col-sm-3 heading">Approver Status</div>
<div class="col-12 col-sm-9 value-static">
<span>{{approverMsg}}</span>
</div>
</div>
{{/if}}
{{/if}}
{{document/view-attachment document=document permissions=permissions}}
{{#each selectedCategories as |cat|}}
<div class="document-category" data-toggle="tooltip" data-placement="top" title="Category">
{{cat.category}}
</div> </div>
</div> {{/each}}
{{#if permissions.documentEdit}} <div class="d-block d-sm-none margin-top-20" />
{{#ui/ui-dialog title="Document Categories" confirmCaption="Select" buttonType="btn-success" show=showCategoryModal onAction=(action 'onSaveCategory')}}
<p>Select who can view documents within category</p>
{{ui/ui-list-picker items=categories nameField='category' singleSelect=false}}
{{/ui/ui-dialog}}
<div id="document-tags-modal" class="modal" tabindex="-1" role="dialog"> {{#each tagz as |t index|}}
<div class="modal-dialog" role="document"> <div class="document-tag" data-toggle="tooltip" data-placement="top" title="Tag">
<div class="modal-content">
<div class="modal-header">Document Tags</div>
<div class="modal-body">
<form onsubmit={{action 'onAddTag'}}>
<div class="form-group">
<label for="add-tag-field">Specify up to three tags per document</label>
{{#each tagzEdit as |t|}}
<div class="m-3 text-secondary">
<div class="button-icon-danger button-icon-small align-middle" {{action 'onRemoveTag' t}}>
<i class="material-icons">clear</i>
</div>
{{concat '#' t}} {{concat '#' t}}
</div> </div>
{{/each}} {{/each}}
{{focus-input type='text' id="add-tag-field" class="form-control mousetrap" placeholder="Tag name" value=newTag}}
<small class="form-text text-success">Press ENTER to add tag</small> <div class="document-meta">
<small class="form-text text-muted">Lowercase, characters, numbers, hyphens only</small> {{document/view-attachment document=document permissions=permissions}}
</div> </div>
</form>
</div> <div class="margin-top-70" />
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-success" onclick={{action 'onSaveTags'}}>Save</button>
</div>
</div>
</div>
</div>
{{/if}}

View file

@ -0,0 +1,21 @@
<div class="content-zone">
<div class="explainer-header">General Options</div>
<p class="explainer-text explainer-gap">Set name, excerpt and lifecycle stage</p>
<form class="view-document">
<div class="form-group">
<label for="document-name">Name</label>
{{focus-input id="document-name" type="text" value=docName
class=(if hasNameError 'form-control mousetrap is-invalid' 'form-control mousetrap') placeholder="Title" autocomplete="off" disabled=noEdits}}
</div>
<div class="form-group">
<label for="document-excerpt">Excerpt</label>
{{textarea id="document-excerpt" rows="4" value=docExcerpt class='form-control mousetrap' placeholder="Excerpt" autocomplete="off" disabled=noEdits}}
<small class="form-text text-muted">Optional description explaining content</small>
</div>
<button type="submit" class="btn btn-success text-uppercase font-weight-bold mt-5" {{action "onSave"}}>Save</button>
</form>
</div>

View file

@ -0,0 +1,45 @@
<div class="content-zone">
<div class="explainer-header">Categories & Tags</div>
<p class="explainer-text explainer-gap">Categorize your content, assign tags to suppliment</p>
<h1>Specify up to three tags</h1>
<p class="form-text text-muted">Lowercase, characters, numbers, hyphens only</p>
<form>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">#</span>
</div>
{{input type='text' id='add-tag-field1' class="form-control mousetrap tag-input" placeholder="Tag name" value=tag1}}
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">#</span>
</div>
{{input type='text' id='add-tag-field2' class="form-control mousetrap tag-input" placeholder="Tag name" value=tag2}}
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">#</span>
</div>
{{input type='text' id='add-tag-field3' class="form-control mousetrap tag-input" placeholder="Tag name" value=tag3}}
</div>
</form>
<div class="mt-5" />
<h1>Assign categories</h1>
<p class="form-text text-muted">Categories allow you divide a space into logical chunks</p>
{{ui/ui-list-picker items=categories nameField='category' singleSelect=false}}
{{#unless selectedCategories}}
<p class="text-danger">This space has no categories defined yet.</p>
{{#if canAddCategory}}
<p>
{{#link-to 'folder.category' space.id space.slug class="btn btn-secondary font-weight-bold"}}Manage categories{{/link-to}}
</p>
{{/if}}
{{/unless}}
<button type="submit" class="btn btn-success text-uppercase font-weight-bold mt-5" {{action "onSave"}}>Save</button>
</div>

View file

@ -1,5 +1,9 @@
<div class="col-12 col-sm-3 heading">Attachments</div> <div class="view-attachment d-print-none">
<div class="col-12 col-sm-9 view-attachment"> {{#if canEdit}}
<div class="upload-document-files">
<div id="upload-document-files" class="btn btn-secondary text-uppercase font-weight-bold">+ Attachments</div>
</div>
{{/if}}
{{#if hasAttachments}} {{#if hasAttachments}}
<ul class="list"> <ul class="list">
{{#each files key="id" as |a index|}} {{#each files key="id" as |a index|}}
@ -18,11 +22,6 @@
{{/each}} {{/each}}
</ul> </ul>
{{/if}} {{/if}}
{{#if canEdit}}
<div class="upload-document-files">
<div id="upload-document-files" class="btn btn-outline-secondary">Upload</div>
</div>
{{/if}}
</div> </div>
{{#ui/ui-dialog title="Delete Attachment" confirmCaption="Delete" buttonType="btn-danger" show=showDialog onAction=(action 'onDelete')}} {{#ui/ui-dialog title="Delete Attachment" confirmCaption="Delete" buttonType="btn-danger" show=showDialog onAction=(action 'onDelete')}}

View file

@ -1,5 +1,11 @@
<div class="text-right non-printable"> <div class="text-right non-printable">
{{#if session.authenticated}} {{#if session.authenticated}}
{{#if permissions.documentEdit}}
{{#link-to 'document.settings' space.id space.slug document.id document.slug class="button-icon-gray align-middle"}}
<i class="material-icons" data-toggle="tooltip" data-placement="top" title="Meta, Lifecycle, Change Control">settings</i>
{{/link-to}}
<div class="button-icon-gap" />
{{/if}}
{{#if permissions.documentAdd}} {{#if permissions.documentAdd}}
<div id="document-template-button" class="button-icon-gray align-middle" data-toggle="tooltip" data-placement="top" title="Save as template"> <div id="document-template-button" class="button-icon-gray align-middle" data-toggle="tooltip" data-placement="top" title="Save as template">
<i class="material-icons" data-toggle="modal" data-target="#document-template-modal" data-backdrop="static">content_copy</i> <i class="material-icons" data-toggle="modal" data-target="#document-template-modal" data-backdrop="static">content_copy</i>

View file

@ -1,6 +1,6 @@
{ {
"name": "documize", "name": "documize",
"version": "1.65.1", "version": "1.65.2",
"description": "The Document IDE", "description": "The Document IDE",
"private": true, "private": true,
"repository": "", "repository": "",

File diff suppressed because it is too large Load diff