Skip to content

Instantly share code, notes, and snippets.

@jlongster
Last active January 5, 2022 15:31
Show Gist options
  • Save jlongster/3b0be4e3406bf0f780c1de539ffb2767 to your computer and use it in GitHub Desktop.
Save jlongster/3b0be4e3406bf0f780c1de539ffb2767 to your computer and use it in GitHub Desktop.
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}>&middot;</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>&middot;</div>}
{showNext && (
<Link to={'/?offset=' + (currentOffset + PAGE_SIZE)}>
Next page →
</Link>
)}
</div>
</div>
);
}
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