mirror of
https://github.com/documize/community.git
synced 2025-08-09 15:35:27 +02:00
revamped TOC logic with tests
This commit is contained in:
parent
c5403565ba
commit
549e8efa91
13 changed files with 638 additions and 306 deletions
18
.tern-project
Normal file
18
.tern-project
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"libs": [
|
||||
"browser",
|
||||
"jquery",
|
||||
"underscore",
|
||||
"is",
|
||||
"ember"
|
||||
],
|
||||
"loadEagerly": [
|
||||
"app/app/*.js"
|
||||
],
|
||||
"plugins": {
|
||||
"requirejs": {
|
||||
"baseURL": "./",
|
||||
"paths": {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@
|
|||
import Ember from 'ember';
|
||||
import NotifierMixin from '../../mixins/notifier';
|
||||
import TooltipMixin from '../../mixins/tooltip';
|
||||
import tocUtil from '../../utils/toc';
|
||||
|
||||
export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
||||
document: {},
|
||||
|
@ -77,332 +78,99 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
|||
// Controls what user can do with the toc (left sidebar).
|
||||
// Identifies the target pages.
|
||||
setState(pageId) {
|
||||
// defaults
|
||||
this.set('tocTools.UpTarget', "");
|
||||
this.set('tocTools.DownTarget', "");
|
||||
this.set('tocTools.AllowIndent', false);
|
||||
this.set('tocTools.AllowOutdent', false);
|
||||
this.set('actionablePage', false);
|
||||
this.set('upDisabled', true);
|
||||
this.set('downDisabled', true);
|
||||
this.set('indentDisabled', true);
|
||||
this.set('outdentDisabled', true);
|
||||
|
||||
if (!this.get('isEditor') || is.empty(pageId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.set('page', pageId);
|
||||
|
||||
var toc = this.get('pages');
|
||||
var page = _.findWhere(toc, {
|
||||
id: pageId
|
||||
});
|
||||
let toc = this.get('pages');
|
||||
let page = _.findWhere(toc, { id: pageId });
|
||||
|
||||
// handle root node
|
||||
if (is.undefined(page) || page.level === 1) {
|
||||
return;
|
||||
let state = tocUtil.getState(toc, page);
|
||||
|
||||
if (!this.get('isEditor') || is.empty(pageId)) {
|
||||
state.actionablePage = state.upDisabled = state.downDisabled = state.indentDisabled = state.outdentDisabled = false;
|
||||
}
|
||||
|
||||
var index = _.indexOf(toc, page, false);
|
||||
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var upPage = toc[index - 1];
|
||||
var downPage = toc[index + 1];
|
||||
|
||||
if (_.isUndefined(upPage)) {
|
||||
this.set('tocTools.UpTarget', "");
|
||||
}
|
||||
|
||||
if (_.isUndefined(downPage)) {
|
||||
this.set('tocTools.DownTarget', "");
|
||||
}
|
||||
|
||||
// can we go up?
|
||||
// can we indent?
|
||||
if (!_.isUndefined(upPage)) {
|
||||
// can only go up if someone is same or higher level?
|
||||
var index2 = _.indexOf(toc, upPage, false);
|
||||
|
||||
if (index2 !== -1) {
|
||||
// up
|
||||
for (var i = index2; i > 0; i--) {
|
||||
if (page.level > toc[i].level) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (page.level === toc[i].level) {
|
||||
this.set('tocTools.UpTarget', toc[i].id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// indent?
|
||||
for (var i2 = index2; i2 > 0; i2--) {
|
||||
if (toc[i2].level < page.level) {
|
||||
this.set('tocTools.AllowIndent', false);
|
||||
break;
|
||||
}
|
||||
|
||||
if (page.level === toc[i2].level) {
|
||||
this.set('tocTools.AllowIndent', true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if page above is root node then some things you can't do
|
||||
if (upPage.level === 1) {
|
||||
this.set('tocTools.AllowIndent', false);
|
||||
this.set('tocTools.UpTarget', "");
|
||||
}
|
||||
}
|
||||
|
||||
// can we go down?
|
||||
if (!_.isUndefined(downPage)) {
|
||||
// can only go down if someone below is at our level or higher
|
||||
var index3 = _.indexOf(toc, downPage, false);
|
||||
|
||||
if (index3 !== -1) {
|
||||
for (var i3 = index3; i3 < toc.length; i3++) {
|
||||
if (toc[i3].level < page.level) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (page.level === toc[i3].level) {
|
||||
this.set('tocTools.DownTarget', toc[i3].id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (page.level > downPage.level) {
|
||||
this.set('tocTools.DownTarget', "");
|
||||
}
|
||||
}
|
||||
|
||||
// can we outdent?
|
||||
this.set('tocTools.AllowOutdent', page.level > 2);
|
||||
|
||||
this.set('upDisabled', this.get('tocTools.UpTarget') === "");
|
||||
this.set('downDisabled', this.get('tocTools.DownTarget') === "");
|
||||
this.set('indentDisabled', !this.get('tocTools.AllowIndent'));
|
||||
this.set('outdentDisabled', !this.get('tocTools.AllowOutdent'));
|
||||
|
||||
this.set('actionablePage',
|
||||
is.not.empty(this.get('tocTools.UpTarget')) ||
|
||||
is.not.empty(this.get('tocTools.DownTarget')) ||
|
||||
this.get('tocTools.AllowIndent') ||
|
||||
this.get('tocTools.AllowOutdent'));
|
||||
this.set('state', state);
|
||||
},
|
||||
|
||||
actions: {
|
||||
// Page up - above pages shunt down.
|
||||
pageUp() {
|
||||
if (this.upDisabled) {
|
||||
if (this.get('state.upDisabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pages = this.get('pages');
|
||||
var current = _.findWhere(pages, {
|
||||
id: this.get('page')
|
||||
});
|
||||
var page1 = _.findWhere(pages, {
|
||||
id: this.tocTools.UpTarget
|
||||
});
|
||||
var page2 = null;
|
||||
var pendingChanges = [];
|
||||
|
||||
if (is.undefined(current) || is.undefined(page1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var index1 = _.indexOf(pages, page1, false);
|
||||
|
||||
if (index1 !== -1 && index1 > 1) {
|
||||
page2 = pages[index1 - 1];
|
||||
}
|
||||
|
||||
var sequence1 = page1.sequence;
|
||||
var sequence2 = is.not.null(page2) ? page2.sequence : 1024;
|
||||
|
||||
var index = _.indexOf(pages, current, false);
|
||||
|
||||
if (index !== -1) {
|
||||
var sequence = (sequence1 + sequence2) / 2;
|
||||
|
||||
pendingChanges.push({
|
||||
pageId: current.id,
|
||||
sequence: sequence
|
||||
});
|
||||
|
||||
for (var i = index + 1; i < pages.length; i++) {
|
||||
if (pages[i].level <= current.level) {
|
||||
break;
|
||||
}
|
||||
|
||||
sequence = (sequence + page1.sequence) / 2;
|
||||
|
||||
pendingChanges.push({
|
||||
pageId: pages[i].id,
|
||||
sequence: sequence
|
||||
});
|
||||
}
|
||||
}
|
||||
let state = this.get('state');
|
||||
let pages = this.get('pages');
|
||||
let page = _.findWhere(pages, { id: this.get('page') });
|
||||
let pendingChanges = tocUtil.moveUp(state, pages, page);
|
||||
|
||||
if (pendingChanges.length > 0) {
|
||||
this.attrs.changePageSequence(pendingChanges);
|
||||
|
||||
this.send('onEntryClick', this.get('page'));
|
||||
this.audit.record("moved-page-up");
|
||||
this.showNotification("Moved up");
|
||||
}
|
||||
},
|
||||
|
||||
// Move down -- pages below shift up.
|
||||
pageDown() {
|
||||
if (this.downDisabled) {
|
||||
if (this.get('state.downDisabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
let state = this.get('state');
|
||||
var pages = this.get('pages');
|
||||
var current = _.findWhere(pages, {
|
||||
id: this.get('page')
|
||||
});
|
||||
var pageIndex = _.indexOf(pages, current, false);
|
||||
var downTarget = _.findWhere(pages, {
|
||||
id: this.tocTools.DownTarget
|
||||
});
|
||||
var downTargetIndex = _.indexOf(pages, downTarget, false);
|
||||
var pendingChanges = [];
|
||||
|
||||
if (pageIndex === -1 || downTargetIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var startingSequence = 0;
|
||||
var upperSequence = 0;
|
||||
var cutOff = _.rest(pages, downTargetIndex);
|
||||
var siblings = _.reject(cutOff, function(p) {
|
||||
return p.level !== current.level || p.id === current.id || p.id === downTarget.id;
|
||||
});
|
||||
|
||||
if (siblings.length > 0) {
|
||||
var aboveThisGuy = siblings[0];
|
||||
var belowThisGuy = pages[_.indexOf(pages, aboveThisGuy, false) - 1];
|
||||
|
||||
if (is.not.null(belowThisGuy) && belowThisGuy.level > current.level) {
|
||||
startingSequence = (aboveThisGuy.sequence + belowThisGuy.sequence) / 2;
|
||||
upperSequence = aboveThisGuy.sequence;
|
||||
} else {
|
||||
var otherGuy = pages[downTargetIndex + 1];
|
||||
|
||||
startingSequence = (otherGuy.sequence + downTarget.sequence) / 2;
|
||||
upperSequence = otherGuy.sequence;
|
||||
}
|
||||
} else {
|
||||
startingSequence = downTarget.sequence * 2;
|
||||
upperSequence = startingSequence * 2;
|
||||
}
|
||||
|
||||
pendingChanges.push({
|
||||
pageId: current.id,
|
||||
sequence: startingSequence
|
||||
});
|
||||
|
||||
var sequence = (startingSequence + upperSequence) / 2;
|
||||
|
||||
for (var i = pageIndex + 1; i < pages.length; i++) {
|
||||
if (pages[i].level <= current.level) {
|
||||
break;
|
||||
}
|
||||
|
||||
var sequence2 = (sequence + upperSequence) / 2;
|
||||
|
||||
pendingChanges.push({
|
||||
pageId: pages[i].id,
|
||||
sequence: sequence2
|
||||
});
|
||||
}
|
||||
var page = _.findWhere(pages, { id: this.get('page') });
|
||||
let pendingChanges = tocUtil.moveDown(state, pages, page);
|
||||
|
||||
if (pendingChanges.length > 0) {
|
||||
this.attrs.changePageSequence(pendingChanges);
|
||||
|
||||
this.send('onEntryClick', this.get('page'));
|
||||
this.audit.record("moved-page-down");
|
||||
this.showNotification("Moved down");
|
||||
}
|
||||
},
|
||||
|
||||
// Indent - changes a page from H2 to H3, etc.
|
||||
pageIndent() {
|
||||
if (this.indentDisabled) {
|
||||
if (this.get('state.indentDisabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
let state = this.get('state');
|
||||
var pages = this.get('pages');
|
||||
var current = _.findWhere(pages, {
|
||||
id: this.get('page')
|
||||
});
|
||||
var pageIndex = _.indexOf(pages, current, false);
|
||||
var pendingChanges = [];
|
||||
|
||||
pendingChanges.push({
|
||||
pageId: current.id,
|
||||
level: current.level + 1
|
||||
});
|
||||
|
||||
for (var i = pageIndex + 1; i < pages.length; i++) {
|
||||
if (pages[i].level <= current.level) {
|
||||
break;
|
||||
}
|
||||
|
||||
pendingChanges.push({
|
||||
pageId: pages[i].id,
|
||||
level: pages[i].level + 1
|
||||
});
|
||||
}
|
||||
var page = _.findWhere(pages, { id: this.get('page') });
|
||||
let pendingChanges = tocUtil.indent(state, pages, page);
|
||||
|
||||
if (pendingChanges.length > 0) {
|
||||
this.attrs.changePageLevel(pendingChanges);
|
||||
|
||||
this.showNotification("Indent");
|
||||
this.audit.record("changed-page-sequence");
|
||||
this.send('onEntryClick', this.get('page'));
|
||||
}
|
||||
},
|
||||
|
||||
// Outdent - changes a page from H3 to H2, etc.
|
||||
pageOutdent() {
|
||||
if (this.outdentDisabled) {
|
||||
if (this.get('state.outdentDisabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
let state = this.get('state');
|
||||
var pages = this.get('pages');
|
||||
var current = _.findWhere(pages, {
|
||||
id: this.get('page')
|
||||
});
|
||||
var pageIndex = _.indexOf(pages, current, false);
|
||||
var pendingChanges = [];
|
||||
|
||||
pendingChanges.push({
|
||||
pageId: current.id,
|
||||
level: current.level - 1
|
||||
});
|
||||
|
||||
for (var i = pageIndex + 1; i < pages.length; i++) {
|
||||
if (pages[i].level <= current.level) {
|
||||
break;
|
||||
}
|
||||
|
||||
pendingChanges.push({
|
||||
pageId: pages[i].id,
|
||||
level: pages[i].level - 1
|
||||
});
|
||||
}
|
||||
var page = _.findWhere(pages, { id: this.get('page') });
|
||||
let pendingChanges = tocUtil.outdent(state, pages, page);
|
||||
|
||||
if (pendingChanges.length > 0) {
|
||||
this.attrs.changePageLevel(pendingChanges);
|
||||
|
||||
this.showNotification("Outdent");
|
||||
this.audit.record("changed-page-sequence");
|
||||
this.send('onEntryClick', this.get('page'));
|
||||
}
|
||||
},
|
||||
|
||||
onEntryClick(id) {
|
||||
|
|
|
@ -16,14 +16,14 @@ export default Ember.Component.extend({
|
|||
tagName: "li",
|
||||
classNames: ["item"],
|
||||
|
||||
indentLevel: Ember.computed('page', function() {
|
||||
let nodeLevel = this.get('page.level');
|
||||
let indent = (nodeLevel - 1) * 20;
|
||||
return indent;
|
||||
}),
|
||||
// indentLevel: Ember.computed('page', function() {
|
||||
// let nodeLevel = this.get('page.level');
|
||||
// let indent = (nodeLevel - 1) * 20;
|
||||
// return indent;
|
||||
// }),
|
||||
|
||||
didReceiveAttrs() {
|
||||
this.set('classNames', ["item", "margin-left-" + this.get("indentLevel")]);
|
||||
// this.set('classNames', ["item", "margin-left-" + this.get("page.tocIndent")]);
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
|
|
@ -19,7 +19,7 @@ export default Ember.Controller.extend(NotifierMixin, {
|
|||
let page = models.PageModel.create({
|
||||
documentId: this.get('model.document.id'),
|
||||
title: `${section.title} Section`,
|
||||
level: 2,
|
||||
level: 1,
|
||||
sequence: 2048,
|
||||
body: "",
|
||||
contentType: section.contentType
|
||||
|
|
|
@ -16,9 +16,9 @@ $color-primary: #084d85;
|
|||
$color-link: #5abc67;
|
||||
$color-attachment: #2180cc;
|
||||
$color-red: #d9493c;
|
||||
$color-green: #5abc67;
|
||||
$color-green: #1c962b;
|
||||
$color-blue: #084d85;
|
||||
$color-gray: #99a2ac;
|
||||
$color-gray: #8b9096;
|
||||
$color-background: #f8f8f8;
|
||||
$color-tooltip: #4c4c4c;
|
||||
$color-toast: #4c4c4c;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.avatar {
|
||||
color: $color-white;
|
||||
background-color: $color-stroke;
|
||||
background-color: $color-gray;
|
||||
@include border-radius(20px);
|
||||
padding: 8px 0 0 0;
|
||||
letter-spacing: 1px;
|
||||
|
@ -11,7 +11,7 @@
|
|||
|
||||
.avatar-large {
|
||||
color: $color-white;
|
||||
background-color: $color-stroke;
|
||||
background-color: $color-gray;
|
||||
@include border-radius(100px);
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
<div class="document-structure hidden-xs hidden-sm">
|
||||
{{#if this.session.authenticated}}
|
||||
<div id="tocToolbar" class="toc-controls {{if actionablePage 'current-page' ''}}">
|
||||
<div id="toc-up-button" class="round-button-mono {{if upDisabled 'disabled'}}" data-tooltip="Move up" data-tooltip-position="top center" {{action 'pageUp'}}>
|
||||
<div id="tocToolbar" class="toc-controls {{if state.actionablePage 'current-page' ''}}">
|
||||
<div id="toc-up-button" class="round-button-mono {{if state.upDisabled 'disabled'}}" data-tooltip="Move up" data-tooltip-position="top center" {{action 'pageUp'}}>
|
||||
<i class="material-icons">arrow_upward</i>
|
||||
</div>
|
||||
<div class="button-gap" />
|
||||
<div id="toc-down-button" class="round-button-mono {{if downDisabled 'disabled'}}" data-tooltip="Move down" data-tooltip-position="top center" {{action 'pageDown'}}>
|
||||
<div id="toc-down-button" class="round-button-mono {{if state.downDisabled 'disabled'}}" data-tooltip="Move down" data-tooltip-position="top center" {{action 'pageDown'}}>
|
||||
<i class="material-icons">arrow_downward</i>
|
||||
</div>
|
||||
<div class="button-gap" />
|
||||
<div id="toc-outdent-button" class="round-button-mono {{if outdentDisabled 'disabled'}}" data-tooltip="Outdent" data-tooltip-position="top center" {{action 'pageOutdent'}}>
|
||||
<div id="toc-outdent-button" class="round-button-mono {{if state.outdentDisabled 'disabled'}}" data-tooltip="Outdent" data-tooltip-position="top center" {{action 'pageOutdent'}}>
|
||||
<i class="material-icons">format_indent_decrease</i>
|
||||
</div>
|
||||
<div class="button-gap" />
|
||||
<div id="toc-indent-button" class="round-button-mono {{if indentDisabled 'disabled'}}" data-tooltip="Indent" data-tooltip-position="top center" {{action 'pageIndent'}}>
|
||||
<div id="toc-indent-button" class="round-button-mono {{if state.indentDisabled 'disabled'}}" data-tooltip="Indent" data-tooltip-position="top center" {{action 'pageIndent'}}>
|
||||
<i class="material-icons">format_indent_increase</i>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<a id="index-{{page.id}}" class="link toc-index-item" {{action 'onClick' page.id}}>{{page.title}}</a>
|
||||
<a id="index-{{page.id}}" class="link toc-index-item {{page.tocIndentCss}}" {{action 'onClick' page.id}}>{{page.title}}</a>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{{#dropdown-dialog target="start-document-button" position="bottom right" button="Add" color="flat-green" onAction=(action 'startDocument') button2=canEditTemplate color2="flat-blue" onAction2=(action 'editTemplate') onOpenCallback=(action 'onOpenCallback')}}
|
||||
{{#dropdown-dialog target="start-document-button" position="bottom right" button="Start" color="flat-green" onAction=(action 'startDocument') button2=canEditTemplate color2="flat-blue" onAction2=(action 'editTemplate') onOpenCallback=(action 'onOpenCallback')}}
|
||||
<div class="upload-container">
|
||||
<div id="upload-documents" class="regular-button button-green">Import Word / Markdown</div>
|
||||
</div>
|
||||
|
|
|
@ -198,7 +198,16 @@ let PageModel = BaseModel.extend({
|
|||
|
||||
tagName: Ember.computed('level', function() {
|
||||
return "h" + this.get('level');
|
||||
})
|
||||
}),
|
||||
|
||||
tocIndent: Ember.computed('level', function() {
|
||||
return (this.get('level') - 1) * 20;
|
||||
}),
|
||||
|
||||
tocIndentCss: Ember.computed('tocIndent', function() {
|
||||
let tocIndent = this.get('tocIndent');
|
||||
return `margin-left-${tocIndent}`;
|
||||
}),
|
||||
});
|
||||
|
||||
let PageMetaModel = BaseModel.extend({
|
||||
|
|
286
app/app/utils/toc.js
Normal file
286
app/app/utils/toc.js
Normal file
|
@ -0,0 +1,286 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
||||
//
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
//
|
||||
// You can operate outside the AGPL restrictions by purchasing
|
||||
// Documize Enterprise Edition and obtaining a commercial license
|
||||
// by contacting <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
function getState(toc, page) {
|
||||
let state = {
|
||||
tocTools: {
|
||||
upTarget: "",
|
||||
downTarget: "",
|
||||
indentIncrement: 0,
|
||||
allowIndent: false,
|
||||
allowOutdent: false
|
||||
},
|
||||
actionablePage: false,
|
||||
upDisabled: true,
|
||||
downDisabled: true,
|
||||
indentDisabled: true,
|
||||
outdentDisabled: true,
|
||||
};
|
||||
|
||||
if (is.undefined(page)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
var index = _.indexOf(toc, page, false);
|
||||
|
||||
if (index === -1) {
|
||||
return state;
|
||||
}
|
||||
|
||||
var upPage = toc[index - 1];
|
||||
var downPage = toc[index + 1];
|
||||
|
||||
if (_.isUndefined(upPage)) {
|
||||
state.tocTools.upTarget = '';
|
||||
}
|
||||
|
||||
if (_.isUndefined(downPage)) {
|
||||
state.tocTools.downTarget = '';
|
||||
}
|
||||
|
||||
// can we go up?
|
||||
// can we indent?
|
||||
if (!_.isUndefined(upPage)) {
|
||||
// can only go up if someone is same or higher level?
|
||||
var index2 = _.indexOf(toc, upPage, false);
|
||||
|
||||
if (index2 !== -1) {
|
||||
// up
|
||||
for (var i = index2; i >= 0; i--) {
|
||||
if (page.level > toc[i].level) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (page.level === toc[i].level) {
|
||||
state.tocTools.upTarget = toc[i].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// indent?
|
||||
state.tocTools.allowIndent = upPage.level >= page.level;
|
||||
state.tocTools.indentIncrement = upPage.level - page.level;
|
||||
|
||||
if (state.tocTools.indentIncrement === 0) {
|
||||
state.tocTools.indentIncrement = 1;
|
||||
}
|
||||
|
||||
// for (var i2 = index2; i2 >= 0; i2--) {
|
||||
// if (page.level < toc[i2].level) {
|
||||
// state.tocTools.allowIndent = false;
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// if (page.level === toc[i2].level) {
|
||||
// state.tocTools.allowIndent = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// can we go down?
|
||||
if (!_.isUndefined(downPage)) {
|
||||
// can only go down if someone below is at our level or higher
|
||||
var index3 = _.indexOf(toc, downPage, false);
|
||||
|
||||
if (index3 !== -1) {
|
||||
for (var i3 = index3; i3 < toc.length; i3++) {
|
||||
if (toc[i3].level < page.level) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (page.level === toc[i3].level) {
|
||||
state.tocTools.downTarget = toc[i3].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (page.level > downPage.level) {
|
||||
state.tocTools.downTarget = '';
|
||||
}
|
||||
}
|
||||
|
||||
// can we outdent?
|
||||
state.tocTools.allowOutdent = page.level > 1;
|
||||
|
||||
state.upDisabled = state.tocTools.upTarget === '';
|
||||
state.downDisabled = state.tocTools.downTarget === '';
|
||||
state.indentDisabled = !state.tocTools.allowIndent;
|
||||
state.outdentDisabled = !state.tocTools.allowOutdent;
|
||||
|
||||
state.actionablePage = is.not.empty(state.tocTools.upTarget) ||
|
||||
is.not.empty(state.tocTools.downTarget) ||
|
||||
state.tocTools.allowIndent || state.tocTools.allowOutdent;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
// move up page and any associated kids
|
||||
function moveUp(state, pages, current) {
|
||||
var page1 = _.findWhere(pages, { id: state.tocTools.upTarget });
|
||||
var page2 = null;
|
||||
var pendingChanges = [];
|
||||
|
||||
if (is.undefined(current) || is.undefined(page1)) {
|
||||
return pendingChanges;
|
||||
}
|
||||
|
||||
var index1 = _.indexOf(pages, page1, false);
|
||||
|
||||
if (index1 !== -1) {
|
||||
page2 = pages[index1 - 1];
|
||||
}
|
||||
|
||||
var sequence1 = page1.sequence;
|
||||
var sequence2 = is.not.null(page2) && is.not.undefined(page2) ? page2.sequence : 0;
|
||||
var index = _.indexOf(pages, current, false);
|
||||
|
||||
if (index !== -1) {
|
||||
var sequence = (sequence1 + sequence2) / 2;
|
||||
|
||||
pendingChanges.push({
|
||||
pageId: current.id,
|
||||
sequence: sequence
|
||||
});
|
||||
|
||||
for (var i = index + 1; i < pages.length; i++) {
|
||||
if (pages[i].level <= current.level) {
|
||||
break;
|
||||
}
|
||||
|
||||
sequence = (sequence + page1.sequence) / 2;
|
||||
|
||||
pendingChanges.push({
|
||||
pageId: pages[i].id,
|
||||
sequence: sequence
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return pendingChanges;
|
||||
}
|
||||
|
||||
// move down page and any associated kids
|
||||
function moveDown(state, pages, current) {
|
||||
var pageIndex = _.indexOf(pages, current, false);
|
||||
var downTarget = _.findWhere(pages, { id: state.tocTools.downTarget });
|
||||
var downTargetIndex = _.indexOf(pages, downTarget, false);
|
||||
var pendingChanges = [];
|
||||
|
||||
if (pageIndex === -1 || downTargetIndex === -1) {
|
||||
return pendingChanges;
|
||||
}
|
||||
|
||||
var startingSequence = 0;
|
||||
var upperSequence = 0;
|
||||
var cutOff = _.rest(pages, downTargetIndex);
|
||||
var siblings = _.reject(cutOff, function(p) {
|
||||
return p.level !== current.level || p.id === current.id || p.id === downTarget.id;
|
||||
});
|
||||
|
||||
if (siblings.length > 0) {
|
||||
var aboveThisGuy = siblings[0];
|
||||
var belowThisGuy = pages[_.indexOf(pages, aboveThisGuy, false) - 1];
|
||||
|
||||
if (is.not.null(belowThisGuy) && belowThisGuy.level > current.level) {
|
||||
startingSequence = (aboveThisGuy.sequence + belowThisGuy.sequence) / 2;
|
||||
upperSequence = aboveThisGuy.sequence;
|
||||
} else {
|
||||
var otherGuy = pages[downTargetIndex + 1];
|
||||
|
||||
startingSequence = (otherGuy.sequence + downTarget.sequence) / 2;
|
||||
upperSequence = otherGuy.sequence;
|
||||
}
|
||||
} else {
|
||||
// startingSequence = downTarget.sequence * 2;
|
||||
startingSequence = cutOff[cutOff.length-1].sequence * 2;
|
||||
upperSequence = startingSequence * 2;
|
||||
}
|
||||
|
||||
pendingChanges.push({
|
||||
pageId: current.id,
|
||||
sequence: startingSequence
|
||||
});
|
||||
|
||||
var sequence = (startingSequence + upperSequence) / 2;
|
||||
|
||||
for (var i = pageIndex + 1; i < pages.length; i++) {
|
||||
if (pages[i].level <= current.level) {
|
||||
break;
|
||||
}
|
||||
|
||||
var sequence2 = (sequence + upperSequence) / 2;
|
||||
|
||||
pendingChanges.push({
|
||||
pageId: pages[i].id,
|
||||
sequence: sequence2
|
||||
});
|
||||
}
|
||||
|
||||
return pendingChanges;
|
||||
}
|
||||
|
||||
// indent page and any associated kisds
|
||||
function indent(state, pages, current) {
|
||||
var pageIndex = _.indexOf(pages, current, false);
|
||||
var pendingChanges = [];
|
||||
|
||||
pendingChanges.push({
|
||||
pageId: current.id,
|
||||
level: current.level + state.tocTools.indentIncrement
|
||||
});
|
||||
|
||||
for (var i = pageIndex + 1; i < pages.length; i++) {
|
||||
if (pages[i].level <= current.level) {
|
||||
break;
|
||||
}
|
||||
|
||||
pendingChanges.push({
|
||||
pageId: pages[i].id,
|
||||
level: pages[i].level + state.tocTools.indentIncrement
|
||||
});
|
||||
}
|
||||
|
||||
return pendingChanges;
|
||||
}
|
||||
|
||||
function outdent(state, pages, current) {
|
||||
var pageIndex = _.indexOf(pages, current, false);
|
||||
var pendingChanges = [];
|
||||
|
||||
pendingChanges.push({
|
||||
pageId: current.id,
|
||||
level: current.level - 1
|
||||
});
|
||||
|
||||
for (var i = pageIndex + 1; i < pages.length; i++) {
|
||||
if (pages[i].level <= current.level) {
|
||||
break;
|
||||
}
|
||||
|
||||
pendingChanges.push({
|
||||
pageId: pages[i].id,
|
||||
level: pages[i].level - 1
|
||||
});
|
||||
}
|
||||
|
||||
return pendingChanges;
|
||||
}
|
||||
|
||||
export default {
|
||||
getState,
|
||||
moveUp,
|
||||
moveDown,
|
||||
indent,
|
||||
outdent
|
||||
};
|
251
app/tests/unit/utils/toc-test.js
Normal file
251
app/tests/unit/utils/toc-test.js
Normal file
|
@ -0,0 +1,251 @@
|
|||
import toc from 'documize/utils/toc';
|
||||
import models from 'documize/utils/model';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Utility | toc');
|
||||
|
||||
test('toc can only move down', function(assert) {
|
||||
let pages = [];
|
||||
|
||||
pages.pushObject(models.PageModel.create({ id: "1", level: 1, sequence: 1024 })); //testing
|
||||
pages.pushObject(models.PageModel.create({ id: "2", level: 1, sequence: 1024*2 }));
|
||||
|
||||
let state = toc.getState(pages, pages[0]);
|
||||
assert.equal(state.tocTools.upTarget, '', 'Has no up target');
|
||||
assert.equal(state.tocTools.downTarget, '2', 'Has down target');
|
||||
assert.equal(state.tocTools.allowIndent, false, 'Cannot indent');
|
||||
assert.equal(state.tocTools.allowOutdent, false, 'Cannot outdent');
|
||||
});
|
||||
|
||||
test('toc can move up or indent', function(assert) {
|
||||
let pages = [];
|
||||
|
||||
pages.pushObject(models.PageModel.create({ id: "1", level: 1, sequence: 1024 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "2", level: 1, sequence: 1024*2 })); //testing
|
||||
|
||||
let state = toc.getState(pages, pages[1]);
|
||||
assert.equal(state.tocTools.upTarget, '1', 'Has up target');
|
||||
assert.equal(state.tocTools.downTarget, '', 'Has no down target');
|
||||
assert.equal(state.tocTools.allowIndent, true, 'Can indent');
|
||||
assert.equal(state.tocTools.allowOutdent, false, 'Cannot outdent');
|
||||
});
|
||||
|
||||
test('toc can only outdent', function(assert) {
|
||||
let pages = [];
|
||||
|
||||
pages.pushObject(models.PageModel.create({ id: "1", level: 1, sequence: 1024 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "2", level: 1, sequence: 1024*2 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "3", level: 2, sequence: 1024*3 })); // testing
|
||||
pages.pushObject(models.PageModel.create({ id: "4", level: 1, sequence: 1024*4 }));
|
||||
|
||||
let state = toc.getState(pages, pages[2]);
|
||||
assert.equal(state.tocTools.upTarget, '', 'Has no up target');
|
||||
assert.equal(state.tocTools.downTarget, '', 'Has no down target');
|
||||
assert.equal(state.tocTools.allowIndent, false, 'Cannot indent');
|
||||
assert.equal(state.tocTools.allowOutdent, true, 'Can outdent');
|
||||
});
|
||||
|
||||
test('toc child can move up or indent', function(assert) {
|
||||
let pages = [];
|
||||
|
||||
pages.pushObject(models.PageModel.create({ id: "1", level: 1, sequence: 1024 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "2", level: 1, sequence: 1024*2 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "3", level: 2, sequence: 1024*3 })); // testing
|
||||
pages.pushObject(models.PageModel.create({ id: "4", level: 1, sequence: 1024*4 }));
|
||||
|
||||
let page = pages[3];
|
||||
let state = toc.getState(pages, page);
|
||||
assert.equal(state.tocTools.upTarget, '2', 'Has up target');
|
||||
assert.equal(state.tocTools.downTarget, '', 'Has no down target');
|
||||
assert.equal(state.tocTools.allowIndent, true, 'Can indent');
|
||||
assert.equal(state.tocTools.allowOutdent, false, 'Cannot outdent');
|
||||
|
||||
let pendingChanges = toc.indent(state, pages, page);
|
||||
assert.equal(pendingChanges.length, 1, 'Has 1 pending change');
|
||||
assert.equal(pendingChanges[0].pageId, 4);
|
||||
assert.equal(pendingChanges[0].level, 2);
|
||||
});
|
||||
|
||||
test('toc top node can indent two places', function(assert) {
|
||||
let pages = [];
|
||||
|
||||
pages.pushObject(models.PageModel.create({ id: "1", level: 1, sequence: 1024 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "2", level: 1, sequence: 1024*2 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "3", level: 2, sequence: 1024*3 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "4", level: 3, sequence: 1024*4 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "5", level: 1, sequence: 1024*5 })); // testing
|
||||
|
||||
let page = pages[4];
|
||||
let state = toc.getState(pages, page);
|
||||
assert.equal(state.tocTools.upTarget, '2', 'Has up target');
|
||||
assert.equal(state.tocTools.downTarget, '', 'Has no down target');
|
||||
assert.equal(state.tocTools.allowIndent, true, 'Can indent');
|
||||
assert.equal(state.tocTools.allowOutdent, false, 'Cannot outdent');
|
||||
|
||||
let pendingChanges = toc.indent(state, pages, page);
|
||||
assert.equal(pendingChanges.length, 1, 'Has 1 pending change');
|
||||
assert.equal(pendingChanges[0].pageId, 5);
|
||||
assert.equal(pendingChanges[0].level, 3);
|
||||
});
|
||||
|
||||
test('toc top node with kids can indent two places', function(assert) {
|
||||
let pages = [];
|
||||
|
||||
pages.pushObject(models.PageModel.create({ id: "1", level: 1, sequence: 1024 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "2", level: 1, sequence: 1024*2 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "3", level: 2, sequence: 1024*3 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "4", level: 3, sequence: 1024*4 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "5", level: 1, sequence: 1024*5 })); // testing
|
||||
pages.pushObject(models.PageModel.create({ id: "6", level: 2, sequence: 1024*6 })); // testing
|
||||
pages.pushObject(models.PageModel.create({ id: "7", level: 3, sequence: 1024*7 })); // testing
|
||||
|
||||
let page = pages[4];
|
||||
let state = toc.getState(pages, page);
|
||||
assert.equal(state.tocTools.upTarget, '2', 'Has up target');
|
||||
assert.equal(state.tocTools.downTarget, '', 'Has no down target');
|
||||
assert.equal(state.tocTools.allowIndent, true, 'Can indent');
|
||||
assert.equal(state.tocTools.allowOutdent, false, 'Cannot outdent');
|
||||
|
||||
let pendingChanges = toc.indent(state, pages, page);
|
||||
assert.equal(pendingChanges.length, 3, 'Has 1 pending change');
|
||||
assert.equal(pendingChanges[0].pageId, 5);
|
||||
assert.equal(pendingChanges[0].level, 3);
|
||||
assert.equal(pendingChanges[1].pageId, 6);
|
||||
assert.equal(pendingChanges[1].level, 4);
|
||||
assert.equal(pendingChanges[2].pageId, 7);
|
||||
assert.equal(pendingChanges[2].level, 5);
|
||||
});
|
||||
|
||||
test('toc same level node with kids can indent one place', function(assert) {
|
||||
let pages = [];
|
||||
|
||||
pages.pushObject(models.PageModel.create({ id: "1", level: 1, sequence: 1024 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "2", level: 1, sequence: 1024*2 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "3", level: 2, sequence: 1024*3 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "4", level: 2, sequence: 1024*4 })); // testing
|
||||
pages.pushObject(models.PageModel.create({ id: "5", level: 3, sequence: 1024*5 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "6", level: 1, sequence: 1024*6 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "7", level: 2, sequence: 1024*7 }));
|
||||
|
||||
let page = pages[3];
|
||||
let state = toc.getState(pages, page);
|
||||
assert.equal(state.tocTools.upTarget, '3', 'Has up target');
|
||||
assert.equal(state.tocTools.downTarget, '', 'Has no down target');
|
||||
assert.equal(state.tocTools.allowIndent, true, 'Can indent');
|
||||
assert.equal(state.tocTools.allowOutdent, true, 'Can outdent');
|
||||
|
||||
let pendingChanges = toc.indent(state, pages, page);
|
||||
assert.equal(pendingChanges.length, 2, 'Has 2 pending changes');
|
||||
assert.equal(pendingChanges[0].pageId, 4);
|
||||
assert.equal(pendingChanges[0].level, 3);
|
||||
assert.equal(pendingChanges[1].pageId, 5);
|
||||
assert.equal(pendingChanges[1].level, 4);
|
||||
});
|
||||
|
||||
test('toc child with deep tree moves correctly', function(assert) {
|
||||
let pages = [];
|
||||
|
||||
pages.pushObject(models.PageModel.create({ id: "1", level: 1, sequence: 1024 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "2", level: 1, sequence: 1024*2 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "3", level: 2, sequence: 1024*4 })); // testing
|
||||
pages.pushObject(models.PageModel.create({ id: "4", level: 3, sequence: 1024*5 })); // testing
|
||||
pages.pushObject(models.PageModel.create({ id: "5", level: 3, sequence: 1024*6 })); // testing
|
||||
pages.pushObject(models.PageModel.create({ id: "6", level: 3, sequence: 1024*7 })); // testing
|
||||
pages.pushObject(models.PageModel.create({ id: "7", level: 1, sequence: 1024*8 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "8", level: 1, sequence: 1024*9 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "9", level: 1, sequence: 1024*10 }));
|
||||
|
||||
let page = pages[2];
|
||||
let state = toc.getState(pages, page);
|
||||
|
||||
assert.equal(state.tocTools.upTarget, '', 'Has no up target');
|
||||
assert.equal(state.tocTools.downTarget, '', 'Has no down target');
|
||||
assert.equal(state.tocTools.allowIndent, false, 'Cannot indent');
|
||||
assert.equal(state.tocTools.allowOutdent, true, 'Can outdent');
|
||||
|
||||
let pendingChanges = toc.outdent(state, pages, page);
|
||||
|
||||
assert.equal(pendingChanges.length, 4, 'Have 4 pending changes');
|
||||
assert.equal(pendingChanges[0].pageId, 3);
|
||||
assert.equal(pendingChanges[0].level, 1);
|
||||
assert.equal(pendingChanges[1].pageId, 4);
|
||||
assert.equal(pendingChanges[1].level, 2);
|
||||
assert.equal(pendingChanges[2].pageId, 5);
|
||||
assert.equal(pendingChanges[2].level, 2);
|
||||
assert.equal(pendingChanges[3].pageId, 6);
|
||||
assert.equal(pendingChanges[3].level, 2);
|
||||
});
|
||||
|
||||
test('toc top level node skips down some', function(assert) {
|
||||
let pages = [];
|
||||
|
||||
pages.pushObject(models.PageModel.create({ id: "1", level: 1, sequence: 110 })); // testing
|
||||
pages.pushObject(models.PageModel.create({ id: "2", level: 1, sequence: 220 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "3", level: 2, sequence: 330 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "4", level: 3, sequence: 440 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "5", level: 3, sequence: 550 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "6", level: 3, sequence: 660 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "7", level: 1, sequence: 770 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "8", level: 1, sequence: 880 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "9", level: 1, sequence: 990 }));
|
||||
|
||||
let page = pages[0];
|
||||
let state = toc.getState(pages, page);
|
||||
|
||||
assert.equal(state.tocTools.upTarget, '', 'Has no up target');
|
||||
assert.equal(state.tocTools.downTarget, '2', 'Has down target');
|
||||
assert.equal(state.tocTools.allowIndent, false, 'Cannot indent');
|
||||
assert.equal(state.tocTools.allowOutdent, false, 'Cannot outdent');
|
||||
|
||||
let pendingChanges = toc.moveDown(state, pages, page);
|
||||
|
||||
assert.equal(pendingChanges.length, 1, 'Have 1 pending change');
|
||||
assert.equal(pendingChanges[0].pageId, 1);
|
||||
assert.equal(pendingChanges[0].sequence, (660+770)/2);
|
||||
});
|
||||
|
||||
test('toc top level node skips up some', function(assert) {
|
||||
let pages = [];
|
||||
|
||||
pages.pushObject(models.PageModel.create({ id: "1", level: 1, sequence: 110 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "2", level: 1, sequence: 220 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "3", level: 2, sequence: 330 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "4", level: 3, sequence: 440 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "5", level: 3, sequence: 550 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "6", level: 3, sequence: 660 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "7", level: 1, sequence: 770 })); // testing
|
||||
pages.pushObject(models.PageModel.create({ id: "8", level: 1, sequence: 880 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "9", level: 1, sequence: 990 }));
|
||||
|
||||
let page = pages[6];
|
||||
let state = toc.getState(pages, page);
|
||||
assert.equal(state.tocTools.upTarget, '2', 'Has up target');
|
||||
assert.equal(state.tocTools.downTarget, '8', 'Has down target');
|
||||
assert.equal(state.tocTools.allowIndent, true, 'Can indent');
|
||||
assert.equal(state.tocTools.allowOutdent, false, 'Cannot outdent');
|
||||
|
||||
let pendingChanges = toc.moveUp(state, pages, page);
|
||||
assert.equal(pendingChanges.length, 1, 'Has 1 pending change');
|
||||
assert.equal(pendingChanges[0].pageId, 7);
|
||||
assert.equal(pendingChanges[0].sequence, (110+220)/2);
|
||||
});
|
||||
|
||||
test('toc move down top node to bottom', function(assert) {
|
||||
let pages = [];
|
||||
|
||||
pages.pushObject(models.PageModel.create({ id: "1", level: 1, sequence: 110 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "2", level: 1, sequence: 220 }));
|
||||
pages.pushObject(models.PageModel.create({ id: "3", level: 2, sequence: 330 }));
|
||||
|
||||
let page = pages[0];
|
||||
let state = toc.getState(pages, page);
|
||||
assert.equal(state.tocTools.upTarget, '', 'Has no up target');
|
||||
assert.equal(state.tocTools.downTarget, '2', 'Has down target');
|
||||
assert.equal(state.tocTools.allowIndent, false, 'Cannot indent');
|
||||
assert.equal(state.tocTools.allowOutdent, false, 'Cannot outdent');
|
||||
|
||||
let pendingChanges = toc.moveDown(state, pages, page);
|
||||
assert.equal(pendingChanges.length, 1, 'Has 1 pending change');
|
||||
assert.equal(pendingChanges[0].pageId, 1);
|
||||
assert.equal(pendingChanges[0].sequence, 330 * 2);
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue