From 716bd062d75f496eb599fea3f39e501bac016a51 Mon Sep 17 00:00:00 2001 From: Harvey Kandola Date: Fri, 1 Jun 2018 11:49:09 +0100 Subject: [PATCH] Provide each doc section with TOC controls Put up/down/indent/outdent options on the section menu dropdown for easier TOC manipulation. --- gui/app/components/document/document-toc.js | 2 +- gui/app/components/document/page-heading.js | 109 ++++++++- gui/app/pods/document/index/controller.js | 6 + gui/app/pods/document/index/template.hbs | 72 ++++-- .../styles/view/document/doc-structure.scss | 4 - .../components/document/document-page.hbs | 37 ++- .../components/document/document-toc.hbs | 2 +- .../components/document/page-heading.hbs | 15 ++ .../components/document/view-content.hbs | 224 ++++++++++-------- 9 files changed, 333 insertions(+), 138 deletions(-) diff --git a/gui/app/components/document/document-toc.js b/gui/app/components/document/document-toc.js index bbd1c304..87e3c0a6 100644 --- a/gui/app/components/document/document-toc.js +++ b/gui/app/components/document/document-toc.js @@ -63,7 +63,7 @@ export default Component.extend(TooltipMixin, { this.removeTooltips(); }, - onDocumentPageAdded(pageId) { // eslint-disable-line no-unused-vars + onDocumentPageAdded(pageId) { schedule('afterRender', () => { this.setState(pageId); }); diff --git a/gui/app/components/document/page-heading.js b/gui/app/components/document/page-heading.js index 12e66dd0..f3829f26 100644 --- a/gui/app/components/document/page-heading.js +++ b/gui/app/components/document/page-heading.js @@ -14,6 +14,7 @@ import { computed } from '@ember/object'; import { debounce } from '@ember/runloop'; import { inject as service } from '@ember/service'; import ModalMixin from '../../mixins/modal'; +import tocUtil from '../../utils/toc'; import Component from '@ember/component'; export default Component.extend(ModalMixin, { @@ -23,7 +24,7 @@ export default Component.extend(ModalMixin, { deleteChildren: false, blockTitle: "", blockExcerpt: "", - canEdit: false, + // canEdit: false, canDelete: false, canMove: false, docSearchFilter: '', @@ -39,10 +40,29 @@ export default Component.extend(ModalMixin, { return permissions.get('documentCopy') || permissions.get('documentTemplate') || this.get('canEdit') || this.get('canMove') || this.get('canDelete'); }), + canEdit: computed('permissions', 'document', 'pages', 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.docSearchResults = []; + this.state = { + actionablePage: false, + upDisabled: true, + downDisabled: true, + indentDisabled: true, + outdentDisabled: true, + pageId: '' + }; }, didReceiveAttrs() { @@ -50,11 +70,13 @@ export default Component.extend(ModalMixin, { this.modalInputFocus('#publish-page-modal-' + this.get('page.id'), '#block-title-' + this.get('page.id')); let permissions = this.get('permissions'); - this.set('canEdit', permissions.get('documentEdit')); + // this.set('canEdit', permissions.get('documentEdit')); this.set('canDelete', permissions.get('documentDelete')); this.set('canMove', permissions.get('documentMove')); + + this.setState(this.get('page.id')); }, - + searchDocs() { let payload = { keywords: this.get('docSearchFilter').trim(), doc: true }; if (payload.keywords.length == 0) return; @@ -64,6 +86,20 @@ export default Component.extend(ModalMixin, { }); }, + // Controls what user can do with the toc enty for this page + setState(pageId) { + let toc = this.get('pages'); + let page = _.find(toc, function(i) { return i.get('page.id') === pageId; }); + let state = tocUtil.getState(toc, page.get('page')); + + if (!this.get('canEdit')) { + state.actionablePage = false; + state.upDisabled = state.downDisabled = state.indentDisabled = state.outdentDisabled = true; + } + + this.set('state', state); + }, + actions: { onEdit() { let page = this.get('page'); @@ -168,5 +204,72 @@ export default Component.extend(ModalMixin, { let refresh = this.get('refresh'); refresh(); }, + + // Page up -- above pages shunt down + pageUp() { + let state = this.get('state'); + + if (state.upDisabled || this.get('document.protection') !== this.get('constants').ProtectionType.None) { + return; + } + + 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.moveUp(state, pages, page); + if (pendingChanges.length > 0) { + let cb = this.get('onPageSequenceChange'); + cb(state.pageId, pendingChanges); + } + }, + + // Move down -- pages below shift up + pageDown() { + if (!this.get('canEdit')) return; + + let state = this.get('state'); + 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) { + let cb = this.get('onPageSequenceChange'); + cb(state.pageId, pendingChanges); + } + }, + + // Indent -- changes a page from H2 to H3, etc. + pageIndent() { + if (!this.get('canEdit')) return; + + let state = this.get('state'); + 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) { + let cb = this.get('onPageLevelChange'); + cb(state.pageId, pendingChanges); + } + }, + + // Outdent -- changes a page from H3 to H2, etc. + pageOutdent() { + if (!this.get('canEdit')) return; + + let state = this.get('state'); + 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) { + let cb = this.get('onPageLevelChange'); + cb(state.pageId, pendingChanges); + } + } } }); diff --git a/gui/app/pods/document/index/controller.js b/gui/app/pods/document/index/controller.js index 8896a9db..9b32d2cc 100644 --- a/gui/app/pods/document/index/controller.js +++ b/gui/app/pods/document/index/controller.js @@ -198,19 +198,25 @@ export default Controller.extend(Tooltips, Notifier, { }, onPageSequenceChange(currentPageId, changes) { + this.showWait(); this.set('currentPageId', currentPageId); + this.get('documentService').changePageSequence(this.get('document.id'), changes).then(() => { this.get('documentService').fetchPages(this.get('document.id'), this.get('session.user.id')).then( (pages) => { this.set('pages', pages); + this.showDone(); }); }); }, onPageLevelChange(currentPageId, changes) { + this.showWait(); this.set('currentPageId', currentPageId); + this.get('documentService').changePageLevel(this.get('document.id'), changes).then(() => { this.get('documentService').fetchPages(this.get('document.id'), this.get('session.user.id')).then( (pages) => { this.set('pages', pages); + this.showDone(); }); }); }, diff --git a/gui/app/pods/document/index/template.hbs b/gui/app/pods/document/index/template.hbs index 70c8b494..b7a9b6af 100644 --- a/gui/app/pods/document/index/template.hbs +++ b/gui/app/pods/document/index/template.hbs @@ -16,17 +16,17 @@ {{#layout/middle-zone-content}} {{toolbar/for-document - document=document - spaces=folders - space=folder - permissions=permissions - roles=roles tab=tab + roles=roles + space=folder + spaces=folders + document=document versions=versions - onDocumentDelete=(action 'onDocumentDelete') + permissions=permissions + refresh=(action 'refresh') onSaveTemplate=(action 'onSaveTemplate') onSaveDocument=(action 'onSaveDocument') - refresh=(action 'refresh')}} + onDocumentDelete=(action 'onDocumentDelete')}}
-
+
{{#if canEdit}}
diff --git a/gui/app/templates/components/document/page-heading.hbs b/gui/app/templates/components/document/page-heading.hbs index c2bd8e7c..d41e03f2 100644 --- a/gui/app/templates/components/document/page-heading.hbs +++ b/gui/app/templates/components/document/page-heading.hbs @@ -38,6 +38,21 @@ {{#if canDelete}} Delete {{/if}} + {{#if (and canEdit state.actionablePage)}} + + {{#unless state.indentDisabled}} + Indent + {{/unless}} + {{#unless state.outdentDisabled}} + Outdent + {{/unless}} + {{#unless state.upDisabled}} + Move up + {{/unless}} + {{#unless state.downDisabled}} + Move down + {{/unless}} + {{/if}}
{{/if}}
diff --git a/gui/app/templates/components/document/view-content.hbs b/gui/app/templates/components/document/view-content.hbs index 23d4d64e..7b59c0b1 100644 --- a/gui/app/templates/components/document/view-content.hbs +++ b/gui/app/templates/components/document/view-content.hbs @@ -1,125 +1,151 @@ {{#if hasPages}} -{{#each pages key="id" as |item index|}} + + {{#each pages key="id" as |item index|}} + {{#if canEdit}} +
+
+
+ SECTION
+
+
+ {{else}} +
+ {{/if}} + {{document/document-page + roles=roles + pages=pages + folder=folder + toEdit=toEdit + blocks=blocks + page=item.page + meta=item.meta + document=document + pending=item.pending + permissions=permissions + refresh=(action refresh) + onSavePage=(action 'onSavePage') + onCopyPage=(action 'onCopyPage') + onMovePage=(action 'onMovePage') + onDeletePage=(action 'onDeletePage') + onSavePageAsBlock=(action 'onSavePageAsBlock') + onPageLevelChange=(action onPageLevelChange) + onPageSequenceChange=(action onPageSequenceChange)}} + {{/each}} + {{#if canEdit}} -
+
+ SECTION
- {{else}} -
{{/if}} {{document/document-page document=document folder=folder page=item.page meta=item.meta pending=item.pending permissions=permissions -toEdit=toEdit roles=roles blocks=blocks onSavePage=(action 'onSavePage') onSavePageAsBlock=(action 'onSavePageAsBlock') onCopyPage=(action -'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onDeletePage') refresh=(action refresh)}} {{/each}} -{{#if canEdit}} -
-
-
+ SECTION
-
-
-{{/if}} -{{#if showLikes}} -
-
- {{#unless voteThanks}} -
- {{folder.likes}} -
-
-    - -
- {{else}} -
Thanks for the feedback!
- {{/unless}} -
-
- {{/if}} + + {{#if showLikes}} +
+
+ {{#unless voteThanks}} +
+ {{folder.likes}} +
+
+    + +
+ {{else}} +
Thanks for the feedback!
+ {{/unless}} +
+
+ {{/if}} + {{/if}} {{#unless hasPages}} {{#if canEdit}} -
-
-
+ SECTION
-
-
-{{/if}} {{/unless}} +
+
+
+ SECTION
+
+
+ {{/if}} +{{/unless}} {{#if canEdit}} -
-
-
-
-
-
- +
+
+
+
+
+
+ +
-
-
-
-
- {{input type="text" id="new-section-name" value=newSectionName class=(if newSectionNameMissing 'mousetrap form-control form-control-lg - is-invalid' 'mousetrap form-control form-control-lg') placeholder="Enter section name" autocomplete="off"}} +
+
+
+ {{input type="text" id="new-section-name" value=newSectionName class=(if newSectionNameMissing 'mousetrap form-control form-control-lg + is-invalid' 'mousetrap form-control form-control-lg') placeholder="Enter section name" autocomplete="off"}} +
-
-
-
-
-
Insert section type
-
    - {{#each sections as |section|}} -
  • -
    - +
    +
    +
    +
    Insert section type
    +
      + {{#each sections as |section|}} +
    • +
      + +
      +
      {{section.title}}
      +
    • + {{/each}} +
    +
    +
    +
    + +
    +
    + {{#if hasBlocks}} +
    Insert re-usable content
    +
      + {{#each blocks as |block|}} +
    • +
      + {{#if permissions.documentTemplate}} {{#link-to 'document.block' folder.id folder.slug document.id document.slug block.id + class="button-icon-gray button-icon-small align-middle"}} + edit + {{/link-to}} +
      +
      + delete +
      + {{/if}} +
      +
      +
      {{block.title}}
      +
      {{block.excerpt}}
      -
      {{section.title}}
    • {{/each}}
    + {{else}} +
    You have no reusable content — publish any section as a template
    + {{/if}}
    - -
    -
    - {{#if hasBlocks}} -
    Insert re-usable content
    -
      - {{#each blocks as |block|}} -
    • -
      - {{#if permissions.documentTemplate}} {{#link-to 'document.block' folder.id folder.slug document.id document.slug block.id - class="button-icon-gray button-icon-small align-middle"}} - edit - {{/link-to}} -
      -
      - delete -
      - {{/if}} -
      -
      -
      {{block.title}}
      -
      {{block.excerpt}}
      -
      -
    • - {{/each}} -
    - {{else}} -
    You have no reusable content — publish any section as a template
    - {{/if}} -
    -
-
-{{/if}} {{#if permissions.documentTemplate}} {{#ui/ui-dialog title="Delete Content Block" confirmCaption="Delete" buttonType="btn-danger" -show=showDeleteBlockDialog onAction=(action 'onDeleteBlock')}} -

Are you sure you want to delete this re-usable content block?

-{{/ui/ui-dialog}} {{/if}} +{{/if}} + +{{#if permissions.documentTemplate}} + {{#ui/ui-dialog title="Delete Content Block" confirmCaption="Delete" buttonType="btn-danger" show=showDeleteBlockDialog onAction=(action 'onDeleteBlock')}} +

Are you sure you want to delete this re-usable content block?

+ {{/ui/ui-dialog}} +{{/if}}