Skip to content

Commit cbd5846

Browse files
authored
Merge pull request #21 from codewars/add-glossary
Add Glossary page
2 parents 8363af7 + d60c8ec commit cbd5846

File tree

7 files changed

+322
-3
lines changed

7 files changed

+322
-3
lines changed

data/glossary.yml

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# A collection of commonly used terms.
2+
#
3+
# Please add new terms in alphabetical order.
4+
5+
## Structure
6+
## ```typescript
7+
## type Term {
8+
## // Slugified word. Must be unique. Assigned to `slugify(term)` if unspecified.
9+
## id: string;
10+
## // The term in our glossary
11+
## term: string;
12+
## // Description of the term in Markdown. Converted to HTML during build.
13+
## description?: string;
14+
## // Optional acronym form.
15+
## acronym?: string;
16+
## // Optional path to a page in our docs. The title will be set automatically.
17+
## see?: string;
18+
## // List of external links
19+
## links?: {title: string; url: string}[];
20+
## }
21+
## ```
22+
23+
## Example
24+
## ```yaml
25+
## - term: Test Driven Development
26+
## description: A software development process ...
27+
## acronym: TDD
28+
## see: /path/to/a-page/
29+
## links:
30+
## - title: Test-driven Development (Wikipedia)
31+
## url: https://en.wikipedia.org/wiki/Test-driven_development
32+
## ```
33+
34+
- term: Dan
35+
description:
36+
links:
37+
- title: Dan (rank) - Wikipedia
38+
url: https://en.wikipedia.org/wiki/Dan_(rank)
39+
40+
- term: Kata
41+
description:
42+
links:
43+
- title: CodeKata by Dave Thomas
44+
url: http://codekata.com/kata/codekata-intro/
45+
- title: Kata (programming) - Wikipedia
46+
url: https://en.wikipedia.org/wiki/Kata_(programming)
47+
- title: Kata - Wikipedia
48+
url: https://en.wikipedia.org/wiki/Kata
49+
50+
- term: Kumite
51+
description: 組手
52+
links:
53+
- title: Kumite - Wikipedia
54+
url: https://en.wikipedia.org/wiki/Kumite
55+
56+
- term: Kyū
57+
description:
58+
links:
59+
- title: Kyū - Wikipedia
60+
url: https://en.wikipedia.org/wiki/Ky%C5%AB
61+
62+
- term: Test Driven Development
63+
description: |
64+
> **TODO** Explain. Include why TDD is relevant to Codewars.
65+
acronym: TDD
66+
links:
67+
- title: Test-driven Development - Wikipedia
68+
url: https://en.wikipedia.org/wiki/Test-driven_development

gridsome.server.js

+28-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ const path = require("path");
1010
const { promisify } = require("util");
1111
const readdir = promisify(fs.readdir);
1212
const readFile = promisify(fs.readFile);
13+
14+
const slugify = require("@sindresorhus/slugify");
1315
const YAML = require("yaml");
16+
// Using Marked for now because it's more simple to use. And because this is
17+
// done at build time it doesn't increase the bundle size.
18+
const marked = require("marked");
19+
const sanitize = require("sanitize-html");
20+
21+
const toHTML = (content) => sanitize(marked(content));
1422

1523
module.exports = function (api) {
1624
api.loadSource(({ addCollection, addMetadata }) => {
@@ -43,13 +51,13 @@ module.exports = function (api) {
4351
}
4452
});
4553

46-
const addNodesFromFile = async (collection, file) => {
54+
const addNodesFromFile = async (collection, file, fn = (x) => x) => {
4755
const contents = await readFile(file, { encoding: "utf-8" });
4856
const items = YAML.parse(contents);
49-
for (const item of items) collection.addNode(item);
57+
for (const item of items) collection.addNode(fn(item));
5058
};
5159

52-
api.loadSource(async ({ addCollection }) => {
60+
api.loadSource(async ({ addCollection, addSchemaTypes }) => {
5361
await addNodesFromFile(
5462
addCollection("Category"),
5563
path.join(__dirname, "data/categories.yml")
@@ -58,6 +66,23 @@ module.exports = function (api) {
5866
addCollection("Tag"),
5967
path.join(__dirname, "data/tags.yml")
6068
);
69+
70+
// Add Schema manually to prevent errors from missing fields.
71+
// See https://gridsome.org/docs/schema-api/
72+
const termSchema = await readFile(
73+
path.join(__dirname, "schemas/term.graphql"),
74+
{ encoding: "utf-8" }
75+
);
76+
addSchemaTypes(termSchema);
77+
await addNodesFromFile(
78+
addCollection("Term"),
79+
path.join(__dirname, "data/glossary.yml"),
80+
(term) => {
81+
if (!term.id) term.id = slugify(term.term);
82+
if (term.description) term.description = toHTML(term.description);
83+
return term;
84+
}
85+
);
6186
});
6287

6388
api.createPages(({ createPage }) => {

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,20 @@
1717
"@gridsome/remark-prismjs": "^0.3.0",
1818
"@gridsome/source-filesystem": "^0.6.2",
1919
"@gridsome/transformer-remark": "^0.5.0",
20+
"@sindresorhus/slugify": "^1.0.0",
2021
"babel-eslint": "^10.1.0",
2122
"eslint": "^7.3.1",
2223
"eslint-config-prettier": "^6.11.0",
2324
"eslint-plugin-prettier-vue": "^2.1.0",
2425
"eslint-plugin-vue": "^6.2.2",
2526
"husky": "^4.2.5",
2627
"lint-staged": "^10.2.11",
28+
"marked": "^1.1.0",
2729
"postcss-import": "^12.0.1",
2830
"postcss-nesting": "^7.0.1",
2931
"prettier": "^2.0.5",
3032
"prism-themes": "^1.3.0",
33+
"sanitize-html": "^1.27.0",
3134
"stylelint": "^13.6.1",
3235
"stylelint-config-prettier": "^8.0.2",
3336
"stylelint-config-standard": "^20.0.0",

schemas/term.graphql

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
type Term implements Node {
2+
id: ID!
3+
"""The term in our glossary"""
4+
term: String!
5+
"""The description of the term in HTML"""
6+
description: String
7+
"""Optional acronym form"""
8+
acronym: String
9+
"""Optional path to a page in our docs"""
10+
see: String
11+
"""Optional list of external links"""
12+
links: [Term_Links!]
13+
}
14+
15+
type Term_Links {
16+
"""External link title"""
17+
title: String!
18+
"""External link URL"""
19+
url: String!
20+
}

src/pages/Glossary.vue

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<template>
2+
<Layout>
3+
<div class="space-y-8">
4+
<h1 class="text-center text-4xl font-medium my-4">Glossary</h1>
5+
6+
<section v-for="group of groupedTerms" :key="group.id" class="space-y-8">
7+
<h2 class="text-center text-2xl mb-2" :id="`terms-${group.id}`">
8+
{{ group.id }}
9+
</h2>
10+
11+
<div
12+
v-for="term of group.terms"
13+
:key="term.id"
14+
:id="term.id"
15+
class="max-w-md mx-auto mt-4 space-y-4"
16+
>
17+
<div class="font-bold">
18+
{{ term.term }}
19+
<span v-if="term.acronym" class="ml-1">({{ term.acronym }})</span>
20+
</div>
21+
22+
<!-- eslint-disable-next-line vue/no-v-html -->
23+
<div
24+
v-if="term.description"
25+
class="content ml-2"
26+
v-html="term.description"
27+
/>
28+
29+
<div v-if="term.page">
30+
See
31+
<g-link :to="term.page.path" class="underline">
32+
{{ term.page.title }}
33+
</g-link>
34+
</div>
35+
36+
<div v-if="term.links && term.links.length > 0">
37+
<ul>
38+
<li
39+
v-for="link of term.links"
40+
:key="link.url"
41+
class="flex flex-row items-center"
42+
>
43+
<a
44+
:href="link.url"
45+
class="hover:underline"
46+
target="_blank"
47+
rel="noopener noreferrer"
48+
>
49+
{{ link.title }}
50+
</a>
51+
<ExternalLinkIcon size="1x" class="ml-1 text-sm" />
52+
</li>
53+
</ul>
54+
</div>
55+
</div>
56+
</section>
57+
</div>
58+
</Layout>
59+
</template>
60+
61+
<page-query>
62+
query {
63+
terms: allTerm(sortBy: "id", order: ASC) {
64+
edges {
65+
node {
66+
id
67+
term
68+
acronym
69+
description
70+
see
71+
links {
72+
title
73+
url
74+
}
75+
}
76+
}
77+
}
78+
79+
allMarkdownPage {
80+
edges {
81+
node {
82+
path
83+
title
84+
}
85+
}
86+
}
87+
}
88+
</page-query>
89+
90+
<script>
91+
import { ExternalLinkIcon } from "vue-feather-icons";
92+
93+
export default {
94+
components: {
95+
ExternalLinkIcon,
96+
},
97+
computed: {
98+
pages() {
99+
return this.$page.allMarkdownPage.edges.map((edge) => edge.node);
100+
},
101+
terms() {
102+
return this.$page.terms.edges.map((e) => {
103+
const term = e.node;
104+
if (term.see) term.page = this.pages.find((p) => p.path === term.see);
105+
return term;
106+
});
107+
},
108+
// Group by the first letter of the term.
109+
groupedTerms() {
110+
const hash = {};
111+
for (const term of this.terms) {
112+
const key = term.term[0].toUpperCase();
113+
if (!hash[key]) hash[key] = [];
114+
hash[key].push(term);
115+
}
116+
return Object.keys(hash)
117+
.sort()
118+
.reduce((gs, k) => (gs.push({ id: k, terms: hash[k] }), gs), []);
119+
},
120+
},
121+
metaInfo() {
122+
const title = "Glossary";
123+
const description = title;
124+
125+
return {
126+
title: title,
127+
meta: [
128+
{
129+
name: "description",
130+
content: description,
131+
},
132+
{
133+
key: "og:title",
134+
name: "og:title",
135+
content: title,
136+
},
137+
{
138+
key: "og:description",
139+
name: "og:description",
140+
content: description,
141+
},
142+
],
143+
};
144+
},
145+
};
146+
</script>

src/pages/Index.vue

+14
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,18 @@
6565
View Tags
6666
</g-link>
6767
</div>
68+
69+
<div
70+
class="flex flex-col items-center px-4 mb-8 text-center w-full md:w-1/3"
71+
>
72+
<BookIcon size="3x" class="mb-6 text-ui-primary" />
73+
<g-link
74+
to="/glossary/"
75+
class="text-xl font-bold leading-snug tracking-wide uppercase text-ui-typo"
76+
>
77+
View Glossary
78+
</g-link>
79+
</div>
6880
</div>
6981

7082
<!-- TODO Link to Zulip and also write how to use it -->
@@ -77,6 +89,7 @@
7789
import Logo from "@/components/Logo";
7890
import {
7991
ArrowRightCircleIcon,
92+
BookIcon,
8093
BookOpenIcon,
8194
FolderIcon,
8295
TagIcon,
@@ -86,6 +99,7 @@ export default {
8699
components: {
87100
Logo,
88101
ArrowRightCircleIcon,
102+
BookIcon,
89103
BookOpenIcon,
90104
FolderIcon,
91105
TagIcon,

0 commit comments

Comments
 (0)