diff --git a/README.md b/README.md index b13bda1..9ffe6f8 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,7 @@ Quick Reference 为开发人员分享快速参考备忘单(主要是方便自己),在看到 [Reference](https://github.com/Randy8080/reference) 快速参考备忘单,感觉非常简单,造轮子使命感突然来了,造个中文版本的,为了方便自己的技术栈查阅,立马撸起来 :)。 -如果您发现此处的备忘单不合适,您可以通过提交 PR 来修复它或提供更好的备忘清单,只针对【中文】用户。 - -以下是开源天使提供的一些备忘清单和快速参考 :)。 +如果您发现此处的备忘单不合适,您可以通过提交 PR 来修复它或提供更好的备忘清单,只针对【中文】用户。以下是开源天使提供的一些备忘清单和快速参考 :)。 ## 编程 @@ -14,14 +12,18 @@ Quick Reference [JavaScript](./docs/javascript.md) [JSON](./docs/json.md) [Markdown](./docs/markdown.md) + ## 工具包 [VSCode](./docs/vscode.md) +[Jest](./docs/jest.md) + ## Linux 命令 [Cron](./docs/cron.md) + ## License diff --git a/docs/javascript.md b/docs/javascript.md index 0309a24..3ae0067 100644 --- a/docs/javascript.md +++ b/docs/javascript.md @@ -75,7 +75,6 @@ var a; console.log(a); // => undefined ``` - ### 字符串 ```javascript @@ -95,7 +94,6 @@ console.log(single.length); 10 % 5 = 0 // 取模 Modulo ``` - ### 注释 ```javascript diff --git a/docs/jest.md b/docs/jest.md new file mode 100644 index 0000000..3c0655e --- /dev/null +++ b/docs/jest.md @@ -0,0 +1,681 @@ +Jest 备忘清单 +=== + +Jest 是一款优雅、简洁的 JavaScript 测试框架。 + +入门 +---- + +### 介绍 + +[Jest](https://jestjs.io/) 是一款优雅、简洁的 JavaScript 测试框架。 + +- 无需配置,大多数 JS 项目中即装即用,无需配置 +- 优秀接口,从 it 到 expect - Jest 将工具包整合在一处。文档齐全、不断维护,非常不错。 +- 隔离的,并行进行测试,发挥每一丝算力。 +- 快照, 轻松编写持续追踪大型对象的测试,并在测试旁或代码内显示实时快照。 +- 代码覆盖, 无需其他操作,您仅需添加 --coverage 参数来生成代码覆盖率报告。 + +### 测试结构 + + + +```js +describe('makePoniesPink', () => { + beforeAll(() => { + /* 在所有测试之前运行 */ + }) + afterAll(() => { + /* 在所有测试后运行 */ + }) + beforeEach(() => { + /* 在每次测试之前运行 */ + }) + afterEach(() => { + /* 每次测试后运行 */ + }) + test('make each pony pink', () => { + const actual = fn(['Alice', 'Bob', 'Eve']) + expect(actual).toEqual(['Pink Alice', 'Pink Bob', 'Pink Eve']) + }) +}) +``` + +匹配器 +---- + +### 基本匹配器 + +```js +expect(42).toBe(42) // 严格相等 (===) +expect(42).not.toBe(3) // 严格相等 (!==) +expect([1, 2]).toEqual([1, 2]) // 深度相等 + +// 深度相等 +expect({ a: undefined, b: 2 }) + .toEqual({ b: 2 }) + +// 严格相等 (Jest 23+) +expect({ a: undefined, b: 2 }) + .not.toStrictEqual({ b: 2 }) +``` + +[Using matchers](http://jestjs.io/docs/en/using-matchers), [matchers docs](https://jestjs.io/docs/en/expect) + +### 真实性 + +```js +// 匹配 if 语句视为 true 的任何内容 +// (not false、0、''、null、undefined、NaN) +expect('foo').toBeTruthy() +// 匹配 if 语句视为 false 的任何内容 +// (false、0、''、null、undefined、NaN) +expect('').toBeFalsy() +// 仅匹配 null +expect(null).toBeNull() +// 仅匹配未定义 +expect(undefined).toBeUndefined() +// toBeUndefined 的反义词 +expect(7).toBeDefined() +// 匹配真假 +expect(true).toEqual(expect.any(Boolean)) +``` + +### 数字 + +```js +// 大于 +expect(2).toBeGreaterThan(1) +// 大于或等于 +expect(1).toBeGreaterThanOrEqual(1) +// 小于 +expect(1).toBeLessThan(2) +// 小于或等于 +expect(1).toBeLessThanOrEqual(1) +// 接近于 +expect(0.2 + 0.1).toBeCloseTo(0.3, 5) +// 原始值的传递类型 +expect(NaN).toEqual(expect.any(Number)) +``` + +### 字符串 + +```js +// 检查字符串是否与正则表达式匹配。 +expect('long string').toMatch('str') +expect('string').toEqual(expect.any(String)) +expect('coffee').toMatch(/ff/) +expect('pizza').not.toMatch('coffee') +expect(['pizza', 'coffee']).toEqual( + [ + expect.stringContaining('zz'), + expect.stringMatching(/ff/) + ] +) +``` + +### 数组 + +```js +expect([]).toEqual(expect.any(Array)) +const exampleArray = [ + 'Alice', 'Bob', 'Eve' +] +expect(exampleArray).toHaveLength(3) +expect(exampleArray).toContain('Alice') +expect(exampleArray).toEqual( + expect.arrayContaining(['Alice', 'Bob']) +) +expect([{ a: 1 }, { a: 2 }]) + .toContainEqual({ a: 1 }) // 包含相等 +``` + +### 对象 + +```js +expect({ a:1 }).toHaveProperty('a') +expect({ a:1 }).toHaveProperty('a', 1) +expect({ a: {b:1} }).toHaveProperty('a.b') +expect({ a:1, b:2 }).toMatchObject({a:1}) +expect({ a:1, b:2 }).toMatchObject({ + a: expect.any(Number), + b: expect.any(Number), +}) +expect([{ a: 1 }, { b: 2 }]).toEqual([ + expect.objectContaining( + { a: expect.any(Number) } + ), + expect.anything(), +]) +``` + +### 模拟函数 + + + +```js +// const fn = jest.fn() +// const fn = jest.fn().mockName('Unicorn') -- 命名为 mock, Jest 22+ +// 函数被调用 +expect(fn).toBeCalled() +// 函数*未*调用 +expect(fn).not.toBeCalled() +// 函数只被调用一次 +expect(fn).toHaveBeenCalledTimes(1) +// 任何执行都带有这些参数 +expect(fn).toBeCalledWith(arg1, arg2) +// 最后一个执行是用这些参数 +expect(fn).toHaveBeenLastCalledWith(arg1, arg2) +// 第 N 个执行带有这些参数(Jest 23+) +expect(fn).toHaveBeenNthCalledWith(callNumber, args) +// 函数返回没有抛出错误(Jest 23+) +expect(fn).toHaveReturnedTimes(2) +// 函数返回一个值(Jest 23+) +expect(fn).toHaveReturnedWith(value) +// 最后一个函数调用返回一个值(Jest 23+) +expect(fn).toHaveLastReturnedWith(value) +// 第 N 个函数调用返回一个值(Jest 23+) +expect(fn).toHaveNthReturnedWith(value) +expect(fn.mock.calls).toEqual([ + ['first', 'call', 'args'], + ['second', 'call', 'args'], +]) // 多次调用 +// fn.mock.calls[0][0] — 第一次调用的第一个参数 +expect(fn.mock.calls[0][0]).toBe(2) +``` + +#### 别名 + +- `toBeCalled` → `toHaveBeenCalled` +- `toBeCalledWith` → `toHaveBeenCalledWith` +- `lastCalledWith` → `toHaveBeenLastCalledWith` +- `nthCalledWith` → `toHaveBeenNthCalledWith` +- `toReturnTimes` → `toHaveReturnedTimes` +- `toReturnWith` → `toHaveReturnedWith` +- `lastReturnedWith` → `toHaveLastReturnedWith` +- `nthReturnedWith` → `toHaveNthReturnedWith` + +### 杂项 + +```js +// 检查对象是否是类的实例。 +expect(new A()).toBeInstanceOf(A) + +// 检查对象是否是函数的实例。 +expect(() => {}).toEqual( + expect.any(Function) +) + +// 匹配除 null 或 undefined 之外的任何内容 +expect('pizza').toEqual(expect.anything()) +``` + +### 快照 + +```js +// 这可确保某个值与最近的快照匹配。 +expect(node).toMatchSnapshot() + +// Jest 23+ +expect(user).toMatchSnapshot({ + date: expect.any(Date), +}) + +// 确保值与最近的快照匹配。 +expect(user).toMatchInlineSnapshot() +``` + +### Promise 匹配器(Jest 20+) + +```js +test('resolve to lemon', () => { + // 验证在测试期间是否调用了一定数量的断言。 + expect.assertions(1) + // 确保添加return语句 + return expect(Promise.resolve('lemon')) + .resolves.toBe('lemon') + + return expect(Promise.reject('octopus')) + .rejects.toBeDefined() + + return expect(Promise.reject( + Error('pizza') + )).rejects.toThrow() +}) +``` + +或者使用 async/await: + +```js +test('resolve to lemon', async () => { + expect.assertions(2) + await expect(Promise.resolve('lemon')) + .resolves.toBe('lemon') + + await expect(Promise.resolve('lemon')) + .resolves.not.toBe('octopus') +}) +``` + +[resolves 文档](https://jestjs.io/docs/en/expect#resolves) + +### 例外 + +```js +// const fn = () => { +// throw new Error('Out of cheese!') +// } + +expect(fn).toThrow() +expect(fn).toThrow('Out of cheese') + +// 测试错误消息在某处说“cheese”:这些是等价的 +expect(fn).toThrowError(/cheese/); +expect(fn).toThrowError('cheese'); + +// 测试准确的错误信息 +expect(fn).toThrowError( + /^Out of cheese!$/ +); +expect(fn).toThrowError( + new Error('Out of cheese!') +); + +// 测试函数在调用时是否抛出与最新快照匹配的错误。 +expect(fn).toThrowErrorMatchingSnapshot() +``` + +#### 别名 + +- `toThrowError` → `toThrow` + +异步测试 +---- + +### 实例 + + + +请参阅 Jest 文档中的 [更多示例](https://jestjs.io/docs/en/tutorial-async)。 + +在异步测试中指定一些预期的断言是一个很好的做法,所以如果你的断言根本没有被调用,测试将会失败。 + +```js +test('async test', () => { + // 在测试期间恰好调用了三个断言 + expect.assertions(3) + // 或者 - 在测试期间至少调用一个断言 + expect.hasAssertions() + // 你的异步测试 +}) +``` + +请注意,您也可以在任何 `describe` 和 `test` 之外对每个文件执行此操作: + +```js +beforeEach(expect.hasAssertions) +``` + +这将验证每个测试用例至少存在一个断言。 它还可以与更具体的 `expect.assertions(3)` 声明配合使用。 + +### async/await + +```js +test('async test', async () => { + expect.assertions(1) + + const result = await runAsyncOperation() + expect(result).toBe(true) +}) +``` + +### done() 回调 + + + +```js +test('async test', (done) => { + expect.assertions(1) + + runAsyncOperation() + + setTimeout(() => { + try { + const result = getAsyncOperationResult() + expect(result).toBe(true) + done() + } catch (err) { + done.fail(err) + } + }) +}) +``` + +将断言包装在 try/catch 块中,否则 Jest 将忽略失败 + +### Promises + +```js +test('async test', () => { + expect.assertions(1) + + return runAsyncOperation().then((result) => { + expect(result).toBe(true) + }) +}) +``` + +从你的测试中 _返回_ 一个 Promise + + +## 模拟 + +### 模拟函数 + + + +```js +test('call the callback', () => { + const callback = jest.fn() + fn(callback) + expect(callback).toBeCalled() + expect(callback.mock.calls[0][1].baz).toBe('pizza') // 第一次调用的第二个参数 + // 匹配第一个和最后一个参数,但忽略第二个参数 + expect(callback).toHaveBeenLastCalledWith('meal', expect.anything(), 'margarita') +}) +``` + +您还可以使用快照: + +```js +test('call the callback', () => { + // mockName 在 Jest 22+ 中可用 + const callback = jest.fn().mockName('Unicorn') + fn(callback) + expect(callback).toMatchSnapshot() + // -> + // [MockFunction Unicorn] { + // "calls": Array [ + // ... +}) +``` + +并将实现传递给 `jest.fn` 函数: + +```js +const callback = jest.fn(() => true) +``` + +[模拟函数文档](https://jestjs.io/docs/en/mock-function-api) + +### 返回、解析和拒绝值 + + +您的模拟可以返回值: + +```js +const callback + = jest.fn().mockReturnValue(true) +const callbackOnce + = jest.fn().mockReturnValueOnce(true) +``` + +或解析值: + +```js +const promise + = jest.fn().mockResolvedValue(true) +const promiseOnce + = jest.fn().mockResolvedValueOnce(true) +``` + +他们甚至可以拒绝值: + +```js +const failedPromise + = jest.fn().mockRejectedValue('Error') +const failedPromiseOnce + = jest.fn().mockRejectedValueOnce('Error') +``` + +你甚至可以结合这些: + +```js +const callback + = jest.fn().mockReturnValueOnce(false).mockReturnValue(true) +// -> +// call 1: false +// call 2+: true +``` + +### 使用 `jest.mock` 方法模拟模块 + +```js +jest.mock('lodash/memoize', () => (a) => a) // The original lodash/memoize should exist +jest.mock('lodash/memoize', () => (a) => a, { virtual: true }) // The original lodash/memoize isn’t required +``` + +[jest.mock docs](https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options) + +> 注意:当使用 `babel-jest` 时,对 `jest.mock` 的调用将自动提升到代码块的顶部。 如果您想明确避免这种行为,请使用 `jest.doMock`。 + +### 使用模拟文件模拟模块 + +创建一个类似 `__mocks__/lodash/memoize.js` 的文件: + +```js +module.exports = (a) => a +``` + +添加到您的测试中: + +```js +jest.mock('lodash/memoize') +``` + +注意:当使用 `babel-jest` 时,对 `jest.mock` 的调用将自动提升到代码块的顶部。 如果您想明确避免这种行为,请使用 `jest.doMock`。 + +[手动模拟文档](https://jestjs.io/docs/en/manual-mocks) + +### 模拟对象方法 + +```js +const spy = jest.spyOn(console, 'log').mockImplementation(() => {}) +expect(console.log.mock.calls).toEqual([['dope'], ['nope']]) +spy.mockRestore() +``` + +```js +const spy = jest.spyOn(ajax, 'request').mockImplementation(() => Promise.resolve({ success: true })) +expect(spy).toHaveBeenCalled() +spy.mockRestore() +``` + +### 模拟 getter 和 setter (Jest 22.1.0+) + +```js +const location = {} +const getTitle = jest + .spyOn(location, 'title', 'get') + .mockImplementation(() => 'pizza') +const setTitle = jest + .spyOn(location, 'title', 'set') + .mockImplementation(() => {}) +``` + +### 定时器模拟 + + + +为使用本机计时器函数(`setTimeout`、`setInterval`、`clearTimeout`、`clearInterval`)的代码编写同步测试。 + +```js +// 启用假计时器 +jest.useFakeTimers() +test('kill the time', () => { + const callback = jest.fn() + // 运行一些使用 setTimeout 或 setInterval 的代码 + const actual = someFunctionThatUseTimers(callback) + // 快进直到所有定时器都执行完毕 + jest.runAllTimers() + // 同步检查结果 + expect(callback).toHaveBeenCalledTimes(1) +}) +``` + +或者使用 [advanceTimersByTime()](https://jestjs.io/docs/en/timer-mocks#advance-timers-by-time) 按时间调整计时器: + +```js +// 启用假计时器 +jest.useFakeTimers() +test('kill the time', () => { + const callback = jest.fn() + // 运行一些使用 setTimeout 或 setInterval 的代码 + const actual = someFunctionThatUseTimers(callback) + // 快进 250 毫秒 + jest.advanceTimersByTime(250) + // 同步检查结果 + expect(callback).toHaveBeenCalledTimes(1) +}) +``` + +对于特殊情况,请使用 [jest.runOnlyPendingTimers()](https://jestjs.io/docs/en/timer-mocks#run-pending-timers)。 + +**注意:** 您应该在测试用例中调用 `jest.useFakeTimers()` 以使用其他假计时器方法。 + +### 模拟 getters 和 setters + +```js +const getTitle = jest.fn(() => 'pizza') +const setTitle = jest.fn() +const location = {} +Object.defineProperty(location, 'title', { + get: getTitle, + set: setTitle, +}) +``` + +### 清除和恢复模拟 + + + +对于一个模拟 + +```js +// 清除模拟使用日期 +// (fn.mock.calls、fn.mock.instances) +fn.mockClear() +// 清除并删除任何模拟的返回值或实现 +fn.mockReset() +// 重置并恢复初始实现 +fn.mockRestore() +``` + +> 注意:`mockRestore` 仅适用于由`jest.spyOn` 创建的模拟。对于所有模拟: + +```js +// 清除所有 mock 的 +// mock.calls、mock.instances、 +// mock.contexts 和 mock.results 属性。 +jest.clearAllMocks() +// 重置所有模拟的状态。 +jest.resetAllMocks() +// 将所有模拟恢复到其原始值。 +jest.restoreAllMocks() +``` + +### 使用模拟时访问原始模块 + +```js +jest.mock('fs') +// 模拟模块 +const fs = require('fs') +// 原始模块 +const fs = require.requireActual('fs') +``` + + +数据驱动测试(Jest 23+) +---- + +### 使用不同的数据运行相同的测试 + +```js +test.each([ + [1, 1, 2], + [1, 2, 3], + [2, 1, 3], +])('.add(%s, %s)', (a, b, expected) => { + expect(a + b).toBe(expected) +}) +``` + +### 使用模板文字相同 + +```js +test.each` + a | b | expected + ${1} | ${1} | ${2} + ${1} | ${2} | ${3} + ${2} | ${1} | ${3} +`('returns $expected when $a is added $b', ({ a, b, expected }) => { + expect(a + b).toBe(expected) +}) +``` + +### 或在“describe”级别 + +```js +describe.each([ + ['mobile'], ['tablet'], ['desktop'] +])('checkout flow on %s', (viewport) => { + test('displays success page', () => { + // + }) +}) +``` + +[describe.each() 文档](https://jestjs.io/docs/en/api#describeeachtablename-fn-timeout)、[test.each() 文档](https://jestjs.io/docs/en/api#testeachtablename-fn-timeout), + +跳过测试 +---- + + +### 不要运行这些测试 + +```js +describe.skip('makePoniesPink'... +tests.skip('make each pony pink'... +``` + +### 仅运行以下测试 + +```js +describe.only('makePoniesPink'... +tests.only('make each pony pink'... +``` + +测试有副作用的模块 +---- + + +### 实例 + +```js +const modulePath = '../module-to-test' +afterEach(() => { + jest.resetModules() +}) +test('第一次测试', () => { + // 准备第一次测试的条件 + const result = require(modulePath) + expect(result).toMatchSnapshot() +}) +test('第二个文本', () => { + // 准备第二次测试的条件 + const fn = () => require(modulePath) + expect(fn).toThrow() +}) +``` + +Node.js 和 Jest 会缓存你需要的模块。 要测试具有副作用的模块,您需要在测试之间重置模块注册表 \ No newline at end of file diff --git a/docs/json.md b/docs/json.md index 801e567..fd713ab 100644 --- a/docs/json.md +++ b/docs/json.md @@ -1,4 +1,4 @@ -JSON +JSON 备忘清单 === 这是理解和编写 JSON 格式配置文件的快速参考备忘单。 diff --git a/package.json b/package.json index 344766a..c3fa79f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "为开发人员分享快速参考备忘单(主要是方便自己)。", "private": false, "scripts": { - "build": "node scripts/build.mjs" + "build": "node scripts/build.mjs", + "start": "node scripts/build.mjs" }, "repository": { "type": "git", diff --git a/scripts/create.mjs b/scripts/create.mjs index 7410191..29ba376 100644 --- a/scripts/create.mjs +++ b/scripts/create.mjs @@ -4,6 +4,7 @@ import rehypeFormat from 'rehype-format'; import { rehypeUrls } from './nodes/rehypeUrls.mjs'; import { htmlTagAddAttri } from './nodes/htmlTagAddAttri.mjs'; import { footer } from './nodes/footer.mjs'; +import { header } from './nodes/header.mjs'; /** 标记 Number */ function panelAddNumber(arr = [], result = []) { @@ -113,25 +114,30 @@ export function getTocsTree(arr = [], result = []) { } export function create(str = '', options = {}) { + let title = str.match(/[^===]+(?=[===])/g); + let description = str.match(/\n==={1,}\n+([\s\S]*?)\n/g); + title = title[0] || ''; + description = (description[0] || '').replace(/^\n[=\n]+/, '').replace(/\[([\s\S]*?)?\]\(([\s\S]*?)?\)/g, '$1').replace(/\n/, ''); const mdOptions = { hastNode: false, remarkPlugins: [], rehypePlugins: [ rehypeFormat, [rehypeDocument, { - title: "Quick Reference", + title: `${title ? `${title} & ` : ''} Quick Reference`, css: [ ...options.css ], meta: [ - { description: '为开发人员分享快速参考备忘单。' }, + { description: `${description}为开发人员分享快速参考备忘单。` }, { keywords: 'Quick,Reference' } ] }], ], rewrite: (node, index, parent) => { - htmlTagAddAttri(node); + htmlTagAddAttri(node, options); rehypeUrls(node); if (node.type === 'element' && node.tagName === 'body') { node.children = getTocsTree([ ...node.children ]); + node.children.unshift(header(options)); node.children.push(footer()); } } diff --git a/scripts/index.mjs b/scripts/index.mjs index eb9da08..39be7a6 100644 --- a/scripts/index.mjs +++ b/scripts/index.mjs @@ -15,13 +15,18 @@ async function createHTML(files = [], num = 0) { return; } ++num; + const githubURL = `https://github.com/jaywcjlove/reference/blob/main/${path.relative(process.cwd(), dataFile.path).replace(path.sep, '/')}`; const mdstr = await fs.readFile(dataFile.path); const htmlPath = path.relative(DOCS, dataFile.path); const outputHTMLPath = path.resolve(OUTOUT, 'docs', htmlPath).replace(/README.md$/i, 'index.html').replace(/.md$/, '.html'); await fs.ensureDir(path.dirname(outputHTMLPath)); + const html = create(mdstr.toString(), { + isHome: /README.md$/.test(path.relative(process.cwd(), dataFile.path)), + githubURL, + homePath: path.relative(path.dirname(outputHTMLPath), path.resolve(OUTOUT, 'index.html')), css: [path.relative(path.dirname(outputHTMLPath), CSS_OUTPUT_PATH)] }); await fs.writeFile(outputHTMLPath, html); diff --git a/scripts/nodes/header.mjs b/scripts/nodes/header.mjs new file mode 100644 index 0000000..b3fe05a --- /dev/null +++ b/scripts/nodes/header.mjs @@ -0,0 +1,82 @@ +import { logo, github, editor } from './logo.mjs'; + +export function header({ homePath, githubURL = '' }) { + const data = [ + { + href: githubURL, + target: '__blank', + label: '编辑', + children: [editor] + }, + { + href: 'https://github.com/jaywcjlove/reference', + target: '__blank', + children: [github] + } + ] + return { + type: 'element', + tagName: 'nav', + properties: { + class: 'header-nav', + }, + children: [ + { + type: 'element', + tagName: 'div', + properties: { + class: ['max-container'], + }, + children: [ + { + type: 'element', + tagName: 'a', + properties: { + href: homePath, + class: ['logo'], + }, + children: logo, + }, + { + type: 'element', + tagName: 'div', + properties: { + class: ['menu'], + }, + children: data.map(({ href, label, children = [], ...props }) => { + const childs = { + type: 'element', + tagName: 'a', + properties: { href, class: [], ...props }, + children: [ + ...children, + { + type: 'element', + tagName: 'span', + properties: {}, + children: label ? [ + { type: 'text', value: label } + ] : [] + } + ] + } + if (label) { + childs.children = [...children, { + type: 'element', + tagName: 'span', + properties: {}, + children: [ + { type: 'text', value: label } + ] + }]; + } else { + childs.children = children; + } + return childs + }), + }, + ], + } + ], + }; +} \ No newline at end of file diff --git a/scripts/nodes/htmlTagAddAttri.mjs b/scripts/nodes/htmlTagAddAttri.mjs index d58c4ea..691bb2d 100644 --- a/scripts/nodes/htmlTagAddAttri.mjs +++ b/scripts/nodes/htmlTagAddAttri.mjs @@ -1,5 +1,8 @@ -export function htmlTagAddAttri(node) { +export function htmlTagAddAttri(node, { isHome }) { if (node && node.tagName === 'html') { node.properties['data-color-mode'] = 'dark'; } + if (node && node.tagName === 'body' && isHome) { + node.properties.class = ['home']; + } } \ No newline at end of file diff --git a/scripts/nodes/logo.mjs b/scripts/nodes/logo.mjs new file mode 100644 index 0000000..40c3b13 --- /dev/null +++ b/scripts/nodes/logo.mjs @@ -0,0 +1,99 @@ +export const logo = [ + { + type: 'element', + tagName: 'svg', + properties: { + viewBox: "0 0 24 24", + fill: "none", + xmlns: "http://www.w3.org/2000/svg", + height: "1em", + width: "1em" + }, + children: [ + { + type: 'element', + tagName: 'path', + properties: { + opacity: ".5", + d: "m21.66 10.44-.98 4.18c-.84 3.61-2.5 5.07-5.62 4.77-.5-.04-1.04-.13-1.62-.27l-1.68-.4c-4.17-.99-5.46-3.05-4.48-7.23l.98-4.19c.2-.85.44-1.59.74-2.2 1.17-2.42 3.16-3.07 6.5-2.28l1.67.39c4.19.98 5.47 3.05 4.49 7.23Z", + fill: "white" + }, + }, + { + type: 'element', + tagName: 'path', + properties: { + d: "M15.06 19.39c-.62.42-1.4.77-2.35 1.08l-1.58.52c-3.97 1.28-6.06.21-7.35-3.76L2.5 13.28c-1.28-3.97-.22-6.07 3.75-7.35l1.58-.52c.41-.13.8-.24 1.17-.31-.3.61-.54 1.35-.74 2.2l-.98 4.19c-.98 4.18.31 6.24 4.48 7.23l1.68.4c.58.14 1.12.23 1.62.27Zm2.43-8.88c-.06 0-.12-.01-.19-.02l-4.85-1.23a.75.75 0 0 1 .37-1.45l4.85 1.23a.748.748 0 0 1-.18 1.47Z" , + fill: "#cbd5e1" + }, + }, + { + type: 'element', + tagName: 'path', + properties: { + d: "M14.56 13.89c-.06 0-.12-.01-.19-.02l-2.91-.74a.75.75 0 0 1 .37-1.45l2.91.74c.4.1.64.51.54.91-.08.34-.38.56-.72.56Z", + fill: "#292D32" + }, + } + ], + }, + { + type: 'element', + tagName: 'span', + properties: { + class: ['title'], + }, + children: [ + { type: 'text', value: 'Quick Reference' } + ] + } +]; + +export const github = { + type: 'element', + tagName: 'svg', + properties: { + viewBox: "0 0 16 16", + fill: "currentColor", + height: "1em", + width: "1em" + }, + children: [ + { + type: 'element', + tagName: 'path', + properties: { + d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z", + }, + }, + ], +} + + +export const editor = { + type: 'element', + tagName: 'svg', + properties: { + viewBox: "0 0 36 36", + fill: "currentColor", + height: "1em", + width: "1em" + }, + children: [ + { + type: 'element', + tagName: 'path', + properties: { + d: "m33 6.4-3.7-3.7a1.71 1.71 0 0 0-2.36 0L23.65 6H6a2 2 0 0 0-2 2v22a2 2 0 0 0 2 2h22a2 2 0 0 0 2-2V11.76l3-3a1.67 1.67 0 0 0 0-2.36ZM18.83 20.13l-4.19.93 1-4.15 9.55-9.57 3.23 3.23ZM29.5 9.43 26.27 6.2l1.85-1.85 3.23 3.23Z", + }, + }, + { + type: 'element', + tagName: 'path', + properties: { + fill: 'none', + d: "M0 0h36v36H0z", + }, + }, + ], +} diff --git a/scripts/nodes/rehypeUrls.mjs b/scripts/nodes/rehypeUrls.mjs index 1b540de..223d97c 100644 --- a/scripts/nodes/rehypeUrls.mjs +++ b/scripts/nodes/rehypeUrls.mjs @@ -1,5 +1,5 @@ export function rehypeUrls(node) { - if (node.type === 'element' && node.properties.href && /.md/.test(node.properties.href)) { + if (node.type === 'element' && node.properties.href && /.md/.test(node.properties.href) && !/^(https?:\/\/)/.test(node.properties.href)) { let href = node.properties.href; node.properties.href = href.replace(/([^\.\/\\]+)\.(md|markdown)/gi, '$1.html'); } diff --git a/scripts/style.css b/scripts/style.css index 1dc18b8..ba4b617 100644 --- a/scripts/style.css +++ b/scripts/style.css @@ -45,10 +45,115 @@ blockquote, dl, dd, h1, h2, h3, h4, h5, h6, hr, figure, p, pre { padding: 0.75rem; } +body.home .h1warp-body, body.home .h1warp .warp-body { + max-width: 940px; + margin-left: auto; + margin-right: auto; + padding: 0.75rem; +} +body.home .h2warp > h2 { + display: inline-block; + padding-right: 0.5rem; +} + +body.home .h2warp > h2::after { + content: ' '; + display: block; + height: 3px; + width: 110%; + margin-top: 0.5rem; + --bg-opacity: 1; + background-color: rgb(30 41 59/var(--bg-opacity)); +} +body.home .h1warp .warp-body p + p { + margin-top: 1.6rem; +} +body.home .h1warp .warp-body p { + text-indent: 2rem; +} + + +body.home .h1warp p { + text-align: left; +} + +.home-card { + display: grid; + gap: 2rem; + grid-template-columns: repeat(4,minmax(0,1fr)); +} +.home-card a { + display: flex; + align-items: center; + cursor: pointer; + border-radius: 0.5rem; + padding: 1rem; + color: rgb(30 41 59/1); + box-shadow: 0 0 #0000,0 0 #0000,0 1px 2px 0 rgba(0,0,0,0.05); + color: rgb(203 213 225/1); + --bg-opacity: 0.5; + background-color: rgb(62 69 72/var(--bg-opacity)); + transition: all .3s; +} +.home-card a:hover { + --bg-opacity: 1; +} + +.header-nav .max-container { + padding-top: 1.8rem; +} + +.header-nav .max-container, .header-nav .logo, .header-nav .menu, .header-nav .menu a { + display: flex; + justify-content: space-between; + align-items: center; +} + +.header-nav .logo { + gap: 0.5rem; + color: currentColor; +} + +.header-nav .title { + font-size: 1.8rem; + line-height: 2rem; + font-weight: bold; +} + +.header-nav .logo svg { + font-size: 40px; +} + +.header-nav .menu a:hover { + background-color: rgb(30 41 59/1); +} + +.header-nav .menu a { + padding-left: 0.75rem; + padding-right: 0.75rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + border-radius: 9999px; + transition: all .3s; +} + +.header-nav .menu { + gap: 0.2rem; +} +.header-nav .menu a > span { + font-size: 0.9rem; +} + +.header-nav a, .header-nav a:visited { + color: rgb(209 213 219/1); + line-height: 1.2; + gap: 0.3rem; +} + .warp-header.h1warp { text-align: center; - margin-top: 2rem; - margin-bottom: 2rem; + margin-top: 4.6rem; + margin-bottom: 5rem; } .warp-header.h1warp .warp-body { @@ -58,7 +163,7 @@ blockquote, dl, dd, h1, h2, h3, h4, h5, h6, hr, figure, p, pre { .warp-header.h1warp > h1 { font-size: 3rem; line-height: 1; - margin-bottom: 1rem; + margin-bottom: 3rem; } .h1warp-body { @@ -145,8 +250,6 @@ blockquote, dl, dd, h1, h2, h3, h4, h5, h6, hr, figure, p, pre { margin: 0px; border-top-width: 1px; - /* border-color: rgb(226 232 240/1); - background-color: rgb(241 245 249/1); */ padding-left: 1rem; padding-right: 1rem; padding-top: 0.25rem; @@ -174,21 +277,16 @@ ol, ul, menu { grid-template-columns: repeat(1,minmax(0,1fr)); } -.h4warp > .warp-body ul, -.h4warp > .warp-body ol, -.h4warp > .warp-body dl, - -.h3warp > .warp-body ul li, -.h3warp > .warp-body ol li, -.h3warp > .warp-body dl li { +.h2warp-body ul li, +.h2warp-body ol li, +.h2warp-body dl li { padding: 9px; padding-left: 26px; position: relative; border-bottom: solid 1px rgb(51 65 85/0.5); } -.h4warp>.warp-body ul:not(.style-none)>li::before, -.h3warp>.warp-body ul:not(.style-none)>li::before { +.h2warp-body ul:not(.style-none)>li::before { content: ''; position: absolute; display: inline-block; @@ -196,10 +294,24 @@ ol, ul, menu { height: 4px; background: #556677; border-radius: 50%; - left: 14px; + left: 13px; top: 18px; } +.h2warp-body ul li, +.h2warp-body ol li, +.h2warp-body dl li { + position: relative; +} + +.warp-body ul:last-child { + margin-bottom: 0; +} + +.warp-body ul:last-child li:last-child { + border-bottom: 0; +} + .h3warp hr { border-bottom: 1px solid #475060; } @@ -301,6 +413,9 @@ pre { overflow-x: auto; } +.cols-1 { + grid-template-columns: repeat(1,minmax(0,1fr)); +} .cols-2 { grid-template-columns: repeat(2,minmax(0,1fr)); }