'toolbar',
'apiclient',
'email_mangler',
+ 'wlxml.apps.WlxmlConfig',
)
if DEBUG:
'js/slugify.js',
# wiki scripts
+ 'js/wiki/caret.js',
'js/wiki/wikiapi.js',
'wiki/js/themes.js',
'js/wiki/xslt.js',
max-width: 36em;
}
+
+// unused?
.htmlview #toc {
display: none;
}
+// where is it used, if any?
.htmlview a {
color: blue;
text-decoration: none;
text-decoration: none;
}
-.htmlview .nazwa_utworu {
- font-size: 3em;
- margin: 1.5em 0;
- text-align: center;
- line-height: 1.5em;
- font-weight: bold;
-}
-
-.htmlview .autor_utworu,
-.htmlview .dzielo_nadrzedne,
-.htmlview .naglowek_czesc,
-.htmlview .srodtytul,
-.htmlview .naglowek_akt
-{
- font-size: 2em;
- margin: 1.5em 0 0;
- font-weight: bold;
- line-height: 1.5em;
-}
-
-.htmlview .podtytul,
-.htmlview .naglowek_scena,
-.htmlview .naglowek_rozdzial
-{
- font-size: 1.5em;
- margin: 1.5em 0 0;
- font-weight: normal;
- line-height: 1.5em;
-}
-
-.htmlview .naglowek_podrozdzial,
-.htmlview .naglowek_osoba
-{
- font-size: 1em;
- margin: 1.5em 0 0;
- line-height: 1.5em;
- font-weight: bold;
-}
-
+// unused?
.htmlview p {
margin: 0;
}
-.htmlview #footnotes div {
- margin: 1.5em 0 0 0;
-}
-
-.htmlview #footnotes p {
- margin-left: 2.5em;
- font-size: 0.875em;
-}
-
-.htmlview blockquote {
- font-size: 0.875em;
-}
-
.htmlview pre {
overflow-x: auto; /* Use horizontal scroller if needed; for Firefox 2, not needed in Firefox 3 */
white-space: pre-wrap; /* css-3 */
/* =================== */
/* = Custom elements = */
/* =================== */
-.htmlview .autor_utwor {
- font-size: 0.5em;
- display: block;
- line-height: 1.5em;
- margin-bottom: 0.25em;
-}
-
-.htmlview .dzielo_nadrzedne {
- /* */
-}
-
-.htmlview .podtytul {
- /* */
-}
-
-.htmlview .didaskalia {
- font-style: italic;
- margin: 0.5em 0 0 1.5em;
-}
-
-.htmlview .kwestia {
- margin: 0.5em 0 0;
-}
.htmlview .strofa {
margin: 1.5em 0 0.5em auto;
margin-left: 6em;
}
-.htmlview .strofa .wers_do_prawej {
- text-align: right;
-}
-
/* błędne wersy */
-.htmlview *:not(.strofa) > *[x-verse]::after {
+.htmlview *:not([x-node='strofa']) > *[x-verse]::after {
content: "Ten wers znajduje się poza strofą.";
display: inline;
background: red;
margin: 0;
}
-.htmlview .akap, .htmlview .akap_cd, .htmlview .akap_dialog {
- text-align: justify;
- margin: 1.5em 0 0;
-}
-
-.htmlview .motto_container {
- display: inline-block;
- margin: 1.5em 0 0;
- clear: right;
-}
-
-.htmlview .motto {
- text-align: justify;
- font-style: italic;
-}
-
-.htmlview p.motto_podpis {
- position: relative;
- right: -3em;
- text-align: right;
-}
-
-.htmlview div.fragment {
- border-bottom: 0.1em solid #999;
- padding-bottom: 1.5em;
-}
-
-.htmlview div.nota p, .htmlview div.dedykacja p {
- text-align: right;
- font-style: italic;
-}
.htmlview div.ramka {
border: 1px darkgray solid;
}
-.htmlview .nota_red {
- background-color: #eee;
- border: 1px solid #888;
- -moz-border-radius: 10px;
- -webkit-border-radius: 10px;
- display: block;
- padding: 0 1em 1em 1em;
-}
-
-.htmlview hr.sekcja_swiatlo {
- margin: 2em 0;
- visibility: hidden;
-}
-
-.htmlview hr.separator_linia {
- margin: 1.5em 0;
- border: none;
- border-bottom: 0.1em solid #000;
-}
.htmlview hr.sekcja_asterysk {
border: none;
text-align: center;
}
-.htmlview hr.sekcja_asterysk:after {
- content: "*";
-}
-
.htmlview div.lista_osob ol {
list-style: none;
padding: 0 0 0 1.5em;
}
-.htmlview p.miejsce_czas {
- font-style: italic;
-}
-
-.htmlview .mat, .htmlview .slowo_obce, .htmlview .tytul_dziela, .htmlview .didaskalia {
- font-style: italic;
-}
-
-
-.htmlview .indeks_dolny {
- font-style: normal;
- vertical-align: sub;
- font-size: .9em;
-}
-
-.htmlview {
- .wyroznienie {
- // letter-spacing: 0.1em;
- background: #dfd;
- border: 1px solid #0f0;
- }
- .slowo_obce {
- background: #ddf;
- border: 1px solid #88f;
- }
- .tytul_dziela {
- background: #fdf;
- border: 1px solid #f8f;
- }
-}
-
-
-.htmlview .osoba {
- font-style: normal;
- font-variant: small-caps;
-}
-
-.htmlview em.wieksze_odstepy {
- font-style: normal;
- word-spacing: 1em;
-}
-
.htmlview .ilustr img {
max-width: 100%;
}
background: green;
}
-.htmlview .motyw {
+.htmlview [x-node='motyw'] {
/* position: absolute; */
float: right;
left: auto;
* Przypisy w tekście
*/
-.htmlview .annotation-inline-box:hover > span[x-annotation-box]{
- display: block;
-}
-
-.htmlview .annotation-inline-box > span[x-annotation-box] {
- display: none;
- width: 300px;
- font-size: 10pt;
- line-height: 12pt;
- font-weight: normal;
- font-style: normal;
- background: #fffe93;
- border: 1px solid black;
- border-radius: 10px;
- -moz-border-radius: 10px;
- -webkit-border-radius: 10px;
- padding: 3px 5px;
- text-decoration: none;
- z-index: 600;
-}
-
-/*
- * Przypisy na końcu utworu (aktualnie nieuzywane)
- */
-.htmlview .annotations-block {
- counter-reset: secondary;
-}
-
-.htmlview .annotations-block .annotation-body {
- position: relative;
- padding-left: 2.5em;
- padding-top: 0.2em;
-}
-
-.htmlview .annotations-block .annotation-backref {
- position: absolute;
- top: 0.4em;
- left: -0.4em;
- width: 2.5em;
- text-align: right;
-}
+.htmlview .annotation-inline-box {
+ &:hover > span[x-annotation-box] {
+ display: block;
+ }
-.htmlview .annotations-block .annotation-backref:before {
- content: "[" counter(secondary) "]";
- counter-increment: secondary;
+ > span[x-annotation-box] {
+ display: none;
+ width: 300px;
+ font-size: 10pt;
+ line-height: 12pt;
+ font-weight: normal;
+ font-style: normal;
+ background: #fffe93;
+ border: 1px solid black;
+ border-radius: 10px;
+ -moz-border-radius: 10px;
+ -webkit-border-radius: 10px;
+ padding: 3px 5px;
+ text-decoration: none;
+ z-index: 600;
+
+ position: absolute;
+ left: 0;
+ top: 1em;
+
+ &.editing {
+ display: block;
+ }
+ }
+
}
/*
#simple-editor {
display: flex;
flex-direction: column;
+ position: relative;
+}
+
+#simple-editor #bubbles {
+ position: absolute;
+ bottom: 10px;
+ right: 20px;
+}
+#simple-editor #bubbles .badge {
+ display: block;
+ margin-top: 5px;
}
.htmlview {
opacity: 0.4;
}
-.poezja_cyt {
- margin:1.5em 2em 0;
- font-size:0.875em
-}
-.wers_akap {
- padding-left: 1em;
-}
+
+
.saveNotify {
position:absolute;
--- /dev/null
+class Caret {
+ constructor(view) {
+ let self = this;
+ self.view = view;
+ self.singleClick = false;
+
+ let caret = this.element = $('<span id="caret"><textarea></textarea></span>');
+
+ // When user writes into caret, add it to the document.
+ $('textarea', caret).on('input', function() {
+ let v = $(this).val();
+ $(this).val('');
+ self.insertChar(v);
+
+ });
+
+ // On click on x-node element, set caret position.
+ self.view.on('click', '*[x-node]', function(e) {
+ if (e.redakcja_caret_inserted) return;
+ e.redakcja_caret_inserted = true;
+
+ if (self.singleClick) {
+ self.singleClick = false;
+ return;
+ }
+
+ self.detach();
+
+ var selection = window.getSelection();
+ if (!selection.isCollapsed) return;
+
+ self.singleClick = true;
+ setTimeout(function() {
+ if (self.singleClick) {
+ self.element.insertBefore(
+ selection.anchorNode.splitText(
+ selection.anchorOffset
+ )
+ )
+ self.focus();
+ }
+ self.singleClick = false;
+ }, 250);
+
+ });
+
+ self.element.on('keydown', function(e) {
+ console.log('KEY');
+
+ // TODO:
+ // delete selection?
+
+ // cases:
+ // we are in <akap> (no going up)
+ // we are in <wyroznienie> (can go up)
+ // we are next to <wyroznienie> (can go inside)
+
+ switch (e.key) {
+ case "ArrowRight":
+ if (e.shiftKey) {
+ self.detach();
+ return;
+ }
+
+ self.moveRight();
+ break;
+ case "ArrowLeft":
+ if (e.shiftKey) {
+ self.detach();
+ return;
+ }
+
+ self.moveLeft();
+ break;
+ case "ArrowUp":
+ if (e.shiftKey) {
+ self.detach();
+ return;
+ }
+ break;
+ case "ArrowDown":
+ if (e.shiftKey) {
+ self.detach();
+ return;
+ }
+ break;
+ case "Backspace":
+ self.deleteBefore();
+ break;
+ case "Delete":
+ self.deleteAfter();
+ break;
+ case "Enter":
+ self.splitBlock();
+ break;
+ // default:
+ // console.log('key', e.key, e.code);
+ }
+ })
+ }
+
+ get attached() {
+ return this.element.parent().length;
+ }
+
+ detach() {
+ console.log(this.view);
+
+ let p;
+ if (this.attached) {
+ p = this.element.parent()[0]
+ this.element.detach();
+ p.normalize()
+ }
+ }
+
+ focus() {
+ $("textarea", self.element).focus();
+ }
+
+ normalize() {
+ this.element.parent()[0].normalize();
+ }
+
+ insertChar(ch) {
+ $(document.createTextNode(ch)).insertBefore(this.element);
+ this.normalize();
+ }
+
+ deleteBefore() {
+ let contents = this.element.parent().contents();
+ // Find the text before caret.
+ let textBefore = contents[contents.index(this.element) - 1];
+
+ // Should be text, but what if not?
+ textBefore.textContent = textBefore.textContent.substr(0, textBefore.textContent.length - 1);
+ this.normalize();
+
+ }
+
+ deleteAfter() {
+ let contents = this.element.parent().contents();
+ // Find the text after caret.
+ let textAfter = contents[contents.index(this.element) + 1];
+ textAfter.textContent = textAfter.textContent.substr(1);
+ }
+
+ splitBlock() {
+ let splitter = this.element;
+ let parent, newParent, splitIndex, index;
+
+ while (!splitter.is('div[x-node]')) {
+ parent = splitter.parent();
+ splitIndex = parent.contents().index(splitter);
+ newParent = parent.clone();
+ index = parent.contents().length - 1;
+ while (index >= splitIndex) {
+ newParent.contents()[index].remove();
+ --index;
+ }
+ while (index >= 0) {
+ console.log(newParent, index);
+ parent.contents()[index].remove();
+ -- index;
+ }
+ newParent.insertBefore(parent);
+
+ console.log('split', parent);
+ splitter = parent;
+ }
+ }
+
+ moveLeft() {
+ this.move({
+ move: -1,
+ edge: (i, l) => {return !i;},
+ enter: (l) => {return l - 1;},
+ splitTarget: (t) => {return t.splitText(t.length - 1);},
+ noSplitTarget: (t) => {return t.splitText(t.length);},
+ })
+ }
+
+ moveRight() {
+ this.move({
+ move: 1,
+ edge: (i, l) => {return i == l - 1;},
+ enter: (l) => {return 0;},
+ splitTarget: (t) => {return t.splitText(1);},
+ noSplitTarget: (t) => {return t;},
+ })
+ }
+
+ move(opts) {
+ if (!this.attached) return;
+
+ this.normalize();
+
+ let contents = this.element.parent().contents();
+ let index = contents.index(this.element);
+ let target, moved, oldparent;
+
+ let parent = this.element.parent()[0];
+
+ if (opts.edge(index, contents.length)) {
+ // We're at the end -- what to do?
+ // can we go up?
+
+ if (parent.nodeName == 'EM') {
+ oldparent = parent;
+ parent = parent.parentNode;
+ contents = $(parent).contents();
+ index = contents.index(oldparent);
+ }
+ }
+
+ index += opts.move;
+ target = contents[index];
+ moved = false;
+
+ while (target.nodeType == 1) {
+ // we've encountered a node.
+ // can we go inside?
+
+ if (target.nodeName == 'EM') {
+ // enter
+ parent = $(target);
+ contents = parent.contents();
+ index = opts.enter(contents.length);
+ target = contents[index];
+
+ // what if it has no elements?
+ } else {
+ // skip
+ index += opts.move; // again, what if end?
+ target = contents[index];
+ moved = true;
+ }
+
+ // if editable?
+ // what if editable but empty?
+
+ }
+
+ if (target.nodeType == 3) {
+ if (!moved) {
+ target = opts.splitTarget(target);
+ } else {
+ target = opts.noSplitTarget(target);
+ }
+
+ this.element.insertBefore(target);
+ }
+ this.normalize();
+ this.focus();
+ }
+}
(function($) {
- class Caret {
- constructor(view) {
- self = this;
- self.view = view;
- self.singleClick = false;
-
- let caret = this.element = $('<span id="caret"><textarea></textarea></span>');
-
- // When user writes into caret, add it to the document.
- $('textarea', caret).on('input', function() {
- let v = $(this).val();
- $(this).val('');
- self.insertChar(v);
-
- });
-
- // On click on x-node element, set caret position.
- self.view.on('click', '*[x-node]', function(e) {
- if (e.redakcja_caret_inserted) return;
- e.redakcja_caret_inserted = true;
-
- if (self.singleClick) {
- self.singleClick = false;
- return;
- }
-
- self.detach();
-
- var selection = window.getSelection();
- if (!selection.isCollapsed) return;
-
- self.singleClick = true;
- setTimeout(function() {
- if (self.singleClick) {
- self.element.insertBefore(
- selection.anchorNode.splitText(
- selection.anchorOffset
- )
- )
- self.focus();
- }
- self.singleClick = false;
- }, 250);
-
- });
-
- self.view.on('keydown', function(e) {
- // TODO:
- // Enter (split block)
- // delete selection?
-
- // cases:
- // we are in <akap> (no going up)
- // we are in <wyroznienie> (can go up)
- // we are next to <wyroznienie> (can go inside)
-
- switch (e.key) {
- case "ArrowRight":
- if (e.shiftKey) {
- self.detach();
- return;
- }
-
- self.moveRight();
- break;
- case "ArrowLeft":
- if (e.shiftKey) {
- self.detach();
- return;
- }
-
- self.moveLeft();
- break;
- case "ArrowUp":
- if (e.shiftKey) {
- self.detach();
- return;
- }
- break;
- case "ArrowDown":
- if (e.shiftKey) {
- self.detach();
- return;
- }
- break;
- case "Backspace":
- self.deleteBefore();
- break;
- case "Delete":
- self.deleteAfter();
- break;
-// default:
-// console.log('key', e.key, e.code);
- }
- })
- }
-
- get attached() {
- return this.element.parent().length;
- }
-
- detach() {
- let p;
- if (this.attached) {
- p = this.element.parent()[0]
- this.element.detach();
- p.normalize()
- }
- }
-
- focus() {
- $("textarea", self.element).focus();
- }
-
- normalize() {
- this.element.parent()[0].normalize();
- }
-
- insertChar(ch) {
- $(document.createTextNode(ch)).insertBefore(this.element);
- this.normalize();
- }
-
- deleteBefore() {
- let contents = this.element.parent().contents();
- // Find the text before caret.
- let textBefore = contents[contents.index(this.element) - 1];
-
- // Should be text, but what if not?
- textBefore.textContent = textBefore.textContent.substr(0, textBefore.textContent.length - 1);
- this.normalize();
-
- }
-
- deleteAfter() {
- let contents = this.element.parent().contents();
- // Find the text after caret.
- let textAfter = contents[contents.index(this.element) + 1];
- textAfter.textContent = textAfter.textContent.substr(1);
- }
-
- moveLeft() {
- this.move({
- move: -1,
- edge: (i, l) => {return !i;},
- enter: (l) => {return l - 1;},
- splitTarget: (t) => {return t.splitText(t.length - 1);},
- noSplitTarget: (t) => {return t.splitText(t.length);},
- })
- }
-
- moveRight() {
- this.move({
- move: 1,
- edge: (i, l) => {return i == l - 1;},
- enter: (l) => {return 0;},
- splitTarget: (t) => {return t.splitText(1);},
- noSplitTarget: (t) => {return t;},
- })
- }
-
- move(opts) {
- if (!this.attached) return;
- this.normalize();
-
- let contents = this.element.parent().contents();
- let index = contents.index(this.element);
- let target, moved, oldparent;
-
- let parent = this.element.parent()[0];
-
- if (opts.edge(index, contents.length)) {
- // We're at the end -- what to do?
- // can we go up?
-
- if (parent.nodeName == 'EM') {
- oldparent = parent;
- parent = parent.parentNode;
- contents = $(parent).contents();
- index = contents.index(oldparent);
- }
- }
-
- index += opts.move;
- target = contents[index];
- moved = false;
-
- while (target.nodeType == 1) {
- // we've encountered a node.
- // can we go inside?
-
- if (target.nodeName == 'EM') {
- // enter
- parent = $(target);
- contents = parent.contents();
- index = opts.enter(contents.length);
- target = contents[index];
-
- // what if it has no elements?
- } else {
- // skip
- index += opts.move; // again, what if end?
- target = contents[index];
- moved = true;
- }
-
- // if editable?
- // what if editable but empty?
-
- }
-
- if (target.nodeType == 3) {
- if (!moved) {
- target = opts.splitTarget(target);
- } else {
- target = opts.noSplitTarget(target);
- }
-
- this.element.insertBefore(target);
- }
- this.normalize();
- this.focus();
- }
- }
-
/* Show theme to the user */
function selectTheme(themeId){
}
if (editable.is('.annotation-inline-box')) {
$('*[x-annotation-box]', editable).css({
- position: 'absolute',
- left: event.clientX - editable.offset().left + 5,
- top: event.clientY - editable.offset().top + 5
+// left: event.clientX - editable.offset().left + 5,
+// top: event.clientY - editable.offset().top + 5
}).show();
}
else {
- $('*[x-annotation-box]').hide();
+// $('*[x-annotation-box]').hide();
}
});
+ perspective.caret = new Caret(element);
+
$('#insert-reference-button').click(function(){
addReference();
return false;
selectTheme($(this).attr('theme-class'));
});
+ element.on('click', '.annotation', function(event) {
+ event.preventDefault();
+ $('[x-annotation-box]', $(this).parent()).toggleClass('editing');
+
+ });
+
old_callback.call(this);
};
VisualPerspective.prototype = new $.wiki.Perspective();
- VisualPerspective.prototype.freezeState = function(){
-
- };
-
VisualPerspective.prototype.onEnter = function(success, failure){
$.wiki.Perspective.prototype.onEnter.call(this);
var htmlView = $('#html-view');
htmlView.html(element);
- perspective.caret = new Caret(htmlView);
-
-
-
htmlView.find('*[x-node]').dblclick(function(e) {
if($(e.target).is('textarea'))
return;
VisualPerspective.prototype.onExit = function(success, failure){
var self = this;
+ self.caret.detach();
+
$.blockUI({
message: 'Zapisywanie widoku...'
});
});
};
-
-
$.wiki.VisualPerspective = VisualPerspective;
})(jQuery);
self.edit($(this).data('node'));
});
+ $(document).on('click', '#bubbles .badge', function(e) {
+ self.edit($(this).data('node'));
+ });
+
self.$pane.on('change', '.form-control', function() {
let $input = $(this);
});
self.$edited;
});
+
+ self.$pane.on('click', '.current-convert', function() {
+ self.convert($(this).attr('data-to'));
+ });
+ self.$pane.on('click', '#current-delete', function() {
+ self.delete();
+ });
+
oldCallback.call(this);
};
let $node = $(element);
$("#parents", self.$pane).empty();
+ $("#bubbles").empty();
+
+ let b = $("<div class='badge badge-primary'></div>").text($node.attr('x-node'));
+ b.data('node', element);
+ $("#bubbles").append(b);
+
$node.parents('[x-node]').each(function() {
let a = $("<li class='breadcrumb-item'>").text($(this).attr('x-node'));
a.data('node', this);
$("#parents", self.$pane).prepend(a)
+
+ let b = $("<div class='badge badge-info'></div>").text($(this).attr('x-node'));
+ b.data('node', this);
+ $("#bubbles").append(b);
})
+
// It's a tag.
node = $(element).attr('x-node');
$("h1", self.$pane).text(node);
// Only utwor can has matadata now.
if (node == 'utwor') {
// Let's find all the metadata.
- $("> .RDF > .Description > [x-node]", $node).each(function() {
+ $("> [x-node='RDF'] > [x-node='Description'] > [x-node]", $node).each(function() {
$meta = $(this);
self.addEditField(
{"name": $meta.attr('x-node'),},
);
});
}
+
+
+
+ // check node type, find relevant tags
+ if ($node[0].nodeName == 'DIV') {
+ $("#current-convert").attr("data-current-type", "div");
+ } else if ($node[0].nodeName == 'EM') {
+ $("#current-convert").attr("data-current-type", "span");
+ }
};
-
+
+
+
PropertiesPerspective.prototype.addEditField = function(defn, value, elem) {
let self = this;
let $form = $("#properties-form", self.$pane);
$fg.appendTo($form);
}
-
+
+ PropertiesPerspective.prototype.convert = function(newtag) {
+ this.$edited.attr('x-node', newtag);
+ // TODO: take care of attributes?
+ }
+
+ PropertiesPerspective.prototype.delete = function(newtag) {
+ p = this.$edited.parent();
+ this.$edited.remove();
+ this.edit(p);
+ }
+
$.wiki.PropertiesPerspective = PropertiesPerspective;
})(jQuery);
});
};
- /*
- * Set document's text
- */
- WikiDocument.prototype.setText = function(text) {
- return this.setDocumentProperty('text', text);
- };
-
- /*
- * Set document's gallery link
- */
- WikiDocument.prototype.setGalleryLink = function(gallery) {
- return this.setDocumentProperty('galleryLink', gallery);
- };
+ /*
+ * Set document's text
+ */
+ WikiDocument.prototype.setText = function(text, setter) {
+ if (text == this.text) return;
+
+ this.text = text;
+ this.has_local_changes = true;
- /*
- * Set document's property
- */
- WikiDocument.prototype.setDocumentProperty = function(property, value) {
- if(this[property] !== value) {
- this[property] = value;
- this.has_local_changes = true;
- }
- };
+ };
+
/*
* Save text back to the server
*/
if (!xml2htmlStylesheet) {
$.blockUI({message: 'Ładowanie arkuszy stylów...'});
$.ajax({
- url: STATIC_URL + 'xsl/wl2html_client.xsl?210709',
+ url: '/wlxml/wl2html.xsl',
dataType: 'xml',
timeout: 10000,
success: function(data) {
url(r'^editor/', include('wiki.urls')),
url(r'^images/', include('wiki_img.urls')),
url(r'^cover/', include('cover.urls')),
+ url(r'^wlxml/', include('wlxml.urls')),
]
{% block extrahead %}
{% load pipeline %}
{% stylesheet 'detail' %}
- <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
+ <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
+ <link rel="stylesheet" href="/wlxml/wl.css"
{% endblock %}
{% block extrabody %}
{% endif %}
</div>
</div>
+ <div id="bubbles"></div>
<div id="html-view" class="htmlview">
</div>
</div>
--- /dev/null
+from django.contrib import admin
+from . import models
+
+
+class AttributeInline(admin.TabularInline):
+ model = models.Attribute
+
+
+@admin.register(models.Tag)
+class TagAdmin(admin.ModelAdmin):
+ inlines = [AttributeInline]
+ list_display = ['name', 'type']
+ list_filter = ['type']
+ fieldsets = [
+ (None, {
+ 'fields': [
+ 'name',
+ 'type',
+ 'similar_to',
+ 'description',
+ 'example',
+ ]
+ }),
+ ('Editor style', {
+ 'fields': [
+ 'editor_css', 'editor_css_after',
+ ]
+ }),
+ ]
+
+
+
+@admin.register(models.TagUsage)
+class TagUsageAdmin(admin.ModelAdmin):
+ list_filter = ['tag']
+
+
+@admin.register(models.AttributeUsage)
+class AttributeUsageAdmin(admin.ModelAdmin):
+ list_filter = ['attribute__tag', 'attribute']
+ list_display = ['tag_usage', 'attribute', 'value']
--- /dev/null
+from django.apps import AppConfig
+
+
+class WlxmlConfig(AppConfig):
+ name = 'wlxml'
+ verbose_name = 'WL-XML'
--- /dev/null
+# Generated by Django 3.1.13 on 2021-08-13 15:44
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('documents', '0006_auto_20210706_0130'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Attribute',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=255)),
+ ],
+ options={
+ 'verbose_name': 'attribute',
+ 'verbose_name_plural': 'attribute',
+ },
+ ),
+ migrations.CreateModel(
+ name='Tag',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(db_index=True, max_length=255, unique=True)),
+ ('type', models.CharField(blank=True, choices=[('section', 'Section, contains blocks'), ('div', 'Block element, like a paragraph'), ('span', 'Inline element, like an emphasis'), ('sep', 'Separator, has no content'), ('aside', 'Aside content, like a footnote'), ('verse', 'Verse element')], max_length=255)),
+ ('editor_css', models.TextField(blank=True)),
+ ('editor_css_after', models.TextField(blank=True)),
+ ('similar_to', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='wlxml.tag')),
+ ],
+ options={
+ 'verbose_name': 'tag',
+ 'verbose_name_plural': 'tags',
+ },
+ ),
+ migrations.CreateModel(
+ name='TagUsage',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('chunk', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='documents.chunk')),
+ ('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wlxml.tag')),
+ ],
+ options={
+ 'verbose_name': 'tag usage',
+ 'verbose_name_plural': 'tags usage',
+ },
+ ),
+ migrations.CreateModel(
+ name='AttributeUsage',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('value', models.CharField(blank=True, max_length=2048)),
+ ('attribute', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wlxml.attribute')),
+ ('tag_usage', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wlxml.tagusage')),
+ ],
+ options={
+ 'verbose_name': 'attribute usage',
+ 'verbose_name_plural': 'attributes usage',
+ },
+ ),
+ migrations.AddField(
+ model_name='attribute',
+ name='tag',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wlxml.tag'),
+ ),
+ migrations.AlterUniqueTogether(
+ name='attribute',
+ unique_together={('tag', 'name')},
+ ),
+ ]
--- /dev/null
+# Generated by Django 3.1.13 on 2021-08-13 15:44
+
+from django.db import migrations
+
+
+initial_tags = {
+ "section": {
+ "opowiadanie": {},
+ "powiesc": {},
+ "liryka_l": {},
+ "liryka_lp": {},
+ "dramat_wierszowany_l": {},
+ "dramat_wierszowany_lp": {},
+ "dramat_wspolczesny": {},
+ "wywiad": {},
+ "nota": {},
+ "nota_red": {
+ "editor_css": """
+ background-color: #eee;
+ border: 1px solid #888;
+ border-radius: 10px;
+ display: block;
+ padding: 0 1em 1em 1em;
+ """
+ },
+ "dedykacja": {},
+ "ramka": {},
+ "lista_osob": {},
+ "dlugi_cytat": {},
+ "poezja_cyt": {
+ "editor_css": """margin: 1.5em 2em 0;
+font-size: 0.875em;"""
+ },
+ "kwestia": {
+ "editor_css": """
+ margin: 5em 0 0;
+ """
+ },
+ "wywiad_pyt": {},
+ "wywiad_odp": {},
+ "motto": {
+ "editor_css": """
+ text-align: justify;
+ font-style: italic;
+ """
+ },
+ },
+ "div": {
+ "didaskalia": {
+ "editor_css": """font-style: italic;
+margin: 0.5em 0 0 1.5em;"""
+ },
+ "naglowek_podrozdzial": {
+ "editor_css": """
+ font-size: 1em;
+ margin: 1.5em 0 0;
+ font-weight: bold;
+ line-height: 1.5em;
+ """
+ },
+ "naglowek_osoba": {
+ "editor_css": """
+ font-size: 1em;
+ margin: 1.5em 0 0;
+ font-weight: bold;
+ line-height: 1.5em;
+ """
+ },
+ "podtytul": {
+ "editor_css": """
+ font-size: 1.5em;
+ margin: 1.5em 0 0;
+ font-weight: normal;
+ line-height: 1.5em;
+ """
+ },
+ "naglowek_scena": {
+ "editor_css": """
+ font-size: 1.5em;
+ margin: 1.5em 0 0;
+ font-weight: normal;
+ line-height: 1.5em;
+ """
+ },
+ "naglowek_rozdzial": {
+ "editor_css": """
+ font-size: 1.5em;
+ margin: 1.5em 0 0;
+ font-weight: normal;
+ line-height: 1.5em;
+ """
+ },
+ "autor_utworu": {
+ "editor_css": """
+ font-size: 2em;
+ margin: 1.5em 0 0;
+ font-weight: bold;
+ line-height: 1.5em;
+ """
+ },
+ "dzielo_nadrzedne": {
+ "editor_css": """
+ font-size: 2em;
+ margin: 1.5em 0 0;
+ font-weight: bold;
+ line-height: 1.5em;
+ """
+ },
+ "naglowek_czesc": {
+ "editor_css": """
+ font-size: 2em;
+ margin: 1.5em 0 0;
+ font-weight: bold;
+ line-height: 1.5em;
+ """
+ },
+ "srodtytul": {
+ "editor_css": """
+ font-size: 2em;
+ margin: 1.5em 0 0;
+ font-weight: bold;
+ line-height: 1.5em;
+ """
+ },
+ "naglowek_akt": {
+ "editor_css": """
+ font-size: 2em;
+ margin: 1.5em 0 0;
+ font-weight: bold;
+ line-height: 1.5em;
+ """
+ },
+ "nazwa_utworu": {
+ "editor_css": """
+ font-size: 3em;
+ margin: 1.5em 0;
+ text-align: center;
+ line-height: 1.5em;
+ font-weight: bold;
+ """,
+ },
+ "naglowek_listy": {},
+ "lista_osoba": {},
+ "miejsce_czas": {
+ "editor_css": """font-style: italic;""",
+ },
+ "akap": {
+ "editor_css": """
+ text-align: justify;
+ margin: 1.5em 0 0;
+ """
+ },
+ "akap_cd": {
+ "editor_css": """
+ text-align: justify;
+ margin: 1.5em 0 0;
+ """
+ },
+ "akap_dialog": {
+ "editor_css": """
+ text-align: justify;
+ margin: 1.5em 0 0;
+ """
+ },
+ "motto_podpis": {
+ "editor_css": """
+ position: relative;
+ right: -3em;
+ text-align: right;
+ """
+ },
+ "uwaga": {},
+ "extra": {},
+ },
+ "verse": {
+ "wers_cd": {},
+ "wers_akap": {
+ "editor_css": """padding-left: 1em;"""
+ },
+ "wers_wciety": {},
+ "wers_do_prawej": {
+ "editor_css": """
+ text-align: right;
+ """
+ },
+ "wers": {},
+ },
+ "span": {
+ "tytul_dziela": {
+ "editor_css": """font-style: italic;"""
+ },
+ "wyroznienie": {
+ "editor_css": """font-style: italic;
+letter-spacing: 0.1em;"""
+ },
+ "slowo_obce": {
+ "editor_css": """font-style: italic;"""
+ },
+ "mat": {
+ "editor_css": """font-style: italic;"""
+ },
+ "didask_tekst": {},
+ "osoba": {
+ "editor_css": """font-style: normal;
+font-variant: small-caps;"""
+ },
+ "wyp_osoba": {},
+ "www": {},
+ "wieksze_odstepy": {
+ "editor_css": """font-style: normal;
+word-spacing: 1em;"""
+ },
+ "indeks_dolny": {
+ "editor_css": """font-style: normal;
+vertical-align: sub;
+font-size: .9em;"""
+ },
+ "zastepnik_wersu": {},
+ },
+ "sep": {
+ "sekcja_swiatlo": {
+ "editor_css": """
+ margin: 2em 0;
+ visibility: hidden;
+ """
+ },
+ "sekcja_asterysk": {
+ "editor_css": """
+ border: none;
+ text-align: center;
+ """,
+ "editor_css_after": """
+ content: "*";
+ """
+ },
+ "separator_linia": {
+ "editor_css": """
+ margin: 1.5em 0;
+ border: none;
+ border-bottom: 0.1em solid #000;
+ """
+ },
+ },
+ "aside": {
+ "pr": {},
+ "pa": {},
+ "pe": {},
+ "pt": {},
+ }
+
+ # To nie są wszystkie tagi.
+ # Brakuje:
+ # strofa?
+ # motyw,begin,end?
+ # ref?
+}
+
+
+def create_tags(apps, schema_editor):
+ Tag = apps.get_model('wlxml', 'Tag')
+ for tag_type, tags in initial_tags.items():
+ for name, props in tags.items():
+ Tag.objects.create(
+ type=tag_type,
+ name=name,
+ **props
+ )
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('wlxml', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RunPython(
+ create_tags,
+ migrations.RunPython.noop
+ )
+ ]
--- /dev/null
+# Generated by Django 3.1.13 on 2021-08-13 23:21
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('wlxml', '0002_initial_data'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='tag',
+ name='description',
+ field=models.TextField(blank=True),
+ ),
+ migrations.AddField(
+ model_name='tag',
+ name='example',
+ field=models.TextField(blank=True),
+ ),
+ ]
--- /dev/null
+# Generated by Django 3.1.13 on 2021-08-14 10:52
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('wlxml', '0003_auto_20210813_2321'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='tag',
+ name='example_html',
+ field=models.FileField(blank=True, upload_to='wlxml/tag/example/html/'),
+ ),
+ migrations.AddField(
+ model_name='tag',
+ name='example_pdf',
+ field=models.FileField(blank=True, upload_to='wlxml/tag/example/pdf/'),
+ ),
+ ]
--- /dev/null
+# Generated by Django 3.1.13 on 2021-08-14 11:03
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('wlxml', '0004_auto_20210814_1052'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='tag',
+ name='example_txt',
+ field=models.FileField(blank=True, upload_to='wlxml/tag/example/txt/'),
+ ),
+ ]
--- /dev/null
+from io import BytesIO
+from django.apps import apps
+from django.core.files import File
+from django.db import models
+from django.urls import reverse
+from django.utils.translation import gettext_lazy as _
+from librarian import DocProvider
+from librarian.parser import WLDocument as LegacyWLDocument
+from librarian.builders import StandaloneHtmlBuilder, TxtBuilder
+from librarian.document import WLDocument
+
+
+class Tag(models.Model):
+ name = models.CharField(max_length=255, unique=True, db_index=True)
+ type = models.CharField(max_length=255, choices=[
+ ('section', _('Section, contains blocks')),
+ ('div', _('Block element, like a paragraph')),
+ ('span', _('Inline element, like an emphasis')),
+ ('sep', _('Separator, has no content')),
+ ('aside', _('Aside content, like a footnote')),
+ ('verse', _('Verse element')),
+ ], blank=True)
+ similar_to = models.ForeignKey('self', models.PROTECT, null=True, blank=True)
+ description = models.TextField(blank=True)
+ example = models.TextField(blank=True)
+
+ example_html = models.FileField(upload_to='wlxml/tag/example/html/', blank=True)
+ example_pdf = models.FileField(upload_to='wlxml/tag/example/pdf/', blank=True)
+ example_txt = models.FileField(upload_to='wlxml/tag/example/txt/', blank=True)
+
+ # border_radius?
+ editor_css = models.TextField(blank=True)
+ editor_css_after = models.TextField(blank=True)
+
+ class Meta:
+ verbose_name = _('tag')
+ verbose_name_plural = _('tags')
+
+ def __str__(self):
+ return self.name
+
+ def get_absolute_url(self):
+ return reverse('wlxml_tag', args=[self.name])
+ ### allowed tags?
+
+ def save(self, **kwargs):
+ docbytes = b'''<utwor>
+
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<rdf:Description rdf:about="http://redakcja.wolnelektury.pl/documents/book/brudnopis/">
+
+<dc:language xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">pol</dc:language>
+<dc:creator xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">test</dc:creator>
+<dc:title xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">test</dc:title>
+<dc:date xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">test</dc:date>
+<dc:publisher xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">test</dc:publisher>
+<dc:identifier.url xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">test</dc:identifier.url>
+<dc:rights xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">test</dc:rights>
+
+</rdf:Description>
+</rdf:RDF>
+
+<opowiadanie>''' + self.example.encode('utf-8') + b'</opowiadanie></utwor>'
+
+
+ doc = WLDocument(filename=BytesIO(docbytes))
+
+ self.example_html.save(
+ self.name + '.html',
+ File(
+ StandaloneHtmlBuilder().build(doc).get_file()),
+ save=False)
+ self.example_txt.save(
+ self.name + '.txt',
+ File(
+ TxtBuilder().build(doc).get_file()),
+ save=False)
+
+ provider=DocProvider()
+ legacy_doc = LegacyWLDocument.from_bytes(docbytes, provider=provider)
+
+ self.example_pdf.save(
+ self.name + '.pdf',
+ File(legacy_doc.as_pdf().get_file()),
+ save=False)
+
+
+ super().save(**kwargs)
+
+
+
+class Attribute(models.Model):
+ tag = models.ForeignKey(Tag, models.CASCADE)
+ name = models.CharField(max_length=255)
+
+ class Meta:
+ verbose_name = _('attribute')
+ verbose_name_plural = _('attribute')
+
+ unique_together = [
+ ('tag', 'name'),
+ ]
+
+ def __str__(self):
+ return self.name
+
+
+class TagUsage(models.Model):
+ tag = models.ForeignKey(Tag, models.CASCADE)
+ chunk = models.ForeignKey('documents.Chunk', models.CASCADE)
+
+ class Meta:
+ verbose_name = _('tag usage')
+ verbose_name_plural = _('tags usage')
+
+ def __str__(self):
+ return f'{self.tag.name} @ {self.chunk.slug}'
+
+
+ @classmethod
+ def update_chunk(cls, chunk):
+ tag_names = set()
+ attribute_items = {}
+ doc = WLDocument.from_bytes(chunk.materialize().encode('utf-8'))
+ for element in doc.edoc.iter():
+ tag_names.add(element.tag)
+ for k, v in element.attrib.iteritems():
+ attribute_items.setdefault(element.tag, set()).add((k, v))
+
+ cls.objects.filter(chunk=chunk).exclude(tag__name__in=tag_names).delete()
+ for tag_name in tag_names:
+ tag, create = Tag.objects.get_or_create(name=tag_name)
+ tu, created = cls.objects.get_or_create(tag=tag, chunk=chunk)
+
+ new_attributes = attribute_items.get(tag_name, [])
+
+ for attr in tu.attributeusage_set.all():
+ key = (attr.attribute.name, value)
+ if key not in new_attributes:
+ attr.delete()
+ else:
+ new_attributes.delete(key)
+
+ for k, v in new_attributes:
+ attribute, created = tag.attribute_set.get_or_create(name=k)
+ tu.attributeusage_set.create(attribute=attribute, value=v)
+
+
+ @classmethod
+ def update_all_chunks(cls):
+ Chunk = apps.get_model('documents', 'Chunk')
+ for chunk in Chunk.objects.all():
+ cls.update_chunk(chunk)
+
+
+class AttributeUsage(models.Model):
+ tag_usage = models.ForeignKey(TagUsage, models.CASCADE)
+ attribute = models.ForeignKey(Attribute, models.CASCADE)
+ value = models.CharField(max_length=2048, blank=True)
+
+ class Meta:
+ verbose_name = _('attribute usage')
+ verbose_name_plural = _('attributes usage')
+
--- /dev/null
+{% for tag in object_list %}
+.htmlview [x-node="{{ tag.name }}"] {
+ {{ tag.editor_css|safe }}
+}
+{% if tag.editor_css_after %}
+.htmlview [x-node="{{ tag.name }}"]::after {
+ {{ tag.editor_css_after|safe }}
+}
+{% endif %}
+{% endfor %}
--- /dev/null
+{% extends "documents/base.html" %}
+
+
+{% block content %}
+<h1>{{ object }}</h1>
+
+<p>{{ object.description|linebreaksbr }}</p>
+
+<h2>Przykład</h2>
+<pre style="border:1px solid black;padding: 1em; margin: 2em;">{{ object.example }}</pre>
+
+
+<h2>HTML</h2>
+<iframe src="{{ object.example_html.url }}" style="width: 100%; height: 500px;"></iframe>
+
+<h2>PDF</h2>
+<iframe src="{{ object.example_pdf.url }}" style="width: 100%; height: 500px;"></iframe>
+
+<h2>TXT</h2>
+<iframe src="{{ object.example_txt.url }}" style="width: 100%; height: 500px;"></iframe>
+
+
+{% endblock %}
--- /dev/null
+<ul>
+{% for tag in object_list %}
+ <li><a href="{{ tag.get_absolute_url }}">{{ tag }}</li>
+{% endfor %}
+</ul>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="1.0"
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="html" omit-xml-declaration="yes" encoding="utf-8" indent="no" />
+
+ <!--
+ Base tag for rendering a fragment of text
+ -->
+ <xsl:template match="chunk">
+ <xsl:apply-templates select="child::node()">
+ <xsl:with-param name="mixed" select="true()" />
+ </xsl:apply-templates>
+ </xsl:template>
+
+ <xsl:template match="utwor">
+ <div>
+ <xsl:call-template name="standard-attributes" />
+ <xsl:apply-templates select="child::node()">
+ <xsl:with-param name="mixed" select="false()" />
+ </xsl:apply-templates>
+ </div>
+ </xsl:template>
+
+
+ <!-- nieedytowany - zawiera bloki section-->
+ <xsl:template match="{{ tags.section|join:"|" }}">
+ <xsl:param name="mixed" />
+ <div>
+ <xsl:call-template name="standard-attributes" />
+ <xsl:apply-templates select="child::node()">
+ <xsl:with-param name="mixed" select="false()" />
+ </xsl:apply-templates>
+ </div>
+ </xsl:template>
+
+ <!-- edytowalny - zawiera tekst div -->
+ <xsl:template match="{{ tags.div|join:"|" }}">
+ <xsl:param name="mixed" />
+ <div x-editable="true">
+ <xsl:call-template name="standard-attributes" />
+ <xsl:apply-templates select="child::node()">
+ <xsl:with-param name="mixed" select="true()" />
+ </xsl:apply-templates>
+ </div>
+ </xsl:template>
+
+ <xsl:template match="ilustr">
+ <div>
+ <xsl:call-template name="standard-attributes" />
+ <img>
+ <xsl:attribute name="src">
+ <xsl:value-of select="@src" />
+ </xsl:attribute>
+ </img>
+ <p class="alt"><xsl:value-of select="@alt"/></p>
+ </div>
+ </xsl:template>
+
+ <!--
+ ********
+ STROFA
+ ********
+ -->
+ <xsl:template match="strofa">
+ <div x-editable="true">
+ <xsl:call-template name="standard-attributes" />
+
+ <xsl:choose>
+ <xsl:when test="count(br) > 0">
+ <xsl:variable name="first-verse" select="br[1]/preceding-sibling::node()" />
+ <xsl:call-template name="verse">
+ <xsl:with-param name="verse-content" select="$first-verse" />
+ </xsl:call-template>
+ <xsl:for-each select="br">
+ <xsl:variable name="lnum" select="count(preceding-sibling::br)" />
+ <!-- select all nodes up to the next br or end of stanza -->
+ <xsl:variable name="current-verse"
+ select="following-sibling::node()[count(preceding-sibling::br) = $lnum+1]" />
+ <xsl:call-template name="verse">
+ <xsl:with-param name="verse-content" select="$current-verse" />
+ </xsl:call-template>
+ </xsl:for-each>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="verse">
+ <xsl:with-param name="verse-content" select="child::node()" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </div>
+ </xsl:template>
+
+ <xsl:template name="verse">
+ <!-- the verse contents including the last br (if any) -->
+ <xsl:param name="verse-content" />
+ <xsl:variable name="first-tag-name" select="name($verse-content/self::*)" />
+ <!-- name of text nodes is '' == false -->
+
+ <!-- THIS IS A HORROR!!! -->
+ <!-- Possible variants: -->
+ <xsl:choose>
+ <!-- Simple verse == not wers_ tags anywhere until the ending br -->
+ <xsl:when test="not($verse-content[starts-with(name(), 'wers')])">
+ <div class="wers" x-node="wers" x-verse="true" x-auto-node="true">
+ <xsl:apply-templates select="$verse-content[local-name(.) != 'br']">
+ <xsl:with-param name="mixed" select="true()" />
+ </xsl:apply-templates>
+ </div>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:apply-templates select="$verse-content[local-name(.) != 'br']">
+ <xsl:with-param name="mixed" select="false()" />
+ </xsl:apply-templates>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ {% if tags.verse %}
+ <xsl:template match="{{ tags.verse|join:"|" }}">
+ <xsl:param name="mixed" />
+ <div x-verse="true">
+ <xsl:call-template name="standard-attributes" />
+ <xsl:apply-templates select="child::node()">
+ <xsl:with-param name="mixed" select="true()" />
+ </xsl:apply-templates>
+ </div>
+ </xsl:template>
+ {% endif %}
+
+ <xsl:template match="br"><xsl:text>/</xsl:text></xsl:template>
+
+ {% if tags.span %}
+ <!-- Style znakowe span -->
+ <xsl:template match="{{ tags.span|join:"|" }}">
+ <xsl:param name="mixed" />
+ <em>
+ <xsl:call-template name="standard-attributes" />
+ <xsl:apply-templates select="child::node()">
+ <xsl:with-param name="mixed" select="true()" />
+ </xsl:apply-templates>
+ </em>
+ </xsl:template>
+ {% endif %}
+
+ {% if tags.sep %}
+ <!-- Separatory sep -->
+ <xsl:template match="{{ tags.sep|join:"|" }}">
+ <xsl:param name="mixed" />
+ <hr><xsl:call-template name="standard-attributes" /></hr>
+ </xsl:template>
+ {% endif %}
+
+ {% if tags.aside %}
+ <!-- Przypisy i motywy aside -->
+ <xsl:template match="{{ tags.aside|join:"|" }}">
+ <span x-editable="true">
+ <xsl:call-template name="standard-attributes">
+ <xsl:with-param name="extra-class" select="'annotation-inline-box'" />
+ </xsl:call-template>
+ <a name="anchor-{generate-id(.)}" />
+ <!-- the link to the non-inline version -->
+ <a href="#annotation-{generate-id(.)}" class="annotation"></a>
+ <!-- inline contents -->
+ <span x-annotation-box="true" x-pass-thru="true">
+ <xsl:apply-templates select="node()">
+ <xsl:with-param name="mixed" select="true()" />
+ </xsl:apply-templates>
+ </span>
+ </span>
+ </xsl:template>
+ {% endif %}
+
+ <xsl:template match="ref">
+ <span x-editable="true" x-edit-no-format="true" x-edit-attribute="href">
+ <xsl:call-template name="standard-attributes">
+ <xsl:with-param name="extra-class" select="'reference-inline-box'" />
+ </xsl:call-template>
+ <a class="reference">📌</a>
+ </span>
+ </xsl:template>
+
+ <xsl:template match="begin">
+ <span>
+ <xsl:call-template name="standard-attributes" />
+ <xsl:attribute name="theme-class">
+ <xsl:value-of select="substring-after(@id, 'b')" />
+ </xsl:attribute>
+ </span>
+ </xsl:template>
+
+ <xsl:template match="motyw">
+ <span x-editable="true" x-edit-no-format="true">
+ <xsl:call-template name="standard-attributes" />
+ <xsl:attribute name="theme-class">
+ <xsl:value-of select="substring-after(@id, 'm')" />
+ </xsl:attribute>
+ <span x-pass-thru="true" class="theme-text-list"><xsl:value-of select=".|node()" /></span>
+ </span>
+ </xsl:template>
+
+ <xsl:template match="end">
+ <span>
+ <xsl:call-template name="standard-attributes" />
+ <xsl:attribute name="theme-class">
+ <xsl:value-of select="substring-after(@id, 'e')" />
+ </xsl:attribute>
+ </span>
+ </xsl:template>
+
+
+ <!-- Tekst -->
+ <xsl:template match="text()">
+ <!-- <xsl:value-of select="." /> -->
+ <xsl:param name="mixed" />
+ <xsl:choose>
+ <xsl:when test="normalize-space(.) = ''">
+ <xsl:value-of select="." />
+ </xsl:when>
+ <xsl:when test="not($mixed)">
+ <span x-node="out-of-flow-text" class="out-of-flow-text" x-editable="true">
+ <xsl:value-of select="." />
+ </span>
+ </xsl:when>
+ <xsl:otherwise><xsl:value-of select="." /></xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <xsl:template match="comment()">
+ <xsl:comment><xsl:value-of select="." /></xsl:comment>
+ </xsl:template>
+
+ <xsl:template match="*[name() != local-name()]">
+ <span>
+ <xsl:call-template name="standard-attributes" />
+ <xsl:apply-templates select="child::node()">
+ <xsl:with-param name="mixed" select="true()" />
+ </xsl:apply-templates>
+ </span>
+ </xsl:template>
+
+ <xsl:template match="*">
+ <span>
+ <xsl:call-template name="standard-attributes">
+ <xsl:with-param name="extra-class">unknown-tag</xsl:with-param>
+ </xsl:call-template>
+ <xsl:apply-templates select="child::node()">
+ <xsl:with-param name="mixed" select="true()" />
+ </xsl:apply-templates>
+ </span>
+ </xsl:template>
+
+ <xsl:template name="standard-attributes">
+ <xsl:param name="extra-class" />
+ <xsl:attribute name="class"><xsl:value-of select="$extra-class" /></xsl:attribute>
+
+ <xsl:attribute name="x-node"><xsl:value-of select="local-name()" /></xsl:attribute>
+
+ <xsl:if test="local-name() != name()">
+ <xsl:attribute name="x-ns"><xsl:value-of select="namespace-uri()" /></xsl:attribute>
+ </xsl:if>
+
+ <xsl:for-each select="@*">
+ <xsl:variable name="id" select="generate-id()" />
+ <xsl:attribute name="x-attr-value-{$id}"><xsl:value-of select="."/></xsl:attribute>
+ <xsl:attribute name="x-attr-name-{$id}"><xsl:value-of select="local-name()"/></xsl:attribute>
+ <xsl:choose>
+ <xsl:when test="namespace-uri()">
+ <xsl:attribute name="x-attr-ns-{$id}"><xsl:value-of select="namespace-uri()"/></xsl:attribute>
+ </xsl:when>
+ <!-- if the element belongs to default namespace and attribut has no namespace -->
+ <xsl:when test="not(namespace-uri(.))">
+ <xsl:attribute name="data-wlf-{local-name()}"><xsl:value-of select="."/></xsl:attribute>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:for-each>
+ </xsl:template>
+
+ <xsl:template match="alien">
+ <span class="alien" x-pass-thru="true">
+ <xsl:apply-templates select="node()">
+ <xsl:with-param name="mixed" select="true()" />
+ </xsl:apply-templates>
+ </span>
+ </xsl:template>
+
+ <xsl:template match="comment()">
+ <xsl:comment><xsl:value-of select="."/></xsl:comment>
+ </xsl:template>
+</xsl:stylesheet>
--- /dev/null
+from django.urls import path
+from . import views
+
+
+urlpatterns = [
+ path('wl2html.xsl', views.XslView.as_view()),
+ path('wl.css', views.EditorCSS.as_view()),
+
+ path('tags/', views.TagsView.as_view(), name='wlxml_tags'),
+ path('tags/<slug>/', views.TagView.as_view(), name='wlxml_tag'),
+]
--- /dev/null
+from io import BytesIO
+from django.views.generic import TemplateView, ListView, DetailView
+from . import models
+from librarian.document import WLDocument
+from librarian.builders import StandaloneHtmlBuilder
+
+
+class XslView(TemplateView):
+ template_name = 'wlxml/wl2html.xsl'
+ content_type = 'application/xslt+xml'
+
+ def get_context_data(self):
+ ctx = super().get_context_data()
+ tags = {}
+ for t in models.Tag.objects.all():
+ tags.setdefault(t.type, []).append(t.name)
+ ctx['tags'] = tags
+ return ctx
+
+
+class EditorCSS(ListView):
+ template_name = 'wlxml/editor.css'
+ content_type = 'text/css'
+ queryset = models.Tag.objects.all()
+
+
+class TagsView(ListView):
+ queryset = models.Tag.objects.all()
+
+
+class TagView(DetailView):
+ queryset = models.Tag.objects.all()
+ slug_field = 'name'
+