WIP: xanadoc editing.
This commit is contained in:
parent
925e549d9b
commit
20a3c0123c
112
sourcing/static/js/xanaedit.js
Normal file
112
sourcing/static/js/xanaedit.js
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function common_start(a, b) {
|
||||||
|
for(var i = 0; i < a.length; i++)
|
||||||
|
if(a[i] != b[i])
|
||||||
|
return i;
|
||||||
|
return a.length
|
||||||
|
}
|
||||||
|
|
||||||
|
function common_end(a, b) {
|
||||||
|
var min_len = Math.min(a.length, b.length);
|
||||||
|
|
||||||
|
for(var i = 1; i < min_len + 1; i++)
|
||||||
|
if(a[a.length - i] != b[b.length - i])
|
||||||
|
return i - 1;
|
||||||
|
return min_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
var editor = document.getElementById("editor");
|
||||||
|
var changes = document.getElementById("changes");
|
||||||
|
|
||||||
|
var edits = [];
|
||||||
|
|
||||||
|
editor.value = text;;
|
||||||
|
|
||||||
|
function update_list_of_edits() {
|
||||||
|
changes.innerHTML = null;
|
||||||
|
edits.forEach(function(edit) {
|
||||||
|
var update;
|
||||||
|
var start = edit.start + 1;
|
||||||
|
if (edit.op == 'insert') {
|
||||||
|
update = start + ' insert: [' + edit.new + ']';
|
||||||
|
} else if (edit.op == 'delete') {
|
||||||
|
update = start + ' delete: [' + edit.old + ']';
|
||||||
|
} else if (edit.op == 'replace') {
|
||||||
|
update = start + ' replace: [' + edit.old + '] with [' + edit.new + ']';
|
||||||
|
}
|
||||||
|
var div = document.createElement("div");
|
||||||
|
div.innerHTML = update;
|
||||||
|
changes.append(div);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_buttons () {
|
||||||
|
if(edits.length) {
|
||||||
|
$('.save-btn').prop('disabled', false);
|
||||||
|
$('#cancel-btn').text('cancel edits');
|
||||||
|
} else {
|
||||||
|
$('.save-btn').prop('disabled', true);
|
||||||
|
$('#cancel-btn').text('back to view page');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(e) {
|
||||||
|
var current = editor.value;
|
||||||
|
if (current == text) // no change
|
||||||
|
return
|
||||||
|
|
||||||
|
var start = common_start(text, current);
|
||||||
|
var end = common_end(text.substring(start), current.substring(start));
|
||||||
|
|
||||||
|
var old_text = text.slice(start, text.length - end);
|
||||||
|
var new_text = current.slice(start, current.length - end);
|
||||||
|
text = current;
|
||||||
|
|
||||||
|
// var div = document.createElement("div");
|
||||||
|
var prev_edit = edits[edits.length - 1];
|
||||||
|
if (old_text && !new_text) { // delete
|
||||||
|
if (prev_edit && prev_edit.op == 'delete' && prev_edit.start == start + 1) {
|
||||||
|
prev_edit.start = start;
|
||||||
|
prev_edit.old = old_text + prev_edit.old;
|
||||||
|
} else if (prev_edit && prev_edit.op == 'delete' && prev_edit.start == start) {
|
||||||
|
prev_edit.old += old_text;
|
||||||
|
} else if (prev_edit && prev_edit.op == 'insert' &&
|
||||||
|
start - prev_edit.new.length + old_text.length == prev_edit.start &&
|
||||||
|
prev_edit.new.endsWith(old_text)) {
|
||||||
|
if (prev_edit.new == old_text) {
|
||||||
|
edits.splice(-1, 1);
|
||||||
|
} else {
|
||||||
|
prev_edit.new = prev_edit.new.slice(0, prev_edit.new.length - old_text.length);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
edits.push({'op': 'delete', 'start': start, 'old': old_text});
|
||||||
|
}
|
||||||
|
// div.innerHTML = start + ' delete: [' + old_text + ']';
|
||||||
|
} else if (!old_text && new_text) { // insert
|
||||||
|
if (prev_edit && prev_edit.op == 'insert' && prev_edit.start == start - prev_edit.new.length) {
|
||||||
|
prev_edit.new += new_text;
|
||||||
|
} else {
|
||||||
|
edits.push({'op': 'insert', 'start': start, 'new': new_text});
|
||||||
|
}
|
||||||
|
// div.innerHTML = start + ' insert: [' + new_text + ']';
|
||||||
|
} else { // replace
|
||||||
|
edits.push({'op': 'replace', 'start': start, 'new': new_text, 'old': old_text});
|
||||||
|
// div.innerHTML = start + ' replace [' + old_text + '] with [' + new_text + ']';
|
||||||
|
}
|
||||||
|
// changes.append(div);
|
||||||
|
|
||||||
|
|
||||||
|
$('input#edits').val(JSON.stringify(edits));
|
||||||
|
|
||||||
|
update_list_of_edits();
|
||||||
|
update_buttons();
|
||||||
|
}
|
||||||
|
|
||||||
|
update_buttons();
|
||||||
|
editor.addEventListener("input", update, false);
|
||||||
|
|
||||||
|
$('#save-btn').click(function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log('save');
|
||||||
|
});
|
|
@ -113,7 +113,7 @@
|
||||||
|
|
||||||
<p class="my-3">
|
<p class="my-3">
|
||||||
<a class="btn btn-primary" href="{{ doc.edit_url }}" role="button">Edit EDL</a>
|
<a class="btn btn-primary" href="{{ doc.edit_url }}" role="button">Edit EDL</a>
|
||||||
<a class="btn btn-primary" href="#" role="button">Xanaedit text</a>
|
<a class="btn btn-primary" href="{{ doc.xanaedit_url }}" role="button">Xanaedit text</a>
|
||||||
<a class="btn btn-primary" href="#" role="button">Reorder paragraphs</a>
|
<a class="btn btn-primary" href="#" role="button">Reorder paragraphs</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
39
sourcing/templates/xanaedit.html
Normal file
39
sourcing/templates/xanaedit.html
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% from "form/controls.html" import render_field %}
|
||||||
|
|
||||||
|
{% block title %}xanaedit {{ doc.title() }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1 class="mt-3">{{ doc.title() }}</h1>
|
||||||
|
|
||||||
|
<p><a href="{{ doc.url }}">view xanadoc (cancel edit)</a></p>
|
||||||
|
|
||||||
|
<form method="POST" action="{{ doc.view_url('finish_xanaedit') }}">
|
||||||
|
<input type="hidden" name="edits" id="edits" value=""/>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-8">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<button class="btn btn-primary save-btn" id="save-btn" disabled>save, continue editing</button>
|
||||||
|
<button type="submit" class="btn btn-primary save-btn" id="finish-btn" disabled>save and finish</button>
|
||||||
|
<a href="{{ doc.url }}" class="btn btn-secondary" id="cancel-btn">back to view page</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<textarea id="editor" rows="16" class="w-100" autofocus></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="col-4" id="changes">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
var text = {{ doc_text.replace('\r\n', '\n')|tojson }};
|
||||||
|
</script>
|
||||||
|
<script src="{{ url_for('static', filename='js/xanaedit.js') }}"></script>
|
||||||
|
{% endblock %}
|
|
@ -8,7 +8,9 @@ from .forms import (LoginForm, SignupForm, AccountSettingsForm,
|
||||||
from .model import User, SourceDoc, Item, XanaDoc, XanaLink
|
from .model import User, SourceDoc, Item, XanaDoc, XanaLink
|
||||||
from .url import get_url
|
from .url import get_url
|
||||||
from .mail import send_mail
|
from .mail import send_mail
|
||||||
from .edl import fulfil_edl_with_sources, fulfil_edl
|
from .edl import fulfil_edl_with_sources, fulfil_edl, parse_edl
|
||||||
|
from .span import Span
|
||||||
|
from .edit import apply_edits
|
||||||
from .database import session
|
from .database import session
|
||||||
from .text import iter_lines, add_highlight
|
from .text import iter_lines, add_highlight
|
||||||
from werkzeug.debug.tbtools import get_current_traceback
|
from werkzeug.debug.tbtools import get_current_traceback
|
||||||
|
@ -17,7 +19,9 @@ from functools import wraps
|
||||||
from .utils import nbsp_at_start
|
from .utils import nbsp_at_start
|
||||||
from itsdangerous import URLSafeTimedSerializer
|
from itsdangerous import URLSafeTimedSerializer
|
||||||
from sqlalchemy_continuum import version_class
|
from sqlalchemy_continuum import version_class
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
login_manager = LoginManager()
|
login_manager = LoginManager()
|
||||||
|
@ -218,7 +222,6 @@ def realize_edl(username, hashid):
|
||||||
item = get_xanadoc(username, hashid)
|
item = get_xanadoc(username, hashid)
|
||||||
|
|
||||||
spans = list(fulfil_edl(item.text))
|
spans = list(fulfil_edl(item.text))
|
||||||
|
|
||||||
doc_text = ''.join(span['text'] for span in spans)
|
doc_text = ''.join(span['text'] for span in spans)
|
||||||
|
|
||||||
return render_template('realize.html',
|
return render_template('realize.html',
|
||||||
|
@ -298,8 +301,36 @@ def history(username, hashid):
|
||||||
|
|
||||||
@bp.route('/<username>/<hashid>/xanaedit')
|
@bp.route('/<username>/<hashid>/xanaedit')
|
||||||
def xanaedit_item(username, hashid):
|
def xanaedit_item(username, hashid):
|
||||||
obj = get_item(username, hashid)
|
doc = get_xanadoc(username, hashid)
|
||||||
return render_template('xanaedit.html', doc=obj)
|
|
||||||
|
spans = list(fulfil_edl(doc.text))
|
||||||
|
doc_text = ''.join(span['text'] for span in spans)
|
||||||
|
|
||||||
|
return render_template('xanaedit.html', doc=doc, doc_text=doc_text)
|
||||||
|
|
||||||
|
@bp.route('/<username>/<hashid>/xanaedit', methods=['POST'])
|
||||||
|
def save_xanaedit(username, hashid):
|
||||||
|
# doc = get_xanadoc(username, hashid)
|
||||||
|
edits = json.loads(request.form['edits'])
|
||||||
|
return jsonify(edits=edits)
|
||||||
|
|
||||||
|
@bp.route('/<username>/<hashid>/finish', methods=['POST'])
|
||||||
|
def finish_xanaedit(username, hashid):
|
||||||
|
doc = get_xanadoc(username, hashid)
|
||||||
|
current_edl = parse_edl(doc.text)
|
||||||
|
|
||||||
|
spans = [Span(*span) for span in current_edl['spans']]
|
||||||
|
edits = json.loads(request.form['edits'])
|
||||||
|
new_text = ''
|
||||||
|
new_text_pos = 0
|
||||||
|
for edit in edits:
|
||||||
|
if edit['op'] != 'insert':
|
||||||
|
continue
|
||||||
|
new_text += edit['new']
|
||||||
|
edit['span'] = Span('placeholder', new_text_pos, len(edit['new']))
|
||||||
|
new_text_pos += len(edit['new'])
|
||||||
|
spans = apply_edits(spans, edits)
|
||||||
|
return Response(pformat(spans), mimetype='text/plain')
|
||||||
|
|
||||||
@bp.route('/<username>/<hashid>/edit', methods=['GET', 'POST'])
|
@bp.route('/<username>/<hashid>/edit', methods=['GET', 'POST'])
|
||||||
def edit_item(username, hashid):
|
def edit_item(username, hashid):
|
||||||
|
|
Loading…
Reference in a new issue