From dfdaa4c6b81749d7b2cd47146e3521adc477d56f Mon Sep 17 00:00:00 2001 From: Harvey Kandola Date: Mon, 27 Feb 2017 17:07:49 +0000 Subject: [PATCH] revamped document view new user experience WIP --- .../components/document/document-heading.js | 54 +++ app/app/components/document/document-view.js | 6 - app/app/components/document/page-heading.js | 13 +- .../layout/zone-document-sidebar.js | 16 + app/app/components/layout/zone-document.js | 27 ++ app/app/pods/document/controller.js | 402 ++++++++++------- app/app/pods/document/index/controller.js | 81 ---- app/app/pods/document/index/template.hbs | 2 - app/app/pods/document/template.hbs | 80 +++- app/app/snippets.txt | 411 ++++++++++++++++++ app/app/styles/color.scss | 2 + app/app/styles/font.scss | 6 +- app/app/styles/view/document/all.scss | 1 + app/app/styles/view/document/content.scss | 93 ++-- app/app/styles/view/document/layout.scss | 80 ++++ app/app/styles/view/document/sidebar.scss | 31 +- app/app/styles/view/document/wysiwyg.scss | 122 +++--- app/app/styles/view/layout.scss | 7 +- app/app/styles/widget/widget-button.scss | 7 + app/app/styles/widget/widget-input.scss | 10 + .../components/document/document-heading.hbs | 22 + .../components/document/document-view.hbs | 18 +- .../components/document/page-heading.hbs | 4 +- .../layout/zone-document-sidebar.hbs | 3 + .../components/layout/zone-document.hbs | 3 + 25 files changed, 1116 insertions(+), 385 deletions(-) create mode 100644 app/app/components/document/document-heading.js create mode 100644 app/app/components/layout/zone-document-sidebar.js create mode 100644 app/app/components/layout/zone-document.js create mode 100644 app/app/snippets.txt create mode 100644 app/app/styles/view/document/layout.scss create mode 100644 app/app/templates/components/document/document-heading.hbs create mode 100644 app/app/templates/components/layout/zone-document-sidebar.hbs create mode 100644 app/app/templates/components/layout/zone-document.hbs diff --git a/app/app/components/document/document-heading.js b/app/app/components/document/document-heading.js new file mode 100644 index 00000000..3b0d6a1b --- /dev/null +++ b/app/app/components/document/document-heading.js @@ -0,0 +1,54 @@ +// 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 Ember from 'ember'; +import NotifierMixin from '../../mixins/notifier'; +import TooltipMixin from '../../mixins/tooltip'; + +const { + computed, +} = Ember; + + +export default Ember.Component.extend(NotifierMixin, TooltipMixin, { + documentService: Ember.inject.service('document'), + editMode: false, + docName: '', + docExcerpt: '', + + hasNameError: computed.empty('docName'), + hasExcerptError: computed.empty('docExcerpt'), + + actions: { + toggleEdit() { + this.set('docName', this.get('document.name')); + this.set('docExcerpt', this.get('document.excerpt')); + this.set('editMode', true); + }, + + onSaveDocument() { + if (this.get('hasNameError') || this.get('hasExcerptError')) { + return; + } + + this.set('document.name', this.get('docName')); + this.set('document.excerpt', this.get('docExcerpt')); + this.showNotification('Saved'); + this.get('documentService').save(this.get('document')); + + this.set('editMode', false); + }, + + cancel() { + this.set('editMode', false); + } + } +}); diff --git a/app/app/components/document/document-view.js b/app/app/components/document/document-view.js index 15466ae7..1d50ce2f 100644 --- a/app/app/components/document/document-view.js +++ b/app/app/components/document/document-view.js @@ -97,11 +97,5 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, { this.attrs.onDeletePage(params); }, - - onTagChange(tags) { - let doc = this.get('document'); - doc.set('tags', tags); - this.get('documentService').save(doc); - } } }); diff --git a/app/app/components/document/page-heading.js b/app/app/components/document/page-heading.js index 7bd974bb..eb4819b4 100644 --- a/app/app/components/document/page-heading.js +++ b/app/app/components/document/page-heading.js @@ -73,21 +73,10 @@ export default Ember.Component.extend(TooltipMixin, { }), didRender() { - if (this.get('isEditor')) { - let self = this; - $(".page-action-button").each(function (i, el) { - self.addTooltip(el); - }); - } - $("#" + this.get('blockTitleId')).removeClass('error'); $("#" + this.get('blockExcerptId')).removeClass('error'); }, - willDestroyElement() { - this.destroyTooltips(); - }, - actions: { onMenuOpen() { if ($('#' + this.get('publishDialogId')).is( ":visible" )) { @@ -198,6 +187,6 @@ export default Ember.Component.extend(TooltipMixin, { this.attrs.onMovePage(page.get('id'), targetDocumentId); return true; - } + } } }); diff --git a/app/app/components/layout/zone-document-sidebar.js b/app/app/components/layout/zone-document-sidebar.js new file mode 100644 index 00000000..56467fb8 --- /dev/null +++ b/app/app/components/layout/zone-document-sidebar.js @@ -0,0 +1,16 @@ +// 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 Ember from 'ember'; +import NotifierMixin from '../../mixins/notifier'; + +export default Ember.Component.extend(NotifierMixin, { +}); diff --git a/app/app/components/layout/zone-document.js b/app/app/components/layout/zone-document.js new file mode 100644 index 00000000..f8f457d6 --- /dev/null +++ b/app/app/components/layout/zone-document.js @@ -0,0 +1,27 @@ +// 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 Ember from 'ember'; +import NotifierMixin from '../../mixins/notifier'; + +const { + inject: { service } +} = Ember; + +export default Ember.Component.extend(NotifierMixin, { + appMeta :service(), + + didRender() { + if (this.get('appMeta').invalidLicense()) { + this.showNotification(`!! Expired or invalid license !!`); + } + } +}); diff --git a/app/app/pods/document/controller.js b/app/app/pods/document/controller.js index 7e08039d..9ab198d2 100644 --- a/app/app/pods/document/controller.js +++ b/app/app/pods/document/controller.js @@ -19,6 +19,7 @@ export default Ember.Controller.extend(NotifierMixin, { page: null, folder: {}, pages: [], + toggled: false, // Jump to the right part of the document. scrollToPage(pageId) { @@ -44,6 +45,21 @@ export default Ember.Controller.extend(NotifierMixin, { }, actions: { + toggleMenu() { + this.set('toggled', !this.get('toggled')); + }, + + onTagChange(tags) { + let doc = this.get('model.document'); + doc.set('tags', tags); + this.get('documentService').save(doc); + }, + + onSaveDocument(doc) { + this.get('documentService').save(doc); + this.showNotification('Saved'); + }, + gotoPage(pageId) { if (is.null(pageId)) { return; @@ -52,157 +68,251 @@ export default Ember.Controller.extend(NotifierMixin, { this.scrollToPage(pageId); }, - onPageSequenceChange(changes) { - this.get('documentService').changePageSequence(this.get('model.document.id'), changes).then(() => { - _.each(changes, (change) => { - let pageContent = _.findWhere(this.get('model.pages'), { - id: change.pageId - }); + onAddBlock(block) { + this.get('sectionService').addBlock(block).then(() => { + this.showNotification("Published"); + }); + }, - if (is.not.undefined(pageContent)) { - pageContent.set('sequence', change.sequence); + onCopyPage(pageId, targetDocumentId) { + let documentId = this.get('model.document.id'); + this.get('documentService').copyPage(documentId, pageId, targetDocumentId).then(() => { + this.showNotification("Copied"); + + // refresh data if copied to same document + if (documentId === targetDocumentId) { + this.get('target.router').refresh(); + } + }); + }, + + onMovePage(pageId, targetDocumentId) { + let documentId = this.get('model.document.id'); + + this.get('documentService').copyPage(documentId, pageId, targetDocumentId).then(() => { + this.showNotification("Moved"); + + this.send('onPageDeleted', { id: pageId, children: false }); + }); + }, + + onPageDeleted(deletePage) { + let documentId = this.get('model.document.id'); + let pages = this.get('model.pages'); + let deleteId = deletePage.id; + let deleteChildren = deletePage.children; + let page = _.findWhere(pages, { + id: deleteId + }); + let pageIndex = _.indexOf(pages, page, false); + let pendingChanges = []; + + this.audit.record("deleted-page"); + + // select affected pages + for (var i = pageIndex + 1; i < pages.get('length'); i++) { + if (pages[i].get('level') <= page.get('level')) { + break; + } + + pendingChanges.push({ + pageId: pages[i].get('id'), + level: pages[i].get('level') - 1 + }); + } + + if (deleteChildren) { + // nuke of page tree + 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('model.pages', _.reject(pages, function (p) { //jshint ignore: line + return p.get('id') === pageId; + })); } + + this.set('model.pages', _.sortBy(pages, "sequence")); + this.get('target.router').refresh(); }); + } else { + // page delete followed by re-leveling child pages + this.get('documentService').deletePage(documentId, deleteId).then(() => { + this.set('model.pages', _.reject(pages, function (p) { + return p.get('id') === deleteId; + })); - this.set('model.pages', this.get('model.pages').sortBy('sequence')); - this.get('target.router').refresh(); - }); - }, - - onPageLevelChange(changes) { - this.get('documentService').changePageLevel(this.get('model.document.id'), changes).then(() => { - _.each(changes, (change) => { - let pageContent = _.findWhere(this.get('model.pages'), { - id: change.pageId - }); - - if (is.not.undefined(pageContent)) { - pageContent.set('level', change.level); - } + this.send('onPageLevelChange', pendingChanges); }); - - let pages = this.get('model.pages'); - pages = pages.sortBy('sequence'); - this.set('model.pages', []); - this.set('model.pages', pages); - this.get('target.router').refresh(); - }); - }, - - onSaveTemplate(name, desc) { - this.get('templateService').saveAsTemplate(this.get('model.document.id'), name, desc).then(function () {}); - }, - - onSaveMeta(doc) { - this.get('documentService').save(doc).then(() => { - this.transitionToRoute('document.index'); - }); - }, - - onAddSection(section) { - this.audit.record("added-section-" + section.get('contentType')); - - let page = { - documentId: this.get('model.document.id'), - title: `${section.get('title')}`, - level: 1, - sequence: 0, - body: "", - contentType: section.get('contentType'), - pageType: section.get('pageType') - }; - - let meta = { - documentId: this.get('model.document.id'), - rawBody: "", - config: "" - }; - - let model = { - page: page, - meta: meta - }; - - this.get('documentService').addPage(this.get('model.document.id'), model).then((newPage) => { - let data = this.get('store').normalize('page', newPage); - this.get('store').push(data); - - this.get('documentService').getPages(this.get('model.document.id')).then((pages) => { - this.set('model.pages', pages.filterBy('pageType', 'section')); - this.set('model.tabs', pages.filterBy('pageType', 'tab')); - - this.get('documentService').getPageMeta(this.get('model.document.id'), newPage.id).then(() => { - this.transitionToRoute('document.edit', - this.get('model.folder.id'), - this.get('model.folder.slug'), - this.get('model.document.id'), - this.get('model.document.slug'), - newPage.id); - }); - }); - }); - }, - - onInsertBlock(block) { - this.audit.record("added-content-block-" + block.get('contentType')); - - let page = { - documentId: this.get('model.document.id'), - title: `${block.get('title')}`, - level: 1, - sequence: 0, - body: block.get('body'), - contentType: block.get('contentType'), - pageType: block.get('pageType'), - blockId: block.get('id') - }; - - let meta = { - documentId: this.get('model.document.id'), - rawBody: block.get('rawBody'), - config: block.get('config'), - externalSource: block.get('externalSource') - }; - - let model = { - page: page, - meta: meta - }; - - this.get('documentService').addPage(this.get('model.document.id'), model).then((newPage) => { - let data = this.get('store').normalize('page', newPage); - this.get('store').push(data); - - this.get('documentService').getPages(this.get('model.document.id')).then((pages) => { - this.set('model.pages', pages.filterBy('pageType', 'section')); - this.set('model.tabs', pages.filterBy('pageType', 'tab')); - - this.get('documentService').getPageMeta(this.get('model.document.id'), newPage.id).then(() => { - this.transitionToRoute('document.edit', - this.get('model.folder.id'), - this.get('model.folder.slug'), - this.get('model.document.id'), - this.get('model.document.slug'), - newPage.id); - }); - }); - }); - }, - - onDeleteBlock(blockId) { - this.get('sectionService').deleteBlock(blockId).then(() => { - this.audit.record("deleted-block"); - this.send("showNotification", "Deleted"); - this.transitionToRoute('document.index'); - }); - }, - - onDocumentDelete() { - this.get('documentService').deleteDocument(this.get('model.document.id')).then(() => { - this.audit.record("deleted-page"); - this.send("showNotification", "Deleted"); - this.transitionToRoute('folder', this.get('model.folder.id'), this.get('model.folder.slug')); - }); + } } } }); + +/* +gotoPage(pageId) { + if (is.null(pageId)) { + return; + } + + this.scrollToPage(pageId); +}, + +onPageSequenceChange(changes) { + this.get('documentService').changePageSequence(this.get('model.document.id'), changes).then(() => { + _.each(changes, (change) => { + let pageContent = _.findWhere(this.get('model.pages'), { + id: change.pageId + }); + + if (is.not.undefined(pageContent)) { + pageContent.set('sequence', change.sequence); + } + }); + + this.set('model.pages', this.get('model.pages').sortBy('sequence')); + this.get('target.router').refresh(); + }); +}, + +onPageLevelChange(changes) { + this.get('documentService').changePageLevel(this.get('model.document.id'), changes).then(() => { + _.each(changes, (change) => { + let pageContent = _.findWhere(this.get('model.pages'), { + id: change.pageId + }); + + if (is.not.undefined(pageContent)) { + pageContent.set('level', change.level); + } + }); + + let pages = this.get('model.pages'); + pages = pages.sortBy('sequence'); + this.set('model.pages', []); + this.set('model.pages', pages); + this.get('target.router').refresh(); + }); +}, + +onSaveTemplate(name, desc) { + this.get('templateService').saveAsTemplate(this.get('model.document.id'), name, desc).then(function () {}); +}, + +onSaveMeta(doc) { + this.get('documentService').save(doc).then(() => { + this.transitionToRoute('document.index'); + }); +}, + +onAddSection(section) { + this.audit.record("added-section-" + section.get('contentType')); + + let page = { + documentId: this.get('model.document.id'), + title: `${section.get('title')}`, + level: 1, + sequence: 0, + body: "", + contentType: section.get('contentType'), + pageType: section.get('pageType') + }; + + let meta = { + documentId: this.get('model.document.id'), + rawBody: "", + config: "" + }; + + let model = { + page: page, + meta: meta + }; + + this.get('documentService').addPage(this.get('model.document.id'), model).then((newPage) => { + let data = this.get('store').normalize('page', newPage); + this.get('store').push(data); + + this.get('documentService').getPages(this.get('model.document.id')).then((pages) => { + this.set('model.pages', pages.filterBy('pageType', 'section')); + this.set('model.tabs', pages.filterBy('pageType', 'tab')); + + this.get('documentService').getPageMeta(this.get('model.document.id'), newPage.id).then(() => { + this.transitionToRoute('document.edit', + this.get('model.folder.id'), + this.get('model.folder.slug'), + this.get('model.document.id'), + this.get('model.document.slug'), + newPage.id); + }); + }); + }); +}, + +onInsertBlock(block) { + this.audit.record("added-content-block-" + block.get('contentType')); + + let page = { + documentId: this.get('model.document.id'), + title: `${block.get('title')}`, + level: 1, + sequence: 0, + body: block.get('body'), + contentType: block.get('contentType'), + pageType: block.get('pageType'), + blockId: block.get('id') + }; + + let meta = { + documentId: this.get('model.document.id'), + rawBody: block.get('rawBody'), + config: block.get('config'), + externalSource: block.get('externalSource') + }; + + let model = { + page: page, + meta: meta + }; + + this.get('documentService').addPage(this.get('model.document.id'), model).then((newPage) => { + let data = this.get('store').normalize('page', newPage); + this.get('store').push(data); + + this.get('documentService').getPages(this.get('model.document.id')).then((pages) => { + this.set('model.pages', pages.filterBy('pageType', 'section')); + this.set('model.tabs', pages.filterBy('pageType', 'tab')); + + this.get('documentService').getPageMeta(this.get('model.document.id'), newPage.id).then(() => { + this.transitionToRoute('document.edit', + this.get('model.folder.id'), + this.get('model.folder.slug'), + this.get('model.document.id'), + this.get('model.document.slug'), + newPage.id); + }); + }); + }); +}, + +onDeleteBlock(blockId) { + this.get('sectionService').deleteBlock(blockId).then(() => { + this.audit.record("deleted-block"); + this.send("showNotification", "Deleted"); + this.transitionToRoute('document.index'); + }); +}, + +onDocumentDelete() { + this.get('documentService').deleteDocument(this.get('model.document.id')).then(() => { + this.audit.record("deleted-page"); + this.send("showNotification", "Deleted"); + this.transitionToRoute('folder', this.get('model.folder.id'), this.get('model.folder.slug')); + }); +} + +*/ diff --git a/app/app/pods/document/index/controller.js b/app/app/pods/document/index/controller.js index b8fbbb14..2eabd9a4 100644 --- a/app/app/pods/document/index/controller.js +++ b/app/app/pods/document/index/controller.js @@ -86,87 +86,6 @@ export default Ember.Controller.extend(NotifierMixin, { }); }, - onAddBlock(block) { - this.get('sectionService').addBlock(block).then(() => { - this.showNotification("Published"); - }); - }, - onCopyPage(pageId, targetDocumentId) { - let documentId = this.get('model.document.id'); - this.get('documentService').copyPage(documentId, pageId, targetDocumentId).then(() => { - this.showNotification("Copied"); - - // refresh data if copied to same document - if (documentId === targetDocumentId) { - this.get('target.router').refresh(); - } - }); - }, - - onMovePage(pageId, targetDocumentId) { - let documentId = this.get('model.document.id'); - - this.get('documentService').copyPage(documentId, pageId, targetDocumentId).then(() => { - this.showNotification("Moved"); - - this.send('onPageDeleted', { id: pageId, children: false }); - }); - }, - - onPageDeleted(deletePage) { - let documentId = this.get('model.document.id'); - let pages = this.get('model.pages'); - let deleteId = deletePage.id; - let deleteChildren = deletePage.children; - let page = _.findWhere(pages, { - id: deleteId - }); - let pageIndex = _.indexOf(pages, page, false); - let pendingChanges = []; - - this.audit.record("deleted-page"); - - // select affected pages - for (var i = pageIndex + 1; i < pages.get('length'); i++) { - if (pages[i].get('level') <= page.get('level')) { - break; - } - - pendingChanges.push({ - pageId: pages[i].get('id'), - level: pages[i].get('level') - 1 - }); - } - - if (deleteChildren) { - // nuke of page tree - 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('model.pages', _.reject(pages, function (p) { //jshint ignore: line - return p.get('id') === pageId; - })); - } - - this.set('model.pages', _.sortBy(pages, "sequence")); - this.get('target.router').refresh(); - }); - } else { - // page delete followed by re-leveling child pages - this.get('documentService').deletePage(documentId, deleteId).then(() => { - this.set('model.pages', _.reject(pages, function (p) { - return p.get('id') === deleteId; - })); - - this.send('onPageLevelChange', pendingChanges); - }); - } - } } }); diff --git a/app/app/pods/document/index/template.hbs b/app/app/pods/document/index/template.hbs index 70063ee5..e69de29b 100644 --- a/app/app/pods/document/index/template.hbs +++ b/app/app/pods/document/index/template.hbs @@ -1,2 +0,0 @@ -{{document/document-view document=model.document links=model.links allPages=model.allPages tabs=model.tabs pages=model.pages folder=model.folder folders=model.folders isEditor=model.isEditor - gotoPage=(action 'gotoPage') onAddBlock=(action 'onAddBlock') onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onPageDeleted')}} diff --git a/app/app/pods/document/template.hbs b/app/app/pods/document/template.hbs index cff52928..47844469 100644 --- a/app/app/pods/document/template.hbs +++ b/app/app/pods/document/template.hbs @@ -1,13 +1,75 @@ {{layout/zone-navigation}} -{{#layout/zone-sidebar}} - {{document/document-sidebar document=model.document folder=model.folder pages=model.pages page=model.page isEditor=model.isEditor sections=model.sections - onAddSection=(action 'onAddSection') onInsertBlock=(action 'onInsertBlock') onDeleteBlock=(action 'onDeleteBlock') changePageSequence=(action 'onPageSequenceChange') changePageLevel=(action 'onPageLevelChange') gotoPage=(action 'gotoPage')}} -{{/layout/zone-sidebar}} +
+ - {{outlet}} -{{/layout/zone-content}} +
+
+
+
+ {{#if toggled}} + + {{else}} + + {{/if}} +
+
+ {{#link-to 'folder' model.folder.id model.folder.slug}} + arrow_back {{model.folder.name}} + {{/link-to}} +
+ {{document/document-heading document=model.document isEditor=model.isEditor onSaveDocument=(action 'onSaveDocument')}} + {{document/document-view document=model.document links=model.links allPages=model.allPages tabs=model.tabs pages=model.pages folder=model.folder folders=model.folders isEditor=model.isEditor gotoPage=(action 'gotoPage') onAddBlock=(action 'onAddBlock') onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onPageDeleted')}} +
+
+
+
+
+
diff --git a/app/app/snippets.txt b/app/app/snippets.txt new file mode 100644 index 00000000..fd551c48 --- /dev/null +++ b/app/app/snippets.txt @@ -0,0 +1,411 @@ +********************************** +********************************** +***** document +********************************** +********************************** + +{{layout/zone-navigation}} + +{{#layout/zone-sidebar}} + {{document/document-sidebar document=model.document folder=model.folder pages=model.pages page=model.page isEditor=model.isEditor sections=model.sections + onAddSection=(action 'onAddSection') onInsertBlock=(action 'onInsertBlock') onDeleteBlock=(action 'onDeleteBlock') changePageSequence=(action 'onPageSequenceChange') changePageLevel=(action 'onPageLevelChange') gotoPage=(action 'gotoPage')}} +{{/layout/zone-sidebar}} + +{{#layout/zone-content}} + {{document/document-toolbar document=model.document pages=model.pages tabs=model.tabs folder=model.folder isEditor=model.isEditor + onSaveTemplate=(action 'onSaveTemplate') onSaveMeta=(action 'onSaveMeta') onDocumentDelete=(action 'onDocumentDelete')}} + + {{outlet}} +{{/layout/zone-content}} + +// 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 Ember from 'ember'; +import NotifierMixin from '../../mixins/notifier'; + +export default Ember.Controller.extend(NotifierMixin, { + documentService: Ember.inject.service('document'), + templateService: Ember.inject.service('template'), + sectionService: Ember.inject.service('section'), + page: null, + folder: {}, + pages: [], + + // Jump to the right part of the document. + scrollToPage(pageId) { + Ember.run.schedule('afterRender', function () { + let dest; + let target = "#page-title-" + pageId; + let targetOffset = $(target).offset(); + + if (is.undefined(targetOffset)) { + return; + } + + dest = targetOffset.top > $(document).height() - $(window).height() ? $(document).height() - $(window).height() : targetOffset.top; + // small correction to ensure we also show page title + dest = dest > 50 ? dest - 74 : dest; + + $("html,body").animate({ + scrollTop: dest + }, 500, "linear"); + $(".toc-index-item").removeClass("selected"); + $("#index-" + pageId).addClass("selected"); + }); + }, + + actions: { + gotoPage(pageId) { + if (is.null(pageId)) { + return; + } + + this.scrollToPage(pageId); + }, + + onPageSequenceChange(changes) { + this.get('documentService').changePageSequence(this.get('model.document.id'), changes).then(() => { + _.each(changes, (change) => { + let pageContent = _.findWhere(this.get('model.pages'), { + id: change.pageId + }); + + if (is.not.undefined(pageContent)) { + pageContent.set('sequence', change.sequence); + } + }); + + this.set('model.pages', this.get('model.pages').sortBy('sequence')); + this.get('target.router').refresh(); + }); + }, + + onPageLevelChange(changes) { + this.get('documentService').changePageLevel(this.get('model.document.id'), changes).then(() => { + _.each(changes, (change) => { + let pageContent = _.findWhere(this.get('model.pages'), { + id: change.pageId + }); + + if (is.not.undefined(pageContent)) { + pageContent.set('level', change.level); + } + }); + + let pages = this.get('model.pages'); + pages = pages.sortBy('sequence'); + this.set('model.pages', []); + this.set('model.pages', pages); + this.get('target.router').refresh(); + }); + }, + + onSaveTemplate(name, desc) { + this.get('templateService').saveAsTemplate(this.get('model.document.id'), name, desc).then(function () {}); + }, + + onSaveMeta(doc) { + this.get('documentService').save(doc).then(() => { + this.transitionToRoute('document.index'); + }); + }, + + onAddSection(section) { + this.audit.record("added-section-" + section.get('contentType')); + + let page = { + documentId: this.get('model.document.id'), + title: `${section.get('title')}`, + level: 1, + sequence: 0, + body: "", + contentType: section.get('contentType'), + pageType: section.get('pageType') + }; + + let meta = { + documentId: this.get('model.document.id'), + rawBody: "", + config: "" + }; + + let model = { + page: page, + meta: meta + }; + + this.get('documentService').addPage(this.get('model.document.id'), model).then((newPage) => { + let data = this.get('store').normalize('page', newPage); + this.get('store').push(data); + + this.get('documentService').getPages(this.get('model.document.id')).then((pages) => { + this.set('model.pages', pages.filterBy('pageType', 'section')); + this.set('model.tabs', pages.filterBy('pageType', 'tab')); + + this.get('documentService').getPageMeta(this.get('model.document.id'), newPage.id).then(() => { + this.transitionToRoute('document.edit', + this.get('model.folder.id'), + this.get('model.folder.slug'), + this.get('model.document.id'), + this.get('model.document.slug'), + newPage.id); + }); + }); + }); + }, + + onInsertBlock(block) { + this.audit.record("added-content-block-" + block.get('contentType')); + + let page = { + documentId: this.get('model.document.id'), + title: `${block.get('title')}`, + level: 1, + sequence: 0, + body: block.get('body'), + contentType: block.get('contentType'), + pageType: block.get('pageType'), + blockId: block.get('id') + }; + + let meta = { + documentId: this.get('model.document.id'), + rawBody: block.get('rawBody'), + config: block.get('config'), + externalSource: block.get('externalSource') + }; + + let model = { + page: page, + meta: meta + }; + + this.get('documentService').addPage(this.get('model.document.id'), model).then((newPage) => { + let data = this.get('store').normalize('page', newPage); + this.get('store').push(data); + + this.get('documentService').getPages(this.get('model.document.id')).then((pages) => { + this.set('model.pages', pages.filterBy('pageType', 'section')); + this.set('model.tabs', pages.filterBy('pageType', 'tab')); + + this.get('documentService').getPageMeta(this.get('model.document.id'), newPage.id).then(() => { + this.transitionToRoute('document.edit', + this.get('model.folder.id'), + this.get('model.folder.slug'), + this.get('model.document.id'), + this.get('model.document.slug'), + newPage.id); + }); + }); + }); + }, + + onDeleteBlock(blockId) { + this.get('sectionService').deleteBlock(blockId).then(() => { + this.audit.record("deleted-block"); + this.send("showNotification", "Deleted"); + this.transitionToRoute('document.index'); + }); + }, + + onDocumentDelete() { + this.get('documentService').deleteDocument(this.get('model.document.id')).then(() => { + this.audit.record("deleted-page"); + this.send("showNotification", "Deleted"); + this.transitionToRoute('folder', this.get('model.folder.id'), this.get('model.folder.slug')); + }); + } + } +}); + + +********************************** +********************************** +***** document/index +********************************** +********************************** + +{{document/document-view document=model.document links=model.links allPages=model.allPages tabs=model.tabs pages=model.pages folder=model.folder folders=model.folders isEditor=model.isEditor gotoPage=(action 'gotoPage') onAddBlock=(action 'onAddBlock') onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onPageDeleted')}} + + +// 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 Ember from 'ember'; +import NotifierMixin from '../../../mixins/notifier'; + +export default Ember.Controller.extend(NotifierMixin, { + documentService: Ember.inject.service('document'), + sectionService: Ember.inject.service('section'), + queryParams: ['page'], + + // Jump to the right part of the document. + scrollToPage(pageId) { + Ember.run.schedule('afterRender', function () { + let dest; + let target = "#page-title-" + pageId; + let targetOffset = $(target).offset(); + + if (is.undefined(targetOffset)) { + return; + } + + dest = targetOffset.top > $(document).height() - $(window).height() ? $(document).height() - $(window).height() : targetOffset.top; + // small correction to ensure we also show page title + dest = dest > 50 ? dest - 74 : dest; + + $("html,body").animate({ + scrollTop: dest + }, 500, "linear"); + $(".toc-index-item").removeClass("selected"); + $("#index-" + pageId).addClass("selected"); + }); + }, + + actions: { + gotoPage(pageId) { + if (is.null(pageId)) { + return; + } + + this.scrollToPage(pageId); + }, + + onPageSequenceChange(changes) { + this.get('documentService').changePageSequence(this.get('model.document.id'), changes).then(() => { + _.each(changes, (change) => { + let pageContent = _.findWhere(this.get('model.pages'), { + id: change.pageId + }); + + if (is.not.undefined(pageContent)) { + pageContent.set('sequence', change.sequence); + } + }); + + this.set('model.pages', this.get('model.pages').sortBy('sequence')); + this.get('target.router').refresh(); + }); + }, + + onPageLevelChange(changes) { + this.get('documentService').changePageLevel(this.get('model.document.id'), changes).then(() => { + _.each(changes, (change) => { + let pageContent = _.findWhere(this.get('model.pages'), { + id: change.pageId + }); + + if (is.not.undefined(pageContent)) { + pageContent.set('level', change.level); + } + }); + + let pages = this.get('model.pages'); + pages = pages.sortBy('sequence'); + this.set('model.pages', pages); + + this.get('target.router').refresh(); + }); + }, + + onAddBlock(block) { + this.get('sectionService').addBlock(block).then(() => { + this.showNotification("Published"); + }); + }, + + onCopyPage(pageId, targetDocumentId) { + let documentId = this.get('model.document.id'); + this.get('documentService').copyPage(documentId, pageId, targetDocumentId).then(() => { + this.showNotification("Copied"); + + // refresh data if copied to same document + if (documentId === targetDocumentId) { + this.get('target.router').refresh(); + } + }); + }, + + onMovePage(pageId, targetDocumentId) { + let documentId = this.get('model.document.id'); + + this.get('documentService').copyPage(documentId, pageId, targetDocumentId).then(() => { + this.showNotification("Moved"); + + this.send('onPageDeleted', { id: pageId, children: false }); + }); + }, + + onPageDeleted(deletePage) { + let documentId = this.get('model.document.id'); + let pages = this.get('model.pages'); + let deleteId = deletePage.id; + let deleteChildren = deletePage.children; + let page = _.findWhere(pages, { + id: deleteId + }); + let pageIndex = _.indexOf(pages, page, false); + let pendingChanges = []; + + this.audit.record("deleted-page"); + + // select affected pages + for (var i = pageIndex + 1; i < pages.get('length'); i++) { + if (pages[i].get('level') <= page.get('level')) { + break; + } + + pendingChanges.push({ + pageId: pages[i].get('id'), + level: pages[i].get('level') - 1 + }); + } + + if (deleteChildren) { + // nuke of page tree + 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('model.pages', _.reject(pages, function (p) { //jshint ignore: line + return p.get('id') === pageId; + })); + } + + this.set('model.pages', _.sortBy(pages, "sequence")); + this.get('target.router').refresh(); + }); + } else { + // page delete followed by re-leveling child pages + this.get('documentService').deletePage(documentId, deleteId).then(() => { + this.set('model.pages', _.reject(pages, function (p) { + return p.get('id') === deleteId; + })); + + this.send('onPageLevelChange', pendingChanges); + }); + } + } + } +}); diff --git a/app/app/styles/color.scss b/app/app/styles/color.scss index 9664e62e..7c75ac12 100644 --- a/app/app/styles/color.scss +++ b/app/app/styles/color.scss @@ -41,6 +41,8 @@ $color-symbol-icon: #a4b8be; $color-table-border: #e1e1e1; $color-table-header: #f5f5f5; +$color-toolbar: #eeeeee; +$color-wysiwyg: #3c3c3c; .color-white { color: $color-white !important; diff --git a/app/app/styles/font.scss b/app/app/styles/font.scss index 79ba89ac..c752429c 100644 --- a/app/app/styles/font.scss +++ b/app/app/styles/font.scss @@ -30,9 +30,9 @@ font-style: normal; } -$font-regular: 'open_sansregular'; -$font-semibold: 'open_sanssemibold'; -$font-light: 'open_sanslight'; +$font-regular: Helvetica; +$font-semibold: Helvetica; +$font-light: Helvetica; @font-face { font-family: "Material Icons"; diff --git a/app/app/styles/view/document/all.scss b/app/app/styles/view/document/all.scss index a7c42597..f288e1bb 100644 --- a/app/app/styles/view/document/all.scss +++ b/app/app/styles/view/document/all.scss @@ -4,6 +4,7 @@ @import "editor.scss"; @import "files.scss"; @import "history.scss"; +@import "layout.scss"; @import "sidebar.scss"; @import "toolbar.scss"; @import "wizard.scss"; diff --git a/app/app/styles/view/document/content.scss b/app/app/styles/view/document/content.scss index ed980dc5..abfc14a9 100644 --- a/app/app/styles/view/document/content.scss +++ b/app/app/styles/view/document/content.scss @@ -1,45 +1,72 @@ -.wiki-layout { - +.zone-document { + min-height: 500px; //ensure dropdowns render in viewport + height: 100%; + margin-left: 60px; + padding: 30px 70px; + z-index: 777; + background-color: $color-off-white; } -.doc-layout { - padding: 60px 50px; - box-shadow: 0 0 0 0.75pt $color-stroke,0 0 3pt 0.75pt $color-stroke; - margin: 30px 40px 50px 40px; +.zone-document-content { + > .back-to-space { + margin: 10px 0; + + > a { + vertical-align: middle; + color: $color-primary; + font-size: 1rem; + + > .material-icons { + font-size: 1rem; + vertical-align: middle; + } + } + } + + .doc-title { + margin: 30px 0 10px; + font-weight: bold; + } + + .doc-excerpt { + font-size: 1rem; + color: $color-gray; + margin: 0 0 75px; + } + + .edit-document-heading { + margin-top: 30px; + + .edit-doc-title { + > input { + font-weight: bold; + font-size: 2rem; + margin: 0 0 10px; + color: $color-wysiwyg; + } + } + + .edit-doc-excerpt { + font-size: 1rem; + margin: 0 0 10px; + color: $color-gray; + } + } } .document-view { - .print-title { - display: none; - font-size: 2.3em; - font-weight: bold; - color:$color-black; - margin-bottom: 20px; - margin-top: 0; - } - - .non-printable-message { - display: none; - font-size: 1em; - font-style: italic; - color: $color-gray; - } - - .is-template { - color: $color-goldy; - font-weight: bold; - font-size: 1.5em; - margin-bottom: 30px; - padding-bottom: 5px; - border-bottom: 1px dotted $color-goldy; - } - > .pages { - margin: 30px 0 50px 0; + margin: 30px 0 50px; > .wysiwyg { > .is-a-page { @extend .transition-all; + @include border-radius(2px); + @include ease-in(); + padding: 50px; + box-shadow: 0 0 0 0.75pt $color-stroke,0 0 3pt 0.75pt $color-stroke; + margin: 30px 0; + background-color: $color-white; &:hover { .page-title { @@ -66,4 +93,4 @@ .dropdown-page-toolbar { width: 300px; -} \ No newline at end of file +} diff --git a/app/app/styles/view/document/layout.scss b/app/app/styles/view/document/layout.scss new file mode 100644 index 00000000..0eed9952 --- /dev/null +++ b/app/app/styles/view/document/layout.scss @@ -0,0 +1,80 @@ +#wrapper { + padding-right: 0; + margin-left: 60px; + -webkit-transition: all 0.5s ease; + -moz-transition: all 0.5s ease; + -o-transition: all 0.5s ease; + transition: all 0.5s ease; + background-color: $color-off-white; +} + +$document-sidebar-width: 400px; + +#wrapper.toggled { + // padding-right: $document-sidebar-width; +} + +#sidebar-wrapper { + // z-index: 1000; + z-index: 888; + position: fixed; + right: $document-sidebar-width; + width: 0; + height: 100%; + margin-right: -$document-sidebar-width; + overflow-y: auto; + background: $color-off-white; + -webkit-transition: all 0.5s ease; + -moz-transition: all 0.5s ease; + -o-transition: all 0.5s ease; + transition: all 0.5s ease; + border-left: 1px solid $color-stroke; +} + +#wrapper.toggled #sidebar-wrapper { + width: $document-sidebar-width; +} + +#page-content-wrapper { + width: 100%; + position: absolute; + padding: 30px 70px; +} + +#wrapper.toggled #page-content-wrapper { + position: absolute; + margin-left: -$document-sidebar-width; +} + +@media(min-width:768px) { + #wrapper { + padding-right: $document-sidebar-width; + } + + #wrapper.toggled { + padding-left: 0; + padding-right: 0; + } + + #sidebar-wrapper { + width: $document-sidebar-width; + } + + #wrapper.toggled #sidebar-wrapper { + width: 0; + + .document-sidebar-toolbar { + position: relative; + } + } + + #page-content-wrapper { + padding: 20px 60px; + position: relative; + } + + #wrapper.toggled #page-content-wrapper { + position: relative; + margin-left: 0; + } +} diff --git a/app/app/styles/view/document/sidebar.scss b/app/app/styles/view/document/sidebar.scss index a34c25b6..8936cd92 100644 --- a/app/app/styles/view/document/sidebar.scss +++ b/app/app/styles/view/document/sidebar.scss @@ -1,6 +1,33 @@ -.document-sidebar { +.document-sidebar-content { + display: inline-block; + width: 340px; + padding: 40px 20px; +} + +.document-sidebar-toolbar { + display: inline-block; + width: 60px; + background-color: $color-toolbar; + text-align: center; + position: fixed; + right: 0; + top: 0; + height: 100%; + padding: 50px 0 0 0; +} + +.xxxx { + + .is-template { + color: $color-goldy; + font-weight: bold; + font-size: 1.5em; + margin-bottom: 30px; + padding-bottom: 5px; + border-bottom: 1px dotted $color-goldy; + } + @extend .no-select; - width: 100%; .stuck-toc { position: fixed; diff --git a/app/app/styles/view/document/wysiwyg.scss b/app/app/styles/view/document/wysiwyg.scss index e34b58d3..2fc7ef4d 100644 --- a/app/app/styles/view/document/wysiwyg.scss +++ b/app/app/styles/view/document/wysiwyg.scss @@ -1,115 +1,91 @@ .wysiwyg { - // font-size: 1rem; - font-size: 15px; - line-height: 30px; - color: #3c3c3c; - - table - { - // width: auto !important; + font-size: 17px; + line-height: 30px; + color: $color-wysiwyg; + table { @include border(1px); - td - { + td { padding: 5px 7px !important; @include border(1px); - p - { + p { margin: 0 !important; padding: 0 !important; } } } - ol, ul - { + ol, + ul { margin: 15px 0; padding: 0 0 0 40px; } - ul - { - li - { + ul { + li { list-style-type: disc; } } - b - { - font-family: $font-semibold; - } - h1 { - font-size: 2em; - font-family: $font-semibold; + font-size: 2rem; + font-weight: normal; } - // h1 - // { - // font-size: 1.6em; - // font-family: open_sanslight; - // color:$color-off-black; - // margin: 0 0 20px 0; - // } - - h2 - { - font-size: 1.7em; - margin: 30px 0 20px 0; - font-family: $font-regular; + h2 { + font-size: 1.7rem; + margin: 30px 0 20px; } - h3 - { + h3 { font-size: 1.5rem; - margin: 30px 0 20px 0; - font-family: $font-regular; + margin: 30px 0 20px; } - h4 - { + h4 { font-size: 1.3rem; - margin: 30px 0 20px 0; - font-family: $font-regular; + margin: 30px 0 20px; } - h5, h6, h7, h8, h9 - { + h5, + h6, + h7, + h8, + h9 { font-size: 1.3rem; - margin: 30px 0 20px 0; - font-family: $font-regular; + margin: 30px 0 20px; } - h2, h3, h4, h5, h6, h7, h8, h9 - { - .page-title - { - color:$color-off-black; - font-family: $font-regular; + h2, + h3, + h4, + h5, + h6, + h7, + h8, + h9 { + .page-title { + color: $color-off-black; } } - pre - { + pre { background-color: $color-off-white; padding: 10px; border: 1px solid $color-border; @include border-radius(3px); } - .code-mirror - { + .code-mirror { background-color: none; padding: 10px; border: none; @include border-radius(0px); } - .wysiwyg-table - { + .wysiwyg-table { border: none; border-collapse: collapse; empty-cells: show; @@ -118,30 +94,36 @@ .fr-dashed-borders td, .fr-dashed-borders th { - border-style: dashed; + border-style: dashed; } + .fr-alternate-rows tbody tr:nth-child(2n) { - background: #f5f5f5; + background: #f5f5f5; } + td, th { - border: 1px solid #f3f5f8; - padding: 5px 7px !important; + border: 1px solid #f3f5f8; + padding: 5px 7px !important; } + td:empty, th:empty { - height: 20px; + height: 20px; } + td.fr-highlighted, th.fr-highlighted { - border: 1px double red; + border: 1px double red; } + td.fr-thick, th.fr-thick { - border-width: 2px; + border-width: 2px; } + th { - background: #f7f6f6; + background: #f7f6f6; } } } diff --git a/app/app/styles/view/layout.scss b/app/app/styles/view/layout.scss index e99c8b09..1f2b298f 100644 --- a/app/app/styles/view/layout.scss +++ b/app/app/styles/view/layout.scss @@ -1,13 +1,14 @@ .zone-navigation { position: fixed; - margin: 0; - padding: 0; + top: 0; width: 60px; min-height: 100%; height: 100%; - background-color: $color-primary; + margin: 0; + padding: 0; z-index: 999; overflow-x: hidden; + background-color: $color-primary; > .bottom-zone, > .top-zone { diff --git a/app/app/styles/widget/widget-button.scss b/app/app/styles/widget/widget-button.scss index 30c575a6..e53f46d9 100644 --- a/app/app/styles/widget/widget-button.scss +++ b/app/app/styles/widget/widget-button.scss @@ -217,6 +217,13 @@ @include button-hover-state($color-white); } +.button-black { + border: 1px solid $color-stroke; + background-color: $color-off-black; + color: $color-white; + @include button-hover-state($color-black); +} + .button-transparent { background-color: transparent; color: $color-gray; diff --git a/app/app/styles/widget/widget-input.scss b/app/app/styles/widget/widget-input.scss index 903473b1..fb38f7f1 100644 --- a/app/app/styles/widget/widget-input.scss +++ b/app/app/styles/widget/widget-input.scss @@ -161,6 +161,16 @@ box-shadow: none !important; } } + + .error-inline { + border-left: 3px solid $color-red; + } +} + +.input-transparent { + > input, textarea { + background-color: transparent !important; + } } .form-bordered { diff --git a/app/app/templates/components/document/document-heading.hbs b/app/app/templates/components/document/document-heading.hbs new file mode 100644 index 00000000..b4818fef --- /dev/null +++ b/app/app/templates/components/document/document-heading.hbs @@ -0,0 +1,22 @@ +{{#unless editMode}} +
+

{{document.name}}

+
{{document.excerpt}}
+
+{{else}} +
+
+ {{focus-input id="document-name" type="text" value=docName class=(if hasNameError 'error-inline') placeholder="Name"}} +
+
+ {{input id="document-excerpt" type="text" value=docExcerpt class=(if hasExcerptError 'error-inline') placeholder="Excerpt"}} +
+
+ check +
+
+
+ close +
+
+{{/unless}} diff --git a/app/app/templates/components/document/document-view.hbs b/app/app/templates/components/document/document-view.hbs index 6b5f7b4f..0fcf6f40 100644 --- a/app/app/templates/components/document/document-view.hbs +++ b/app/app/templates/components/document/document-view.hbs @@ -1,23 +1,9 @@ -
- {{#if document.template}} -
TEMPLATE
- {{/if}} - -
-

{{document.name}}

-
- - {{document/tag-editor documentTags=document.tags isEditor=isEditor onChange=(action 'onTagChange')}} - - - +
{{#each pages key="id" as |page index|}}
- {{document/page-heading tagName=page.tagName document=document folder=folder page=page isEditor=isEditor + {{document/page-heading tagName=page.tagName document=document folder=folder page=page isEditor=isEditor onAddBlock=(action 'onAddBlock') onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onDeletePage')}} {{section/base-renderer page=page}}
diff --git a/app/app/templates/components/document/page-heading.hbs b/app/app/templates/components/document/page-heading.hbs index 014be543..57e75239 100644 --- a/app/app/templates/components/document/page-heading.hbs +++ b/app/app/templates/components/document/page-heading.hbs @@ -3,12 +3,12 @@