diff --git a/README.zh-CN.md b/README.zh-CN.md
index cb954b0a..89e9f4ea 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -30,7 +30,7 @@ TypeScript 类型体操姿势合集
> 点击下方徽章查看题目内容
-




By Tags
+




By Tags
## 推荐读物
diff --git a/actions/issue-pr.js b/actions/issue-pr.js
index 23d3227c..2d8363b4 100644
--- a/actions/issue-pr.js
+++ b/actions/issue-pr.js
@@ -74,7 +74,7 @@ module.exports = async(github, context, core) => {
&& i.title.startsWith(`#${no} `),
)
- const dir = `questions/${no}-${slug(info.title)}`
+ const dir = `questions/${no}-${info.difficulty}-${slug(info.title.replace(/\./g, '-').replace(/<.*>/g, ''))}`
const userEmail = `${user.id}+${user.login}@users.noreply.github.com`
await PushCommit(github, {
@@ -107,7 +107,7 @@ module.exports = async(github, context, core) => {
head: `pulls/${no}`,
title: `#${no} - ${info.title}`,
body: `This is an auto-generated PR that auto reflect on #${no}, please go to #${no} for discussion or making changes.\n\nCloses #${no}`,
- labels: ['auto-generated']
+ labels: ['auto-generated'],
})
core.info('-----Pull Request-----')
diff --git a/questions/10-medium-tuple-to-union/README.md b/questions/10-medium-tuple-to-union/README.md
index 0e0134b3..5c6a63f4 100644
--- a/questions/10-medium-tuple-to-union/README.md
+++ b/questions/10-medium-tuple-to-union/README.md
@@ -10,4 +10,4 @@ type Arr = ['1', '2', '3']
const a: TupleToUnion // expected to be '1' | '2' | '3'
```
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/questions/11-easy-tuple-to-object/README.md b/questions/11-easy-tuple-to-object/README.md
index 8495f163..418fdb99 100644
--- a/questions/11-easy-tuple-to-object/README.md
+++ b/questions/11-easy-tuple-to-object/README.md
@@ -10,4 +10,4 @@ const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
const result: TupleToObject // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
```
-
+
diff --git a/questions/12-medium-chainable-options/README.md b/questions/12-medium-chainable-options/README.md
index 336c3ba1..304d9b46 100644
--- a/questions/12-medium-chainable-options/README.md
+++ b/questions/12-medium-chainable-options/README.md
@@ -29,4 +29,4 @@ You don't need to write any js/ts logic to handle the problem - just in type lev
You can assume that `key` only accept `string` and the `value` and be anything - just leave it as-is. Same `key` won't be passed twice.
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/questions/12-medium-chainable-options/README.zh-CN.md b/questions/12-medium-chainable-options/README.zh-CN.md
index d2430ca5..d05a98a8 100644
--- a/questions/12-medium-chainable-options/README.zh-CN.md
+++ b/questions/12-medium-chainable-options/README.zh-CN.md
@@ -29,4 +29,4 @@ interface Result {
你可以假设 `key` 只接受字符串而 `value` 接受任何类型,你只需要暴露它传递的类型而不需要进行任何处理。同样的 `key` 只会被使用一次。
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/questions/13-warm-hello-world/README.md b/questions/13-warm-hello-world/README.md
index 2773169b..a9f92109 100644
--- a/questions/13-warm-hello-world/README.md
+++ b/questions/13-warm-hello-world/README.md
@@ -16,4 +16,4 @@ type test = Expect>
Click the `Take the Challenge` button to start coding! Happy Hacking!
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/questions/13-warm-hello-world/README.zh-CN.md b/questions/13-warm-hello-world/README.zh-CN.md
index cc12a77f..082f25ad 100644
--- a/questions/13-warm-hello-world/README.zh-CN.md
+++ b/questions/13-warm-hello-world/README.zh-CN.md
@@ -18,4 +18,4 @@ type test = Expect>
点击上方的 `接受挑战` 开始编码!旅途愉快!
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/questions/14-easy-first/README.md b/questions/14-easy-first/README.md
index 7fe3cad2..d35ffb8d 100644
--- a/questions/14-easy-first/README.md
+++ b/questions/14-easy-first/README.md
@@ -13,4 +13,4 @@ type head1 = First // expected to be 'a'
type head2 = First // expected to be 3
```
-
\ No newline at end of file
+
Related Challenges
\ No newline at end of file
diff --git a/questions/15-medium-last/README.md b/questions/15-medium-last/README.md
index a260e851..859a00ef 100644
--- a/questions/15-medium-last/README.md
+++ b/questions/15-medium-last/README.md
@@ -14,4 +14,4 @@ type tail1 = Last // expected to be 'c'
type tail2 = Last // expected to be 1
```
-
\ No newline at end of file
+
Related Challenges
\ No newline at end of file
diff --git a/questions/16-medium-pop/README.md b/questions/16-medium-pop/README.md
index df0e074d..4687c77d 100644
--- a/questions/16-medium-pop/README.md
+++ b/questions/16-medium-pop/README.md
@@ -16,4 +16,4 @@ type re2 = Pop // expected to be [3, 2]
**Extra**: Similarly, can you implement `Shift`, `Push` and `Unshift` as well?
-
\ No newline at end of file
+
Related Challenges
\ No newline at end of file
diff --git a/questions/17-hard-currying-1/README.md b/questions/17-hard-currying-1/README.md
index 15248c5d..c32a2428 100644
--- a/questions/17-hard-currying-1/README.md
+++ b/questions/17-hard-currying-1/README.md
@@ -21,4 +21,4 @@ In this challenge, the curried function only accept one argument at a time. Once
**Extra**: Similarly, can you implement `Shift`, `Push` and `Unshift` as well?
-
\ No newline at end of file
+
Related Challenges
\ No newline at end of file
diff --git a/questions/18-easy-tuple-length/README.md b/questions/18-easy-tuple-length/README.md
index 884c2bae..9c632b95 100644
--- a/questions/18-easy-tuple-length/README.md
+++ b/questions/18-easy-tuple-length/README.md
@@ -12,4 +12,4 @@ type teslaLength = Length // expected 4
type spaceXLength = Length // expected 5
```
-
+
diff --git a/questions/2-medium-return-type/README.md b/questions/2-medium-return-type/README.md
index fd8ff2c4..d64d28b2 100644
--- a/questions/2-medium-return-type/README.md
+++ b/questions/2-medium-return-type/README.md
@@ -15,4 +15,4 @@ const fn = (v: boolean) => {
type a = MyReturnType // should be "1 | 2"
```
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/questions/2-medium-return-type/README.zh-CN.md b/questions/2-medium-return-type/README.zh-CN.md
index 744cdde0..736dde3a 100644
--- a/questions/2-medium-return-type/README.zh-CN.md
+++ b/questions/2-medium-return-type/README.zh-CN.md
@@ -15,4 +15,4 @@ const fn = (v: boolean) => {
type a = MyReturnType // 应推导出 "1 | 2"
```
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/questions/3-medium-omit/README.md b/questions/3-medium-omit/README.md
index 1c7f364b..b7028702 100644
--- a/questions/3-medium-omit/README.md
+++ b/questions/3-medium-omit/README.md
@@ -1,4 +1,4 @@
-Omit<T, K>

by Anthony Fu @antfu
+Omit<T, K>

by Anthony Fu @antfu
Implement the built-in `Omit` generic without using it.
@@ -13,12 +13,11 @@ interface Todo {
completed: boolean
}
-type TodoPreview = MyOmit
+type TodoPreview = MyOmit
const todo: TodoPreview = {
- title: 'Clean room',
completed: false,
}
```
-
\ No newline at end of file
+
Related Challenges
\ No newline at end of file
diff --git a/questions/3-medium-omit/README.zh-CN.md b/questions/3-medium-omit/README.zh-CN.md
new file mode 100644
index 00000000..a3869bc0
--- /dev/null
+++ b/questions/3-medium-omit/README.zh-CN.md
@@ -0,0 +1,23 @@
+实现 Omit<T, K>

by Anthony Fu @antfu
+
+不使用 `Omit` 实现 TypeScript 的 `Omit` 范型。
+
+`Omit` 会创建一个省略 `K` 中字段的 `T` 对象。
+
+例如:
+
+```ts
+interface Todo {
+ title: string
+ description: string
+ completed: boolean
+}
+
+type TodoPreview = MyOmit
+
+const todo: TodoPreview = {
+ completed: false,
+}
+```
+
+
相关挑战
\ No newline at end of file
diff --git a/questions/3-medium-omit/info.yml b/questions/3-medium-omit/info.yml
index 2673bd1d..5b1c3a34 100644
--- a/questions/3-medium-omit/info.yml
+++ b/questions/3-medium-omit/info.yml
@@ -5,4 +5,6 @@ author:
email: hi@antfu.me
github: antfu
-tags: union, built-in
\ No newline at end of file
+tags: union, built-in
+
+related: 4
\ No newline at end of file
diff --git a/questions/4-easy-pick/README.md b/questions/4-easy-pick/README.md
index e8e7b30b..27f75f15 100644
--- a/questions/4-easy-pick/README.md
+++ b/questions/4-easy-pick/README.md
@@ -21,4 +21,4 @@ const todo: TodoPreview = {
}
```
-
\ No newline at end of file
+
Related Challenges
\ No newline at end of file
diff --git a/questions/4-easy-pick/info.yml b/questions/4-easy-pick/info.yml
index 95f6c6ed..6c9a4701 100644
--- a/questions/4-easy-pick/info.yml
+++ b/questions/4-easy-pick/info.yml
@@ -5,4 +5,6 @@ author:
email: hi@antfu.me
github: antfu
-tags: union, built-in
\ No newline at end of file
+tags: union, built-in
+
+related: 3
\ No newline at end of file
diff --git a/questions/5-extreme-readonly-keys/README.md b/questions/5-extreme-readonly-keys/README.md
index f346a0d5..17adcc1b 100644
--- a/questions/5-extreme-readonly-keys/README.md
+++ b/questions/5-extreme-readonly-keys/README.md
@@ -14,4 +14,4 @@ interface Todo {
type Keys = GetReadonlyKeys // expected to be "title" | "description"
```
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/questions/5-extreme-readonly-keys/info.yml b/questions/5-extreme-readonly-keys/info.yml
index 39f97914..d02006a9 100644
--- a/questions/5-extreme-readonly-keys/info.yml
+++ b/questions/5-extreme-readonly-keys/info.yml
@@ -5,4 +5,4 @@ author:
email: hi@antfu.me
github: antfu
-tags: utils, object-keys
\ No newline at end of file
+tags: utils, object-keys
diff --git a/questions/6-hard-simple-vue/README.md b/questions/6-hard-simple-vue/README.md
index 0f52f3bb..1ad28603 100644
--- a/questions/6-hard-simple-vue/README.md
+++ b/questions/6-hard-simple-vue/README.md
@@ -1,16 +1,16 @@
Simple Vue

by Anthony Fu @antfu
-{WIP} Implement a simpiled version of a Vue-like typing support.
+Implement a simpiled version of a Vue-like typing support.
By providing a function name `SimpleVue` (similar to `Vue.extend` or `defineComponent`), it should properly infer the `this` type inside computed and methods.
In this challenge, we assume that SimpleVue take an Object with `data`, `computed` and `methods` fields as it's only argument,
-`data` is a simple function that returns a object that expose the the context `this`,
+- `data` is a simple function that returns a object that expose the the context `this`,
-`computed` is an Object of functions that take the context as `this`, doing some calculation and returns the result. The computed results should be exposed to the context as the plain return values instead of functions.
+- `computed` is an Object of functions that take the context as `this`, doing some calculation and returns the result. The computed results should be exposed to the context as the plain return values instead of functions.
-`methods` is an Object of functions that take the context as `this` as well. Methods can access the fields exposed by `data`, `computed` as well as other `methods`. The different between `computed` is that `methods` exposed as functions as-is.
+- `methods` is an Object of functions that take the context as `this` as well. Methods can access the fields exposed by `data`, `computed` as well as other `methods`. The different between `computed` is that `methods` exposed as functions as-is.
The type of `SimpleVue`'s return value can be arbitrary.
@@ -36,4 +36,4 @@ const instance = SimpleVue({
})
```
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/questions/7-easy-readonly/README.md b/questions/7-easy-readonly/README.md
index 243dd818..08a3c0ec 100644
--- a/questions/7-easy-readonly/README.md
+++ b/questions/7-easy-readonly/README.md
@@ -22,4 +22,4 @@ todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property
```
-
\ No newline at end of file
+
Related Challenges
\ No newline at end of file
diff --git a/questions/7-easy-readonly/info.yml b/questions/7-easy-readonly/info.yml
index 436d3a53..f1fd4555 100644
--- a/questions/7-easy-readonly/info.yml
+++ b/questions/7-easy-readonly/info.yml
@@ -5,4 +5,6 @@ author:
email: hi@antfu.me
github: antfu
-tags: built-in, readonly, object-keys
\ No newline at end of file
+tags: built-in, readonly, object-keys
+
+related: 8, 9
\ No newline at end of file
diff --git a/questions/8-medium-readonly-2/README.md b/questions/8-medium-readonly-2/README.md
index 948497e5..c548d656 100644
--- a/questions/8-medium-readonly-2/README.md
+++ b/questions/8-medium-readonly-2/README.md
@@ -24,4 +24,4 @@ todo.description = "barFoo" // Error: cannot reassign a readonly property
todo.completed = true // OK
```
-
\ No newline at end of file
+
Related Challenges
\ No newline at end of file
diff --git a/questions/8-medium-readonly-2/info.yml b/questions/8-medium-readonly-2/info.yml
index 850f78df..28b6eb59 100644
--- a/questions/8-medium-readonly-2/info.yml
+++ b/questions/8-medium-readonly-2/info.yml
@@ -5,4 +5,6 @@ author:
email: hi@antfu.me
github: antfu
-tags: readonly, object-keys
\ No newline at end of file
+tags: readonly, object-keys
+
+related: 7, 9
diff --git a/questions/9-medium-deep-readonly/README.md b/questions/9-medium-deep-readonly/README.md
index 91ca6531..516a8bcf 100644
--- a/questions/9-medium-deep-readonly/README.md
+++ b/questions/9-medium-deep-readonly/README.md
@@ -26,4 +26,4 @@ type Expected = {
const todo: DeepReadonly // should be same as `Expaceted`
```
-
\ No newline at end of file
+
Related Challenges
\ No newline at end of file
diff --git a/questions/9-medium-deep-readonly/info.yml b/questions/9-medium-deep-readonly/info.yml
index 7076a928..d41eaa4c 100644
--- a/questions/9-medium-deep-readonly/info.yml
+++ b/questions/9-medium-deep-readonly/info.yml
@@ -5,4 +5,6 @@ author:
email: hi@antfu.me
github: antfu
-tags: readonly, object-keys, deep
\ No newline at end of file
+tags: readonly, object-keys, deep
+
+related: 7, 8
diff --git a/scripts/loader.ts b/scripts/loader.ts
index f9d77721..34b3681e 100644
--- a/scripts/loader.ts
+++ b/scripts/loader.ts
@@ -36,15 +36,26 @@ export function readmeCleanUp(text: string) {
.replace(/[\s\S]*/, '')
.trim()
}
+
export function loadInfo(s: string): Partial | undefined {
const object = YAML.safeLoad(s) as any
if (!object)
return undefined
- if (object.tags)
- object.tags = (object.tags as string).split(',').map(i => i.trim()).filter(Boolean)
- else
- object.tags = undefined
+ const arrayKeys = ['tags', 'related']
+
+ for (const key of arrayKeys) {
+ if (object[key]) {
+ object[key] = (object[key] || '')
+ .toString()
+ .split(',')
+ .map((i: string) => i.trim())
+ .filter(Boolean)
+ }
+ else {
+ object[key] = undefined
+ }
+ }
return object
}
@@ -77,5 +88,6 @@ export async function loadQuizes(): Promise {
export function resolveInfo(quiz: Quiz, locale: string = defaultLocale) {
const info = Object.assign({}, quiz.info[defaultLocale], quiz.info[locale])
info.tags = quiz.info[locale]?.tags || quiz.info[defaultLocale]?.tags || []
- return info
+ info.related = quiz.info[locale]?.related || quiz.info[defaultLocale]?.related || []
+ return info as QuizMetaInfo
}
diff --git a/scripts/locales/en.json b/scripts/locales/en.json
index 6e978bbb..0fbeb8d9 100644
--- a/scripts/locales/en.json
+++ b/scripts/locales/en.json
@@ -16,5 +16,6 @@
"link.more-challenges": "More Challenges: ",
"link.share-solutions": "Share your solutions: ",
"link.view-on-github": "View on Github: ",
+ "readme.related-challenges": "Related Challenges",
"title.question": "Question"
}
diff --git a/scripts/locales/zh-CN.json b/scripts/locales/zh-CN.json
index 71722820..fc3a8999 100644
--- a/scripts/locales/zh-CN.json
+++ b/scripts/locales/zh-CN.json
@@ -10,10 +10,12 @@
"difficulty.warm": "热身",
"display": "简体中文",
"divider.code-start": "你的代码",
+ "divider.further-steps": "下一步",
"divider.test-cases": "测试用例",
"link.checkout-solutions": "查看解答:",
"link.more-challenges": "更多题目:",
"link.share-solutions": "分享你的解答:",
"link.view-on-github": "在 Github 上查看:",
+ "readme.related-challenges": "相关挑战",
"title.question": "题目"
}
diff --git a/scripts/readme.ts b/scripts/readme.ts
index bb30a511..ce4aae39 100644
--- a/scripts/readme.ts
+++ b/scripts/readme.ts
@@ -63,6 +63,14 @@ function quizToBadge(quiz: Quiz, locale: string) {
)
}
+function quizNoToBadges(ids: (string|number)[], quizes: Quiz[], locale: string) {
+ return ids
+ .map(i => quizes.find(q => q.no === Number(i)))
+ .filter(Boolean)
+ .map(i => quizToBadge(i!, locale))
+ .join(' ')
+}
+
function getAllTags(quizes: Quiz[], locale: string) {
const set = new Set()
for (const quiz of quizes) {
@@ -80,7 +88,7 @@ function getQuizesByTag(quizes: Quiz[], locale: string, tag: string) {
})
}
-async function insertInfoReadme(filepath: string, quiz: Quiz, locale: SupportedLocale) {
+async function insertInfoReadme(filepath: string, quiz: Quiz, locale: SupportedLocale, quizes: Quiz[]) {
if (!fs.existsSync(filepath))
return
let text = await fs.readFile(filepath, 'utf-8')
@@ -111,8 +119,9 @@ async function insertInfoReadme(filepath: string, quiz: Quiz, locale: SupportedL
/[\s\S]*/,
'
'
+ toBadgeLink(`../../${f('README', locale, 'md')}`, '', t(locale, 'badge.back'), 'grey')
+ + toBadgeLink(toAnswerShort(quiz.no, locale), '', t(locale, 'badge.share-your-solutions'), 'teal')
+ toBadgeLink(toSolutionsShort(quiz.no), '', t(locale, 'badge.checkout-solutions'), 'de5a77', '?logo=awesome-lists&logoColor=white')
- + toBadgeLink(toAnswerShort(quiz.no, locale), '', t(locale, 'badge.share-your-solutions'), 'green')
+ + (Array.isArray(info.related) && info.related.length ? `
${t(locale, 'readme.related-challenges')}
${quizNoToBadges(info.related, quizes, locale)}` : '')
+ '',
)
@@ -175,6 +184,7 @@ async function updateQuestionsREADME(quizes: Quiz[]) {
),
quiz,
locale,
+ quizes,
)
}
}
diff --git a/scripts/types.ts b/scripts/types.ts
index 7ae5fbf4..7a7cb6c4 100644
--- a/scripts/types.ts
+++ b/scripts/types.ts
@@ -11,6 +11,7 @@ export interface QuizMetaInfo {
original_issues: number[]
recommended_solutions: number[]
tags: string[]
+ related?: string[]
}
export type Difficulty = 'warm-up' | 'easy' | 'medium' | 'hard' | 'extreme' | 'pending'