@hestia-earth/guide 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +48 -0
- package/envs/.dev.env +2 -0
- package/envs/.develop.env +2 -0
- package/envs/.master.env +2 -0
- package/package.json +41 -0
- package/scripts/build-content.js +54 -0
- package/scripts/build-index.js +91 -0
- package/scripts/build.js +57 -0
- package/scripts/check-content.js +80 -0
- package/scripts/template.css +6 -0
- package/scripts/template.html +22 -0
- package/scripts/template.js +5 -0
- package/scripts/utils.js +117 -0
- package/test.js +4 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019-2024 Harmonised Environmental Storage and Tracking of the Impacts of Agriculture (HESTIA) Project
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Hestia Guide
|
|
2
|
+
|
|
3
|
+
This repository contains the raw content of the guide written in Markdown language.
|
|
4
|
+
|
|
5
|
+
Every file under _src/content_ will be automatically deployed to https://hestia-earth.gitlab.io/hestia-guide domain for testing.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
- src/content/introduction.md will be deployed to https://hestia-earth.gitlab.io/hestia-guide/introduction
|
|
9
|
+
- src/content/how-to-upload.md will be deployed to https://hestia-earth.gitlab.io/hestia-guide/how-to-upload
|
|
10
|
+
|
|
11
|
+
Note: only lower chars, dashes (`-`) and numbers are allowed in the filenames.
|
|
12
|
+
|
|
13
|
+
## Writing Guidelines
|
|
14
|
+
|
|
15
|
+
1. Create a new file under `src/content` and make sure the filename only uses lowercase letters, numbers and dashes.
|
|
16
|
+
1. Start the file with a header level 1 using a single `#`. Example:
|
|
17
|
+
|
|
18
|
+
```markdown
|
|
19
|
+
# This is the header
|
|
20
|
+
|
|
21
|
+
This is the content.
|
|
22
|
+
|
|
23
|
+
## This is a sub-header
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The header level 1 will be automatically used as Title for the Guide page, so please try to keep it short.
|
|
27
|
+
|
|
28
|
+
The second line (omitting the blank lines) will be used as abstract.
|
|
29
|
+
|
|
30
|
+
**Important**: the folder and the file name should reflect the Guide navigation structure.
|
|
31
|
+
|
|
32
|
+
Example: if you are adding a page called "Signing in" under "HESTIA 101", please add `src/content/hestia-101/3_signin.md` file. This will automatically:
|
|
33
|
+
- show the page under "HESTIA 101" menu
|
|
34
|
+
- show the page as item number 3 in the menu (if there are 2 pages before number `1_`, `2_` in the same folder)
|
|
35
|
+
- match the page with the url `hestia-101-signin`
|
|
36
|
+
- the title of the page will be used also in the menu
|
|
37
|
+
|
|
38
|
+
### Adding metadata
|
|
39
|
+
|
|
40
|
+
It is possible to add a list of metadata, using the following format:
|
|
41
|
+
```
|
|
42
|
+
/<name of metadata> ~Title~
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
These metadata must be added at the end of the file, one per line. The list of metadata is:
|
|
46
|
+
|
|
47
|
+
| Name | Can be added multiple times | Description |
|
|
48
|
+
| ---- | ---- | ---- |
|
|
49
|
+
| `previous` | ❌ | Link to the previous guide page (will show at the bottom) |
|
|
50
|
+
| `next` | ❌ | Link to the next guide page (will show at the bottom) |
|
|
51
|
+
| `tag` | ✅ | Used to group pages together as "related" |
|
|
52
|
+
|
|
53
|
+
**Example:**
|
|
54
|
+
|
|
55
|
+
```markdown
|
|
56
|
+
# This is the title
|
|
57
|
+
|
|
58
|
+
This is the content of the page.
|
|
59
|
+
|
|
60
|
+
/tag ~Hestia~
|
|
61
|
+
/tag ~Beginners~
|
|
62
|
+
/previous ~how-to-upload~
|
|
63
|
+
/next ~how-to-access-data~
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Adding images or videos
|
|
67
|
+
|
|
68
|
+
To add an image or a video to a page:
|
|
69
|
+
- add the file under the `src/assets` folder. Note: you can use sub-directories to organise the files.
|
|
70
|
+
- make sure to use a simple name, without special characters. So only letters and numbers, dashes or underscores.
|
|
71
|
+
- in the content of the page, use either
|
|
72
|
+
```html
|
|
73
|
+
<img src="/guide-content/assets/<path to the file>" alt="Text to describe the image" height="auto">
|
|
74
|
+
```
|
|
75
|
+
or to use videos in different formats
|
|
76
|
+
```html
|
|
77
|
+
<video width="320" height="240" controls>
|
|
78
|
+
<source src="/guide-content/assets/<path to the file>.mp4" type="video/mp4">
|
|
79
|
+
<source src="/guide-content/assets/<path to the file>.ogg" type="video/ogg">
|
|
80
|
+
</video>
|
|
81
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface IGuideIndexPageContent {
|
|
2
|
+
id: string;
|
|
3
|
+
mdPath: string;
|
|
4
|
+
jsonPath: string;
|
|
5
|
+
title: string;
|
|
6
|
+
createdAt: string;
|
|
7
|
+
updatedAt?: string;
|
|
8
|
+
tags?: Record<string, string[]>;
|
|
9
|
+
}
|
|
10
|
+
type guidePages = {
|
|
11
|
+
[url: string]: IGuideIndexPage;
|
|
12
|
+
};
|
|
13
|
+
export interface IGuideIndexPage {
|
|
14
|
+
content?: IGuideIndexPageContent;
|
|
15
|
+
pages?: guidePages;
|
|
16
|
+
}
|
|
17
|
+
export declare const loadIndex: () => IGuideIndexPage;
|
|
18
|
+
export declare const findPage: (id: string) => IGuideIndexPageContent;
|
|
19
|
+
export interface IGuidePageContent {
|
|
20
|
+
content: string;
|
|
21
|
+
tags?: Record<string, string[]>;
|
|
22
|
+
}
|
|
23
|
+
export declare const loadPage: ({ jsonPath }: IGuideIndexPageContent) => IGuidePageContent;
|
|
24
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
3
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
4
|
+
if (!m) return o;
|
|
5
|
+
var i = m.call(o), r, ar = [], e;
|
|
6
|
+
try {
|
|
7
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
8
|
+
}
|
|
9
|
+
catch (error) { e = { error: error }; }
|
|
10
|
+
finally {
|
|
11
|
+
try {
|
|
12
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
13
|
+
}
|
|
14
|
+
finally { if (e) throw e.error; }
|
|
15
|
+
}
|
|
16
|
+
return ar;
|
|
17
|
+
};
|
|
18
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
19
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
20
|
+
if (ar || !(i in from)) {
|
|
21
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
22
|
+
ar[i] = from[i];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
26
|
+
};
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.loadPage = exports.findPage = exports.loadIndex = void 0;
|
|
29
|
+
var contentDir = "../content";
|
|
30
|
+
var loadIndex = function () {
|
|
31
|
+
return require("".concat(contentDir, "/index.json"));
|
|
32
|
+
};
|
|
33
|
+
exports.loadIndex = loadIndex;
|
|
34
|
+
var flatPages = function (_a) {
|
|
35
|
+
var content = _a.content, pages = _a.pages;
|
|
36
|
+
return __spreadArray([
|
|
37
|
+
content
|
|
38
|
+
], __read(Object.values(pages || {}).flatMap(function (values) { return flatPages(values); })), false).filter(Boolean);
|
|
39
|
+
};
|
|
40
|
+
var findPage = function (id) {
|
|
41
|
+
return flatPages((0, exports.loadIndex)()).find(function (page) { return page.id === id; });
|
|
42
|
+
};
|
|
43
|
+
exports.findPage = findPage;
|
|
44
|
+
var loadPage = function (_a) {
|
|
45
|
+
var jsonPath = _a.jsonPath;
|
|
46
|
+
return require("".concat(contentDir, "/").concat(jsonPath));
|
|
47
|
+
};
|
|
48
|
+
exports.loadPage = loadPage;
|
package/envs/.dev.env
ADDED
package/envs/.master.env
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hestia-earth/guide",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Hestia Guide pages",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"typings": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "node scripts/check-content.js",
|
|
9
|
+
"build": "rm -rf dist && tsc -p tsconfig.dist.json",
|
|
10
|
+
"build:www": "node scripts/build.js",
|
|
11
|
+
"build:content": "rimraf content && node scripts/build-content.js && node scripts/build-index.js",
|
|
12
|
+
"release": "standard-version -a",
|
|
13
|
+
"postrelease": "git push origin master --follow-tags"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+ssh://git@gitlab.com/hestia-earth/hestia-guide.git"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"hestia",
|
|
21
|
+
"guide",
|
|
22
|
+
"markdown"
|
|
23
|
+
],
|
|
24
|
+
"author": "Guillaume Royer <guillaume@hestia.earth>",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://gitlab.com/hestia-earth/hestia-guide/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://gitlab.com/hestia-earth/hestia-guide#readme",
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@commitlint/cli": "^19.5.0",
|
|
32
|
+
"@commitlint/config-conventional": "^19.5.0",
|
|
33
|
+
"git-date-extractor": "^4.0.1",
|
|
34
|
+
"husky": "^4.3.8",
|
|
35
|
+
"rimraf": "^6.0.1",
|
|
36
|
+
"standard-version": "^9.5.0"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"typescript": "^5.6.3"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const { writeFileSync } = require("fs");
|
|
2
|
+
const { join } = require("path");
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
mkdirs,
|
|
6
|
+
encoding,
|
|
7
|
+
srcDir,
|
|
8
|
+
listContent,
|
|
9
|
+
readContent,
|
|
10
|
+
removeContentTags,
|
|
11
|
+
getTags,
|
|
12
|
+
} = require("./utils");
|
|
13
|
+
|
|
14
|
+
const outputDir = join(__dirname, "..", "content");
|
|
15
|
+
|
|
16
|
+
mkdirs(outputDir);
|
|
17
|
+
|
|
18
|
+
const destFilename = (filename) =>
|
|
19
|
+
`${outputDir}/${filename.replace(srcDir, "").replace(/[\d]+_/g, "")}`;
|
|
20
|
+
|
|
21
|
+
const buildMarkdownContent = (filename) => {
|
|
22
|
+
const content = readContent(filename, true);
|
|
23
|
+
const dest = destFilename(filename);
|
|
24
|
+
mkdirs(dest);
|
|
25
|
+
writeFileSync(dest, content, encoding);
|
|
26
|
+
return filename;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const buildJSONContent = (filename) => {
|
|
30
|
+
const content = readContent(filename, false);
|
|
31
|
+
const tags = getTags(content);
|
|
32
|
+
const dest = destFilename(filename).replace(".md", ".json");
|
|
33
|
+
mkdirs(dest);
|
|
34
|
+
writeFileSync(
|
|
35
|
+
dest,
|
|
36
|
+
JSON.stringify(
|
|
37
|
+
{
|
|
38
|
+
tags,
|
|
39
|
+
content: removeContentTags(content),
|
|
40
|
+
},
|
|
41
|
+
null,
|
|
42
|
+
2
|
|
43
|
+
),
|
|
44
|
+
encoding
|
|
45
|
+
);
|
|
46
|
+
return filename;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const build = () => {
|
|
50
|
+
const files = listContent();
|
|
51
|
+
return files.map(buildMarkdownContent).map(buildJSONContent);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
build();
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const { writeFileSync, readdirSync } = require("fs");
|
|
2
|
+
const { join } = require("path");
|
|
3
|
+
const { getStamps } = require("git-date-extractor");
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
encoding,
|
|
7
|
+
ROOT,
|
|
8
|
+
srcDir,
|
|
9
|
+
mkdirs,
|
|
10
|
+
contentDir,
|
|
11
|
+
isDir,
|
|
12
|
+
listContent,
|
|
13
|
+
readContent,
|
|
14
|
+
contentUrl,
|
|
15
|
+
getTitle,
|
|
16
|
+
getTags,
|
|
17
|
+
} = require("./utils");
|
|
18
|
+
|
|
19
|
+
const outputDir = join(__dirname, "..", "content");
|
|
20
|
+
|
|
21
|
+
mkdirs(outputDir);
|
|
22
|
+
|
|
23
|
+
const fileData = (stamps, filename) => {
|
|
24
|
+
const { created, modified } = stamps[join(contentDir, filename)];
|
|
25
|
+
const content = readContent(filename);
|
|
26
|
+
const lines = content.split("\n").filter(Boolean);
|
|
27
|
+
const title = getTitle(lines[0]);
|
|
28
|
+
const tags = getTags(content);
|
|
29
|
+
const filepath = filename.replace(/^[/]/g, "").replace(/[\d]+_/g, "");
|
|
30
|
+
const id = filepath
|
|
31
|
+
.replace(".md", "")
|
|
32
|
+
.replace(/\//g, "-")
|
|
33
|
+
.replace("-content", "");
|
|
34
|
+
return {
|
|
35
|
+
id,
|
|
36
|
+
mdPath: filepath,
|
|
37
|
+
jsonPath: filepath.replace(".md", ".json"),
|
|
38
|
+
title,
|
|
39
|
+
createdAt: new Date(created * 1000).toJSON(),
|
|
40
|
+
updatedAt: new Date(modified * 1000).toJSON(),
|
|
41
|
+
tags,
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const folderData = (stamps, folder) => {
|
|
46
|
+
const pages = listContent(join(srcDir, folder)).filter(
|
|
47
|
+
(f) => !f.endsWith("0_content.md")
|
|
48
|
+
);
|
|
49
|
+
return {
|
|
50
|
+
content: fileData(stamps, join(folder, "0_content.md")),
|
|
51
|
+
...(pages.length
|
|
52
|
+
? {
|
|
53
|
+
pages: Object.fromEntries(
|
|
54
|
+
pages
|
|
55
|
+
.map((f) => f.replace(srcDir, ""))
|
|
56
|
+
.map((filename) => [
|
|
57
|
+
contentUrl(filename),
|
|
58
|
+
{ content: fileData(stamps, filename) },
|
|
59
|
+
])
|
|
60
|
+
),
|
|
61
|
+
}
|
|
62
|
+
: {}),
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const run = async () => {
|
|
67
|
+
const stamps = await getStamps({
|
|
68
|
+
projectRootPath: ROOT,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// keep the folder structure
|
|
72
|
+
const folders = readdirSync(srcDir).filter((f) => isDir(join(srcDir, f)));
|
|
73
|
+
const data = folders.map((folder) => [folder, folderData(stamps, folder)]);
|
|
74
|
+
const pages = Object.fromEntries(data);
|
|
75
|
+
const content = {
|
|
76
|
+
pages,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
writeFileSync(
|
|
80
|
+
join(outputDir, "index.json"),
|
|
81
|
+
JSON.stringify(content, null, 2),
|
|
82
|
+
encoding
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
run()
|
|
87
|
+
.then(() => process.exit(0))
|
|
88
|
+
.catch((err) => {
|
|
89
|
+
console.error(err);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
});
|
package/scripts/build.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const { existsSync, copyFileSync, unlinkSync, writeFileSync } = require("fs");
|
|
2
|
+
const { join } = require("path");
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
mkdirs,
|
|
6
|
+
encoding,
|
|
7
|
+
srcDir,
|
|
8
|
+
listContent,
|
|
9
|
+
readContent,
|
|
10
|
+
} = require("./utils");
|
|
11
|
+
|
|
12
|
+
const outputDir = join(__dirname, "..", "www");
|
|
13
|
+
|
|
14
|
+
mkdirs(outputDir);
|
|
15
|
+
|
|
16
|
+
const copyFile = (src, dest) => {
|
|
17
|
+
existsSync(dest) && unlinkSync(dest);
|
|
18
|
+
mkdirs(dest);
|
|
19
|
+
copyFileSync(src, dest);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const copyTemplates = (folder) => {
|
|
23
|
+
mkdirs(folder);
|
|
24
|
+
["template.css", "template.html", "template.js"].map((v) =>
|
|
25
|
+
copyFile(join(__dirname, v), join(folder, v.replace("template", "index")))
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const buildContent = (filename) => {
|
|
30
|
+
const destFolder = join(outputDir, filename.replace(".md", ""));
|
|
31
|
+
copyTemplates(destFolder);
|
|
32
|
+
|
|
33
|
+
const content = readContent(filename, true);
|
|
34
|
+
|
|
35
|
+
writeFileSync(join(destFolder, "content.md"), content, encoding);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const generateIndex = (files) => {
|
|
39
|
+
copyTemplates(outputDir);
|
|
40
|
+
const content = `
|
|
41
|
+
# Table of Contents
|
|
42
|
+
|
|
43
|
+
${files
|
|
44
|
+
.map((f) => f.replace(".md", ""))
|
|
45
|
+
.map((f) => `- [${f}](./${f})`)
|
|
46
|
+
.join("\n\n")}
|
|
47
|
+
`.trim();
|
|
48
|
+
writeFileSync(join(outputDir, "content.md"), content, encoding);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const build = () => {
|
|
52
|
+
const files = listContent().map((f) => f.replace(srcDir, ""));
|
|
53
|
+
generateIndex(files);
|
|
54
|
+
return files.map(buildContent);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
build();
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const {
|
|
2
|
+
listContent,
|
|
3
|
+
readContent,
|
|
4
|
+
isTag,
|
|
5
|
+
listAssets,
|
|
6
|
+
baseAssetRef,
|
|
7
|
+
} = require("./utils");
|
|
8
|
+
|
|
9
|
+
const allAssets = listAssets();
|
|
10
|
+
|
|
11
|
+
const checkFilename = (filename) =>
|
|
12
|
+
filename
|
|
13
|
+
.split("/")
|
|
14
|
+
.pop()
|
|
15
|
+
.replace(".md", "")
|
|
16
|
+
.match(/^[\d]_[a-z\d\-]+$/g);
|
|
17
|
+
|
|
18
|
+
const isValidLinks = (url) => !url.match(/\s/g) && !!new URL(url)?.host;
|
|
19
|
+
|
|
20
|
+
const getUrls = (content) =>
|
|
21
|
+
(content.match(/href="([^"]*)/g) || []).map((v) => v.replace('href="', ""));
|
|
22
|
+
|
|
23
|
+
const checkAssets = (content) => {
|
|
24
|
+
const urls = (content.match(/src="([^"]*)/g) || []).map((v) =>
|
|
25
|
+
v.replace(`src="${baseAssetRef}`, "")
|
|
26
|
+
);
|
|
27
|
+
const invalid = urls.filter((v) => !allAssets.includes(v));
|
|
28
|
+
if (invalid.length > 0) {
|
|
29
|
+
console.error(
|
|
30
|
+
"Invalid referenced assets",
|
|
31
|
+
invalid.map((v) => `${baseAssetRef}${v}`)
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return invalid.length === 0;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const checks = {
|
|
38
|
+
'File should start with "# "': (content) => content.startsWith("# "),
|
|
39
|
+
"Links must be valid": (content) => getUrls(content).every(isValidLinks),
|
|
40
|
+
"Links must not point to staging website": (content) =>
|
|
41
|
+
[
|
|
42
|
+
"http://www-staging.hestia.earth",
|
|
43
|
+
"https://www-staging.hestia.earth",
|
|
44
|
+
].every((v) => !content.includes(v)),
|
|
45
|
+
'Images and videos must be included in the "src/assets" folder': checkAssets,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const checkContent = (filename) => {
|
|
49
|
+
const content = readContent(filename);
|
|
50
|
+
return [
|
|
51
|
+
checkFilename(filename)
|
|
52
|
+
? ""
|
|
53
|
+
: "File names must contain lowercase letters, numbers or dashes only, and start with a number followed by an underscore for sorting.",
|
|
54
|
+
...Object.entries(checks)
|
|
55
|
+
.filter(([key, value]) => !value(content))
|
|
56
|
+
.map(([key, value]) => key),
|
|
57
|
+
].filter(Boolean);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const run = () => {
|
|
61
|
+
const files = listContent();
|
|
62
|
+
const allErrors = files
|
|
63
|
+
.map((f) => ({
|
|
64
|
+
f,
|
|
65
|
+
errors: checkContent(f),
|
|
66
|
+
}))
|
|
67
|
+
.filter(({ errors }) => errors.length);
|
|
68
|
+
|
|
69
|
+
if (allErrors.length) {
|
|
70
|
+
allErrors.map(({ f, errors }) => {
|
|
71
|
+
console.error("Error in", f, ":");
|
|
72
|
+
errors.map((e) => console.error(`\t- ${e}`));
|
|
73
|
+
});
|
|
74
|
+
process.exit(1);
|
|
75
|
+
} else {
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
run();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Markdown Preview</title>
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
+
|
|
8
|
+
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.2.1/marked.min.js"></script>
|
|
9
|
+
|
|
10
|
+
<link rel="stylesheet" href="https://cdn.hestia.earth/prod/styles.css">
|
|
11
|
+
<link rel="stylesheet" href="index.css">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<noscript>Please enable JavaScript to continue using this application.</noscript>
|
|
15
|
+
|
|
16
|
+
<a href="../"><< Back to index</a>
|
|
17
|
+
|
|
18
|
+
<div id="main"></div>
|
|
19
|
+
|
|
20
|
+
<script type="text/javascript" src="index.js"></script>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
package/scripts/utils.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const {
|
|
2
|
+
readdirSync,
|
|
3
|
+
readFileSync,
|
|
4
|
+
lstatSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
} = require("fs");
|
|
8
|
+
const { join, resolve, parse } = require("path");
|
|
9
|
+
|
|
10
|
+
const encoding = "utf8";
|
|
11
|
+
const ROOT = resolve(join(__dirname, ".."));
|
|
12
|
+
const contentDir = join("src", "content");
|
|
13
|
+
const srcDir = join(ROOT, contentDir);
|
|
14
|
+
const assetDir = join(ROOT, "src", "assets");
|
|
15
|
+
|
|
16
|
+
const isDir = (path) => {
|
|
17
|
+
try {
|
|
18
|
+
return lstatSync(path).isDirectory();
|
|
19
|
+
} catch (_err) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const mkdirs = (folder) =>
|
|
25
|
+
!existsSync(parse(folder).dir) &&
|
|
26
|
+
mkdirSync(parse(folder).dir, { recursive: true });
|
|
27
|
+
|
|
28
|
+
const unique = (values) =>
|
|
29
|
+
values.some((v) => typeof v === "object")
|
|
30
|
+
? [...new Set(values.map((v) => JSON.stringify(v)))].map((v) =>
|
|
31
|
+
JSON.parse(v)
|
|
32
|
+
)
|
|
33
|
+
: [...new Set(values)];
|
|
34
|
+
|
|
35
|
+
const baseAssetRef = "/guide-content/assets";
|
|
36
|
+
const listAssets = (directory = assetDir) =>
|
|
37
|
+
readdirSync(directory)
|
|
38
|
+
.flatMap((path) =>
|
|
39
|
+
isDir(join(directory, path))
|
|
40
|
+
? listAssets(join(directory, path))
|
|
41
|
+
: !path.startsWith(".")
|
|
42
|
+
? join(directory, path)
|
|
43
|
+
: null
|
|
44
|
+
)
|
|
45
|
+
.filter(Boolean)
|
|
46
|
+
.map((v) => v.replace(assetDir, ""));
|
|
47
|
+
|
|
48
|
+
const listContent = (directory = srcDir) =>
|
|
49
|
+
readdirSync(directory)
|
|
50
|
+
.flatMap((path) =>
|
|
51
|
+
isDir(join(directory, path))
|
|
52
|
+
? listContent(join(directory, path))
|
|
53
|
+
: !path.startsWith(".")
|
|
54
|
+
? join(directory, path)
|
|
55
|
+
: null
|
|
56
|
+
)
|
|
57
|
+
.filter(Boolean)
|
|
58
|
+
.filter((f) => f.endsWith(".md"));
|
|
59
|
+
const contentPath = (filename) =>
|
|
60
|
+
filename.includes(srcDir) ? filename : join(srcDir, filename);
|
|
61
|
+
const contentUrl = (filename) =>
|
|
62
|
+
filename
|
|
63
|
+
.replace(/\//g, "-")
|
|
64
|
+
.substring(1)
|
|
65
|
+
.replace(/[\d]+_/g, "")
|
|
66
|
+
.replace(".md", "");
|
|
67
|
+
const readContent = (filename, removeTags = false) => {
|
|
68
|
+
const content = readFileSync(contentPath(filename), encoding).trim();
|
|
69
|
+
return removeTags ? removeContentTags(content) : content;
|
|
70
|
+
};
|
|
71
|
+
const getTitle = (value) => value.replace("# ", "");
|
|
72
|
+
|
|
73
|
+
const removeContentTags = (content) =>
|
|
74
|
+
content.replace(/\/[a-z]+\s\~(.*)\~/g, "");
|
|
75
|
+
const isTag =
|
|
76
|
+
(key = "tag") =>
|
|
77
|
+
(value) =>
|
|
78
|
+
value.startsWith(`/${key}`);
|
|
79
|
+
const getTag =
|
|
80
|
+
(key = "tag") =>
|
|
81
|
+
(value) =>
|
|
82
|
+
value.replace(`/${key} `, "").replace(/\~/g, "");
|
|
83
|
+
const getTags = (content) =>
|
|
84
|
+
content
|
|
85
|
+
.match(/\/([a-z]+)\s\~(.*)\~/g)
|
|
86
|
+
?.map((v) => ({
|
|
87
|
+
key: v.split(" ")[0].trim().replace("/", ""),
|
|
88
|
+
value: v.split(" ")[1].trim().replace(/\~/g, ""),
|
|
89
|
+
}))
|
|
90
|
+
.reduce(
|
|
91
|
+
(prev, { key, value }) => ({
|
|
92
|
+
...prev,
|
|
93
|
+
[key]: [...(prev[key] || []), value],
|
|
94
|
+
}),
|
|
95
|
+
{}
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
encoding,
|
|
100
|
+
ROOT,
|
|
101
|
+
contentDir,
|
|
102
|
+
srcDir,
|
|
103
|
+
isDir,
|
|
104
|
+
mkdirs,
|
|
105
|
+
unique,
|
|
106
|
+
listContent,
|
|
107
|
+
contentPath,
|
|
108
|
+
contentUrl,
|
|
109
|
+
readContent,
|
|
110
|
+
getTitle,
|
|
111
|
+
removeContentTags,
|
|
112
|
+
isTag,
|
|
113
|
+
getTag,
|
|
114
|
+
getTags,
|
|
115
|
+
listAssets,
|
|
116
|
+
baseAssetRef,
|
|
117
|
+
};
|
package/test.js
ADDED