mirror of
https://github.com/codex-team/codex.docs.git
synced 2025-08-08 06:55:26 +02:00
Merge branch 'main' into feature/add-sidebar-toggler
This commit is contained in:
commit
b0b42d958c
21 changed files with 879 additions and 1060 deletions
|
@ -1,2 +1,8 @@
|
||||||
# codex-docs password.
|
# codex-docs password.
|
||||||
PASSWORD=
|
PASSWORD=
|
||||||
|
|
||||||
|
# Backend errors tracking Integraton Token
|
||||||
|
HAWK_TOKEN_BACKEND=
|
||||||
|
|
||||||
|
# Client errors tracking Integraton Token
|
||||||
|
HAWK_TOKEN_CLIENT=
|
||||||
|
|
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
|
@ -17,10 +17,10 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
@ -28,18 +28,18 @@ jobs:
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v3
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=branch
|
type=ref,event=branch
|
||||||
type=ref,event=pr
|
type=ref,event=pr
|
||||||
type=raw,value={{branch}}-{{sha}}-{{date 'X'}}
|
type=raw,value={{branch}}-{{sha}}-{{date 'X'}},enable=${{ startsWith(github.ref, 'refs/heads') }}
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
|
||||||
- name: Build and push image
|
- name: Build and push image
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile.prod
|
file: docker/Dockerfile.prod
|
||||||
|
|
13
nodemon.json
Normal file
13
nodemon.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"verbose": true,
|
||||||
|
"ignore": [
|
||||||
|
".git",
|
||||||
|
"node_modules",
|
||||||
|
"public",
|
||||||
|
"src/frontend"
|
||||||
|
],
|
||||||
|
"watch": [
|
||||||
|
"**/*"
|
||||||
|
],
|
||||||
|
"ext": "js,twig"
|
||||||
|
}
|
119
package-lock.json
generated
119
package-lock.json
generated
|
@ -2013,6 +2013,20 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/set-array": "^1.0.1",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.9"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/resolve-uri": {
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz",
|
||||||
|
@ -2023,6 +2037,25 @@
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@jridgewell/set-array": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/source-map": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/gen-mapping": "^0.3.0",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.4.11",
|
"version": "1.4.11",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz",
|
||||||
|
@ -2031,11 +2064,10 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.4",
|
"version": "0.3.14",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
|
||||||
"integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==",
|
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.0.3",
|
"@jridgewell/resolve-uri": "^3.0.3",
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
|
@ -6692,10 +6724,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/got": {
|
"node_modules/got": {
|
||||||
"version": "11.8.3",
|
"version": "11.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/got/-/got-11.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz",
|
||||||
"integrity": "sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==",
|
"integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sindresorhus/is": "^4.0.0",
|
"@sindresorhus/is": "^4.0.0",
|
||||||
"@szmarczak/http-timer": "^4.0.5",
|
"@szmarczak/http-timer": "^4.0.5",
|
||||||
|
@ -12876,15 +12907,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser": {
|
"node_modules/terser": {
|
||||||
"version": "5.12.1",
|
"version": "5.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
|
||||||
"integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
|
"integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@jridgewell/source-map": "^0.3.2",
|
||||||
"acorn": "^8.5.0",
|
"acorn": "^8.5.0",
|
||||||
"commander": "^2.20.0",
|
"commander": "^2.20.0",
|
||||||
"source-map": "~0.7.2",
|
|
||||||
"source-map-support": "~0.5.20"
|
"source-map-support": "~0.5.20"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -12936,16 +12966,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/terser/node_modules/source-map": {
|
|
||||||
"version": "0.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
|
||||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/text-table": {
|
"node_modules/text-table": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||||
|
@ -15041,12 +15061,39 @@
|
||||||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@jridgewell/gen-mapping": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@jridgewell/set-array": "^1.0.1",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@jridgewell/resolve-uri": {
|
"@jridgewell/resolve-uri": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz",
|
||||||
"integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==",
|
"integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@jridgewell/set-array": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@jridgewell/source-map": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@jridgewell/gen-mapping": "^0.3.0",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@jridgewell/sourcemap-codec": {
|
"@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.4.11",
|
"version": "1.4.11",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz",
|
||||||
|
@ -15054,9 +15101,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@jridgewell/trace-mapping": {
|
"@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.4",
|
"version": "0.3.14",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
|
||||||
"integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==",
|
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@jridgewell/resolve-uri": "^3.0.3",
|
"@jridgewell/resolve-uri": "^3.0.3",
|
||||||
|
@ -18443,9 +18490,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"got": {
|
"got": {
|
||||||
"version": "11.8.3",
|
"version": "11.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/got/-/got-11.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz",
|
||||||
"integrity": "sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==",
|
"integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@sindresorhus/is": "^4.0.0",
|
"@sindresorhus/is": "^4.0.0",
|
||||||
"@szmarczak/http-timer": "^4.0.5",
|
"@szmarczak/http-timer": "^4.0.5",
|
||||||
|
@ -22755,14 +22802,14 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"terser": {
|
"terser": {
|
||||||
"version": "5.12.1",
|
"version": "5.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
|
||||||
"integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
|
"integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"@jridgewell/source-map": "^0.3.2",
|
||||||
"acorn": "^8.5.0",
|
"acorn": "^8.5.0",
|
||||||
"commander": "^2.20.0",
|
"commander": "^2.20.0",
|
||||||
"source-map": "~0.7.2",
|
|
||||||
"source-map-support": "~0.5.20"
|
"source-map-support": "~0.5.20"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -22771,12 +22818,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
|
||||||
"source-map": {
|
|
||||||
"version": "0.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
|
||||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
"editor-upgrade": "yarn add -D @editorjs/{editorjs,header,code,delimiter,list,link,image,table,inline-code,marker,warning,checklist,raw}@latest"
|
"editor-upgrade": "yarn add -D @editorjs/{editorjs,header,code,delimiter,list,link,image,table,inline-code,marker,warning,checklist,raw}@latest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hawk.so/javascript": "^3.0.1",
|
||||||
|
"@hawk.so/nodejs": "^3.1.2",
|
||||||
"config": "^3.3.6",
|
"config": "^3.3.6",
|
||||||
"cookie-parser": "^1.4.5",
|
"cookie-parser": "^1.4.5",
|
||||||
"csurf": "^1.11.0",
|
"csurf": "^1.11.0",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import express, { Request, Response } from 'express';
|
import express, { NextFunction, Request, Response } from 'express';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import cookieParser from 'cookie-parser';
|
import cookieParser from 'cookie-parser';
|
||||||
import morgan from 'morgan';
|
import morgan from 'morgan';
|
||||||
|
@ -7,18 +7,30 @@ import routes from './routes';
|
||||||
import HttpException from './exceptions/httpException';
|
import HttpException from './exceptions/httpException';
|
||||||
import * as dotenv from 'dotenv';
|
import * as dotenv from 'dotenv';
|
||||||
import config from 'config';
|
import config from 'config';
|
||||||
|
import HawkCatcher from '@hawk.so/nodejs';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import appConfig from 'config';
|
import appConfig from 'config';
|
||||||
import { downloadFavicon, FaviconData } from './utils/downloadFavicon';
|
import { downloadFavicon, FaviconData } from './utils/downloadFavicon';
|
||||||
|
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
const app = express();
|
const app = express();
|
||||||
const localConfig = rcParser.getConfiguration();
|
const localConfig = rcParser.getConfiguration();
|
||||||
|
|
||||||
|
// Initialize the backend error tracking catcher.
|
||||||
|
if (process.env.HAWK_TOKEN_BACKEND) {
|
||||||
|
HawkCatcher.init(process.env.HAWK_TOKEN_BACKEND);
|
||||||
|
}
|
||||||
|
|
||||||
// Get url to upload favicon from config
|
// Get url to upload favicon from config
|
||||||
const favicon: string = appConfig.get('favicon');
|
const favicon: string = appConfig.get('favicon');
|
||||||
|
|
||||||
app.locals.config = localConfig;
|
app.locals.config = localConfig;
|
||||||
|
// Set client error tracking token as app local.
|
||||||
|
if (process.env.HAWK_TOKEN_CLIENT) {
|
||||||
|
app.locals.config.hawkClientToken = process.env.HAWK_TOKEN_CLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
// view engine setup
|
// view engine setup
|
||||||
app.set('views', path.join(__dirname, './', 'views'));
|
app.set('views', path.join(__dirname, './', 'views'));
|
||||||
app.set('view engine', 'twig');
|
app.set('view engine', 'twig');
|
||||||
|
@ -55,15 +67,24 @@ app.use('/favicon', express.static(downloadedFaviconFolder));
|
||||||
|
|
||||||
app.use('/', routes);
|
app.use('/', routes);
|
||||||
|
|
||||||
// error handler
|
|
||||||
app.use(function (err: HttpException, req: Request, res: Response) {
|
|
||||||
// set locals, only providing error in development
|
|
||||||
res.locals.message = err.message;
|
|
||||||
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
|
||||||
|
|
||||||
// render the error page
|
// global error handler
|
||||||
res.status(err.status || 500);
|
app.use(function (err: unknown, req: Request, res: Response, next: NextFunction) {
|
||||||
res.render('error');
|
// send any type of error to hawk server.
|
||||||
|
if (process.env.HAWK_TOKEN_BACKEND && err instanceof Error) {
|
||||||
|
HawkCatcher.send(err);
|
||||||
|
}
|
||||||
|
// only send Http based exception to client.
|
||||||
|
if (err instanceof HttpException) {
|
||||||
|
// set locals, only providing error in development
|
||||||
|
res.locals.message = err.message;
|
||||||
|
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||||
|
// render the error page
|
||||||
|
res.status(err.status || 500);
|
||||||
|
res.render('error');
|
||||||
|
}
|
||||||
|
next(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
</head>
|
</head>
|
||||||
<script>
|
<script>
|
||||||
window.config = {
|
window.config = {
|
||||||
misprintsChatId: "{{ config.misprintsChatId }}"
|
misprintsChatId: "{{ config.misprintsChatId }}",
|
||||||
|
hawkClientToken:"{{ config.hawkClientToken }}",
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<body>
|
<body>
|
||||||
|
@ -26,9 +27,7 @@
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside class="docs__aside-right">
|
<aside class="docs__aside-right" id="layout-sidebar-right"></aside>
|
||||||
<div style="width: 100%; height: 100px; background: grey"/>
|
|
||||||
</aside>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/dist/main.bundle.js"></script>
|
<script src="/dist/main.bundle.js"></script>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<a name="{{ text | urlify }}" style="display: inline-block; position: absolute; margin-top: -20px;"></a>
|
<h{{ level }} id="{{ text | urlify }}" class="block-header block-header--{{ level }} block-header--anchor">
|
||||||
<h{{ level }} class="block-header block-header--{{ level }} block-header--anchor">
|
|
||||||
<a href="#{{ text | urlify }}">
|
<a href="#{{ text | urlify }}">
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</a>
|
</a>
|
||||||
|
|
13
src/frontend/.eslintrc
Normal file
13
src/frontend/.eslintrc
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"codex"
|
||||||
|
],
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"parserOptions": {
|
||||||
|
"sourceType": "module",
|
||||||
|
"allowImportExportEverywhere": true
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"HTMLElement": true
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ import Writing from './modules/writing';
|
||||||
import Page from './modules/page';
|
import Page from './modules/page';
|
||||||
import Extensions from './modules/extensions';
|
import Extensions from './modules/extensions';
|
||||||
import Sidebar from './modules/sidebar';
|
import Sidebar from './modules/sidebar';
|
||||||
|
import HawkCatcher from '@hawk.so/javascript';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main app class
|
* Main app class
|
||||||
|
@ -30,6 +31,9 @@ class Docs {
|
||||||
this.page = new Page();
|
this.page = new Page();
|
||||||
this.extensions = new Extensions();
|
this.extensions = new Extensions();
|
||||||
this.sidebar = new Sidebar();
|
this.sidebar = new Sidebar();
|
||||||
|
if (window.config.hawkClientToken) {
|
||||||
|
this.hawk = new HawkCatcher(window.config.hawkClientToken);
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', (event) => {
|
document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
this.docReady();
|
this.docReady();
|
||||||
|
|
405
src/frontend/js/classes/table-of-content.js
Normal file
405
src/frontend/js/classes/table-of-content.js
Normal file
|
@ -0,0 +1,405 @@
|
||||||
|
import * as Decorators from '../utils/decorators';
|
||||||
|
import * as $ from '../utils/dom';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate dynamic table of content
|
||||||
|
*/
|
||||||
|
export default class TableOfContent {
|
||||||
|
/**
|
||||||
|
* Initialize table of content
|
||||||
|
*
|
||||||
|
* @param {object} options - constructor params
|
||||||
|
* @param {string} options.tagSelector - selector for tags to observe
|
||||||
|
* @param {HTMLElement} options.appendTo - element for appending of the table of content
|
||||||
|
*/
|
||||||
|
constructor({ tagSelector, appendTo }) {
|
||||||
|
/**
|
||||||
|
* Array of tags to observe
|
||||||
|
*/
|
||||||
|
this.tags = [];
|
||||||
|
this.tagsSectionsMap = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selector for tags to observe
|
||||||
|
*/
|
||||||
|
this.tagSelector = tagSelector || 'h2,h3,h4';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element to append the Table of Content
|
||||||
|
*/
|
||||||
|
this.tocParentElement = appendTo;
|
||||||
|
|
||||||
|
if (!this.tocParentElement) {
|
||||||
|
throw new Error('Table of Content wrapper not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nodes = {
|
||||||
|
/**
|
||||||
|
* Main Table of Content element
|
||||||
|
*/
|
||||||
|
wrapper: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of Table of Content links
|
||||||
|
*/
|
||||||
|
items: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently highlighted element of ToC
|
||||||
|
*/
|
||||||
|
this.activeItem = null;
|
||||||
|
|
||||||
|
this.CSS = {
|
||||||
|
tocContainer: 'table-of-content',
|
||||||
|
tocHeader: 'table-of-content__header',
|
||||||
|
tocElement: 'table-of-content__list',
|
||||||
|
tocElementItem: 'table-of-content__list-item',
|
||||||
|
tocElementItemActive: 'table-of-content__list-item--active',
|
||||||
|
tocElementItemIndent: number => `table-of-content__list-item--indent-${number}x`,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize table of content
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
this.tags = this.getSectionTagsOnThePage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if no tags found then table of content is not needed
|
||||||
|
*/
|
||||||
|
if (this.tags.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize table of content element
|
||||||
|
*/
|
||||||
|
this.createTableOfContent();
|
||||||
|
this.addTableOfContent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate bounds for each tag and watch active section
|
||||||
|
*/
|
||||||
|
this.calculateBounds();
|
||||||
|
this.watchActiveSection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all section tags on the page
|
||||||
|
*
|
||||||
|
* @returns {HTMLElement[]}
|
||||||
|
*/
|
||||||
|
getSectionTagsOnThePage() {
|
||||||
|
return Array.from(document.querySelectorAll(this.tagSelector));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate top line position for each tag
|
||||||
|
*/
|
||||||
|
calculateBounds() {
|
||||||
|
this.tagsSectionsMap = this.tags.map((tag) => {
|
||||||
|
const rect = tag.getBoundingClientRect();
|
||||||
|
const top = Math.floor(rect.top + window.scrollY);
|
||||||
|
|
||||||
|
return {
|
||||||
|
top,
|
||||||
|
tag,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch active section while scrolling
|
||||||
|
*/
|
||||||
|
watchActiveSection() {
|
||||||
|
/**
|
||||||
|
* Where content zone starts in document
|
||||||
|
*/
|
||||||
|
const contentTopOffset = this.getScrollPadding();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional offset for correct calculation of active section
|
||||||
|
*
|
||||||
|
* Cause opening page with anchor could scroll almost
|
||||||
|
* near to the target section and we need to add 1px to calculations
|
||||||
|
*/
|
||||||
|
const activationOffset = 1;
|
||||||
|
|
||||||
|
const detectSection = () => {
|
||||||
|
/**
|
||||||
|
* Calculate scroll position
|
||||||
|
*
|
||||||
|
* @todo research how not to use magic number
|
||||||
|
*/
|
||||||
|
const scrollPosition = contentTopOffset + window.scrollY + activationOffset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the nearest section above the scroll position
|
||||||
|
*/
|
||||||
|
const section = this.tagsSectionsMap.filter((tag) => {
|
||||||
|
return tag.top <= scrollPosition;
|
||||||
|
}).pop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If section found then set it as active
|
||||||
|
*/
|
||||||
|
if (section) {
|
||||||
|
const targetLink = section.tag.querySelector('a').getAttribute('href');
|
||||||
|
|
||||||
|
this.setActiveItem(targetLink);
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* Otherwise no active link will be highlighted
|
||||||
|
*/
|
||||||
|
this.setActiveItem(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a flag to reduce number of calls to detectSection function
|
||||||
|
*/
|
||||||
|
const throttledDetectSectionFunction = Decorators.throttle(() => {
|
||||||
|
detectSection();
|
||||||
|
}, 400);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll listener
|
||||||
|
*/
|
||||||
|
document.addEventListener('scroll', throttledDetectSectionFunction, {
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create table of content
|
||||||
|
*
|
||||||
|
* <section>
|
||||||
|
* <header>On this page</header>
|
||||||
|
* <ul>
|
||||||
|
* <li><a href="#"></a></li>
|
||||||
|
* ...
|
||||||
|
* </ul>
|
||||||
|
* </section>
|
||||||
|
*/
|
||||||
|
createTableOfContent() {
|
||||||
|
this.tocElement = $.make('section', this.CSS.tocElement);
|
||||||
|
|
||||||
|
this.tags.forEach((tag) => {
|
||||||
|
const linkTarget = tag.querySelector('a').getAttribute('href');
|
||||||
|
|
||||||
|
const linkWrapper = $.make('li', this.CSS.tocElementItem);
|
||||||
|
const linkBlock = $.make('a', null, {
|
||||||
|
innerText: tag.innerText,
|
||||||
|
href: `${linkTarget}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional indent for h3-h6 headers
|
||||||
|
*/
|
||||||
|
switch (tag.tagName.toLowerCase()) {
|
||||||
|
case 'h3':
|
||||||
|
linkWrapper.classList.add(this.CSS.tocElementItemIndent(1));
|
||||||
|
break;
|
||||||
|
case 'h4':
|
||||||
|
linkWrapper.classList.add(this.CSS.tocElementItemIndent(2));
|
||||||
|
break;
|
||||||
|
case 'h5':
|
||||||
|
linkWrapper.classList.add(this.CSS.tocElementItemIndent(3));
|
||||||
|
break;
|
||||||
|
case 'h6':
|
||||||
|
linkWrapper.classList.add(this.CSS.tocElementItemIndent(4));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
linkWrapper.appendChild(linkBlock);
|
||||||
|
this.tocElement.appendChild(linkWrapper);
|
||||||
|
|
||||||
|
this.nodes.items.push(linkWrapper);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add table of content to the page
|
||||||
|
*/
|
||||||
|
addTableOfContent() {
|
||||||
|
this.nodes.wrapper = $.make('section', this.CSS.tocContainer);
|
||||||
|
|
||||||
|
const header = $.make('header', this.CSS.tocHeader, {
|
||||||
|
textContent: 'On this page',
|
||||||
|
});
|
||||||
|
|
||||||
|
this.nodes.wrapper.appendChild(header);
|
||||||
|
this.nodes.wrapper.appendChild(this.tocElement);
|
||||||
|
|
||||||
|
this.tocParentElement.appendChild(this.nodes.wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight link's item with a given href
|
||||||
|
*
|
||||||
|
* @param {string|null} targetLink - href of the link. Null if we need to clear all highlights
|
||||||
|
*/
|
||||||
|
setActiveItem(targetLink) {
|
||||||
|
/**
|
||||||
|
* Clear current highlight
|
||||||
|
*/
|
||||||
|
if (this.activeItem) {
|
||||||
|
this.activeItem.classList.remove(this.CSS.tocElementItemActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If targetLink is null, that means we reached top, nothing to highlight
|
||||||
|
*/
|
||||||
|
if (targetLink === null) {
|
||||||
|
this.activeItem = null;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looking for a target link
|
||||||
|
*
|
||||||
|
* @todo do not fire DOM search, use saved map instead
|
||||||
|
*/
|
||||||
|
const targetElement = this.tocElement.querySelector(`a[href="${targetLink}"]`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getting link's wrapper
|
||||||
|
*/
|
||||||
|
const listItem = targetElement.parentNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight and save current item
|
||||||
|
*/
|
||||||
|
listItem.classList.add(this.CSS.tocElementItemActive);
|
||||||
|
this.activeItem = listItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If need, scroll parent to active item
|
||||||
|
*/
|
||||||
|
this.scrollToActiveItemIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Document scroll ending callback
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
scrollToActiveItemIfNeeded() {
|
||||||
|
/**
|
||||||
|
* Do nothing if the Table of Content has no internal scroll at this page
|
||||||
|
*
|
||||||
|
* @todo compute it once
|
||||||
|
*/
|
||||||
|
const hasScroll = this.nodes.wrapper.scrollHeight > this.nodes.wrapper.clientHeight;
|
||||||
|
|
||||||
|
if (!hasScroll) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If some item is highlighted, check whether we need to scroll to it or not
|
||||||
|
*/
|
||||||
|
if (this.activeItem) {
|
||||||
|
/**
|
||||||
|
* First, check do we need to scroll to item?
|
||||||
|
* We need to scroll in case when:
|
||||||
|
* item bottom coord is bigger than parent height + current parent scroll
|
||||||
|
*
|
||||||
|
* @todo use memoization for calculating of the itemBottomCoordWithPadding
|
||||||
|
*/
|
||||||
|
const itemOffsetTop = this.activeItem.offsetTop;
|
||||||
|
const itemHeight = this.activeItem.offsetHeight;
|
||||||
|
const itemBottomCoord = itemOffsetTop + itemHeight;
|
||||||
|
const scrollPadding = 10; // scroll offset below/above item
|
||||||
|
const itemBottomCoordWithPadding = itemBottomCoord + scrollPadding;
|
||||||
|
const itemTopCoordWithPadding = itemOffsetTop - scrollPadding;
|
||||||
|
|
||||||
|
const scrollableParentHeight = this.nodes.wrapper.offsetHeight; // @todo compute it once
|
||||||
|
const scrollableParentScrolledDistance = this.nodes.wrapper.scrollTop;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll bottom required if item ends below the parent bottom boundary
|
||||||
|
*/
|
||||||
|
const isScrollDownRequired = itemBottomCoordWithPadding > scrollableParentHeight + scrollableParentScrolledDistance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll upward required when item starts above the visible parent zone
|
||||||
|
*/
|
||||||
|
const isScrollUpRequired = itemTopCoordWithPadding < scrollableParentScrolledDistance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If item is fully visible, scroll is not required
|
||||||
|
*/
|
||||||
|
const isScrollRequired = isScrollDownRequired || isScrollUpRequired;
|
||||||
|
|
||||||
|
|
||||||
|
if (isScrollRequired === false) {
|
||||||
|
/**
|
||||||
|
* Item is visible, scroll is not needed
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Now compute the scroll distance to make item visible
|
||||||
|
*/
|
||||||
|
let distanceToMakeItemFullyVisible;
|
||||||
|
|
||||||
|
|
||||||
|
if (isScrollDownRequired) {
|
||||||
|
distanceToMakeItemFullyVisible = itemBottomCoordWithPadding - scrollableParentHeight;
|
||||||
|
} else { // scrollUpRequired=true
|
||||||
|
distanceToMakeItemFullyVisible = itemTopCoordWithPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the scroll
|
||||||
|
* Using RAF to prevent overloading of regular scroll animation FPS
|
||||||
|
*/
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
this.nodes.wrapper.scrollTop = distanceToMakeItemFullyVisible;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get scroll padding top value from HTML element
|
||||||
|
*
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
getScrollPadding() {
|
||||||
|
const defaultScrollPaddingValue = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to get calculated value or fallback to default value
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
/**
|
||||||
|
* Getting the HTML element
|
||||||
|
*/
|
||||||
|
const htmlElement = document.documentElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getting css scroll padding value
|
||||||
|
*/
|
||||||
|
const scrollPaddingTopValue = window.getComputedStyle(htmlElement)
|
||||||
|
.getPropertyValue('scroll-padding-top');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse value to number
|
||||||
|
*/
|
||||||
|
return parseInt(scrollPaddingTopValue, 10);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On any errors return default value
|
||||||
|
*/
|
||||||
|
return defaultScrollPaddingValue;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,27 +1,23 @@
|
||||||
/**
|
|
||||||
* @typedef {object} pageModuleSettings
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Page
|
* @class Page
|
||||||
* @classdesc Class for page module
|
* @classdesc Class for page module
|
||||||
*/
|
*/
|
||||||
export default class Writing {
|
export default class Page {
|
||||||
/**
|
/**
|
||||||
* Creates base properties
|
* Creates base properties
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
this.codeStyler = null;
|
this.codeStyler = null;
|
||||||
|
this.tableOfContent = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by ModuleDispatcher to initialize module from DOM
|
* Called by ModuleDispatcher to initialize module from DOM
|
||||||
* @param {pageModuleSettings} settings - module settings
|
|
||||||
* @param {HTMLElement} moduleEl - module element
|
|
||||||
*/
|
*/
|
||||||
init(settings = {}, moduleEl) {
|
init() {
|
||||||
this.codeStyler = this.createCodeStyling();
|
this.codeStyler = this.createCodeStyling();
|
||||||
};
|
this.tableOfContent = this.createTableOfContent();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init code highlighting
|
* Init code highlighting
|
||||||
|
@ -29,8 +25,35 @@ export default class Writing {
|
||||||
async createCodeStyling() {
|
async createCodeStyling() {
|
||||||
const { default: CodeStyler } = await import(/* webpackChunkName: "code-styling" */ './../classes/codeStyler');
|
const { default: CodeStyler } = await import(/* webpackChunkName: "code-styling" */ './../classes/codeStyler');
|
||||||
|
|
||||||
return new CodeStyler({
|
try {
|
||||||
selector: '.block-code__content'
|
// eslint-disable-next-line no-new
|
||||||
});
|
new CodeStyler({
|
||||||
};
|
selector: '.block-code__content',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error); // @todo send to Hawk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init table of content
|
||||||
|
*
|
||||||
|
* @returns {Promise<TableOfContent>}
|
||||||
|
*/
|
||||||
|
async createTableOfContent() {
|
||||||
|
const { default: TableOfContent } = await import(/* webpackChunkName: "table-of-content" */ '../classes/table-of-content');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-new
|
||||||
|
new TableOfContent({
|
||||||
|
tagSelector:
|
||||||
|
'h2.block-header--anchor,' +
|
||||||
|
'h3.block-header--anchor,' +
|
||||||
|
'h4.block-header--anchor',
|
||||||
|
appendTo: document.getElementById('layout-sidebar-right'),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error); // @todo send to Hawk
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
62
src/frontend/js/utils/decorators.js
Normal file
62
src/frontend/js/utils/decorators.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/**
|
||||||
|
* A few useful utility functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throttle decorator function
|
||||||
|
*
|
||||||
|
* @param {Function} func - function to throttle
|
||||||
|
* @param {number} ms - milliseconds to throttle
|
||||||
|
*
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
export function throttle(func, ms) {
|
||||||
|
let isThrottled = false,
|
||||||
|
savedArgs,
|
||||||
|
savedThis;
|
||||||
|
|
||||||
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||||
|
function wrapper() {
|
||||||
|
if (isThrottled) {
|
||||||
|
savedArgs = arguments;
|
||||||
|
savedThis = this;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
func.apply(this, arguments);
|
||||||
|
|
||||||
|
isThrottled = true;
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
isThrottled = false;
|
||||||
|
|
||||||
|
if (savedArgs) {
|
||||||
|
wrapper.apply(savedThis, savedArgs);
|
||||||
|
savedArgs = savedThis = null;
|
||||||
|
}
|
||||||
|
}, ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debounce decorator function
|
||||||
|
*
|
||||||
|
* @param {Function} f - function to debounce
|
||||||
|
* @param {number} ms - milliseconds to debounce
|
||||||
|
*
|
||||||
|
* @returns {(function(): void)|*}
|
||||||
|
*/
|
||||||
|
export function debounce(f, ms) {
|
||||||
|
let timeoutId = null;
|
||||||
|
|
||||||
|
return function () {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutId = setTimeout(() => f.apply(this, arguments), ms);
|
||||||
|
};
|
||||||
|
}
|
23
src/frontend/js/utils/dom.js
Normal file
23
src/frontend/js/utils/dom.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Helper method for elements creation
|
||||||
|
*
|
||||||
|
* @param {string} tagName - name of tag to create
|
||||||
|
* @param {string | string[]} classNames - list of CSS classes
|
||||||
|
* @param {object} attributes - any properties to add
|
||||||
|
* @returns {HTMLElement}
|
||||||
|
*/
|
||||||
|
export function make(tagName, classNames = null, attributes = {}) {
|
||||||
|
const el = document.createElement(tagName);
|
||||||
|
|
||||||
|
if (Array.isArray(classNames)) {
|
||||||
|
el.classList.add(...classNames);
|
||||||
|
} else if (classNames) {
|
||||||
|
el.classList.add(classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const attrName in attributes) {
|
||||||
|
el[attrName] = attributes[attrName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return el;
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class to handle interaction with local storage
|
* Utility class to handle interaction with local storage
|
||||||
*/
|
*/
|
||||||
|
@ -27,4 +26,4 @@ export class Storage {
|
||||||
get() {
|
get() {
|
||||||
return localStorage.getItem(this.key);
|
return localStorage.getItem(this.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
html {
|
||||||
|
scroll-padding-top: calc(var(--layout-height-header) + 50px);
|
||||||
|
}
|
||||||
|
|
||||||
.docs-header {
|
.docs-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -6,7 +10,6 @@
|
||||||
border-bottom: 1px solid var(--color-line-gray);
|
border-bottom: 1px solid var(--color-line-gray);
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
position: relative;
|
|
||||||
height: var(--layout-height-header);
|
height: var(--layout-height-header);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
|
|
58
src/frontend/styles/components/table-of-content.pcss
Normal file
58
src/frontend/styles/components/table-of-content.pcss
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
.table-of-content {
|
||||||
|
border-left: 1px solid var(--color-line-gray);
|
||||||
|
padding-left: var(--layout-padding-horizontal);
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
padding: var(--layout-padding-vertical) var(--layout-padding-horizontal);
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 21px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__list {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
@apply --squircle;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-link-hover);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
background-color: var(--color-link-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--indent-1x { margin-left: 6px; }
|
||||||
|
&--indent-2x { margin-left: 12px; }
|
||||||
|
&--indent-3x { margin-left: 18px; }
|
||||||
|
&--indent-4x { margin-left: 24px; }
|
||||||
|
|
||||||
|
& > a {
|
||||||
|
padding: 4px 8px;
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
line-height: 150%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,15 +20,15 @@
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
||||||
@media (--desktop) {
|
@media (--desktop) {
|
||||||
margin-right: var(--layout-padding-horizontal);
|
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
padding: var(--layout-padding-vertical) var(--layout-padding-horizontal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
--max-space-between-cols: 160px;
|
--max-space-between-cols: 160px;
|
||||||
padding: var(--layout-padding-vertical) var(--layout-padding-horizontal);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
max-width: calc(var(--layout-width-main-col) + var(--max-space-between-cols) + var(--layout-sidebar-width));
|
max-width: calc(var(--layout-width-main-col) + var(--max-space-between-cols) + var(--layout-sidebar-width));
|
||||||
|
@ -46,7 +46,10 @@
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: calc(var(--layout-height-header) + var(--layout-padding-vertical));
|
overflow: auto;
|
||||||
|
height: calc(100vh - var(--layout-height-header));
|
||||||
|
|
||||||
|
top: calc(var(--layout-height-header));
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
|
|
||||||
@media (--desktop) {
|
@media (--desktop) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
@import 'normalize.css';
|
@import 'normalize.css';
|
||||||
|
|
||||||
@import './vars.pcss';
|
@import './vars.pcss';
|
||||||
@import './layout.pcss';
|
@import './layout.pcss';
|
||||||
@import './carbon.pcss';
|
@import './carbon.pcss';
|
||||||
|
@ -9,6 +10,7 @@
|
||||||
@import './components/auth.pcss';
|
@import './components/auth.pcss';
|
||||||
@import './components/button.pcss';
|
@import './components/button.pcss';
|
||||||
@import './components/sidebar.pcss';
|
@import './components/sidebar.pcss';
|
||||||
|
@import './components/table-of-content.pcss';
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: system-ui, Helvetica, Arial, Verdana;
|
font-family: system-ui, Helvetica, Arial, Verdana;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue