Edit the story source in the textarea.
Also close the special character window on a click outside it.
This commit is contained in:
+58
-22
@@ -13,6 +13,9 @@
|
|||||||
* padding-left steps of 30px. The server cleans the story with HTML
|
* padding-left steps of 30px. The server cleans the story with HTML
|
||||||
* Purifier in any case.
|
* Purifier in any case.
|
||||||
*
|
*
|
||||||
|
* The source code button swaps the editor for the plain textarea, so that
|
||||||
|
* the HTML can be edited directly, and swaps it back when pressed again.
|
||||||
|
*
|
||||||
* Usage: XxxEditor.attach(textarea) returns an object with getContent(),
|
* Usage: XxxEditor.attach(textarea) returns an object with getContent(),
|
||||||
* setContent(html) and focus().
|
* setContent(html) and focus().
|
||||||
*/
|
*/
|
||||||
@@ -347,6 +350,7 @@ var XxxEditor = (function () {
|
|||||||
var redoStack = [];
|
var redoStack = [];
|
||||||
var lastInput = { time: 0, type: "" };
|
var lastInput = { time: 0, type: "" };
|
||||||
var savedRange = null;
|
var savedRange = null;
|
||||||
|
var sourceMode = false;
|
||||||
|
|
||||||
// Paragraphs are the default block, as in TinyMCE
|
// Paragraphs are the default block, as in TinyMCE
|
||||||
try { document.execCommand("defaultParagraphSeparator", false, "p"); } catch (e) { }
|
try { document.execCommand("defaultParagraphSeparator", false, "p"); } catch (e) { }
|
||||||
@@ -354,6 +358,8 @@ var XxxEditor = (function () {
|
|||||||
/* ---------- content ---------- */
|
/* ---------- content ---------- */
|
||||||
|
|
||||||
function sync() {
|
function sync() {
|
||||||
|
// In source mode the textarea itself holds the story
|
||||||
|
if (sourceMode) return;
|
||||||
var html = serialize(box);
|
var html = serialize(box);
|
||||||
if (textarea.value !== html) {
|
if (textarea.value !== html) {
|
||||||
textarea.value = html;
|
textarea.value = html;
|
||||||
@@ -406,6 +412,7 @@ var XxxEditor = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setContent(html) {
|
function setContent(html) {
|
||||||
|
if (sourceMode) showEditor();
|
||||||
box.textContent = "";
|
box.textContent = "";
|
||||||
box.appendChild(parse(html));
|
box.appendChild(parse(html));
|
||||||
// " " in an empty paragraph becomes a <br> that holds the caret
|
// " " in an empty paragraph becomes a <br> that holds the caret
|
||||||
@@ -771,24 +778,50 @@ var XxxEditor = (function () {
|
|||||||
});
|
});
|
||||||
body.appendChild(table);
|
body.appendChild(table);
|
||||||
body.appendChild(preview);
|
body.appendChild(preview);
|
||||||
dialog = openDialog("Special character", body, [{ text: "Close" }]);
|
dialog = openDialog("Special character", body, [{ text: "Close" }], null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- source code ---------- */
|
/* ---------- source code ---------- */
|
||||||
|
|
||||||
function codeDialog() {
|
// Show the plain textarea in place of the editor, with the HTML in it
|
||||||
var source = el("textarea", "xed-source");
|
function showSource() {
|
||||||
source.value = serialize(box);
|
sync();
|
||||||
source.spellcheck = false;
|
sourceMode = true;
|
||||||
openDialog("Source code", source, [
|
box.hidden = true;
|
||||||
{ text: "Ok", primary: true, action: function () {
|
textarea.hidden = false;
|
||||||
remember();
|
textarea.style.display = "";
|
||||||
setContent(source.value);
|
Object.keys(buttons).forEach(function (command) {
|
||||||
box.focus();
|
if (command !== "code") buttons[command].disabled = true;
|
||||||
changed();
|
});
|
||||||
} },
|
buttons.code.classList.add("xed-active");
|
||||||
{ text: "Cancel" }
|
buttons.code.setAttribute("aria-pressed", "true");
|
||||||
], source, "xed-window-code");
|
textarea.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back to the editor, with the HTML from the textarea (cleaned)
|
||||||
|
function showEditor() {
|
||||||
|
sourceMode = false;
|
||||||
|
textarea.hidden = true;
|
||||||
|
textarea.style.display = "none";
|
||||||
|
box.hidden = false;
|
||||||
|
Object.keys(buttons).forEach(function (command) {
|
||||||
|
buttons[command].disabled = false;
|
||||||
|
});
|
||||||
|
buttons.code.classList.remove("xed-active");
|
||||||
|
buttons.code.setAttribute("aria-pressed", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSource() {
|
||||||
|
if (!sourceMode) {
|
||||||
|
showSource();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var html = textarea.value;
|
||||||
|
remember();
|
||||||
|
showEditor();
|
||||||
|
setContent(html);
|
||||||
|
box.focus();
|
||||||
|
changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreSelection() {
|
function restoreSelection() {
|
||||||
@@ -799,10 +832,10 @@ var XxxEditor = (function () {
|
|||||||
|
|
||||||
/* ---------- dialogs ---------- */
|
/* ---------- dialogs ---------- */
|
||||||
|
|
||||||
// A modal window in the style of the TinyMCE skin
|
// A modal window. With *light*, a click outside the window closes it.
|
||||||
function openDialog(title, content, actions, focus, extraClass) {
|
function openDialog(title, content, actions, focus, light) {
|
||||||
var block = el("div", "xed-modal-block");
|
var block = el("div", "xed-modal-block" + (light ? " xed-modal-light" : ""));
|
||||||
var win = el("div", "xed-window" + (extraClass ? " " + extraClass : ""));
|
var win = el("div", "xed-window");
|
||||||
win.setAttribute("role", "dialog");
|
win.setAttribute("role", "dialog");
|
||||||
win.setAttribute("aria-modal", "true");
|
win.setAttribute("aria-modal", "true");
|
||||||
var head = el("div", "xed-window-head");
|
var head = el("div", "xed-window-head");
|
||||||
@@ -842,6 +875,7 @@ var XxxEditor = (function () {
|
|||||||
foot.appendChild(button);
|
foot.appendChild(button);
|
||||||
});
|
});
|
||||||
close.addEventListener("click", cancel);
|
close.addEventListener("click", cancel);
|
||||||
|
if (light) block.addEventListener("click", cancel);
|
||||||
function onKey(event) {
|
function onKey(event) {
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -881,7 +915,7 @@ var XxxEditor = (function () {
|
|||||||
function run(command) {
|
function run(command) {
|
||||||
if (command === "link") return linkDialog();
|
if (command === "link") return linkDialog();
|
||||||
if (command === "charmap") return charmapDialog();
|
if (command === "charmap") return charmapDialog();
|
||||||
if (command === "code") return codeDialog();
|
if (command === "code") return toggleSource();
|
||||||
box.focus();
|
box.focus();
|
||||||
if (!currentRange()) {
|
if (!currentRange()) {
|
||||||
// No caret yet: start at the end
|
// No caret yet: start at the end
|
||||||
@@ -930,6 +964,7 @@ var XxxEditor = (function () {
|
|||||||
function refresh() {
|
function refresh() {
|
||||||
var range = document.activeElement === box ? currentRange() : null;
|
var range = document.activeElement === box ? currentRange() : null;
|
||||||
Object.keys(buttons).forEach(function (command) {
|
Object.keys(buttons).forEach(function (command) {
|
||||||
|
if (command === "code") return;
|
||||||
var on = !!range && isActive(command, range);
|
var on = !!range && isActive(command, range);
|
||||||
buttons[command].classList.toggle("xed-active", on);
|
buttons[command].classList.toggle("xed-active", on);
|
||||||
if (buttons[command].hasAttribute("aria-pressed")) buttons[command].setAttribute("aria-pressed", on ? "true" : "false");
|
if (buttons[command].hasAttribute("aria-pressed")) buttons[command].setAttribute("aria-pressed", on ? "true" : "false");
|
||||||
@@ -949,7 +984,7 @@ var XxxEditor = (function () {
|
|||||||
button.type = "button";
|
button.type = "button";
|
||||||
button.title = spec.title + (spec.key ? " (Ctrl+" + spec.key.toUpperCase() + ")" : "");
|
button.title = spec.title + (spec.key ? " (Ctrl+" + spec.key.toUpperCase() + ")" : "");
|
||||||
button.setAttribute("aria-label", spec.title);
|
button.setAttribute("aria-label", spec.title);
|
||||||
if (/^(bold|italic|align|blockquote)/.test(spec.command)) button.setAttribute("aria-pressed", "false");
|
if (/^(bold|italic|align|blockquote|code)/.test(spec.command)) button.setAttribute("aria-pressed", "false");
|
||||||
button.appendChild(icon(spec.command));
|
button.appendChild(icon(spec.command));
|
||||||
// Keep the selection in the editor when the button is pressed
|
// Keep the selection in the editor when the button is pressed
|
||||||
button.addEventListener("mousedown", function (event) { event.preventDefault(); });
|
button.addEventListener("mousedown", function (event) { event.preventDefault(); });
|
||||||
@@ -1051,13 +1086,14 @@ var XxxEditor = (function () {
|
|||||||
// The site style sets display on #story, which beats the hidden attribute
|
// The site style sets display on #story, which beats the hidden attribute
|
||||||
textarea.hidden = true;
|
textarea.hidden = true;
|
||||||
textarea.style.display = "none";
|
textarea.style.display = "none";
|
||||||
|
textarea.classList.add("xed-source");
|
||||||
setContent(textarea.value);
|
setContent(textarea.value);
|
||||||
if (textarea.form) textarea.form.addEventListener("submit", sync);
|
if (textarea.form) textarea.form.addEventListener("submit", sync);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getContent: function () { return serialize(box); },
|
getContent: function () { return sourceMode ? textarea.value : serialize(box); },
|
||||||
setContent: function (html) { setContent(html); },
|
setContent: function (html) { setContent(html); },
|
||||||
focus: function () { box.focus(); }
|
focus: function () { (sourceMode ? textarea : box).focus(); }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user