From ab0ef578a9bd673cb159c4bf1003a8e4ce3e36b8 Mon Sep 17 00:00:00 2001 From: Ruben Berenguel Montoro Date: Mon, 11 Nov 2024 12:56:14 +0100 Subject: [PATCH] Less parsers --- index.html | 1 + lib/common.js | 2 + lib/linkUtils.js | 4 +- lib/parser.js | 120 ++++++++++++++++++++++++++++++++ lib/quoteUtils.js | 77 +++++++++------------ lib/taskUtils.js | 122 ++------------------------------- style.css | 11 ++- tests/test_task_parsing.html | 1 + tests/test_task_rendering.html | 1 + 9 files changed, 176 insertions(+), 163 deletions(-) create mode 100644 lib/parser.js diff --git a/index.html b/index.html index 9863c9d..664752d 100644 --- a/index.html +++ b/index.html @@ -19,6 +19,7 @@ + diff --git a/lib/common.js b/lib/common.js index d99e21b..affd081 100644 --- a/lib/common.js +++ b/lib/common.js @@ -22,6 +22,8 @@ const numMojis = [ const linkRegex = /\[(.*?)\]\((.*?)\)/; const linkPlusRegex = /\[(.*?)\]\((.*?)\)(\s*.*)/; +const linkWithShortcut = /`(.*?)`\s*\[(.*?)\]\((.*?)\)/; + function toTop(div) { const allDivs = document.querySelectorAll("div"); diff --git a/lib/linkUtils.js b/lib/linkUtils.js index 9abdea9..9c53fc7 100644 --- a/lib/linkUtils.js +++ b/lib/linkUtils.js @@ -21,7 +21,9 @@ Use different files for different columns, and prefix secondary URL title by * window.hasKeys = ""; // Store the first key pressed -const linkWithShortcut = /`(.*?)`\s*\[(.*?)\]\((.*?)\)/; +// This parser would be a bit annoying to merge with the one in parser.js: +// This is stateful (parses the whole doc at once) but the other parses +// just blocks of a predefined shape. It's easier to keep them separate. function textToLinkObjects(text) { let links = []; diff --git a/lib/parser.js b/lib/parser.js new file mode 100644 index 0000000..63a25f4 --- /dev/null +++ b/lib/parser.js @@ -0,0 +1,120 @@ +function textToObject(text, hPos = 0, filepath) { + const lines = text.split("\n"); + let obj = {}; + obj._hPos = hPos; + obj._filepath = filepath; + obj.lines = []; // Only used (for now) for quotes + let settings = false; + let projects = false; + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + // Extract the task. If it has a link, it is the main link. + // Handle settings and projects inline, breaking. + if (line.startsWith("# ")) { + let cleaned = line.replace("# ", "").trim(); + if (cleaned == "Settings") { + settings = true; + obj._valid = true; + obj.settings = {}; + continue; + } + if (cleaned == "Projects") { + projects = true; + obj._valid = true; + obj.projects = []; + continue; + } + if (cleaned == "hr") { + obj.hr = true; + obj._valid = true; + continue; + } + if (cleaned.startsWith("[x]") || cleaned.startsWith("[X]")) { + obj.done = true; + cleaned = cleaned.slice(3); + } + if (cleaned.endsWith("(someday)") || cleaned.endsWith("(Someday)")) { + obj.someday = true; + obj.prio = -1000; + cleaned = cleaned.replace(/\s+\(.omeday\)/, ""); + } + const linkMatch = cleaned.match(linkPlusRegex); + if (linkMatch) { + obj.text = linkMatch[1] + (linkMatch[3] ?? ""); + obj.link = linkMatch[2]; + } else { + obj.text = cleaned; + } + obj._valid = true; + continue; + } + if (line == "[x]" || line == "[X]") { + obj.done = true; + continue; + } + if (line.startsWith("> ")) { + obj.lines.push(line.replace(/^> /, "")); + obj._valid = true; + continue; + } + if (line.startsWith("prio:") ) { + if(!obj.someday){ + obj.prio = parseInt(line.replace("prio:", "")); + } + continue; + } + if (line.startsWith("color:")) { + obj.color = line.replace("color:", "").trim(); + continue; + } + if (line.startsWith("extracolor:")) { + obj.extraColor = line.replace("extracolor:", "").trim(); + continue; + } + + // Get the lists of links or properties if it's a settings list + if (line.startsWith("- ")) { + const cleaned = line.replace("- ", "").trim(); + if (settings) { + const [prop, val] = cleaned.split(":"); + obj.settings[prop.trim()] = val.trim(); + continue; + } + if (projects) { + const thingy = cleaned.split(","); + if (thingy.length == 1) { + obj.projects.push(thingy[0]); + } else { + obj.projects.push(thingy.map((p) => p.trim())); + } + continue; + } + const linkMatch = cleaned.match(linkRegex); + if (linkMatch) { + if (obj.links) { + obj.links.push([linkMatch[1], linkMatch[2]]); + } else { + obj.links = [[linkMatch[1], linkMatch[2]]]; + } + continue; + } else { + // If we are in a list, haven't matched a known property, + // and match no link, we are in a log message. + if (obj.msg) { + obj.msg.push(cleaned); + } else { + obj.msg = [cleaned]; + } + } + } else { + // If the line does not start a list and we have not processed a known command… + const cleaned = line.trim(); + if (cleaned) { + // Could be empty, so… + obj.extra = cleaned; + } + } + } + return obj; + } + \ No newline at end of file diff --git a/lib/quoteUtils.js b/lib/quoteUtils.js index af007a8..0cbb742 100644 --- a/lib/quoteUtils.js +++ b/lib/quoteUtils.js @@ -1,7 +1,11 @@ -// TODO(me) Just a copypaste of pieces of taskUtils to get this working. Needs cleanup, and might not merge settings correctly +// TODO(me) This might need cleanup, it has been hacked quickly into the parser /* +- Handles settings as in task lists +- Start the text by adding a blockquote block > (can be multiple lines) +- Add a line at the end with the author or reference. + Example quotes file: # Settings @@ -11,6 +15,7 @@ Example quotes file: - borderRadius: 0.2em - width: 20em - padding: 1em +- margin-bottom: 2em --- @@ -18,43 +23,15 @@ Example quotes file: Douglas Adams +--- +> Everyone has a plan: until they get punched in the face -*/ +Mike Tyson -function textToQuoteObject(text) { - const lines = text.split("\n"); - let obj = {}; - obj._valid = true; - obj.lines = []; - let settings = false; - for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim(); - if (line == "# Settings") { - settings = true; - obj._valid = true; - obj.settings = {}; - continue; - } - if (line.startsWith("- ") && settings) { - const cleaned = line.replace("- ", "").trim(); - const [prop, val] = cleaned.split(":"); - obj.settings[prop.trim()] = val.trim(); - continue; - } - console.log(line); - if (line.startsWith("> ")) { - obj.lines.push(line.replace(/^> /, "")); - } - if (line.length === 0) { - continue; - } - obj.author = line; - } - return obj; -} +*/ function quotesFromMarkdown(paths, cb, basepath) { const promises = []; @@ -73,13 +50,13 @@ function quotesFromMarkdown(paths, cb, basepath) { const blocks = markdown.split(/---/); for (let i = 0; i < blocks.length; i++) { const block = blocks[i]; - let obj = textToQuoteObject(block); + let obj = textToObject(block); if (basepath) { - obj = textToQuoteObject(block); + obj = textToObject(block); } if (!obj._valid) { - tasks.push({ + quotes.push({ text: `Error loading quote ${i} from file ${path}`, error: true, }); @@ -101,28 +78,40 @@ function quotesFromMarkdown(paths, cb, basepath) { .catch((error) => console.error("Error loading markdown file:", error)); } +const getSettings = (objs) => { + return objs.filter(o => o.settings) +} + function addQuotesToDiv(_quotes, targetDivId) { - const quotes = shuffleArray(_quotes); + const settings = getSettings(_quotes) + const quotes = settings.concat(shuffleArray(_quotes)); // Just place them at the beginning and call it a day const d = () => document.createElement("DIV"); const targetDiv = document.getElementById(targetDivId); if (!targetDiv) { console.error("Target div not found:", targetDivId); return; } + let wrapperNode = d(); + wrapperNode.id = "quoteWrapper"; + let rendered = false; for (let i = 0; i < quotes.length; i++) { const quote = quotes[i]; if (quote.settings) { for (const key in quote.settings) { - targetDiv.style[key] = quote.settings[key]; + wrapperNode.style[key] = quote.settings[key]; } continue; } + if(rendered){ + // Just show one quote. + return; + } const div = d(); - const color = colors[i % colors.length]; + const color = colors[Math.floor((Math.random()*colors.length) % colors.length)]; div.style.color = `var(${color})`; - div.classList.add("quote-wrapper"); + div.classList.add("quote"); const q = d(); - q.classList.add("quote"); + q.classList.add("text"); const a = d(); a.classList.add("author"); for (const line of quote.lines) { @@ -130,9 +119,11 @@ function addQuotesToDiv(_quotes, targetDivId) { p.textContent = line; q.appendChild(p); } - a.textContent = quote.author; + a.textContent = quote.extra; div.appendChild(q); div.appendChild(a); - targetDiv.appendChild(div); + wrapperNode.appendChild(div); + targetDiv.appendChild(wrapperNode) + rendered = true; } } diff --git a/lib/taskUtils.js b/lib/taskUtils.js index 91c404d..5bdcd54 100644 --- a/lib/taskUtils.js +++ b/lib/taskUtils.js @@ -105,117 +105,6 @@ const linkHandler = (e) => { e.target.closest(`[data-destination]`).dataset["destination"]; }; -function textToTaskObject(text, hPos = 0, filepath) { - const lines = text.split("\n"); - let obj = {}; - obj._hPos = hPos; - obj._filepath = filepath; - let settings = false; - let projects = false; - for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim(); - // Extract the task. If it has a link, it is the main link. - // Handle settings and projects inline, breaking. - if (line.startsWith("# ")) { - let cleaned = line.replace("# ", "").trim(); - if (cleaned == "Settings") { - settings = true; - obj._valid = true; - obj.settings = {}; - continue; - } - if (cleaned == "Projects") { - projects = true; - obj._valid = true; - obj.projects = []; - continue; - } - if (cleaned == "hr") { - obj.hr = true; - obj._valid = true; - continue; - } - if (cleaned.startsWith("[x]") || cleaned.startsWith("[X]")) { - obj.done = true; - cleaned = cleaned.slice(3); - } - if (cleaned.endsWith("(someday)") || cleaned.endsWith("(Someday)")) { - obj.someday = true; - obj.prio = -1000; - cleaned = cleaned.replace(/\s+\(.omeday\)/, ""); - } - const linkMatch = cleaned.match(linkPlusRegex); - if (linkMatch) { - obj.text = linkMatch[1] + (linkMatch[3] ?? ""); - obj.link = linkMatch[2]; - } else { - obj.text = cleaned; - } - obj._valid = true; - continue; - } - if (line == "[x]" || line == "[X]") { - obj.done = true; - continue; - } - if (line.startsWith("prio:") && !obj.someday) { - obj.prio = parseInt(line.replace("prio:", "")); - continue; - } - if (line.startsWith("color:")) { - obj.color = line.replace("color:", "").trim(); - continue; - } - if (line.startsWith("extracolor:")) { - obj.extraColor = line.replace("extracolor:", "").trim(); - continue; - } - // Get the lists of links or properties if it's a settings list - if (line.startsWith("- ")) { - const cleaned = line.replace("- ", "").trim(); - if (settings) { - const [prop, val] = cleaned.split(":"); - obj.settings[prop.trim()] = val.trim(); - continue; - } - if (projects) { - const thingy = cleaned.split(","); - if (thingy.length == 1) { - obj.projects.push(thingy[0]); - } else { - obj.projects.push(thingy.map((p) => p.trim())); - } - continue; - } - const linkMatch = cleaned.match(linkRegex); - if (linkMatch) { - if (obj.links) { - obj.links.push([linkMatch[1], linkMatch[2]]); - } else { - obj.links = [[linkMatch[1], linkMatch[2]]]; - } - continue; - } else { - // If we are in a list, haven't matched a known property, - // and match no link, we are in a log message. - if (obj.msg) { - obj.msg.push(cleaned); - } else { - obj.msg = [cleaned]; - } - } - } else { - // If the line does not start a list and we have not processed a known command… - const cleaned = line.trim(); - if (cleaned) { - // Could be empty, so… - obj.extra = cleaned; - } - } - } - return obj; -} - function tasksFromMarkdown(paths, cb, basepath) { // By providing a basepath, shift + right clicking on the task opens VS Code at the // exact task heading @@ -239,9 +128,9 @@ function tasksFromMarkdown(paths, cb, basepath) { const blocks = markdown.split(/---/); for (let i = 0; i < blocks.length; i++) { const block = blocks[i]; - let obj = textToTaskObject(block, (hPos = headingLineNumbers[i])); + let obj = textToObject(block, (hPos = headingLineNumbers[i])); if (basepath) { - obj = textToTaskObject( + obj = textToObject( block, (hPos = headingLineNumbers[i]), (filepath = basepath + path), @@ -292,7 +181,6 @@ function addTasksToDiv(_tasks, targetDivId) { let someday = false; targetDiv.addEventListener("mouseover", () => toTop(targetDiv)); let projects = []; - targetDiv.innerHTML = ""; let wrapperNode = d(); wrapperNode.id = "taskWrapper"; const maxPrio = Math.max(...tasks.map((e) => e.prio ?? 0)); @@ -315,7 +203,7 @@ function addTasksToDiv(_tasks, targetDivId) { const task = sorted[i]; if (task.settings) { for (const key in task.settings) { - targetDiv.style[key] = task.settings[key]; + wrapperNode.style[key] = task.settings[key]; } continue; } @@ -521,17 +409,15 @@ function addTasksToDiv(_tasks, targetDivId) { // I could just use count const somedays = tasks.filter((t) => t.someday && !t.done); const count = somedays.length; - console.log(`Someday: ${count}`); const taskWrapper = d(); taskWrapper.classList.add("taskRow"); - taskWrapper.classList.add("someday-header"); + taskWrapper.id = "someday"; const taskText = d(); taskText.classList.add("taskText"); taskWrapper.appendChild(taskText); taskText.textContent = `Someday (${count})`; taskText.style.color = `var(--task-default)`; const firstSomeday = wrapperNode.querySelector(".taskRow.someday"); - console.log(firstSomeday); const hr = document.createElement("HR"); hr.style.borderColor = `var(--task-default)`; hr.classList.add("dim"); diff --git a/style.css b/style.css index 89a6a9f..a0dc2c6 100644 --- a/style.css +++ b/style.css @@ -244,6 +244,10 @@ hr.dim { display: none; } +#someday { + cursor: pointer; +} + .taskRow[data-title][data-prio]:hover::after { content: "prio:" attr(data-prio) "\A" attr(data-title); /* Combine content */ white-space: pre; /* Preserve newline character */ @@ -295,9 +299,14 @@ hr.dim { } .quote { + padding-bottom: 2em; +} + +.quote.text { + } -.author { +.quote .author { font-size: 90%; filter: grayscale(0.7) opacity(0.8); float: right; diff --git a/tests/test_task_parsing.html b/tests/test_task_parsing.html index e1fe10e..dba4da7 100644 --- a/tests/test_task_parsing.html +++ b/tests/test_task_parsing.html @@ -21,6 +21,7 @@

Task pseudomarkdown parsing

+ diff --git a/tests/test_task_rendering.html b/tests/test_task_rendering.html index e7eeb3a..5829d59 100644 --- a/tests/test_task_rendering.html +++ b/tests/test_task_rendering.html @@ -21,6 +21,7 @@

Task rendering (WIP)

+