Skip to content

Commit

Permalink
feat(blog): Blog support.
Browse files Browse the repository at this point in the history
  • Loading branch information
andymac4182 committed Apr 19, 2023
1 parent 12e035e commit e0bdc24
Show file tree
Hide file tree
Showing 9 changed files with 415 additions and 77 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -12,6 +12,6 @@
"packages/*"
],
"lint-staged": {
"packages/**/*": "npm run fmt -ws --if-present"
"packages/**/*.ts": "prettier --write"
}
}
255 changes: 255 additions & 0 deletions packages/lib/src/ConniePageConfig.ts
@@ -0,0 +1,255 @@
import { MarkdownFile } from "./adaptors";

export type PageContentType = "page" | "blogpost";

type ConfluencePerPageConfig = {
pageTitle: FrontmatterConfig<string>;
frontmatterToPublish: FrontmatterConfig<undefined>;
tags: FrontmatterConfig<string[]>;
pageId: FrontmatterConfig<string | undefined>;
dontChangeParentPageId: FrontmatterConfig<boolean>;
blogPostDate: FrontmatterConfig<string | undefined>;
contentType: FrontmatterConfig<PageContentType>;
};

interface FrontmatterConfig<OUT> {
key: string;
default: OUT;
alwaysProcess?: boolean;
process: ProcessFunction<unknown, OUT>;
}

type ProcessFunction<IN, OUT> = (
value: IN,
markdownFile: MarkdownFile,
alreadyParsed: Partial<ConfluencePerPageValues>
) => OUT | Error;

type excludedProperties = "frontmatterToPublish";

export type ConfluencePerPageValues = Omit<
{
[K in keyof ConfluencePerPageConfig]: ConfluencePerPageConfig[K]["default"]; // TODO: Accumlate Errors
},
excludedProperties
>;

export function processConniePerPageConfig(
markdownFile: MarkdownFile
): ConfluencePerPageValues {
const result: Partial<ConfluencePerPageValues> = {};
const config = conniePerPageConfig;

for (const propertyKey in config) {
const {
process,
default: defaultValue,
key,
alwaysProcess,
} = config[propertyKey as keyof ConfluencePerPageConfig];
if (key in markdownFile.frontmatter || alwaysProcess) {
const frontmatterValue = markdownFile.frontmatter[key];
result[propertyKey as keyof ConfluencePerPageValues] = process(
frontmatterValue as never,
markdownFile,
result
) as never;
} else {
result[propertyKey as keyof ConfluencePerPageValues] =
defaultValue as never;
}
}
return preventOverspreading(result, "frontmatterToPublish");
}

function preventOverspreading<T>(
source: Partial<T>,
...keysToOmit: excludedProperties[]
): T {
const result: Partial<T> = {};

for (const key in source) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (!keysToOmit.includes(key as any)) {
result[key as keyof T] = source[key];
}
}

return result as T;
}

type ValidationResult = {
valid: boolean;
reasons?: string[];
};

function validateDate(dateString: string): ValidationResult {
const reasons: string[] = [];

// Regular expression to check the format: YYYY-MM-DD
const regex = /^\d{4}-\d{2}-\d{2}$/;

if (!regex.test(dateString)) {
reasons.push("Invalid format");
} else {
const parts = dateString.split("-");
const year = parseInt(parts[0], 10);
const month = parseInt(parts[1], 10) - 1; // Date months are 0-based
const day = parseInt(parts[2], 10);

if (year < 0 || year > 9999) {
reasons.push("Invalid year");
}

if (month < 0 || month > 11) {
reasons.push("Invalid month");
}

const date = new Date(year, month, day);

if (
date.getFullYear() !== year ||
date.getMonth() !== month ||
date.getDate() !== day
) {
reasons.push("Invalid day");
}
}

if (reasons.length > 0) {
return { valid: false, reasons };
} else {
return { valid: true };
}
}

export const conniePerPageConfig: ConfluencePerPageConfig = {
pageTitle: {
key: "connie-title",
default: "",
alwaysProcess: true,
process: (yamlValue, markdownFile) => {
return typeof yamlValue === "string"
? yamlValue
: markdownFile.pageTitle;
},
},
frontmatterToPublish: {
key: "connie-frontmatter-to-publish",
default: undefined,
process: (yamlValue, markdownFile) => {
if (yamlValue && Array.isArray(yamlValue)) {
let frontmatterHeader =
"| Key | Value | \n | ----- | ----- |\n";
for (const key of yamlValue) {
if (markdownFile.frontmatter[key]) {
const keyString = key.toString();
const valueString = JSON.stringify(
markdownFile.frontmatter[key]
);
frontmatterHeader += `| ${keyString} | ${valueString} |\n`;
}
}
markdownFile.contents =
frontmatterHeader + markdownFile.contents;
}
return undefined;
},
},
tags: {
key: "tags",
default: [],
process: (yamlValue, markdownFile) => {
const tags: string[] = [];
if (Array.isArray(yamlValue)) {
for (const label of yamlValue) {
if (typeof label === "string") {
tags.push(label);
}
}
}
return tags;
},
},
pageId: {
key: "connie-page-id",
default: undefined,
process: (yamlValue, markdownFile) => {
let pageId: string | undefined;
switch (typeof yamlValue) {
case "string":
case "number":
pageId = yamlValue.toString();
break;
default:
pageId = undefined;
}
return pageId;
},
},
dontChangeParentPageId: {
key: "connie-dont-change-parent-page",
default: false,
process: (dontChangeParentPageId) =>
typeof dontChangeParentPageId === "boolean"
? dontChangeParentPageId
: false,
},
blogPostDate: {
key: "connie-blog-post-date",
default: undefined,
process: (yamlValue: string) => {
const blogPostDateValidation = validateDate(yamlValue);
if (blogPostDateValidation.valid) {
return yamlValue;
} else {
return new Error(
`Blog post date error. ${blogPostDateValidation.reasons?.join()}`
);
}
},
},
contentType: {
key: "connie-content-type",
default: "page",
alwaysProcess: true,
process: (yamlValue, markdownFile, alreadyParsed) => {
if (yamlValue !== undefined && typeof yamlValue !== "string") {
return Error(`Provided "connie-content-type" isn't a string.`);
}

if (
typeof yamlValue === "string" &&
!["page", "blogpost"].includes(yamlValue)
) {
return Error(
`Provided "connie-content-type" isn't "page" or "blogpost".`
);
}

let contentType: PageContentType = "page";

if (alreadyParsed.blogPostDate) {
contentType = "blogpost";

if (
typeof yamlValue === "string" &&
yamlValue !== contentType
) {
return Error(
`When "connie-blog-post-date" is specified "connie-content-type" must be "blogpost" or not specified.`
);
}
}

if (
yamlValue !== undefined &&
["page", "blogpost"].includes(yamlValue)
) {
contentType = yamlValue as PageContentType;
}

return contentType;
},
},
};
11 changes: 11 additions & 0 deletions packages/lib/src/MdToADF.test.ts
Expand Up @@ -185,6 +185,17 @@ const markdownTestCases: MarkdownFile[] = [
"connie-dont-change-parent-page": "invalid",
},
},
{
folderName: "blog",
absoluteFilePath: "/path/to/blog.md",
fileName: "blog.md",
contents:
"# Header 1\n\n## Header 2\n\n### Header 3\n\n#### Header 4\n\n##### Header 5\n\n###### Header 6",
pageTitle: "Blog",
frontmatter: {
"connie-blog-post-date": "2022-01-01",
},
},
];
test.each(markdownTestCases)("parses $fileName", (markdown: MarkdownFile) => {
const mdToADF = new MdToADF();
Expand Down
68 changes: 5 additions & 63 deletions packages/lib/src/MdToADF.ts
Expand Up @@ -6,6 +6,7 @@ import { MarkdownTransformer } from "./MarkdownTransformer";
import { traverse } from "@atlaskit/adf-utils/traverse";
import { MarkdownFile } from "./adaptors";
import { LocalAdfFile } from "./Publisher";
import { processConniePerPageConfig } from "./ConniePageConfig";

const frontmatterRegex = /^\s*?---\n([\s\S]*?)\n---/g;

Expand Down Expand Up @@ -83,75 +84,16 @@ export class MdToADF {
}

convertMDtoADF(file: MarkdownFile): LocalAdfFile {
let markdown = file.contents.replace(frontmatterRegex, "");
file.contents = file.contents.replace(frontmatterRegex, "");

file.pageTitle =
file.frontmatter["connie-title"] &&
typeof file.frontmatter["connie-title"] === "string"
? file.frontmatter["connie-title"]
: file.pageTitle;
const results = processConniePerPageConfig(file);

if (
file.frontmatter["connie-frontmatter-to-publish"] &&
Array.isArray(file.frontmatter["connie-frontmatter-to-publish"])
) {
let frontmatterHeader = "| Key | Value | \n | ----- | ----- |\n";
for (const key of file.frontmatter[
"connie-frontmatter-to-publish"
]) {
if (file.frontmatter[key]) {
const keyString = key.toString();
const valueString = JSON.stringify(file.frontmatter[key]);
frontmatterHeader += `| ${keyString} | ${valueString} |\n`;
}
}
markdown = frontmatterHeader + markdown;
}

const tags: string[] = [];
if (
file.frontmatter["tags"] &&
Array.isArray(file.frontmatter["tags"])
) {
for (const label of file.frontmatter["tags"]) {
if (typeof label === "string") {
tags.push(label);
}
}
}

let pageId: string | undefined;
if (file.frontmatter["connie-page-id"]) {
switch (typeof file.frontmatter["connie-page-id"]) {
case "string":
case "number":
pageId = file.frontmatter["connie-page-id"].toString();
break;
default:
pageId = undefined;
}
}

let dontChangeParentPageId = false;
if (file.frontmatter["connie-dont-change-parent-page"]) {
switch (typeof file.frontmatter["connie-dont-change-parent-page"]) {
case "boolean":
dontChangeParentPageId =
file.frontmatter["connie-dont-change-parent-page"];
break;
default:
dontChangeParentPageId = false;
}
}

const adrobj = this.parse(markdown);
const adrobj = this.parse(file.contents);

return {
...file,
...results,
contents: adrobj,
tags,
pageId,
dontChangeParentPageId,
};
}
}

0 comments on commit e0bdc24

Please sign in to comment.