notes editor - use tiny cloud editor

This commit is contained in:
Azgaar 2022-02-14 22:51:31 +03:00
parent 13abacc242
commit fe8457ac4c
5 changed files with 142 additions and 134 deletions

View file

@ -2036,7 +2036,7 @@ div.textual span,
fill: none; fill: none;
} }
div#notes { #notes {
display: none; display: none;
position: fixed; position: fixed;
width: 28vw; width: 28vw;
@ -2046,22 +2046,31 @@ div#notes {
border: 1px solid #5e4fa2; border: 1px solid #5e4fa2;
background: rgba(255, 250, 228, 0.7); background: rgba(255, 250, 228, 0.7);
box-shadow: 2px 2px 5px -3px #3a2804; box-shadow: 2px 2px 5px -3px #3a2804;
white-space: pre-line;
} }
div#notesHeader { @media screen and (max-width: 600px) {
#notes {
width: 50vw;
}
}
#notesHeader {
font-weight: bold; font-weight: bold;
font-size: 1.3em; font-size: 1.3em;
padding: 0 0 4px 14px; padding: 16px 0 4px 12px;
border-bottom: 1px solid #5e4fa2; border-bottom: 1px solid #5e4fa2;
} }
div#notesBody { #notesBody {
padding: 0 1em; padding: 14px 12px;
max-height: 80vh; max-height: 80vh;
overflow: auto; overflow: auto;
} }
#notesBody p {
margin: 4px;
}
svg.button { svg.button {
position: relative; position: relative;
background-color: transparent; background-color: transparent;

View file

@ -2911,22 +2911,20 @@
</div> </div>
</div> </div>
<div id="notesEditor" class="dialog stable textual" style="display: none"> <div id="notesEditor" class="dialog stable" style="display: none">
<div> <div style="margin-bottom: 0.3em">
<span>Select object: </span> <strong>Element: </strong>
<select id="notesSelect" data-tip="Select object" style="width: 12em"></select> <select id="notesSelect" data-tip="Select element id" style="width: 12em"></select>
<span>Object name: </span> <strong>Element name: </strong>
<input id="notesName" data-tip="Type to change object name" autocorrect="off" spellcheck="false" style="width: 16em"> <input id="notesName" data-tip="Set element name" autocorrect="off" spellcheck="false" style="width: 16em">
<span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span> <span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span>
</div> </div>
<div id="notesText" data-tip="Type and style object description" style="padding: .4em 0"></div> <div id="notesLegend" style="padding: .4em 0"></div>
<div> <div style="margin-top: 0.3em">
<button id="notesSpeak" data-tip="Speak the note. You can change voice and language in options" class="icon-voice"></button>
<button id="notesFocus" data-tip="Focus on selected object" class="icon-target"></button> <button id="notesFocus" data-tip="Focus on selected object" class="icon-target"></button>
<button id="notesPin" data-tip="Toggle notes box dispay: hide or do not hide the box on mouse move" class="icon-pin"></button> <button id="notesPin" data-tip="Toggle notes box dispay: hide or do not hide the box on mouse move" class="icon-pin"></button>
<button id="notesDownload" data-tip="Download notes to PC" class="icon-download"></button> <button id="notesDownload" data-tip="Download notes to PC" class="icon-download"></button>
<button id="notesUpload" data-tip="Upload notes from PC" class="icon-upload"></button> <button id="notesUpload" data-tip="Upload notes from PC" class="icon-upload"></button>
<button id="notesClearStyle" data-tip="Remove all styling, get plain text only" class="icon-eraser"></button>
<button id="notesRemove" data-tip="Remove this note" class="icon-trash fastDelete"></button> <button id="notesRemove" data-tip="Remove this note" class="icon-trash fastDelete"></button>
</div> </div>
</div> </div>
@ -4549,7 +4547,6 @@
<script defer src="modules/coa-renderer.js"></script> <script defer src="modules/coa-renderer.js"></script>
<script defer src="libs/rgbquant.min.js"></script> <script defer src="libs/rgbquant.min.js"></script>
<script defer src="libs/jquery.ui.touch-punch.min.js"></script> <script defer src="libs/jquery.ui.touch-punch.min.js"></script>
<script defer src="libs/pell.min.js"></script>
<script defer src="libs/jszip.min.js"></script> <script defer src="libs/jszip.min.js"></script>
<script defer src="modules/io/save.js"></script> <script defer src="modules/io/save.js"></script>
@ -4558,6 +4555,8 @@
<script defer src="modules/io/export.js"></script> <script defer src="modules/io/export.js"></script>
<script defer src="modules/io/export-json.js"></script> <script defer src="modules/io/export-json.js"></script>
<script src="https://cdn.tiny.cloud/1/4i6a79ymt2y0cagke174jp3meoi28vyecrch12e5puyw3p9a/tinymce/5/tinymce.min.js" referrerpolicy="origin"></script>
<!-- Web Components --> <!-- Web Components -->
<script defer src="components/fill-box.js"></script> <script defer src="components/fill-box.js"></script>
</body> </body>

2
libs/pell.min.js vendored
View file

@ -1,2 +0,0 @@
// https://github.com/jaredreich/pell, MIT License
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Pell=t()}(this,function(){"use strict";const e=(e,t,n)=>e.addEventListener(t,n),t=(e,t)=>e.appendChild(t),n=e=>document.createElement(e),i=e=>document.queryCommandState(e),o=(e,t=null)=>document.execCommand(e,!1,t),l={bold:{icon:"<b>B</b>",title:"Bold",state:()=>i("bold"),result:()=>o("bold")},italic:{icon:"<i>I</i>",title:"Italic",state:()=>i("italic"),result:()=>o("italic")},underline:{icon:"<u>U</u>",title:"Underline",state:()=>i("underline"),result:()=>o("underline")},strikethrough:{icon:"<strike>S</strike>",title:"Strike-through",state:()=>i("strikeThrough"),result:()=>o("strikeThrough")},heading1:{icon:"<b>H<sub>1</sub></b>",title:"Heading 1",result:()=>o("formatBlock","<h1>")},heading2:{icon:"<b>H<sub>2</sub></b>",title:"Heading 2",result:()=>o("formatBlock","<h2>")},paragraph:{icon:"&#182;",title:"Paragraph",result:()=>o("formatBlock","<p>")},quote:{icon:"&#8220; &#8221;",title:"Quote",result:()=>o("formatBlock","<blockquote>")},olist:{icon:"&#35;",title:"Ordered List",result:()=>o("insertOrderedList")},ulist:{icon:"&#8226;",title:"Unordered List",result:()=>o("insertUnorderedList")},code:{icon:"&lt;/&gt;",title:"Code",result:()=>o("formatBlock","<pre>")},line:{icon:"&#8213;",title:"Horizontal Line",result:()=>o("insertHorizontalRule")},link:{icon:"&#128279;",title:"Link",result:()=>navigator.clipboard.readText().then(e=>o("createLink",e))},image:{icon:"&#128247;",title:"Image",result:()=>{navigator.clipboard.readText().then(e=>o("insertImage",e)),o("enableObjectResizing")}}},r={actionbar:"pell-actionbar",button:"pell-button",content:"pell-content",selected:"pell-button-selected"};return{exec:o,init:i=>{const a=i.actions?i.actions.map(e=>"string"==typeof e?l[e]:l[e.name]?{...l[e.name],...e}:e):Object.keys(l).map(e=>l[e]),s={...r,...i.classes},c=i.defaultParagraphSeparator||"div",u=n("div");u.className=s.actionbar,t(i.element,u);const d=i.element.content=n("div");return d.contentEditable=!0,d.className=s.content,d.oninput=(({target:{firstChild:e}})=>{e&&3===e.nodeType?o("formatBlock",`<${c}>`):"<br>"===d.innerHTML&&(d.innerHTML=""),i.onChange(d.innerHTML)}),d.onkeydown=(e=>{"Enter"===e.key&&"blockquote"===(e=>document.queryCommandValue(e))("formatBlock")&&setTimeout(()=>o("formatBlock",`<${c}>`),0)}),t(i.element,d),a.forEach(i=>{const o=n("button");if(o.className=s.button,o.innerHTML=i.icon,o.title=i.title,o.setAttribute("type","button"),o.onclick=(()=>i.result()&&d.focus()),i.state){const t=()=>o.classList[i.state()?"add":"remove"](s.selected);e(d,"keyup",t),e(d,"mouseup",t),e(o,"click",t)}t(u,o)}),i.styleWithCSS&&o("styleWithCSS"),o("defaultParagraphSeparator",c),i.element}}});

View file

@ -70,7 +70,8 @@ function mouseMove() {
const point = d3.mouse(this); const point = d3.mouse(this);
const i = findCell(point[0], point[1]); // pack cell id const i = findCell(point[0], point[1]); // pack cell id
if (i === undefined) return; if (i === undefined) return;
showNotes(d3.event, i);
showNotes(d3.event);
const g = findGridCell(point[0], point[1]); // grid cell id const g = findGridCell(point[0], point[1]); // grid cell id
if (tooltip.dataset.main) showMainTip(); if (tooltip.dataset.main) showMainTip();
else showMapTooltip(point, d3.event, i, g); else showMapTooltip(point, d3.event, i, g);
@ -78,7 +79,7 @@ function mouseMove() {
} }
// show note box on hover (if any) // show note box on hover (if any)
function showNotes(e, i) { function showNotes(e) {
if (notesEditor.offsetParent) return; if (notesEditor.offsetParent) return;
let id = e.target.id || e.target.parentNode.id || e.target.parentNode.parentNode.id; let id = e.target.id || e.target.parentNode.id || e.target.parentNode.parentNode.id;
if (e.target.parentNode.parentNode.id === "burgLabels") id = "burg" + e.target.dataset.id; if (e.target.parentNode.parentNode.id === "burgLabels") id = "burg" + e.target.dataset.id;

View file

@ -1,25 +1,22 @@
"use strict"; "use strict";
function editNotes(id, name) { function editNotes(id, name) {
// elements
const notesLegend = document.getElementById("notesLegend");
const notesName = document.getElementById("notesName");
const notesSelect = document.getElementById("notesSelect");
const notesPin = document.getElementById("notesPin");
// update list of objects // update list of objects
const select = document.getElementById("notesSelect"); notesSelect.options.length = 0;
select.options.length = 0;
for (const note of notes) { for (const note of notes) {
select.options.add(new Option(note.id, note.id)); notesSelect.options.add(new Option(note.id, note.id));
} }
// initiate pell (html editor) // update pin notes icon
const notesText = document.getElementById("notesText"); const notesArePinned = options.pinNotes;
notesText.innerHTML = ""; if (notesArePinned) notesPin.classList.add("pressed");
const editor = Pell.init({ else notesPin.classList.remove("pressed");
element: notesText,
onChange: html => {
const note = notes.find(note => note.id === select.value);
if (!note) return;
note.legend = html;
showNote(note);
}
});
// select an object // select an object
if (notes.length || id) { if (notes.length || id) {
@ -29,136 +26,140 @@ function editNotes(id, name) {
if (!name) name = id; if (!name) name = id;
note = {id, name, legend: ""}; note = {id, name, legend: ""};
notes.push(note); notes.push(note);
select.options.add(new Option(id, id)); notesSelect.options.add(new Option(id, id));
} }
select.value = id;
notesName.value = note.name; notesSelect.value = id;
editor.content.innerHTML = note.legend; notesName.value = note.name;
showNote(note); notesLegend.innerHTML = note.legend;
} else { initEditor();
editor.content.innerHTML = "There are no added notes. Click on element (e.g. label) and add a free text note"; updateNotesBox(note);
document.getElementById("notesName").value = ""; } else {
// if notes array is empty
notesName.value = "";
notesLegend.innerHTML = "No notes added. Click on an element (e.g. label or marker) and add a free text note";
} }
// open a dialog
$("#notesEditor").dialog({ $("#notesEditor").dialog({
title: "Notes Editor", title: "Notes Editor",
minWidth: "40em", width: "70vw",
width: "50vw", height: window.innerHeight * 0.75,
position: {my: "center", at: "center", of: "svg"} position: {my: "center", at: "center", of: "svg"},
close: removeEditor
}); });
$("[aria-describedby='notesEditor']").css("top", "10vh");
if (modules.editNotes) return; if (modules.editNotes) return;
modules.editNotes = true; modules.editNotes = true;
// add listeners // add listeners
document.getElementById("notesSelect").addEventListener("change", changeObject); document.getElementById("notesSelect").addEventListener("change", changeElement);
document.getElementById("notesName").addEventListener("input", changeName); document.getElementById("notesName").addEventListener("input", changeName);
document.getElementById("notesPin").addEventListener("click", () => (options.pinNotes = !options.pinNotes)); document.getElementById("notesPin").addEventListener("click", toggleNotesPin);
document.getElementById("notesSpeak").addEventListener("click", () => speak(editor.content.innerHTML));
document.getElementById("notesFocus").addEventListener("click", validateHighlightElement); document.getElementById("notesFocus").addEventListener("click", validateHighlightElement);
document.getElementById("notesDownload").addEventListener("click", downloadLegends); document.getElementById("notesDownload").addEventListener("click", downloadLegends);
document.getElementById("notesUpload").addEventListener("click", () => legendsToLoad.click()); document.getElementById("notesUpload").addEventListener("click", () => legendsToLoad.click());
document.getElementById("legendsToLoad").addEventListener("change", function () { document.getElementById("legendsToLoad").addEventListener("change", function () {
uploadFile(this, uploadLegends); uploadFile(this, uploadLegends);
}); });
document.getElementById("notesClearStyle").addEventListener("click", clearStyle);
document.getElementById("notesRemove").addEventListener("click", triggerNotesRemove); document.getElementById("notesRemove").addEventListener("click", triggerNotesRemove);
function showNote(note) { function initEditor() {
document.getElementById("notes").style.display = "block"; tinymce.init({
selector: "#notesLegend",
height: "90%",
menubar: false,
plugins: `autolink lists link charmap print formatpainter casechange code fullscreen image link media table paste hr checklist wordcount`,
toolbar: `fullscreen code | undo redo | bold italic strikethrough forecolor backcolor | formatpainter removeformat | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image media table | blockquote hr casechange checklist charmap print | fontselect fontsizeselect`,
media_alt_source: false,
media_poster: false,
setup: editor => {
editor.on("Change", updateLegend);
}
});
}
function updateLegend() {
const note = notes.find(note => note.id === notesSelect.value);
if (note && tinymce?.activeEditor) {
note.legend = tinymce.activeEditor.getContent();
updateNotesBox(note);
}
}
function updateNotesBox(note) {
document.getElementById("notesHeader").innerHTML = note.name; document.getElementById("notesHeader").innerHTML = note.name;
document.getElementById("notesBody").innerHTML = note.legend; document.getElementById("notesBody").innerHTML = note.legend;
} }
function changeObject() { function changeElement() {
const note = notes.find(note => note.id === this.value); const note = notes.find(note => note.id === this.value);
if (!note) return; if (!note) return tip("Note element is not found", true, "error", 4000);
notesName.value = note.name; notesName.value = note.name;
editor.content.innerHTML = note.legend; tinymce.activeEditor.setContent(note.legend);
updateNotesBox(note);
} }
function changeName() { function changeName() {
const id = document.getElementById("notesSelect").value; const note = notes.find(note => note.id === notesSelect.value);
const note = notes.find(note => note.id === id); if (!note) return tip("Note element is not found", true, "error", 4000);
if (!note) return;
note.name = this.value; note.name = this.value;
showNote(note);
} }
function validateHighlightElement() { function validateHighlightElement() {
const select = document.getElementById("notesSelect"); const element = document.getElementById(notesSelect.value);
const element = document.getElementById(select.value); if (element) return highlightElement(element, 3);
// if element is not found confirmationDialog({
if (element === null) {
alertMessage.innerHTML = "Related element is not found. Would you like to remove the note?";
$("#alert").dialog({
resizable: false,
title: "Element not found", title: "Element not found",
buttons: { message: "Note element is not found. Would you like to remove the note?",
Remove: function () { confirm: "Remove",
$(this).dialog("close"); cancel: "Keep",
removeLegend(); onConfirm: removeLegend
},
Keep: function () {
$(this).dialog("close");
}
}
}); });
return;
}
highlightElement(element, 3); // if element is found
} }
function downloadLegends() { function downloadLegends() {
const data = JSON.stringify(notes); const notesData = JSON.stringify(notes);
const name = getFileName("Notes") + ".txt"; const name = getFileName("Notes") + ".txt";
downloadFile(data, name); downloadFile(notesData, name);
} }
function uploadLegends(dataLoaded) { function uploadLegends(dataLoaded) {
if (!dataLoaded) { if (!dataLoaded) return tip("Cannot load the file. Please check the data format", false, "error");
tip("Cannot load the file. Please check the data format", false, "error");
return;
}
notes = JSON.parse(dataLoaded); notes = JSON.parse(dataLoaded);
document.getElementById("notesSelect").options.length = 0; notesSelect.options.length = 0;
editNotes(notes[0].id, notes[0].name); editNotes(notes[0].id, notes[0].name);
} }
function clearStyle() {
editor.content.innerHTML = editor.content.textContent;
}
function triggerNotesRemove() { function triggerNotesRemove() {
alertMessage.innerHTML = "Are you sure you want to remove the selected note?"; confirmationDialog({
$("#alert").dialog({
resizable: false,
title: "Remove note", title: "Remove note",
buttons: { message: "Are you sure you want to remove the selected note? There is no way to undo this action",
Remove: function () { confirm: "Remove",
$(this).dialog("close"); onConfirm: removeLegend
removeLegend();
},
Keep: function () {
$(this).dialog("close");
}
}
}); });
} }
function removeLegend() { function removeLegend() {
const select = document.getElementById("notesSelect"); const index = notes.findIndex(n => n.id === notesSelect.value);
const index = notes.findIndex(n => n.id === select.value);
notes.splice(index, 1); notes.splice(index, 1);
select.options.length = 0; notesSelect.options.length = 0;
if (!notes.length) { if (!notes.length) {
$("#notesEditor").dialog("close"); $("#notesEditor").dialog("close");
return; return;
} }
notesText.innerHTML = "";
editNotes(notes[0].id, notes[0].name); editNotes(notes[0].id, notes[0].name);
} }
function toggleNotesPin() {
options.pinNotes = !options.pinNotes;
this.classList.toggle("pressed");
}
function removeEditor() {
tinymce.remove();
}
} }