Skip to content

Commit

Permalink
new, more flexible, build process - based on JSON config files
Browse files Browse the repository at this point in the history
  • Loading branch information
marko-knoebl committed May 8, 2021
1 parent 83cab1a commit 3d75d33
Show file tree
Hide file tree
Showing 569 changed files with 4,544 additions and 899,882 deletions.
9 changes: 9 additions & 0 deletions authoring_notes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# directory structure

- _sections_ (or _src_ in the future):
- only allowed files are json files, md files and exercise solutions
- _exercises_ (may be contained in _src_ in the future): exercise tasks in markdown + solutions
- _assets_ (may be in _src_ in the future): images
- _build_: build scripts
- _pages_: pages of old website (deprecated)

# image styles

recommended styling approaches:
Expand Down
3 changes: 3 additions & 0 deletions babel.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["@babel/env", "@babel/react", "@babel/typescript"]
}
161 changes: 161 additions & 0 deletions build/Collection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import fs from "fs";
import path from "path";
import upath from "upath";
import { loadUrlContent } from "./loadUrlContent";
import { Topic } from "./Topic";

type CollectionJsonfileConfig = {
type: "collection";
titles: Record<string, string>;
languages: Array<string>;
childUrls: Array<string>;
pageUrls?: Record<string, string>;
topicPageUrls?: Record<string, string>;
};

class Collection {
/**
* source url relative to srcBaseDir, e.g.:
* index.json
* react.json
* react/react-advanced/index.json
* react-advanced/index-collection.json
*/
relSrcUrl: string;

/** e.g. "src" or "sections" */
srcBaseDir: string;

/** e.g. "dist" */
distBaseDir: string;

/**
* relative id, e.g.:
* index
* react
* react/react-basics
* would identify collection files, e.g.:
* index.json
* react.json / react/index.json / index-collection.json
* react/react-basics.json / react/react-basics/index.json / ...
*/
relId: string;

/** e.g. {"en": "React basics", "de": "React Grundlagen"} */
titles: Record<string, string>;

/**
* child URLs relative to srcBaseDir, e.g.:
* ["./python-all.json", "./react-all.json", ...]
* ["./react/index-topic.json", ...]
*/
childUrls: Array<string>;

/** contents of child elements */
children: Array<Collection | Topic>;

/**
* relative page URLs
* e.g. {"en": "./index-page-en.md", "de": "./index-page-de.md"}
*/
pageUrls?: Record<string, string>;

/** markdown strings */
pages?: Record<string, string>;

/**
* relative topic page URLs
* e.g. {"en": "./index-topic-page-en.md"}
*/
topicPageUrls?: Record<string, string>;

/** markdown strings */
topicPages?: Record<string, string>;

/** available languages */
languages: Array<string>;

constructor(srcUrl: string, srcBaseDir: string, distBaseDir: string) {
srcUrl = upath.normalize(srcUrl);
this.srcBaseDir = srcBaseDir;
this.distBaseDir = distBaseDir;
this.relSrcUrl = srcUrl.slice(this.srcBaseDir.length + 1);
this.relId = getRelIdFromRelSrcUrl(this.relSrcUrl);
}

load() {
const config: CollectionJsonfileConfig = JSON.parse(
fs.readFileSync(`${this.srcBaseDir}/${this.relSrcUrl}`, {
encoding: "utf-8",
})
);
this.titles = config.titles;
this.languages = config.languages;
this.childUrls = config.childUrls;
this.children = this.childUrls.map((childUrl) => {
const absChildUrl = upath.normalize(
`${this.srcBaseDir}/${path.dirname(this.relSrcUrl)}/${childUrl}`
);
return loadUrlContent(
absChildUrl,
this.srcBaseDir,
this.distBaseDir,
this
);
});
this.pageUrls = config.pageUrls;
this.pages = {};
if (this.pageUrls) {
for (let lang in this.pageUrls) {
const url = this.pageUrls[lang];
this.pages[lang] = fs.readFileSync(`${this.srcBaseDir}/${url}`, {
encoding: "utf-8",
});
}
}
this.topicPageUrls = config.topicPageUrls;
this.topicPages = {};
if (this.topicPageUrls) {
for (let lang in this.topicPageUrls) {
const url = this.topicPageUrls[lang];
this.topicPages[lang] = fs.readFileSync(`${this.srcBaseDir}/${url}`, {
encoding: "utf-8",
});
}
}
}

build() {
for (let child of this.children) {
child.build();
}
}
}

function getRelIdFromRelSrcUrl(relSrcUrl: string) {
let relId;
if (relSrcUrl.endsWith("/index-collection.json")) {
relId = relSrcUrl.slice(
0,
relSrcUrl.length - "/index-collection.json".length
);
} else if (relSrcUrl.endsWith("/index.json")) {
relId = relSrcUrl.slice(0, relSrcUrl.length - "/index.json".length);
} else {
relId = relSrcUrl.slice(0, relSrcUrl.length - ".json".length);
}
return relId;
}

function getRelSrcUrlFromRelId(relId: string) {
if (fs.existsSync(`${relId}.json`)) {
return `${relId}.json`;
} else if (fs.existsSync(`${relId}/index.json`)) {
return `${relId}/index.json`;
} else if (fs.existsSync(`${relId}/index-collection.json`)) {
return `${relId}/index-collection.json`;
}
}

export { Collection };
export type { CollectionJsonfileConfig };
25 changes: 25 additions & 0 deletions build/HtmlPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, { ReactNode } from "react";

/**
* An HTML page template with CSS resets and some styling
*
* - includes the reboot CSS reset
* - sets the html and body height to 100%
*/
function HtmlPage(props: { lang: string; children: ReactNode }) {
return (
<html lang={props.lang} style={{ height: "100%" }}>
<head>
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap-reboot.min.css"
/>
</head>
<body style={{ height: "100%" }}>{props.children}</body>
</html>
);
}

export { HtmlPage };
90 changes: 90 additions & 0 deletions build/PresentationTranslation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import fs from "fs";
import path from "path";
import { Node as UnistNode } from "unist";
import vfile from "vfile";

import { PresentationDataNew } from "./parsedMdToHtmlPresentation";
import { parseAndIncludeMd } from "./parseAndIncludeMd";

type PresentationData = {
filename: string;
htmlTree: object;
htmlDocumentString: string;
lang: string;
mdTree: any;
mdString: string;
presentationTree: object;
presentationString: string;
slideCount: number;
topic: string;
};

class PresentationTranslation {
/**
* relative source url, e.g.:
* typescript/10-basics-en.md
*/
relSrcUrl: string;

/** e.g. "src" or "sections" */
srcBaseDir: string;

/** e.g. "dist" */
distBaseDir: string;

/**
* relative id, e.g.:
* typescript/10-basics-en
*/
relId: string;

/** e.g. "en" or "de" */
language: string;

mdContentStr: string;
mdTree: UnistNode | undefined;
presentationData: PresentationData;
presentationDataNew: PresentationDataNew;

constructor(srcUrl: string, srcBaseDir: string, distBaseDir: string) {
if (!srcUrl.endsWith(".md")) {
throw new Error("invalid filename");
}
this.srcBaseDir = srcBaseDir;
this.relSrcUrl = srcUrl.slice(this.srcBaseDir.length + 1);
this.distBaseDir = distBaseDir;
this.language = this.relSrcUrl.slice(
this.relSrcUrl.length - 5,
this.relSrcUrl.length - 3
);
this.relId = getRelIdFromRelSrcUrl(this.relSrcUrl);
}

load() {
this.mdContentStr = fs.readFileSync(
`${this.srcBaseDir}/${this.relSrcUrl}`,
{ encoding: "utf-8" }
);

const entryFile = vfile({
contents: this.mdContentStr,
path: `${this.srcBaseDir}/${this.relSrcUrl}`,
});
this.mdTree = parseAndIncludeMd(entryFile);
}

getTitle() {
for (let child of this.mdTree.children as Array<UnistNode>) {
if (child.type === "heading") {
return child.children[0].value;
}
}
throw new Error("could not find a heading to extract a title");
}
}

function getRelIdFromRelSrcUrl(relSrcUrl: string) {
return relSrcUrl.slice(0, relSrcUrl.length - ".md".length);
}

export { PresentationTranslation };
Loading

0 comments on commit 3d75d33

Please sign in to comment.