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
|
||||
* 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(),
|
||||
* setContent(html) and focus().
|
||||
*/
|
||||
@@ -347,6 +350,7 @@ var XxxEditor = (function () {
|
||||
var redoStack = [];
|
||||
var lastInput = { time: 0, type: "" };
|
||||
var savedRange = null;
|
||||
var sourceMode = false;
|
||||
|
||||
// Paragraphs are the default block, as in TinyMCE
|
||||
try { document.execCommand("defaultParagraphSeparator", false, "p"); } catch (e) { }
|
||||
@@ -354,6 +358,8 @@ var XxxEditor = (function () {
|
||||
/* ---------- content ---------- */
|
||||
|
||||
function sync() {
|
||||
// In source mode the textarea itself holds the story
|
||||
if (sourceMode) return;
|
||||
var html = serialize(box);
|
||||
if (textarea.value !== html) {
|
||||
textarea.value = html;
|
||||
@@ -406,6 +412,7 @@ var XxxEditor = (function () {
|
||||
}
|
||||
|
||||
function setContent(html) {
|
||||
if (sourceMode) showEditor();
|
||||
box.textContent = "";
|
||||
box.appendChild(parse(html));
|
||||
// " " in an empty paragraph becomes a <br> that holds the caret
|
||||
@@ -771,24 +778,50 @@ var XxxEditor = (function () {
|
||||
});
|
||||
body.appendChild(table);
|
||||
body.appendChild(preview);
|
||||
dialog = openDialog("Special character", body, [{ text: "Close" }]);
|
||||
dialog = openDialog("Special character", body, [{ text: "Close" }], null, true);
|
||||
}
|
||||
|
||||
/* ---------- source code ---------- */
|
||||
|
||||
function codeDialog() {
|
||||
var source = el("textarea", "xed-source");
|
||||
source.value = serialize(box);
|
||||
source.spellcheck = false;
|
||||
openDialog("Source code", source, [
|
||||
{ text: "Ok", primary: true, action: function () {
|
||||
remember();
|
||||
setContent(source.value);
|
||||
box.focus();
|
||||
changed();
|
||||
} },
|
||||
{ text: "Cancel" }
|
||||
], source, "xed-window-code");
|
||||
// Show the plain textarea in place of the editor, with the HTML in it
|
||||
function showSource() {
|
||||
sync();
|
||||
sourceMode = true;
|
||||
box.hidden = true;
|
||||
textarea.hidden = false;
|
||||
textarea.style.display = "";
|
||||
Object.keys(buttons).forEach(function (command) {
|
||||
if (command !== "code") buttons[command].disabled = true;
|
||||
});
|
||||
buttons.code.classList.add("xed-active");
|
||||
buttons.code.setAttribute("aria-pressed", "true");
|
||||
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() {
|
||||
@@ -799,10 +832,10 @@ var XxxEditor = (function () {
|
||||
|
||||
/* ---------- dialogs ---------- */
|
||||
|
||||
// A modal window in the style of the TinyMCE skin
|
||||
function openDialog(title, content, actions, focus, extraClass) {
|
||||
var block = el("div", "xed-modal-block");
|
||||
var win = el("div", "xed-window" + (extraClass ? " " + extraClass : ""));
|
||||
// A modal window. With *light*, a click outside the window closes it.
|
||||
function openDialog(title, content, actions, focus, light) {
|
||||
var block = el("div", "xed-modal-block" + (light ? " xed-modal-light" : ""));
|
||||
var win = el("div", "xed-window");
|
||||
win.setAttribute("role", "dialog");
|
||||
win.setAttribute("aria-modal", "true");
|
||||
var head = el("div", "xed-window-head");
|
||||
@@ -842,6 +875,7 @@ var XxxEditor = (function () {
|
||||
foot.appendChild(button);
|
||||
});
|
||||
close.addEventListener("click", cancel);
|
||||
if (light) block.addEventListener("click", cancel);
|
||||
function onKey(event) {
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
@@ -881,7 +915,7 @@ var XxxEditor = (function () {
|
||||
function run(command) {
|
||||
if (command === "link") return linkDialog();
|
||||
if (command === "charmap") return charmapDialog();
|
||||
if (command === "code") return codeDialog();
|
||||
if (command === "code") return toggleSource();
|
||||
box.focus();
|
||||
if (!currentRange()) {
|
||||
// No caret yet: start at the end
|
||||
@@ -930,6 +964,7 @@ var XxxEditor = (function () {
|
||||
function refresh() {
|
||||
var range = document.activeElement === box ? currentRange() : null;
|
||||
Object.keys(buttons).forEach(function (command) {
|
||||
if (command === "code") return;
|
||||
var on = !!range && isActive(command, range);
|
||||
buttons[command].classList.toggle("xed-active", on);
|
||||
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.title = spec.title + (spec.key ? " (Ctrl+" + spec.key.toUpperCase() + ")" : "");
|
||||
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));
|
||||
// Keep the selection in the editor when the button is pressed
|
||||
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
|
||||
textarea.hidden = true;
|
||||
textarea.style.display = "none";
|
||||
textarea.classList.add("xed-source");
|
||||
setContent(textarea.value);
|
||||
if (textarea.form) textarea.form.addEventListener("submit", sync);
|
||||
|
||||
return {
|
||||
getContent: function () { return serialize(box); },
|
||||
getContent: function () { return sourceMode ? textarea.value : serialize(box); },
|
||||
setContent: function (html) { setContent(html); },
|
||||
focus: function () { box.focus(); }
|
||||
focus: function () { (sourceMode ? textarea : box).focus(); }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user