});
-// Stany modelu:
//
-// -> error -> loading
-// /
-// empty -> loading -> synced -> unsynced -> loading
-// \
-// -> dirty -> updating -> updated -> synced
+// HTML Document Model
//
-Editor.XMLModel = Editor.Model.extend({
- _className: 'Editor.XMLModel',
- serverURL: null,
- data: '',
+Editor.HTMLModel = Editor.Model.extend({
+ _className: 'Editor.HTMLModel',
+ textURL: null,
state: 'empty',
-
- init: function(document, serverURL) {
+
+ init: function(document, textURL) {
this._super();
this.set('state', 'empty');
this.set('revision', document.get('revision'));
this.document = document;
- this.serverURL = serverURL;
- this.toolbarButtonsModel = new Editor.ToolbarButtonsModel();
+
+ this.textURL = textURL;
+
+ this.htmlXSL = null;
+ this.wlmlXSL = null;
+ this.rawText = null;
+
+ // create a parser and a serializer
+ this.parser = new DOMParser();
+ this.serializer = new XMLSerializer();
+
this.addObserver(this, 'data', this.dataChanged.bind(this));
},
-
+
load: function(force) {
if (force || this.get('state') == 'empty') {
this.set('state', 'loading');
- messageCenter.addMessage('info', 'xmlload', 'Wczytuję XML...');
+ messageCenter.addMessage('info', 'xmlload', 'Wczytuję HTML...');
+
+ // request all stylesheets
$.ajax({
- url: this.serverURL,
+ url: documentInfo.staticURL + 'xsl/wl2html_client.xsl',
+ dataType: 'xml',
+ success: this.htmlXSLLoadSuccess.bind(this),
+ error: this.loadingFailed.bind(this)
+ });
+
+ $.ajax({
+ url: documentInfo.staticURL + 'xsl/html2wl_client.xsl',
+ dataType: 'xml',
+ success: this.wlmlXSLLoadSuccess.bind(this),
+ error: this.loadingFailed.bind(this)
+ });
+
+ $.ajax({
+ url: this.textURL,
dataType: 'text',
data: {
revision: this.get('revision'),
user: this.document.get('user')
},
- success: this.loadingSucceeded.bind(this),
+ success: this.textLoadSuccess.bind(this),
error: this.loadingFailed.bind(this)
});
return true;
}
return false;
},
-
- loadingSucceeded: function(data) {
+
+ asWLML: function(element, inner)
+ {
+ console.log("Source", element);
+ var doc = this.parser.parseFromString(this.serializer.serializeToString(element), 'text/xml');
+
+ var result = this.wlmlXSL.transformToDocument(doc);
+
+ if(!result) {
+ console.log("Failed", this.wlmlXSL, doc);
+ throw "Failed to transform fragment";
+ }
+
+ console.log("Transformed", doc, " to: ", result.documentElement);
+ if(inner) {
+ var children = result.documentElement.childNodes;
+ var buf = '';
+
+ for(var i=0; i < children.length; i++)
+ buf += this.serializer.serializeToString(children.item(i));
+
+ return buf;
+ }
+
+ return this.serializer.serializeToString(result.documentElement);
+ },
+
+ innerAsWLML: function(elem)
+ {
+ return this.asWLML(elem, true);
+ },
+
+ updateInnerWithWLML: function($element, innerML)
+ {
+ var e = $element.clone().html('<span x-node="out-of-flow-text" x-content="%"></span>')[0];
+ var s = this.asWLML(e);
+ // hurray for dirty hacks :P
+ s = s.replace(/>%<\//, '>'+innerML+'</');
+ return this.updateWithWLML($element, s);
+ },
+
+ updateWithWLML: function($element, text)
+ {
+ // filter the string
+ text = text.replace(/\/\s+/g, '<br />');
+ try {
+ var chunk = this.parser.parseFromString("<chunk>"+text+"</chunk>", "text/xml");
+ } catch(e) {
+ console.log('Caught parse exception.');
+ return "<p>Źle sformatowana zawartość:" + e.toString() + "</p>";
+ }
+
+ var parseError = chunk.getElementsByTagName('parsererror');
+ console.log("Errors:", parseError);
+
+ if(parseError.length > 0)
+ {
+ console.log("Parse errors.")
+ return this.serializer.serializeToString(parseError.item(0));
+ }
+
+ console.log("Transforming to HTML");
+ var result = this.htmlXSL.transformToFragment(chunk, $element[0].ownerDocument).firstChild;
+
+ if(!result) {
+ return "Błąd aplikacji - nie udało się wygenerować nowego widoku HTML.";
+ }
+
+ var errors = result.getElementsByTagName('error');
+ if(errors.length > 0)
+ {
+ var errorMessage = 'Wystąpiły błędy:<ul>';
+ for(var i=0; i < errors.length; i++)
+ {
+ var estr = this.serializer.serializeToString(errors.item(i));
+ console.log("XFRM error:", estr);
+ errorMessage += "<li>"+estr+"</li>";
+ }
+ errorMessage += "</ul>";
+ return errorMessage;
+ }
+
+ try {
+ $element.replaceWith(result);
+ this.set('state', 'dirty');
+ return false;
+ } catch(e) {
+ return "Błąd podczas wstawiania tekstu: '" + e.toString() + "'";
+ }
+ },
+
+ createXSLT: function(xslt_doc) {
+ var p = new XSLTProcessor();
+ p.importStylesheet(xslt_doc);
+ return p;
+ },
+
+ htmlXSLLoadSuccess: function(data)
+ {
+ try {
+ this.htmlXSL = this.createXSLT(data);
+
+ if(this.wlmlXSL && this.htmlXSL && this.rawText)
+ this.loadSuccess();
+ } catch(e) {
+ console.log(e);
+ this.set('error', e.toString() );
+ this.set('state', 'error');
+ }
+ },
+
+ wlmlXSLLoadSuccess: function(data)
+ {
+ try {
+ this.wlmlXSL = this.createXSLT(data);
+
+ if(this.wlmlXSL && this.htmlXSL && this.rawText)
+ this.loadSuccess();
+ } catch(e) {
+ console.log(e);
+ this.set('error', e.toString() );
+ this.set('state', 'error');
+ }
+ },
+
+ textLoadSuccess: function(data) {
+ this.rawText = data;
+
+ if(this.wlmlXSL && this.htmlXSL && this.rawText)
+ this.loadSuccess();
+ },
+
+ loadSuccess: function() {
if (this.get('state') != 'loading') {
alert('erroneous state:', this.get('state'));
}
- this.set('data', data);
+
+ // prepare text
+ var doc = null;
+ doc = this.rawText.replace(/\/\s+/g, '<br />');
+ doc = this.parser.parseFromString(doc, 'text/xml');
+ doc = this.htmlXSL.transformToFragment(doc, document).firstChild;
+
+ this.set('data', doc);
this.set('state', 'synced');
- messageCenter.addMessage('success', 'xmlload', 'Wczytałem XML :-)');
+ messageCenter.addMessage('success', 'xmlload', 'Wczytałem HTML :-)');
},
-
- loadingFailed: function() {
+
+ loadingFailed: function(response)
+ {
if (this.get('state') != 'loading') {
alert('erroneous state:', this.get('state'));
}
+
var message = parseXHRError(response);
-
+
this.set('error', '<h2>Błąd przy ładowaniu XML</h2><p>'+message+'</p>');
this.set('state', 'error');
- messageCenter.addMessage('error', 'xmlload', 'Nie udało mi się wczytać XML. Spróbuj ponownie :-(');
+ messageCenter.addMessage('error', 'xmlload', 'Nie udało mi się wczytać HTML. Spróbuj ponownie :-(');
},
-
+
save: function(message) {
if (this.get('state') == 'dirty') {
- this.set('state', 'updating');
- messageCenter.addMessage('info', 'xmlsave', 'Zapisuję XML...');
-
+ this.set('state', 'saving');
+
+ messageCenter.addMessage('info', 'htmlsave', 'Zapisuję HTML...');
+ var wlml = this.asWLML(this.get('data'));
+
var payload = {
- contents: this.get('data'),
+ contents: wlml,
revision: this.get('revision'),
user: this.document.get('user')
};
+
if (message) {
payload.message = message;
}
-
+
$.ajax({
- url: this.serverURL,
+ url: this.textURL,
type: 'post',
dataType: 'json',
data: payload,
}
return false;
},
-
+
saveSucceeded: function(data) {
- if (this.get('state') != 'updating') {
+ if (this.get('state') != 'saving') {
alert('erroneous state:', this.get('state'));
}
this.set('revision', data.revision);
this.set('state', 'updated');
- messageCenter.addMessage('success', 'xmlsave', 'Zapisałem XML :-)');
+ messageCenter.addMessage('success', 'htmlsave', 'Zapisałem :-)');
},
-
+
saveFailed: function() {
- if (this.get('state') != 'updating') {
+ if (this.get('state') != 'saving') {
alert('erroneous state:', this.get('state'));
}
- messageCenter.addMessage('error', 'xmlsave', 'Nie udało mi się zapisać XML. Spróbuj ponownie :-(');
+ messageCenter.addMessage('error', 'htmlsave', 'Nie udało mi się zapisać.');
this.set('state', 'dirty');
},
-
+
// For debbuging
set: function(property, value) {
if (property == 'state') {
}
return this._super(property, value);
},
-
+
dataChanged: function(property, value) {
if (this.get('state') == 'synced') {
this.set('state', 'dirty');
}
},
-
+
dispose: function() {
this.removeObserver(this);
this._super();
});
-Editor.HTMLModel = Editor.Model.extend({
- _className: 'Editor.HTMLModel',
- dataURL: null,
- htmlURL: null,
- renderURL: null,
- displaData: '',
- xmlParts: {},
+// Stany modelu:
+//
+// -> error -> loading
+// /
+// empty -> loading -> synced -> unsynced -> loading
+// \
+// -> dirty -> updating -> updated -> synced
+//
+Editor.XMLModel = Editor.Model.extend({
+ _className: 'Editor.XMLModel',
+ serverURL: null,
+ data: '',
state: 'empty',
- init: function(document, dataURL, htmlURL) {
+ init: function(document, serverURL) {
this._super();
this.set('state', 'empty');
- this.set('revision', document.get('revision'));
-
+ this.set('revision', document.get('revision'));
this.document = document;
- this.htmlURL = htmlURL;
- this.dataURL = dataURL;
- this.renderURL = documentInfo.renderURL;
- this.xmlParts = {};
+ this.serverURL = serverURL;
+ this.toolbarButtonsModel = new Editor.ToolbarButtonsModel();
+ this.addObserver(this, 'data', this.dataChanged.bind(this));
},
load: function(force) {
if (force || this.get('state') == 'empty') {
this.set('state', 'loading');
-
- // load the transformed data
- // messageCenter.addMessage('info', 'Wczytuję HTML...');
-
+ messageCenter.addMessage('info', 'xmlload', 'Wczytuję XML...');
$.ajax({
- url: this.htmlURL,
+ url: this.serverURL,
dataType: 'text',
data: {
revision: this.get('revision'),
success: this.loadingSucceeded.bind(this),
error: this.loadingFailed.bind(this)
});
+ return true;
}
- },
+ return false;
+ },
loadingSucceeded: function(data) {
if (this.get('state') != 'loading') {
}
this.set('data', data);
this.set('state', 'synced');
+ messageCenter.addMessage('success', 'xmlload', 'Wczytałem XML :-)');
},
- loadingFailed: function(response) {
+ loadingFailed: function(response)
+ {
if (this.get('state') != 'loading') {
alert('erroneous state:', this.get('state'));
}
var message = parseXHRError(response);
- this.set('error', '<p>Nie udało się wczytać widoku HTML: </p>' + message);
- this.set('state', 'error');
- },
-
- getXMLPart: function(elem, callback)
- {
- var path = elem.attr('wl2o:path');
- if(!this.xmlParts[path])
- this.loadXMLPart(elem, callback);
- else
- callback(path, this.xmlParts[path]);
- },
-
- loadXMLPart: function(elem, callback)
- {
- var path = elem.attr('wl2o:path');
- var self = this;
-
- $.ajax({
- url: this.dataURL,
- dataType: 'text',
- data: {
- revision: this.get('revision'),
- user: this.document.get('user'),
- part: path
- },
- success: function(data) {
- self.xmlParts[path] = data;
- callback(path, data);
- },
- // TODO: error handling
- error: function(data) {
- console.log('Failed to load fragment');
- callback(undefined, undefined);
- }
- });
- },
-
- putXMLPart: function(elem, data) {
- var self = this;
-
- var path = elem.attr('wl2o:path');
- this.xmlParts[path] = data;
-
- this.set('state', 'unsynced');
-
- /* re-render the changed fragment */
- $.ajax({
- url: this.renderURL,
- type: "POST",
- dataType: 'text; charset=utf-8',
- data: {
- fragment: data,
- part: path
- },
- success: function(htmldata) {
- elem.replaceWith(htmldata);
- self.set('state', 'dirty');
- }
- });
+ this.set('error', '<h2>Błąd przy ładowaniu XML</h2><p>'+message+'</p>');
+ this.set('state', 'error');
+ messageCenter.addMessage('error', 'xmlload', 'Nie udało mi się wczytać XML. Spróbuj ponownie :-(');
},
-
+
save: function(message) {
if (this.get('state') == 'dirty') {
this.set('state', 'updating');
-
+ messageCenter.addMessage('info', 'xmlsave', 'Zapisuję XML...');
+
var payload = {
- chunks: $.toJSON(this.xmlParts),
+ contents: this.get('data'),
revision: this.get('revision'),
user: this.document.get('user')
};
-
if (message) {
payload.message = message;
}
-
- console.log(payload)
-
+
$.ajax({
- url: this.dataURL,
+ url: this.serverURL,
type: 'post',
dataType: 'json',
data: payload,
return true;
}
return false;
-
},
-
+
saveSucceeded: function(data) {
if (this.get('state') != 'updating') {
alert('erroneous state:', this.get('state'));
}
-
- // flush the cache
- this.xmlParts = {};
-
this.set('revision', data.revision);
this.set('state', 'updated');
+ messageCenter.addMessage('success', 'xmlsave', 'Zapisałem XML :-)');
},
-
+
saveFailed: function() {
if (this.get('state') != 'updating') {
alert('erroneous state:', this.get('state'));
- }
+ }
+ messageCenter.addMessage('error', 'xmlsave', 'Nie udało mi się zapisać XML. Spróbuj ponownie :-(');
this.set('state', 'dirty');
},
-
+
// For debbuging
set: function(property, value) {
if (property == 'state') {
console.log(this.description(), ':', property, '=', value);
}
return this._super(property, value);
+ },
+
+ dataChanged: function(property, value) {
+ if (this.get('state') == 'synced') {
+ this.set('state', 'dirty');
+ }
+ },
+
+ dispose: function() {
+ this.removeObserver(this);
+ this._super();
}
});
-
Editor.ImageGalleryModel = Editor.Model.extend({
_className: 'Editor.ImageGalleryModel',
serverURL: null,
this.pages = [];
},
+ setGallery: function(path) {
+ $.ajax({
+ url: this.serverURL,
+ type: 'post',
+ data: {
+ path: path,
+ },
+ success: this.settingGallerySucceeded.bind(this)
+ });
+ },
+
+ settingGallerySucceeded: function(data) {
+ console.log('settingGallerySucceeded');
+ this.load(true);
+ },
+
load: function(force) {
if (force || this.get('state') == 'empty') {
console.log("setting state");
this.contentModels = {
'xml': new Editor.XMLModel(this, data.text_url),
- 'html': new Editor.HTMLModel(this, data.text_url, data.html_url),
+ 'html': new Editor.HTMLModel(this, data.text_url),
'gallery': new Editor.ImageGalleryModel(this, data.gallery_url)
};
alert('erroneous state:', this.get('state'));
}
- var message = parseXHRError(response);
- this.set('error', '<h2>Nie udało się wczytać dokumentu</h2><p>'+message+"</p>");
+ var err = parseXHRError(response);
+ this.set('error', '<h2>Nie udało się wczytać dokumentu</h2><p>'+err.error_message+"</p>");
this.set('state', 'error');
},
revision: this.get('revision'),
user: this.get('user')
},
- complete: this.updateCompleted.bind(this),
- success: function(data) {
- this.set('updateData', data);
- console.log("new data:", data)
- }.bind(this)
+ complete: this.updateCompleted.bind(this)
});
},
- updateCompleted: function(xhr, textStatus) {
- console.log(xhr.status, textStatus);
-
- if (xhr.status == 200)
+ updateCompleted: function(xhr, textStatus)
+ {
+ console.log(xhr.status, xhr.responseText);
+ var response = parseXHRResponse(xhr);
+ if(response.success)
{
- var udata = this.get('updateData');
- if(udata.timestamp == udata.parent_timestamp)
+ if( (response.data.result == 'no-op')
+ || (response.data.timestamp == response.data.parent_timestamp))
{
- // no change
+ if( (response.data.revision) && (response.data.revision != this.get('revision')) )
+ {
+ // we're out of sync
+ this.set('state', 'unsynced');
+ return;
+ }
+
messageCenter.addMessage('info', 'doc_update',
- 'Nic się nie zmieniło od ostatniej aktualizacji. Po co mam uaktualniać?');
+ 'Już posiadasz najbardziej aktualną wersję.');
+ this.set('state', 'synced');
+ return;
+ }
+
+ // result: success
+ this.set('revision', response.data.revision);
+ this.set('user', response.data.user);
+
+ messageCenter.addMessage('info', 'doc_update',
+ 'Uaktualnienie dokumentu do wersji ' + response.data.revision);
+ for (var key in this.contentModels) {
+ this.contentModels[key].set('revision', this.get('revision') );
+ this.contentModels[key].set('state', 'empty');
}
- else {
- this.set('revision', udata.revision);
- this.set('user', udata.user);
- messageCenter.addMessage('info', 'doc_update',
- 'Uaktualnienie dokumentu do wersji ' + udata.revision);
-
- for (var key in this.contentModels) {
- this.contentModels[key].set('revision', this.get('revision') );
- this.contentModels[key].set('state', 'empty');
- }
- }
- } else if (xhr.status == 409) { // Konflikt podczas operacji
- messageCenter.addMessage('error', 'doc_update',
- 'Wystąpił konflikt podczas aktualizacji. Pędź po programistów! :-(');
- } else {
- messageCenter.addMessage('critical', 'doc_update',
- 'Nieoczekiwany błąd. Pędź po programistów! :-(');
+
+ this.set('state', 'synced');
+ return;
}
+
+ // no success means trouble
+ messageCenter.addMessage(response.error_level, 'doc_update',
+ response.error_message);
- this.set('state', 'synced');
- this.set('updateData', null);
+ this.set('state', 'unsynced');
},
merge: function(message) {
this.set('state', 'loading');
- messageCenter.addMessage('info', null,
+ messageCenter.addMessage('info', 'doc_merge',
'Scalam dokument z głównym repozytorium...');
$.ajax({
},
mergeCompleted: function(xhr, textStatus) {
- console.log(xhr.status, textStatus);
- if (xhr.status == 200) { // Sukces
- this.set('revision', this.get('updateData').revision);
- this.set('user', this.get('updateData').user);
-
- for (var key in this.contentModels) {
- this.contentModels[key].set('revision', this.get('revision'));
- this.contentModels[key].set('state', 'empty');
+ console.log(xhr.status, xhr.responseText);
+ var response = parseXHRResponse(xhr);
+
+ if(response.success) {
+
+ if( (response.data.result == 'no-op') ||
+ ( response.data.shared_parent_timestamp
+ && response.data.shared_timestamp
+ && (response.data.shared_timestamp == response.data.shared_parent_timestamp)) )
+ {
+ if( (response.data.revision) && (response.data.revision != this.get('revision')) )
+ {
+ // we're out of sync
+ this.set('state', 'unsynced');
+ return;
+ }
+
+ messageCenter.addMessage('info', 'doc_merge',
+ 'Twoja aktualna wersja nie różni się od ostatnio zatwierdzonej.');
+ this.set('state', 'synced');
+ return;
+ }
+
+ if( response.data.result == 'accepted')
+ {
+ messageCenter.addMessage('info', 'doc_merge',
+ 'Prośba o zatwierdzenie została przyjęta i oczekuję na przyjęcie.');
+ this.set('state', 'synced');
+ return;
}
- messageCenter.addMessage('success', null, 'Scaliłem dokument z głównym repozytorium :-)');
- } else if (xhr.status == 202) { // Wygenerowano PullRequest
- messageCenter.addMessage('success', null, 'Wysłałem prośbę o scalenie dokumentu z głównym repozytorium.');
- } else if (xhr.status == 204) { // Nic nie zmieniono
- messageCenter.addMessage('info', null, 'Nic się nie zmieniło od ostatniego scalenia. Po co mam scalać?');
- } else if (xhr.status == 409) { // Konflikt podczas operacji
- messageCenter.addMessage('error', null, 'Wystąpił konflikt podczas scalania. Pędź po programistów! :-(');
- } else if (xhr.status == 500) {
- messageCenter.addMessage('critical', null, 'Błąd serwera. Pędź po programistów! :-(');
+ // result: success
+ this.set('revision', response.data.revision);
+ this.set('user', response.data.user);
+
+ messageCenter.addMessage('info', 'doc_merge',
+ 'Twoja wersja dokumentu została zatwierdzona.');
+
+ this.set('state', 'synced');
+ return;
}
- this.set('state', 'synced');
- this.set('mergeData', null);
+
+ // no success means trouble
+ messageCenter.addMessage(response.error_level, 'doc_merge',
+ response.error_message);
+
+ this.set('state', 'unsynced');
},
// For debbuging