added markdown loading/parsing
This commit is contained in:
parent
7c4585b43d
commit
c6fcdc82ef
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* Takes a string or object with `content` property, extracts
|
||||
* and parses front-matter from the string, then returns an object
|
||||
* with `data`, `content` and other [useful properties](#returned-object).
|
||||
*
|
||||
* @param {Object|String} `input` String, or object with `content` string
|
||||
* @param {Object} `options`
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
declare function matter<
|
||||
I extends matter.Input,
|
||||
O extends matter.GrayMatterOption<I, O>,
|
||||
>(input: I | { content: I }, options?: O): matter.GrayMatterFile<I>
|
||||
|
||||
declare namespace matter {
|
||||
type Input = string
|
||||
interface GrayMatterOption<
|
||||
I extends Input,
|
||||
O extends GrayMatterOption<I, O>,
|
||||
> {
|
||||
parser?: () => void
|
||||
eval?: boolean
|
||||
excerpt?: boolean | ((input: I, options: O) => string)
|
||||
excerpt_separator?: string
|
||||
engines?: {
|
||||
[index: string]:
|
||||
| ((input: string) => object)
|
||||
| {
|
||||
parse: (input: string) => object
|
||||
stringify?: (data: object) => string
|
||||
}
|
||||
}
|
||||
language?: string
|
||||
delimiters?: string | [string, string]
|
||||
}
|
||||
interface GrayMatterFile<I extends Input> {
|
||||
data: { [key: string]: any }
|
||||
content: string
|
||||
excerpt?: string
|
||||
orig: I
|
||||
language: string
|
||||
matter: string
|
||||
stringify(lang: string): string
|
||||
}
|
||||
|
||||
/**
|
||||
* Stringify an object to YAML or the specified language, and
|
||||
* append it to the given string. By default, only YAML and JSON
|
||||
* can be stringified. See the [engines](#engines) section to learn
|
||||
* how to stringify other languages.
|
||||
*
|
||||
* @param {String|Object} `file` The content string to append to stringified front-matter, or a file object with `file.content` string.
|
||||
* @param {Object} `data` Front matter to stringify.
|
||||
* @param {Object} `options` [Options](#options) to pass to gray-matter and [js-yaml].
|
||||
* @return {String} Returns a string created by wrapping stringified yaml with delimiters, and appending that to the given string.
|
||||
*/
|
||||
export function stringify<O extends GrayMatterOption<string, O>>(
|
||||
file: string | { content: string },
|
||||
data: object,
|
||||
options?: GrayMatterOption<string, O>,
|
||||
): string
|
||||
|
||||
/**
|
||||
* Synchronously read a file from the file system and parse
|
||||
* front matter. Returns the same object as the [main function](#matter).
|
||||
*
|
||||
* ```js
|
||||
* var file = matter.read('./content/blog-post.md');
|
||||
* ```
|
||||
* @param {String} `filepath` file path of the file to read.
|
||||
* @param {Object} `options` [Options](#options) to pass to gray-matter.
|
||||
* @return {Object} Returns [an object](#returned-object) with `data` and `content`
|
||||
*/
|
||||
export function read<O extends GrayMatterOption<string, O>>(
|
||||
fp: string,
|
||||
options?: GrayMatterOption<string, O>,
|
||||
): matter.GrayMatterFile<string>
|
||||
|
||||
/**
|
||||
* Returns true if the given `string` has front matter.
|
||||
* @param {String} `string`
|
||||
* @param {Object} `options`
|
||||
* @return {Boolean} True if front matter exists.
|
||||
*/
|
||||
export function test<O extends matter.GrayMatterOption<string, O>>(
|
||||
str: string,
|
||||
options?: GrayMatterOption<string, O>,
|
||||
): boolean
|
||||
|
||||
/**
|
||||
* Detect the language to use, if one is defined after the
|
||||
* first front-matter delimiter.
|
||||
* @param {String} `string`
|
||||
* @param {Object} `options`
|
||||
* @return {Object} Object with `raw` (actual language string), and `name`, the language with whitespace trimmed
|
||||
*/
|
||||
export function language<O extends matter.GrayMatterOption<string, O>>(
|
||||
str: string,
|
||||
options?: GrayMatterOption<string, O>,
|
||||
): { name: string; raw: string }
|
||||
}
|
||||
|
||||
export = matter
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
import fs from 'fs';
|
||||
import sections from 'section-matter';
|
||||
import defaults from './lib/defaults';
|
||||
import stringify from './lib/stringify';
|
||||
import excerpt from './lib/excerpt';
|
||||
import engines from './lib/engines';
|
||||
import toFile from './lib/to-file';
|
||||
import parse from './lib/parse';
|
||||
import * as utils from './lib/utils';
|
||||
|
||||
|
||||
/**
|
||||
* Takes a string or object with `content` property, extracts
|
||||
* and parses front-matter from the string, then returns an object
|
||||
* with `data`, `content` and other [useful properties](#returned-object).
|
||||
*
|
||||
* @param {Object|String} `input` String, or object with `content` string
|
||||
* @param {Object} `options`
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
export default function matter(input, options) {
|
||||
if (input === '') {
|
||||
return { data: {}, content: input, excerpt: '', orig: input };
|
||||
}
|
||||
|
||||
let file = toFile(input);
|
||||
const cached = matter.cache[file.content];
|
||||
|
||||
if (!options) {
|
||||
if (cached) {
|
||||
file = Object.assign({}, cached);
|
||||
file.orig = cached.orig;
|
||||
return file;
|
||||
}
|
||||
|
||||
// only cache if there are no options passed. if we cache when options
|
||||
// are passed, we would need to also cache options values, which would
|
||||
// negate any performance benefits of caching
|
||||
matter.cache[file.content] = file;
|
||||
}
|
||||
|
||||
return parseMatter(file, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse front matter
|
||||
*/
|
||||
|
||||
function parseMatter(file, options) {
|
||||
const opts = defaults(options);
|
||||
const open = opts.delimiters[0];
|
||||
const close = '\n' + opts.delimiters[1];
|
||||
let str = file.content;
|
||||
|
||||
if (opts.language) {
|
||||
file.language = opts.language;
|
||||
}
|
||||
|
||||
// get the length of the opening delimiter
|
||||
const openLen = open.length;
|
||||
if (!utils.startsWith(str, open, openLen)) {
|
||||
excerpt(file, opts);
|
||||
return file;
|
||||
}
|
||||
|
||||
// if the next character after the opening delimiter is
|
||||
// a character from the delimiter, then it's not a front-
|
||||
// matter delimiter
|
||||
if (str.charAt(openLen) === open.slice(-1)) {
|
||||
return file;
|
||||
}
|
||||
|
||||
// strip the opening delimiter
|
||||
str = str.slice(openLen);
|
||||
const len = str.length;
|
||||
|
||||
// use the language defined after first delimiter, if it exists
|
||||
const language = matter.language(str, opts);
|
||||
if (language.name) {
|
||||
file.language = language.name;
|
||||
str = str.slice(language.raw.length);
|
||||
}
|
||||
|
||||
// get the index of the closing delimiter
|
||||
let closeIndex = str.indexOf(close);
|
||||
if (closeIndex === -1) {
|
||||
closeIndex = len;
|
||||
}
|
||||
|
||||
// get the raw front-matter block
|
||||
file.matter = str.slice(0, closeIndex);
|
||||
|
||||
const block = file.matter.replace(/^\s*#[^\n]+/gm, '').trim();
|
||||
if (block === '') {
|
||||
file.isEmpty = true;
|
||||
file.empty = file.content;
|
||||
file.data = {};
|
||||
} else {
|
||||
|
||||
// create file.data by parsing the raw file.matter block
|
||||
file.data = parse(file.language, file.matter, opts);
|
||||
}
|
||||
|
||||
// update file.content
|
||||
if (closeIndex === len) {
|
||||
file.content = '';
|
||||
} else {
|
||||
file.content = str.slice(closeIndex + close.length);
|
||||
if (file.content[0] === '\r') {
|
||||
file.content = file.content.slice(1);
|
||||
}
|
||||
if (file.content[0] === '\n') {
|
||||
file.content = file.content.slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
excerpt(file, opts);
|
||||
|
||||
if (opts.sections === true || typeof opts.section === 'function') {
|
||||
sections(file, opts.section);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose engines
|
||||
*/
|
||||
|
||||
matter.engines = engines;
|
||||
|
||||
/**
|
||||
* Stringify an object to YAML or the specified language, and
|
||||
* append it to the given string. By default, only YAML and JSON
|
||||
* can be stringified. See the [engines](#engines) section to learn
|
||||
* how to stringify other languages.
|
||||
*
|
||||
* @param {String|Object} `file` The content string to append to stringified front-matter, or a file object with `file.content` string.
|
||||
* @param {Object} `data` Front matter to stringify.
|
||||
* @param {Object} `options` [Options](#options) to pass to gray-matter and [js-yaml].
|
||||
* @return {String} Returns a string created by wrapping stringified yaml with delimiters, and appending that to the given string.
|
||||
* @api public
|
||||
*/
|
||||
|
||||
matter.stringify = function(file, data, options) {
|
||||
if (typeof file === 'string') file = matter(file, options);
|
||||
return stringify(file, data, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronously read a file from the file system and parse
|
||||
* front matter. Returns the same object as the [main function](#matter).
|
||||
*
|
||||
* ```js
|
||||
* const file = matter.read('./content/blog-post.md');
|
||||
* ```
|
||||
* @param {String} `filepath` file path of the file to read.
|
||||
* @param {Object} `options` [Options](#options) to pass to gray-matter.
|
||||
* @return {Object} Returns [an object](#returned-object) with `data` and `content`
|
||||
* @api public
|
||||
*/
|
||||
|
||||
matter.read = function(filepath, options) {
|
||||
const str = fs.readFileSync(filepath, 'utf8');
|
||||
const file = matter(str, options);
|
||||
file.path = filepath;
|
||||
return file;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the given `string` has front matter.
|
||||
* @param {String} `string`
|
||||
* @param {Object} `options`
|
||||
* @return {Boolean} True if front matter exists.
|
||||
* @api public
|
||||
*/
|
||||
|
||||
matter.test = function(str, options) {
|
||||
return utils.startsWith(str, defaults(options).delimiters[0]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detect the language to use, if one is defined after the
|
||||
* first front-matter delimiter.
|
||||
* @param {String} `string`
|
||||
* @param {Object} `options`
|
||||
* @return {Object} Object with `raw` (actual language string), and `name`, the language with whitespace trimmed
|
||||
*/
|
||||
|
||||
matter.language = function(str, options) {
|
||||
const opts = defaults(options);
|
||||
const open = opts.delimiters[0];
|
||||
|
||||
if (matter.test(str)) {
|
||||
str = str.slice(open.length);
|
||||
}
|
||||
|
||||
const language = str.slice(0, str.search(/\r?\n/));
|
||||
return {
|
||||
raw: language,
|
||||
name: language ? language.trim() : ''
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose `matter`
|
||||
*/
|
||||
|
||||
matter.cache = {};
|
||||
matter.clearCache = function() {
|
||||
matter.cache = {};
|
||||
};
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import engines from './engines'
|
||||
import * as utils from './utils'
|
||||
|
||||
export default function(options) {
|
||||
const opts = Object.assign({}, options);
|
||||
|
||||
// ensure that delimiters are an array
|
||||
opts.delimiters = utils.arrayify(opts.delims || opts.delimiters || '---');
|
||||
if (opts.delimiters.length === 1) {
|
||||
opts.delimiters.push(opts.delimiters[0]);
|
||||
}
|
||||
|
||||
opts.language = (opts.language || opts.lang || 'yaml').toLowerCase();
|
||||
opts.engines = Object.assign({}, engines, opts.parsers, opts.engines);
|
||||
return opts;
|
||||
};
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
export default function getEngine(name, options) {
|
||||
let engine = options.engines[name] || options.engines[aliase(name)];
|
||||
if (typeof engine === 'undefined') {
|
||||
throw new Error(`gray-matter engine "${name}" is not registered`);
|
||||
}
|
||||
if (typeof engine === 'function') {
|
||||
engine = { parse: engine };
|
||||
}
|
||||
return engine;
|
||||
};
|
||||
|
||||
function aliase(name) {
|
||||
switch (name.toLowerCase()) {
|
||||
case 'js':
|
||||
case 'javascript':
|
||||
return 'javascript';
|
||||
case 'coffee':
|
||||
case 'coffeescript':
|
||||
case 'cson':
|
||||
return 'coffee';
|
||||
case 'yaml':
|
||||
case 'yml':
|
||||
return 'yaml';
|
||||
default: {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import yaml from 'js-yaml';
|
||||
|
||||
/**
|
||||
* Default engines
|
||||
*/
|
||||
|
||||
const engines = {};
|
||||
export default engines;
|
||||
|
||||
/**
|
||||
* YAML
|
||||
*/
|
||||
|
||||
engines.yaml = {
|
||||
parse: yaml.safeLoad.bind(yaml),
|
||||
stringify: yaml.safeDump.bind(yaml)
|
||||
};
|
||||
|
||||
/**
|
||||
* JSON
|
||||
*/
|
||||
|
||||
engines.json = {
|
||||
parse: JSON.parse.bind(JSON),
|
||||
stringify: function(obj, options) {
|
||||
const opts = Object.assign({replacer: null, space: 2}, options);
|
||||
return JSON.stringify(obj, opts.replacer, opts.space);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* JavaScript
|
||||
*/
|
||||
|
||||
engines.javascript = {
|
||||
parse: function parse(str, options, wrap) {
|
||||
/* eslint no-eval: 0 */
|
||||
try {
|
||||
if (wrap !== false) {
|
||||
str = '(function() {\nreturn ' + str.trim() + ';\n}());';
|
||||
}
|
||||
return eval(str) || {};
|
||||
} catch (err) {
|
||||
if (wrap !== false && /(unexpected|identifier)/i.test(err.message)) {
|
||||
return parse(str, options, false);
|
||||
}
|
||||
throw new SyntaxError(err);
|
||||
}
|
||||
},
|
||||
stringify: function() {
|
||||
throw new Error('stringifying JavaScript is not supported');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import defaults from './defaults';
|
||||
|
||||
export default function excerpt(file, options) {
|
||||
const opts = defaults(options);
|
||||
|
||||
if (file.data == null) {
|
||||
file.data = {};
|
||||
}
|
||||
|
||||
if (typeof opts.excerpt === 'function') {
|
||||
return opts.excerpt(file, opts);
|
||||
}
|
||||
|
||||
const sep = file.data.excerpt_separator || opts.excerpt_separator;
|
||||
if (sep == null && (opts.excerpt === false || opts.excerpt == null)) {
|
||||
return file;
|
||||
}
|
||||
|
||||
const delimiter = typeof opts.excerpt === 'string'
|
||||
? opts.excerpt
|
||||
: (sep || opts.delimiters[0]);
|
||||
|
||||
// if enabled, get the excerpt defined after front-matter
|
||||
const idx = file.content.indexOf(delimiter);
|
||||
if (idx !== -1) {
|
||||
file.excerpt = file.content.slice(0, idx);
|
||||
}
|
||||
|
||||
return file;
|
||||
};
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import getEngine from './engine';
|
||||
import defaults from './defaults';
|
||||
|
||||
|
||||
export default function parse(language, str, options) {
|
||||
const opts = defaults(options);
|
||||
const engine = getEngine(language, opts);
|
||||
if (typeof engine.parse !== 'function') {
|
||||
throw new TypeError(`expected "${language}.parse" to be a function`);
|
||||
}
|
||||
return engine.parse(str, opts);
|
||||
};
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import typeOf from 'kind-of';
|
||||
import getEngine from './engine';
|
||||
import defaults from './defaults';
|
||||
|
||||
|
||||
export default function stringify(file, data, options) {
|
||||
if (data == null && options == null) {
|
||||
switch (typeOf(file)) {
|
||||
case 'object':
|
||||
data = file.data;
|
||||
options = {};
|
||||
break;
|
||||
case 'string':
|
||||
return file;
|
||||
default: {
|
||||
throw new TypeError('expected file to be a string or object');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const str = file.content;
|
||||
const opts = defaults(options);
|
||||
if (data == null) {
|
||||
if (!opts.data) return file;
|
||||
data = opts.data;
|
||||
}
|
||||
|
||||
const language = file.language || opts.language;
|
||||
const engine = getEngine(language, opts);
|
||||
if (typeof engine.stringify !== 'function') {
|
||||
throw new TypeError('expected "' + language + '.stringify" to be a function');
|
||||
}
|
||||
|
||||
data = Object.assign({}, file.data, data);
|
||||
const open = opts.delimiters[0];
|
||||
const close = opts.delimiters[1];
|
||||
const matter = engine.stringify(data, options).trim();
|
||||
let buf = '';
|
||||
|
||||
if (matter !== '{}') {
|
||||
buf = newline(open) + newline(matter) + newline(close);
|
||||
}
|
||||
|
||||
if (typeof file.excerpt === 'string' && file.excerpt !== '') {
|
||||
if (str.indexOf(file.excerpt.trim()) === -1) {
|
||||
buf += newline(file.excerpt) + newline(close);
|
||||
}
|
||||
}
|
||||
|
||||
return buf + newline(str);
|
||||
};
|
||||
|
||||
function newline(str) {
|
||||
return str.slice(-1) !== '\n' ? str + '\n' : str;
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import typeOf from 'kind-of';
|
||||
import stringify from './stringify';
|
||||
import * as utils from './utils';
|
||||
|
||||
|
||||
/**
|
||||
* Normalize the given value to ensure an object is returned
|
||||
* with the expected properties.
|
||||
*/
|
||||
|
||||
export default function toFile(file) {
|
||||
if (typeOf(file) !== 'object') {
|
||||
file = { content: file };
|
||||
}
|
||||
|
||||
if (typeOf(file.data) !== 'object') {
|
||||
file.data = {};
|
||||
}
|
||||
|
||||
// if file was passed as an object, ensure that
|
||||
// "file.content" is set
|
||||
if (file.contents && file.content == null) {
|
||||
file.content = file.contents;
|
||||
}
|
||||
|
||||
// set non-enumerable properties on the file object
|
||||
utils.define(file, 'language', file.language || '');
|
||||
utils.define(file, 'matter', file.matter || '');
|
||||
utils.define(file, 'stringify', function(data, options) {
|
||||
if (options && options.language) {
|
||||
file.language = options.language;
|
||||
}
|
||||
return stringify(file, data, options);
|
||||
});
|
||||
|
||||
// strip BOM and ensure that "file.content" is a string
|
||||
file.content = utils.toString(file.content);
|
||||
file.isEmpty = false;
|
||||
file.excerpt = '';
|
||||
return file;
|
||||
};
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import stripBom from 'strip-bom-string'
|
||||
import typeOf from 'kind-of'
|
||||
|
||||
export function define(obj, key, val) {
|
||||
Reflect.defineProperty(obj, key, {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: val
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if `val` is an object
|
||||
*/
|
||||
|
||||
export function isObject(val) {
|
||||
return typeOf(val) === 'object';
|
||||
};
|
||||
|
||||
/**
|
||||
* Cast `val` to a string.
|
||||
*/
|
||||
|
||||
export function toString(input) {
|
||||
if (typeof input !== 'string') {
|
||||
throw new TypeError('expected input to be a string');
|
||||
}
|
||||
return stripBom(input);
|
||||
};
|
||||
|
||||
/**
|
||||
* Cast `val` to an array.
|
||||
*/
|
||||
|
||||
export function arrayify(val) {
|
||||
return val ? (Array.isArray(val) ? val : [val]) : [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if `str` starts with `substr`.
|
||||
*/
|
||||
|
||||
export function startsWith(str, substr, len) {
|
||||
if (typeof len !== 'number') len = substr.length;
|
||||
return str.slice(0, len) === substr;
|
||||
};
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"name": "gray-matter",
|
||||
"version": "4.0.3",
|
||||
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"gray-matter.d.ts",
|
||||
"index.js",
|
||||
"lib"
|
||||
],
|
||||
"main": "index.js",
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha"
|
||||
},
|
||||
"dependencies": {
|
||||
"js-yaml": "^3.13.1",
|
||||
"kind-of": "^6.0.2",
|
||||
"section-matter": "^1.0.0",
|
||||
"strip-bom-string": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ansi-green": "^0.1.1",
|
||||
"benchmarked": "^2.0.0",
|
||||
"coffeescript": "^2.2.3",
|
||||
"delimiter-regex": "^2.0.0",
|
||||
"extend-shallow": "^3.0.2",
|
||||
"front-matter": "^2.3.0",
|
||||
"gulp-format-md": "^1.0.0",
|
||||
"minimist": "^1.2.0",
|
||||
"mocha": "^6.1.4",
|
||||
"toml": "^2.3.3",
|
||||
"vinyl": "^2.1.0",
|
||||
"write": "^1.0.3"
|
||||
},
|
||||
"browser": {
|
||||
"fs": false
|
||||
},
|
||||
"typings": "gray-matter.d.ts",
|
||||
"eslintConfig": {
|
||||
"rules": {
|
||||
"no-console": 0
|
||||
}
|
||||
},
|
||||
"verb": {
|
||||
"toc": false,
|
||||
"layout": "default",
|
||||
"tasks": [
|
||||
"readme"
|
||||
],
|
||||
"plugins": [
|
||||
"gulp-format-md"
|
||||
],
|
||||
"helpers": {
|
||||
"examples": "./examples/helper.js"
|
||||
},
|
||||
"lint": {
|
||||
"reflinks": true
|
||||
},
|
||||
"related": {
|
||||
"list": [
|
||||
"assemble",
|
||||
"metalsmith",
|
||||
"verb"
|
||||
]
|
||||
},
|
||||
"reflinks": [
|
||||
"coffe-script",
|
||||
"generate",
|
||||
"js-yaml",
|
||||
"toml",
|
||||
"update"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +1,4 @@
|
|||
// global.d.ts
|
||||
/// <reference types="vite-plugin-md-to-html/types" />
|
||||
declare module '*.md' {
|
||||
const value: string // markdown is just a string
|
||||
export default value
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
|
|
@ -11,14 +11,20 @@
|
|||
"@emotion/react": "^11.11.1",
|
||||
"@mantine/core": "^6.0.19",
|
||||
"@mantine/hooks": "^6.0.19",
|
||||
"date-fns": "^2.30.0",
|
||||
"gray-matter": "file:deps/gray-matter",
|
||||
"jsonlines": "^0.1.1",
|
||||
"preact": "^10.13.1",
|
||||
"preact-iso": "^2.3.1",
|
||||
"preact-render-to-string": "^6.2.1"
|
||||
"reading-time-estimator": "^1.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||
"@preact/preset-vite": "^2.5.0",
|
||||
"@types/node": "^20.4.9",
|
||||
"glob": "^10.3.3",
|
||||
"markdown-to-jsx": "^7.3.2",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^4.4.9",
|
||||
"vite-plugin-md-to-html": "^0.0.18"
|
||||
"vite": "^4.4.9"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
|
|
@ -0,0 +1,2 @@
|
|||
{"title":"Objects as Reference","date":"2023-01-11T00:00:00.000Z","location":"London, UK","slug":"test"}
|
||||
{"title":"Foobar","date":"2023-10-30T00:00:00.000Z","location":"Somewhere in England","slug":"foobar"}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
---
|
||||
title: Foobar
|
||||
date: 2023-10-30
|
||||
location: Somewhere in England
|
||||
---
|
||||
|
||||
|
||||
## Reference Systems
|
||||
The last essential component for our understanding of objects is to recognise that reference does not exist in relation to individual objects, but is encoded in *systems* which condition, prefigure, or otherwise shape *how one can refer.* Reference is an aspect of all systems, but while some systems use reference, other systems *provide* it. Filesystems, databases, the Unicode standard, programming languages, URL schemes, and other disparate systems, all provide ways to *refer.* Some of these (like Unicode) have a tightly controlled set of objects already defined (i.e. Unicode codepoints), while databases provide no predefined objects but control how one can refer and the structure of the objects referred to. We might view these systems as shaping *families of objects*, which may be more or less homogenous across a range of characteristics. Observing these systems collectively with all of their overlaps and interactions, a comprehensive picture of their interrelations becomes untenable. Much like software in the real world, these complex interactions and overlaps create something closer to ecologies of objects, rather than clearly delineated, singular systems. A broader *ecological* or *complex systems* analysis is out of scope for this paper, but would depend on foundation we are trying to develop here. As such, we will focus on individual systems of reference and not their complex interactions.
|
||||
|
||||
## Characterising Reference
|
||||
Objecthood is not just a binary condition but a *spectrum* — while something is *an* object if we refer to it, the way in which we refer which shapes the nature of its objecthood. To compare, contrast and otherwise understand objects, we need richer language to understand reference and its role in shaping their existence. The following is a collection of 6 dimensions with which we can position objects and the systems that shape them, paired with illustrative examples. We can think of objects as occupying positions or ranges on these dimensions, and systems shaping upper or lower bounds in which objects are positioned. The goal of these is not formalisation, but to provide language with which we can compare, contrast, and articulate features of existing systems as well as systems which do not yet exist.
|
||||
|
||||
**1. Granular - Coarse:** Can the smallest objects be referred to? Or must one reference larger assemblages of these objects?
|
||||
|
||||
> Example: A spreadsheet provides reference to individual cells which contain — often but not always — small quantities of information, whereas a traditional filesystem is very coarse, providing reference to arbitrarily large files and not their constituent parts. Note that granular referenceability does not preclude more coarse reference, but coarse reference *does* preclude (or necessitate fragile workarounds for) granularity.
|
||||
|
||||
**2. Open - Closed:** Can the objects be referred to from any system? or are objects contained *within* a system which excludes, limits, or otherwise controls referenceability?
|
||||
|
||||
> Example: An self-contained application with fully internal state and no APIs or means of reference is a fully closed system, a filesystem which provides the same means of reference as it gives to itself is an open system.
|
||||
|
||||
**3. Persistent - Transient:** Is reference assured over time?
|
||||
|
||||
> Example: File objects stored in a decentralised filesystem are highly persistent — existing for as long as any one person is using them. In contrast, webpages have an average half-life of just over 2 years and are far less persistent [@Fetterly_2003]. An extremely transient object would be one which exists for only moments, such as a value in a running program.
|
||||
|
||||
**4. Atemporal - Temporal:** Can the reference or referent change?
|
||||
|
||||
> Example: Content-addressed objects are atemporal — neither their reference or referent change. Unicode words are a mix — reference is fixed, but the referent can change through social means, i.e. the meaning of words can change. A file in a traditional filesystem is temporal — the reference (filename) can change and so can the referent (file content).
|
||||
|
||||
**5. Technical - Social:** Is the the relation between reference and referent maintained through technical or social means?
|
||||
|
||||
> Example: A file path or database ID is a technical reference, whereas a word or term in text (e.g. "Python") is a primarily social one, as the reference is connected to the referent only in the minds of readers.
|
||||
|
||||
**6. Public - Private:** Is power over reference held in common, or is this power held by a private organisation?
|
||||
|
||||
> Example: Text is public, you can freely use words or phrases across various systems with near ubiquity. On the other hand, Tweets are private, while Twitter lets you reference tweets via their URL, this is at the behest of the company and they are free to change or to remove this capacity at any time. Private objects range from songs on music apps, interface widgets in creative tools, messages in communication apps, and so on.
|
||||
|
||||
# The Role of Objects in Organisation
|
||||
The organisational role of objects stems from the fact that objects are precisely the things which we can talk about and put into relation. Organisation can mean different things depending on the context, but we can identify two broad meanings of the term:
|
||||
|
||||
1. *Organisation-as-object:* A collection of elements with with a given arrangement and structure. This usage emphasises the ways in which these elements or components *are* arranged, grouped, and interconnected.
|
||||
2. *Organisation-as-process:* The process of arranging and structuring elements or components in a systematic and orderly way. This usage of organisation organisation emphasises the ways in which these elements or components *come to be* arranged, grouped, and interconnected to form a cohesive whole that functions according to certain principles or rules.
|
||||
|
||||
We take this combined meaning as a working definition of organisation: The way in which elements *are* or *come to be* arranged, structured, and interconnected. The role of objects here is hopefully clear, as to express or represent structure or relations between objects, these objects must first exist.[^4] This structuring is rarely independent of the systems which condition these objects; changes to the position of a file object in its hierarchy changes the reference itself because its reference is determined by its location in the hierarchical structure. Note that this does not mean a change in *referent*, a text file that is moved to a different place in the hierarchy retains its content (though a small change is often made to its metadata). We also see this coupling in filesystems through the enforcement of metadata.
|
||||
|
||||
[^4]: We often create structure for things which do not yet exist. However, to do this we still must use existing objects, it is just their role that is different (i.e. as stand-ins or "holes" which may or may not get replaced or filled later).
|
||||
|
||||
## Structural Characteristics of Reference
|
||||
While reference provides objecthood, this is often entangled with the way in which objects are organised. For example, a reference to a file is derived from its position in a hierarchy, and a reference to a named class in a programs source code is dependent on there being only one class of the same name within some scope to which the class objects belong. I posit that provision of structure ought to be decoupled from provision of reference in information infrastructure, and that this decoupling key to enabling more pluralistic and flexible organisation of information.
|
||||
|
||||
**7. Independent - Dependent:** An independent reference refers to an object directly, whereas a dependent reference requires reference to another object.
|
||||
|
||||
> Example: A PDF file is an independent object, whereas the pages of that PDF are dependent.[^9]
|
||||
|
||||
[^9]: All objects involve *some* level of dependence, as objects cannot exist with complete independence from the systems that their existence is conditional upon.
|
||||
|
||||
**8. Primitive - Composite:** A primitive reference points to a unit of information which cannot be further divided into units with semantic value, whereas a composite reference points to some collection or arrangement of these primitives.
|
||||
|
||||
> Example: A text paragraph is a composite object because it is composed of characters, a character is a primitive because it cannot be decomposed further into semantically meaningful objects — known as a "seme" in semantics.
|
||||
|
||||
**9. Pointer - Token:** A pointer reference points to a well-defined referent, a token reference points to no referent or one that escapes delineation.
|
||||
|
||||
> Example: A content-addressed file is a pointer because it has a mechanised relation to its referent (a hash derived from data, i.e. its referent). A tag in a notes app is a token, because its through interrelations with other objects (notes for example), that it becomes useful.[^5]
|
||||
|
||||
[^5]: Note that by ignoring a referent we can treat a pointer as a token, but the reverse is not the case. It is common for a pointer object to be repurposed as a token, such as a note being used as a tag in graph-like notes systems such as Roam or Obsidian.
|
||||
|
||||
# Objects and Identity
|
||||
Stateful objects present an interesting challenge for our account. State refers to the present condition of a system, a *stateful* system is thus one which *can have* a present condition — this is in contrast to a stateless system which does not have a present, i.e. has no relation to time [@Harris_2010]. A stateful *object* is therefore one that can have a present condition. While this is a true statement it is not a sufficient definition for objects, as it is non-obvious how we can distinguish between an object that has changed, and an object that has been replaced with a "different" object. We can articulate this more concretely as the problem of *identity*,[^6] which in philosophy is approximate to the question of "sameness” [@Noonan_2022]. While a stateful object has an identity — there is a way in which the object is the same *over time* — object identity is not limited to continuity over time. Identity can extend over space (such as two image objects on opposite sides of the world being "the same"), can have fuzzy boundaries (people can disagree on what objects are part of a meme), can split and recombine (an applications code repository can be forked creating two apps with new identities, and later recombined into a singular "canonical" app), and so on. Object identity depends on technical as well as social systems, and while some systems do treat identity as an essential design consideration [@Hickey_], it is most often a consequence of other decisions around system design, technical efficiency, or other factors. I argue we should *decouple* identity from reference, especially in key systems such as filesystems, databases, and other information infrastructure. I am skeptical that there is any singular *solution* to identity management, but new approaches which are not embedded inside existing systems are sorely needed.
|
||||
|
||||
[^6]: This philosophical concept of identity is distinct from the better-known notion of identity in psychology and the social sciences. The concept in the social sciences has to do with a person's self-conception, social presentation, and more generally, the aspects of a person that make them unique, or qualitatively different from others. Whereas in philosophy identity has to do with a relation between X and Y that holds only if X and Y are the same.
|
||||
|
||||
# Discussion
|
||||
I'm going to switch modes for this last section. I've run out of steam for this draft, so let me just touch on some things I might like to discuss in colloquial language.
|
||||
|
||||
Identifying three key responsibilities or roles of "object systems", and arguing that the tight coupling of these responsibilities is detrimental to our information infrastructure. Decoupling these is key to enabling "better" objects:
|
||||
|
||||
1. *Reference*, which conditions the ways objects come to exist and several of their key properties.
|
||||
2. *Structure*, which conditions how objects are or can be interrelated and the forms they must take. E.g. filesystems impose metadata on their objects, task manager apps impose ways that tasks can be organised, etc.
|
||||
3. *Identity*, which conditions the ways objects can be considered the same. This is the least-explored part of the paper so far but is super important, I think there's a lot of under-explored ways we can manage identity, but currently this is left to the mutability rules of a system (e.g. file modification) or is implicit in the structure and use of objects (e.g. we consider an image in two formats to be the same). This topic is so big and interesting it may merit its own paper, and I do have one idea for a generalisable approach, which is to view identity as a kind of *governance.* That is to say, that identity comes first from the ways that *people* agree that something is the same. We could imagine identity systems that are highly pluralistic with many overlapping identities. This governance position is currently dominated by system designers such as those designing filesystems, but I think we could articulate a much more decentralised and participatory approach to identity.
|
||||
|
||||
Looking at the "wants from objects", what do different discourses want from the objects of concern in their discourse? Malleable software wants interface objects that can are independent of apps; Itemised systems want user-facing data objects to be independent of application boundaries and freely structurable (calendar events, notes, tasks, contacts, etc); post-document systems want the objects currently hidden inside documents to be decoupled from the documents; and so on... One running theme through a lot of these wants is that objects should be robustly referenceable and independent, and that we need to *disaggregate* key objects into smaller referenceable parts. I'd like to expand and articulate these wants through the dimensions laid out earlier in the paper. And I'd like to argue that these discourses should be articulating demands of information *infrastructure* to enable success with these efforts, and recognise the limits of building isolated systems or new platforms — though this can be an effective way of exploring their goals.
|
||||
|
||||
I'd like to explore the predictive and explanatory power of this work. If it is a truly *robust* theory, which I think it could become, this needs to be proved out more empirically. Part of this would involve expanding approaches to analysis, while the dimensions are a nice start I would like to do a more systemic analysis of objects "in the wild" and explore the possibilities of formalising some of this work.
|
||||
|
||||
While individual systems have been explored, in practice these systems overlap and interconnect in many ways. Characterising interrelationships among multiple systems is a key point to expand on. E.g. the relation between an interface element and the "code" objects that underly it. There's lots of work that cares about these relationships and tries to do things with them, such as bidirectional lenses, linking languages, or linked data protocols. An empirical analysis would need to consider the many systems in seemingly "singular" objects: a JSON file, for example, involves file objects, unicode objects, named ontology elements, and these all interact nontrivially, such as the way that hierarchical structure is encoded in a linear structure of unicode objects.
|
||||
|
||||
There's a lot that could be said about expressibility, user agency, and the role of objects in "digital language" — as through reference we create new “words" to speak *with* and new objects to speak *about.*
|
||||
|
||||
I'd like to explore implications for infrastructure design, and this is probably a theme that needs to run through the rest of the paper...
|
||||
|
||||
There's a political-economic dimension, mentioned in the "public-private" dimension, that's worth expanding on. Referenceability is one way that companies can obstruct more open, decentralised, and I think generally "better" systems from emerging. One could argue that some companies benefit from a "monopoly on referenceability" and that this is something we should try and escape, a fun idea here is to borrow the notion of "adversarial interoperability" and advocate for a kind of "adversarial referenceability" as a political/economic demand [@Doctorow_2019a].
|
||||
|
||||
Through this work it became clear that certain roles of objects are poorly supported:
|
||||
|
||||
1. *semantic objects* — which gain usefulness through being stable tokens around which semantic structure can emerge — are poorly supported in our systems. Unicode words or phrases are nice tokens, but are highly dependent on files and are not independent in the way we'd like. Files (especially content-addressed ones) are stable and independent but are primarily *pointers* and have too much imposed structure to be good semantic objects. Imagining the best of both worlds is interesting to me: objects that are robust, atemporal, independent, tokens. Imagine if a phrase like "graph databases" could be its own object with a robust reference like a hash, which could be freely linked to, put into relation with other objects and be used in many contexts and places and not tied to a specific system (as files tend to be, but words do not)...
|
||||
2. *relational objects* — which gain usefulness through expressing relations *between* objects — are also poorly served, as most relations are implicitly embedded in systems and have no independent representation. Research to address this often imposes *a ton* of assumptions and conditions, such as RDF or other semantic web efforts. I'm interested to explore what can be done here and am working on one approach for content-addressed systems with *content-addressed relations*, these do not impose (but can support the representation of) structure — no ordering of relata, no types/labels, no set arity, etc. These relational objects are compelling to me because they can be stable, independent, atemporal, and granular. One could imagine an ambient graph-like network emerging not through some decentralised database or protocol, but as a side effect of creating these kinds of relational objects in many different contexts.
|
||||
|
||||
I've focused on limited kinds of objects, things like files, words, PDFs, reminders, etc. Objects that are mostly referenced through *addressing systems.* The choice of examples in this paper need to be interrogated a lot more and there are other kinds of objects which I barely addressed at all, like the artefacts of HCI research: buttons, widgets, etc. Grappling with the wider existence of objects feels out of scope but is pretty important if this theory is to stand up to scrutiny.
|
||||
|
||||
Lastly, an obvious omission to this paper and discussion is a deeper exploration of the *usefulness* of this work to *specific* research efforts, this needs to change but I am still figuring out which discourses and specific research work I want to give the most attention in this paper. Feedback on this is *incredibly welcome*!
|
||||
|
||||
There's much more to do! And much to do that I *do not know about yet*! If you can help me figure out *any* of this I will be eternally grateful!
|
||||
|
||||
# References {.unnumbered}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
title: Objects as Reference (WIP)
|
||||
author: Orion Reed
|
||||
title: "Objects as Reference"
|
||||
date: 2023-01-11
|
||||
location: Devon, UK
|
||||
---
|
||||
|
||||
**NOTE TO READERS.** This draft is not intended to resemble a complete paper but is a sketch of what one might look like. The goal at this stage is to start discussions, find conceptual issues or major omissions, and discover relevant research. Perhaps most importantly, I'm interested in hearing how *your work* could benefit from this line of research. Any feedback will help move towards more salient, compelling and useful answers.
|
||||
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1,20 @@
|
|||
import {glob} from 'glob';
|
||||
import fs from 'fs';
|
||||
import matter from 'gray-matter';
|
||||
|
||||
function loadStrings() {
|
||||
const posts = glob.sync('public/posts/*.md').map((file) => {
|
||||
const content = fs.readFileSync(file, 'utf8');
|
||||
const { title, date, location } = matter(content).data;
|
||||
const slug = file.replace('public/posts/', '').replace('.md', '');
|
||||
return { title, date, location, slug };
|
||||
});
|
||||
return posts;
|
||||
}
|
||||
|
||||
function saveStrings(posts) {
|
||||
const jsonl = posts.map((post) => JSON.stringify(post)).join('\n');
|
||||
fs.writeFileSync('public/posts.jsonl', jsonl);
|
||||
}
|
||||
|
||||
saveStrings(loadStrings());
|
||||
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="27.68" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 296"><path fill="#673AB8" d="m128 0l128 73.9v147.8l-128 73.9L0 221.7V73.9z"></path><path fill="#FFF" d="M34.865 220.478c17.016 21.78 71.095 5.185 122.15-34.704c51.055-39.888 80.24-88.345 63.224-110.126c-17.017-21.78-71.095-5.184-122.15 34.704c-51.055 39.89-80.24 88.346-63.224 110.126Zm7.27-5.68c-5.644-7.222-3.178-21.402 7.573-39.253c11.322-18.797 30.541-39.548 54.06-57.923c23.52-18.375 48.303-32.004 69.281-38.442c19.922-6.113 34.277-5.075 39.92 2.148c5.644 7.223 3.178 21.403-7.573 39.254c-11.322 18.797-30.541 39.547-54.06 57.923c-23.52 18.375-48.304 32.004-69.281 38.441c-19.922 6.114-34.277 5.076-39.92-2.147Z"></path><path fill="#FFF" d="M220.239 220.478c17.017-21.78-12.169-70.237-63.224-110.126C105.96 70.464 51.88 53.868 34.865 75.648c-17.017 21.78 12.169 70.238 63.224 110.126c51.055 39.889 105.133 56.485 122.15 34.704Zm-7.27-5.68c-5.643 7.224-19.998 8.262-39.92 2.148c-20.978-6.437-45.761-20.066-69.28-38.441c-23.52-18.376-42.74-39.126-54.06-57.923c-10.752-17.851-13.218-32.03-7.575-39.254c5.644-7.223 19.999-8.261 39.92-2.148c20.978 6.438 45.762 20.067 69.281 38.442c23.52 18.375 42.739 39.126 54.06 57.923c10.752 17.85 13.218 32.03 7.574 39.254Z"></path><path fill="#FFF" d="M127.552 167.667c10.827 0 19.603-8.777 19.603-19.604c0-10.826-8.776-19.603-19.603-19.603c-10.827 0-19.604 8.777-19.604 19.603c0 10.827 8.777 19.604 19.604 19.604Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
|
|
@ -1,34 +1,33 @@
|
|||
import {
|
||||
Box,
|
||||
Text,
|
||||
createStyles,
|
||||
Container,
|
||||
Group,
|
||||
Title,
|
||||
Anchor,
|
||||
} from '@mantine/core'
|
||||
|
||||
import { Box, createStyles, Container, Group, Anchor } from '@mantine/core'
|
||||
const useStyles = createStyles((theme) => ({
|
||||
navlink: {
|
||||
home: {
|
||||
color: theme.black,
|
||||
fontFamily: theme.headings.fontFamily,
|
||||
fontSize: '1.2em',
|
||||
fontWeight: 700,
|
||||
fontWeight: 800,
|
||||
},
|
||||
link: {
|
||||
color: theme.black,
|
||||
fontFamily: theme.headings.fontFamily,
|
||||
fontSize: '1.2em',
|
||||
fontWeight: 400,
|
||||
},
|
||||
}))
|
||||
export function Header() {
|
||||
export function Header({ dark }: { dark?: boolean }) {
|
||||
const { classes } = useStyles()
|
||||
|
||||
return (
|
||||
<Box bg="red" py="2rem" mb="1rem">
|
||||
<Container size="md">
|
||||
<Group position="apart">
|
||||
<Title>Orion Reed</Title>
|
||||
<Group>
|
||||
<Anchor className={classes.navlink}>Posts</Anchor>
|
||||
<Anchor className={classes.navlink}>Stream</Anchor>
|
||||
<Anchor className={classes.navlink}>Contact</Anchor>
|
||||
</Group>
|
||||
<Box bg={dark ? 'red' : ''} py="2rem">
|
||||
<Container size="40em">
|
||||
<Group align="end">
|
||||
<Anchor href="/" className={classes.home}>
|
||||
orion reed
|
||||
</Anchor>
|
||||
<Anchor href="/posts" className={classes.link}>
|
||||
posts
|
||||
</Anchor>
|
||||
<Anchor href="/stream" className={classes.link}>
|
||||
stream
|
||||
</Anchor>
|
||||
</Group>
|
||||
</Container>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
import { render } from 'preact'
|
||||
import { LocationProvider, Router, Route } from 'preact-iso'
|
||||
import { Home } from './pages/Home'
|
||||
import { Posts } from './pages/Posts'
|
||||
import { NotFound } from './pages/404'
|
||||
import { Home } from '@/pages/Home'
|
||||
import { Posts } from '@/pages/Posts'
|
||||
import Post from '@/pages/Post'
|
||||
import Stream from '@/pages/Stream'
|
||||
import { NotFound } from '@/pages/404'
|
||||
import { MantineProvider } from '@mantine/styles'
|
||||
import { Container } from '@mantine/core'
|
||||
import { Header } from './components/Header'
|
||||
import { style } from './style'
|
||||
import { Header } from '@/components/Header'
|
||||
import { style } from '@/style'
|
||||
export function App() {
|
||||
return (
|
||||
<MantineProvider withGlobalStyles withNormalizeCSS theme={style}>
|
||||
<Header />
|
||||
<Container size="md">
|
||||
<LocationProvider>
|
||||
<Router>
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/posts" component={Posts} />
|
||||
<Route default component={NotFound} />
|
||||
</Router>
|
||||
</LocationProvider>
|
||||
</Container>
|
||||
<LocationProvider>
|
||||
<Router>
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/posts" component={Posts} />
|
||||
<Route path="/posts/:title" component={Post} />
|
||||
<Route path="/stream" component={Stream} />
|
||||
<Route default component={NotFound} />
|
||||
</Router>
|
||||
</LocationProvider>
|
||||
</MantineProvider>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +1,41 @@
|
|||
import { Box, MantineProvider, Text, Title } from '@mantine/core'
|
||||
import post, { attributes } from '../posts/test.md'
|
||||
import { TypographyStylesProvider } from '@mantine/core'
|
||||
import { Header } from '@/components/Header'
|
||||
import { Container, Text } from '@mantine/core'
|
||||
|
||||
export function Home() {
|
||||
document.title = attributes.title
|
||||
const t = post
|
||||
return (
|
||||
<TypographyStylesProvider>
|
||||
<Text weight={400}>
|
||||
At vero eos et accusamus et iusto odio dignissimos ducimus qui
|
||||
blanditiis praesentium voluptatum deleniti atque corrupti quos dolores
|
||||
et quas molestias excepturi sint occaecati cupiditate non provident,
|
||||
similique sunt in culpa qui officia deserunt mollitia animi, id est
|
||||
laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita
|
||||
distinctio. Nam libero tempore, cum soluta nobis est eligendi optio
|
||||
cumque nihil impedit quo minus id quod maxime placeat facere possimus,
|
||||
omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem
|
||||
quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet
|
||||
ut et voluptates repudiandae sint et molestiae non recusandae. Itaque
|
||||
earum rerum hic tenetur a sapiente delectus, ut aut reiciendis
|
||||
voluptatibus maiores alias consequatur aut perferendis doloribus
|
||||
asperiores repellat.
|
||||
<br />
|
||||
At vero eos et accusamus et iusto odio dignissimos ducimus qui
|
||||
blanditiis praesentium voluptatum deleniti atque corrupti quos dolores
|
||||
et quas molestias excepturi sint occaecati cupiditate non provident,
|
||||
similique sunt in culpa qui officia deserunt mollitia animi, id est
|
||||
laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita
|
||||
distinctio. Nam libero tempore, cum soluta nobis est eligendi optio
|
||||
cumque nihil impedit quo minus id quod maxime placeat facere possimus,
|
||||
omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem
|
||||
quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet
|
||||
ut et voluptates repudiandae sint et molestiae non recusandae. Itaque
|
||||
earum rerum hic tenetur a sapiente delectus, ut aut reiciendis
|
||||
voluptatibus maiores alias consequatur aut perferendis doloribus
|
||||
asperiores repellat.
|
||||
</Text>
|
||||
<div dangerouslySetInnerHTML={{ __html: post }} />
|
||||
</TypographyStylesProvider>
|
||||
<>
|
||||
<Header />
|
||||
<Container size="40em">
|
||||
<Text weight={400}>
|
||||
At vero eos et accusamus et iusto odio dignissimos ducimus qui
|
||||
blanditiis praesentium voluptatum deleniti atque corrupti quos dolores
|
||||
et quas molestias excepturi sint occaecati cupiditate non provident,
|
||||
similique sunt in culpa qui officia deserunt mollitia animi, id est
|
||||
laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita
|
||||
distinctio. Nam libero tempore, cum soluta nobis est eligendi optio
|
||||
cumque nihil impedit quo minus id quod maxime placeat facere possimus,
|
||||
omnis voluptas assumenda est, omnis dolor repellendus. Temporibus
|
||||
autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe
|
||||
eveniet ut et voluptates repudiandae sint et molestiae non recusandae.
|
||||
Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis
|
||||
voluptatibus maiores alias consequatur aut perferendis doloribus
|
||||
asperiores repellat.
|
||||
<br />
|
||||
At vero eos et accusamus et iusto odio dignissimos ducimus qui
|
||||
blanditiis praesentium voluptatum deleniti atque corrupti quos dolores
|
||||
et quas molestias excepturi sint occaecati cupiditate non provident,
|
||||
similique sunt in culpa qui officia deserunt mollitia animi, id est
|
||||
laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita
|
||||
distinctio. Nam libero tempore, cum soluta nobis est eligendi optio
|
||||
cumque nihil impedit quo minus id quod maxime placeat facere possimus,
|
||||
omnis voluptas assumenda est, omnis dolor repellendus. Temporibus
|
||||
autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe
|
||||
eveniet ut et voluptates repudiandae sint et molestiae non recusandae.
|
||||
Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis
|
||||
voluptatibus maiores alias consequatur aut perferendis doloribus
|
||||
asperiores repellat.
|
||||
</Text>
|
||||
</Container>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
import {
|
||||
Box,
|
||||
Container,
|
||||
Text,
|
||||
TypographyStylesProvider,
|
||||
createStyles,
|
||||
Group,
|
||||
} from '@mantine/core'
|
||||
import Markdown from 'markdown-to-jsx'
|
||||
import matter from 'gray-matter'
|
||||
import { readingTime } from 'reading-time-estimator'
|
||||
import { Header } from '@/components/Header'
|
||||
import { useRoute } from 'preact-iso'
|
||||
import { useState, useEffect } from 'preact/hooks'
|
||||
import { format } from 'date-fns'
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
title: {
|
||||
color: theme.black,
|
||||
fontSize: '2.5em',
|
||||
fontWeight: 400,
|
||||
},
|
||||
subtitle: {
|
||||
color: theme.black,
|
||||
fontSize: '2em',
|
||||
fontWeight: 400,
|
||||
},
|
||||
info: {
|
||||
color: theme.black,
|
||||
opacity: 0.8,
|
||||
fontWeight: 500,
|
||||
},
|
||||
}))
|
||||
|
||||
function friendlyDate(dateString: string): string {
|
||||
const inputDate = new Date(dateString)
|
||||
const formattedDate = format(inputDate, 'do MMM yyyy')
|
||||
return formattedDate
|
||||
}
|
||||
|
||||
async function getPost(name: string) {
|
||||
const response = await fetch(`${name}.md?raw`)
|
||||
return matter(await response.text())
|
||||
}
|
||||
|
||||
export default function Post() {
|
||||
const current = useRoute().params.title
|
||||
const [post, setPost] = useState(null)
|
||||
const { classes } = useStyles()
|
||||
|
||||
useEffect(() => {
|
||||
if (current) {
|
||||
getPost(current).then(setPost)
|
||||
}
|
||||
}, [current])
|
||||
|
||||
if (!post) {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
</>
|
||||
)
|
||||
} else {
|
||||
const readTime = readingTime(post.content).text
|
||||
const date = friendlyDate(post.data.date)
|
||||
const location = post.data.location
|
||||
return (
|
||||
<>
|
||||
<Header dark />
|
||||
<Box mb="lg" bg="red" py="lg">
|
||||
<Container size="40em">
|
||||
<Text className={classes.title}>{post.data.title}</Text>
|
||||
<Text className={classes.subtitle}>{post.data.subtitle}</Text>
|
||||
<Group position="apart">
|
||||
<Text className={classes.info}>{date}</Text>
|
||||
<Text className={classes.info}>{location}</Text>
|
||||
<Text className={classes.info}>{readTime}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
</Box>
|
||||
<Container size="40em">
|
||||
<TypographyStylesProvider>
|
||||
<Markdown>{post.content}</Markdown>
|
||||
</TypographyStylesProvider>
|
||||
</Container>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,84 @@
|
|||
import { MantineProvider, Text } from '@mantine/core'
|
||||
import { Header } from '@/components/Header'
|
||||
import {
|
||||
Container,
|
||||
Group,
|
||||
Title,
|
||||
Text,
|
||||
Anchor,
|
||||
useMantineTheme,
|
||||
} from '@mantine/core'
|
||||
import { format } from 'date-fns'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
|
||||
function friendlyDate(dateString: string): string {
|
||||
const inputDate = new Date(dateString)
|
||||
return format(inputDate, 'do MMM yyyy')
|
||||
}
|
||||
|
||||
async function getPosts() {
|
||||
const response = await fetch('posts.jsonl')
|
||||
return await (await response.text()).split('\n').map((post) => {
|
||||
return JSON.parse(post)
|
||||
})
|
||||
}
|
||||
|
||||
function PostListItem({ slug, title, date }) {
|
||||
const black = useMantineTheme().black
|
||||
return (
|
||||
<Group>
|
||||
<Anchor href={`posts/${slug}`} color={black}>
|
||||
{title}
|
||||
</Anchor>
|
||||
<Text color="dimmed" fs="italic">
|
||||
{friendlyDate(date)}
|
||||
</Text>
|
||||
</Group>
|
||||
)
|
||||
}
|
||||
|
||||
function Frame({ children }) {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Container size="40em">
|
||||
<Title>Posts</Title>
|
||||
{children}
|
||||
</Container>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
type Post = {
|
||||
slug: string
|
||||
title: string
|
||||
date: string
|
||||
}
|
||||
|
||||
export function Posts() {
|
||||
return <Text>Welcome to Mantine!</Text>
|
||||
const [posts, setPost] = useState<Array<Post>>(null)
|
||||
useEffect(() => {
|
||||
getPosts().then(setPost)
|
||||
}, [])
|
||||
|
||||
if (!posts) {
|
||||
return (
|
||||
<Frame>
|
||||
<Text>Loading posts...</Text>
|
||||
</Frame>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Frame>
|
||||
{posts.map((post) => {
|
||||
return (
|
||||
<PostListItem
|
||||
slug={post.slug}
|
||||
title={post.title}
|
||||
date={post.date}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Frame>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import { Header } from '@/components/Header'
|
||||
import { Container } from '@mantine/core'
|
||||
|
||||
export default function Stream() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Container size="40em">
|
||||
Stream is work-in-progress... Check back sometime.
|
||||
</Container>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import { Title } from '@mantine/core'
|
||||
export const style = {
|
||||
fontFamily: 'Alegreya, serif',
|
||||
headings: {
|
||||
|
|
@ -15,12 +14,4 @@ export const style = {
|
|||
},
|
||||
black: '#24292e',
|
||||
primaryColor: 'red',
|
||||
// components: {
|
||||
// Title: {
|
||||
// // margin: '0rem',
|
||||
// styles: {
|
||||
// root: { margin: '0rem', border: '1px solid blue' },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@
|
|||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact"
|
||||
"jsxImportSource": "preact",
|
||||
"baseUrl": ".",
|
||||
"types": ["vite/client"],
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
},
|
||||
"include": ["node_modules/vite/client.d.ts", "**/*"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,28 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import preact from '@preact/preset-vite'
|
||||
import { vitePluginMdToHTML } from 'vite-plugin-md-to-html'
|
||||
import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill'
|
||||
import path from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
preact(),
|
||||
vitePluginMdToHTML({
|
||||
resolveImageLinks: true,
|
||||
}),
|
||||
],
|
||||
plugins: [preact()],
|
||||
assetsInclude: ['**/*.md'],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
// Node.js global to browser globalThis
|
||||
define: {
|
||||
global: 'globalThis',
|
||||
},
|
||||
// Enable esbuild polyfill plugins
|
||||
plugins: [
|
||||
NodeGlobalsPolyfillPlugin({
|
||||
buffer: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue