Git Product home page Git Product logo

tefkah / zkp Goto Github PK

View Code? Open in Web Editor NEW
8.0 2.0 1.0 11.11 MB

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

TypeScript 85.81% JavaScript 4.11% Shell 0.28% CSS 5.86% HTML 3.94%
open-science open-scholarship academia monorepo nextjs open-publishing typescript zettelkasten

zkp's Introduction

ZKP

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.

zkp's People

Contributors

tefkah avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

gbacciagaluppi

zkp's Issues

Make the dataBy... files inherit from the same function

Make the dataBy... files inherit from the same function

https://github.com/ThomasFKJorna/thesis-visualization/blob/ec2c917753e6d199e16fe8d2002153d53709dd92/apps/thesis/utils/mdx/mdxDataByName.ts#L8

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

Make the dataBy... files inherit from the same function

Make the dataBy... files inherit from the same function

https://github.com/ThomasFKJorna/thesis-visualization/blob/7289aecda657260aab38442aa44c8c584a406ab7/scripts/clone/mdxDataBySlug.mjs#L33

// @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

[design] more coherent aesthetic

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.

  • pick a color pallette
  • pick a serif body font
  • pick a sans everything else font
  • make thing better

Create a reusable library for the MDX compiler for this as well, stop reinventin...

Create a reusable library for the MDX compiler for this as well, stop reinventing friggin markdown rendering all the time

https://github.com/ThomasFKJorna/thesis-visualization/blob/8492d0cdacac9a1bf1559419c59db3542d41861e/apps/thesis/components/Discussions/md.tsx#L13

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

Make the dataBy... files inherit from the same function

https://github.com/ThomasFKJorna/thesis-visualization/blob/ec2c917753e6d199e16fe8d2002153d53709dd92/apps/thesis/utils/mdx/mdxDataBySlug.ts#L32

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/g...

Create a version of useDiscussion which can have data loaded through getStatic/getServerSide props

https://github.com/ThomasFKJorna/thesis-visualization/blob/30049cf6e0a939842bcf9768258e78cd12f91b2a/apps/thesis/pages/discussions/index.tsx#L32

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

Add support for As mentioned in @Shech2019 like in rehypeCitation

https://github.com/ThomasFKJorna/thesis-visualization/blob/d9738eddf4e8935b35c860613bcbf41da8e41f24/libs/zkp-editor/src/lib/mdx/transformers/cite.ts#L5

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

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: '' }],

}

}

https://github.com/ThomasFKJorna/thesis-visualization/blob/d9738eddf4e8935b35c860613bcbf41da8e41f24/libs/zkp-editor/src/lib/uniorgToSlate.ts#L421

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

Port diffs to MDX

Port diffs to MDX

https://github.com/ThomasFKJorna/thesis-visualization/blob/8492d0cdacac9a1bf1559419c59db3542d41861e/apps/thesis/components/Commits/ParsedCommit.tsx#L2

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

Fix the cookies for the discussions

This is how things look when you haven't visited a discussion
image

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

support column groups

support column groups

see https://orgmode.org/manual/Column-Groups.html

https://github.com/ThomasFKJorna/thesis-visualization/blob/d9738eddf4e8935b35c860613bcbf41da8e41f24/libs/zkp-editor/src/lib/uniorgToSlate.ts#L247

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 ...

Move citations component to separate lib, no longer needed for this project but a waste to just throw away

https://github.com/ThomasFKJorna/thesis-visualization/blob/8492d0cdacac9a1bf1559419c59db3542d41861e/apps/thesis/components/FileViewer/Citations.tsx#L27

  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

make this an env var

https://github.com/ThomasFKJorna/thesis-visualization/blob/5755e742fed4b9c1a1850a787de86da5f7239b7f/scripts/clone/getCommitDiff.mjs#L21

/* 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

[feature] warm cache on hover sidebar

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.

Write up a proper analysis of Streven's classification of idealizations.

milestones:

https://github.com/ThomasFKJorna/thesis-visualization/blob/913b062c0a6db3c9f16c8d6b320503a294977d78/libs/zkp-editor/src/lib/test.md#L57

---
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

Figure out a more modular way to check whether a heading is active

https://github.com/ThomasFKJorna/thesis-visualization/blob/8492d0cdacac9a1bf1559419c59db3542d41861e/apps/thesis/components/Header/HeaderLink.tsx#L13

  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

[bug] card hover only works after refresh

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

count the words

if the files are just added or removed.

https://github.com/ThomasFKJorna/thesis-visualization/blob/5755e742fed4b9c1a1850a787de86da5f7239b7f/scripts/clone/getCommitDiff.mjs#L31

/* 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

Export Giscus functionality to its own sublibary

Export Giscus functionality to its own sublibary

https://github.com/ThomasFKJorna/thesis-visualization/blob/913b062c0a6db3c9f16c8d6b320503a294977d78/libs/discus/src/lib/CommentsOld/Giscus.tsx#L4

/* 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

Make the dataBy... files inherit from the same function

const data = await getFreshDataBySlug(noteDir)

return data

}

https://github.com/ThomasFKJorna/thesis-visualization/blob/68b23ca74f23221cd70aa101e3ba4ada41a3bc63/scripts/clone/mdxDataBySlug.mjs#L33

// @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

Create a reusable library for the MDX compiler for this as well, stop reinventin...

Create a reusable library for the MDX compiler for this as well, stop reinventing friggin markdown rendering all the time

https://github.com/ThomasFKJorna/thesis-visualization/blob/913b062c0a6db3c9f16c8d6b320503a294977d78/libs/discus/src/lib/components/Comments/Md/MarkdownToReact.tsx#L11

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

Extract this to a separate component

https://github.com/ThomasFKJorna/thesis-visualization/blob/8492d0cdacac9a1bf1559419c59db3542d41861e/apps/thesis/components/FileViewer/Link.tsx#L65

  // 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

Deny access using middleware instead of client side functions

https://github.com/ThomasFKJorna/thesis-visualization/blob/30049cf6e0a939842bcf9768258e78cd12f91b2a/apps/thesis/pages/discussions/index.tsx#L41

    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

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)

}

https://github.com/ThomasFKJorna/thesis-visualization/blob/5755e742fed4b9c1a1850a787de86da5f7239b7f/scripts/clone/getCommitDiff.mjs#L79

/* 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

Fix popover dependency cycle

Fix popover dependency cycle

eslint-disable-next-line import/no-cycle

https://github.com/ThomasFKJorna/thesis-visualization/blob/30049cf6e0a939842bcf9768258e78cd12f91b2a/apps/thesis/components/FileViewer/Link.tsx#L23

  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

[bug] obstructed computation is increasingly off as i increases

https://github.com/ThomasFKJorna/thesis-visualization/blob/68b23ca74f23221cd70aa101e3ba4ada41a3bc63/apps/thesis/stores/noteStore.tsx#L79

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

[chore] convert to nx monorepo

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.

don't hardcode the opacitycolor

don't hardcode the opacitycolor

https://github.com/ThomasFKJorna/thesis-visualization/blob/68b23ca74f23221cd70aa101e3ba4ada41a3bc63/apps/thesis/components/Link/NodeLink.tsx#L72

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

Separate fake from real ids

https://github.com/ThomasFKJorna/thesis-visualization/blob/68b23ca74f23221cd70aa101e3ba4ada41a3bc63/apps/thesis/services/thesis/parseDiff.tsx#L33

// 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

Update users when a change happens/once a week

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

  1. Cron runs
  2. Fetches users from db that want to be updated
  3. Checks current status of repo vs their "information"
  4. updates them

Hmm that is kind of complicated, would be better to do a webhook.

That would look like

  1. Webhook runs when something happens on website (would mostly just be a build)
  2. Webhook triggers gh action somehow (i think that's an option)
  3. GH action looks for users that want to be notified
  4. GH action send them an email

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)

Change parameters to take an object jfc

Change parameters to take an object jfc

https://github.com/ThomasFKJorna/thesis-visualization/blob/8492d0cdacac9a1bf1559419c59db3542d41861e/apps/thesis/utils/getCommitDiff.ts#L139

    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

Port diffs to MDX

https://github.com/ThomasFKJorna/thesis-visualization/blob/30049cf6e0a939842bcf9768258e78cd12f91b2a/apps/thesis/components/Diff/ParsedCommit.tsx#L3

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.