Needs Clipboard inserter chrome extension
LunaHook is preferred
- Fix any more bugs
- Add time elapsed / reading speed
| <html> | |
| <link | |
| href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap" | |
| rel="stylesheet" | |
| /> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <title>Texthooker</title> | |
| <style type="text/css"> | |
| /* noto-sans-jp-regular - latin_japanese */ | |
| @font-face { | |
| font-family: "Local Noto Sans JP"; | |
| font-style: normal; | |
| font-weight: 400; | |
| src: url("./fonts/noto-sans-jp-v28-latin_japanese-regular.eot"); /* IE9 Compat Modes */ | |
| src: local(""), | |
| url("./fonts/noto-sans-jp-v28-latin_japanese-regular.eot?#iefix") | |
| format("embedded-opentype"), | |
| /* IE6-IE8 */ | |
| url("./fonts/noto-sans-jp-v28-latin_japanese-regular.woff2") | |
| format("woff2"), | |
| /* Super Modern Browsers */ | |
| url("./fonts/noto-sans-jp-v28-latin_japanese-regular.woff") | |
| format("woff"), | |
| /* Modern Browsers */ | |
| url("./fonts/noto-sans-jp-v28-latin_japanese-regular.ttf") | |
| format("truetype"), | |
| /* Safari, Android, iOS */ | |
| url("./fonts/noto-sans-jp-v28-latin_japanese-regular.svg#NotoSansJP") | |
| format("svg"); /* Legacy iOS */ | |
| } | |
| body { | |
| background-color: #202020; | |
| color: #bdbdbd; | |
| font-weight: 400; | |
| line-height: 150%; | |
| margin-top: 1%; | |
| margin-left: 1.5%; | |
| margin-right: 10%; | |
| margin-bottom: 20%; | |
| font-family: "Local Noto Sans JP", "Noto Sans JP"; | |
| } | |
| .container { | |
| position: fixed; | |
| top: 3px; | |
| right: 5px; | |
| display: inline-block; | |
| } | |
| .container > div { | |
| display: inline-block; | |
| } | |
| .line_box { | |
| margin-top: 24px; | |
| } | |
| .undoed_line { | |
| animation-name: shine; | |
| animation-duration: 0.5s; | |
| } | |
| @keyframes shine { | |
| from { | |
| background-color: #8665af; | |
| } | |
| to { | |
| background-color: transparent; | |
| } | |
| } | |
| .remove_button { | |
| background-color: rgba(25, 25, 25, 0); | |
| color: #9d9d9d; | |
| cursor: pointer; | |
| cursor: hand; | |
| display: inline-block; | |
| font-size: 0.5em; | |
| line-height: 100%; | |
| margin-left: 8px; | |
| margin-bottom: 2px; | |
| padding: 5px; | |
| visibility: hidden; | |
| -webkit-touch-callout: none; | |
| -webkit-user-select: none; | |
| -khtml-user-select: none; | |
| -moz-user-select: none; | |
| -ms-user-select: none; | |
| -o-user-select: none; | |
| user-select: none; | |
| } | |
| .line_box:hover > .remove_button { | |
| visibility: visible; | |
| } | |
| #counter { | |
| text-align: right; | |
| } | |
| #remove_button { | |
| background-color: rgba(25, 25, 25, 0.8); | |
| color: #9d9d9d; | |
| font-size: 0.5em; | |
| line-height: 100%; | |
| float: right; | |
| padding-left: 8px; | |
| padding-right: 8px; | |
| padding-top: 5px; | |
| padding-bottom: 5px; | |
| cursor: pointer; | |
| cursor: hand; | |
| } | |
| #clear_button { | |
| background-color: rgba(25, 25, 25, 0.8); | |
| color: #9d9d9d; | |
| font-size: 0.5em; | |
| line-height: 100%; | |
| float: right; | |
| margin-right: 10px; | |
| padding-left: 8px; | |
| padding-right: 8px; | |
| padding-top: 5px; | |
| padding-bottom: 5px; | |
| cursor: pointer; | |
| cursor: hand; | |
| } | |
| #undo_button { | |
| background-color: rgba(25, 25, 25, 0.8); | |
| color: #9d9d9d; | |
| font-size: 0.5em; | |
| line-height: 100%; | |
| float: right; | |
| padding-left: 8px; | |
| padding-right: 8px; | |
| padding-top: 5px; | |
| padding-bottom: 5px; | |
| cursor: pointer; | |
| cursor: hand; | |
| } | |
| #counter-container { | |
| cursor: pointer; | |
| background-color: rgba(25, 25, 25, 0.8); | |
| color: #9d9d9d; | |
| font-size: 0.5em; | |
| line-height: 100%; | |
| padding-left: 8px; | |
| padding-right: 8px; | |
| padding-top: 5px; | |
| padding-bottom: 5px; | |
| } | |
| #detailed-counter { | |
| display: none; | |
| text-align: right; | |
| } | |
| #detailed-counter div { | |
| margin-top: 7; | |
| } | |
| #detailed-counter span { | |
| float: left; | |
| margin-right: 5px; | |
| } | |
| #settings { | |
| position: fixed; | |
| font-size: 0.5em !important; | |
| background-color: rgba(25, 25, 25, 0.8); | |
| padding: 0 5px; | |
| right: 15; | |
| bottom: 15; | |
| } | |
| #font-size-input { | |
| left-margin: auto; | |
| font-size: 0.6rem; | |
| right: 0; | |
| color: white; | |
| background-color: transparent; | |
| width: 2.5rem; | |
| margin-left: 1.35rem; | |
| border: #686868 1px solid; | |
| } | |
| select { | |
| font-size: 1em !important; | |
| background-color: rgba(25, 25, 25, 0.8); | |
| color: white; | |
| font-family: "Noto Sans JP"; | |
| } | |
| </style> | |
| <!-- | |
| To change background color or text color, just replace the style values above with the hex values for the colors you want. | |
| If you want the background of the counter to remain semi-transparent, you must use rgb values like above. The last number (where I've put 0.8) is the opacity level (1.0 = completely opaque). | |
| To change font size, just change the em value to what works for you (the standard size is 1, I like it at 1.5). | |
| To change font weight (boldness), just edit the value above. 100 is quite thin, 400 is default, 900 is quite thick. You may want it higher than default for Mincho fonts. | |
| The line-height value changes the spacing between lines. | |
| To use the font of your choice, remove the list of fonts above and put the ENGLISH name of your font in quotation marks (some JP font names are in Japanese). | |
| Be sure to leave a semi-colon at the end of the line. | |
| To find the English name of a given font, first install it, then open Firefox. | |
| Go to about:preferences#content in the address bar, then click on the 'Default font' drop-down menu. | |
| The "correct" name of your font will be listed here - just copy that down and paste it up above. | |
| Note that if you would like to use your browser default font, replace the font-family line with font-family:""; or delete it altogether. | |
| Your default is probably Gothic - if you want to try out a good Mincho font, try Aozora Mincho at http://www.freejapanesefont.com/aozora-mincho-download/ | |
| For various other free Japanese fonts, visit http://www.freejapanesefont.com/ | |
| For more font attribute information visit http://www.w3schools.com/css/css_font.asp | |
| --> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <!-- This is the div used for the "clear localStorage" button. --> | |
| <div | |
| id="clear_button" | |
| title="Clear localStorage" | |
| onclick="clearEverything()" | |
| > | |
| 🚫 | |
| </div> | |
| <!-- End button div. --> | |
| <!-- This is the div used for the "undo last deletion" button. --> | |
| <div id="undo_button" title="Undo last deletion" onclick="undoDeletion()"> | |
| ↶ | |
| </div> | |
| <!-- End button div. --> | |
| <!-- This is the div used for the "remove last line" button. --> | |
| <div | |
| id="remove_button" | |
| title="Remove last line" | |
| onclick="deleteLastLine()" | |
| > | |
| x | |
| </div> | |
| <!-- End button div. --> | |
| <!-- This is the div used for the counter. --> | |
| <div id="counter-container" onclick="expandCounter()"> | |
| <div id="counter" title="No. of characters / No. of lines">0 / 0</div> | |
| <div id="detailed-counter"></div> | |
| </div> | |
| <!-- End counter div. --> | |
| </div> | |
| <div id="settings"> | |
| <!-- Change font size --> | |
| <div id="font-size-container"> | |
| <label for="font-size-input">Font Size</label> | |
| <input id="font-size-input" type="number" /> | |
| </div> | |
| <!-- This is the div used for changing between what to save in localStorage --> | |
| <div | |
| id="local-storage-selection" | |
| title="Select what to save in localStorage" | |
| > | |
| <label for="local-storage">In localStorage</label> | |
| <select id="local-storage"> | |
| <option value="text">Text</option> | |
| <option value="chars">Characters</option> | |
| <option value="nothing">Nothing</option> | |
| </select> | |
| </div> | |
| <!-- End localStorage div. --> | |
| </div> | |
| <script> | |
| //The text inserter/scroller and the counter begin here. | |
| //These are needed later. | |
| const CHARS_TO_IGNORE = | |
| "「」『』[]()〈〉≪≫。、.,':!?!?…――─ー~→♪" + '"' + " " + " "; | |
| let lines = { total: 0 }; | |
| let chars = { total: 0 }; | |
| let history = []; | |
| let expanded = false; | |
| const selection = document.querySelector("#local-storage"); | |
| const fontSizeInput = document.querySelector("#font-size-input"); | |
| function removeChineseKeepJapanese(text) { | |
| const chars = [")", "」", "』", "。", "?", "!", "―", "『","「", "…", "("]; | |
| let idx = -1; | |
| if (/\$A/gi.test(text) || text.length > 500) { | |
| return ""; | |
| } | |
| chars.some((char) => { | |
| idx += 1; | |
| return text.match(char); | |
| }); | |
| const x = text.split(chars[idx]).filter(s => s.length); | |
| let res = ""; | |
| for (let i = 0; i < Math.round(x.length / 2); i++) { | |
| res += x[i]; | |
| } | |
| return res + chars[idx] + "\n"; | |
| } | |
| // Change font size when value changes | |
| fontSizeInput.addEventListener("change", (e) => { | |
| localStorage.setItem("font-size", e.target.value); | |
| document.documentElement.style.fontSize = e.target.value; | |
| }); | |
| //This function is invoked when a node(line) is inserted. | |
| let callback = function (mutations) { | |
| //Confirm that a new line (a <p> tag) was inserted. | |
| //(Rikai also inserts and removes a node (a div).) | |
| mutations.forEach((mutation) => { | |
| if ( | |
| mutation.target == document.body && | |
| mutation.type == "childList" && | |
| mutation.addedNodes.length >= 1 | |
| ) { | |
| let ptag; | |
| mutation.addedNodes.forEach((node) => { | |
| if (node.tagName == "P") { | |
| ptag = node; | |
| } | |
| }); | |
| if (!ptag) return; | |
| //Found the inserted line. | |
| //Wrap the inserted text in a div and append a "remove line" button. | |
| ptag.textContent = removeChineseKeepJapanese(ptag.textContent); | |
| let text = ptag.textContent; | |
| ptag.remove(); | |
| let div = document.createElement("div"); | |
| div.classList.add("line_box"); | |
| div.innerHTML = | |
| '<span></span><div class="remove_button" onclick="delet(this)">x</div>'; | |
| div.getElementsByTagName("span")[0].textContent = text; | |
| if (!isNaN(mutation.index)) { | |
| const lines = Array.from(document.querySelectorAll(".line_box")); | |
| div.classList.add("undoed_line"); | |
| const nextSibling = lines[mutation.index]; | |
| (nextSibling?.parentNode || document.body).insertBefore( | |
| div, | |
| nextSibling | |
| ); | |
| div.scrollIntoView(); | |
| // Add to local storage | |
| if ( | |
| mutation.source !== "localStorage" && | |
| selection.value === "text" | |
| ) { | |
| updateLocalStorage("text", (pr) => { | |
| let dates = pr.dates; | |
| let isDatePresent = false; | |
| const newItem = { | |
| date: mutation.date, | |
| index: mutation.index, | |
| }; | |
| dates = dates.reduce((pr, cur) => { | |
| const diff = new Date(mutation.date) - new Date(cur.date); | |
| if (diff === 0) { | |
| isDatePresent = true; | |
| } else if (diff < 0) { | |
| cur.index++; | |
| if (!isDatePresent) { | |
| isDatePresent = true; | |
| return [...pr, newItem, cur]; | |
| } | |
| } | |
| return [...pr, cur]; | |
| }, []); | |
| if (!isDatePresent) { | |
| dates.push(newItem); | |
| } | |
| return { | |
| text: [ | |
| ...pr.text.slice(0, mutation.index), | |
| ptag.textContent, | |
| ...pr.text.slice(mutation.index), | |
| ], | |
| dates, | |
| }; | |
| }); | |
| } | |
| } else { | |
| document.body.appendChild(div); | |
| // Add to local storage | |
| if ( | |
| mutation.source !== "localStorage" && | |
| selection.value === "text" | |
| ) { | |
| const curDate = formatDate(); | |
| updateLocalStorage("text", (pr) => ({ | |
| text: [...pr.text, ptag.textContent], | |
| dates: | |
| pr.dates[pr.dates.length - 1]?.date === curDate | |
| ? pr.dates | |
| : [ | |
| ...pr.dates, | |
| { date: curDate, index: [...pr.text].length }, | |
| ], | |
| })); | |
| } | |
| //The text-scroller is below. | |
| //I've included it in the "new line" function (we are in it now). | |
| //(That is, it won't run unless a new line was added.) | |
| //Like this it won't autoscroll down every time Rikai is used. | |
| var LEEWAY = 200; // Amount of "leeway" pixels before latching onto the bottom. | |
| // Some obscene browser shit because making sense is for dweebs | |
| var b = document.body; | |
| var offset = b.scrollHeight - b.offsetHeight; | |
| var scrollPos = b.scrollTop + offset; | |
| var scrollBottom = b.scrollHeight - (b.clientHeight + offset); | |
| // If we are at the bottom, go to the bottom again. | |
| if (scrollPos >= scrollBottom - LEEWAY) { | |
| window.scrollTo(0, document.body.scrollHeight); | |
| } | |
| } | |
| //Update the counter. | |
| line = text; | |
| line = line | |
| .replace(/(\r\n|\n|\r)/gm, "") | |
| .split(" ") | |
| .join(""); | |
| for (var i = 0; i < CHARS_TO_IGNORE.length; i++) { | |
| line = line.split(CHARS_TO_IGNORE[i]).join(""); | |
| } | |
| let lineLen = [...line].length; | |
| updateCounter(lineLen, 1, mutation.date || formatDate()); | |
| } | |
| }); | |
| }; | |
| // End of new line and scroller script. | |
| //Register the above new line callback function. | |
| let observer = new MutationObserver(callback); | |
| let observerOptions = { childList: true, attributes: false }; | |
| observer.observe(document.body, observerOptions); | |
| //Beginning of "remove line" function. | |
| function delet(xdiv) { | |
| //Get the length of the line being removed. | |
| let line = xdiv.parentNode.getElementsByTagName("span")[0].textContent; | |
| let filteredLine = line | |
| .replace(/(\r\n|\n|\r)/gm, "") | |
| .split(" ") | |
| .join(""); | |
| for (var i = 0; i < CHARS_TO_IGNORE.length; i++) { | |
| filteredLine = filteredLine.split(CHARS_TO_IGNORE[i]).join(""); | |
| } | |
| let lineLen = [...filteredLine].length; | |
| // Remove line from localStorage | |
| const lines = Array.from(document.querySelectorAll(".remove_button")); | |
| const index = lines.findIndex((a) => a === xdiv); | |
| let dateOfLine = null; | |
| if (index > -1) { | |
| if (selection.value === "text") { | |
| updateLocalStorage("text", (pr) => { | |
| let dates = pr.dates; | |
| dates = dates.reduce((pr, cur, i) => { | |
| if (cur.index <= index) { | |
| dateOfLine = cur.date; | |
| } | |
| if ( | |
| cur.index === index && | |
| (dates[i + 1]?.index - 1 === cur.index || | |
| lines.length - 1 === cur.index) | |
| ) { | |
| return pr; | |
| } | |
| if (cur.index > index) { | |
| cur.index--; | |
| } | |
| return [...pr, cur]; | |
| }, []); | |
| return { | |
| text: [...pr.text.slice(0, index), ...pr.text.slice(index + 1)], | |
| dates, | |
| }; | |
| }); | |
| } | |
| // Used in the next if | |
| const getNewHistory = (pr) => { | |
| if (pr.length >= 25) { | |
| pr.shift(); | |
| } | |
| return [ | |
| ...pr, | |
| { | |
| text: line, | |
| date: dateOfLine, | |
| index, | |
| }, | |
| ]; | |
| }; | |
| if (selection.value !== "text") { | |
| history = getNewHistory(history); | |
| } else { | |
| updateLocalStorage("history", getNewHistory); | |
| } | |
| } | |
| //Remove the line. | |
| xdiv.parentNode.remove(); | |
| //Update the counter. | |
| updateCounter(-lineLen, -1, dateOfLine); | |
| } | |
| //End of "remove line" function. | |
| //Function to update the char and line counter. | |
| function updateCounter(charDiff, lineDiff, date, shouldDisplay = true) { | |
| chars.total += charDiff; | |
| chars[date] = (chars[date] ?? 0) + charDiff; | |
| lines.total += lineDiff; | |
| lines[date] = (lines[date] ?? 0) + lineDiff; | |
| if (shouldDisplay) { | |
| displayCounter(); | |
| } | |
| if (selection.value === "chars") { | |
| updateLocalStorage("chars", (_) => ({ chars, lines })); | |
| } | |
| } | |
| function deleteLastLine() { | |
| var lines = document.getElementsByClassName("line_box"); | |
| var line_count = lines.length; | |
| if (line_count > 0) { | |
| var last_line = | |
| lines[line_count - 1].getElementsByClassName("remove_button")[0]; | |
| delet(last_line); | |
| } | |
| } | |
| function initFromLocalStorage() { | |
| // Set font size | |
| const fontSize = localStorage.getItem("font-size"); | |
| document.documentElement.style.fontSize = fontSize; | |
| fontSizeInput.value = fontSize; | |
| selection.value = localStorage.getItem("valueInLS"); | |
| switch (selection.value) { | |
| case "text": | |
| const text = JSON.parse(localStorage.getItem("text")); | |
| const reverseDates = text.dates.reverse(); | |
| let fragment = document.createDocumentFragment(); | |
| for (let i = 0; i < text.text.length; i++) { | |
| let div = document.createElement("div"); | |
| div.classList.add("line_box"); | |
| //div.style.fontFamily = "Noto Sans JP" | |
| div.innerHTML = | |
| '<span></span><div class="remove_button" onclick="delet(this)">x</div>'; | |
| div.getElementsByTagName("span")[0].textContent = text.text[i]; | |
| fragment.appendChild(div); | |
| } | |
| document.body.appendChild(fragment); | |
| text.text.forEach((a, i) => { | |
| a = a | |
| .replace(/(\r\n|\n|\r)/gm, "") | |
| .split(" ") | |
| .join(""); | |
| for (var j = 0; j < CHARS_TO_IGNORE.length; j++) { | |
| a = a.split(CHARS_TO_IGNORE[j]).join(""); | |
| } | |
| let lineLen = [...a].length; | |
| updateCounter( | |
| lineLen, | |
| 1, | |
| reverseDates.find((b) => b.index <= i)?.date || | |
| text.dates[0].date, | |
| false | |
| ); | |
| }); | |
| displayCounter(); | |
| break; | |
| case "chars": | |
| const charsInLS = JSON.parse(localStorage.getItem("chars")); | |
| chars = charsInLS.chars; | |
| lines = charsInLS.lines; | |
| displayCounter(); | |
| break; | |
| } | |
| } | |
| function displayCounter() { | |
| let charsdisp = chars.total.toLocaleString(); | |
| let linesdisp = lines.total.toLocaleString(); | |
| document.getElementById("counter").textContent = | |
| charsdisp + " / " + linesdisp; | |
| const formattedDatetoLocaleString = (date) => { | |
| return new Date(date.split("/").reverse()).toLocaleDateString(); | |
| }; | |
| document.querySelector("#detailed-counter").innerHTML = Object.keys( | |
| chars | |
| ) | |
| .filter((a) => a !== "total" && chars[a] !== 0) | |
| .map( | |
| (key) => `<div> | |
| <span> | |
| ${formattedDatetoLocaleString(key)}: | |
| </span> ${chars[key].toLocaleString()} / ${lines[ | |
| key | |
| ].toLocaleString()} | |
| </div>` | |
| ) | |
| .join(""); | |
| } | |
| function updateLocalStorage(key, fn) { | |
| localStorage.setItem( | |
| key, | |
| JSON.stringify(fn(JSON.parse(localStorage.getItem(key)))) | |
| ); | |
| } | |
| function clearEverything() { | |
| updateLocalStorage("text", (_) => ({ text: [], dates: [] })); | |
| updateLocalStorage("chars", (_) => ({ | |
| lines: { total: 0 }, | |
| dates: { total: 0 }, | |
| })); | |
| updateLocalStorage("history", (_) => []); | |
| document | |
| .querySelectorAll(".line_box") | |
| .forEach((a) => a.parentNode.removeChild(a)); | |
| lines = { total: 0 }; | |
| chars = { total: 0 }; | |
| history = []; | |
| displayCounter(); | |
| } | |
| function formatDate(date = new Date()) { | |
| // 4 hour offset | |
| const offset = 4 * 3600 * 1000; | |
| date = new Date(date - offset); | |
| return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}`; | |
| } | |
| function undoDeletion() { | |
| let line; | |
| if (selection.value !== "text") { | |
| line = history.pop(); | |
| } else { | |
| line = JSON.parse(localStorage.getItem("history")).pop(); | |
| updateLocalStorage("history", (pr) => pr.slice(0, pr.length - 1)); | |
| } | |
| if (line) { | |
| callback([ | |
| { | |
| index: line.index, | |
| date: line.date, | |
| target: document.body, | |
| type: "childList", | |
| addedNodes: [ | |
| { | |
| tagName: "P", | |
| textContent: line.text, | |
| remove: () => null, | |
| }, | |
| ], | |
| }, | |
| ]); | |
| } | |
| } | |
| function expandCounter() { | |
| if (expanded) { | |
| document.querySelector("#detailed-counter").style.display = "none"; | |
| } else { | |
| document.querySelector("#detailed-counter").style.display = "block"; | |
| } | |
| expanded = !expanded; | |
| } | |
| // Initialize localStorage | |
| if (!localStorage.getItem("text")) { | |
| updateLocalStorage("text", (_) => ({ text: [], dates: [] })); | |
| } | |
| // Backwards compatibility | |
| updateLocalStorage("text", (pr) => { | |
| if (pr?.constructor === Array) { | |
| const yesterday = new Date(new Date() - 24 * 3600 * 1000); | |
| return { | |
| text: pr, | |
| dates: | |
| pr.length > 0 ? [{ date: formatDate(yesterday), index: 0 }] : [], | |
| }; | |
| } | |
| return pr; | |
| }); | |
| if (!localStorage.getItem("history")) { | |
| localStorage.setItem("history", "[]"); | |
| } | |
| if (!localStorage.getItem("chars")) { | |
| updateLocalStorage("chars", (_) => ({ | |
| chars: { total: 0 }, | |
| lines: { total: 0 }, | |
| })); | |
| } | |
| if (!localStorage.getItem("valueInLS")) { | |
| localStorage.setItem("valueInLS", "text"); | |
| } | |
| if (!localStorage.getItem("font-size")) { | |
| localStorage.setItem("font-size", 26); | |
| } | |
| initFromLocalStorage(); | |
| document.addEventListener("keydown", (e) => { | |
| if (e.code === "KeyZ" && e.ctrlKey) { | |
| undoDeletion(); | |
| } | |
| }); | |
| selection.addEventListener("change", (e) => { | |
| // If changed from chars to something else and data has been lost | |
| const isFromChars = | |
| JSON.parse(localStorage.getItem("chars")).lines.total > | |
| document.querySelectorAll(".line_box").length; | |
| const question = | |
| "This action will reset your stats and clear the page. Are you sure you want to procceed?"; | |
| switch (selection.value) { | |
| case "nothing": | |
| if (isFromChars) { | |
| const answer = confirm(question); | |
| if (answer) { | |
| clearEverything(); | |
| } else { | |
| break; | |
| } | |
| } else { | |
| updateLocalStorage("text", (_) => ({ text: [], dates: [] })); | |
| updateLocalStorage("chars", (_) => ({ | |
| lines: { total: 0 }, | |
| chars: { total: 0 }, | |
| })); | |
| if (localStorage.getItem("history").length > 0) { | |
| history = JSON.parse(localStorage.getItem("history")); | |
| updateLocalStorage("history", (_) => []); | |
| } | |
| } | |
| break; | |
| case "chars": | |
| updateLocalStorage("text", (_) => ({ text: [], dates: [] })); | |
| updateLocalStorage("chars", (_) => ({ lines, chars })); | |
| if (localStorage.getItem("history").length > 0) { | |
| history = JSON.parse(localStorage.getItem("history")); | |
| updateLocalStorage("history", (_) => []); | |
| } | |
| break; | |
| case "text": | |
| if (isFromChars) { | |
| const answer = confirm(question); | |
| if (answer) { | |
| clearEverything(); | |
| } else { | |
| break; | |
| } | |
| } else { | |
| let prIndex = 0; | |
| updateLocalStorage("text", (_) => ({ | |
| text: Array.from( | |
| document.querySelectorAll(".line_box > span") | |
| ).map((a) => a.textContent), | |
| dates: Object.keys(lines) | |
| .filter((a) => a !== "total") | |
| .map((key) => { | |
| prIndex += lines[key]; | |
| return { date: key, index: prIndex - lines[key] }; | |
| }), | |
| })); | |
| updateLocalStorage("chars", (_) => ({ | |
| lines: { total: 0 }, | |
| chars: { total: 0 }, | |
| })); | |
| updateLocalStorage("history", (_) => history); | |
| history = []; | |
| } | |
| break; | |
| } | |
| localStorage.setItem("valueInLS", selection.value); | |
| }); | |
| </script> | |
| </body> | |
| </html> |