From d491fa23da7d7dbbc6011b5f2c47c4d28f8aac10 Mon Sep 17 00:00:00 2001 From: PrinOrange Date: Thu, 26 Sep 2024 07:31:22 +0800 Subject: [PATCH] [fix] upgrade the frontmatter stringify-handling functions --- lib/frontmatter.ts | 38 ++++++++++++ scripts/newpost.ts | 147 +++++++++++++++++++++++++++------------------ 2 files changed, 128 insertions(+), 57 deletions(-) create mode 100644 lib/frontmatter.ts diff --git a/lib/frontmatter.ts b/lib/frontmatter.ts new file mode 100644 index 0000000..740731e --- /dev/null +++ b/lib/frontmatter.ts @@ -0,0 +1,38 @@ +/** + * Checks if the object contains only valid types. + */ +function isValidFrontmatterObject(obj: Record): boolean { + return Object.values(obj).every( + (value) => + value == null || // null or undefined + typeof value === "string" || + typeof value === "boolean" || + typeof value === "number" || + (Array.isArray(value) && value.every((item) => typeof item === "string")), // valid string array + ); +} + +/** + * Converts a valid object into YAML-like frontmatter string. + * Throws error if object contains invalid types. + */ +export function stringifyFrontmatter(obj: Record): string { + if (!isValidFrontmatterObject(obj)) { + throw new Error("Invalid frontmatter"); + } + + return `---\n${Object.entries(obj) + .map(([key, value]) => { + if (value == null) { + return `${key}: null`; // Handle null values explicitly + } + if (Array.isArray(value)) { + return `${key}: ${JSON.stringify(value)}`; // Handle arrays + } + if (typeof value === "string") { + return `${key}: ${JSON.stringify(value)}`; // Wrap strings with quotes + } + return `${key}: ${value}`; // Handle numbers and booleans + }) + .join("\n")}\n---`; +} diff --git a/scripts/newpost.ts b/scripts/newpost.ts index d1303cb..2ece3ff 100644 --- a/scripts/newpost.ts +++ b/scripts/newpost.ts @@ -1,60 +1,60 @@ -import { type ChildProcessWithoutNullStreams, type SpawnSyncReturns, spawn, spawnSync } from "child_process"; -import fs from "fs"; -import path from "path"; import { PostFilesDirectory } from "@/consts/consts"; import { getCurrentTime } from "@/lib/date"; +import { stringifyFrontmatter } from "@/lib/frontmatter"; +import { type ChildProcessWithoutNullStreams, type SpawnSyncReturns, spawn, spawnSync } from "child_process"; import colors from "colors"; +import fs from "fs"; import inquirer, { type QuestionCollection } from "inquirer"; import _ from "lodash"; +import path from "path"; import { titleCase } from "title-case"; type TAnswer = { - title: string; - subtitle: string; - tags: string; - noPrompt: boolean; - pin: boolean; - allowShare: boolean; + inputTitle: string; + inputSubtitle: string; + inputTags: string; + inputNoPrompt: boolean; + inputPin: boolean; + inputAllowShare: boolean; }; const questions: QuestionCollection = [ { type: "input", - name: "title", - message: "What's the title?", + name: "inputTitle", + message: "What's the title? (Required)", validate: (input: string) => (input.trim() === "" ? "Please enter a title." : true), }, - { type: "input", name: "subtitle", message: "What's the subtitle?" }, - { type: "input", name: "tags", message: "Assign tags for the posts and separate them with commas." }, - { type: "confirm", name: "noPrompt", message: "Do NOT prompt this post? (D:false)", default: false }, - { type: "confirm", name: "pin", message: "Do you want to pin this post? (D:false)", default: false }, - { type: "confirm", name: "allowShare", message: "Do you allow everybody share this post? (D:true)", default: true }, + { + type: "input", + name: "inputSubtitle", + message: "What's the subtitle? (Optional)", + }, + { + type: "input", + name: "inputTags", + message: "Assign tags for the posts and separate them with commas. (Required, default: others)", + }, + { + type: "confirm", + name: "inputNoPrompt", + message: "Do NOT prompt this post? (Required, Default:false)", + default: false, + }, + { + type: "confirm", + name: "inputPin", + message: "Do you want to pin this post? (Required, Default:false)", + default: false, + }, + { + type: "confirm", + name: "inputAllowShare", + message: "Do you allow everybody share this post? (Required, Default:true)", + default: true, + }, ]; -const createTemplate = (answers: TAnswer): string => { - const { year, month, day } = getCurrentTime(); - const tags = JSON.stringify( - answers.tags - .split(",") - .map((tag) => tag.trim()) - .filter((tag) => tag !== ""), - ); - - return `--- -title: "${titleCase(answers.title)}" -subtitle: "${answers.subtitle}" -summary: "" -author: "" -coverURL: "" -time: "${year}-${month}-${day}" -tags: ${tags} -noPrompt: ${answers.noPrompt} -pin: ${answers.pin} -allowShare: ${answers.allowShare} -closed: false ----`; -}; - const writePostFile = (filePath: string, content: string): void => { fs.writeFile(filePath, content, "utf-8", (err) => { if (err) { @@ -65,28 +65,61 @@ const writePostFile = (filePath: string, content: string): void => { console.log(colors.green(colors.bold("Create Post Succeed."))); console.log(`Open the file ${colors.cyan(filePath)} to write your blog now.`); console.log("Some fields, such as summary, need to be filled in by yourself after opening the file."); - - const openFileCommand: Record ChildProcessWithoutNullStreams | SpawnSyncReturns> = { - win32: () => spawn("cmd", ["/c", "start", filePath]), - darwin: () => spawnSync("open", [filePath]), - linux: () => spawnSync("xdg-open", [filePath]), - freebsd: () => spawnSync("xdg-open", [filePath]), - }; - - const command = openFileCommand[process.platform]; - if (command) command(); - else { - console.log("Unsupported platform. Open the file manually please."); - } }); }; +const openPostFile = (filePath: string) => { + const openFileCommand: Record ChildProcessWithoutNullStreams | SpawnSyncReturns> = { + win32: () => spawn("cmd", ["/c", "start", filePath]), + darwin: () => spawnSync("open", [filePath]), + linux: () => spawnSync("xdg-open", [filePath]), + freebsd: () => spawnSync("xdg-open", [filePath]), + }; + + const command = openFileCommand[process.platform]; + if (command) command(); + else { + console.log("Unsupported platform. Open the file manually please."); + } +}; + inquirer.prompt(questions).then((answers) => { + // Process the answer const { year, month, day } = getCurrentTime(); - const content = createTemplate(answers); - const sluggedTitle = _.kebabCase(answers.title); - const postFileName = `${year}-${month}-${day}-${sluggedTitle}.md`; + const title = titleCase(answers.inputTitle); + const subtitle = titleCase(answers.inputSubtitle); + + const tags = _.uniq( + answers.inputTags + .split(",") + .map((tag) => tag.trim()) + .filter((tag) => tag !== ""), + ); + if (tags.length === 0) tags.push("others"); + + const pin = answers.inputPin; + const noPrompt = answers.inputNoPrompt; + const allowShare = answers.inputAllowShare; + const time = `${year}-${month}-${day}`; + + // Stringify the frontmatters + const stringifiedFrontmatter = stringifyFrontmatter({ + title: title, + subtitle: subtitle, + summary: "", + coverURL: null, + time: time, + tags: tags, + pin: pin, + noPrompt: noPrompt, + allowShare: allowShare, + closed: false, + }); + + // Output the new post file + const postFileName = `${year}-${month}-${day}-${_.kebabCase(answers.inputTitle)}.md`; const postFilePath = path.resolve(path.join(PostFilesDirectory, postFileName)); - writePostFile(postFilePath, content); + writePostFile(postFilePath, `${stringifiedFrontmatter}\n`); + openPostFile(postFilePath); });