feat: initial refactor to use marka as backend
This commit is contained in:
@@ -1,9 +1,3 @@
|
||||
import { parseDocument } from "@lib/documents.ts";
|
||||
import { parse, stringify } from "@std/yaml";
|
||||
import { createCrud } from "@lib/crud.ts";
|
||||
import { extractHashTags, formatDate } from "@lib/string.ts";
|
||||
import { fixRenderedMarkdown } from "@lib/helpers.ts";
|
||||
|
||||
export type Article = {
|
||||
id: string;
|
||||
type: "article";
|
||||
@@ -21,88 +15,3 @@ export type Article = {
|
||||
rating?: number;
|
||||
};
|
||||
};
|
||||
|
||||
function renderArticle(article: Article) {
|
||||
const meta = article.meta;
|
||||
if ("date" in meta) {
|
||||
meta.date = formatDate(meta.date);
|
||||
}
|
||||
|
||||
return fixRenderedMarkdown(`${meta
|
||||
? `---
|
||||
${stringify(meta)}
|
||||
---`
|
||||
: `---
|
||||
---`
|
||||
}
|
||||
# ${article.name}
|
||||
${article.tags.map((t) => `#${t}`).join(" ")}
|
||||
${article.content}
|
||||
`);
|
||||
}
|
||||
|
||||
function parseArticle(original: string, id: string): Article {
|
||||
const doc = parseDocument(original);
|
||||
|
||||
let meta = {} as Article["meta"];
|
||||
let name = "";
|
||||
|
||||
const range = [Infinity, -Infinity];
|
||||
|
||||
for (const child of doc.children) {
|
||||
if (child.type === "yaml") {
|
||||
try {
|
||||
meta = parse(child.value) as Article["meta"];
|
||||
} catch (err) {
|
||||
console.log("Error parsing YAML", err);
|
||||
console.log("YAML:", child.value);
|
||||
}
|
||||
|
||||
if (meta["rating"] && typeof meta["rating"] === "string") {
|
||||
meta.rating = [...meta.rating?.matchAll("⭐")].length;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
child.type === "heading" && child.depth === 1 && !name &&
|
||||
child.children.length === 1 && child.children[0].type === "text"
|
||||
) {
|
||||
name = child.children[0].value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
const start = child.position?.start.offset || Infinity;
|
||||
const end = child.position?.end.offset || -Infinity;
|
||||
if (start < range[0]) range[0] = start;
|
||||
if (end > range[1]) range[1] = end;
|
||||
}
|
||||
}
|
||||
|
||||
let content = original.slice(range[0], range[1]);
|
||||
const tags = extractHashTags(content);
|
||||
for (const tag of tags) {
|
||||
content = content.replace("#" + tag, "");
|
||||
}
|
||||
|
||||
return {
|
||||
type: "article",
|
||||
id,
|
||||
name,
|
||||
tags,
|
||||
content,
|
||||
meta,
|
||||
};
|
||||
}
|
||||
|
||||
const crud = createCrud<Article>({
|
||||
prefix: "Media/articles/",
|
||||
parse: parseArticle,
|
||||
render: renderArticle,
|
||||
hasThumbnails: true,
|
||||
});
|
||||
export const getAllArticles = crud.readAll;
|
||||
export const getArticle = crud.read;
|
||||
export const createArticle = crud.create;
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import { parseDocument } from "@lib/documents.ts";
|
||||
import { parse, stringify } from "yaml";
|
||||
import { createCrud } from "@lib/crud.ts";
|
||||
import { extractHashTags, formatDate } from "@lib/string.ts";
|
||||
import { fixRenderedMarkdown } from "@lib/helpers.ts";
|
||||
|
||||
export type Movie = {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -21,103 +15,3 @@ export type Movie = {
|
||||
rating: number;
|
||||
};
|
||||
};
|
||||
|
||||
export function renderMovie(movie: Movie) {
|
||||
const meta = movie.meta;
|
||||
if ("date" in meta && typeof meta.date !== "string") {
|
||||
meta.date = formatDate(meta.date) as unknown as Date;
|
||||
}
|
||||
|
||||
delete meta.thumbnail;
|
||||
delete meta.average;
|
||||
|
||||
const movieImage = ``;
|
||||
|
||||
return fixRenderedMarkdown(`${
|
||||
meta
|
||||
? `---
|
||||
${stringify(meta)}
|
||||
---`
|
||||
: `---
|
||||
---`
|
||||
}
|
||||
# ${movie.name}
|
||||
${
|
||||
// So we do not add a new image to the description everytime we render
|
||||
(movie.meta.image && !movie.description.includes(movieImage))
|
||||
? movieImage
|
||||
: ""}
|
||||
${movie.tags.map((t) => `#${t}`).join(" ")}
|
||||
${movie.description}
|
||||
`);
|
||||
}
|
||||
|
||||
export function parseMovie(original: string, id: string): Movie {
|
||||
const doc = parseDocument(original);
|
||||
|
||||
let meta = {} as Movie["meta"];
|
||||
let name = "";
|
||||
|
||||
const range = [Infinity, -Infinity];
|
||||
|
||||
for (const child of doc.children) {
|
||||
if (child.type === "yaml") {
|
||||
try {
|
||||
meta = (parse(child.value) || {}) as Movie["meta"];
|
||||
} catch (_) {
|
||||
// ignore here
|
||||
}
|
||||
|
||||
if (meta["rating"] && typeof meta["rating"] === "string") {
|
||||
meta.rating = [...meta.rating?.matchAll("⭐")].length;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
child.type === "heading" && child.depth === 1 && !name &&
|
||||
child.children.length === 1 && child.children[0].type === "text"
|
||||
) {
|
||||
name = child.children[0].value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
const start = child.position?.start.offset || Infinity;
|
||||
const end = child.position?.end.offset || -Infinity;
|
||||
if (start < range[0]) range[0] = start;
|
||||
if (end > range[1]) range[1] = end;
|
||||
}
|
||||
}
|
||||
|
||||
let description = original.slice(range[0], range[1]);
|
||||
const tags = extractHashTags(description);
|
||||
for (const tag of tags) {
|
||||
description = description.replace("#" + tag, "");
|
||||
}
|
||||
|
||||
return {
|
||||
type: "movie",
|
||||
id,
|
||||
name,
|
||||
tags,
|
||||
description,
|
||||
meta,
|
||||
};
|
||||
}
|
||||
|
||||
const crud = createCrud<Movie>({
|
||||
prefix: "Media/movies/",
|
||||
parse: parseMovie,
|
||||
render: renderMovie,
|
||||
hasThumbnails: true,
|
||||
});
|
||||
|
||||
export const getMovie = async (id: string) => {
|
||||
const movie = await crud.read(id);
|
||||
return movie;
|
||||
};
|
||||
|
||||
export const getAllMovies = crud.readAll;
|
||||
export const createMovie = crud.create;
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
import {
|
||||
type DocumentChild,
|
||||
getTextOfRange,
|
||||
parseDocument,
|
||||
} from "@lib/documents.ts";
|
||||
import { parse, stringify } from "yaml";
|
||||
import { createCrud } from "@lib/crud.ts";
|
||||
import { extractHashTags } from "@lib/string.ts";
|
||||
import { Ingredient, IngredientGroup } from "@lib/recipeSchema.ts";
|
||||
import { fixRenderedMarkdown } from "@lib/helpers.ts";
|
||||
import { parseIngredients } from "@lib/parseIngredient.ts";
|
||||
|
||||
export type Recipe = {
|
||||
type: "recipe";
|
||||
id: string;
|
||||
@@ -31,179 +19,3 @@ export type Recipe = {
|
||||
thumbnail?: string;
|
||||
};
|
||||
};
|
||||
|
||||
function extractSteps(
|
||||
content: string,
|
||||
seperator: RegExp = /\n(?=\d+\.)/g,
|
||||
): string[] {
|
||||
const steps = content.split(seperator).map((step) => {
|
||||
const match = step.match(/^(\d+)\.\s*(.*)/);
|
||||
if (match) return match[2];
|
||||
return step;
|
||||
}).filter((step) => !!step);
|
||||
return steps as string[];
|
||||
}
|
||||
|
||||
export function parseRecipe(original: string, id: string): Recipe {
|
||||
const doc = parseDocument(original);
|
||||
|
||||
let name = "";
|
||||
let meta: Recipe["meta"] = {};
|
||||
|
||||
const groups: DocumentChild[][] = [];
|
||||
let group: DocumentChild[] = [];
|
||||
for (const child of doc.children) {
|
||||
if (child.type === "yaml") {
|
||||
try {
|
||||
meta = parse(child.value) as Recipe["meta"];
|
||||
} catch (err) {
|
||||
console.log("Error parsing YAML", err);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
child.type === "heading" && child.depth === 1 && !name &&
|
||||
child.children.length === 1 && child.children[0].type === "text"
|
||||
) {
|
||||
name = child.children[0].value;
|
||||
continue;
|
||||
}
|
||||
if (child.type === "thematicBreak") {
|
||||
groups.push(group);
|
||||
group = [];
|
||||
continue;
|
||||
}
|
||||
group.push(child);
|
||||
}
|
||||
|
||||
if (group.length) {
|
||||
groups.push(group);
|
||||
}
|
||||
|
||||
let description = getTextOfRange(groups[0], original);
|
||||
|
||||
let ingredientsText = getTextOfRange(groups[1], original);
|
||||
if (ingredientsText) {
|
||||
ingredientsText = ingredientsText.replace(/#+\s?Ingredients?/, "");
|
||||
} else {
|
||||
ingredientsText = "";
|
||||
}
|
||||
|
||||
const ingredients = parseIngredients(ingredientsText);
|
||||
|
||||
const instructionText = getTextOfRange(groups[2], original);
|
||||
let instructions = extractSteps(instructionText || "");
|
||||
if (instructions.length <= 1) {
|
||||
const d = extractSteps(instructionText || "", /\n/g);
|
||||
if (d.length > instructions.length) {
|
||||
instructions = d;
|
||||
}
|
||||
}
|
||||
|
||||
const tags = extractHashTags(description || "");
|
||||
if (description) {
|
||||
for (const tag of tags) {
|
||||
description = description.replace("#" + tag, "");
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: "recipe",
|
||||
id,
|
||||
meta,
|
||||
name,
|
||||
tags,
|
||||
markdown: original,
|
||||
notes: getTextOfRange(groups[3], original)?.split("\n"),
|
||||
description,
|
||||
ingredients,
|
||||
instructions,
|
||||
};
|
||||
}
|
||||
|
||||
function filterUndefinedFromObject<T extends { [key: string]: unknown }>(
|
||||
obj: T,
|
||||
) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).filter(([_, v]) => v !== undefined),
|
||||
);
|
||||
}
|
||||
|
||||
export function renderRecipe(recipe: Recipe) {
|
||||
const meta = filterUndefinedFromObject(recipe.meta || {});
|
||||
|
||||
// Clean up meta properties
|
||||
delete meta.thumbnail;
|
||||
delete meta.average;
|
||||
|
||||
const recipeImage = meta.image ? `` : "";
|
||||
|
||||
// Format ingredient groups and standalone ingredients
|
||||
const ingredients = recipe.ingredients
|
||||
.map((item) => {
|
||||
if ("items" in item) {
|
||||
return `\n*${item.name}*\n${
|
||||
item.items
|
||||
.map((ing) => {
|
||||
if (ing.quantity && ing.unit) {
|
||||
return `- **${ing.quantity.trim() || ""}${
|
||||
ing.unit.trim() || ""
|
||||
}** ${ing.name}`;
|
||||
}
|
||||
return `- ${ing.name}`;
|
||||
})
|
||||
.join("\n")
|
||||
}`;
|
||||
}
|
||||
if (item.quantity && item.unit) {
|
||||
return `- **${item.quantity?.trim() || ""}${
|
||||
item.unit?.trim() || ""
|
||||
}** ${item.name}`;
|
||||
}
|
||||
|
||||
if (item.quantity) {
|
||||
return `- **${item.quantity}** ${item.name}`;
|
||||
}
|
||||
|
||||
return `- ${item.name}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
// Format instructions as a numbered list
|
||||
const instructions = recipe.instructions
|
||||
? recipe.instructions.map((step, i) => `${i + 1}. ${step}`).join("\n")
|
||||
: "";
|
||||
|
||||
// Render the final markdown
|
||||
return fixRenderedMarkdown(`${
|
||||
Object.keys(meta).length
|
||||
? `---
|
||||
${stringify(meta)}
|
||||
---`
|
||||
: `---
|
||||
---`
|
||||
}
|
||||
# ${recipe.name}
|
||||
${recipe.meta?.image ? recipeImage : ""}
|
||||
${recipe.tags.map((t) => `#${t.replaceAll(" ", "-")}`).join(" ")}
|
||||
${recipe.description || ""}
|
||||
|
||||
---
|
||||
|
||||
${ingredients ? `## Ingredients\n\n${ingredients}\n\n---\n` : ""}
|
||||
${instructions ? `${instructions}\n\n---` : ""}
|
||||
${recipe.notes?.length ? `\n${recipe.notes.join("\n")}` : ""}
|
||||
`);
|
||||
}
|
||||
|
||||
const crud = createCrud<Recipe>({
|
||||
prefix: `Recipes/`,
|
||||
parse: parseRecipe,
|
||||
render: renderRecipe,
|
||||
hasThumbnails: true,
|
||||
});
|
||||
|
||||
export const getAllRecipes = crud.readAll;
|
||||
export const getRecipe = crud.read;
|
||||
export const updateRecipe = crud.update;
|
||||
export const createRecipe = crud.create;
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import { parseDocument } from "@lib/documents.ts";
|
||||
import { parse, stringify } from "yaml";
|
||||
import { createCrud } from "@lib/crud.ts";
|
||||
import { extractHashTags, formatDate } from "@lib/string.ts";
|
||||
import { fixRenderedMarkdown } from "@lib/helpers.ts";
|
||||
|
||||
export type Series = {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -21,99 +15,3 @@ export type Series = {
|
||||
done?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
function renderSeries(series: Series) {
|
||||
const meta = series.meta;
|
||||
if ("date" in meta) {
|
||||
meta.date = formatDate(meta.date);
|
||||
}
|
||||
|
||||
delete meta.thumbnail;
|
||||
delete meta.average;
|
||||
|
||||
const movieImage = ``;
|
||||
|
||||
return fixRenderedMarkdown(`${
|
||||
meta
|
||||
? `---
|
||||
${stringify(meta)}
|
||||
---`
|
||||
: `---
|
||||
---`
|
||||
}
|
||||
# ${series.name}
|
||||
${
|
||||
// So we do not add a new image to the description everytime we render
|
||||
(series.meta.image && !series.description.includes(movieImage))
|
||||
? movieImage
|
||||
: ""}
|
||||
${series.tags.map((t) => `#${t}`).join(" ")}
|
||||
${series.description}
|
||||
`);
|
||||
}
|
||||
|
||||
export function parseSeries(original: string, id: string): Series {
|
||||
const doc = parseDocument(original);
|
||||
|
||||
let meta = {} as Series["meta"];
|
||||
let name = "";
|
||||
|
||||
const range = [Infinity, -Infinity];
|
||||
|
||||
for (const child of doc.children) {
|
||||
if (child.type === "yaml") {
|
||||
try {
|
||||
meta = (parse(child.value) || {}) as Series["meta"];
|
||||
} catch (_) {
|
||||
// ignore here
|
||||
}
|
||||
|
||||
if (meta["rating"] && typeof meta["rating"] === "string") {
|
||||
meta.rating = [...meta.rating?.matchAll("⭐")].length;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
child.type === "heading" && child.depth === 1 && !name &&
|
||||
child.children.length === 1 && child.children[0].type === "text"
|
||||
) {
|
||||
name = child.children[0].value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
const start = child.position?.start.offset || Infinity;
|
||||
const end = child.position?.end.offset || -Infinity;
|
||||
if (start < range[0]) range[0] = start;
|
||||
if (end > range[1]) range[1] = end;
|
||||
}
|
||||
}
|
||||
|
||||
let description = original.slice(range[0], range[1]);
|
||||
const tags = extractHashTags(description);
|
||||
for (const tag of tags) {
|
||||
description = description.replace("#" + tag, "");
|
||||
}
|
||||
|
||||
return {
|
||||
type: "series",
|
||||
id,
|
||||
name,
|
||||
tags,
|
||||
description,
|
||||
meta,
|
||||
};
|
||||
}
|
||||
|
||||
const crud = createCrud<Series>({
|
||||
prefix: "Media/series/",
|
||||
parse: parseSeries,
|
||||
render: renderSeries,
|
||||
hasThumbnails: true,
|
||||
});
|
||||
|
||||
export const getSeries = crud.read;
|
||||
export const getAllSeries = crud.readAll;
|
||||
export const createSeries = crud.create;
|
||||
|
||||
Reference in New Issue
Block a user