# base UI
'js/wiki/base.js',
'js/wiki/toolbar.js',
+ 'js/lib/diff.js',
+ 'js/wiki/undo.js',
# dialogs
'js/wiki/dialog_save.js',
--- /dev/null
+$.wiki.diff = function(a, b) {
+ MAXD = 500;
+
+ let VV = new Array(),
+ N = a.length,
+ M = b.length,
+ V, Vp, D, x, y, k;
+ V = VV[-1] = Array();
+ V[1] = 0;
+ let endD = null;
+ for (D = 0; D < MAXD && endD === null; D++) {
+ Vp = V;
+ V = VV[D] = Array();
+ for (k = -D; k <= D; k += 2) {
+ if (k == -D || (k != D && Vp[k-1] < Vp[k + 1])) {
+ x = Vp[k + 1];
+ } else {
+ x = Vp[k - 1] + 1;
+ }
+ y = x - k;
+ while (x < N && y < M && a[x] == b[y]) {
+ x ++;
+ y ++;
+ }
+ V[k] = x;
+ if (x == N && y == M) {
+ endD = D;
+ break;
+ }
+ }
+ }
+ if (endD === null) {
+ // Max D limit reached, diff too big. Bail and just return the whole target text.
+ return b;
+ }
+
+ // Now go back.
+ result = []
+ let snake, px, py;
+ for (D = endD; D; --D) {
+ k = x - y;
+ V = VV[D - 1];
+ if (V[k - 1] === undefined || V[k + 1] > V[k - 1]) {
+ // move up
+ k ++;
+ px = V[k];
+ py = px - k;
+ if (result.length && result[0][0] && result[0][1] == px) {
+ result[0][2] = b[py] + result[0][2];
+ } else {
+ result.unshift(
+ [true, px, b[py]]
+ )
+ }
+ } else {
+ // move down
+ k --;
+ px = V[k];
+ py = px - k;
+ if (result.length && !result[0][0] && result[0][1] == px + 1) {
+ result[0][1]--;
+ result[0][2]++;
+ } else {
+ result.unshift(
+ [false, px, 1]
+ )
+ }
+ }
+ x = px;
+ y = py;
+ }
+ return result
+}
+
+
+$.wiki.patch = function(a, p) {
+ for (i = p.length - 1; i >= 0; -- i) {
+ let c = p[i];
+ if (c[0]) {
+ a = a.substr(0, c[1]) + c[2] + a.substr(c[1]);
+ } else {
+ a = a.substr(0, c[1]) + a.substr(c[1] + c[2]);
+ }
+ }
+ return a;
+}
$.wiki.exitTab = function(tab){
var self = this;
var $tab = $(tab);
- if (!('.active', $tab)) return;
+ if (!('.active', $tab).length) return;
$('.active', $tab).removeClass('active');
self.perspectives[$tab.attr('id')].onExit();
$('#' + $tab.attr('data-ui-related')).hide();
self.singleClick = true;
setTimeout(function() {
if (self.singleClick) {
+ $.wiki.activePerspective().flush();
self.element.insertBefore(
anchorNode.splitText(
selection.anchorOffset
move(opts) {
if (!this.attached) return;
+
+ $.wiki.activePerspective().flush();
this.normalize();
/* Load configuration */
$.wiki.loadConfig();
- var initAll = function(a, f) {
- if (a.length == 0) return f();
-
- $.wiki.initTab({
- tab: a.pop(),
- doc: CurrentDocument,
- callback: function(){
- initAll(a, f);
- }
- });
- };
-
-
- /*
- * Initialize all perspectives
- */
- initAll( $.makeArray($('#tabs li')), initialize);
- console.log(location.hash);
+ $('.tabs li').each((i, e) => {
+ $.wiki.initTab({tab: e, doc: CurrentDocument});
+ });
+ initialize();
});
--- /dev/null
+{
+
+ class Undo {
+ maxItems = 100;
+ stack = [];
+ position = 0;
+
+ stats = {size: 0};
+
+ constructor() {
+ $(() => {
+ this.$undo = $("#undoBtn");
+ this.$undo.on('click', () => {CurrentDocument.undo();})
+ this.$redo = $("#redoBtn");
+ this.$redo.on('click', () => {CurrentDocument.redo();})
+ this.$stats = $("#undoStats");
+ })
+ }
+
+ refresh() {
+ this.$undo.prop('disabled', !this.canUndo);
+ this.$redo.prop('disabled', !this.canRedo);
+ this.$undo.attr('title', 'undo\n\n' + this.renderStats())
+ }
+
+ renderStats() {
+ return this.stats.size / 1e6;
+ }
+
+ push(state) {
+ // Has the state actually changed?
+ if (state == this.materialize(this.position))
+ return;
+
+ while (this.position) {
+ this.pop();
+ --this.position;
+ }
+
+ this.put(state);
+ this.trim();
+ this.refresh();
+ }
+ pop() {
+ this.stats.size -= this.stack[0].length;
+ return this.stack.shift()
+ }
+ put(state) {
+ this.stack.unshift(state);
+ this.stats.size += state.length;
+ }
+ trim() {
+ while (this.stack.length > this.maxItems) {
+ this.stats.size -= this.stack.pop().length;
+ }
+ }
+ materialize(n) {
+ return this.stack[n];
+ }
+
+ undo() {
+ if (!this.canUndo) return;
+ let val = this.materialize(++this.position);
+ this.refresh();
+ return val;
+ }
+
+ redo() {
+ if (!this.canRedo) return;
+ let val = this.materialize(--this.position);
+ this.refresh();
+ return val;
+ }
+
+ get canUndo() {
+ return this.stack.length > this.position + 1;
+ }
+
+ get canRedo() {
+ return this.position > 0;
+ }
+ }
+
+
+ class TextUndo extends Undo {
+ stats = {
+ Items: 0,
+ Size: 0,
+ textSize: 0,
+ textItems: 0,
+ diffSize: 0,
+ diffItems: 0,
+ diffChanges: 0,
+ }
+
+ statsFor(item) {
+ if (Array.isArray(item)) {
+ return {
+ diffItems: 1,
+ diffChanges: item.length,
+ diffSize: JSON.stringify(item).length
+ }
+ } else {
+ return {
+ textItems: 1,
+ textSize: item.length
+ }
+ }
+ }
+ addStats(stats) {
+ for (let i in stats) {
+ this.stats[i] += stats[i]
+ }
+ }
+ subStats(stats) {
+ for (let i in stats) {
+ this.stats[i] -= stats[i]
+ }
+ }
+ renderStats() {
+ this.stats['Items'] = this.stats['textItems'] + this.stats['diffItems'];
+ this.stats['Size'] = this.stats['textSize'] + this.stats['diffSize'];
+ let stats = '', v;
+ for (let k in this.stats) {
+ v = this.stats[k];
+ if (k.endsWith('Size')) {
+ let level = 0;
+ while (v > 1000) {
+ v /= 1000;
+ level++;
+ }
+ v = Math.round(v)
+ v += ['B', 'kB', 'MB', 'GB'][level];
+ }
+ stats += k + ': ' + v + '\n';
+ }
+ return stats;
+ }
+
+
+ put(state) {
+ if (this.stack.length) {
+ let tip = this.materialize(0);
+ this.subStats(this.statsFor(this.stack[0]))
+ this.stack[0] = $.wiki.diff(state, tip);
+ this.addStats(this.statsFor(this.stack[0]))
+ }
+ this.stack.unshift(state);
+ this.addStats(this.statsFor(state));
+ }
+ pop() {
+ if (this.stack.length > 1) {
+ this.subStats(this.statsFor(this.stack[1]))
+ this.stack[1] = this.materialize(1);
+ this.addStats(this.statsFor(this.stack[1]))
+ }
+ this.subStats(this.statsFor(this.stack[0]))
+ return this.stack.shift();
+ }
+ trim() {
+ while (this.stack.length > this.maxItems) {
+ this.subStats(this.statsFor(
+ this.stack.pop()
+ ));
+ }
+ }
+
+ materialize(n) {
+ if (n >= this.stack.length) return;
+ let state, base_i, i;
+ for (i = 0; i <= n; ++i) {
+ if (!Array.isArray(this.stack[i])) {
+ base_i = i;
+ }
+ }
+ state = this.stack[base_i];
+ for (i = base_i + 1; i <= n; ++i) {
+ state = $.wiki.patch(state, this.stack[i]);
+ }
+ return state;
+ }
+ }
+
+ $.wiki.undo = new TextUndo();
+}
$origin.html($(element).html());
}
$overlay.remove();
+ $.wiki.activePerspective().flush();
},
error: function(text){
alert('Błąd! ' + text);
self.caret = new Caret(element);
$('#insert-reference-button').click(function(){
+ self.flush();
self.addReference();
return false;
});
$('#insert-annotation-button').click(function(){
+ self.flush();
addAnnotation();
return false;
});
$('#insert-theme-button').click(function(){
+ self.flush();
addTheme();
return false;
});
$(".insert-inline-tag").click(function() {
+ self.flush();
self.insertInlineTag($(this).attr('data-tag'));
return false;
});
$(".insert-char").click(function() {
+ self.flush();
addSymbol(caret=self.caret);
return false;
});
$(document).on('click', '.edit-button', function(event){
+ self.flush();
event.preventDefault();
openForEdit($(this).parent());
});
$(document).on('click', '.uwaga-button', function(event){
+ self.flush();
event.preventDefault();
createUwagaBefore($(this).parent());
});
});
element.on('click', '.annotation', function(event) {
+ self.flush();
event.preventDefault();
event.redakcja_caret_ignore = true;
$('[x-annotation-box]', $(this).parent()).toggleClass('editing');
var htmlView = $('#html-view');
htmlView.html(element);
+ if ('PropertiesPerspective' in $.wiki.perspectives)
+ $.wiki.perspectives.PropertiesPerspective.enable();
_finalize(success);
},
_finalize(failure);
}
});
- };
+ }
+
+ flush() {
+ let self = this;
+ return new Promise((resolve, reject) => {
+ if ($('#html-view .error').length > 0) {
+ reject()
+ } else {
+ //return _finalize(failure);
+ html2text({
+ element: $('#html-view').get(0),
+ stripOuter: true,
+ success: (text) => {
+ self.doc.setText(text);
+ resolve();
+ },
+ error: (text) => {
+ reject(text);
+ //$('#source-editor').html('<p>Wystąpił błąd:</p><pre>' + text + '</pre>');
+ }
+ });
+ }
+ });
+ }
onExit(success, failure) {
var self = this;
self.caret.detach();
- $.wiki.exitTab('#PropertiesPerspective');
-
- $.blockUI({
- message: 'Zapisywanie widoku...'
- });
-
- function _finalize(callback){
- $.unblockUI();
- if (callback)
- callback();
- }
-
- if ($('#html-view .error').length > 0)
- return _finalize(failure);
+ if ('PropertiesPerspective' in $.wiki.perspectives)
+ $.wiki.perspectives.PropertiesPerspective.disable();
- html2text({
- element: $('#html-view').get(0),
- stripOuter: true,
- success: function(text){
- self.doc.setText(text);
- _finalize(success);
- },
- error: function(text){
- $('#source-editor').html('<p>Wystąpił błąd:</p><pre>' + text + '</pre>');
- _finalize(failure);
- }
+ self.flush().then(() => {
+ success && success();
+ }).catch((e) => {
+ // TODO report
+ console.log('REJECTED!', e);
+ failure && failure();
});
};
insertInlineTag(tag) {
this.caret.detach();
+ let self = this;
let selection = window.getSelection();
var n = selection.rangeCount;
success: function(html) {
// What if no end?
node.insertBefore($(html)[0], end);
+ self.flush();
}
});
},
class PropertiesPerspective extends $.wiki.SidebarPerspective {
vsplitbar = 'WŁAŚCIWOŚCI';
+ $edited = null;
constructor(options) {
super(options);
} else {
$input.data("edited").text(inputval);
}
+ $.wiki.perspectives.VisualPerspective.flush();
return;
}
let htmlElem = $(html);
self.$edited.replaceWith(htmlElem);
self.edit(htmlElem);
+ $.wiki.activePerspective().flush();
}
});
},
let $fg = $(this).closest('.form-group');
$('input', $fg).data('edited').remove();
self.displayMetaProperty($fg);
+ $.wiki.perspectives.VisualPerspective.flush();
return false;
});
if (element === null) {
self.$edited = null;
+ $("h1", self.$pane).text('');
return;
}
if ($.wiki.activePerspective() != 'VisualPerspective')
$.wiki.switchToTab('#VisualPerspective');
- if (self.$edited === null) {
- self.edit($('[x-node="utwor"]')[0]);
+ this.enable();
+ }
+
+ enable() {
+ if (this.$edited === null) {
+ this.edit($('[x-node="utwor"]')[0]);
}
}
+ disable() {
+ this.edit(null);
+ }
}
$.wiki.PropertiesPerspective = PropertiesPerspective;
if (self.text === null || self.revision !== data.revision) {
self.text = data.text;
+ $.wiki.undo.push(data.text);
self.revision = data.revision;
self.gallery = data.gallery;
changed = true;
/*
* Set document's text
*/
- setText(text) {
+ setText(text, silent=false) {
if (text == this.text) return;
+ if (!silent) {
+ $.wiki.undo.push(text);
+ }
this.text = text;
this.has_local_changes = true;
}
+ undo() {
+ let ctx = $.wiki.exitContext();
+ this.setText(
+ $.wiki.undo.undo(),
+ true
+ );
+ $.wiki.enterContext(ctx);
+ }
+ redo() {
+ let ctx = $.wiki.exitContext();
+ this.setText(
+ $.wiki.undo.redo(),
+ true
+ );
+ $.wiki.enterContext(ctx);
+ }
+
/*
* Save text back to the server
*/
{% include "wiki/tabs/history_view_item.html" %}
{% endblock %}
+{% block tools-menu %}
+ <div class="btn-group mr-auto">
+ <button id="undoBtn" class="btn btn-secondary" title="undo">↺</button>
+ <button id="redoBtn" class="btn btn-secondary" title="redo">↻</button>
+ </div>
+{% endblock %}
+
{% block tabs-content %}
{% include "wiki/tabs/summary_view.html" %}
{% include "wiki/tabs/wysiwyg_editor.html" %}
{% block tabs-menu %}{% endblock %}
</ul>
+ {% block tools-menu %}
+ {% endblock %}
+
<ul class="tabs nav nav-tabs" id="tabs-right">
{% block tabs-right %}{% endblock %}
</ul>