From 5dd7d9c1814ad2ca7c5838af50e6e799563d8573 Mon Sep 17 00:00:00 2001 From: sauls8t Date: Mon, 22 Jan 2018 10:31:03 +0000 Subject: [PATCH] upgraded Ember and Bootstrap, merged changes --- gui/.eslintrc.js | 43 +- gui/.travis.yml | 22 +- gui/app/components/customize/auth-settings.js | 24 +- gui/app/components/customize/smtp-settings.js | 1 + gui/app/components/customize/user-admin.js | 37 +- gui/app/components/customize/user-settings.js | 7 +- gui/app/components/document/block-editor.js | 6 +- gui/app/components/document/content-linker.js | 20 +- .../components/document/document-editor.js | 6 +- .../components/document/document-heading.js | 4 +- gui/app/components/document/document-meta.js | 100 +- gui/app/components/document/document-page.js | 59 +- gui/app/components/document/document-toc.js | 205 +- gui/app/components/document/page-heading.js | 145 +- .../components/document/view-attachment.js | 18 +- gui/app/components/document/view-content.js | 152 +- gui/app/components/document/view-revision.js | 9 +- gui/app/components/focus-input.js | 7 +- gui/app/components/focus-textarea.js | 5 +- gui/app/components/folder/category-admin.js | 7 +- gui/app/components/folder/document-tags.js | 3 - gui/app/components/folder/documents-list.js | 7 +- gui/app/components/folder/space-heading.js | 1 + gui/app/components/folder/space-view.js | 9 +- gui/app/components/forgot-password.js | 2 +- gui/app/components/onboard/share-folder.js | 2 +- gui/app/components/password-reset.js | 2 +- gui/app/components/search/search-results.js | 6 +- .../section/airtable/type-editor.js | 6 +- .../components/section/base-editor-inline.js | 13 +- gui/app/components/section/base-editor.js | 14 +- .../components/section/code/type-editor.js | 11 +- .../components/section/gemini/type-editor.js | 17 +- .../components/section/github/type-editor.js | 10 +- .../section/markdown/type-editor.js | 6 +- .../section/papertrail/type-editor.js | 11 +- .../components/section/table/type-editor.js | 15 +- .../components/section/trello/type-editor.js | 11 +- .../components/section/wysiwyg/type-editor.js | 8 +- gui/app/components/spaces/space-list.js | 10 +- gui/app/components/toolbar/for-document.js | 37 +- gui/app/components/toolbar/for-space.js | 32 +- gui/app/components/toolbar/for-spaces.js | 10 +- gui/app/components/toolbar/nav-bar.js | 2 +- gui/app/components/ui-select.js | 6 +- gui/app/components/ui/ui-dialog.js | 4 +- gui/app/components/ui/ui-list-picker.js | 9 +- gui/app/components/ui/ui-radio.js | 3 +- gui/app/components/user-notification.js | 5 +- gui/app/components/user-profile.js | 8 +- gui/app/constants/constants.js | 45 +- gui/app/constants/econstants.js | 26 + gui/app/initializers/application.js | 4 + gui/app/initializers/constants.js | 2 + gui/app/initializers/econstants.js | 24 + gui/app/mixins/modal.js | 1 + gui/app/mixins/section.js | 20 +- gui/app/mixins/tooltip.js | 15 +- .../doc-search-result.js} | 27 +- gui/app/models/document-activity.js | 8 +- gui/app/models/page-container.js | 30 + gui/app/models/page-pending.js | 28 + gui/app/models/page.js | 18 +- gui/app/models/user-activity.js | 11 +- gui/app/pods/auth/forgot/route.js | 2 +- gui/app/pods/auth/login/route.js | 1 + gui/app/pods/auth/reset/route.js | 1 + gui/app/pods/auth/share/route.js | 2 +- gui/app/pods/customize/folders/controller.js | 18 +- gui/app/pods/customize/users/controller.js | 6 +- gui/app/pods/document/controller.js | 3 +- gui/app/pods/document/index/controller.js | 117 +- gui/app/pods/document/index/route.js | 17 +- gui/app/pods/document/index/template.hbs | 25 +- gui/app/pods/document/route.js | 7 +- gui/app/pods/folder/route.js | 2 - gui/app/pods/search/controller.js | 15 +- gui/app/pods/setup/route.js | 1 + gui/app/router.js | 7 + gui/app/serializers/page-container.js | 13 + gui/app/serializers/page-pending.js | 13 + gui/app/services/activity.js | 28 +- gui/app/services/browser.js | 14 + gui/app/services/document.js | 129 +- gui/app/services/folder.js | 8 +- gui/app/services/kc-auth.js | 7 +- gui/app/services/pinned.js | 6 +- gui/app/services/search.js | 20 +- gui/app/services/session.js | 1 + gui/app/styles/view/document/copy-move.scss | 55 + .../styles/view/document/doc-structure.scss | 12 + gui/app/styles/view/document/document.scss | 4 +- .../styles/view/document/view-activity.scss | 94 +- .../components/document/document-heading.hbs | 2 +- .../components/document/document-meta.hbs | 33 + .../components/document/document-page.hbs | 8 +- .../components/document/document-toc.hbs | 78 +- .../components/document/page-heading.hbs | 206 +- .../components/document/view-activity.hbs | 13 - .../components/document/view-attachment.hbs | 5 +- .../components/document/view-content.hbs | 140 +- .../components/document/view-revision.hbs | 20 +- gui/app/utils/constants.js | 7 +- gui/app/utils/group-by.js | 49 + gui/app/utils/model.js | 31 +- gui/app/utils/toc.js | 80 +- gui/config/targets.js | 1 - gui/mirage/factories/user.js | 4 +- gui/package-lock.json | 7988 ++++++++++++++++- gui/package.json | 24 +- gui/testem.js | 16 +- gui/tests/helpers/start-app.js | 30 +- gui/tests/test-helper.js | 12 +- gui/yarn.lock | 384 +- 114 files changed, 9814 insertions(+), 1361 deletions(-) create mode 100644 gui/app/constants/econstants.js create mode 100644 gui/app/initializers/econstants.js rename gui/app/{components/document/view-activity.js => models/doc-search-result.js} (53%) create mode 100644 gui/app/models/page-container.js create mode 100644 gui/app/models/page-pending.js create mode 100644 gui/app/serializers/page-container.js create mode 100644 gui/app/serializers/page-pending.js create mode 100644 gui/app/styles/view/document/copy-move.scss delete mode 100644 gui/app/templates/components/document/view-activity.hbs create mode 100644 gui/app/utils/group-by.js diff --git a/gui/.eslintrc.js b/gui/.eslintrc.js index e3b0a0d3..20f01e08 100644 --- a/gui/.eslintrc.js +++ b/gui/.eslintrc.js @@ -4,17 +4,46 @@ module.exports = { ecmaVersion: 2017, sourceType: 'module' }, - extends: 'eslint:recommended', + plugins: [ + 'ember' + ], + extends: [ + 'eslint:recommended', + 'plugin:ember/recommended' + ], env: { - browser: true, - jquery: true, - qunit: true, - embertest: true + browser: true }, rules: { }, + overrides: [ + // node files + { + files: [ + 'testem.js', + 'ember-cli-build.js', + 'config/**/*.js' + ], + parserOptions: { + sourceType: 'script', + ecmaVersion: 2015 + }, + env: { + browser: false, + node: true + } + }, + + // test files + { + files: ['tests/**/*.js'], + excludedFiles: ['tests/dummy/**/*.js'], + env: { + embertest: true + } + } + ], globals: { - "$": true, "is": true, "_": true, "tinymce": true, @@ -31,5 +60,5 @@ module.exports = { "Keycloak": true, "slug": true, "interact": true - } + } }; diff --git a/gui/.travis.yml b/gui/.travis.yml index 385f003d..68a25a16 100644 --- a/gui/.travis.yml +++ b/gui/.travis.yml @@ -4,18 +4,26 @@ node_js: - "6" sudo: false +dist: trusty + +addons: + chrome: stable cache: - directories: - - $HOME/.npm + yarn: true + +env: + global: + # See https://git.io/vdao3 for details. + - JOBS=1 before_install: - - npm config set spin false - - npm install -g phantomjs-prebuilt - - phantomjs --version + - curl -o- -L https://yarnpkg.com/install.sh | bash + - export PATH=$HOME/.yarn/bin:$PATH install: - - npm install + - yarn install --non-interactive script: - - npm test + - yarn lint:js + - yarn test diff --git a/gui/app/components/customize/auth-settings.js b/gui/app/components/customize/auth-settings.js index 38048d6d..11bc5db7 100644 --- a/gui/app/components/customize/auth-settings.js +++ b/gui/app/components/customize/auth-settings.js @@ -27,16 +27,20 @@ export default Component.extend({ KeycloakPublicKeyError: empty('keycloakConfig.publicKey'), KeycloakAdminUserError: empty('keycloakConfig.adminUser'), KeycloakAdminPasswordError: empty('keycloakConfig.adminPassword'), - keycloakConfig: { - url: '', - realm: '', - clientId: '', - publicKey: '', - adminUser: '', - adminPassword: '', - group: '', - disableLogout: false, - defaultPermissionAddSpace: false + + init() { + this._super(...arguments); + this.keycloakConfig = { + url: '', + realm: '', + clientId: '', + publicKey: '', + adminUser: '', + adminPassword: '', + group: '', + disableLogout: false, + defaultPermissionAddSpace: false + }; }, didReceiveAttrs() { diff --git a/gui/app/components/customize/smtp-settings.js b/gui/app/components/customize/smtp-settings.js index 8f661894..7a3176e3 100644 --- a/gui/app/components/customize/smtp-settings.js +++ b/gui/app/components/customize/smtp-settings.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import { empty } from '@ember/object/computed'; import Component from '@ember/component'; diff --git a/gui/app/components/customize/user-admin.js b/gui/app/components/customize/user-admin.js index c3eb66f8..e1fcd8d4 100644 --- a/gui/app/components/customize/user-admin.js +++ b/gui/app/components/customize/user-admin.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import Component from '@ember/component'; import { schedule, debounce } from '@ember/runloop'; import AuthProvider from '../../mixins/auth'; @@ -17,13 +18,17 @@ import ModalMixin from '../../mixins/modal'; export default Component.extend(AuthProvider, ModalMixin, { editUser: null, deleteUser: null, - password: {}, filter: '', - filteredUsers: [], - selectedUsers: [], hasSelectedUsers: false, showDeleteDialog: false, + init() { + this._super(...arguments); + this.password = {}; + this.filteredUsers = []; + this.selectedUsers = []; + }, + didReceiveAttrs() { this._super(...arguments); @@ -74,25 +79,29 @@ export default Component.extend(AuthProvider, ModalMixin, { toggleActive(id) { let user = this.users.findBy("id", id); user.set('active', !user.get('active')); - this.attrs.onSave(user); + let cb = this.get('onSave'); + cb(user); }, toggleEditor(id) { let user = this.users.findBy("id", id); user.set('editor', !user.get('editor')); - this.attrs.onSave(user); + let cb = this.get('onSave'); + cb(user); }, toggleAdmin(id) { let user = this.users.findBy("id", id); user.set('admin', !user.get('admin')); - this.attrs.onSave(user); + let cb = this.get('onSave'); + cb(user); }, toggleUsers(id) { let user = this.users.findBy("id", id); user.set('viewUsers', !user.get('viewUsers')); - this.attrs.onSave(user); + let cb = this.get('onSave'); + cb(user); }, onShowEdit(id) { @@ -135,11 +144,14 @@ export default Component.extend(AuthProvider, ModalMixin, { $('#edit-user-modal').modal('hide'); $('#edit-user-modal').modal('dispose'); - this.attrs.onSave(user); + let cb = this.get('onSave'); + cb(user); if (is.not.empty(password.password) && is.not.empty(password.confirmation) && password.password === password.confirmation) { - this.attrs.onPassword(user, password.password); + + let pw = this.get('onPassword'); + pw(user, password.password); } }, @@ -153,7 +165,9 @@ export default Component.extend(AuthProvider, ModalMixin, { this.set('selectedUsers', []); this.set('hasSelectedUsers', false); - this.attrs.onDelete(this.get('deleteUser.id')); + + let cb = this.get('onDelete'); + cb(this.get('deleteUser.id')); return true; }, @@ -162,7 +176,8 @@ export default Component.extend(AuthProvider, ModalMixin, { let su = this.get('selectedUsers'); su.forEach(userId => { - this.attrs.onDelete(userId); + let cb = this.get('onDelete'); + cb(userId); }); this.set('selectedUsers', []); diff --git a/gui/app/components/customize/user-settings.js b/gui/app/components/customize/user-settings.js index 17d1f003..ff0de148 100644 --- a/gui/app/components/customize/user-settings.js +++ b/gui/app/components/customize/user-settings.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import { empty, and } from '@ember/object/computed'; import Component from '@ember/component'; import { isEmpty } from '@ember/utils'; @@ -16,7 +17,6 @@ import { get, set } from '@ember/object'; import AuthProvider from '../../mixins/auth'; export default Component.extend(AuthProvider, { - newUser: { firstname: "", lastname: "", email: "", active: true }, firstnameEmpty: empty('newUser.firstname'), lastnameEmpty: empty('newUser.lastname'), emailEmpty: empty('newUser.email'), @@ -24,6 +24,11 @@ export default Component.extend(AuthProvider, { hasLastnameEmptyError: and('lastnameEmpty', 'lastnameError'), hasEmailEmptyError: and('emailEmpty', 'emailError'), + init() { + this._super(...arguments); + this.newUser = { firstname: "", lastname: "", email: "", active: true }; + }, + actions: { add() { if (isEmpty(this.get('newUser.firstname'))) { diff --git a/gui/app/components/document/block-editor.js b/gui/app/components/document/block-editor.js index 99f97b2e..5847e644 100644 --- a/gui/app/components/document/block-editor.js +++ b/gui/app/components/document/block-editor.js @@ -44,11 +44,13 @@ export default Component.extend({ actions: { onCancel() { - this.attrs.onCancel(); + let cb = this.get('onCancel'); + cb(); }, onAction(page, meta) { - this.attrs.onAction(page, meta); + let cb = this.get('onAction'); + cb(page, meta); } } }); diff --git a/gui/app/components/document/content-linker.js b/gui/app/components/document/content-linker.js index a1e3a9b5..df5f0b1a 100644 --- a/gui/app/components/document/content-linker.js +++ b/gui/app/components/document/content-linker.js @@ -20,25 +20,17 @@ export default Component.extend(ModalMixin, TooltipMixin, { link: service(), linkName: '', selection: null, - tab1Selected: true, tab2Selected: false, tab3Selected: false, showSections: computed('tab1Selected', function() { return this.get('tab1Selected'); }), showAttachments: computed('tab2Selected', function() { return this.get('tab2Selected'); }), showSearch: computed('tab3Selected', function() { return this.get('tab3Selected'); }), - keywords: '', - matches: { - documents: [], - pages: [], - attachments: [] - }, hasMatches: computed('matches', function () { let m = this.get('matches'); return m.documents.length || m.pages.length || m.attachments.length; }), - modalId: computed('page', function() { return '#content-linker-modal-' + this.get('page.id'); }), showModal: false, onToggle: function() { @@ -63,17 +55,23 @@ export default Component.extend(ModalMixin, TooltipMixin, { this.modalOpen(modalId, {show: true}); }.observes('showModal'), + init() { + this._super(...arguments); + this.matches = { + documents: [], + pages: [], + attachments: [] + }; + }, + didRender() { this._super(...arguments); - this.renderTooltips(); }, willDestroyElement() { this._super(...arguments); - this.removeTooltips(); - this.modalClose(this.get('modalId')); }, diff --git a/gui/app/components/document/document-editor.js b/gui/app/components/document/document-editor.js index 59ca7819..56ac7c80 100644 --- a/gui/app/components/document/document-editor.js +++ b/gui/app/components/document/document-editor.js @@ -18,11 +18,13 @@ export default Component.extend({ actions: { onCancel() { - this.attrs.onCancel(); + let cb = this.get('onCancel'); + cb(); }, onAction(page, meta) { - this.attrs.onAction(page, meta); + let cb = this.get('onAction'); + cb(page, meta); } } }); \ No newline at end of file diff --git a/gui/app/components/document/document-heading.js b/gui/app/components/document/document-heading.js index 68fceb18..0d7165c1 100644 --- a/gui/app/components/document/document-heading.js +++ b/gui/app/components/document/document-heading.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import { empty } from '@ember/object/computed'; import { computed } from '@ember/object'; import { schedule } from '@ember/runloop'; @@ -62,7 +63,8 @@ export default Component.extend({ this.set('document.excerpt', this.get('docExcerpt')); this.set('editMode', false); - this.attrs.onSaveDocument(this.get('document')); + let cb = this.get('onSaveDocument'); + cb(this.get('document')); }, onCancel() { diff --git a/gui/app/components/document/document-meta.js b/gui/app/components/document/document-meta.js index 1bfddb85..591b07ce 100644 --- a/gui/app/components/document/document-meta.js +++ b/gui/app/components/document/document-meta.js @@ -9,7 +9,9 @@ // // https://documize.com +import $ from 'jquery'; import { computed } from '@ember/object'; +import { notEmpty } from '@ember/object/computed'; import { inject as service } from '@ember/service'; import Component from '@ember/component'; import { A } from "@ember/array" @@ -19,12 +21,9 @@ export default Component.extend({ documentService: service('document'), categoryService: service('category'), sessionService: service('session'), - maxTags: 3, + categories: A([]), newCategory: '', - tagz: A([]), - tagzModal: A([]), - newTag: '', showCategoryModal: false, hasCategories: computed('categories', function() { return this.get('categories').length > 0; @@ -36,9 +35,63 @@ export default Component.extend({ return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage'); }), + maxTags: 3, + tagz: A([]), + tagzModal: A([]), + newTag: '', + + contributorMsg: '', + approverMsg: '', + userChanges: notEmpty('contributorMsg'), + isApprover: computed('permissions', function() { + return this.get('permissions.documentApprove'); + }), + changeControlMsg: computed('document.protection', function() { + let p = this.get('document.protection'); + let constants = this.get('constants'); + let msg = ''; + + switch (p) { + case constants.ProtectionType.None: + msg = constants.ProtectionType.NoneLabel; + break; + case constants.ProtectionType.Lock: + msg = constants.ProtectionType.LockLabel; + break; + case constants.ProtectionType.Review: + msg = constants.ProtectionType.ReviewLabel; + break; + } + + return msg; + }), + approvalMsg: computed('document.{protection,approval}', function() { + let p = this.get('document.protection'); + let a = this.get('document.approval'); + let constants = this.get('constants'); + let msg = ''; + + if (p === constants.ProtectionType.Review) { + switch (a) { + case constants.ApprovalType.Anybody: + msg = constants.ApprovalType.AnybodyLabel; + break; + case constants.ApprovalType.Majority: + msg = constants.ApprovalType.MajorityLabel; + break; + case constants.ApprovalType.Unanimous: + msg = constants.ApprovalType.UnanimousLabel; + break; + } + } + + return msg; + }), + didReceiveAttrs() { this._super(...arguments); this.load(); + this.workflowStatus(); }, didInsertElement() { @@ -100,6 +153,39 @@ export default Component.extend({ this.set('tagz', A(tagz)); }, + workflowStatus() { + let pages = this.get('pages'); + let contributorMsg = ''; + let userPendingCount = 0; + let userReviewCount = 0; + let userRejectedCount = 0; + let approverMsg = ''; + let approverPendingCount = 0; + let approverReviewCount = 0; + let approverRejectedCount = 0; + + pages.forEach((item) => { + if (item.get('userHasChangePending')) userPendingCount+=1; + if (item.get('userHasChangeAwaitingReview')) userReviewCount+=1; + if (item.get('userHasChangeRejected')) userRejectedCount+=1; + if (item.get('changePending')) approverPendingCount+=1; + if (item.get('changeAwaitingReview')) approverReviewCount+=1; + if (item.get('changeRejected')) approverRejectedCount+=1; + }); + + if (userPendingCount > 0 || userReviewCount > 0 || userRejectedCount > 0) { + let label = userPendingCount === 1 ? 'change' : 'changes'; + contributorMsg = `${userPendingCount} ${label} progressing, ${userReviewCount} awaiting review, ${userRejectedCount} rejected`; + } + this.set('contributorMsg', contributorMsg); + + if (approverPendingCount > 0 || approverReviewCount > 0 || approverRejectedCount > 0) { + let label = approverPendingCount === 1 ? 'change' : 'changes'; + approverMsg = `${approverPendingCount} ${label} progressing, ${approverReviewCount} awaiting review, ${approverRejectedCount} rejected`; + } + this.set('approverMsg', approverMsg); + }, + actions: { onShowCategoryModal() { this.set('showCategoryModal', true); @@ -179,13 +265,15 @@ export default Component.extend({ let doc = this.get('document'); doc.set('tags', save); - this.attrs.onSaveDocument(doc); + + let cb = this.get('onSaveDocument'); + cb(doc); this.load(); this.set('newTag', ''); $('#document-tags-modal').modal('hide'); $('#document-tags-modal').modal('dispose'); - }, + } } }); diff --git a/gui/app/components/document/document-page.js b/gui/app/components/document/document-page.js index ca7b2106..239e9885 100644 --- a/gui/app/components/document/document-page.js +++ b/gui/app/components/document/document-page.js @@ -17,46 +17,55 @@ export default Component.extend(TooltipMixin, { documentService: service('document'), sectionService: service('section'), editMode: false, + editPage: null, + editMeta: null, didReceiveAttrs() { this._super(...arguments); + if (this.get('isDestroyed') || this.get('isDestroying')) return; + if (this.get('toEdit') === this.get('page.id') && this.get('permissions.documentEdit')) this.send('onEdit'); - if (this.get('isDestroyed') || this.get('isDestroying')) { - return; + if (this.get('session.authenticated')) { + this.workflow(); } + }, - let page = this.get('page'); - - this.get('documentService').getPageMeta(page.get('documentId'), page.get('id')).then((meta) => { - if (this.get('isDestroyed') || this.get('isDestroying')) { - return; - } - - this.set('meta', meta); - if (this.get('toEdit') === this.get('page.id') && this.get('permissions.documentEdit')) { - this.send('onEdit'); - } - }); + workflow() { + this.set('editPage', this.get('page')); + this.set('editMeta', this.get('meta')); }, actions: { onSavePage(page, meta) { - this.set('page', page); - this.set('meta', meta); + let constants = this.get('constants'); + + if (this.get('document.protection') === constants.ProtectionType.Review) { + if (this.get('page.status') === constants.ChangeState.Published) { + page.set('relativeId', this.get('page.id')); + } + if (this.get('page.status') === constants.ChangeState.PendingNew) { + page.set('relativeId', ''); + } + } + this.set('editMode', false); - this.get('onSavePage')(page, meta); + let cb = this.get('onSavePage'); + cb(page, meta); }, onSavePageAsBlock(block) { - this.attrs.onSavePageAsBlock(block); + let cb = this.get('onSavePageAsBlock'); + cb(block); }, onCopyPage(documentId) { - this.attrs.onCopyPage(this.get('page.id'), documentId); + let cb = this.get('onCopyPage'); + cb(this.get('page.id'), documentId); }, onMovePage(documentId) { - this.attrs.onMovePage(this.get('page.id'), documentId); + let cb = this.get('onMovePage'); + cb(this.get('page.id'), documentId); }, onDeletePage(deleteChildren) { @@ -72,16 +81,14 @@ export default Component.extend(TooltipMixin, { children: deleteChildren }; - this.attrs.onDeletePage(params); + let cb = this.get('onDeletePage'); + cb(params); }, + // Calculate if user is editing page or a pending change as per approval process onEdit() { - if (this.get('editMode')) { - return; - } - + if (this.get('editMode')) return; this.get('toEdit', ''); - // this.set('pageId', this.get('page.id')); this.set('editMode', true); }, diff --git a/gui/app/components/document/document-toc.js b/gui/app/components/document/document-toc.js index e5af682f..93d1419a 100644 --- a/gui/app/components/document/document-toc.js +++ b/gui/app/components/document/document-toc.js @@ -9,101 +9,119 @@ // // https://documize.com +import $ from 'jquery'; import { computed } from '@ember/object'; import { schedule } from '@ember/runloop'; import { inject as service } from '@ember/service'; import Component from '@ember/component'; import tocUtil from '../../utils/toc'; +import TooltipMixin from '../../mixins/tooltip'; -export default Component.extend({ +export default Component.extend(TooltipMixin, { documentService: service('document'), - document: {}, - folder: {}, - pages: [], - currentPageId: '', - state: { - actionablePage: false, - upDisabled: true, - downDisabled: true, - indentDisabled: true, - outdentDisabled: true - }, + isDesktop: false, emptyState: computed('pages', function () { return this.get('pages.length') === 0; }), - isDesktop: false, + canEdit: computed('permssions', 'document', function() { + let constants = this.get('constants'); + let permissions = this.get('permissions'); + let authenticated = this.get('session.authenticated'); + let notEmpty = this.get('pages.length') > 0; + + if (notEmpty && authenticated && permissions.get('documentEdit') && this.get('document.protection') === constants.ProtectionType.None) return true; + if (notEmpty && authenticated && permissions.get('documentApprove') && this.get('document.protection') === constants.ProtectionType.Review) return true; + + return false; + }), + + init() { + this._super(...arguments); + this.state = { + actionablePage: false, + upDisabled: true, + downDisabled: true, + indentDisabled: true, + outdentDisabled: true, + pageId: '' + }; + }, didReceiveAttrs() { this._super(...arguments); - this.setState(this.get('currentPageId')); + let cp = this.get('currentPageId'); + this.setState(is.empty(cp) ? '' : cp); }, didInsertElement() { this._super(...arguments); - - this.setSize(); this.eventBus.subscribe('documentPageAdded', this, 'onDocumentPageAdded'); - this.eventBus.subscribe('resized', this, 'onResize'); - this.attachResizer(); + this.eventBus.subscribe('resized', this, 'setSize'); + + this.setSize(); + this.renderTooltips(); }, willDestroyElement() { this._super(...arguments); - this.eventBus.unsubscribe('documentPageAdded'); this.eventBus.unsubscribe('resized'); let t = '#doc-toc'; - if (interact.isSet(t)) { - interact(t).unset(); - } + if (interact.isSet(t)) interact(t).unset(); + this.removeTooltips(); }, onDocumentPageAdded(pageId) { - this.send('onEntryClick', pageId); - this.setSize(); - }, - - onResize() { this.setSize(); + this.send('onGotoPage', pageId); }, setSize() { - let isDesktop = $(window).width() >= 1800; - this.set('isDesktop', isDesktop); + schedule('afterRender', () => { + let isDesktop = $(window).width() >= 1800; + this.set('isDesktop', isDesktop); - if (isDesktop) { - let h = $(window).height() - $("#nav-bar").height() - 140; - $("#doc-toc").css('max-height', h); - - let i = $("#doc-view").offset(); - - if (is.not.undefined(i)) { - let l = i.left - 100; - if (l > 350) l = 350; - $("#doc-toc").width(l); + if (isDesktop) { + let h = $(window).height() - $("#nav-bar").height() - 140; + $("#doc-toc").css('max-height', h); + + let i = $("#doc-view").offset(); + + if (is.not.undefined(i)) { + let l = i.left - 100; + if (l > 350) l = 350; + $("#doc-toc").width(l); + $("#doc-toc").css({ + 'display': 'inline-block', + 'position': 'fixed', + 'width': l+'px', + 'height': 'auto', + 'transform': '', + }); + + this.attachResizer(); + } + } else { $("#doc-toc").css({ - 'display': 'inline-block', - 'position': 'fixed', - 'width': l+'px', - 'height': 'auto', - 'transform': '', + 'display': 'block', + 'position': 'relative', + 'width': '100%', + 'height': '500px', + 'transform': 'none', }); } - } else { - $("#doc-toc").css({ - 'display': 'block', - 'position': 'relative', - 'width': '100%', - 'height': '500px', - 'transform': 'none', - }); - } + }); }, attachResizer() { schedule('afterRender', () => { + let t = '#doc-toc'; + if (interact.isSet(t)) { + interact(t).unset(); + } + interact('#doc-toc') .draggable({ autoScroll: true, @@ -166,15 +184,12 @@ export default Component.extend({ }, // Controls what user can do with the toc (left sidebar) - // Identifies the target pages setState(pageId) { - this.set('currentPageId', pageId); - let toc = this.get('pages'); - let page = _.findWhere(toc, { id: pageId }); - let state = tocUtil.getState(toc, page); + let page = _.find(toc, function(i) { return i.get('page.id') === pageId; }); + let state = tocUtil.getState(toc, is.not.undefined(page) ? page.get('page') : page); - if (!this.get('permissions.documentEdit') || is.empty(pageId)) { + if (!this.get('canEdit')) { state.actionablePage = false; state.upDisabled = state.downDisabled = state.indentDisabled = state.outdentDisabled = true; } @@ -185,77 +200,77 @@ export default Component.extend({ actions: { // Page up -- above pages shunt down pageUp() { - if (this.get('state.upDisabled')) { + let state = this.get('state'); + + if (state.upDisabled || this.get('document.protection') !== this.get('constants').ProtectionType.None) { return; } - let state = this.get('state'); let pages = this.get('pages'); - let page = _.findWhere(pages, { id: this.get('currentPageId') }); - let pendingChanges = tocUtil.moveUp(state, pages, page); + let page = _.find(pages, function(i) { return i.get('page.id') === state.pageId; }); + if (is.not.undefined(page)) page = page.get('page'); + let pendingChanges = tocUtil.moveUp(state, pages, page); if (pendingChanges.length > 0) { - this.attrs.onPageSequenceChange(pendingChanges); + let cb = this.get('onPageSequenceChange'); + cb(state.pageId, pendingChanges); } }, // Move down -- pages below shift up pageDown() { - if (this.get('state.downDisabled')) { - return; - } + if (!this.get('canEdit')) return; let state = this.get('state'); - var pages = this.get('pages'); - var page = _.findWhere(pages, { id: this.get('currentPageId') }); - let pendingChanges = tocUtil.moveDown(state, pages, page); + let pages = this.get('pages'); + let page = _.find(pages, function(i) { return i.get('page.id') === state.pageId; }); + if (is.not.undefined(page)) page = page.get('page'); + let pendingChanges = tocUtil.moveDown(state, pages, page); if (pendingChanges.length > 0) { - this.attrs.onPageSequenceChange(pendingChanges); + let cb = this.get('onPageSequenceChange'); + cb(state.pageId, pendingChanges); } }, // Indent -- changes a page from H2 to H3, etc. pageIndent() { - if (this.get('state.indentDisabled')) { - return; - } + if (!this.get('canEdit')) return; let state = this.get('state'); - var pages = this.get('pages'); - var page = _.findWhere(pages, { id: this.get('currentPageId') }); - let pendingChanges = tocUtil.indent(state, pages, page); + let pages = this.get('pages'); + let page = _.find(pages, function(i) { return i.get('page.id') === state.pageId; }); + if (is.not.undefined(page)) page = page.get('page'); + let pendingChanges = tocUtil.indent(state, pages, page); if (pendingChanges.length > 0) { - this.attrs.onPageLevelChange(pendingChanges); + let cb = this.get('onPageLevelChange'); + cb(state.pageId, pendingChanges); } }, // Outdent -- changes a page from H3 to H2, etc. pageOutdent() { - if (this.get('state.outdentDisabled')) { - return; - } - + if (!this.get('canEdit')) return; + let state = this.get('state'); - var pages = this.get('pages'); - var page = _.findWhere(pages, { id: this.get('currentPageId') }); - let pendingChanges = tocUtil.outdent(state, pages, page); + let pages = this.get('pages'); + let page = _.find(pages, function(i) { return i.get('page.id') === state.pageId; }); + if (is.not.undefined(page)) page = page.get('page'); + let pendingChanges = tocUtil.outdent(state, pages, page); if (pendingChanges.length > 0) { - this.attrs.onPageLevelChange(pendingChanges); + let cb = this.get('onPageLevelChange'); + cb(state.pageId, pendingChanges); } }, - onEntryClick(id) { - if (id !== '') { - let jumpTo = "#page-" + id; - this.set('tab', 'content'); - if (!$(jumpTo).inView()) { - $(jumpTo).velocity("scroll", { duration: 250, offset: -100 }); - } - this.setState(id); - } + onGotoPage(id) { + if (id === '') return; + this.setState(id); + + let cb = this.get('onShowPage'); + cb(id); } } }); diff --git a/gui/app/components/document/page-heading.js b/gui/app/components/document/page-heading.js index f866ee2b..5c6ead58 100644 --- a/gui/app/components/document/page-heading.js +++ b/gui/app/components/document/page-heading.js @@ -9,66 +9,72 @@ // // https://documize.com -import Component from '@ember/component'; +import $ from 'jquery'; import { computed } from '@ember/object'; +import { debounce } from '@ember/runloop'; import { inject as service } from '@ember/service'; -import { A } from "@ember/array" import ModalMixin from '../../mixins/modal'; +import Component from '@ember/component'; export default Component.extend(ModalMixin, { documentService: service('document'), + searchService: service('search'), + router: service(), deleteChildren: false, blockTitle: "", blockExcerpt: "", - documentList: A([]), //includes the current document - documentListOthers: A([]), //excludes the current document - hasMenuPermissions: computed('permissions', function() { - let permissions = this.get('permissions'); - return permissions.get('documentDelete') || permissions.get('documentCopy') || - permissions.get('documentMove') || permissions.get('documentTemplate'); + canEdit: false, + canDelete: false, + canMove: false, + docSearchFilter: '', + onKeywordChange: function () { + debounce(this, this.searchDocs, 750); + }.observes('docSearchFilter'), + emptySearch: computed('docSearchResults', function() { + return this.get('docSearchResults.length') === 0; }), + hasMenuPermissions: computed('permissions', 'userPendingItem', 'canEdit', 'canMove', 'canDelete', function() { + let permissions = this.get('permissions'); + + return permissions.get('documentCopy') || permissions.get('documentTemplate') || + this.get('canEdit') || this.get('canMove') || this.get('canDelete'); + }), + + init() { + this._super(...arguments); + this.docSearchResults = []; + }, didReceiveAttrs() { this._super(...arguments); - - // Fetch document targets once - if (this.get('documentList').length > 0) { - return; - } - this.modalInputFocus('#publish-page-modal-' + this.get('page.id'), '#block-title-' + this.get('page.id')); - this.load(); - }, - load() { - let permissions = this.get('permissions'); - if (permissions.get('documentMove') || permissions.get('documentCopy')) { - this.get('documentService').getPageMoveCopyTargets().then((d) => { - let me = this.get('document'); + }, - d.forEach((i) => { - i.set('selected', false); - }); - - if (this.get('isDestroyed') || this.get('isDestroying')) { - return; - } - - this.set('documentList', A(d)); - this.set('documentListOthers', A(d.filter((item) => item.get('id') !== me.get('id')))); - }); - } + searchDocs() { + let payload = { keywords: this.get('docSearchFilter').trim(), doc: true }; + if (payload.keywords.length == 0) return; + + this.get('searchService').find(payload).then((response)=> { + this.set('docSearchResults', response); + }); }, actions: { onEdit() { - this.attrs.onEdit(); + let page = this.get('page'); + + if (page.get('pageType') == this.get('constants').PageType.Tab) { + this.get('router').transitionTo('document.section', page.get('id')); + } else { + let cb = this.get('onEdit'); + cb(); + } }, onDeletePage() { - this.attrs.onDeletePage(this.get('deleteChildren')); - - this.load(); + let cb = this.get('onDeletePage'); + cb(this.get('deleteChildren')); this.modalClose('#delete-page-modal-' + this.get('page.id')); }, @@ -103,57 +109,60 @@ export default Component.extend(ModalMixin, { externalSource: pm.get('externalSource') }; - this.attrs.onSavePageAsBlock(block); + let cb = this.get('onSavePageAsBlock'); + cb(block); this.set('blockTitle', ''); this.set('blockExcerpt', ''); $(titleElem).removeClass('is-invalid'); $(excerptElem).removeClass('is-invalid'); - this.load(); - this.modalClose('#publish-page-modal-' + this.get('page.id')); + + let refresh = this.get('refresh'); + refresh(); }); }, + onSelectSearchResult(documentId) { + let results = this.get('docSearchResults'); + results.forEach((d) => { + d.set('selected', d.get('documentId') === documentId); + }); + this.set('docSearchResults', results); + }, + onCopyPage() { - // can't proceed if no data - if (this.get('documentList.length') === 0) { - return; - } + let item = this.get('docSearchResults').findBy('selected', true); + let documentId = is.not.undefined(item) ? item.get('documentId') : ''; - let targetDocumentId = this.get('documentList').findBy('selected', true).get('id'); - - // fall back to self - if (is.null(targetDocumentId)) { - targetDocumentId = this.get('document.id'); - } - - this.attrs.onCopyPage(targetDocumentId); - - this.load(); + if (is.empty(documentId)) return; this.modalClose('#copy-page-modal-' + this.get('page.id')); + + let cb = this.get('onCopyPage'); + cb(documentId); + + let refresh = this.get('refresh'); + refresh(); }, onMovePage() { - // can't proceed if no data - if (this.get('documentListOthers.length') === 0) { - return; - } + let item = this.get('docSearchResults').findBy('selected', true); + let documentId = is.not.undefined(item) ? item.get('documentId') : ''; - let targetDocumentId = this.get('documentListOthers').findBy('selected', true).get('id'); + if (is.empty(documentId)) return; - // fall back to first document - if (is.null(targetDocumentId)) { - targetDocumentId = this.get('documentListOthers')[0].get('id'); - } - - this.attrs.onMovePage(targetDocumentId); - - this.load(); + // can't move into self + if (documentId === this.get('document.id')) return; this.modalClose('#move-page-modal-' + this.get('page.id')); - } + + let cb = this.get('onMovePage'); + cb(documentId); + + let refresh = this.get('refresh'); + refresh(); + }, } }); diff --git a/gui/app/components/document/view-attachment.js b/gui/app/components/document/view-attachment.js index b55f02a2..1d20b712 100644 --- a/gui/app/components/document/view-attachment.js +++ b/gui/app/components/document/view-attachment.js @@ -18,24 +18,28 @@ export default Component.extend({ documentService: service('document'), appMeta: service(), hasAttachments: notEmpty('files'), - deleteAttachment: { - id: "", - name: "", - }, canShow: computed('permissions', 'files', function() { return this.get('files.length') > 0 || this.get('permissions.documentEdit'); }), + canEdit: computed('permissions', 'document.protection', function() { + return this.get('document.protection') !== this.get('constants').ProtectionType.Lock && this.get('permissions.documentEdit'); + }), showDialog: false, init() { this._super(...arguments); this.getAttachments(); + + this.deleteAttachment = { + id: "", + name: "", + }; }, didInsertElement() { this._super(...arguments); - if (!this.get('permissions.documentEdit')) { + if (!this.get('permissions.documentEdit') || this.get('document.protection') === this.get('constants').ProtectionType.Lock) { return; } @@ -78,10 +82,6 @@ export default Component.extend({ this.set('drop', dzone); }, - willDestroyElement() { - this._super(...arguments); - }, - getAttachments() { this.get('documentService').getAttachments(this.get('document.id')).then((files) => { this.set('files', files); diff --git a/gui/app/components/document/view-content.js b/gui/app/components/document/view-content.js index 1309ef2c..8c156d71 100644 --- a/gui/app/components/document/view-content.js +++ b/gui/app/components/document/view-content.js @@ -9,11 +9,14 @@ // // https://documize.com +import $ from 'jquery'; import { notEmpty, empty } from '@ember/object/computed'; import { schedule } from '@ember/runloop'; import { inject as service } from '@ember/service'; +import { computed } from '@ember/object'; import Component from '@ember/component'; import TooltipMixin from '../../mixins/tooltip'; +import models from '../../utils/model'; export default Component.extend(TooltipMixin, { documentService: service('document'), @@ -28,11 +31,15 @@ export default Component.extend(TooltipMixin, { toEdit: '', showDeleteBlockDialog: false, deleteBlockId: '', + canEdit: computed('permissions', 'document.protection', function() { + let canEdit = this.get('document.protection') !== this.get('constants').ProtectionType.Lock && this.get('permissions.documentEdit'); - didReceiveAttrs() { - this._super(...arguments); - this.loadBlocks(); - }, + if (canEdit) this.setupAddWizard(); + return canEdit; + }), + hasBlocks: computed('blocks', function() { + return this.get('blocks.length') > 0; + }), didRender() { this._super(...arguments); @@ -41,20 +48,20 @@ export default Component.extend(TooltipMixin, { didInsertElement() { this._super(...arguments); - this.setupAddWizard(); - if (this.attrs.onGotoPage !== null) { - this.attrs.onGotoPage(this.get('pageId')); + if (this.get('session.authenticated')) { + this.setupAddWizard(); + this.renderTooltips(); } - - this.renderTooltips(); }, willDestroyElement() { this._super(...arguments); - $('.start-section:not(.start-section-empty-state)').off('.hoverIntent'); - this.removeTooltips(); + if (this.get('session.authenticated')) { + $('.start-section:not(.start-section-empty-state)').off('.hoverIntent'); + this.removeTooltips(); + } }, contentLinkHandler() { @@ -73,7 +80,7 @@ export default Component.extend(TooltipMixin, { link.orphan = true; } else { if (link.linkType === "section") { - self.attrs.onGotoPage(link.targetId); + this.get('browser').scrollTo(`#page-${link.targetId}`); } } } @@ -105,80 +112,105 @@ export default Component.extend(TooltipMixin, { }, addSection(model) { - // calculate sequence of page (position in document) let sequence = 0; let level = 1; let beforePage = this.get('beforePage'); + let constants = this.get('constants'); + // calculate sequence of page (position in document) if (is.not.null(beforePage)) { level = beforePage.get('level'); // get any page before the beforePage so we can insert this new section between them - let index = _.findIndex(this.get('pages'), function(p) { return p.get('id') === beforePage.get('id'); }); + let index = _.findIndex(this.get('pages'), function(item) { return item.get('page.id') === beforePage.get('id'); }); if (index !== -1) { let beforeBeforePage = this.get('pages')[index-1]; if (is.not.undefined(beforeBeforePage)) { - sequence = (beforePage.get('sequence') + beforeBeforePage.get('sequence')) / 2; + sequence = (beforePage.get('sequence') + beforeBeforePage.get('page.sequence')) / 2; } else { sequence = beforePage.get('sequence') / 2; } + + model.page.set('sequence', sequence); + model.page.set('level', level); } } - model.page.sequence = sequence; - model.page.level = level; + if (this.get('document.protection') === constants.ProtectionType.Review) { + model.page.set('status', model.page.get('relativeId') === '' ? constants.ChangeState.PendingNew : constants.ChangeState.Pending); + } this.send('onHideSectionWizard'); return this.get('onInsertSection')(model); }, - loadBlocks() { - if (is.not.undefined(this.get('folder'))) { - this.get('sectionService').getSpaceBlocks(this.get('folder.id')).then((blocks) => { - if (this.get('isDestroyed') || this.get('isDestroying')) { - return; - } - - this.set('blocks', blocks); - this.set('hasBlocks', blocks.get('length') > 0); - }); - } - }, - actions: { onSavePageAsBlock(block) { - const promise = this.attrs.onSavePageAsBlock(block); + let cb = this.get('onSavePageAsBlock'); + const promise = cb(block); + promise.then(() => { - this.loadBlocks(); + let refresh = this.get('refresh'); + refresh(); }); }, onCopyPage(pageId, documentId) { - this.attrs.onCopyPage(pageId, documentId); + let cb = this.get('onCopyPage'); + cb(pageId, documentId); }, onMovePage(pageId, documentId) { - this.attrs.onMovePage(pageId, documentId); + let cb = this.get('onMovePage'); + cb(pageId, documentId); }, onDeletePage(params) { - this.attrs.onDeletePage(params); + let cb = this.get('onDeletePage'); + cb(params); }, onSavePage(page, meta) { - this.set('toEdit', ''); - this.attrs.onSavePage(page, meta); + let document = this.get('document'); + let constants = this.get('constants'); + + switch (document.get('protection')) { + case constants.ProtectionType.Lock: + break; + case constants.ProtectionType.Review: + // detect edits to newly created pending page + if (page.get('relativeId') === '' && page.get('status') === constants.ChangeState.PendingNew) { + // new page, edits + this.set('toEdit', ''); + let cb = this.get('onSavePage'); + cb(page, meta); + } else if (page.get('relativeId') !== '' && page.get('status') === constants.ChangeState.Published) { + // existing page, first edit + const promise = this.addSection({ page: page, meta: meta }); + promise.then((/*id*/) => { this.set('toEdit', ''); }); + } else if (page.get('relativeId') !== '' && page.get('status') === constants.ChangeState.Pending) { + // existing page, subsequent edits + this.set('toEdit', ''); + let cb = this.get('onSavePage'); + cb(page, meta); + } + break; + case constants.ProtectionType.None: + // for un-protected documents, edits welcome! + this.set('toEdit', ''); + // let cb2 = this.get('onSavePage'); + // cb2(page, meta); + this.attrs.onSavePage(page, meta); // eslint-disable-line ember/no-attrs-in-components + + break; + } }, onShowSectionWizard(page) { - if (is.undefined(page)) { - page = { id: '0' }; - } - - this.set('pageId', ''); + if (is.undefined(page)) page = { id: '0' }; let beforePage = this.get('beforePage'); if (is.not.null(beforePage) && $("#new-section-wizard").is(':visible') && beforePage.get('id') === page.id) { @@ -217,15 +249,11 @@ export default Component.extend(TooltipMixin, { return; } - let page = { - documentId: this.get('document.id'), - title: sectionName, - level: 1, - sequence: 0, // calculated elsewhere - body: "", - contentType: section.get('contentType'), - pageType: section.get('pageType') - }; + let page = models.PageModel.create(); + page.set('documentId', this.get('document.id')); + page.set('title', sectionName); + page.set('contentType', section.get('contentType')); + page.set('pageType', section.get('pageType')); let meta = { documentId: this.get('document.id'), @@ -240,14 +268,7 @@ export default Component.extend(TooltipMixin, { const promise = this.addSection(model); promise.then((id) => { - this.set('pageId', id); - - if (model.page.pageType === 'section') { - this.set('toEdit', id); - } else { - this.set('toEdit', ''); - } - + this.set('toEdit', model.page.pageType === 'section' ? id: ''); this.setupAddWizard(); }); }, @@ -263,7 +284,7 @@ export default Component.extend(TooltipMixin, { documentId: this.get('document.id'), title: `${block.get('title')}`, level: 1, - sequence: 0, // calculated elsewhere + sequence: 1024, body: block.get('body'), contentType: block.get('contentType'), pageType: block.get('pageType'), @@ -283,9 +304,7 @@ export default Component.extend(TooltipMixin, { }; const promise = this.addSection(model); - promise.then((id) => { - this.set('pageId', id); - + promise.then((/*id*/) => { this.setupAddWizard(); }); }, @@ -299,11 +318,14 @@ export default Component.extend(TooltipMixin, { this.set('showDeleteBlockDialog', false); let id = this.get('deleteBlockId'); - const promise = this.attrs.onDeleteBlock(id); + + let cb = this.get('onDeleteBlock'); + let promise = cb(id); promise.then(() => { this.set('deleteBlockId', ''); - this.loadBlocks(); + let refresh = this.get('refresh'); + refresh(); }); return true; diff --git a/gui/app/components/document/view-revision.js b/gui/app/components/document/view-revision.js index df8083ec..3189f9d1 100644 --- a/gui/app/components/document/view-revision.js +++ b/gui/app/components/document/view-revision.js @@ -16,7 +16,6 @@ import ModalMixin from '../../mixins/modal'; export default Component.extend(ModalMixin, { documentService: service('document'), - revisions: [], revision: null, diff: '', hasRevisions: computed('revisions', function() { @@ -26,6 +25,11 @@ export default Component.extend(ModalMixin, { return this.get('diff').length > 0; }), + init() { + this._super(...arguments); + this.revisions = []; + }, + didReceiveAttrs() { this._super(...arguments); this.fetchRevisions(); @@ -65,7 +69,8 @@ export default Component.extend(ModalMixin, { onRollback() { let revision = this.get('revision'); - this.attrs.onRollback(revision.pageId, revision.id); + let cb = this.get('onRollback'); + cb(revision.pageId, revision.id); this.modalClose('#document-rollback-modal'); } diff --git a/gui/app/components/focus-input.js b/gui/app/components/focus-input.js index bff5e77c..dc29b097 100644 --- a/gui/app/components/focus-input.js +++ b/gui/app/components/focus-input.js @@ -12,7 +12,8 @@ import TextField from '@ember/component/text-field'; export default TextField.extend({ - becomeFocused: function() { - this.$().focus(); - }.on('didInsertElement') + didInsertElement() { + this._super(...arguments); + this.$().focus(); + } }); \ No newline at end of file diff --git a/gui/app/components/focus-textarea.js b/gui/app/components/focus-textarea.js index 54112aca..e7edd7b4 100644 --- a/gui/app/components/focus-textarea.js +++ b/gui/app/components/focus-textarea.js @@ -12,7 +12,8 @@ import TextArea from '@ember/component/text-area'; export default TextArea.extend({ - becomeFocused: function() { + didInsertElement() { + this._super(...arguments); this.$().focus(); - }.on('didInsertElement') + } }); \ No newline at end of file diff --git a/gui/app/components/folder/category-admin.js b/gui/app/components/folder/category-admin.js index 914cf43c..c7028338 100644 --- a/gui/app/components/folder/category-admin.js +++ b/gui/app/components/folder/category-admin.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import Component from '@ember/component'; import { inject as service } from '@ember/service'; import TooltipMixin from '../../mixins/tooltip'; @@ -22,7 +23,11 @@ export default Component.extend(ModalMixin, TooltipMixin, { newCategory: '', deleteId: '', dropdown: null, - users: [], + + init() { + this._super(...arguments); + this.users = []; + }, didReceiveAttrs() { this._super(...arguments); diff --git a/gui/app/components/folder/document-tags.js b/gui/app/components/folder/document-tags.js index 8c1c237d..1a68039d 100644 --- a/gui/app/components/folder/document-tags.js +++ b/gui/app/components/folder/document-tags.js @@ -12,9 +12,6 @@ import Component from '@ember/component'; export default Component.extend({ - documentTags: [], - tagz: [], - init() { this._super(...arguments); let tagz = []; diff --git a/gui/app/components/folder/documents-list.js b/gui/app/components/folder/documents-list.js index 686b25a9..cbeee850 100644 --- a/gui/app/components/folder/documents-list.js +++ b/gui/app/components/folder/documents-list.js @@ -48,7 +48,8 @@ export default Component.extend({ this.set('selectedDocuments', A([])); this.set('showDeleteDialog', false); - this.attrs.onDeleteDocument(list); + let cb = this.get('onDeleteDocument'); + cb(list); return true; }, @@ -72,7 +73,9 @@ export default Component.extend({ this.set('showMoveDialog', false); this.set('selectedDocuments', A([])); - this.attrs.onMoveDocument(list, moveSpaceId); + + let cb = this.get('onMoveDocument'); + cb(list, moveSpaceId); return true; }, diff --git a/gui/app/components/folder/space-heading.js b/gui/app/components/folder/space-heading.js index f7b382c6..4bf9c824 100644 --- a/gui/app/components/folder/space-heading.js +++ b/gui/app/components/folder/space-heading.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import { empty } from '@ember/object/computed'; import { schedule } from '@ember/runloop'; import { inject as service } from '@ember/service'; diff --git a/gui/app/components/folder/space-view.js b/gui/app/components/folder/space-view.js index 4f992113..44812544 100644 --- a/gui/app/components/folder/space-view.js +++ b/gui/app/components/folder/space-view.js @@ -23,12 +23,16 @@ export default Component.extend(AuthMixin, { folderService: service('folder'), localStorage: service('localStorage'), hasCategories: gt('categories.length', 0), - filteredDocs: [], categoryLinkName: 'Manage', spaceSettings: computed('permissions', function() { return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage'); }), + init() { + this._super(...arguments); + this.filteredDocs = []; + }, + didReceiveAttrs() { this._super(...arguments); this.setup(); @@ -108,7 +112,8 @@ export default Component.extend(AuthMixin, { }); this.set('documents', documents); - this.attrs.onRefresh(); + let cb = this.get('onRefresh'); + cb.onRefresh(); }); }, diff --git a/gui/app/components/forgot-password.js b/gui/app/components/forgot-password.js index 10748f7e..2657cf4e 100644 --- a/gui/app/components/forgot-password.js +++ b/gui/app/components/forgot-password.js @@ -9,8 +9,8 @@ // // https://documize.com +import $ from 'jquery'; import { empty, and } from '@ember/object/computed'; - import { set } from '@ember/object'; import Component from '@ember/component'; import { isEmpty } from '@ember/utils'; diff --git a/gui/app/components/onboard/share-folder.js b/gui/app/components/onboard/share-folder.js index 014edd2e..35e791b3 100644 --- a/gui/app/components/onboard/share-folder.js +++ b/gui/app/components/onboard/share-folder.js @@ -9,8 +9,8 @@ // // https://documize.com +import $ from 'jquery'; import { inject as service } from '@ember/service'; - import Component from '@ember/component'; export default Component.extend({ diff --git a/gui/app/components/password-reset.js b/gui/app/components/password-reset.js index 64304935..236067f4 100644 --- a/gui/app/components/password-reset.js +++ b/gui/app/components/password-reset.js @@ -9,8 +9,8 @@ // // https://documize.com +import $ from 'jquery'; import { empty, and } from '@ember/object/computed'; - import Component from '@ember/component'; import { isEqual, isEmpty } from '@ember/utils'; import { set } from '@ember/object'; diff --git a/gui/app/components/search/search-results.js b/gui/app/components/search/search-results.js index 3d146c35..3bce1ced 100644 --- a/gui/app/components/search/search-results.js +++ b/gui/app/components/search/search-results.js @@ -12,9 +12,13 @@ import Component from '@ember/component'; export default Component.extend({ - results: [], resultPhrase: "", + init() { + this._super(...arguments); + this.results = []; + }, + didReceiveAttrs() { let docs = this.get('results'); let duped = []; diff --git a/gui/app/components/section/airtable/type-editor.js b/gui/app/components/section/airtable/type-editor.js index 555c2aef..00855a12 100644 --- a/gui/app/components/section/airtable/type-editor.js +++ b/gui/app/components/section/airtable/type-editor.js @@ -24,7 +24,8 @@ export default Component.extend({ }, onCancel() { - this.attrs.onCancel(); + let cb = this.get('onCancel'); + cb(); }, onAction(title) { @@ -33,7 +34,8 @@ export default Component.extend({ page.set('title', title); meta.set('rawBody', this.get("data")); - this.attrs.onAction(page, meta); + let cb = this.get('onAction'); + cb(page, meta); } } }); diff --git a/gui/app/components/section/base-editor-inline.js b/gui/app/components/section/base-editor-inline.js index 0f9f9b5c..d9c97c11 100644 --- a/gui/app/components/section/base-editor-inline.js +++ b/gui/app/components/section/base-editor-inline.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import { empty } from '@ember/object/computed'; import { computed } from '@ember/object'; import TooltipMixin from '../../mixins/tooltip'; @@ -75,21 +76,25 @@ export default Component.extend(TooltipMixin, ModalMixin, { return; } - this.attrs.onAction(this.get('page.title')); + let cb = this.get('onAction'); + cb(this.get('page.title')); }, onCancel() { - if (this.attrs.isDirty() !== null && this.attrs.isDirty()) { + let isDirty = this.get('isDirty'); + if (isDirty() !== null && isDirty()) { this.modalOpen('#discard-modal-' + this.get('page.id'), {show: true}); return; } - this.attrs.onCancel(); + let cb = this.get('onCancel'); + cb(); }, onDiscard() { this.modalClose('#discard-modal-' + this.get('page.id')); - this.attrs.onCancel(); + let cb = this.get('onCancel'); + cb(); }, onPreview() { diff --git a/gui/app/components/section/base-editor.js b/gui/app/components/section/base-editor.js index d373c02b..c21c7ca1 100644 --- a/gui/app/components/section/base-editor.js +++ b/gui/app/components/section/base-editor.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import { empty } from '@ember/object/computed'; import Component from '@ember/component'; import ModalMixin from '../../mixins/modal'; @@ -44,17 +45,21 @@ export default Component.extend(ModalMixin, { actions: { onCancel() { - if (this.attrs.isDirty() !== null && this.attrs.isDirty()) { + let isDirty = this.get('isDirty'); + if (isDirty() !== null && isDirty()) { this.modalOpen('#discard-modal', {show: true}); return; } - this.attrs.onCancel(); + let cb = this.get('onCancel'); + cb(); }, onDiscard() { this.modalClose('#discard-modal'); - this.attrs.onCancel(); + + let cb = this.get('onCancel'); + cb(); }, onAction() { @@ -72,7 +77,8 @@ export default Component.extend(ModalMixin, { return; } - this.attrs.onAction(this.get('page.title')); + let cb = this.get('onAction'); + cb(this.get('page.title')); } } }); diff --git a/gui/app/components/section/code/type-editor.js b/gui/app/components/section/code/type-editor.js index db53131a..0f512113 100644 --- a/gui/app/components/section/code/type-editor.js +++ b/gui/app/components/section/code/type-editor.js @@ -16,7 +16,7 @@ import TooltipMixin from '../../../mixins/tooltip'; export default Component.extend(TooltipMixin, { isDirty: false, pageBody: "", - syntaxOptions: [], + codeSyntax: null, codeEditor: null, editorId: computed('page', function () { @@ -29,7 +29,8 @@ export default Component.extend(TooltipMixin, { }), init() { - this._super(...arguments); + this._super(...arguments); + this.syntaxOptions = []; let self = this; let rawBody = this.get('meta.rawBody'); @@ -128,7 +129,8 @@ export default Component.extend(TooltipMixin, { }, onCancel() { - this.attrs.onCancel(); + let cb = this.get('onCancel'); + cb(); }, onAction(title) { @@ -138,7 +140,8 @@ export default Component.extend(TooltipMixin, { page.set('title', title); page.set('body', meta.get('rawBody')); - this.attrs.onAction(page, meta); + let cb = this.get('onAction'); + cb(page, meta); } } }); diff --git a/gui/app/components/section/gemini/type-editor.js b/gui/app/components/section/gemini/type-editor.js index bb347634..9cc43c7e 100644 --- a/gui/app/components/section/gemini/type-editor.js +++ b/gui/app/components/section/gemini/type-editor.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import { set } from '@ember/object'; import { schedule } from '@ember/runloop'; import { inject as service } from '@ember/service'; @@ -21,9 +22,13 @@ export default Component.extend(SectionMixin, TooltipMixin, { isDirty: false, waiting: false, authenticated: false, - user: {}, - workspaces: [], - config: {}, + + init() { + this._super(...arguments); + this.user = {}; + this.workspaces = []; + this.config = {}; + }, didReceiveAttrs() { let config = {}; @@ -195,7 +200,8 @@ export default Component.extend(SectionMixin, TooltipMixin, { }, onCancel() { - this.attrs.onCancel(); + let cb = this.get('onCancel'); + cb(); }, onAction(title) { @@ -206,7 +212,8 @@ export default Component.extend(SectionMixin, TooltipMixin, { meta.set('config', JSON.stringify(this.get('config'))); meta.set('externalSource', true); - this.attrs.onAction(page, meta); + let cb = this.get('onAction'); + cb(page, meta); } } }); diff --git a/gui/app/components/section/github/type-editor.js b/gui/app/components/section/github/type-editor.js index ef351d9e..0d3c665f 100644 --- a/gui/app/components/section/github/type-editor.js +++ b/gui/app/components/section/github/type-editor.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import EmberObject from '@ember/object'; import { A } from '@ember/array'; import { inject as service } from '@ember/service'; @@ -21,9 +22,13 @@ export default Component.extend(SectionMixin, NotifierMixin, { isDirty: false, busy: false, authenticated: false, - config: {}, owners: null, + init() { + this._super(...arguments); + this.config = {}; + }, + didReceiveAttrs() { let self = this; let page = this.get('page'); @@ -227,7 +232,8 @@ export default Component.extend(SectionMixin, NotifierMixin, { }, onCancel() { - this.attrs.onCancel(); + let cb = this.get('onCancel'); + cb(); }, onAction(title) { diff --git a/gui/app/components/section/markdown/type-editor.js b/gui/app/components/section/markdown/type-editor.js index 9bba1edb..c7169be2 100644 --- a/gui/app/components/section/markdown/type-editor.js +++ b/gui/app/components/section/markdown/type-editor.js @@ -111,7 +111,8 @@ export default Component.extend({ }, onCancel() { - this.attrs.onCancel(); + let cb = this.get('onCancel'); + cb(); }, onAction(title) { @@ -120,7 +121,8 @@ export default Component.extend({ page.set('title', title); meta.set('rawBody', this.getBody()); - this.attrs.onAction(page, meta); + let cb = this.get('onCancel'); + cb(page, meta); } } }); diff --git a/gui/app/components/section/papertrail/type-editor.js b/gui/app/components/section/papertrail/type-editor.js index 103cbc17..9e3555d7 100644 --- a/gui/app/components/section/papertrail/type-editor.js +++ b/gui/app/components/section/papertrail/type-editor.js @@ -21,8 +21,12 @@ export default Component.extend(SectionMixin, NotifierMixin, { isDirty: false, waiting: false, authenticated: false, - config: {}, - items: {}, + + init() { + this._super(...arguments); + this.config = {}; + this.items = {}; + }, didReceiveAttrs() { let config = {}; @@ -140,7 +144,8 @@ export default Component.extend(SectionMixin, NotifierMixin, { }, onCancel() { - this.attrs.onCancel(); + let cb = this.get('onCancel'); + cb(); }, onAction(title) { diff --git a/gui/app/components/section/table/type-editor.js b/gui/app/components/section/table/type-editor.js index 46e43aa5..0f1454ac 100644 --- a/gui/app/components/section/table/type-editor.js +++ b/gui/app/components/section/table/type-editor.js @@ -9,8 +9,9 @@ // // https://documize.com +import $ from 'jquery'; +import { schedule } from 'ember/runloop'; import { computed } from '@ember/object'; - import Component from '@ember/component'; export default Component.extend({ @@ -38,8 +39,10 @@ export default Component.extend({ tableResizerOffset: 10 }); - $(id).on('froalaEditor.contentChanged', () => { - this.set('isDirty', true); + schedule('afterRender', function() { + $(id).on('froalaEditor.contentChanged', () => { + this.set('isDirty', true); // eslint-disable-line ember/jquery-ember-run + }); }); }, @@ -53,7 +56,8 @@ export default Component.extend({ }, onCancel() { - this.attrs.onCancel(); + let cb = this.get('onCancel'); + cb(); }, onAction(title) { @@ -69,7 +73,8 @@ export default Component.extend({ meta.set('rawBody', body); - this.attrs.onAction(page, meta); + let cb = this.get('onAction'); + cb(page, meta); } } }); diff --git a/gui/app/components/section/trello/type-editor.js b/gui/app/components/section/trello/type-editor.js index 9cda70ea..06de3844 100644 --- a/gui/app/components/section/trello/type-editor.js +++ b/gui/app/components/section/trello/type-editor.js @@ -11,7 +11,6 @@ /*global Trello*/ import $ from 'jquery'; - import { htmlSafe } from '@ember/string'; import { computed, set } from '@ember/object'; import { inject as service } from '@ember/service'; @@ -25,11 +24,9 @@ export default Component.extend(SectionMixin, NotifierMixin, TooltipMixin, { isDirty: false, busy: false, authenticated: false, - config: {}, boards: null, noBoards: false, appKey: "", - boardStyle: computed('config.board', function () { let board = this.get('config.board'); @@ -41,6 +38,11 @@ export default Component.extend(SectionMixin, NotifierMixin, TooltipMixin, { return htmlSafe("background-color: " + color); }), + init() { + this._super(...arguments); + this.config = {}; + }, + didReceiveAttrs() { let page = this.get('page'); let config = {}; @@ -219,7 +221,8 @@ export default Component.extend(SectionMixin, NotifierMixin, TooltipMixin, { }, onCancel() { - this.attrs.onCancel(); + let cb = this.get('onCancel'); + cb(); }, onAction(title) { diff --git a/gui/app/components/section/wysiwyg/type-editor.js b/gui/app/components/section/wysiwyg/type-editor.js index 0c7044aa..fd78ebaa 100644 --- a/gui/app/components/section/wysiwyg/type-editor.js +++ b/gui/app/components/section/wysiwyg/type-editor.js @@ -9,8 +9,8 @@ // // https://documize.com +import $ from 'jquery'; import { computed, set } from '@ember/object'; - import Component from '@ember/component'; import { inject as service } from '@ember/service'; @@ -116,7 +116,8 @@ export default Component.extend({ }, onCancel() { - this.attrs.onCancel(); + let cb = this.get('onCancel'); + cb(); }, onAction(title) { @@ -127,7 +128,8 @@ export default Component.extend({ page.set('title', title); meta.set('rawBody', editor.getContent()); - this.attrs.onAction(page, meta); + let cb = this.get('onAction'); + cb(page, meta); } } }); diff --git a/gui/app/components/spaces/space-list.js b/gui/app/components/spaces/space-list.js index 885fc2d2..a9d687df 100644 --- a/gui/app/components/spaces/space-list.js +++ b/gui/app/components/spaces/space-list.js @@ -16,13 +16,17 @@ import NotifierMixin from '../../mixins/notifier'; import AuthMixin from '../../mixins/auth'; export default Component.extend(TooltipMixin, NotifierMixin, AuthMixin, { - publicFolders: [], - protectedFolders: [], - privateFolders: [], hasPublicFolders: false, hasProtectedFolders: false, hasPrivateFolders: false, + init() { + this._super(...arguments); + this.publicFolders = []; + this.protectedFolders = []; + this.privateFolders = []; + }, + didReceiveAttrs() { let folders = this.get('spaces'); let publicFolders = []; diff --git a/gui/app/components/toolbar/for-document.js b/gui/app/components/toolbar/for-document.js index c170acdf..3ad8abe0 100644 --- a/gui/app/components/toolbar/for-document.js +++ b/gui/app/components/toolbar/for-document.js @@ -9,29 +9,36 @@ // // https://documize.com -import Component from '@ember/component'; +import $ from 'jquery'; import { inject as service } from '@ember/service'; +import Component from '@ember/component'; import AuthMixin from '../../mixins/auth'; import TooltipMixin from '../../mixins/tooltip'; import ModalMixin from '../../mixins/modal'; export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, { - spaceService: service('folder'), + userSvc: service('user'), + store: service(), + spaceSvc: service('folder'), session: service(), appMeta: service(), pinned: service(), - pinState : { - isPinned: false, - pinId: '', - newName: '' - }, - saveTemplate: { - name: '', - description: '' - }, showTools: true, // show document related tools? favourite, delete, make template... showDocumentLink: false, // show link to document in breadcrumbs +init() { + this._super(...arguments); + this.pinState = { + isPinned: false, + pinId: '', + newName: '' + }; + this.saveTemplate = { + name: '', + description: '' + }; + }, + didReceiveAttrs() { this._super(...arguments); @@ -62,8 +69,9 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, { actions: { onDocumentDelete() { this.modalClose('#document-delete-modal'); - - this.attrs.onDocumentDelete(); + + let cb = this.get('onDocumentDelete'); + cb(); }, onPrintDocument() { @@ -118,7 +126,8 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, { this.set('saveTemplate.name', ''); this.set('saveTemplate.description', ''); - this.attrs.onSaveTemplate(name, excerpt); + let cb = this.get('onSaveTemplate'); + cb(name, excerpt); this.modalClose('#document-template-modal'); diff --git a/gui/app/components/toolbar/for-space.js b/gui/app/components/toolbar/for-space.js index 631ea993..51f1f443 100644 --- a/gui/app/components/toolbar/for-space.js +++ b/gui/app/components/toolbar/for-space.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import Component from '@ember/component'; import { computed } from '@ember/object'; import { schedule } from '@ember/runloop'; @@ -29,12 +30,7 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, { copyTemplate: true, copyPermission: true, copyDocument: false, - clonedSpace: { id: '' }, - pinState : { - isPinned: false, - pinId: '', - newName: '' - }, + spaceSettings: computed('permissions', function() { return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage'); }), @@ -49,10 +45,21 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, { templateDocName: '', templateDocNameError: false, selectedTemplate: '', - importedDocuments: [], - importStatus: [], + dropzone: null, + init() { + this._super(...arguments); + this.importedDocuments = []; + this.importStatus = []; + this.clonedSpace = { id: '' }; + this.pinState = { + isPinned: false, + pinId: '', + newName: '' + }; + }, + didReceiveAttrs() { this._super(...arguments); @@ -226,8 +233,8 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, { this.set('deleteSpaceName', ''); $("#delete-space-name").removeClass("is-invalid"); - this.attrs.onDeleteSpace(this.get('space.id')); - + let cb = this.get('onDeleteSpace'); + cb(this.get('space.id')); this.modalClose('#space-delete-modal'); }, @@ -330,8 +337,9 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, { this.set('importedDocuments', documents); if (documents.length === 0) { - this.modalClose("#import-doc-modal"); - this.attrs.onRefresh(); + this.modalClose("#import-doc-modal"); + let cb = this.get('onRefresh'); + cb(); } }, diff --git a/gui/app/components/toolbar/for-spaces.js b/gui/app/components/toolbar/for-spaces.js index 33403da0..e9c7c432 100644 --- a/gui/app/components/toolbar/for-spaces.js +++ b/gui/app/components/toolbar/for-spaces.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import Component from '@ember/component'; import { schedule } from '@ember/runloop'; import { notEmpty } from '@ember/object/computed'; @@ -20,9 +21,13 @@ export default Component.extend(NotifierMixin, AuthMixin, { copyTemplate: true, copyPermission: true, copyDocument: false, - clonedSpace: { id: '' }, hasClone: notEmpty('clonedSpace.id'), + init() { + this._super(...arguments); + this.clonedSpace = { id: '' }; + }, + didInsertElement() { this._super(...arguments); @@ -64,7 +69,8 @@ export default Component.extend(NotifierMixin, AuthMixin, { $('#add-space-modal').modal('hide'); $('#add-space-modal').modal('dispose'); - this.attrs.onAddSpace(payload); + let cb = this.get('onAddSpace'); + cb(payload); } } }); diff --git a/gui/app/components/toolbar/nav-bar.js b/gui/app/components/toolbar/nav-bar.js index a214f967..871feb8f 100644 --- a/gui/app/components/toolbar/nav-bar.js +++ b/gui/app/components/toolbar/nav-bar.js @@ -21,13 +21,13 @@ export default Component.extend({ store: service(), pinned: service(), enableLogout: true, - pins: [], hasPins: notEmpty('pins'), hasSpacePins: notEmpty('spacePins'), hasDocumentPins: notEmpty('documentPins'), init() { this._super(...arguments); + this.pins = []; if (this.get('appMeta.authProvider') === constants.AuthProvider.Keycloak) { let config = this.get('appMeta.authConfig'); diff --git a/gui/app/components/ui-select.js b/gui/app/components/ui-select.js index e50476d8..6f270d0b 100644 --- a/gui/app/components/ui-select.js +++ b/gui/app/components/ui-select.js @@ -15,7 +15,6 @@ import Component from '@ember/component'; export default Component.extend({ cssClass: "", - content: [], prompt: null, optionValuePath: 'id', optionLabelPath: 'name', @@ -26,6 +25,11 @@ export default Component.extend({ // leaking changes to it via a 2-way binding _selection: reads('selection'), + init() { + this._super(...arguments); + this.content = []; + }, + actions: { change() { const selectEl = this.$('select')[0]; diff --git a/gui/app/components/ui/ui-dialog.js b/gui/app/components/ui/ui-dialog.js index c9a83c22..8e6447a5 100644 --- a/gui/app/components/ui/ui-dialog.js +++ b/gui/app/components/ui/ui-dialog.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import Component from '@ember/component'; import stringUtil from '../../utils/string'; @@ -56,7 +57,8 @@ export default Component.extend({ return; } - let result = this.attrs.onAction(); + let cb = this.get('onAction'); + let result = cb(); if (result) { this.set('show', false); } diff --git a/gui/app/components/ui/ui-list-picker.js b/gui/app/components/ui/ui-list-picker.js index e508ae18..72454788 100644 --- a/gui/app/components/ui/ui-list-picker.js +++ b/gui/app/components/ui/ui-list-picker.js @@ -16,7 +16,6 @@ import { computed } from '@ember/object'; export default Component.extend({ nameField: 'category', singleSelect: false, - items: [], maxHeight: 0, onSelect: null, styleCss: computed('maxHeight', function () { @@ -29,13 +28,19 @@ export default Component.extend({ } }), + init() { + this._super(...arguments); + this.items = []; + }, + actions: { onToggle(item) { // callback takes precedence // caller sets item to 'selected' let cb = this.get('onSelect'); if (cb !== null) { - this.attrs.onSelect(item); + let cb = this.get('onSelect'); + cb(item); return; } diff --git a/gui/app/components/ui/ui-radio.js b/gui/app/components/ui/ui-radio.js index 8c908289..9a1b74c6 100644 --- a/gui/app/components/ui/ui-radio.js +++ b/gui/app/components/ui/ui-radio.js @@ -19,7 +19,8 @@ export default Component.extend({ actions: { onCheck() { if (this.get('onClick') !== null) { - this.attrs.onClick(this.get('value')); + let cb = this.get('onClick'); + cb(this.get('value')); } else { this.set('selected', !this.get('selected')); } diff --git a/gui/app/components/user-notification.js b/gui/app/components/user-notification.js index b54bc141..5865b6a4 100644 --- a/gui/app/components/user-notification.js +++ b/gui/app/components/user-notification.js @@ -14,7 +14,10 @@ import Component from '@ember/component'; import miscUtil from '../utils/misc'; export default Component.extend({ - notifications: [], + init() { + this._super(...arguments); + this.notifications = []; + }, didInsertElement() { this.eventBus.subscribe('notifyUser', this, 'showNotification'); diff --git a/gui/app/components/user-profile.js b/gui/app/components/user-profile.js index a793842a..6c004ca5 100644 --- a/gui/app/components/user-profile.js +++ b/gui/app/components/user-profile.js @@ -9,15 +9,14 @@ // // https://documize.com +import $ from 'jquery'; import { empty } from '@ember/object/computed'; - import Component from '@ember/component'; import { computed, set } from '@ember/object'; import { isPresent, isEqual, isEmpty } from '@ember/utils'; import AuthProvider from '../mixins/auth'; export default Component.extend(AuthProvider, { - password: { password: "", confirmation: "" }, hasFirstnameError: empty('model.firstname'), hasLastnameError: empty('model.lastname'), hasEmailError: computed('model.email', function() { @@ -45,6 +44,11 @@ export default Component.extend(AuthProvider, { } }), + init() { + this._super(...arguments); + this.password = { password: "", confirmation: "" }; + }, + actions: { save() { let password = this.get('password.password'); diff --git a/gui/app/constants/constants.js b/gui/app/constants/constants.js index 3aebe5bc..6d440867 100644 --- a/gui/app/constants/constants.js +++ b/gui/app/constants/constants.js @@ -12,21 +12,42 @@ import EmberObject from "@ember/object"; let constants = EmberObject.extend({ - ProtectionType: { - None: 0, - Lock: 1, - Review: 2, + // Document + ProtectionType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects + None: 0, + Lock: 1, + Review: 2, - NoneLabel: 'Changes permitted without approval', - LockLabel: 'Locked, changes not permitted', - ReviewLabel: 'Changes require approval before publication', + NoneLabel: 'Changes permitted without approval', + LockLabel: 'Locked, changes not permitted', + ReviewLabel: 'Changes require approval before publication' }, - ApprovalType: { - None: 0, - Anybody: 1, - Majority: 2, - Unanimous: 3 + // Document + ApprovalType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects + None: 0, + Anybody: 1, + Majority: 2, + Unanimous: 3, + + AnybodyLabel: 'Approval required from any approver', + MajorityLabel: 'Majority approval required from approvers', + UnanimousLabel: 'Unanimous approval required from all approvers' + }, + + // Section + ChangeState: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects + Published: 0, + Pending: 1, + UnderReview: 2, + Rejected: 3, + PendingNew: 4, + }, + + // Section + PageType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects + Tab: 'tab', + Section: 'section' } }); diff --git a/gui/app/constants/econstants.js b/gui/app/constants/econstants.js new file mode 100644 index 00000000..2dd1b625 --- /dev/null +++ b/gui/app/constants/econstants.js @@ -0,0 +1,26 @@ +// Copyright 2016 Documize Inc. . 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 . +// +// https://documize.com + +import EmberObject from "@ember/object"; + +let econstants = EmberObject.extend({ + // Document + ActionType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects + Read: 1, + Feedback: 2, + Contribute: 3, + ApprovalRequest: 4, + Approved: 5, + Rejected: 6, + }, +}); + +export default { econstants } \ No newline at end of file diff --git a/gui/app/initializers/application.js b/gui/app/initializers/application.js index 17122099..d8ab6149 100644 --- a/gui/app/initializers/application.js +++ b/gui/app/initializers/application.js @@ -9,7 +9,9 @@ // // https://documize.com +import $ from 'jquery'; import constants from '../constants/constants'; +import econstants from '../constants/econstants'; export function initialize(application) { // address insecure jquery defaults (kudos: @nathanhammond) @@ -22,7 +24,9 @@ export function initialize(application) { }); let cs = constants.constants; + let ec = econstants.econstants; application.register('constants:main', cs); + application.register('econstants:main', ec); Dropzone.autoDiscover = false; CodeMirror.modeURL = "/codemirror/mode/%N/%N.js"; diff --git a/gui/app/initializers/constants.js b/gui/app/initializers/constants.js index 234d0ea4..18c2202c 100644 --- a/gui/app/initializers/constants.js +++ b/gui/app/initializers/constants.js @@ -14,6 +14,8 @@ export function initialize(application) { application.inject('controller', 'constants', 'constants:main'); application.inject('component', 'constants', 'constants:main'); application.inject('template', 'constants', 'constants:main'); + application.inject('service', 'constants', 'constants:main'); + application.inject('model', 'constants', 'constants:main'); } export default { diff --git a/gui/app/initializers/econstants.js b/gui/app/initializers/econstants.js new file mode 100644 index 00000000..dbfcf12f --- /dev/null +++ b/gui/app/initializers/econstants.js @@ -0,0 +1,24 @@ +// Copyright 2016 Documize Inc. . 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 . +// +// https://documize.com + +export function initialize(application) { + application.inject('route', 'econstants', 'econstants:main'); + application.inject('controller', 'econstants', 'econstants:main'); + application.inject('component', 'econstants', 'econstants:main'); + application.inject('template', 'econstants', 'econstants:main'); + application.inject('service', 'econstants', 'econstants:main'); +} + +export default { + name: 'econstants', + after: "application", + initialize: initialize +}; \ No newline at end of file diff --git a/gui/app/mixins/modal.js b/gui/app/mixins/modal.js index 2335513d..6677dcfc 100644 --- a/gui/app/mixins/modal.js +++ b/gui/app/mixins/modal.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import Mixin from '@ember/object/mixin'; // ID values expected format: diff --git a/gui/app/mixins/section.js b/gui/app/mixins/section.js index ad67b222..29b18ea8 100644 --- a/gui/app/mixins/section.js +++ b/gui/app/mixins/section.js @@ -12,15 +12,15 @@ import Mixin from '@ember/object/mixin'; export default Mixin.create({ - isReadonly: function () { - if (this.get('page.userId') === this.get('session.session.authenticated.user.id')) { - return undefined; - } else { - return "readonly"; - } - }.property('page'), + // isReadonly() { + // if (this.get('page.userId') === this.get('session.session.authenticated.user.id')) { + // return undefined; + // } else { + // return "readonly"; + // } + // }.property('page'), - isMine: function () { - return this.get('page.userId') === this.get('session.session.authenticated.user.id'); - }.property('page') + // isMine() { + // return this.get('page.userId') === this.get('session.session.authenticated.user.id'); + // }.property('page') }); \ No newline at end of file diff --git a/gui/app/mixins/tooltip.js b/gui/app/mixins/tooltip.js index a9453f13..505b2914 100644 --- a/gui/app/mixins/tooltip.js +++ b/gui/app/mixins/tooltip.js @@ -9,12 +9,11 @@ // // https://documize.com +import $ from 'jquery'; import Mixin from '@ember/object/mixin'; import { schedule } from '@ember/runloop'; export default Mixin.create({ - tooltips: [], - renderTooltips() { schedule('afterRender', () => { $('[data-toggle="tooltip"]').tooltip('dispose'); @@ -24,5 +23,17 @@ export default Mixin.create({ removeTooltips() { $('[data-toggle="tooltip"]').tooltip('dispose'); + }, + + renderPopovers() { + schedule('afterRender', () => { + $('[data-toggle="popover"]').popover('dispose'); + $('body').popover({selector: '[data-toggle="popover"]', delay: 250}); + }); + }, + + removePopovers() { + $('[data-toggle="tooltip"]').popover('dispose'); } + }); diff --git a/gui/app/components/document/view-activity.js b/gui/app/models/doc-search-result.js similarity index 53% rename from gui/app/components/document/view-activity.js rename to gui/app/models/doc-search-result.js index ab2898d3..32a81e21 100644 --- a/gui/app/components/document/view-activity.js +++ b/gui/app/models/doc-search-result.js @@ -9,17 +9,20 @@ // // https://documize.com -import { inject as service } from '@ember/service'; -import Component from '@ember/component'; +import Model from 'ember-data/model'; +import attr from 'ember-data/attr'; -export default Component.extend({ - documentService: service('document'), - - didReceiveAttrs() { - this._super(...arguments); - - this.get('documentService').getActivity(this.get('document.id'), 7).then((activity) => { - this.set('activity', activity); - }); - } +export default Model.extend({ + orgId: attr(), + document: attr(), + documentId: attr(), + documentSlug: attr(), + tags: attr(), + excerpt: attr(), + itemId: attr(), + itemType: attr(), + space: attr(), + spaceId: attr(), + spaceSlug: attr(), + selected: attr() }); diff --git a/gui/app/models/document-activity.js b/gui/app/models/document-activity.js index 444adb43..2b10b159 100644 --- a/gui/app/models/document-activity.js +++ b/gui/app/models/document-activity.js @@ -19,6 +19,7 @@ export default Model.extend({ folderId: attr('string'), documentId: attr('string'), pageId: attr('string'), + pageTitle: attr('string'), userId: attr('string'), firstname: attr('string'), lastname: attr('string'), @@ -56,6 +57,9 @@ export default Model.extend({ case constants.UserActivityType.PublishedBlock: label = 'Published Block'; break; + case constants.UserActivityType.Rejected: + label = 'Rejected'; + break; default: break; } @@ -94,11 +98,13 @@ export default Model.extend({ case constants.UserActivityType.PublishedBlock: color = 'color-blue'; break; + case constants.UserActivityType.Rejected: + color = 'color-red'; + break; default: break; } return color; }) - }); diff --git a/gui/app/models/page-container.js b/gui/app/models/page-container.js new file mode 100644 index 00000000..998d0403 --- /dev/null +++ b/gui/app/models/page-container.js @@ -0,0 +1,30 @@ +// Copyright 2016 Documize Inc. . 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 . +// +// https://documize.com + +import Model from 'ember-data/model'; +import attr from 'ember-data/attr'; +// import { belongsTo, hasMany } from 'ember-data/relationships'; + +export default Model.extend({ + // page: belongsTo('page', { inverse: null }), + // meta: belongsTo('page-meta', { inverse: null }), + // pending: hasMany('page-pending', { inverse: null }), + page: attr(), + meta: attr(), + pending: attr(), + changePending: attr('boolean'), + changeAwaitingReview: attr('boolean'), + changeRejected: attr('boolean'), + userHasChangePending: attr('boolean'), + userHasChangeAwaitingReview: attr('boolean'), + userHasChangeRejected: attr('boolean'), + userHasNewPagePending: attr('boolean') +}); diff --git a/gui/app/models/page-pending.js b/gui/app/models/page-pending.js new file mode 100644 index 00000000..75eb6734 --- /dev/null +++ b/gui/app/models/page-pending.js @@ -0,0 +1,28 @@ +// Copyright 2016 Documize Inc. . 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 . +// +// https://documize.com + +import Model from 'ember-data/model'; +import attr from 'ember-data/attr'; +// import { belongsTo } from 'ember-data/relationships'; + +export default Model.extend({ + // page: belongsTo('page', { inverse: null }), + // meta: belongsTo('page-meta', { inverse: null }), + page: attr(), + meta: attr(), + owner: attr('string'), + changePending: attr('boolean'), + changeAwaitingReview: attr('boolean'), + changeRejected: attr('boolean'), + userHasChangePending: attr('boolean'), + userHasChangeAwaitingReview: attr('boolean'), + userHasChangeRejected: attr('boolean') +}); diff --git a/gui/app/models/page.js b/gui/app/models/page.js index 9f3951aa..e015c5d9 100644 --- a/gui/app/models/page.js +++ b/gui/app/models/page.js @@ -19,7 +19,7 @@ export default Model.extend({ contentType: attr('string'), pageType: attr('string'), level: attr('number', { defaultValue: 1 }), - sequence: attr('number', { defaultValue: 0 }), + sequence: attr('number', { defaultValue: 1024 }), numbering: attr('string'), revisions: attr('number', { defaultValue: 0 }), blockId: attr('string'), @@ -27,8 +27,9 @@ export default Model.extend({ body: attr('string'), rawBody: attr('string'), meta: attr(), - approval: attr('number', { defaultValue: 0 }), + status: attr('number', { defaultValue: 0 }), relativeId: attr('string'), + userId: attr('string'), tagName: computed('level', function () { return "h2"; @@ -48,5 +49,16 @@ export default Model.extend({ }), created: attr(), - revised: attr() + revised: attr(), + + // is this a new page that is pending and belongs to the user? + isNewPageUserPending(userId) { + return this.get('relativeId') === '' && this.get('userId') === userId && ( + this.get('status') === this.get('constants').ChangeState.PendingNew || this.get('status') === this.get('constants').ChangeState.UnderReview); + }, + + // is this new page ready for review? + isNewPageReviewReady() { + return this.get('relativeId') === '' && this.get('status') === this.get('constants').ChangeState.UnderReview; + } }); diff --git a/gui/app/models/user-activity.js b/gui/app/models/user-activity.js index de6b3cef..a84add03 100644 --- a/gui/app/models/user-activity.js +++ b/gui/app/models/user-activity.js @@ -15,10 +15,13 @@ import attr from 'ember-data/attr'; export default Model.extend({ documentName: attr('string'), + documentId: attr('string'), folderId: attr('string'), contributed: attr('string'), viewed: attr('string'), created: attr('string'), + approved: attr('string'), + rejected: attr('string'), hasContributed: computed('contributed', function () { return this.get('contributed').length > 0; @@ -28,5 +31,11 @@ export default Model.extend({ }), hasCreated: computed('created', function () { return this.get('created').length > 0; - }) + }), + hasApproved: computed('approved', function () { + return this.get('approved').length > 0; + }), + hasRejected: computed('rejected', function () { + return this.get('rejected').length > 0; + }) }); diff --git a/gui/app/pods/auth/forgot/route.js b/gui/app/pods/auth/forgot/route.js index 6c744df0..b9b27b08 100644 --- a/gui/app/pods/auth/forgot/route.js +++ b/gui/app/pods/auth/forgot/route.js @@ -9,8 +9,8 @@ // // https://documize.com +import $ from 'jquery'; import { inject as service } from '@ember/service'; - import Route from '@ember/routing/route'; import constants from '../../../utils/constants'; diff --git a/gui/app/pods/auth/login/route.js b/gui/app/pods/auth/login/route.js index cbb65871..9b5f9cc0 100644 --- a/gui/app/pods/auth/login/route.js +++ b/gui/app/pods/auth/login/route.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import { Promise as EmberPromise } from 'rsvp'; import { inject as service } from '@ember/service'; import Route from '@ember/routing/route'; diff --git a/gui/app/pods/auth/reset/route.js b/gui/app/pods/auth/reset/route.js index 25426ac3..93a2aaca 100644 --- a/gui/app/pods/auth/reset/route.js +++ b/gui/app/pods/auth/reset/route.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import Route from '@ember/routing/route'; export default Route.extend({ diff --git a/gui/app/pods/auth/share/route.js b/gui/app/pods/auth/share/route.js index dfe56c4f..5ad520c3 100644 --- a/gui/app/pods/auth/share/route.js +++ b/gui/app/pods/auth/share/route.js @@ -9,8 +9,8 @@ // // https://documize.com +import $ from 'jquery'; import { inject as service } from '@ember/service'; - import Route from '@ember/routing/route'; import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin'; diff --git a/gui/app/pods/customize/folders/controller.js b/gui/app/pods/customize/folders/controller.js index aa69c532..4cc6571e 100644 --- a/gui/app/pods/customize/folders/controller.js +++ b/gui/app/pods/customize/folders/controller.js @@ -9,27 +9,33 @@ // // https://documize.com +import $ from 'jquery'; +import { computed } from '@ember/computed'; import { inject as service } from '@ember/service'; import Controller from '@ember/controller'; import TooltipMixin from '../../../mixins/tooltip'; export default Controller.extend(TooltipMixin, { folderService: service('folder'), - folders: [], dropdown: null, - deleteSpace: { - id: '', - name: '' + + init() { + this._super(...arguments); + this.folders = []; + this.deleteSpace = { + id: '', + name: '' + }; }, - label: function () { + label: computed('folders', function() { switch (this.get('folders').length) { case 1: return "space"; default: return "spaces"; } - }.property('folders'), + }), actions: { onShow(id) { diff --git a/gui/app/pods/customize/users/controller.js b/gui/app/pods/customize/users/controller.js index ee40fa83..7c81f6c6 100644 --- a/gui/app/pods/customize/users/controller.js +++ b/gui/app/pods/customize/users/controller.js @@ -15,7 +15,11 @@ import Controller from '@ember/controller'; export default Controller.extend({ userService: service('user'), - newUser: { firstname: "", lastname: "", email: "", active: true }, + + init() { + this._super(...arguments); + this.newUser = { firstname: "", lastname: "", email: "", active: true }; + }, actions: { add(user) { diff --git a/gui/app/pods/document/controller.js b/gui/app/pods/document/controller.js index df6a9453..fe1fb43c 100644 --- a/gui/app/pods/document/controller.js +++ b/gui/app/pods/document/controller.js @@ -10,7 +10,6 @@ // https://documize.com import Controller from '@ember/controller'; -import NotifierMixin from '../../mixins/notifier'; -export default Controller.extend(NotifierMixin, { +export default Controller.extend({ }); diff --git a/gui/app/pods/document/index/controller.js b/gui/app/pods/document/index/controller.js index eea3a626..e9722869 100644 --- a/gui/app/pods/document/index/controller.js +++ b/gui/app/pods/document/index/controller.js @@ -19,8 +19,7 @@ export default Controller.extend(TooltipMixin, { templateService: service('template'), sectionService: service('section'), linkService: service('link'), - queryParams: ['pageId', 'tab'], - pageId: '', + currentPageId: '', tab: 'content', actions: { @@ -28,9 +27,13 @@ export default Controller.extend(TooltipMixin, { this.set('tab', tab); }, + onShowPage(pageId) { + this.set('tab', 'content'); + this.get('browser').scrollTo(`#page-${pageId}`); + }, + onSaveDocument(doc) { this.get('documentService').save(doc); - this.get('browser').setTitle(doc.get('name')); this.get('browser').setMetaDescription(doc.get('excerpt')); }, @@ -60,19 +63,26 @@ export default Controller.extend(TooltipMixin, { }, onSavePage(page, meta) { - let documentId = this.get('document.id'); + let document = this.get('document'); + let documentId = document.get('id'); + let constants = this.get('constants'); + + // if document approval mode is locked return + if (document.get('protection') == constants.ProtectionType.Lock) { + // should not really happen + return; + } + + // Go ahead and save edits as normal let model = { page: page.toJSON({ includeId: true }), meta: meta.toJSON({ includeId: true }) }; - this.get('documentService').updatePage(documentId, page.get('id'), model).then((up) => { - this.set('pageId', up.get('id')); - - this.get('documentService').getPages(this.get('document.id')).then((pages) => { + this.get('documentService').updatePage(documentId, page.get('id'), model).then((/*up*/) => { + this.get('documentService').fetchPages(documentId, this.get('session.user.id')).then((pages) => { this.set('pages', pages); - - this.get('linkService').getDocumentLinks(this.get('document.id')).then((links) => { + this.get('linkService').getDocumentLinks(documentId).then((links) => { this.set('links', links); }); }); @@ -81,55 +91,38 @@ export default Controller.extend(TooltipMixin, { onPageDeleted(deletePage) { let documentId = this.get('document.id'); - let pages = this.get('pages'); let deleteId = deletePage.id; let deleteChildren = deletePage.children; - let page = _.findWhere(pages, { - id: deleteId - }); - let pageIndex = _.indexOf(pages, page, false); let pendingChanges = []; + + let pages = this.get('pages'); + let pageIndex = _.findIndex(pages, function(i) { return i.get('page.id') === deleteId; }); + let item = pages[pageIndex]; // select affected pages for (var i = pageIndex + 1; i < pages.get('length'); i++) { - if (pages[i].get('level') <= page.get('level')) { - break; - } + if (i === pageIndex + 1 && pages[i].get('page.level') === item.get('page.level')) break; + if (pages[i].get('page.level') <= item.get('page.level')) break; - pendingChanges.push({ - pageId: pages[i].get('id'), - level: pages[i].get('level') - 1 - }); + pendingChanges.push({ pageId: pages[i].get('page.id'), level: pages[i].get('page.level') - 1 }); } - this.set('pageId', ''); - if (deleteChildren) { - // nuke of page tree - pendingChanges.push({ - pageId: deleteId - }); + pendingChanges.push({ pageId: deleteId }); this.get('documentService').deletePages(documentId, deleteId, pendingChanges).then(() => { - // update our models so we don't have to reload from db - for (var i = 0; i < pendingChanges.length; i++) { - let pageId = pendingChanges[i].pageId; - this.set('pages', _.reject(pages, function (p) { //jshint ignore: line - return p.get('id') === pageId; - })); - } - - this.set('pages', _.sortBy(pages, "sequence")); - this.get('target._routerMicrolib').refresh(); + // this.send('onPageLevelChange', '', pendingChanges); + this.get('documentService').fetchPages(this.get('document.id'), this.get('session.user.id')).then((pages) => { + this.set('pages', pages); + }); }); } else { // page delete followed by re-leveling child pages this.get('documentService').deletePage(documentId, deleteId).then(() => { - this.set('pages', _.reject(pages, function (p) { - return p.get('id') === deleteId; - })); - - this.send('onPageLevelChange', pendingChanges); + // this.send('onPageLevelChange', '', pendingChanges); + this.get('documentService').fetchPages(this.get('document.id'), this.get('session.user.id')).then((pages) => { + this.set('pages', pages); + }); }); } }, @@ -139,10 +132,10 @@ export default Controller.extend(TooltipMixin, { this.get('documentService').addPage(this.get('document.id'), data).then((newPage) => { let data = this.get('store').normalize('page', newPage); this.get('store').push(data); - this.set('pageId', newPage.id); - this.get('documentService').getPages(this.get('document.id')).then((pages) => { + this.get('documentService').fetchPages(this.get('document.id'), this.get('session.user.id')).then((pages) => { this.set('pages', pages); + this.eventBus.publish('documentPageAdded', newPage.id); if (newPage.pageType === 'tab') { this.transitionToRoute('document.section', @@ -185,34 +178,24 @@ export default Controller.extend(TooltipMixin, { this.get('templateService').saveAsTemplate(this.get('document.id'), name, desc).then(function () {}); }, - onPageSequenceChange(changes) { + onPageSequenceChange(currentPageId, changes) { + this.set('currentPageId', currentPageId); this.get('documentService').changePageSequence(this.get('document.id'), changes).then(() => { - this.get('documentService').getPages(this.get('document.id')).then( (pages) => { + this.get('documentService').fetchPages(this.get('document.id'), this.get('session.user.id')).then( (pages) => { this.set('pages', pages); }); }); }, - onPageLevelChange(changes) { + onPageLevelChange(currentPageId, changes) { + this.set('currentPageId', currentPageId); this.get('documentService').changePageLevel(this.get('document.id'), changes).then(() => { - this.get('documentService').getPages(this.get('document.id')).then( (pages) => { + this.get('documentService').fetchPages(this.get('document.id'), this.get('session.user.id')).then( (pages) => { this.set('pages', pages); }); }); }, - onGotoPage(id) { - if (id !== '') { - this.set('pageId', id); - this.set('tab', 'content'); - - let jumpTo = "#page-" + id; - if (!$(jumpTo).inView()) { - $(jumpTo).velocity("scroll", { duration: 250, offset: -100 }); - } - } - }, - onTagChange(tags) { let doc = this.get('document'); doc.set('tags', tags); @@ -224,6 +207,16 @@ export default Controller.extend(TooltipMixin, { this.set('tab', 'content'); this.get('target._routerMicrolib').refresh(); }); - } + }, + + refresh() { + this.get('documentService').fetchPages(this.get('document.id'), this.get('session.user.id')).then((data) => { + this.set('pages', data); + }); + + this.get('sectionService').getSpaceBlocks(this.get('folder.id')).then((data) => { + this.set('blocks', data); + }); + } } }); diff --git a/gui/app/pods/document/index/route.js b/gui/app/pods/document/index/route.js index a095ea9d..bf18cffb 100644 --- a/gui/app/pods/document/index/route.js +++ b/gui/app/pods/document/index/route.js @@ -9,7 +9,7 @@ // // https://documize.com -import { hash } from 'rsvp'; +import { Promise as EmberPromise, 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'; @@ -20,6 +20,15 @@ export default Route.extend(AuthenticatedRouteMixin, { folderService: service('folder'), userService: service('user'), + beforeModel() { + return new EmberPromise((resolve) => { + this.get('documentService').fetchPages(this.paramsFor('document').document_id, this.get('session.user.id')).then((data) => { + this.set('pages', data); + resolve(); + }); + }); + }, + model() { let document = this.modelFor('document').document; this.browser.setTitle(document.get('name')); @@ -29,11 +38,12 @@ export default Route.extend(AuthenticatedRouteMixin, { folders: this.modelFor('document').folders, folder: this.modelFor('document').folder, document: this.modelFor('document').document, - pages: this.get('documentService').getPages(this.modelFor('document').document.get('id')), + pages: this.get('pages'), links: this.modelFor('document').links, sections: this.modelFor('document').sections, permissions: this.modelFor('document').permissions, - roles: this.modelFor('document').roles + roles: this.modelFor('document').roles, + blocks: this.modelFor('document').blocks }); }, @@ -46,5 +56,6 @@ export default Route.extend(AuthenticatedRouteMixin, { controller.set('sections', model.sections); controller.set('permissions', model.permissions); controller.set('roles', model.roles); + controller.set('blocks', model.blocks); } }); diff --git a/gui/app/pods/document/index/template.hbs b/gui/app/pods/document/index/template.hbs index 7153a4e4..def0c8cd 100644 --- a/gui/app/pods/document/index/template.hbs +++ b/gui/app/pods/document/index/template.hbs @@ -1,9 +1,10 @@ {{toolbar/nav-bar}} -{{toolbar/for-document document=document spaces=folders space=folder permissions=permissions roles=roles +{{toolbar/for-document document=document spaces=folders space=folder permissions=permissions roles=roles tab=tab onDocumentDelete=(action 'onDocumentDelete') onSaveTemplate=(action 'onSaveTemplate') - onSaveDocument=(action 'onSaveDocument')}} + onSaveDocument=(action 'onSaveDocument') + refresh=(action 'refresh')}}
@@ -11,26 +12,25 @@ {{document/document-heading document=document permissions=permissions onSaveDocument=(action 'onSaveDocument')}} - {{document/document-meta document=document folder=folder folders=folders permissions=permissions + {{document/document-meta document=document folder=folder folders=folders permissions=permissions pages=pages onSaveDocument=(action 'onSaveDocument')}}
{{document/document-toc document=document folder=folder pages=pages page=page - permissions=permissions roles=roles currentPageId=pageId tab=tab - onPageSequenceChange=(action 'onPageSequenceChange') onPageLevelChange=(action 'onPageLevelChange') onGotoPage=(action 'onGotoPage')}} + permissions=permissions roles=roles tab=tab currentPageId=currentPageId onShowPage=(action 'onShowPage') + onPageSequenceChange=(action 'onPageSequenceChange') onPageLevelChange=(action 'onPageLevelChange')}}
-
+
  • Content
  • Attachments
  • {{#if session.authenticated}} -
  • Activity
  • Revisions
  • {{/if}}
@@ -38,20 +38,17 @@ {{#if (eq tab 'content')}} {{document/view-content - document=document links=links pages=pages - folder=folder folders=folders sections=sections permissions=permissions pageId=pageId + document=document links=links pages=pages blocks=blocks + folder=folder folders=folders sections=sections permissions=permissions roles=roles onSavePage=(action 'onSavePage') onInsertSection=(action 'onInsertSection') - onSavePageAsBlock=(action 'onSavePageAsBlock') onDeleteBlock=(action 'onDeleteBlock') onGotoPage=(action 'onGotoPage') - onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onPageDeleted')}} + onSavePageAsBlock=(action 'onSavePageAsBlock') onDeleteBlock=(action 'onDeleteBlock') + onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onPageDeleted') refresh=(action 'refresh')}} {{/if}} {{#if (eq tab 'attachment')}} {{document/view-attachment document=document permissions=permissions}} {{/if}} - {{#if (eq tab 'activity')}} - {{document/view-activity document=document pages=pages permissions=permissions}} - {{/if}} {{#if (eq tab 'revision')}} {{document/view-revision document=document folder=folder pages=pages onRollback=(action 'onRollback')}} diff --git a/gui/app/pods/document/route.js b/gui/app/pods/document/route.js index 1e6b55d1..aea59b93 100644 --- a/gui/app/pods/document/route.js +++ b/gui/app/pods/document/route.js @@ -20,8 +20,7 @@ export default Route.extend(AuthenticatedRouteMixin, { folderService: service('folder'), linkService: service('link'), - beforeModel(transition) { - this.set('pageId', is.not.undefined(transition.queryParams.page) ? transition.queryParams.page : ""); + beforeModel() { this.set('folderId', this.paramsFor('document').folder_id); this.set('documentId', this.paramsFor('document').document_id); @@ -43,11 +42,11 @@ export default Route.extend(AuthenticatedRouteMixin, { folders: this.get('folders'), folder: this.get('folder'), document: this.get('document'), - page: this.get('pageId'), permissions: this.get('permissions'), roles: this.get('roles'), links: this.get('links'), - sections: this.get('sectionService').getAll() + sections: this.get('sectionService').getAll(), + blocks: this.get('sectionService').getSpaceBlocks(this.get('folder.id')) }); }, diff --git a/gui/app/pods/folder/route.js b/gui/app/pods/folder/route.js index 709b4688..41fac938 100644 --- a/gui/app/pods/folder/route.js +++ b/gui/app/pods/folder/route.js @@ -10,7 +10,6 @@ // https://documize.com import { Promise as EmberPromise, 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'; @@ -20,7 +19,6 @@ export default Route.extend(AuthenticatedRouteMixin, { folderService: service('folder'), templateService: service('template'), session: service(''), - folder: {}, beforeModel() { this.set('folderId', this.paramsFor('folder').folder_id) diff --git a/gui/app/pods/search/controller.js b/gui/app/pods/search/controller.js index 52440a13..311136ac 100644 --- a/gui/app/pods/search/controller.js +++ b/gui/app/pods/search/controller.js @@ -17,32 +17,35 @@ export default Controller.extend({ searchService: service('search'), queryParams: ['filter', 'matchDoc', 'matchContent', 'matchTag', 'matchFile'], filter: '', - results: [], - matchDoc: true, - matchContent: true, - matchFile: false, - matchTag: false, - onKeywordChange: function () { debounce(this, this.fetch, 750); }.observes('filter'), + matchDoc: true, onMatchDoc: function () { debounce(this, this.fetch, 750); }.observes('matchDoc'), + matchContent: true, onMatchContent: function () { debounce(this, this.fetch, 750); }.observes('matchContent'), + matchTag: false, onMatchTag: function () { debounce(this, this.fetch, 750); }.observes('matchTag'), + matchFile: false, onMatchFile: function () { debounce(this, this.fetch, 750); }.observes('matchFile'), + init() { + this._super(...arguments); + this.results = []; + }, + fetch() { let self = this; let payload = { diff --git a/gui/app/pods/setup/route.js b/gui/app/pods/setup/route.js index bb999212..79ac5421 100644 --- a/gui/app/pods/setup/route.js +++ b/gui/app/pods/setup/route.js @@ -9,6 +9,7 @@ // // https://documize.com +import $ from 'jquery'; import Route from '@ember/routing/route'; export default Route.extend({ diff --git a/gui/app/router.js b/gui/app/router.js index 4e080031..e2fae3c5 100644 --- a/gui/app/router.js +++ b/gui/app/router.js @@ -21,6 +21,10 @@ export default Router.map(function () { path: '/' }); + this.route('dashboard', { + path: 'dashboard' + }); + this.route('folder', { path: 's/:folder_id/:folder_slug' }, function() { @@ -61,6 +65,9 @@ export default Router.map(function () { this.route('auth', { path: 'auth' }); + this.route('audit', { + path: 'audit' + }); }); this.route('setup', { diff --git a/gui/app/serializers/page-container.js b/gui/app/serializers/page-container.js new file mode 100644 index 00000000..bdc8cff8 --- /dev/null +++ b/gui/app/serializers/page-container.js @@ -0,0 +1,13 @@ +import ApplicationSerializer from './application'; + +export default ApplicationSerializer.extend({ + normalize(modelClass, resourceHash) { + return { + data: { + id: resourceHash.id ? resourceHash.id : 0, + type: modelClass.modelName, + attributes: resourceHash + } + }; + } +}); diff --git a/gui/app/serializers/page-pending.js b/gui/app/serializers/page-pending.js new file mode 100644 index 00000000..bdc8cff8 --- /dev/null +++ b/gui/app/serializers/page-pending.js @@ -0,0 +1,13 @@ +import ApplicationSerializer from './application'; + +export default ApplicationSerializer.extend({ + normalize(modelClass, resourceHash) { + return { + data: { + id: resourceHash.id ? resourceHash.id : 0, + type: modelClass.modelName, + attributes: resourceHash + } + }; + } +}); diff --git a/gui/app/services/activity.js b/gui/app/services/activity.js index a02f53e3..f822c0ea 100644 --- a/gui/app/services/activity.js +++ b/gui/app/services/activity.js @@ -12,33 +12,15 @@ import Service, { inject as service } from '@ember/service'; export default Service.extend({ - sessionService: service('session'), ajax: service(), - store: service(), - - // document number of views, edits, approvals, etc. - getDocumentSummary(documentId, days) { - return this.get('ajax').request(`activity/document/${documentId}?days=${days}`, { + + getDocumentSummary(documentId) { + return this.get('ajax').request(`activity/document/${documentId}`, { method: "GET" }).then((response) => { - let data = { - viewers: [], - changers: [] - }; - - data.viewers = response.viewers.map((obj) => { - let data = this.get('store').normalize('documentActivity', obj); - return this.get('store').push(data); - }); - - data.changers = response.changers.map((obj) => { - let data = this.get('store').normalize('documentActivity', obj); - return this.get('store').push(data); - }); - - return data; + return response; }).catch(() => { return []; }); - }, + } }); diff --git a/gui/app/services/browser.js b/gui/app/services/browser.js index 5e8a0f24..d504fc8b 100644 --- a/gui/app/services/browser.js +++ b/gui/app/services/browser.js @@ -9,7 +9,9 @@ // // https://documize.com +import $ from 'jquery'; import Service, { inject as service } from '@ember/service'; +import { schedule } from '@ember/runloop'; export default Service.extend({ sessionService: service('session'), @@ -42,5 +44,17 @@ export default Service.extend({ } $('head').append(''); + }, + + scrollTo(id) { + schedule('afterRender', () => { + let elem = $(id).offset(); + + if (is.undefined(elem)) return; + + $('html, body').animate({ + scrollTop: elem.top + }, 250); + }); } }); \ No newline at end of file diff --git a/gui/app/services/document.js b/gui/app/services/document.js index 97977433..37372e65 100644 --- a/gui/app/services/document.js +++ b/gui/app/services/document.js @@ -52,6 +52,8 @@ export default Service.extend({ }); return documents; + }).catch((error) => { + return error; }); }, @@ -100,6 +102,8 @@ export default Service.extend({ }).then((response) => { let data = this.get('store').normalize('page', response); return this.get('store').push(data); + }).catch((error) => { + return error; }); }, @@ -121,7 +125,6 @@ export default Service.extend({ // Returns document page with content getPage(documentId, pageId) { - return this.get('ajax').request(`documents/${documentId}/pages/${pageId}`, { method: 'GET' }).then((response) => { @@ -132,7 +135,6 @@ export default Service.extend({ // Returns document page meta object getPageMeta(documentId, pageId) { - return this.get('ajax').request(`documents/${documentId}/pages/${pageId}/meta`, { method: 'GET' }).then((response) => { @@ -203,34 +205,12 @@ export default Service.extend({ }); }, - //************************************************** - // Activity - //************************************************** - - // document meta referes to number of views, edits, approvals, etc. - getActivity(documentId, days) { - return this.get('ajax').request(`documents/${documentId}/activity?days=${days}`, { - method: "GET" - }).then((response) => { - let data = []; - data = response.map((obj) => { - let data = this.get('store').normalize('documentActivity', obj); - return this.get('store').push(data); - }); - - return data; - }).catch(() => { - return []; - }); - }, - //************************************************** // Table of contents //************************************************** // Returns all pages without the content getTableOfContents(documentId) { - return this.get('ajax').request(`documents/${documentId}/pages?content=0`, { method: 'GET' }).then((response) => { @@ -268,7 +248,6 @@ export default Service.extend({ // document attachments without the actual content getAttachments(documentId) { - return this.get('ajax').request(`documents/${documentId}/attachments`, { method: 'GET' }).then((response) => { @@ -380,8 +359,108 @@ export default Service.extend({ data.links = response.links; return data; + }).catch((error) => { + return error; }); + }, + // fetchPages returns all pages, page meta and pending changes for document. + // This method bulk fetches data to reduce network chatter. + // We produce a bunch of calculated boolean's for UI display purposes + // that can tell us quickly about pending changes for UI display. + fetchPages(documentId, currentUserId) { + let constants = this.get('constants'); + let changePending = false; + let changeAwaitingReview = false; + let changeRejected = false; + let userHasChangePending = false; + let userHasChangeAwaitingReview = false; + let userHasChangeRejected = false; + + return this.get('ajax').request(`fetch/page/${documentId}`, { + method: 'GET' + }).then((response) => { + let data = A([]); + + response.forEach((page) => { + changePending = false; + changeAwaitingReview = false; + changeRejected = false; + userHasChangePending = false; + userHasChangeAwaitingReview = false; + userHasChangeRejected = false; + + let p = this.get('store').normalize('page', page.page); + p = this.get('store').push(p); + + let m = this.get('store').normalize('page-meta', page.meta); + m = this.get('store').push(m); + + let pending = A([]); + page.pending.forEach((i) => { + let p = this.get('store').normalize('page', i.page); + p = this.get('store').push(p); + + let m = this.get('store').normalize('page-meta', i.meta); + m = this.get('store').push(m); + + let belongsToMe = p.get('userId') === currentUserId; + let pageStatus = p.get('status'); + + let pi = { + id: p.get('id'), + page: p, + meta: m, + owner: i.owner, + changePending: pageStatus === constants.ChangeState.Pending || pageStatus === constants.ChangeState.PendingNew, + changeAwaitingReview: pageStatus === constants.ChangeState.UnderReview, + changeRejected: pageStatus === constants.ChangeState.Rejected, + userHasChangePending: belongsToMe && (pageStatus === constants.ChangeState.Pending || pageStatus === constants.ChangeState.PendingNew), + userHasChangeAwaitingReview: belongsToMe && pageStatus === constants.ChangeState.UnderReview, + userHasChangeRejected: belongsToMe && pageStatus === constants.ChangeState.Rejected + }; + + let pim = this.get('store').normalize('page-pending', pi); + pim = this.get('store').push(pim); + pending.pushObject(pim); + + if (p.get('status') === constants.ChangeState.Pending || p.get('status') === constants.ChangeState.PendingNew) { + changePending = true; + userHasChangePending = belongsToMe; + } + if (p.get('status') === constants.ChangeState.UnderReview) { + changeAwaitingReview = true; + userHasChangeAwaitingReview = belongsToMe; + } + if (p.get('status') === constants.ChangeState.Rejected) { + changeRejected = p.get('status') === constants.ChangeState.Rejected; + userHasChangeRejected = changeRejected && belongsToMe; + } + }); + + let pi = { + id: p.get('id'), + page: p, + meta: m, + pending: pending, + changePending: changePending, + changeAwaitingReview: changeAwaitingReview, + changeRejected: changeRejected, + userHasChangePending: userHasChangePending, + userHasChangeAwaitingReview: userHasChangeAwaitingReview, + userHasChangeRejected: userHasChangeRejected, + userHasNewPagePending: p.isNewPageUserPending(this.get('sessionService.user.id')) + }; + + let pim = this.get('store').normalize('page-container', pi); + pim = this.get('store').push(pim); + data.pushObject(pim); + }); + + return data; + }).catch((error) => { + return error; + }); } }); diff --git a/gui/app/services/folder.js b/gui/app/services/folder.js index ed27dff4..71e7e284 100644 --- a/gui/app/services/folder.js +++ b/gui/app/services/folder.js @@ -19,10 +19,12 @@ export default BaseService.extend({ ajax: service(), localStorage: service(), store: service(), - - // selected folder currentFolder: null, - permissions: {}, + + init() { + this._super(...arguments); + this.permissions = {}; + }, // Add a new folder. add(payload) { diff --git a/gui/app/services/kc-auth.js b/gui/app/services/kc-auth.js index 3b9511b0..253c7858 100644 --- a/gui/app/services/kc-auth.js +++ b/gui/app/services/kc-auth.js @@ -10,7 +10,6 @@ // https://documize.com import { Promise as EmberPromise } from 'rsvp'; - import Service, { inject as service } from '@ember/service'; import netUtil from '../utils/net'; @@ -19,7 +18,11 @@ export default Service.extend({ ajax: service(), appMeta: service(), keycloak: null, - config: {}, + + init() { + this._super(...arguments); + this.config = {}; + }, boot() { return new EmberPromise((resolve, reject) => { diff --git a/gui/app/services/pinned.js b/gui/app/services/pinned.js index 5918c829..5c4a5c66 100644 --- a/gui/app/services/pinned.js +++ b/gui/app/services/pinned.js @@ -19,9 +19,13 @@ export default Service.extend({ ajax: service(), appMeta: service(), store: service(), - pins: [], initialized: false, + init() { + this._super(...arguments); + this.pins = []; + }, + getUserPins() { let userId = this.get('session.user.id'); diff --git a/gui/app/services/search.js b/gui/app/services/search.js index ca1b2a8a..a66dbd01 100644 --- a/gui/app/services/search.js +++ b/gui/app/services/search.js @@ -10,17 +10,33 @@ // https://documize.com import Service, { inject as service } from '@ember/service'; +import { A } from '@ember/array'; +import ArrayProxy from '@ember/array/proxy'; export default Service.extend({ sessionService: service('session'), ajax: service(), + store: service(), - // find all matching documents. + // find all matching documents find(payload) { return this.get('ajax').request("search", { method: "POST", data: JSON.stringify(payload), contentType: 'json' + }).then((response) => { + let results = ArrayProxy.create({ + content: A([]) + }); + + results = response.map((doc) => { + let data = this.get('store').normalize('doc-search-result', doc); + return this.get('store').push(data); + }); + + return results; + }).catch((error) => { + return error; }); }, -}); +}); \ No newline at end of file diff --git a/gui/app/services/session.js b/gui/app/services/session.js index be40b39c..4aec5af2 100644 --- a/gui/app/services/session.js +++ b/gui/app/services/session.js @@ -40,6 +40,7 @@ export default SimpleAuthSession.extend({ }), authenticated: computed('session.content.authenticated.user', function () { + if (is.null(this.get('session.authenticator'))) return false; return this.get('session.authenticator') !== 'authenticator:anonymous' && this.get('session.content.authenticated.user.id') !== '0'; }), diff --git a/gui/app/styles/view/document/copy-move.scss b/gui/app/styles/view/document/copy-move.scss new file mode 100644 index 00000000..70af570c --- /dev/null +++ b/gui/app/styles/view/document/copy-move.scss @@ -0,0 +1,55 @@ +.document-copy-move { + > .documents-list { + margin: 0; + padding: 0; + width: 100%; + + > .document { + @include ease-in(); + margin: 0 0 5px 0; + padding: 10px 15px; + color: $color-gray; + background-color: $color-off-white; + cursor: pointer; + position: relative; + list-style-type: none; + + &:hover { + color: $color-black; + } + + > .title { + color : $color-off-black; + font-size: 1rem; + } + + > .space { + color : $color-gray; + font-size: 0.8rem; + font-style: italic; + font-weight: bold; + margin-bottom: 10px; + } + + > .snippet { + color : $color-gray; + font-size: 0.9rem; + } + + > .material-icons { + position: absolute; + top: 10px; + right: 10px; + color: $color-white; + } + } + + > .selected { + background-color: $color-link !important; + + > .title, .space, .snippet { + color: $color-white !important; + } + } + } +} diff --git a/gui/app/styles/view/document/doc-structure.scss b/gui/app/styles/view/document/doc-structure.scss index 9517824d..1fc84e63 100644 --- a/gui/app/styles/view/document/doc-structure.scss +++ b/gui/app/styles/view/document/doc-structure.scss @@ -1,3 +1,7 @@ +.document-tabnav { + margin: 50px 0 100px 0; +} + .document-structure { margin: 0 0 0 0; @@ -15,6 +19,14 @@ display: inline-block; } + > .page-state-pending { + color: $color-red; + } + + > .page-state-review { + color: $color-green; + } + > .page-title { display: inline-block; font-size: 1.8rem; diff --git a/gui/app/styles/view/document/document.scss b/gui/app/styles/view/document/document.scss index a40441a1..3c42e318 100644 --- a/gui/app/styles/view/document/document.scss +++ b/gui/app/styles/view/document/document.scss @@ -1,4 +1,5 @@ @import "add-section.scss"; +@import "copy-move.scss"; @import "doc-meta.scss"; @import "doc-structure.scss"; @import "doc-toc.scss"; @@ -6,5 +7,4 @@ @import "view-attachment.scss"; @import "view-activity.scss"; @import "view-revision.scss"; -@import "wysiwyg.scss"; - +@import "wysiwyg.scss"; \ No newline at end of file diff --git a/gui/app/styles/view/document/view-activity.scss b/gui/app/styles/view/document/view-activity.scss index 762f8d3a..ba2e718e 100644 --- a/gui/app/styles/view/document/view-activity.scss +++ b/gui/app/styles/view/document/view-activity.scss @@ -1,66 +1,64 @@ .view-activity { - > .items { - list-style-type: none; - margin: 0; + margin: 50px; + + .title { + font-size: 1.8rem; + font-weight: bold; + margin: 0 0 30px 0; + color: $color-gray; + } + + > .list { + margin: 0 0 50px 0; padding: 0; - white-space: nowrap; + width: 100%; > .item { - margin: 0; - padding: 15px 0; - width: 100%; + @include ease-in(); + list-style: none; + padding: 10px 0; + margin: 5px 0; + white-space: nowrap; + position: relative; - > .avatar-box { - display: inline-block; - cursor: default; - position: relative; - overflow: hidden; - width: 35px; - height: 35px; - line-height: 34px; - padding: 0; - border-radius: 50%; - text-align: center; - font-weight: bold; - background-color: $color-gray; - color: $color-white; - vertical-align: middle; - - margin: 0 20px 0 0; + > .dash { + position: absolute; + top: 19px; + left: -20px; + background-color: $color-border; + height: 3px; + width: 10px; } - > .activity { + > .spacer { + display: inline-block; + padding-left: 10px; + } + + > .details { + @include ease-in(); + vertical-align: top; display: inline-block; - > .name { - display: inline-block; + > .doc { font-size: 1.2rem; + font-weight: normal; color: $color-off-black; - font-weight: bold; + letter-spacing: 0.5px; } - - > .detail { - display: inline-block; - font-size: 1rem; + + > .note { color: $color-gray; - - .viewed { - color: $color-goldy; - } - - .added { - color: $color-green; - } - - .changed { - color: $color-blue; - } - - .deleted { - color: $color-red; - } + font-size: 1rem; + margin-top: 2px; } } } } + + > .list-timeline { + border-left: 5px solid $color-border; + padding-left: 20px; + margin-left: 30px; + } } diff --git a/gui/app/templates/components/document/document-heading.hbs b/gui/app/templates/components/document/document-heading.hbs index a2d03002..0ac07878 100644 --- a/gui/app/templates/components/document/document-heading.hbs +++ b/gui/app/templates/components/document/document-heading.hbs @@ -1,6 +1,6 @@ {{#unless editMode}}
-
+

{{#if document.template}} Template   diff --git a/gui/app/templates/components/document/document-meta.hbs b/gui/app/templates/components/document/document-meta.hbs index a8402ca7..18300dc9 100644 --- a/gui/app/templates/components/document/document-meta.hbs +++ b/gui/app/templates/components/document/document-meta.hbs @@ -42,6 +42,39 @@

+
+
Change Control
+
+ {{changeControlMsg}} +
+
+ + {{#if (eq document.protection constants.ProtectionType.Review)}} +
+
Approval Process
+
+ {{approvalMsg}} +
+
+ + {{#if userChanges}} +
+
Your Contributions
+
+ {{contributorMsg}} +
+
+ {{/if}} + + {{#if isApprover}} +
+
Approver Status
+
+ {{approverMsg}} +
+
+ {{/if}} + {{/if}}
diff --git a/gui/app/templates/components/document/document-page.hbs b/gui/app/templates/components/document/document-page.hbs index 12081409..142f1dda 100644 --- a/gui/app/templates/components/document/document-page.hbs +++ b/gui/app/templates/components/document/document-page.hbs @@ -1,12 +1,12 @@ -
+
{{#if editMode}}
- {{document/document-editor document=document folder=folder page=page meta=meta onCancel=(action 'onCancelEdit') onAction=(action 'onSavePage')}} + {{document/document-editor document=document folder=folder page=editPage meta=editMeta onCancel=(action 'onCancelEdit') onAction=(action 'onSavePage')}}
{{else}} - {{document/page-heading document=document folder=folder page=page permissions=permissions tabMode=tabMode + {{document/page-heading document=document folder=folder page=page meta=meta pending=pending permissions=permissions tabMode=tabMode roles=roles blocks=blocks onEdit=(action 'onEdit') onSavePageAsBlock=(action 'onSavePageAsBlock') - onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onDeletePage')}} + onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onDeletePage') refresh=(action refresh)}}
{{section/base-renderer page=page}}
diff --git a/gui/app/templates/components/document/document-toc.hbs b/gui/app/templates/components/document/document-toc.hbs index 55e1f2a5..59444555 100644 --- a/gui/app/templates/components/document/document-toc.hbs +++ b/gui/app/templates/components/document/document-toc.hbs @@ -1,38 +1,46 @@ -
-
-
Table of contents
- {{#if session.authenticated}} - {{#if permissions.documentEdit}} - {{#unless emptyState}} -
-
- arrow_upward -
-
-
- arrow_downward -
-
-
- arrow_back -
-
-
- arrow_forward -
+{{#unless emptyState}} +
+
+
Table of contents
+ {{#if canEdit}} +
+
+ arrow_upward
- {{/unless}} +
+
+ arrow_downward +
+
+
+ arrow_back +
+
+
+ arrow_forward +
+
{{/if}} - {{/if}} -
+
- -
+ +
+{{/unless}} \ No newline at end of file diff --git a/gui/app/templates/components/document/page-heading.hbs b/gui/app/templates/components/document/page-heading.hbs index 18b4ecf9..3eb5464f 100644 --- a/gui/app/templates/components/document/page-heading.hbs +++ b/gui/app/templates/components/document/page-heading.hbs @@ -1,5 +1,4 @@
-
-
-
-
- {{#if permissions.documentEdit}} + {{#unless (eq document.protection constants.ProtectionType.Lock)}} +
+
- {{#if (is-equal page.pageType 'tab')}} - {{#link-to 'document.section' page.id}} -
- mode_edit -
- {{/link-to}} - {{else}} -
+ {{#if canEdit}} +
mode_edit
{{/if}} @@ -31,111 +23,147 @@ more_vert
{{/if}}
- {{/if}} +
+
+ {{/unless}} +
+
+ +{{#if permissions.documentCopy}} + +{{/if}} - {{#if permissions.documentCopy}} -