diff --git a/markup.css b/markup.css index cf3585a..8f87660 100644 --- a/markup.css +++ b/markup.css @@ -212,6 +212,14 @@ L + ratio { } display: none; } +/**********/ +/** Time **/ +/**********/ +.Markup time { + text-decoration: dotted underline; + cursor: help; +} + /***********/ /** Quote **/ /***********/ diff --git a/parse.js b/parse.js index 195d9e2..eefc877 100644 --- a/parse.js +++ b/parse.js @@ -65,7 +65,7 @@ class Markup_12y2 { constructor() { // About __proto__ in object literals: // https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-runtime-semantics-propertydefinitionevaluation - const IS_BLOCK = {__proto__:null, code:'block', divider:'block', ROOT:'block', heading:'block', quote:'block', table:'block', table_cell:'block', image:'block', video:'block', audio:'block', spoiler:'block', align:'block', list:'block', list_item:'block', youtube:'block', anchor:'block', table_divider:'block', ruby:'text', key:'text'} + const IS_BLOCK = {__proto__:null, code:'block', divider:'block', ROOT:'block', heading:'block', quote:'block', table:'block', table_cell:'block', image:'block', video:'block', audio:'block', spoiler:'block', align:'block', list:'block', list_item:'block', youtube:'block', anchor:'block', table_divider:'block', ruby:'text', key:'text', time:'text'} // 'text' is for inline-block elements @@ -477,6 +477,19 @@ class Markup_12y2 { constructor() { let [txt="true"] = rargs OPEN('ruby', {text: txt}) word_maybe() + } break; case '\\time': { + let [timestamp, style] = rargs + if (!/^[tTdDfFR]$/.test(style)) + style = 'f' + if (/^\d+(.\d+)?$/.test(timestamp)) { + timestamp = Number.parseFloat(timestamp) * 1000 + } else { + timestamp = Date.parse(timestamp) + } + if (Number.isNaN(timestamp)) + timestamp = 0 + timestamp = new Date(timestamp) + BLOCK('time', {timestamp, style}) } break; case '\\key': { OPEN('key') word_maybe() diff --git a/render.js b/render.js index f0eac37..06405cf 100644 --- a/render.js +++ b/render.js @@ -348,6 +348,94 @@ we should create our own fake bullet elements instead.*/ key: 𐀶``, + time: function({timestamp, style}) { + let e = this() + /* +https://discord.com/developers/docs/reference#message-formatting-timestamp-styles +https://gist.github.com/LeviSnoot/d9147767abeef2f770e9ddcd91eb85aa +stealing syntax / formatting ideas from this + +two types of timestamps are accepted: +- unix timestamps (entirely numeric) +- `Date.parse()` date+time strings + +## Big overview +|default|`1719878490`|\time[1719878490]| +|time|\time[1719878490;t]|\time[1719878490;T]| +|date|\time[1719878490;d]|\time[1719878490;D]| +|full|\time[1719878490;f]|\time[1719878490;F]| +|rel.|\{}|\time[1719878490;R]| + +## Short vs. Long forms +\time[2024-06-12 01:00 EST;f] / \time[2024-06-12 01:00 EST;F] +\time[2024-06-12 01:00 EST;t] / \time[2024-06-12 01:00 EST;T] +\time[2024-06-12 01:00 EST;d] / \time[2024-06-12 01:00 EST;D] + +## Relative-to-render times +They have hover text! +\time[2024-06-12 01:00 EST;R] +Asdf \time[2024-06-12 19:46 EDT;R] asdf? +out \time[2024-06-12 18:46 EDT;R]? + +## `Date.parse()` acts differently depending on whitespace +\time[2001-06-12;R] +\time[2001-06-12 ;R] +implies timezone was acknowledged? why..? + +## Strange behavior with trailing `{` and whitespace in general +\time[2024-06-12 01:00 EST;f] a b c +\time[2024-06-12 01:00 EST;f]{ a b c +\time[2024-06-12 01:00 EST;f]{} a b c +\time[1719878400.01;f]{ +\time[1719878400.99;f]{ fractions work!! (if you want that specificity) + +## Expected End of Birthday +\time[1719878400;R] +\time[1718496000;R] + */ + if (timestamp instanceof Date) { + const locale = Intl.DateTimeFormat().resolvedOptions().locale + e.title = timestamp.toLocaleString() + switch (style) { default: { // TODO: this is embarrassing + e.textContent = timestamp.toUTCString() + } break; case 't': { + e.textContent = timestamp.toLocaleTimeString(locale, { 'timeStyle': 'short' }) + } break; case 'T': { + e.textContent = timestamp.toLocaleTimeString(locale, { 'timeStyle': 'medium' }) + } break; case 'd': { + e.textContent = timestamp.toLocaleDateString(locale, { 'dateStyle': 'short' }) + } break; case 'D': { + e.textContent = timestamp.toLocaleDateString(locale, { 'dateStyle': 'medium' }) + } break; case 'f': { + e.textContent = timestamp.toLocaleString() // ? + } break; case 'F': { + e.textContent = timestamp.toLocaleString() + } break; case 'R': { + const timez = [[1000, 'ms'], [60, 'seconds'], [60, 'minutes'], [24, 'hours'], [Infinity, 'days']] + let render = new Date() // makes static rendering bad... + let after_render = timestamp - render + e.title += `\nRendered at ${render.toLocaleTimeString()}` + // also i'm recreating code that already exists in the frontend.. + for (let [level_up, metric] of timez) { // bad naming on purpose + if (Math.abs(after_render) > level_up) { + after_render /= level_up + } else { + after_render = Math.round(after_render * 10) / 10 + if (after_render > 0) + e.textContent = `in ${after_render} ${metric}` + else + e.textContent = `${-after_render} ${metric} ago` + break + } + } + // oh hey i could just set a signal handler here to update this!! + }} + } else { + e.textContent = timestamp + } + return e + }.bind(𐀶`