Prototype for a self-hosted, FOSS publishing (and possibly editing) platform which aims to unite the different Zettelkasten system on the markent.
Currently used to display my thesis in philosophy of physics.
Prototype for an open-source, easily self-hostable, Zettelkasten-style publishing (and possibly editing) platform. Currently used to display my thesis in philosophy of physics
Home Page: https://thesis.tefkah.com
License: GNU General Public License v3.0
Make the dataBy... files inherit from the same function
import { Stats } from 'fs'
import { readFile, writeFile } from 'fs/promises'
import { join } from 'path'
import readdirp from 'readdirp'
import { DATA_DIR, NOTE_DIR } from '../paths'
import { slugify } from '../slug'
// TODO: Make the dataBy... files inherit from the same function
export const mdxDataByName = async () => {
const datapath = join(DATA_DIR, 'dataByName.json')
try {
const data = JSON.parse(await readFile(datapath, 'utf8'))
return data
} catch (e) {
const data = (await readdirp.promise(NOTE_DIR, { alwaysStat: true }))
// Only include md(x) files
.filter((entry) => /\.mdx?$/.test(entry.path))
.reduce(
(acc, curr) => {
const name = curr.basename.replace(/\.mdx?$/, '').toLowerCase()
acc[name] = {
stats: curr.stats!,
fullPath: curr.fullPath,
path: curr.path,
name,
slug: slugify(name),
folders: curr.basename.split('/') ?? [],
basename: curr.basename,
}
return acc
},
{} as {
[name: string]: {
basename: string
stats: Stats
fullPath: string
name: string
folders: string[]
path: string
slug: string
}
},
)
await writeFile(datapath, JSON.stringify(data))
return data
}
}
77d42ee8f93d0ed08d853f78455059e6b325e717
do not inline all the stacked note styles
{stacked && (
<Heading
size="md"
// TODO: do not inline all the stacked note styles
sx={{
textDecoration: 'none',
fontSize: '17px',
d1f5dff629969348e703dba42d8bc4f42ddd2ecb
Make the dataBy... files inherit from the same function
// @ts-check
import { readFile, writeFile } from 'fs/promises'
import { join } from 'path'
import readdirp from 'readdirp'
import { slugify } from './slug.mjs'
export const getFreshDataBySlug = async (noteDir) => {
const rawDir = await readdirp.promise(noteDir, { alwaysStat: true })
// Only include md(x) files
return rawDir
.filter((entry) => /\.mdx?$/.test(entry.path))
.reduce((acc, curr) => {
const name = curr.basename.replace(/\.mdx?$/, '')
const slug = slugify(name)
const { atime, mtime, ctime, birthtime, ...stats } = { ...curr.stats }
acc[slug] = {
stats,
fullPath: curr.fullPath,
path: curr.path,
name,
slug,
folders:
curr.path
.replace(curr.basename, '')
.split('/')
.filter((entry) => entry) ?? [],
basename: curr.basename,
}
return acc
}, {})
}
// TODO: Make the dataBy... files inherit from the same function
export const mdxDataBySlug = async (dataDir, noteDir) => {
if (process.env.ALWAYS_FRESH !== 'true' && process.env.NODE_ENV !== 'production') {
const data = await getFreshDataBySlug(noteDir)
return data
}
const datapath = join(dataDir, 'dataBySlug.json')
try {
const data = JSON.parse(await readFile(datapath, 'utf8'))
return data
} catch (e) {
console.log('No data found, writing new')
const data = await getFreshDataBySlug(noteDir)
await writeFile(datapath, JSON.stringify(data))
return data
}
}
44216a0511fb8e45758ba1776aca690b32e814ea
Import the text for the first page from an MDX file
import { BasicLayout } from '../components/Layouts'
import { Main } from '../components/Main'
// TODO: Import the text for the first page from an MDX file
export const Index = () => (
<>
<Head>
28d890cb4a3862782c0090a29f4ccee674392d85
Atm the design of the website is kind of all over the place: mostly a clone of GH and then some random serif elements thrown in, combined with no coherent color pallette.
Create a reusable library for the MDX compiler for this as well, stop reinventing friggin markdown rendering all the time
import rehypeKatex from 'rehype-katex'
import 'katex/dist/katex.css'
// TODO: Create a reusable library for the MDX compiler for this as well, stop reinventing friggin markdown rendering all the time
export const markdownToReact = (markdown: string) => {
const htmlWithLinks = markdown.replace(/(https?:\/\/([^<]*))/g, '<a href="$1">$1</a>')
const processor = unified()
89e7f275d4006e0dafdacfb1cc5fb84c68827d22
Make the dataBy... files inherit from the same function
import { readFile, writeFile } from 'fs/promises'
import { join } from 'path'
import readdirp from 'readdirp'
import { DATA_DIR, NOTE_DIR } from '../paths'
import { slugify } from '../slug'
import { DataBy } from '../../types'
export const getFreshDataBySlug = async () =>
(await readdirp.promise(NOTE_DIR, { alwaysStat: true }))
// Only include md(x) files
.filter((entry) => /\.mdx?$/.test(entry.path))
.reduce((acc, curr) => {
const name = curr.basename.replace(/\.mdx?$/, '')
const slug = slugify(name)
const { atime, mtime, ctime, birthtime, ...stats } = { ...curr.stats }
acc[slug] = {
stats,
fullPath: curr.fullPath,
path: curr.path,
name,
slug,
folders:
curr.path
.replace(curr.basename, '')
.split('/')
.filter((entry) => entry) ?? [],
basename: curr.basename,
}
return acc
}, {} as DataBy)
// TODO: Make the dataBy... files inherit from the same function
export const mdxDataBySlug = async (): Promise<DataBy> => {
if (process.env.NODE_ENV !== 'production') {
const data = await getFreshDataBySlug()
console.log(data)
return data
}
const datapath = join(DATA_DIR, 'dataBySlug.json')
try {
const data = JSON.parse(await readFile(datapath, 'utf8'))
return data
} catch (e) {
const data = await getFreshDataBySlug()
await writeFile(datapath, JSON.stringify(data))
return data
}
}
9a5b3d8811770f1afb5f8affcfc3d21225dddef3
Create a version of useDiscussion which can have data loaded through getStatic/getServerSide props
export const DiscussionsPage = (props: Props) => {
const { access, token, discussionCategories } = props
console.log(token)
// TODO: Create a version of useDiscussion which can have data loaded through getStatic/getServerSide props
const { data, isLoading } = useDiscussion({
first: 10,
repo: 'thomasfkjorna/thesis-discussions',
term: '',
7d22ea43b77cfe762d3396e5883259bf70f77eee
Add support for As mentioned in @Shech2019
like in rehypeCitation
import {} from 'remark-slate-transformer'
import { InlineCiteNode } from '@benrbray/mdast-util-cite'
/**
* TODO: Add support for `As mentioned in @Shech2019` like in rehypeCitation
*/
export const cite = (node: InlineCiteNode, next: (children: any[]) => any) => {
const { type, position, value, ...rest } = node
return {
...rest,
type: 'cite',
text: value,
}
}
1c12d649f3115b6171c326f9d0be20a5d6d3fd4f
set alt
const { type, url, title, alt } = node
return {
type,
url,
title,
alt,
children: [{ text: '' }],
}
}
const { type, children, referenceType, identifier, label } = node
return {
type,
children: convertNodes(children, deco),
referenceType,
identifier,
label,
}
}
const { type, alt, referenceType, identifier, label } = node
return {
type,
alt,
referenceType,
identifier,
label,
children: [{ text: '' }],
}
}
import * as slate from './slate'
import * as uniorg from 'uniorg'
export type Decoration = {
[key in (
| uniorg.Italic
| uniorg.Bold
| uniorg.Code
| uniorg.Verbatim
| uniorg.StrikeThrough
| uniorg.Underline
| uniorg.Superscript
| uniorg.Subscript
)['type']]?: true
}
const defaultOptions: OrgToSlateOptions = {
imageFilenameExtensions: [
'png',
'jpeg',
'jpg',
'gif',
'tiff',
'tif',
'xbm',
'xpm',
'pbm',
'pgm',
'ppm',
'pnm',
'svg',
],
useSections: false,
footnotesSection: (footnotes) => [
{ type: 'headline', level: 1, children: [{ text: 'Footnotes' }] },
...footnotes,
],
}
export interface OrgToSlateOptions {
imageFilenameExtensions: string[]
/**
* Whether to wrap org sections into <section>.
*/
useSections: boolean
/**
* A function to wrap footnotes. First argument of the function is
* an array of all footnote definitions and the function should
* return a new Hast node to be appended to the document.
*
* Roughly corresponds to `org-html-footnotes-section`.
*
* Default is:
* ```
* <h1>Footnotes:</h1>
* {...footnotes}
* ```
*/
footnotesSection: (footnotes: Node[]) => Array<Node | SlateNode>
}
// `org-html-html5-elements`
const html5Elements = new Set([
'article',
'aside',
'audio',
'canvas',
'details',
'figcaption',
'figure',
'footer',
'header',
'menu',
'meter',
'nav',
'output',
'progress',
'section',
'summary',
'video',
])
export function uniorgToSlate(
node: uniorg.OrgData,
opts: Partial<OrgToSlateOptions> = {},
): slate.Node[] {
return createSlateRoot(node)
}
function createSlateRoot(root: uniorg.OrgData): slate.Node[] {
return convertNodes(root.children, {})
}
function convertNodes(nodes: uniorg.OrgNode[], deco: Decoration): slate.Node[] {
if (nodes.length === 0) {
return [{ text: '' }]
}
return nodes.reduce<slate.Node[]>((acc, node) => {
acc.push(...createSlateNode(node, deco))
return acc
}, [])
}
function createSlateNode(node: uniorg.OrgNode, deco: Decoration): SlateNode[] {
switch (node.type) {
// case 'org-data':
// return [{ type: 'root', children: convertNodes(node.children, deco) }]
case 'paragraph':
return [createParagraph(node, deco)]
case 'section':
return [createSection(node, deco)]
case 'headline':
return [createHeadline(node, deco)]
case 'quote-block':
return [createBlockquote(node, deco)]
case 'comment-block':
return [createCommentBlock(node, deco)]
case 'example-block':
return [createExampleBlock(node, deco)]
case 'special-block':
return [createSpecialBlock(node, deco)]
case 'export-block':
return [createExportBlock(node, deco)]
case 'src-block':
return [createSrcBlock(node, deco)]
case 'plain-list':
return [createList(node, deco)]
case 'list-item':
return [createListItem(node, deco)]
case 'table':
return [createTable(node, deco)]
case 'table-row':
return [createTableRow(node, deco)]
case 'table-cell':
return [createTableCell(node, deco)]
case 'code':
return [createCode(node)]
case 'footnote-definition':
return [createFootnoteDefinition(node, deco)]
case 'verbatim':
case 'text':
return [createText(node.value, deco)]
case 'italic':
case 'bold':
case 'underline':
case 'superscript':
case 'subscript':
case 'strike-through': {
const { type, children } = node
return children.reduce<SlateNode[]>((acc, n) => {
acc.push(...createSlateNode(n, { ...deco, [type]: true }))
return acc
}, [])
}
case 'code': {
const { type, value } = node
return [createText(value, { ...deco, [type]: true })]
}
case 'link':
return [createLink(node, deco)]
case 'footnote-reference':
return [createFootnoteReference(node)]
case 'latex-environment':
return [createMath(node)]
case 'latex-fragment':
return [createInlineMath(node)]
default:
const _: never = node as never
break
}
return []
}
export type Paragraph = ReturnType<typeof createParagraph>
function createParagraph(node: uniorg.Paragraph, deco: Decoration) {
const { type, children } = node
return {
type,
children: convertNodes(children as uniorg.OrgNode[], deco),
}
}
export type Section = ReturnType<typeof createSection>
function createSection(node: uniorg.Section, deco: Decoration) {
const { type, children } = node
return {
type,
children: convertNodes(children, deco),
}
}
export type Heading = ReturnType<typeof createHeadline>
function createHeadline(node: uniorg.Headline, deco: Decoration) {
const { type, children, level } = node
if (level === 15) console.log(children)
return {
type,
level,
children: children.length === 0 ? [{ text: '', deco }] : convertNodes(children, deco),
}
}
export type Blockquote = ReturnType<typeof createBlockquote>
function createBlockquote(node: uniorg.QuoteBlock, deco: Decoration) {
return {
type: node.type,
children: convertNodes(node.children, deco),
}
}
export type List = ReturnType<typeof createList>
function createList(node: uniorg.List, deco: Decoration) {
const { type, children, listType } = node
if (listType === 'unordered') {
return { type: 'unorderedList', children: convertNodes(children, deco) }
} else if (listType === 'ordered') {
return { type: 'orderedList', children: convertNodes(children, deco) }
} else {
return { type: 'definitionList', children: convertNodes(children, deco) }
}
}
export type ListItem = ReturnType<typeof createListItem>
function createListItem(node: uniorg.ListItem, deco: Decoration) {
const { type, children } = node
return {
type,
children: convertNodes(children, deco),
}
}
export type Table = ReturnType<typeof createTable>
function createTable(node: uniorg.Table, deco: Decoration) {
// table.el tables are not supported for export
if (node.tableType === 'table.el') {
return { type: 'pre', children: [{ text: node.value }] }
}
const { type, tableType, children, tblfm } = node
// TODO: support column groups
// see https://orgmode.org/manual/Column-Groups.html
const table = { type: 'table', children: <slate.Node[]>[] }
let hasHead = false
let group: uniorg.TableRow[] = []
children.forEach((r) => {
if (r.rowType === 'rule') {
// rule finishes the group
if (!hasHead) {
table.children.push({
type: 'tableHead',
children: group.map((row: uniorg.TableRow) => ({
type: 'tableRow',
children: row.children.map((cell) => ({
type: 'th',
children: convertNodes([cell], deco),
})),
})),
})
hasHead = true
} else {
table.children.push({ type: 'tableBody', children: convertNodes(group, deco) })
}
group = []
}
group.push(r)
})
if (group.length) {
table.children.push({ type: 'tableBody', children: convertNodes(group, deco) })
}
return table
}
export type TableRow = ReturnType<typeof createTableRow>
function createTableRow(node: uniorg.TableRow, deco: Decoration) {
const { type, children } = node
return {
type: 'tableRow',
children: convertNodes(children, deco),
}
}
export type TableCell = ReturnType<typeof createTableCell>
function createTableCell(node: uniorg.TableCell, deco: Decoration) {
const { type, children } = node
return {
type: 'tableCell',
children: convertNodes(children, deco),
}
}
export type Code = ReturnType<typeof createCode>
function createCode(node: uniorg.Code) {
const { type, value } = node
return {
type,
children: [{ text: value }],
}
}
export type SrcBlock = ReturnType<typeof createSrcBlock>
function createSrcBlock(node: uniorg.SrcBlock, deco: Decoration) {
const { type, value, language } = node
return {
type,
language,
children: [{ text: value }],
}
}
export type CommentBlock = ReturnType<typeof createSrcBlock>
function createCommentBlock(node: uniorg.CommentBlock, deco: Decoration) {
const { type, value } = node
return {
type,
children: [{ text: value }],
}
}
export type ExampleBlock = ReturnType<typeof createSrcBlock>
function createExampleBlock(node: uniorg.ExampleBlock, deco: Decoration) {
const { type, value } = node
return {
type,
children: [{ text: value }],
}
}
export type VerseBlock = ReturnType<typeof createVerseBlock>
function createVerseBlock(node: uniorg.VerseBlock, deco: Decoration) {
const { type, children } = node
return {
type,
children: convertNodes(children, deco),
}
}
export type ExportBlock = ReturnType<typeof createExportBlock>
function createExportBlock(node: uniorg.ExportBlock, deco: Decoration) {
const { type, value, backend } = node
return {
type,
backend,
children: [{ text: value }],
}
}
export type SpecialBlock = ReturnType<typeof createSpecialBlock>
function createSpecialBlock(node: uniorg.SpecialBlock, deco: Decoration) {
const { type, blockType, children } = node
return {
type,
blockType,
children: convertNodes(children, deco),
}
}
export type Math = ReturnType<typeof createMath>
function createMath(node: uniorg.LatexEnvironment) {
const { type, value } = node
return {
type,
children: [{ text: value }],
}
}
export type InlineMath = ReturnType<typeof createInlineMath>
function createInlineMath(node: uniorg.LatexFragment) {
const { type, value } = node
return {
type,
children: [{ text: value }],
}
}
export type FootnoteDefinition = ReturnType<typeof createFootnoteDefinition>
function createFootnoteDefinition(node: uniorg.FootnoteDefinition, deco: Decoration) {
const { type, children, label } = node
return {
type,
children: convertNodes(children, deco),
label,
}
}
export type Text = ReturnType<typeof createText>
function createText(text: string, deco: Decoration) {
return {
...deco,
text,
}
}
export type Link = ReturnType<typeof createLink>
function createLink(node: uniorg.Link, deco: Decoration) {
const { type, children, path, linkType, rawLink: link } = node
const imageRe = new RegExp(`\.(${defaultOptions.imageFilenameExtensions.join('|')})$`)
if (link.match(imageRe)) {
// TODO: set alt
return { type: 'image', src: link, children: [{ text: '' }] }
}
return {
type: 'link',
href: link,
children: children.length ? convertNodes(children, deco) : [{ text: link }],
}
}
//export type Image = ReturnType<typeof createImage>
// function createImage(node: uniorg.Image) {
// const { type, url, title, alt } = node
// return {
// type,
// url,
// title,
// alt,
// children: [{ text: '' }],
// }
// }
//export type LinkReference = ReturnType<typeof createLinkReference>
// function createLinkReference(node: uniorg.LinkReference, deco: Decoration) {
// const { type, children, referenceType, identifier, label } = node
// return {
// type,
// children: convertNodes(children, deco),
// referenceType,
// identifier,
// label,
// }
// }
// export type ImageReference = ReturnType<typeof createImageReference>
// function createImageReference(node: uniorg.ImageReference) {
// const { type, alt, referenceType, identifier, label } = node
// return {
// type,
// alt,
// referenceType,
// identifier,
// label,
// children: [{ text: '' }],
// }
// }
export type FootnoteReference = ReturnType<typeof createFootnoteReference>
function createFootnoteReference(node: uniorg.FootnoteReference) {
const { type, label } = node
return {
type,
label,
children: [{ text: '' }],
}
}
export type SlateNode =
| Paragraph
| Heading
| Blockquote
| List
| ListItem
| Table
| TableRow
| TableCell
| Code
| FootnoteDefinition
| Text
| Link
| FootnoteReference
| Math
| InlineMath
| SrcBlock
| ExampleBlock
| VerseBlock
| ExampleBlock
| CommentBlock
| SpecialBlock
7e8c8efa8af83e747b452a4c98963fa292b0e24e
I need to migrate my db to us east, will speed things up a lot.
Probably also use redis or smth rather than have supabase host a full scale postgres db, wayyyyy overkill
Port diffs to MDX
import { Skeleton } from '@chakra-ui/react'
// TODO: Port diffs to MDX
import { ParsedDiff } from '../../services/thesis/parseDiff'
import { DiffBox } from '../Diff'
export const ParsedCommit = (props: { [key: string]: any }) => {
const { commitData, isLoading } = props
return commitData?.map((commit: any) => {
if (!commit) return null
const { file, diff, additions, deletions } = commit || {
file: '',
diff: '',
additions: 0,
deletions: 0,
}
return isLoading ? (
<Skeleton />
) : (
<DiffBox
key={file.filepath}
{...{ isLoaded: !isLoading, oid: '', filepath: file, deletions, additions }}
>
[<ParsedDiff {...{ diff, truncated: true }} />]
</DiffBox>
)
})
}
c70d6305fbf3f7af46f088634ca1aef52a13bd7a
types aaaaa
),
div: Box,
a: (node) => {
// @ts-expect-error TODO: types aaaaa
const { href, className, alias, children, ...rest } = node
if (href?.includes('http')) {
return (
c1a6738170bfdb601f43d04677b1a118b6dbe45b
This is how things look when you haven't visited a discussion
But when I visit a discussion it doesn't remove the notification for "new shit". It just always seems like there are new comments.
I should probably store this data in the db as well, e.g. has visited etc. That way I can also send updates when something changes and users don't have to visit the website JUST to see that something changes.
This could get very messy very quickly though, and will require you to have a server at some point.
Could maybe get away with it if I can get Vercel to run cron jobs, otherwise maybe move to render
Kind of innefficient as I'm already fetching stuff through getServerSideProps anyway.
It's annoying to see that spinner all the time
https://thesis.tefkah.com/discussions is the main offender
support column groups
see https://orgmode.org/manual/Column-Groups.html
import * as slate from './slate'
import * as uniorg from 'uniorg'
export type Decoration = {
[key in (
| uniorg.Italic
| uniorg.Bold
| uniorg.Code
| uniorg.Verbatim
| uniorg.StrikeThrough
| uniorg.Underline
| uniorg.Superscript
| uniorg.Subscript
)['type']]?: true
}
const defaultOptions: OrgToSlateOptions = {
imageFilenameExtensions: [
'png',
'jpeg',
'jpg',
'gif',
'tiff',
'tif',
'xbm',
'xpm',
'pbm',
'pgm',
'ppm',
'pnm',
'svg',
],
useSections: false,
footnotesSection: (footnotes) => [
{ type: 'headline', level: 1, children: [{ text: 'Footnotes' }] },
...footnotes,
],
}
export interface OrgToSlateOptions {
imageFilenameExtensions: string[]
/**
* Whether to wrap org sections into <section>.
*/
useSections: boolean
/**
* A function to wrap footnotes. First argument of the function is
* an array of all footnote definitions and the function should
* return a new Hast node to be appended to the document.
*
* Roughly corresponds to `org-html-footnotes-section`.
*
* Default is:
* ```
* <h1>Footnotes:</h1>
* {...footnotes}
* ```
*/
footnotesSection: (footnotes: Node[]) => Array<Node | SlateNode>
}
// `org-html-html5-elements`
const html5Elements = new Set([
'article',
'aside',
'audio',
'canvas',
'details',
'figcaption',
'figure',
'footer',
'header',
'menu',
'meter',
'nav',
'output',
'progress',
'section',
'summary',
'video',
])
export function uniorgToSlate(
node: uniorg.OrgData,
opts: Partial<OrgToSlateOptions> = {},
): slate.Node[] {
return createSlateRoot(node)
}
function createSlateRoot(root: uniorg.OrgData): slate.Node[] {
return convertNodes(root.children, {})
}
function convertNodes(nodes: uniorg.OrgNode[], deco: Decoration): slate.Node[] {
if (nodes.length === 0) {
return [{ text: '' }]
}
return nodes.reduce<slate.Node[]>((acc, node) => {
acc.push(...createSlateNode(node, deco))
return acc
}, [])
}
function createSlateNode(node: uniorg.OrgNode, deco: Decoration): SlateNode[] {
switch (node.type) {
// case 'org-data':
// return [{ type: 'root', children: convertNodes(node.children, deco) }]
case 'paragraph':
return [createParagraph(node, deco)]
case 'section':
return [createSection(node, deco)]
case 'headline':
return [createHeadline(node, deco)]
case 'quote-block':
return [createBlockquote(node, deco)]
case 'comment-block':
return [createCommentBlock(node, deco)]
case 'example-block':
return [createExampleBlock(node, deco)]
case 'special-block':
return [createSpecialBlock(node, deco)]
case 'export-block':
return [createExportBlock(node, deco)]
case 'src-block':
return [createSrcBlock(node, deco)]
case 'plain-list':
return [createList(node, deco)]
case 'list-item':
return [createListItem(node, deco)]
case 'table':
return [createTable(node, deco)]
case 'table-row':
return [createTableRow(node, deco)]
case 'table-cell':
return [createTableCell(node, deco)]
case 'code':
return [createCode(node)]
case 'footnote-definition':
return [createFootnoteDefinition(node, deco)]
case 'verbatim':
case 'text':
return [createText(node.value, deco)]
case 'italic':
case 'bold':
case 'underline':
case 'superscript':
case 'subscript':
case 'strike-through': {
const { type, children } = node
return children.reduce<SlateNode[]>((acc, n) => {
acc.push(...createSlateNode(n, { ...deco, [type]: true }))
return acc
}, [])
}
case 'code': {
const { type, value } = node
return [createText(value, { ...deco, [type]: true })]
}
case 'link':
return [createLink(node, deco)]
case 'footnote-reference':
return [createFootnoteReference(node)]
case 'latex-environment':
return [createMath(node)]
case 'latex-fragment':
return [createInlineMath(node)]
default:
const _: never = node as never
break
}
return []
}
export type Paragraph = ReturnType<typeof createParagraph>
function createParagraph(node: uniorg.Paragraph, deco: Decoration) {
const { type, children } = node
return {
type,
children: convertNodes(children as uniorg.OrgNode[], deco),
}
}
export type Section = ReturnType<typeof createSection>
function createSection(node: uniorg.Section, deco: Decoration) {
const { type, children } = node
return {
type,
children: convertNodes(children, deco),
}
}
export type Heading = ReturnType<typeof createHeadline>
function createHeadline(node: uniorg.Headline, deco: Decoration) {
const { type, children, level } = node
if (level === 15) console.log(children)
return {
type,
level,
children: children.length === 0 ? [{ text: '', deco }] : convertNodes(children, deco),
}
}
export type Blockquote = ReturnType<typeof createBlockquote>
function createBlockquote(node: uniorg.QuoteBlock, deco: Decoration) {
return {
type: node.type,
children: convertNodes(node.children, deco),
}
}
export type List = ReturnType<typeof createList>
function createList(node: uniorg.List, deco: Decoration) {
const { type, children, listType } = node
if (listType === 'unordered') {
return { type: 'unorderedList', children: convertNodes(children, deco) }
} else if (listType === 'ordered') {
return { type: 'orderedList', children: convertNodes(children, deco) }
} else {
return { type: 'definitionList', children: convertNodes(children, deco) }
}
}
export type ListItem = ReturnType<typeof createListItem>
function createListItem(node: uniorg.ListItem, deco: Decoration) {
const { type, children } = node
return {
type,
children: convertNodes(children, deco),
}
}
export type Table = ReturnType<typeof createTable>
function createTable(node: uniorg.Table, deco: Decoration) {
// table.el tables are not supported for export
if (node.tableType === 'table.el') {
return { type: 'pre', children: [{ text: node.value }] }
}
const { type, tableType, children, tblfm } = node
// TODO: support column groups
// see https://orgmode.org/manual/Column-Groups.html
const table = { type: 'table', children: <slate.Node[]>[] }
let hasHead = false
let group: uniorg.TableRow[] = []
children.forEach((r) => {
if (r.rowType === 'rule') {
// rule finishes the group
if (!hasHead) {
table.children.push({
type: 'tableHead',
children: group.map((row: uniorg.TableRow) => ({
type: 'tableRow',
children: row.children.map((cell) => ({
type: 'th',
children: convertNodes([cell], deco),
})),
})),
})
hasHead = true
} else {
table.children.push({ type: 'tableBody', children: convertNodes(group, deco) })
}
group = []
}
group.push(r)
})
if (group.length) {
table.children.push({ type: 'tableBody', children: convertNodes(group, deco) })
}
return table
}
export type TableRow = ReturnType<typeof createTableRow>
function createTableRow(node: uniorg.TableRow, deco: Decoration) {
const { type, children } = node
return {
type: 'tableRow',
children: convertNodes(children, deco),
}
}
export type TableCell = ReturnType<typeof createTableCell>
function createTableCell(node: uniorg.TableCell, deco: Decoration) {
const { type, children } = node
return {
type: 'tableCell',
children: convertNodes(children, deco),
}
}
export type Code = ReturnType<typeof createCode>
function createCode(node: uniorg.Code) {
const { type, value } = node
return {
type,
children: [{ text: value }],
}
}
export type SrcBlock = ReturnType<typeof createSrcBlock>
function createSrcBlock(node: uniorg.SrcBlock, deco: Decoration) {
const { type, value, language } = node
return {
type,
language,
children: [{ text: value }],
}
}
export type CommentBlock = ReturnType<typeof createSrcBlock>
function createCommentBlock(node: uniorg.CommentBlock, deco: Decoration) {
const { type, value } = node
return {
type,
children: [{ text: value }],
}
}
export type ExampleBlock = ReturnType<typeof createSrcBlock>
function createExampleBlock(node: uniorg.ExampleBlock, deco: Decoration) {
const { type, value } = node
return {
type,
children: [{ text: value }],
}
}
export type VerseBlock = ReturnType<typeof createVerseBlock>
function createVerseBlock(node: uniorg.VerseBlock, deco: Decoration) {
const { type, children } = node
return {
type,
children: convertNodes(children, deco),
}
}
export type ExportBlock = ReturnType<typeof createExportBlock>
function createExportBlock(node: uniorg.ExportBlock, deco: Decoration) {
const { type, value, backend } = node
return {
type,
backend,
children: [{ text: value }],
}
}
export type SpecialBlock = ReturnType<typeof createSpecialBlock>
function createSpecialBlock(node: uniorg.SpecialBlock, deco: Decoration) {
const { type, blockType, children } = node
return {
type,
blockType,
children: convertNodes(children, deco),
}
}
export type Math = ReturnType<typeof createMath>
function createMath(node: uniorg.LatexEnvironment) {
const { type, value } = node
return {
type,
children: [{ text: value }],
}
}
export type InlineMath = ReturnType<typeof createInlineMath>
function createInlineMath(node: uniorg.LatexFragment) {
const { type, value } = node
return {
type,
children: [{ text: value }],
}
}
export type FootnoteDefinition = ReturnType<typeof createFootnoteDefinition>
function createFootnoteDefinition(node: uniorg.FootnoteDefinition, deco: Decoration) {
const { type, children, label } = node
return {
type,
children: convertNodes(children, deco),
label,
}
}
export type Text = ReturnType<typeof createText>
function createText(text: string, deco: Decoration) {
return {
...deco,
text,
}
}
export type Link = ReturnType<typeof createLink>
function createLink(node: uniorg.Link, deco: Decoration) {
const { type, children, path, linkType, rawLink: link } = node
const imageRe = new RegExp(`\.(${defaultOptions.imageFilenameExtensions.join('|')})$`)
if (link.match(imageRe)) {
// TODO: set alt
return { type: 'image', src: link, children: [{ text: '' }] }
}
return {
type: 'link',
href: link,
children: children.length ? convertNodes(children, deco) : [{ text: link }],
}
}
//export type Image = ReturnType<typeof createImage>
// function createImage(node: uniorg.Image) {
// const { type, url, title, alt } = node
// return {
// type,
// url,
// title,
// alt,
// children: [{ text: '' }],
// }
// }
//export type LinkReference = ReturnType<typeof createLinkReference>
// function createLinkReference(node: uniorg.LinkReference, deco: Decoration) {
// const { type, children, referenceType, identifier, label } = node
// return {
// type,
// children: convertNodes(children, deco),
// referenceType,
// identifier,
// label,
// }
// }
// export type ImageReference = ReturnType<typeof createImageReference>
// function createImageReference(node: uniorg.ImageReference) {
// const { type, alt, referenceType, identifier, label } = node
// return {
// type,
// alt,
// referenceType,
// identifier,
// label,
// children: [{ text: '' }],
// }
// }
export type FootnoteReference = ReturnType<typeof createFootnoteReference>
function createFootnoteReference(node: uniorg.FootnoteReference) {
const { type, label } = node
return {
type,
label,
children: [{ text: '' }],
}
}
export type SlateNode =
| Paragraph
| Heading
| Blockquote
| List
| ListItem
| Table
| TableRow
| TableCell
| Code
| FootnoteDefinition
| Text
| Link
| FootnoteReference
| Math
| InlineMath
| SrcBlock
| ExampleBlock
| VerseBlock
| ExampleBlock
| CommentBlock
| SpecialBlock
3912631230620db84e393ec1f4858aed960bd8a4
Move citations component to separate lib, no longer needed for this project but a waste to just throw away
csl: CSLCitation[]
}
/**
* TODO: Move citations component to separate lib, no longer needed for this project but a waste to just throw away
*/
export const Citations = (props: CitationProps) => {
const { csl } = props
f96927f86d1845d2117ac27686a3134b88010142
make this an env var
/* eslint-disable consistent-return */
import { TREE, walk } from 'isomorphic-git'
import fs from 'fs'
import * as Diff from 'diff'
import { extname } from 'path'
const bufferToString = async (tree) => {
const content = (await tree?.content()) || []
return content.length ? Buffer.from(content).toString('utf8') : ''
}
const diffMap = async (props) => {
const { filepath, trees, type, justStats } = props
const [tree1, tree2] = trees
if (type === 'equal') {
return
}
// ignore dirs
if (filepath === '.') {
return
}
// just show .org diffs
// TODO make this an env var
if (!['.org', '.md', '.mdx'].includes(extname(filepath))) {
return
}
if ((await tree1?.type()) === 'tree' || (await tree2?.type()) === 'tree') {
return
}
// ignore unmodified files
if ((await tree1?.oid()) === (await tree2?.oid())) return
if (!tree1 || !tree2) {
// TODO count the words
const added = tree2 ? true : undefined
const impTree = tree2 || tree1
const string = await bufferToString(impTree)
const count = string.split(' ').length
return {
filepath,
oid: (await tree1?.oid()) || (await tree2?.oid()) || '',
diff: justStats
? []
: [
{
count,
value: string,
added,
removed: !added,
},
],
additions: added ? count : 0,
deletions: !added ? count : 0,
}
}
// We don't want to do all the expensive diffing
// if the files are just added or removed.
const treeStrings = []
// eslint-disable-next-line no-restricted-syntax
for (const tree of trees) {
// eslint-disable-next-line no-await-in-loop
const string = await bufferToString(tree)
treeStrings.push(string)
}
const [t1String, t2String] = treeStrings
// console.log(t1String)
const diff = Diff.diffWordsWithSpace(t1String, t2String)
// get total additions for filecommit
const { additions, deletions } = diff.reduce(
(acc, curr) => {
if (curr.added) {
acc.additions += curr.value.split(' ').length
}
if (curr.removed) {
acc.deletions += curr.value.split(' ').length
}
return acc
},
{ additions: 0, deletions: 0 },
)
// and get rid of undefined props as Next doesnit like it
// TODO maybe optimize this to one loop, but prolly not a big deal
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const cleanDiff = diff.map((curr) => {
if (curr.removed === undefined) {
return { ...curr, removed: false }
}
if (curr.added === undefined) {
return { ...curr, added: false }
}
return curr
})
return {
filepath,
oid: (await tree1?.oid()) || (await tree2?.oid()) || '',
diff: justStats ? [] : diff,
additions,
deletions,
}
}
export const getCommitDiff = async (
commitHash1,
commitHash2,
dir = 'notes',
gitdir = `${dir}/git`,
justStats,
) =>
walk({
fs,
dir,
gitdir,
trees: [TREE({ ref: commitHash1 }), TREE({ ref: commitHash2 })],
map: (filename, trees) => diffMap({ filepath: filename, trees, justStats }),
})
export const getCommitDiffForSingleFile = async (
commitHash1,
commitHash2,
dir = 'notes',
gitdir = `${dir}/git`,
file,
justStats,
) =>
walk({
fs,
dir,
gitdir,
trees: [TREE({ ref: commitHash1 }), TREE({ ref: commitHash2 })],
map: (filename, trees) => {
if (file && filename !== file)
return new Promise((resolve) => {
resolve(undefined)
})
return diffMap({ filepath: filename, trees, justStats })
},
})
// export async function getModifiedCommitDiff(
// commitHash1: string,
// commitHash2: string,
// dir: string = 'notes',
// gitdir: string = `${dir}/git`,
// ): Promise<FileDiff> {
// return await doSomethingAtFileStateChange(commitHash1, commitHash2, dir, gitdir, diffMap)
// }
3e60feb4a1a765a97acf8a576a3eba02140f4185
Hovering over the sidebar should warm the cache used when fetching loading the page. This should lead to almost instant transitions, which is very nice.
Be sure to use the same cache as for #10
Write up a proper analysis of Streven's classification of idealizations.
milestones:
---
title: II. Idealizations
tags:
- chapter
- idealizations
- thesis
id: 1fbefa5c-d5a1-4061-a043-e7c2ffd7c596
mtime: 20210701194946
ctime: 20210701194946
---
# II. Idealizations
{/* turned out larger than expected. */}
# On Idealizations
## Idealization is the salient feature of models
As mentioned previously, instead of focusing on the general process of abstraction by examining the relation between physical models and "reality", we will look at a specific feature of the practice of modeling which, as I will argue, best exemplifies the problems with drawing boundaries: idealization.
## What is idealization
For the purposes at hand, we don't need to identify all the necessary and sufficient conditions for counting something as an idealization. Instead, we can simply focus on a single crucial feature: they _falsely_ represent one or more aspects of the real world in order to ease calculation or reasoning ==(are these all the things people do with models? reasoning is pretty broad so should be safe)==. This mirrors the major issue with boundary drawing/discretization, namely that some boundary is drawn in order to facilitate reasoning about some object or concept, when identifying or justifying that boundary is difficult. The shared difficulty, then, consists in justifying either the use or reality of such simplifications.
However, while one of key features of idealizations is that they intentionally distort some aspect of the target system,
{/* common descriptions of idealization specifically include the fact that they introduce a _false_ description, */}
we should not assume that the boundary drawing problem necessarily introduces false descriptions, as doing so would beg the question. Instead we will take the by-now familiar stance of examining _how, if at all_ the use of idealizations is justified
{/*in physics?*/}
, and hopefully extrapolate from there.
### Characterizations of idealization?
Characterizations of idealizations are abound in the literature, although few dare claim to provide an exhaustive one. @Norton2012 's characterization of idealization as opposed to approximation is well received:
- Approximation: proposition which sort of describes the target
- Idealization: model whose properties sort of describe the target
Norton's standard example is a ball falling through the atmosphere. We have an excellent formula for calculating the velocity $v(t)$ starting at $v=t=0$ as$v(t)=\frac{g}{k}(1-\exp(-kt))=gt-\frac{gkt^2}{2}+\frac{gk^2t^3}{6}-\ldots$
where $g$ is the acceleration due to gravity, and $k$ a friction coefficient.
As any physics student knows, it is often convenient to leave out the influence of air resistance. Setting $k=0$ yields $v(t)=gt$.
Given Norton's distinction, we can view the above formula in two ways: either as an approximation or as an idealization. If we view it as an approximation, i.e. a proposition describing the actual system (a ball falling through the Earth's atmosphere) inexactly, we can say that this is a good approximation for the initial part of the ball's descent, becoming a worse and worse approximation as the ball continues to fall. We could also view it as an idealization, in which case we view the above equation as representing a (false) model of a ball falling in a vacuum. It is not the description of this model that is false, rather the model (idealization) misrepresents our world in some way, namely by leaving out air.
In this example, the difference between an approximation and an idealization is simply a matter of perspective: we can freely shift between viewing the formula as an approximation or idealization. This "shifting" Norton calls promotion and demotion respectively. Promotion consists in taking the approximation and taking it as the basis for describing a new, idealized model. In this case, removing the effect of air resistance is promoted to an idealized situation of an Earth without any atmosphere. Demotion on the other hand consists in extracting one or more inexact propositions from an existing idealization and applying them to the actual system, e.g. seeing that in No-Air-Earth the speed of the ball is $v(t)=gt$ and applying it to the ball falling in our actual Earth. ==I mention this now because it will come in handy later==.
[@Strevens2007] puts forth another classification of idealizations, which characterizes idealizations more similarly to how I did above, as deliberate falsifications of reality. Specifically, Strevens conceptualizes idealizations in terms of the operation driving them: setting a parameter to zero, infinity, or some other number.
{/** TODO: Write up a proper analysis of Streven's classification of idealizations.
*
* labels: write, big
* milestones:
*/}
{/* ### Why do we not consider the target system an idealization? It's also a model */}
{/* ### Is idealization different than abstraction?*/}
## Narrowing
As is tradition by now, I wish to focus on a subsection of idealizations first in order to get a better grasp on them. Idealization as construed above does not differ that much from a model, indeed Norton even seems to treat them as synonyms. Analyzing models on their own is difficult, and since idealizations are difficult to distinguish from models entirely, analyzing idealizations on their own will in all likelihood also turn out to be difficult. Therefore let us turn to a more problematic example of idealization, one which has more easily identifiable examples: infinite idealizations.
Infinite idealizations (also called "asymptotic idealizations" by for instance [@Strevens2019] or "asymptotic explanation" by for instance \[cite//b:@Batterman2005]) are, put crudely, an idealization which is arrived at through some sort of limiting operation, through which a parameter of the original model is made to approach infinity[^infinitesimal]. Commonly discussed examples in the literature are the thermodynamic limit (TDL), the fractional quantum Hall effect (FQHE), the Aharanov-Bohm effect, infinite population models โฆ.
As promised, infinite idealizations run into more obvious problems more quickly: in the thermodynamic limit the number of particles goes to infinity, the FQHE posits 2D particles, the AB effect requires an infinitely long solenoid and in order to explain gene distributions biologists assume infinite populations. _Prima facie_, the problem appears straightforward: such things do not exist. โCan you show me an infinitely long solenoid?โ, the detractor exclaims. However, why does this appear so problematic? After all, was intentional misrepresentation not the characterizing feature of idealizations? Why is setting air resistance to $0$ less problematic than setting the $z$-axis to $0$?
โฆ
## Characterizations of infinite idealizations
{/**
* TODO: Good introduction of the literature review on infinite idealizations
* Specifically, make it clear WHY you are looking at these sources, what you hope to gleam from them, and why doing such a survey is useful to begin with.
* labels: write
*/}
{/**
* TODO: Expand the current literature review on infinite idealizations with at least one more source
* labels: write
*/}
### Norton
First in @Norton2012 and more in depth in @Norton2014, Norton describes his unease with so-called โinfinite idealizationsโ. Although never providing a strict definition, we can make an educated guess to one:
>[!definition] **Infinite Idealization (Norton)**
>
> An infinite idealization is made by performing a limiting operation on an idealized system, taking some parameter (such as length, number, volume) to either zero or infinity. The infinite idealization is a new system, namely the old system with the parameter _set_ to zero or infinity. However, these systems sometimes misbehave either the limit not being logically possible or conflicting in some other way with other assumptions we hold, ==which is bad==.
This characterization somewhat goes against the spirit of what Norton intends to argue, namely that such infinite idealizations are not idealizations at all, but can only be sensibly understood as _approximations_ as defined above. For "the essential starting point of the notion of idealization is that we have a consistently describable system, even if it is fictitious." [@Norton2014, pp. 200] We should however not assume such strong requirements for idealizations, as whether "being consistently describable" is a good feature for a model to have _is_ what is under discussion. For now we will refer to these systems as infinite idealizations.
Norton furthermore distinguishes between _well-behaved_ and _ill-behaved idealizations_.[^well-behaved] _Ill-behaved_ idealizations are infinite idealizations whose limit system (the system with the parameter set to zero or infinity) does not match with target system in some way. This mismatch can take two forms [@Norton2012, (3.2, 3.3)]: the limit system might not exist, e.g., an infinite sphere, or the limit system might have a property which conflicts with a property of the target system. For the former, if we define a sphere as all points which are equidistant from some other point, then an infinite sphere does not exist, as there are no points at infinity. ($\mathbb{R}=(-\infty, \infty)$ not $[-\infty,\infty]$) For the latter, Norton imagines modeling an arbitrarily long ellipsoid as an infinite cylinder. While they look similar, the ratio of surface to volume for an ellipsoid is different from that for a cylinder, so the idealization has a fundamental mismatch.
{/**
* TODO: Create figure which shows the differences between ellipsoid and cylinder volumes
* labels: visualization
*/}
In short: for Norton infinite idealization simply is the end result of the process of a limiting operation. Furthermore, these idealizations can sometimes be well-behaved, and sometimes ill-behaved.
### Strevens
@Strevens2019a defines infinite idealization (or โasymptotic idealizationsโ as he calls them, we will stick with infinite here) slightly differently than Norton. Luckily, Strevens does provide a clear definition, which is in contrast to what he calls a โsimpleโ idealization, which "is achieved by the straightforward operation of setting some parameter or parameters in the model to non-actual values, often zero". A clear example is the air-resistance coefficient above. At first, he contrasts this straightforwardly with infinite idealizations in the Nortonian sense, as โin \[infinite\] idealization, by contrast, a fiction is introduced by taking some sort of limitโ. We will take this definition to be identical to Norton's.
However, later on in the paper Strevens adds another layer to the definition,
{/*TODO: find a good quote for Strevens adding another layer to the definition for idealizations*/}
namely that scientists use infinite idealizations when it is not possible to use a simple idealization to directly set the relevant property to zero (or infinity)
{/*TODO: Clarify the distinction between infinite and "normal" idealizations for Strevens*/}
. Furthermore, he adds, โ\[Infinite\] idealization is an interesting proposition, then, only in those cases where a simple substitution cannot be performed, which is to say only in those cases where a veridical model for mathematical reasons falls apart or otherwise behaves badly at the limiting value.โ While Strevens later argues why these interesting cases (Norton's mismatches) _do_ make sense, we do not have to concern us with evaluating their correctness just yet, we simply need to note that Strevens makes the same distinction as Norton here.
Then, we can define
>[!definition] **Infinite Idealization (Strevens):**
>
> An infinite idealization is made by performing a limiting operation on a system, taking some "extrapolation" parameter (such as length, number, volume) to either zero or infinity **in order to set some other parameter to zero or infinity.** The infinite idealization is the system with the extrapolation parameter and the relevant paramenter set to either zero or infinity (dont' need to be the same). However, sometimes these systems misbehave, **which is interesting**.
_(bold to highlight differences with Norton)_
{/*Batterman also has some definition but it is rather vague.*/}
### Why this is confusing
I am not satisfied with definitions as they stand. They contain too many distinct criteria, such as whether the idealizations misbehave, how the limit is taken, and what kind of subset infinite idealizations form of idealizations in general. All putative infinite idealizations are discussed using the same umbrella term, even when the previously mentioned criteria differ. I believe that the discussion of infinite idealization would be much clearer if we were able to distinguish between the factors that contribute to the idealization braking down.
## A Categorization of Idealization
Focusing on the discussion of infinite idealizations solely, I believe 4 (maybe 5) distinctions can be drawn. I will first present the distinctions, and then show how I believe they are ordered. The goal is to create categories for (infinite) idealizations to facilitate reasoning and argument, since, as we have seen above, Norton and Strevens are not per se arguing over the same definition, even if it is close.
### Simple vs. Infinite Idealizations
Strevens -- and Norton less explicitly -- the discussion is presented as being about this distinction, but actually concerns a subclass of infinite idealizations we shall discuss below. However, the simple vs. infinite distinction is a useful one, but I will draw it differently than Strevens.
1) _Simple Idealization_.
A simple idealization is an idealization in which no limit is taken in order to set the relevant parameter.
2) _Infinite Idealization_.
An infinite idealization is one in which a limit is taken in order to set a parameter.
Note that no reference has been made to whether or not it affects another parameter, or whether the limit operation is successful. I argue that this is first and foremost the distinction between these idealizations, and that other qualities should be discussed separately. (I am not sure whether idealizations can be split up neatly into two disjoint sets like these (I'm not sure if that can be done at all, see [@WEBER2010]), but i'll just treat it like it does)
This is a distinction based on _method_: _how_ is the idealization achieved? The idealized system might end up the same in some cases, but the operation is the relevant piece.
### Direct vs. Indirect Idealizations
This distinction is also due to Strevens, although he does not discuss it separately and makes it co-refering(?) with the first distinction. Direct idealizations, as the name implies, directly alter the relevant parameter e.g. setting air resistance to zero in order to have zero air resistance. Indirect idealizations on the other hand, alter a parameter in order to alter the actually relevant parameter, e.g. infinite population in order to set genetic drift to zero, or infinite particles in order to achieve a singularity.
This is a distinction based on _goal_: _what_ should the idealization achieve? Indirect idealizations are sometimes necessary in order to get rid of a pesky parameter. Note that while this is an intention based distinction, in some models the same parameter might be set directly or indirectly. While in Newtonian Mechanics we might set the air resistance to zero directly, in a more complete QFT description of the same situation we have no access to such a parameter.
Also note that this distinction is not the same as the one between infinite and simple idealizations: as Strevens notes, it is completely in the realm of possibility to directly set a parameter to zero using an infinite limiting operation, "but you would merely be showing off."
Additionally, only infinite idealizations can be indirect, but not all are.
### Unproblematic/boring vs. problematic/interesting
(not sure what to call this yet, should be catchy)
This distinction is both due to Strevens and Norton, and is what I believe the main distinction we ought to discuss. This distinction is only relevant for **indirect infinite** idealizations, as both Norton and Strevens agree that all direct and simple idealizations provide little puzzlement. Indirect infinite idealizations can be boring i.e. there is no mismatch with the idealized system and the target system (I have no example), or interesting, by creating such a mismatch. All the idealizations under discussion fall under this category.
This is a distinction based on _result_: _what_ is the idealization like? (Not too sure about this characterization, not very catchy)
### Absent vs. Contradictory
(also unsure about these names) (other idea self-contradictory and "external"-contradictory? extracontradictory?)
This distinction is due to Norton, as Strevens does not explicitly distinguish between the two. This distinction only concerns **interesting idealizations**. For absent idealizations, the idealized model system simply cannot exist in its own terms: an infinite sphere does not denote anything. Contradictory idealizations, on the other hand, postulate some property of the model system which conflicts with another property we hold to be incontrovertible, or at least uncontroversial. Most of the idealizations under discussion fall under this category: infinite populations are not self contradictory, but they prevent probabilistic reasoning using uniform distributions.
This is a distinction based on ???
### Putative distinctions
There are two more distinctions of which I am not sure I can genuinely draw them.
#### Quantitative vs qualitative mismatch/contradiction.
- Quantitative Here I mean Norton's ellipsoid elongating to a cylinder: the mismatch comes from the ellipsoid having a certain volume/surface ratio in all finite stages, but a different one when infinitely long. No property is set to zero or infinity, unless you count "cylinderness".
- Qualitative Some property becomes true or false in the infinite limit which is not false or true in the finite case. Most of the actual examples are here: having or not having phase transitions, being 2D or 3D, exhibiting or not exhibiting an effect.
The reason I doubt this distinction is that I feel like it's a question of framing. A very important question, which I should investigate, but not a distinction of kind per se.
#### Logical/transcendental contradiction vs a physical/intuitive contradiction.
Strevens uses the example of the infinite population idealization in evolutionary biology to exemplify the former: the main problem is that for an infinite population it is no longer possible to have countable additivity with a uniform distribution, and so you cannot use the Strong Law of Large Numbers and could not say anything about the probability of genetic drift (might be badly paraphrasing): the method itself is no longer useful, but it's not a direct self-contradiction as the infinite sphere, as an infinite population is a sensible concept. The latter is a bit more vague, but here I mean e.g. the thermodynamic limit: it does not work because it stipulates an infinite number of particles. However, this is in conflict with the whole idea that the world consists of molecules. BUT not directly so, as [@Shech2013] points out, it is only a real paradox if we stipulate that statistical mechanics in the thermodynamic limit is a true/accurate representation of the world, which we need to justify by e.g. (or i.e.? I don't know of any others) an indispensability argument.
This is a distinction based on????
I doubt this because the former category might refer to the same as โabsentโ idealizations.
## Order of the distinctions
I think the order is best explained by this beautiful diagram, the entire box being โidealization-spaceโ. Not included is how this is linked to approximations, nor the distinctions I am unsure about, this is simply to show how the above distinctions work:
![](../media/idealization_distinctions.png)
## Discussion
While these distinctions might appear nitpicky, I think they are vital for making sure we are discussing the correct problem. Additionally, in making these distinctions it became clear that perhaps more distinctions need to be made, such as the specific nature of the contradiction induced, see Section X.
What is clear is how infinite idealizations (the interesting ones at least) differ from idealizations in general or simple ones. Infinite idealizations are a subcategory of idealizations, together with simple idealizations (whether they completely fill the category of idealization is left open). However, the distinction between infinite idealizations and simple idealizations does not prove particularly enlightening. Other distinctions will be more fruitful to investigate. I do not think this warrants a change in nomenclature per se, as there is substantial literature on infinite idealizations already. Clarification would be in order though.
# Footnotes
[^well-behaved]: Again, Norton actually does not consider ill-behaved idealizations to be idealizations at all, but for now we shall simply pretend he does in order to compare his stance.
[^infinitesimal]: (or zero, in case of infinitesimal idealizations. While there might be some differences between the two, for now I will assume they behave the same.)
f32e6ba31001288a906c53e1a588a76342c1a808
Figure out a more modular way to check whether a heading is active
dontFetch?: boolean
}
/**
* TODO: Figure out a more modular way to check whether a heading is active
*/
const isActive = (slug: string, children: string) => {
const isActivity = ['commit', 'compare', 'activity'].includes(
slug.replace(/\/(.+?)(\/|\b).*/g, '$1'),
dadd13d06f347c4d6790502d13addc558065a627
When hovering over an internal link, we should show a card.
It does do that, only without the content. However, on refresh it does show it. This probably has to do with awaiting something incorrectly/misuing swr.
count the words
if the files are just added or removed.
/* eslint-disable consistent-return */
import { TREE, walk } from 'isomorphic-git'
import fs from 'fs'
import * as Diff from 'diff'
import { extname } from 'path'
const bufferToString = async (tree) => {
const content = (await tree?.content()) || []
return content.length ? Buffer.from(content).toString('utf8') : ''
}
const diffMap = async (props) => {
const { filepath, trees, type, justStats } = props
const [tree1, tree2] = trees
if (type === 'equal') {
return
}
// ignore dirs
if (filepath === '.') {
return
}
// just show .org diffs
// TODO make this an env var
if (!['.org', '.md', '.mdx'].includes(extname(filepath))) {
return
}
if ((await tree1?.type()) === 'tree' || (await tree2?.type()) === 'tree') {
return
}
// ignore unmodified files
if ((await tree1?.oid()) === (await tree2?.oid())) return
if (!tree1 || !tree2) {
// TODO count the words
const added = tree2 ? true : undefined
const impTree = tree2 || tree1
const string = await bufferToString(impTree)
const count = string.split(' ').length
return {
filepath,
oid: (await tree1?.oid()) || (await tree2?.oid()) || '',
diff: justStats
? []
: [
{
count,
value: string,
added,
removed: !added,
},
],
additions: added ? count : 0,
deletions: !added ? count : 0,
}
}
// We don't want to do all the expensive diffing
// if the files are just added or removed.
const treeStrings = []
// eslint-disable-next-line no-restricted-syntax
for (const tree of trees) {
// eslint-disable-next-line no-await-in-loop
const string = await bufferToString(tree)
treeStrings.push(string)
}
const [t1String, t2String] = treeStrings
// console.log(t1String)
const diff = Diff.diffWordsWithSpace(t1String, t2String)
// get total additions for filecommit
const { additions, deletions } = diff.reduce(
(acc, curr) => {
if (curr.added) {
acc.additions += curr.value.split(' ').length
}
if (curr.removed) {
acc.deletions += curr.value.split(' ').length
}
return acc
},
{ additions: 0, deletions: 0 },
)
// and get rid of undefined props as Next doesnit like it
// TODO maybe optimize this to one loop, but prolly not a big deal
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const cleanDiff = diff.map((curr) => {
if (curr.removed === undefined) {
return { ...curr, removed: false }
}
if (curr.added === undefined) {
return { ...curr, added: false }
}
return curr
})
return {
filepath,
oid: (await tree1?.oid()) || (await tree2?.oid()) || '',
diff: justStats ? [] : diff,
additions,
deletions,
}
}
export const getCommitDiff = async (
commitHash1,
commitHash2,
dir = 'notes',
gitdir = `${dir}/git`,
justStats,
) =>
walk({
fs,
dir,
gitdir,
trees: [TREE({ ref: commitHash1 }), TREE({ ref: commitHash2 })],
map: (filename, trees) => diffMap({ filepath: filename, trees, justStats }),
})
export const getCommitDiffForSingleFile = async (
commitHash1,
commitHash2,
dir = 'notes',
gitdir = `${dir}/git`,
file,
justStats,
) =>
walk({
fs,
dir,
gitdir,
trees: [TREE({ ref: commitHash1 }), TREE({ ref: commitHash2 })],
map: (filename, trees) => {
if (file && filename !== file)
return new Promise((resolve) => {
resolve(undefined)
})
return diffMap({ filepath: filename, trees, justStats })
},
})
// export async function getModifiedCommitDiff(
// commitHash1: string,
// commitHash2: string,
// dir: string = 'notes',
// gitdir: string = `${dir}/git`,
// ): Promise<FileDiff> {
// return await doSomethingAtFileStateChange(commitHash1, commitHash2, dir, gitdir, diffMap)
// }
7eac34a34a4f49c32987f2d97bc9a73fbb9490c4
Reimplement backlinks
currentId: string
}
/**
* TODO: Reimplement backlinks
*/
export const Backlinks = (props: Props) => {
const { currentId, backLinks } = props
404f545207c7d43aa5c2ea6d34b8871a966bc54b
Export Giscus functionality to its own sublibary
/* eslint-disable no-nested-ternary */
// ported from the great https://github.com/giscus/giscus
/**
* TODO: Export Giscus functionality to its own sublibary
*/
import {
Link as ChakraLink,
Box,
Text,
Heading,
Flex,
Button,
VStack,
Container,
HStack,
Icon,
} from '@chakra-ui/react'
import { Waveform } from '@uiball/loaders'
import { useSession } from 'next-auth/react'
import { useCookies } from 'react-cookie'
import { useEffect } from 'react'
import { VscCircleFilled } from 'react-icons/vsc'
import { useFrontBackDiscussion } from '../../services/giscus/discussions'
import { Comment } from './Comment'
import { CommentBox } from './CommentBox'
import { ReactButtons } from './ReactButtons'
interface IGiscusProps {
onDiscussionCreateRequest?: () => Promise<string>
onError?: (message: string) => void
repo: string
term: string
number?: number
category: string
full?: boolean
}
export const Giscus = ({
repo,
term,
number,
category,
full,
onDiscussionCreateRequest,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onError,
}: IGiscusProps) => {
const { data: session } = useSession()
const token = session?.accessToken as string
const query = { repo, term, category, number }
const { updateReactions, increaseSize, backMutators, frontMutators, ...data } =
useFrontBackDiscussion(query, token)
const [cookies, setCookie] = useCookies<string>([])
// useEffect(() => {
// console.log(data)
// if (data.error && onError) {
// console.log(data)
// onError(data?.error?.message as string)
// }
// }, [data.error, onError])
const handleDiscussionCreateRequest = async () => {
const id = onDiscussionCreateRequest ? await onDiscussionCreateRequest() : ''
// Force revalidate
frontMutators.mutate()
backMutators.mutate()
return id
}
const shouldCreateDiscussion = data.isNotFound && !number
// const shouldShowBranding = !!data.discussion.url
const isDataLoaded = !data.error && !data.isNotFound && !data.isLoading
const shouldShowReplyCount = isDataLoaded && data.totalReplyCount > 0
const shouldShowCommentBox =
(data.isRateLimited && !token) ||
(!data.isLoading && !data.isLocked && (!data.error || (data.isNotFound && !number)))
useEffect(() => {
if (isDataLoaded) {
setCookie('visit', {
...cookies.visit,
[term]: {
lastVisit: new Date().toISOString(),
totalCount: data.totalCommentCount || 0 + data.totalReplyCount || 0,
commentCount: data.totalCommentCount || 0,
replyCount: data.totalReplyCount || 0,
},
})
}
}, [data])
return (
<VStack w="full" alignItems="flex-start" spacing={10}>
{full && data.discussion.body && (
<Box>
<Container p={0}>{data.discussion.body}</Container>
</Box>
)}
<Box w="full">
<Flex flexDir="column">
<HStack
alignItems="center"
spacing={4}
className="gsc-header"
justifyContent="flex-start"
flex="auto"
pb={2}
whiteSpace="nowrap"
>
{!data.isLoading && (shouldCreateDiscussion || !data.error) ? (
<Flex fontSize="sm">
<ReactButtons
subjectId={data.discussion.id}
reactionGroups={data.discussion.reactions}
onReact={updateReactions}
onDiscussionCreateRequest={handleDiscussionCreateRequest}
/>
</Flex>
) : null}
<Heading mr={2} fontWeight="regular" size="sm" as="h4" className="gsc-comments-count">
{shouldCreateDiscussion && !data.totalCommentCount ? (
'0 Comments'
) : data.error && !data.backData ? (
'Something went wrong'
) : data.isLoading ? (
<HStack spacing={4} as="span" alignItems="center">
<Waveform /> <Text>Loading Comments</Text>
</HStack>
) : (
<ChakraLink href={data.discussion.url} isExternal className="color-text-primary">
{`${data.totalCommentCount} comments`}
</ChakraLink>
)}
</Heading>
{shouldShowReplyCount ? (
<HStack alignItems="center">
<Heading fontSize="sm" fontWeight="semibold" as="h4">
<Icon as={VscCircleFilled} />
</Heading>
<Heading
mr={2}
size="md"
as="h4"
fontWeight="semibold"
className="gsc-replies-count"
>
{`${data.totalReplyCount} replies`}
</Heading>
</HStack>
) : null}
</HStack>
<Flex flexDir="column" className="gsc-timeline">
{!data.isLoading
? data.frontComments.map((comment) => (
<Comment
key={comment.id}
comment={comment}
replyBox={
token && !data.isLocked ? (
<CommentBox
discussionId={data.discussion.id}
context={repo}
// @ts-expect-error the type is just wrong, don't feel like fixing
onSubmit={frontMutators.addNewReply}
replyToId={comment.id}
viewer={data.viewer}
/>
) : undefined
}
onCommentUpdate={frontMutators.updateComment}
onReplyUpdate={frontMutators.updateReply}
/>
))
: null}
{data.numHidden > 0 ? (
<Flex
justifyContent="center"
py={2}
my={4}
bg="center"
bgRepeat="x"
className="pagination-loader-container gsc-pagination"
>
<Button
display="flex"
flexDir="column"
justifyContent="center"
alignItems="center"
px={6}
py={2}
fontSize="sm"
onClick={increaseSize}
disabled={data.isLoadingMore}
>
<Text
as="span"
className="color-text-secondary"
>{`${data.numHidden} hidden comments`}</Text>
<Text as="span" className="color-text-link font-semibold">
{data.isLoadingMore ? 'Loading' : 'Load more'}โฆ
</Text>
</Button>
</Flex>
) : null}
{!data.isLoading
? data.backComments?.map((comment) => (
<Comment
key={comment.id}
comment={comment}
replyBox={
token && !data.isLocked ? (
<CommentBox
discussionId={data.discussion.id}
context={repo}
// @ts-expect-error the type is just wrong, don't feel like fixing
onSubmit={backMutators.addNewReply}
replyToId={comment.id}
viewer={data.viewer}
/>
) : undefined
}
onCommentUpdate={backMutators.updateComment}
onReplyUpdate={backMutators.updateReply}
/>
))
: null}
</Flex>
{shouldShowCommentBox ? (
<>
<Box
my={4}
fontSize="sm"
borderTop={2}
className="gsc-comment-box-separator color-border-primary"
color="grey.700"
/>
<CommentBox
viewer={data.viewer}
discussionId={data.discussion.id}
context={repo}
// @ts-expect-error the type is just wrong, don't feel like fixing
onSubmit={backMutators.addNewComment}
onDiscussionCreateRequest={handleDiscussionCreateRequest}
/>
</>
) : null}
</Flex>
</Box>
</VStack>
)
}
8ef5405bf9e21ecea96cd696e1904d49ba737616
Make the dataBy... files inherit from the same function
const data = await getFreshDataBySlug(noteDir)
return data
}
// @ts-check
import { readFile, writeFile } from 'fs/promises'
import { join } from 'path'
import readdirp from 'readdirp'
import { slugify } from './slug.mjs'
export const getFreshDataBySlug = async (noteDir) => {
const rawDir = await readdirp.promise(noteDir, { alwaysStat: true })
// Only include md(x) files
return rawDir
.filter((entry) => /\.mdx?$/.test(entry.path))
.reduce((acc, curr) => {
const name = curr.basename.replace(/\.mdx?$/, '')
const slug = slugify(name)
const { atime, mtime, ctime, birthtime, ...stats } = { ...curr.stats }
acc[slug] = {
stats,
fullPath: curr.fullPath,
path: curr.path,
name,
slug,
folders:
curr.path
.replace(curr.basename, '')
.split('/')
.filter((entry) => entry) ?? [],
basename: curr.basename,
}
return acc
}, {})
}
// TODO: Make the dataBy... files inherit from the same function
export const mdxDataBySlug = async (dataDir, noteDir) => {
// if (process.env.ALWAYS_FRESH !== 'true' && process.env.NODE_ENV !== 'production') {
// const data = await getFreshDataBySlug(noteDir)
// return data
// }
const datapath = join(dataDir, 'dataBySlug.json')
try {
const data = JSON.parse(await readFile(datapath, 'utf8'))
return data
} catch (e) {
console.log('No data found, writing new')
const data = await getFreshDataBySlug(noteDir)
await writeFile(datapath, JSON.stringify(data))
return data
}
}
ab0c3cf5d36cc3b44d26f2017387fd1afdfeb784
The homepage is kinda uggo
Create a reusable library for the MDX compiler for this as well, stop reinventing friggin markdown rendering all the time
import React from 'react'
import { unified } from 'unified'
import remarkParse from 'remark-parse'
import rehype2react, { Options } from 'rehype-react'
import remarkRehype from 'remark-rehype'
import remarkGFM from 'remark-gfm'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
// import 'katex/dist/katex.css'
// TODO: Create a reusable library for the MDX compiler for this as well, stop reinventing friggin markdown rendering all the time
export interface MarkdownToReactProps {
children: string
}
export const MarkdownToReact = ({ children }: MarkdownToReactProps) => {
const htmlWithLinks = children.replace(/(https?:\/\/([^<]*))/g, '<a href="$1">$1</a>')
const processor = unified()
.use(remarkParse)
.use(remarkMath)
.use(remarkGFM)
.use(remarkRehype)
.use(rehypeKatex)
.use(rehype2react, {
createElement: React.createElement,
Fragment: React.Fragment,
})
return processor.processSync(htmlWithLinks).result
}
c50014a6ef835d1071d758c633f251e505a483b4
Extract this to a separate component
// openContextMenu: any
// isWiki?: boolean
// noUnderline?: boolean
}
export interface NormalLinkProps {
href: string
children: string
}
/**
* TODO: Extract this to a separate component
*/
export const NodeLink = (props: NodeLinkProps) => {
const {
// noUnderline,
0a1e16fd7b42fbf9831fdd61ae9f733435f5e2eb
Deny access using middleware instead of client side functions
list: true,
})
// TODO: Deny access using middleware instead of client side functions
if (!access) {
window.location.replace('/')
}
const [cookies, setCookie] = useCookies(['visit'])
if (!cookies.visit) setCookie('visit', {})
f65e6eb54596f7417367eb98b245d8f2227a46a2
maybe optimize this to one loop, but prolly not a big deal
eslint-disable-next-line @typescript-eslint/no-unused-vars
commitHash1: string,
commitHash2: string,
dir: string = 'notes',
gitdir: string = ${dir}/git
,
): Promise {
return await doSomethingAtFileStateChange(commitHash1, commitHash2, dir, gitdir, diffMap)
}
/* eslint-disable consistent-return */
import { TREE, walk } from 'isomorphic-git'
import fs from 'fs'
import * as Diff from 'diff'
import { extname } from 'path'
const bufferToString = async (tree) => {
const content = (await tree?.content()) || []
return content.length ? Buffer.from(content).toString('utf8') : ''
}
const diffMap = async (props) => {
const { filepath, trees, type, justStats } = props
const [tree1, tree2] = trees
if (type === 'equal') {
return
}
// ignore dirs
if (filepath === '.') {
return
}
// just show .org diffs
// TODO make this an env var
if (!['.org', '.md', '.mdx'].includes(extname(filepath))) {
return
}
if ((await tree1?.type()) === 'tree' || (await tree2?.type()) === 'tree') {
return
}
// ignore unmodified files
if ((await tree1?.oid()) === (await tree2?.oid())) return
if (!tree1 || !tree2) {
// TODO count the words
const added = tree2 ? true : undefined
const impTree = tree2 || tree1
const string = await bufferToString(impTree)
const count = string.split(' ').length
return {
filepath,
oid: (await tree1?.oid()) || (await tree2?.oid()) || '',
diff: justStats
? []
: [
{
count,
value: string,
added,
removed: !added,
},
],
additions: added ? count : 0,
deletions: !added ? count : 0,
}
}
// We don't want to do all the expensive diffing
// if the files are just added or removed.
const treeStrings = []
// eslint-disable-next-line no-restricted-syntax
for (const tree of trees) {
// eslint-disable-next-line no-await-in-loop
const string = await bufferToString(tree)
treeStrings.push(string)
}
const [t1String, t2String] = treeStrings
// console.log(t1String)
const diff = Diff.diffWordsWithSpace(t1String, t2String)
// get total additions for filecommit
const { additions, deletions } = diff.reduce(
(acc, curr) => {
if (curr.added) {
acc.additions += curr.value.split(' ').length
}
if (curr.removed) {
acc.deletions += curr.value.split(' ').length
}
return acc
},
{ additions: 0, deletions: 0 },
)
// and get rid of undefined props as Next doesnit like it
// TODO maybe optimize this to one loop, but prolly not a big deal
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const cleanDiff = diff.map((curr) => {
if (curr.removed === undefined) {
return { ...curr, removed: false }
}
if (curr.added === undefined) {
return { ...curr, added: false }
}
return curr
})
return {
filepath,
oid: (await tree1?.oid()) || (await tree2?.oid()) || '',
diff: justStats ? [] : diff,
additions,
deletions,
}
}
export const getCommitDiff = async (
commitHash1,
commitHash2,
dir = 'notes',
gitdir = `${dir}/git`,
justStats,
) =>
walk({
fs,
dir,
gitdir,
trees: [TREE({ ref: commitHash1 }), TREE({ ref: commitHash2 })],
map: (filename, trees) => diffMap({ filepath: filename, trees, justStats }),
})
export const getCommitDiffForSingleFile = async (
commitHash1,
commitHash2,
dir = 'notes',
gitdir = `${dir}/git`,
file,
justStats,
) =>
walk({
fs,
dir,
gitdir,
trees: [TREE({ ref: commitHash1 }), TREE({ ref: commitHash2 })],
map: (filename, trees) => {
if (file && filename !== file)
return new Promise((resolve) => {
resolve(undefined)
})
return diffMap({ filepath: filename, trees, justStats })
},
})
// export async function getModifiedCommitDiff(
// commitHash1: string,
// commitHash2: string,
// dir: string = 'notes',
// gitdir: string = `${dir}/git`,
// ): Promise<FileDiff> {
// return await doSomethingAtFileStateChange(commitHash1, commitHash2, dir, gitdir, diffMap)
// }
e5dc53901b5834497f6d86bbc082d170698203c3
Maybe I can just reuse https://thesis.tefkah.com/activity or just move that there, but I want some way to display changes
Fix popover dependency cycle
eslint-disable-next-line import/no-cycle
Container,
PopoverTrigger as OrigPopoverTrigger,
} from '@chakra-ui/react'
import { Popover as HeadlessPopover } from '@zkp/popover'
import React from 'react'
// import 'katex/dist/katex.css'
import { ExternalLinkIcon } from '@chakra-ui/icons'
// TODO: Fix popover dependency cycle
// eslint-disable-next-line import/no-cycle
import { PopoverPreview } from './PopoverPreview'
import { NodeLink } from '../Link/NodeLink'
f39dc0ddfe618e27eee6d3ecb1f296195ea1bd8e
[bug] obstructed computation is increasingly off as i increases
import { MutableRefObject } from 'react'
import create from 'zustand'
import { combine } from 'zustand/middleware'
import { StackedNotesState } from '../types'
export interface InitialNoteState {
stackedNotesState: StackedNotesState
scrollContainer: React.RefObject<HTMLDivElement | null> | null
noteWidth: number
obstructedOffset: number
obstructedPageWidth: number
}
export interface SetNoteState {
scrollToIndex: ({
ref,
i,
noteWidth,
}: {
ref: MutableRefObject<HTMLDivElement | null> | null
i: number
noteWidth: number
}) => void
scrollToId: ({
stackedNotesState,
ref,
id,
noteWidth,
}: {
stackedNotesState: StackedNotesState
ref: MutableRefObject<HTMLDivElement | null> | null
id: string
noteWidth: number
}) => void
unHighlightNotes: () => void
setHighlightedNote: (id: string) => void
updateStackedNotesState: ({
x,
width,
obstructedPageWidth,
obstructedOffset,
}: {
allNotes: string[]
x: number
width: number
obstructedPageWidth: number
obstructedOffset: number
}) => void
setStackedNotesState: (stackedNotesState: StackedNotesState) => void
setNoteWidth: (width: number) => void
setScrollContainer: (ref: MutableRefObject<HTMLDivElement | null>) => void
scrollToEnd: (ref: MutableRefObject<HTMLDivElement | null>) => void
getStackStateById: (id: string) => StackedNotesState[typeof id]
removeNoteById: (id: string) => void
}
export const useNotes = create(
combine<InitialNoteState, SetNoteState>(
{
stackedNotesState: {},
scrollContainer: null,
noteWidth: 600,
obstructedOffset: 120,
obstructedPageWidth: 40,
},
(set, get) => ({
getStackStateById: (id: string) => get().stackedNotesState[id],
setStackedNotesState: (stackedNotesState: StackedNotesState) =>
set((state) => ({ ...state, stackedNotesState })),
updateStackedNotesState: ({ allNotes, x, width, obstructedPageWidth, obstructedOffset }) =>
set((state) => ({
...state,
stackedNotesState: allNotes.reduce((acc, curr, i, a) => {
acc[curr] = {
highlighted: false,
overlay:
x > Math.max(width * (i - 1) - obstructedPageWidth * (i - 2), 0) ||
x < Math.max(0, width * (i - 2)),
// TODO: [bug] obstructed computation is increasingly off as i increases
obstructed:
x > Math.max(width * (i + 1) - obstructedPageWidth * (i - 1), 0) ||
x + width * allNotes.length < width * i + obstructedOffset ||
x < width * (i - 1 || 0) - obstructedPageWidth * i,
active: i === a.length - 1,
index: i,
}
return acc
}, {} as StackedNotesState),
})),
unHighlightNotes: () =>
set((state) => {
/**
* Saves a rerender if nothing was highlighted
*/
let shouldRerender = false
const stackedNotesState = Object.entries(state.stackedNotesState).reduce((acc, curr) => {
const [key, val] = curr
if (val.highlighted) shouldRerender = true
acc[key] = { ...val, highlighted: false }
return acc
}, {} as StackedNotesState)
if (!shouldRerender) return state
return {
...state,
stackedNotesState,
}
}),
setHighlightedNote: (id: string) =>
set((state) => {
let shouldRerender = false
const stackedNotesState = Object.entries(state.stackedNotesState).reduce((acc, curr) => {
const [key, val] = curr
acc[key] = { ...val, highlighted: key === id }
if (key === id) shouldRerender = true
return acc
}, {} as StackedNotesState)
if (!shouldRerender) return state
return {
...state,
stackedNotesState,
}
}),
setNoteWidth: (width: number) => set((state) => ({ ...state, noteWidth: width })),
scrollToEnd: (ref) => {
if (!ref) return
ref.current?.scrollTo({ left: ref.current?.scrollWidth, behavior: 'smooth' })
},
scrollToIndex: ({ ref, i, noteWidth }) => {
if (!ref) return
ref.current?.scrollTo({
left: i * noteWidth,
behavior: 'smooth',
})
},
scrollToId: ({ ref, id, noteWidth, stackedNotesState }) => {
if (!ref) return
const index = Object.entries(stackedNotesState).find(([noteId]) => noteId === id)?.[1]
?.index
if (!index) return
ref.current?.scrollTo({
left: index * noteWidth,
behavior: 'smooth',
})
},
removeNoteById: (id: string) =>
set((state) => {
const { [id]: dummy, ...rest } = state.stackedNotesState
return { ...state, stackedNotesState: rest }
}),
setScrollContainer: (ref) => set((state) => ({ ...state, scrollContainer: ref })),
}),
),
)
3a6f0d5fa1f0a6c2c4d5dc068fff0b61e1d41e01
This thing has become extremely unwieldy and slow, plus some things that i am developing here i'd like to reuse in org-roam-ui, such as the Slate Editor etc.
Will be a pain though.
Export Giscus functionality to its own sublibary
// ported from the great https://github.com/giscus/giscus
/**
* TODO: Export Giscus functionality to its own sublibary
*/
import {
Link as ChakraLink,
Box,
14c071b0cc22737792ddb99c2a1879b9930e7238
don't hardcode the opacitycolor
import { useRouter } from 'next/router'
import Link from 'next/link'
import shallow from 'zustand/shallow'
import { Text } from '@chakra-ui/react'
import { useNotes } from '../../stores/noteStore'
export interface NodeLinkProps {
href: any
children: any
currentId: string
// openContextMenu: any
// isWiki?: boolean
// noUnderline?: boolean
}
export const NodeLink = (props: NodeLinkProps) => {
const {
// noUnderline,
// setSidebarHighlightedNode,
// setPreviewNode,
// n nodeById,
// openContextMenu
currentId,
href,
children,
// isWiki,
} = props
const router = useRouter()
const {
noteWidth,
stackedNotesState,
scrollContainer,
scrollToId,
setHighlightedNote,
unHighlightNotes,
} = useNotes(
(state) => ({
noteWidth: state.noteWidth,
stackedNotesState: state.stackedNotesState,
scrollContainer: state.scrollContainer,
scrollToId: state.scrollToId,
setHighlightedNote: state.setHighlightedNote,
unHighlightNotes: state.unHighlightNotes,
}),
shallow,
)
const linkTarget = href.replace(/^\//, '')
const scroll = () =>
scrollToId({ id: linkTarget, stackedNotesState, noteWidth, ref: scrollContainer })
// const theme = useTheme()
// const type = href.replaceAll(/(.*?)\:?.*/g, '$1')
// const uri = href.replaceAll(/.*?\:(.*)/g, '$1')
// const ID = id ?? uri
// const linkText = isWiki ? `[[${children}]]` : children
return (
<Text
as="span"
tabIndex={0}
display="inline"
overflow="hidden"
fontWeight={600}
color="primary"
// color={highlightColor}
// textDecoration={noUnderline ? undefined : 'underline'}
// onContextMenu={(e) => {
// e.preventDefault()
// openContextMenu(nodeById[uri], e)
// }}
// onClick={() => setPreviewNode(nodeById[uri])}
// TODO don't hardcode the opacitycolor
_hover={{
textDecoration: 'none',
cursor: 'pointer',
// bgColor: coolHighlightColor + '22'
}}
// _focus={{ outlineColor: highlightColor }}
>
<Link passHref href={href}>
<Text
as="span"
role="link"
onMouseEnter={() => linkTarget && setHighlightedNote(linkTarget)}
onMouseLeave={() => unHighlightNotes()}
onClick={async (e) => {
e.preventDefault()
const basepath = router.asPath.replace(/(.*?)\?s.*/, '$1')
// eslint-disable-next-line no-nested-ternary
const safeQuery = router.query.s
? Array.isArray(router.query.s)
? router.query.s
: [router.query.s]
: []
if (!router.query.s) {
await router.push(
{
pathname: basepath,
query: { ...router.query, s: [...safeQuery, linkTarget] },
},
{
pathname: basepath,
query: { s: [...safeQuery, linkTarget] },
},
{ shallow: true },
)
scroll()
return
}
if (linkTarget && safeQuery.includes(linkTarget)) {
scroll()
return
}
const index = safeQuery.indexOf(currentId)
const newQ = index > -1 ? safeQuery.slice(0, index + 1) : safeQuery
await router.push(
{
pathname: basepath,
query: { ...router.query, s: [...(newQ ?? []), linkTarget] },
},
{
pathname: basepath,
query: { s: [...(newQ ?? []), linkTarget] },
},
{ shallow: true },
)
scroll()
/* if (router.asPath.replace(/.*\//, '') === currentId) {
const url = `${router.asPath}/${id}`
router.push(url, url, { shallow: true })
return
}
// truncate the path to the current id
if()
const truncatedPath = .replace(new RegExp(`(.*?\/${currentId}).*`), '$1') */
}}
>
{children}
</Text>
</Link>
</Text>
)
}
58c23d2a8dbc44caa11a2bffed8bc90fd76137d0
Separate fake from real ids
// Parse a diff baby
import { Box } from '@chakra-ui/react'
import { Change } from 'diff'
import { FileDiff } from '../../types'
import { ParsedOrg } from './parseOrg'
interface Props {
diff: FileDiff
}
export const diffToString = (diffs: FileDiff) => {
if (!diffs) {
return ''
}
return diffs.diff
.map((diff: Change) => {
const { added, removed, value } = diff
if (!added && !removed) return value
const begin = diff.added ? '\n\n#+begin_addition\n\n' : '\n\n#+begin_deletion\n\n'
const end = diff.added ? '\n\n#+end_addition\n\n' : '\n\n#+end_deletion\n\n'
return `${begin}${diff.value}${end}`
})
.join('')
}
export const ParsedDiff = (props: Props) => {
const { diff } = props
const diffString = typeof diff === 'string' ? diff : diffToString(diff)
// TODO: Separate fake from real ids
return (
<Box>
<ParsedOrg currentId="AAA FAKE ID" text={diffString} />
</Box>
)
}
2a6ab44f6b11e42b9ab6ef5af7e6bbd70ac5f4ae
lmao ofc I can just do the cron tasks with github actions! easy peasy.
Notifications for comments are already done by GH, but other changes I can set up an email service for, that'd be hype (although I don't know if you can do that with gh actions).
Could send a weekly digest of changes at the very least.
Flow something like
Hmm that is kind of complicated, would be better to do a webhook.
That would look like
Webhook would also need to send something somewhere that will collect a "changelog" of the last week
Not sure how to do that though
Originally posted by @ThomasFKJorna in #24 (comment)
Ideas:
Update viewgithub button with environment var
<HStack as="span">
<LinkOverlay
isExternal
// TODO: Update viewgithub button with environment var
href={
// eslint-disable-next-line no-nested-ternary
full
? slug
: repo
bd7881b91532f8036d6d05a94dc2bf5009279968
Change parameters to take an object jfc
map: (filename: string, trees: Array<WalkerEntry | null>) =>
diffMap({ filepath: filename, trees, justStats }),
})
}
/**
* TODO: Change parameters to take an object jfc
*/
export const getCommitDiffForSingleFile = async (
commitHash1: string,
commitHash2: string,
84d10a5c501e4afda4f2ca1280332abb55855740
Port diffs to MDX
import { Skeleton } from '@chakra-ui/react'
import { Diff } from '@zkp/types'
// TODO: Port diffs to MDX
import { ParsedDiff } from '../../services/thesis/parseDiff'
import { DiffBox } from './DiffBox'
export const ParsedCommit = (props: { commitData: Diff[]; isLoading: boolean }) => {
const { commitData, isLoading } = props
return (
// eslint-disable-next-line react/jsx-no-useless-fragment
<>
{commitData
?.filter((rawCommit) => rawCommit)
?.map((commit) => {
const { file, diff, additions, deletions } = commit || {
file: '',
diff: '',
additions: 0,
deletions: 0,
}
return isLoading ? (
<Skeleton />
) : (
<DiffBox
key={file}
{...{ isLoaded: !isLoading, oid: '', filepath: file, deletions, additions }}
>
[<ParsedDiff {...{ diff, truncated: true }} />]
</DiffBox>
)
})}
</>
)
}
5ab70d8dab0af422eac5215b190bcf358591eca4
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.