Last active
January 5, 2022 15:31
-
-
Save jlongster/3b0be4e3406bf0f780c1de539ffb2767 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useLoaderData, Link, useSearchParams } from 'remix'; | |
import { parseISO, format } from 'date-fns'; | |
import groupBy from 'lodash/groupBy'; | |
let PAGE_SIZE = 5; | |
function safeParseInt(str) { | |
let parsed = parseInt(str); | |
return isNaN(parsed) ? 0 : parsed; | |
} | |
export async function loader({ request }) { | |
let url = new URL(request.url); | |
let offset = safeParseInt(url.searchParams.get('offset') || 0); | |
let res = await fetch( | |
`https://discord-life.jlongster.com/roam/daily?offset=${offset}&limit=${PAGE_SIZE}` | |
); | |
let data = await res.json(); | |
return data; | |
} | |
export default function Index(props) { | |
let { data: posts, count } = useLoaderData(); | |
let [params] = useSearchParams(); | |
let currentOffset = safeParseInt(params.get('offset')); | |
let showPrev = currentOffset > 0; | |
let showNext = currentOffset + PAGE_SIZE < count; | |
return ( | |
<div className="list"> | |
{posts.map((post, idx) => { | |
return ( | |
<div key={idx} className="item"> | |
<h1>{format(parseISO(post.pageTitle), 'MMMM do, yyyy')}</h1> | |
{post.sections.map((section, idx) => { | |
return ( | |
<div key={idx}> | |
<h2>{section.title}</h2> | |
<div className="content"> | |
{section.blocks.map((block, idx) => ( | |
<p key={idx} id={block.id}> | |
<a className="__link" href={'#' + block.id}>·</a> | |
{block.content} | |
</p> | |
))} | |
</div> | |
</div> | |
); | |
})} | |
</div> | |
); | |
})} | |
<div className="page-links"> | |
{showPrev && ( | |
<Link to={'/?offset=' + Math.max(currentOffset - PAGE_SIZE, 0)}> | |
← Previous page | |
</Link> | |
)} | |
{showPrev && showNext && <div>·</div>} | |
{showNext && ( | |
<Link to={'/?offset=' + (currentOffset + PAGE_SIZE)}> | |
Next page → | |
</Link> | |
)} | |
</div> | |
</div> | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const getRoamClient = require("../clients/roam"); | |
let roamAuth = { | |
graph: process.env.ROAM_API_GRAPH, | |
email: process.env.ROAM_API_EMAIL, | |
password: process.env.ROAM_API_PASSWORD | |
} | |
function cleanupDailyId(id) { | |
let m = id.match(/(\d{2})-(\d{2})-(\d{4})/); | |
if(m) { | |
return `${m[3]}-${m[1]}-${m[2]}`; | |
} | |
return null; | |
} | |
function getTitle(item) { | |
if(/\d{4}-\d{2}-\d{2}/.test(item.pageId)) { | |
return item.pageId; | |
} | |
return item.pageName; | |
} | |
function reversed(num) { | |
return num === -1 ? 1 : num === 1 ? -1 : 0 | |
} | |
function sort(data) { | |
data.sort((entry1, entry2) => { | |
let s1 = reversed(getTitle(entry1).localeCompare(getTitle(entry2))) | |
if(s1 === 0) { | |
let s2 = entry1.sectionOrder - entry2.sectionOrder; | |
if(s2 === 0) { | |
return entry1.order - entry2.order; | |
} | |
return s2; | |
} | |
return s1; | |
}) | |
return data; | |
} | |
function group(data) { | |
let result = []; | |
let cache = new Map(); | |
for(let x of data) { | |
let pageTitle = getTitle(x); | |
if(!cache.has(pageTitle)) { | |
let blank = {pageTitle, sections: []} | |
cache.set(pageTitle, blank) | |
result.push(blank) | |
} | |
let page = cache.get(pageTitle); | |
let section = page.sections.find(s => s.title === x.title); | |
if(section == null) { | |
section = {title: x.title, blocks: []} | |
page.sections.push(section) | |
} | |
section.blocks.push({ content: x.content, id: x.contentid }) | |
} | |
return result; | |
} | |
function isDaily(item) { | |
return /\d{4}-\d{2}-\d{2}/.test(item.pageId) | |
} | |
function safeParseInt(str) { | |
let parsed = parseInt(str); | |
return isNaN(parsed) ? 0 : parsed; | |
} | |
module.exports = async function run(app) { | |
let roam = await getRoamClient(roamAuth); | |
app.get('/roam/daily', async (req, res) => { | |
let offset = safeParseInt(req.query.offset || 0) | |
let limit = Math.min(safeParseInt(req.query.limit || 10), 100) | |
res.header("Content-Type", "text/javascript"); | |
let query = ` | |
[:find ?title ?content ?contentid ?order ?porder ?pagename ?pageid | |
:where | |
[?n :node/title "public"] | |
[?b :block/refs ?n] | |
[?b :block/refs ?refs] | |
[?refs :node/title ?title] | |
(not [?refs :node/title "public"]) | |
[?b :block/children ?c] | |
[?b :block/order ?porder] | |
[?c :block/string ?content] | |
[?c :block/uid ?contentid] | |
[?c :block/order ?order] | |
[?c :block/page ?page] | |
[?page :node/title ?pagename] | |
[?page :block/uid ?pageid] | |
] | |
`; | |
let raw = await roam.runQuery(query); | |
let data = raw.map(item => ({ | |
title: item[0], | |
content: item[1], | |
contentid: item[2], | |
order: item[3], | |
sectionOrder: item[4], | |
pageName: item[5], | |
pageId: cleanupDailyId(item[6]) || item[6] | |
})).filter(item => isDaily(item)); | |
let filtered = group(sort(data)); | |
res.send(JSON.stringify({ data: filtered.slice(offset, offset + limit), count: filtered.length })) | |
}) | |
app.get('/roam/sitecss', async (req, res) => { | |
let filterQuery = ` | |
[:find [?filters ...] | |
:where | |
[?p :node/title "site/css"] | |
[?p :block/uid ?page] | |
[?w :window/id ?id] | |
[(clojure.string/ends-with? ?id ?page)] | |
[?w :window/filters ?filters ]] | |
`; | |
let filters = await roam.runQuery(filterQuery) | |
let includes = new Set(filters[0].includes) | |
let removes = new Set(filters[0].removes) | |
let query = ` | |
[:find ?bid ?refids ?content | |
:where | |
[?p :node/title "site/css"] | |
[?b :block/page ?p] | |
[?b :block/string ?content] | |
[?b :block/uid ?bid] | |
[(clojure.string/includes? ?content "\`\`\`css")] | |
[?b :block/parents ?parent] | |
[?parent :block/refs ?refs] | |
[?refs :block/uid ?refids]] | |
`; | |
let cssBlocks1 = await roam.runQuery(query); | |
let cssBlocks2 = cssBlocks1.reduce((acc, [id, pid, content]) => { | |
acc[id] = acc[id] || { parents: [], content: null }; | |
acc[id].parents.push(pid); | |
acc[id].content = content; | |
return acc; | |
}, {}) | |
let cssBlocks3 = Object.fromEntries(Object.entries(cssBlocks2).filter(entry => { | |
let parents = entry[1].parents; | |
let enabled = true; | |
if(includes.size > 0) { | |
enabled = !!parents.find(p => includes.has(p)); | |
} | |
enabled = enabled && !parents.find(p => removes.has(p)) | |
return enabled; | |
})) | |
let css = Object.entries(cssBlocks3).map(entry => { | |
let m = entry[1].content.match(/```css((.|[\n\r])*)```/m); | |
if(m) { | |
return m[1].trim(); | |
} | |
return ''; | |
}); | |
res.header('Content-Type', 'text/css'); | |
res.header('Cache-Control', 'max-age=300'); | |
res.send(css.join('\n\n')); | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment