Author: Fedor Katurov <gotham48@gmail.com>
Date:   Thu Nov 3 10:38:11 2022 +0600

    added whole content
This commit is contained in:
muerwre 2022-11-03 04:46:54 +00:00
parent dd104eed49
commit 25f1a3121c
290 changed files with 458 additions and 10942 deletions

View file

@ -1,11 +0,0 @@
node_modules
*.log*
.nuxt
.nitro
.cache
.output
.env
.idea
.vscode
content/.obsidian
.DS_Store

View file

@ -1,28 +0,0 @@
kind: pipeline
name: build
type: docker
platform:
os: linux
arch: amd64
steps:
- name: build-master
image: plugins/docker
when:
branch:
- master
settings:
dockerfile: docker/Dockerfile
tag:
- ${DRONE_BRANCH}
custom_labels:
- "commit=${DRONE_COMMIT_SHA}"
username:
from_secret: global_docker_login
password:
from_secret: global_docker_password
registry:
from_secret: global_docker_registry
repo:
from_secret: docker_repo

View file

@ -1,29 +0,0 @@
kind: pipeline
name: build
type: docker
platform:
os: linux
arch: amd64
steps:
- name: build
image: node:16
commands:
- yarn
- yarn generate
- rm -rf ./docs
- mv ./.output/public ./docs
- touch ./docs/.nojekyll
- name: publish
image: plugins/gh-pages
settings:
target_branch: gh-pages
ssh_key:
from_secret: global_ssh_key
username:
from_secret: github_username
password:
from_secret: global_github_token
ssh_key:
from_secret: global_ssh_key

13
.gitignore vendored
View file

@ -1,13 +0,0 @@
node_modules
*.log*
.nuxt
.nitro
.cache
.output
.env
dist
.idea
.vscode
content/.obsidian
.DS_Store
.obsidian

6
200.html Normal file
View file

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html >
<head><link rel="modulepreload" as="script" crossorigin href="/nuxt/entry.9584492c.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/ContentNavigation.3feae645.js"><link rel="prefetch" as="style" href="/nuxt/ContentNavigation.82aba83b.css"><link rel="prefetch" as="style" href="/nuxt/ProseCode.vue_used_vue_type_style_index_1_lang.24930a5d.css"><link rel="prefetch" as="style" href="/nuxt/LayoutThemeToggle.vue_used_vue_type_style_index_0_lang.cfa40af4.css"><link rel="prefetch" as="style" href="/nuxt/LayoutMainMenuToggle.vue_used_vue_type_style_index_0_lang.5944fda9.css"><link rel="prefetch" as="script" crossorigin href="/nuxt/navigation.d66e160d.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/ProseCode.vue_used_vue_type_style_index_1_lang.module.230a312a.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/LayoutThemeToggle.vue_used_vue_type_style_index_0_lang.module.548e5575.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/LayoutMainMenuToggle.vue_used_vue_type_style_index_0_lang.module.ab60ac3f.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/ContentList.ac186f34.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/ContentSlot.2a654c2d.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/DocumentDrivenEmpty.d3ab28ab.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/DocumentDrivenNotFound.47713d66.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/Markdown.273e7520.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/client-db.2d7ab389.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/content.c14c0a2c.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/LayoutThemeToggle.9b30b6e6.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/LayoutFooter.163a17fa.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/default.be8f54bd.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/LayoutMainMenuToggle.7c76ad2a.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/LayoutMainMenu.06c3729f.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/web-socket.d671e65f.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/error-component.ed309281.js"><link rel="prefetch" as="style" href="/nuxt/entry.9c0c7058.css"><link rel="stylesheet" href="/nuxt/entry.9c0c7058.css" media="print" onload="this.media='all'; this.onload=null;"><script>const w=window,de=document.documentElement,knownColorSchemes=["dark","light"],preference=window.localStorage.getItem("nuxt-color-mode")||"dark";let value=preference==="system"?getColorScheme():preference;const forcedColorMode=de.getAttribute("data-color-mode-forced");forcedColorMode&&(value=forcedColorMode),addColorScheme(value),w["__NUXT_COLOR_MODE__"]={preference,value,getColorScheme,addColorScheme,removeColorScheme};function addColorScheme(e){const o=""+e+"",t="";de.classList?de.classList.add(o):de.className+=" "+o,t&&de.setAttribute("data-"+t,e)}function removeColorScheme(e){const o=""+e+"",t="";de.classList?de.classList.remove(o):de.className=de.className.replace(new RegExp(o,"g"),""),t&&de.removeAttribute("data-"+t)}function prefersColorScheme(e){return w.matchMedia("(prefers-color-scheme"+e+")")}function getColorScheme(){if(w.matchMedia&&prefersColorScheme("").media!=="not all"){for(const e of knownColorSchemes)if(prefersColorScheme(":"+e).matches)return e}return"light"}
</script></head>
<body ><div id="__nuxt"></div><script>window.__NUXT__=(function(a,b,c,d){return {serverRendered:a,config:{public:{content:{clientDB:{isSPA:a,integrity:1667450761748},navigation:{fields:["blblblb"]},base:"_content",tags:{p:"prose-p",a:"prose-a",blockquote:"prose-blockquote","code-inline":"prose-code-inline",code:"prose-code",em:"prose-em",h1:"prose-h1",h2:"prose-h2",h3:"prose-h3",h4:"prose-h4",h5:"prose-h5",h6:"prose-h6",hr:"prose-hr",img:"prose-img",ul:"prose-ul",ol:"prose-ol",li:"prose-li",strong:"prose-strong",table:"prose-table",thead:"prose-thead",tbody:"prose-tbody",td:"prose-td",th:"prose-th",tr:"prose-tr"},highlight:{theme:{default:"github-dark",light:"solarized-light"},preload:[b,"c","go","graphql","scss",b,c,"docker","typescript","javascript","nginx","bash","yaml",c],apiURL:"\u002Fapi\u002F_content\u002Fhighlight"},wsUrl:d,documentDriven:a,anchorLinks:{depth:4,exclude:[1]}}},app:{baseURL:"\u002F",buildAssetsDir:"nuxt\u002F",cdnURL:d}},data:{},state:{}}}(false,"shell","sh",""))</script><script type="module" src="/nuxt/entry.9584492c.js" crossorigin></script></body>
</html>

6
404.html Normal file
View file

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html >
<head><link rel="modulepreload" as="script" crossorigin href="/nuxt/entry.9584492c.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/ContentNavigation.3feae645.js"><link rel="prefetch" as="style" href="/nuxt/ContentNavigation.82aba83b.css"><link rel="prefetch" as="style" href="/nuxt/ProseCode.vue_used_vue_type_style_index_1_lang.24930a5d.css"><link rel="prefetch" as="style" href="/nuxt/LayoutThemeToggle.vue_used_vue_type_style_index_0_lang.cfa40af4.css"><link rel="prefetch" as="style" href="/nuxt/LayoutMainMenuToggle.vue_used_vue_type_style_index_0_lang.5944fda9.css"><link rel="prefetch" as="script" crossorigin href="/nuxt/navigation.d66e160d.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/ProseCode.vue_used_vue_type_style_index_1_lang.module.230a312a.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/LayoutThemeToggle.vue_used_vue_type_style_index_0_lang.module.548e5575.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/LayoutMainMenuToggle.vue_used_vue_type_style_index_0_lang.module.ab60ac3f.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/ContentList.ac186f34.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/ContentSlot.2a654c2d.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/DocumentDrivenEmpty.d3ab28ab.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/DocumentDrivenNotFound.47713d66.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/Markdown.273e7520.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/client-db.2d7ab389.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/content.c14c0a2c.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/LayoutThemeToggle.9b30b6e6.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/LayoutFooter.163a17fa.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/default.be8f54bd.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/LayoutMainMenuToggle.7c76ad2a.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/LayoutMainMenu.06c3729f.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/web-socket.d671e65f.js"><link rel="prefetch" as="script" crossorigin href="/nuxt/error-component.ed309281.js"><link rel="prefetch" as="style" href="/nuxt/entry.9c0c7058.css"><link rel="stylesheet" href="/nuxt/entry.9c0c7058.css" media="print" onload="this.media='all'; this.onload=null;"><script>const w=window,de=document.documentElement,knownColorSchemes=["dark","light"],preference=window.localStorage.getItem("nuxt-color-mode")||"dark";let value=preference==="system"?getColorScheme():preference;const forcedColorMode=de.getAttribute("data-color-mode-forced");forcedColorMode&&(value=forcedColorMode),addColorScheme(value),w["__NUXT_COLOR_MODE__"]={preference,value,getColorScheme,addColorScheme,removeColorScheme};function addColorScheme(e){const o=""+e+"",t="";de.classList?de.classList.add(o):de.className+=" "+o,t&&de.setAttribute("data-"+t,e)}function removeColorScheme(e){const o=""+e+"",t="";de.classList?de.classList.remove(o):de.className=de.className.replace(new RegExp(o,"g"),""),t&&de.removeAttribute("data-"+t)}function prefersColorScheme(e){return w.matchMedia("(prefers-color-scheme"+e+")")}function getColorScheme(){if(w.matchMedia&&prefersColorScheme("").media!=="not all"){for(const e of knownColorSchemes)if(prefersColorScheme(":"+e).matches)return e}return"light"}
</script></head>
<body ><div id="__nuxt"></div><script>window.__NUXT__=(function(a,b,c,d){return {serverRendered:a,config:{public:{content:{clientDB:{isSPA:a,integrity:1667450761748},navigation:{fields:["blblblb"]},base:"_content",tags:{p:"prose-p",a:"prose-a",blockquote:"prose-blockquote","code-inline":"prose-code-inline",code:"prose-code",em:"prose-em",h1:"prose-h1",h2:"prose-h2",h3:"prose-h3",h4:"prose-h4",h5:"prose-h5",h6:"prose-h6",hr:"prose-hr",img:"prose-img",ul:"prose-ul",ol:"prose-ol",li:"prose-li",strong:"prose-strong",table:"prose-table",thead:"prose-thead",tbody:"prose-tbody",td:"prose-td",th:"prose-th",tr:"prose-tr"},highlight:{theme:{default:"github-dark",light:"solarized-light"},preload:[b,"c","go","graphql","scss",b,c,"docker","typescript","javascript","nginx","bash","yaml",c],apiURL:"\u002Fapi\u002F_content\u002Fhighlight"},wsUrl:d,documentDriven:a,anchorLinks:{depth:4,exclude:[1]}}},app:{baseURL:"\u002F",buildAssetsDir:"nuxt\u002F",cdnURL:d}},data:{},state:{}}}(false,"shell","sh",""))</script><script type="module" src="/nuxt/entry.9584492c.js" crossorigin></script></body>
</html>

View file

@ -1,35 +0,0 @@
# Self-hosted Obsidian Vault
Use [Obsidian](https://obsidian.md) for content editing at `./content` folder. Made with
[NuxtJS](https://v3.nuxtjs.org) and [Nuxt Content Plugin](https://content.nuxtjs.org/).
## Running
```shell
yarn
yarn dev
```
## Publishing
```shell
yarn generate
cp -a ./.outputs/public ./somewhere
```
- Dockerfile included in `./docker`
- Sample `drone-ci` configurations for gh-pages (`./.drone.gh-pages.yml`) and docker registry
(`./.drone.docker.yml`).
## Supported Obsidian features
- WikiLinks (should be set up to relative in order to work)
- Highlight
- Code blocks
- Nested pages
## Other feature
- Adaptive layout
- SEO Optimized
- Day / Night theme switching

1
_payload.js Normal file
View file

@ -0,0 +1 @@
export default {data:{navigation:[{title:"Blockchain",_path:"\u002Fblockchain",children:[{title:"Common Typescript Examples",_path:"\u002Fblockchain\u002Fcommon-typescript-examples"},{title:"Smart Contracts",_path:"\u002Fblockchain\u002Fsmart-contracts"}]},{title:"Css",_path:"\u002Fcss",children:[{title:"Automatic Grid Like Masonry With Pure CSS",_path:"\u002Fcss\u002Fautomatic-grid-like-masonry-with-pure-css"},{title:"Sass Nth Child Iterate Mixin",_path:"\u002Fcss\u002Fsass-nth-child-iterate-mixin"},{title:"Test If Browser Supports CSS Rules",_path:"\u002Fcss\u002Ftest-if-browser-supports-css-rules"}]},{title:"Docker",_path:"\u002Fdocker",children:[{title:"Building Static Pages With Docker",_path:"\u002Fdocker\u002Fbuilding-static-pages-with-docker"},{title:"Drone Ci",_path:"\u002Fdocker\u002Fdrone-ci"},{title:"Github Pages With Drone Ci",_path:"\u002Fdocker\u002Fgithub-pages-with-drone-ci"},{title:"Private Docker Registry",_path:"\u002Fdocker\u002Fprivate-docker-registry"},{title:"Refresh Containers On Pull",_path:"\u002Fdocker\u002Frefresh-containers-on-pull"},{title:"Seed Dump Inside Docker",_path:"\u002Fdocker\u002Fseed-dump-inside-docker"},{title:"Wait For Mysql",_path:"\u002Fdocker\u002Fwait-for-mysql"},{title:"Wait For Redis",_path:"\u002Fdocker\u002Fwait-for-redis"}]},{title:"Frontend",_path:"\u002Ffrontend",children:[{title:"React Native",_path:"\u002Ffrontend\u002Freact-native",children:[{title:"OAuth2 Login",_path:"\u002Ffrontend\u002Freact-native\u002Foauth2-login"},{title:"Preserve FlatList Scroll Position In React Native",_path:"\u002Ffrontend\u002Freact-native\u002Fpreserve-flatlist-scroll-position-in-react-native"},{title:"Useful Comands",_path:"\u002Ffrontend\u002Freact-native\u002Fuseful-comands"}]},{title:"React",_path:"\u002Ffrontend\u002Freact",children:[{title:"Axios Refresh Token On React",_path:"\u002Ffrontend\u002Freact\u002Faxios-refresh-token-on-react"},{title:"Axios With AbortController",_path:"\u002Ffrontend\u002Freact\u002Faxios-with-abortcontroller"}]},{title:"Vue",_path:"\u002Ffrontend\u002Fvue",children:[{title:"Adding Global Properties To Component",_path:"\u002Ffrontend\u002Fvue\u002Fadding-global-properties-to-component"},{title:"Make Nuxt Handle Obsidian Highlights",_path:"\u002Ffrontend\u002Fvue\u002Fmake-nuxt-handle-obsidian-highlights"}]}]},{title:"Git",_path:"\u002Fgit",children:[{title:"Force Git To Use HTTPS",_path:"\u002Fgit\u002Fforce-git-to-use-https"},{title:"Git Aliases And Useful Commands",_path:"\u002Fgit\u002Fgit-aliases-and-useful-commands"}]},{title:"Graphql",_path:"\u002Fgraphql",children:[{title:"Apollo Client Pagination",_path:"\u002Fgraphql\u002Fapollo-client-pagination"},{title:"Refresh Token In Apollo Client",_path:"\u002Fgraphql\u002Frefresh-token-in-apollo-client"}]},{title:"Linux",_path:"\u002Flinux",children:[{title:"Gitea For Git Hosting",_path:"\u002Flinux\u002Fgitea-for-git-hosting"},{title:"Google Photos Alternative With Photoprism",_path:"\u002Flinux\u002Fgoogle-photos-alternative-with-photoprism"},{title:"Resume Or Start Screen Session",_path:"\u002Flinux\u002Fresume-or-start-screen-session"},{title:"Rsync File With SSH",_path:"\u002Flinux\u002Frsync-file-with-ssh"},{title:"Setting Up NGINX",_path:"\u002Flinux\u002Fsetting-up-nginx"},{title:"SSH",_path:"\u002Flinux\u002Fssh"}]},{title:"Sql",_path:"\u002Fsql",children:[{title:"MySQL And MariaDB Setup",_path:"\u002Fsql\u002Fmysql-and-mariadb-setup"},{title:"Postgress Setup",_path:"\u002Fsql\u002Fpostgress-setup"}]},{title:"Typescript",_path:"\u002Ftypescript",children:[{title:"Add Global Variable To Window",_path:"\u002Ftypescript\u002Fadd-global-variable-to-window"},{title:"Flatten Object With Periods",_path:"\u002Ftypescript\u002Fflatten-object-with-periods"},{title:"Type Guards",_path:"\u002Ftypescript\u002Ftype-guards"}]}]},prerenderedAt:1667450784524}

1
api/_content/cache.json Normal file

File diff suppressed because one or more lines are too long

1
api/_content/navigation Normal file
View file

@ -0,0 +1 @@
[{"title":"Blockchain","_path":"/blockchain","children":[{"title":"Common Typescript Examples","_path":"/blockchain/common-typescript-examples"},{"title":"Smart Contracts","_path":"/blockchain/smart-contracts"}]},{"title":"Css","_path":"/css","children":[{"title":"Automatic Grid Like Masonry With Pure CSS","_path":"/css/automatic-grid-like-masonry-with-pure-css"},{"title":"Sass Nth Child Iterate Mixin","_path":"/css/sass-nth-child-iterate-mixin"},{"title":"Test If Browser Supports CSS Rules","_path":"/css/test-if-browser-supports-css-rules"}]},{"title":"Docker","_path":"/docker","children":[{"title":"Building Static Pages With Docker","_path":"/docker/building-static-pages-with-docker"},{"title":"Drone Ci","_path":"/docker/drone-ci"},{"title":"Github Pages With Drone Ci","_path":"/docker/github-pages-with-drone-ci"},{"title":"Private Docker Registry","_path":"/docker/private-docker-registry"},{"title":"Refresh Containers On Pull","_path":"/docker/refresh-containers-on-pull"},{"title":"Seed Dump Inside Docker","_path":"/docker/seed-dump-inside-docker"},{"title":"Wait For Mysql","_path":"/docker/wait-for-mysql"},{"title":"Wait For Redis","_path":"/docker/wait-for-redis"}]},{"title":"Frontend","_path":"/frontend","children":[{"title":"React Native","_path":"/frontend/react-native","children":[{"title":"OAuth2 Login","_path":"/frontend/react-native/oauth2-login"},{"title":"Preserve FlatList Scroll Position In React Native","_path":"/frontend/react-native/preserve-flatlist-scroll-position-in-react-native"},{"title":"Useful Comands","_path":"/frontend/react-native/useful-comands"}]},{"title":"React","_path":"/frontend/react","children":[{"title":"Axios Refresh Token On React","_path":"/frontend/react/axios-refresh-token-on-react"},{"title":"Axios With AbortController","_path":"/frontend/react/axios-with-abortcontroller"}]},{"title":"Vue","_path":"/frontend/vue","children":[{"title":"Adding Global Properties To Component","_path":"/frontend/vue/adding-global-properties-to-component"},{"title":"Make Nuxt Handle Obsidian Highlights","_path":"/frontend/vue/make-nuxt-handle-obsidian-highlights"}]}]},{"title":"Git","_path":"/git","children":[{"title":"Force Git To Use HTTPS","_path":"/git/force-git-to-use-https"},{"title":"Git Aliases And Useful Commands","_path":"/git/git-aliases-and-useful-commands"}]},{"title":"Graphql","_path":"/graphql","children":[{"title":"Apollo Client Pagination","_path":"/graphql/apollo-client-pagination"},{"title":"Refresh Token In Apollo Client","_path":"/graphql/refresh-token-in-apollo-client"}]},{"title":"Linux","_path":"/linux","children":[{"title":"Gitea For Git Hosting","_path":"/linux/gitea-for-git-hosting"},{"title":"Google Photos Alternative With Photoprism","_path":"/linux/google-photos-alternative-with-photoprism"},{"title":"Resume Or Start Screen Session","_path":"/linux/resume-or-start-screen-session"},{"title":"Rsync File With SSH","_path":"/linux/rsync-file-with-ssh"},{"title":"Setting Up NGINX","_path":"/linux/setting-up-nginx"},{"title":"SSH","_path":"/linux/ssh"}]},{"title":"Sql","_path":"/sql","children":[{"title":"MySQL And MariaDB Setup","_path":"/sql/mysql-and-mariadb-setup"},{"title":"Postgress Setup","_path":"/sql/postgress-setup"}]},{"title":"Typescript","_path":"/typescript","children":[{"title":"Add Global Variable To Window","_path":"/typescript/add-global-variable-to-window"},{"title":"Flatten Object With Periods","_path":"/typescript/flatten-object-with-periods"},{"title":"Type Guards","_path":"/typescript/type-guards"}]}]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"_path":"/linux/ssh","_dir":"linux","_draft":false,"_partial":false,"_locale":"en","_empty":false,"title":"SSH","description":"","excerpt":{"type":"root","children":[{"type":"element","tag":"h2","props":{"id":"config-aliases-for-ssh-hosts"},"children":[{"type":"text","value":"Config aliases for #SSH hosts"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"#SSH config can be used to made aliases for different hosts. Should be put at "},{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"~/.ssh/config"}]},{"type":"text","value":". To simply call "},{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"ssh router"}]},{"type":"text","value":" without parameters, use this:"}]},{"type":"element","tag":"code","props":{"code":"Host router\n HostName 192.168.0.1\n IdentityFile ~/.ssh/id_rsa\n User root\n Port 22522\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"Host router\n HostName 192.168.0.1\n IdentityFile ~/.ssh/id_rsa\n User root\n Port 22522\n"}]}]}]}]},"body":{"type":"root","children":[{"type":"element","tag":"h2","props":{"id":"config-aliases-for-ssh-hosts"},"children":[{"type":"text","value":"Config aliases for #SSH hosts"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"#SSH config can be used to made aliases for different hosts. Should be put at "},{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"~/.ssh/config"}]},{"type":"text","value":". To simply call "},{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"ssh router"}]},{"type":"text","value":" without parameters, use this:"}]},{"type":"element","tag":"code","props":{"code":"Host router\n HostName 192.168.0.1\n IdentityFile ~/.ssh/id_rsa\n User root\n Port 22522\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"element","tag":"span","props":{"class":"line"},"children":[{"type":"element","tag":"span","props":{},"children":[{"type":"text","value":"Host router\n HostName 192.168.0.1\n IdentityFile ~/.ssh/id_rsa\n User root\n Port 22522"}]}]}]}]}]}],"toc":{"title":"","searchDepth":2,"depth":2,"links":[{"id":"config-aliases-for-ssh-hosts","depth":2,"text":"Config aliases for #SSH hosts"}]}},"_type":"markdown","_id":"content:Linux:SSH.md","_source":"content","_file":"Linux/SSH.md","_extension":"md"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"_path":"/linux/resume-or-start-screen-session","_dir":"linux","_draft":false,"_partial":false,"_locale":"en","_empty":false,"title":"Resume Or Start Screen Session","description":"Running this script will enter currently running screen session or will start new one.","excerpt":{"type":"root","children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Running this script will enter currently running "},{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"screen"}]},{"type":"text","value":" session or will start new one."}]},{"type":"element","tag":"code","props":{"code":"( screen -r bash || ( screen -d bash && screen -r bash || screen -SAm bash bash ) )\n","language":"shell"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"( screen -r bash || ( screen -d bash && screen -r bash || screen -SAm bash bash ) )\n"}]}]}]}]},"body":{"type":"root","children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Running this script will enter currently running "},{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"screen"}]},{"type":"text","value":" session or will start new one."}]},{"type":"element","tag":"code","props":{"code":"( screen -r bash || ( screen -d bash && screen -r bash || screen -SAm bash bash ) )\n","language":"shell"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"element","tag":"span","props":{"class":"line"},"children":[{"type":"element","tag":"span","props":{"class":"ct-ac822e"},"children":[{"type":"text","value":"( screen -r bash "}]},{"type":"element","tag":"span","props":{"class":"ct-35a9ce"},"children":[{"type":"text","value":"||"}]},{"type":"element","tag":"span","props":{"class":"ct-ac822e"},"children":[{"type":"text","value":" ( screen -d bash "}]},{"type":"element","tag":"span","props":{"class":"ct-35a9ce"},"children":[{"type":"text","value":"&&"}]},{"type":"element","tag":"span","props":{"class":"ct-ac822e"},"children":[{"type":"text","value":" screen -r bash "}]},{"type":"element","tag":"span","props":{"class":"ct-35a9ce"},"children":[{"type":"text","value":"||"}]},{"type":"element","tag":"span","props":{"class":"ct-ac822e"},"children":[{"type":"text","value":" screen -SAm bash bash ) )"}]}]}]}]}]},{"type":"element","tag":"style","children":[{"type":"text","value":".ct-35a9ce{color:#FF7B72}.ct-ac822e{color:#C9D1D9}.light .ct-ac822e{color:#657B83}.light .ct-35a9ce{color:#859900}"}]}],"toc":{"title":"","searchDepth":2,"depth":2,"links":[]}},"_type":"markdown","_id":"content:Linux:Resume or start screen session.md","_source":"content","_file":"Linux/Resume or start screen session.md","_extension":"md"}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"_path":"/docker/building-static-pages-with-docker","_dir":"docker","_draft":false,"_partial":false,"_locale":"en","_empty":false,"title":"Building Static Pages With Docker","description":"Sample #Dockerfile for static Typescript builds such a #nextjs, #gatsby or #nuxt:","excerpt":{"type":"root","children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Sample #Dockerfile for static Typescript builds such a #nextjs, #gatsby or #nuxt:"}]},{"type":"element","tag":"code","props":{"code":"FROM node:16-alpine as builder\nWORKDIR /app\nCOPY package.json yarn.lock ./\nRUN yarn\nCOPY . .\n\n# your generate command here\nRUN yarn generate\n\nFROM nginx\nCOPY --from=builder /app/dist /usr/share/nginx/html\n","language":"Dockerfile"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"FROM node:16-alpine as builder\nWORKDIR /app\nCOPY package.json yarn.lock ./\nRUN yarn\nCOPY . .\n\n# your generate command here\nRUN yarn generate\n\nFROM nginx\nCOPY --from=builder /app/dist /usr/share/nginx/html\n"}]}]}]}]},"body":{"type":"root","children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Sample #Dockerfile for static Typescript builds such a #nextjs, #gatsby or #nuxt:"}]},{"type":"element","tag":"code","props":{"code":"FROM node:16-alpine as builder\nWORKDIR /app\nCOPY package.json yarn.lock ./\nRUN yarn\nCOPY . .\n\n# your generate command here\nRUN yarn generate\n\nFROM nginx\nCOPY --from=builder /app/dist /usr/share/nginx/html\n","language":"Dockerfile"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"element","tag":"span","props":{"class":"line"},"children":[{"type":"element","tag":"span","props":{},"children":[{"type":"text","value":"FROM node:16-alpine as builder\nWORKDIR /app\nCOPY package.json yarn.lock ./\nRUN yarn\nCOPY . .\n\n# your generate command here\nRUN yarn generate\n\nFROM nginx\nCOPY --from=builder /app/dist /usr/share/nginx/html"}]}]}]}]}]}],"toc":{"title":"","searchDepth":2,"depth":2,"links":[]}},"_type":"markdown","_id":"content:Docker:Building static pages with Docker.md","_source":"content","_file":"Docker/Building static pages with Docker.md","_extension":"md"}

View file

@ -0,0 +1 @@
{"_path":"/git/force-git-to-use-https","_dir":"git","_draft":false,"_partial":false,"_locale":"en","_empty":false,"title":"Force Git To Use HTTPS","description":"Forces #git to use https even if remote url is #SSH. Useful for the networks with blocked #ssh protocol.","excerpt":{"type":"root","children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Forces #git to use https even if remote url is #SSH. Useful for the networks with blocked #ssh protocol."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Put this inside your "},{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"~/.gitconfig"}]},{"type":"text","value":":"}]},{"type":"element","tag":"code","props":{"code":"[url \"https://github.com\"]\n insteadOf = git://github.com\n","language":"c"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"[url \"https://github.com\"]\n insteadOf = git://github.com\n"}]}]}]}]},"body":{"type":"root","children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Forces #git to use https even if remote url is #SSH. Useful for the networks with blocked #ssh protocol."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Put this inside your "},{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"~/.gitconfig"}]},{"type":"text","value":":"}]},{"type":"element","tag":"code","props":{"code":"[url \"https://github.com\"]\n insteadOf = git://github.com\n","language":"c"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"element","tag":"span","props":{"class":"line"},"children":[{"type":"element","tag":"span","props":{"class":"ct-35ce96"},"children":[{"type":"text","value":"[url "}]},{"type":"element","tag":"span","props":{"class":"ct-528a2e"},"children":[{"type":"text","value":"\"https://github.com\""}]},{"type":"element","tag":"span","props":{"class":"ct-35ce96"},"children":[{"type":"text","value":"]"}]}]},{"type":"element","tag":"span","props":{"class":"line"},"children":[{"type":"element","tag":"span","props":{"class":"ct-35ce96"},"children":[{"type":"text","value":" insteadOf "}]},{"type":"element","tag":"span","props":{"class":"ct-fa8b57"},"children":[{"type":"text","value":"="}]},{"type":"element","tag":"span","props":{"class":"ct-35ce96"},"children":[{"type":"text","value":" git:"}]},{"type":"element","tag":"span","props":{"class":"ct-3930f2"},"children":[{"type":"text","value":"//github.com"}]}]}]}]}]},{"type":"element","tag":"style","children":[{"type":"text","value":".ct-3930f2{color:#8B949E}.ct-fa8b57{color:#FF7B72}.ct-528a2e{color:#A5D6FF}.ct-35ce96{color:#C9D1D9}.light .ct-35ce96{color:#657B83}.light .ct-528a2e{color:#2AA198}.light .ct-fa8b57{color:#859900}.light .ct-3930f2{color:#93A1A1}"}]}],"toc":{"title":"","searchDepth":2,"depth":2,"links":[]}},"_type":"markdown","_id":"content:Git:Force git to use HTTPS.md","_source":"content","_file":"Git/Force git to use HTTPS.md","_extension":"md"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,27 +0,0 @@
@import "./variables";
@mixin phone {
@media (max-width: $size-phone) {
@content;
}
}
@mixin tablet {
@media (max-width: $size-tablet) {
@content;
}
}
@mixin desktop {
@media (max-width: $size-desktop) {
@content;
}
}
@mixin color-per-child($colors) {
@each $color in $colors {
&:nth-child(#{index(($colors), ($color))}) {
color: $color;
}
}
}

View file

@ -1,10 +0,0 @@
.page-enter-active,
.page-leave-active {
transition: all 0.4s;
}
.page-enter-from,
.page-leave-to {
opacity: 0;
transform: translate(0, 50px);
}

View file

@ -1,3 +0,0 @@
$size-phone: 560px;
$size-tablet: 768px;
$size-desktop: 1024px;

View file

@ -1,206 +0,0 @@
@import url("https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@600&family=Roboto:wght@400;700&display=swap");
@import "./variables";
@import "./transitions.scss";
body,
html {
font-family: var(--family-roboto);
background: var(--color-background);
color: var(--color-text);
padding: 0;
margin: 0;
}
* {
box-sizing: border-box;
transition: color 250ms;
}
a {
color: var(--color-link);
h1 > &,
h2 > &,
h3 > &,
h4 > &,
h5 > & {
text-decoration: none;
color: var(--color-header);
}
}
pre {
background-color: var(--color-code-background);
padding: 10px;
border-radius: 10px;
overflow: scroll;
width: 100%;
line-height: 1.5em;
}
:not(pre) > code {
background-color: var(--color-code-background);
color: var(--color-code-inline);
padding: 0 5px;
border-radius: 4px;
}
h1,
h2,
h3,
h4,
h5 {
font-family: var(--family-roboto-slab);
color: var(--color-header);
font-weight: 700;
}
h1 {
color: var(--color-heading-primary);
font-size: 2.6rem;
margin-bottom: 1.5rem;
&:not(:first-child) {
margin-top: 3rem;
}
}
h2 {
color: var(--color-heading-secondary);
&:not(:first-child) {
margin-top: 2rem;
}
}
h3,
h4,
h5 {
color: var(--color-heading-tertiary);
}
p,
li {
line-height: 1.45em;
}
li {
&:not(:last-child) {
margin-bottom: 0.25em;
}
}
button {
background: none;
border: none;
padding: 0;
&:focus {
border: none;
}
}
table {
border-collapse: collapse;
border: 2px solid var(--color-line);
td,
th {
border: 1px solid var(--color-line);
padding: 5px 10px;
text-align: left;
}
thead {
background: var(--color-table-head);
border-bottom: 2px solid var(--color-line);
}
}
blockquote {
border-left: 3px solid var(--color-primary);
color: var(--color-text);
padding: 0 20px;
ul,
ol {
padding-left: 15px;
}
}
.highlight {
background-color: var(--color-highlight-background);
color: var(--color-highlight-color);
padding: 0 1px;
border-radius: 3px;
}
:root {
// fonts
--family-roboto-slab: "Roboto Slab", "Segoe UI", Tahoma, Geneva, Verdana,
sans-serif;
--family-roboto: "Roboto", "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
// breakpoints
--size-phone: $phone;
--size-tablet: $size-tablet;
--size-desktop: $size-desktop;
}
:root.dark {
// palette
--color-primary: #e84a72;
--color-background: #16161c;
--color-menu-background: #1a1c23;
--color-line: #2d2f36;
--color-menu-overlay-background: #{transparentize(#16161c, 0.1)};
--color-code-background: #1a1c23;
--color-code-language-background: #{lighten(#1a1c23, 4%)};
--color-code-language-name: #1eaeae;
--color-text: #fdf0ed;
--color-text-secondary: #{mix(#ffffff, #1a1c23, 60%)};
--color-link: #e84a72;
--color-code-inline: #1eb980;
--color-heading-primary: white;
--color-heading-secondary: #f9cbbe;
--color-heading-tertiary: #f9cec3;
--color-menu-title: #fadad1;
--color-menu-link: #fab28e;
--color-menu-link-active: #e84a72;
--color-menu-line: #2e303e;
--color-table-head: #{mix(#e84a72, #1a1c23, 10%)};
--color-rating-1: #ded187;
--color-rating-2: #dbde87;
--color-rating-3: #bade87;
--color-rating-4: #9cde87;
--color-rating-5: #87deaa;
--color-highlight-color: var(--color-text);
--color-highlight-background: #254e50;
}
:root.light {
$pinky: #{mix(#fadad1, #fce9e4, 50%)};
--color-primary: #e84a72;
--color-background: #fce9e4;
--color-menu-background: #{$pinky};
--color-line: #{$pinky};
--color-menu-overlay-background: #{transparentize(#16161c, 0.1)};
--color-code-background: #{$pinky};
--color-code-language-background: #{lighten(#1a1c23, 4%)};
--color-code-language-name: #1eaeae;
--color-text: #5a5d68;
--color-text-secondary: #{mix(#ffffff, #5a5d68, 20%)};
--color-link: #e84a72;
--color-code-inline: #8931b9;
--color-heading-primary: #4c5161;
--color-heading-secondary: #{mix(#f9cbbe, #1eaeae, 35%)};
--color-heading-tertiary: #{mix(#f9cbbe, #1eaeae, 35%)};
--color-menu-title: #{mix(#f9cbbe, #1eaeae, 35%)};
--color-menu-link: #{mix(#f9cbbe, #e84a72, 20%)};
--color-menu-link-active: #e84a72;
--color-menu-line: #f9cbbe;
--color-table-head: #{mix(#e84a72, #fadad1, 10%)};
--color-highlight-color: var(--color-text);
--color-highlight-background: #fab795;
}

View file

@ -1,56 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 300 300">
<title>SVG Logo</title>
<desc>Designed for the SVG Logo Contest in 2006 by Harvey Rayner, and adopted by W3C in 2009. It is available under the Creative Commons license for those who have an SVG product or who are using SVG on their site.</desc>
<metadata id="license">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://web.resource.org/cc/">
<cc:Work rdf:about="">
<dc:title>SVG Logo</dc:title>
<dc:date>14-08-2009</dc:date>
<dc:creator>
<cc:Agent><dc:title>W3C</dc:title></cc:Agent>
<cc:Agent><dc:title>Harvey Rayner, designer</dc:title></cc:Agent>
</dc:creator>
<dc:description>See document description</dc:description>
<cc:license rdf:resource="http://creativecommons.org/licenses/by-nc-sa/2.5/"/>
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/by-nc-sa/2.5/">
<cc:permits rdf:resource="http://web.resource.org/cc/Reproduction"/>
<cc:permits rdf:resource="http://web.resource.org/cc/Distribution"/>
<cc:requires rdf:resource="http://web.resource.org/cc/Notice"/>
<cc:requires rdf:resource="http://web.resource.org/cc/Attribution"/>
<cc:prohibits rdf:resource="http://web.resource.org/cc/CommercialUse"/>
<cc:permits rdf:resource="http://web.resource.org/cc/DerivativeWorks"/>
<cc:requires rdf:resource="http://web.resource.org/cc/ShareAlike"/>
</cc:License>
</rdf:RDF>
</metadata>
<defs>
<g id="SVG" fill="#ffffff" transform="scale(2) translate(20,79)">
<path id="S" d="M 5.482,31.319 C2.163,28.001 0.109,23.419 0.109,18.358 C0.109,8.232 8.322,0.024 18.443,0.024 C28.569,0.024 36.782,8.232 36.782,18.358 L26.042,18.358 C26.042,14.164 22.638,10.765 18.443,10.765 C14.249,10.765 10.850,14.164 10.850,18.358 C10.850,20.453 11.701,22.351 13.070,23.721 L13.075,23.721 C14.450,25.101 15.595,25.500 18.443,25.952 L18.443,25.952 C23.509,26.479 28.091,28.006 31.409,31.324 L31.409,31.324 C34.728,34.643 36.782,39.225 36.782,44.286 C36.782,54.412 28.569,62.625 18.443,62.625 C8.322,62.625 0.109,54.412 0.109,44.286 L10.850,44.286 C10.850,48.480 14.249,51.884 18.443,51.884 C22.638,51.884 26.042,48.480 26.042,44.286 C26.042,42.191 25.191,40.298 23.821,38.923 L23.816,38.923 C22.441,37.548 20.468,37.074 18.443,36.697 L18.443,36.692 C13.533,35.939 8.800,34.638 5.482,31.319 L5.482,31.319 L5.482,31.319 Z"/>
<path id="V" d="M 73.452,0.024 L60.482,62.625 L49.742,62.625 L36.782,0.024 L47.522,0.024 L55.122,36.687 L62.712,0.024 L73.452,0.024 Z"/>
<path id="G" d="M 91.792,25.952 L110.126,25.952 L110.126,44.286 L110.131,44.286 C110.131,54.413 101.918,62.626 91.792,62.626 C81.665,62.626 73.458,54.413 73.458,44.286 L73.458,44.286 L73.458,18.359 L73.453,18.359 C73.453,8.233 81.665,0.025 91.792,0.025 C101.913,0.025 110.126,8.233 110.126,18.359 L99.385,18.359 C99.385,14.169 95.981,10.765 91.792,10.765 C87.597,10.765 84.198,14.169 84.198,18.359 L84.198,44.286 L84.198,44.286 C84.198,48.481 87.597,51.880 91.792,51.880 C95.981,51.880 99.380,48.481 99.385,44.291 L99.385,44.286 L99.385,36.698 L91.792,36.698 L91.792,25.952 L91.792,25.952 Z"/>
</g>
</defs>
<path id="base" fill="#000" d="M8.5,150 H291.5 V250 C291.5,273.5 273.5,291.5 250,291.5 H50 C26.5,291.5 8.5,273.5 8.5,250 Z"/>
<g stroke-width="38.0086" stroke="#000">
<g id="svgstar" transform="translate(150, 150)">
<path id="svgbar" fill="#ffb13b" d="M-84.1487,-15.8513 a22.4171,22.4171 0 1 0 0,31.7026 h168.2974 a22.4171,22.4171 0 1 0 0,-31.7026 Z"/>
<use xlink:href="#svgbar" transform="rotate(45)"/>
<use xlink:href="#svgbar" transform="rotate(90)"/>
<use xlink:href="#svgbar" transform="rotate(135)"/>
</g>
</g>
<use xlink:href="#svgstar"/>
<use xlink:href="#base" opacity="0.85"/>
<use xlink:href="#SVG"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

1
bio/_payload.js Normal file
View file

@ -0,0 +1 @@
export default {data:{},prerenderedAt:1667450784326}

6
bio/index.html Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,44 +0,0 @@
<template>
<section :class="$style.grid">
<div>
<img src="~/assets/svg/howdy.svg" />
</div>
<div :class="$style.text">
<h1>Howdy!</h1>
<p>
My name is <b>Fedor Katurov</b>, I'm a fullstack developer from Siberia.
</p>
<p>
I develop frontend applications with <b>React</b>, <b>Vue</b> and
numerous other frameworks for the most of my time, but I'm also capable
of doing <b>Typescript</b> and <b>Golang</b> backend.
</p>
<p>
I've started coding more than 15 years ago as a hobby and still love
doing that on my work and free time.
</p>
</div>
</section>
</template>
<script lang="ts" setup></script>
<style lang="scss" module>
.grid {
display: grid;
grid-template-columns: 1fr 2fr;
grid-column-gap: 40px;
}
.text {
color: var(--color-text-secondary);
b {
color: var(--color-text);
}
}
</style>

View file

@ -1,20 +0,0 @@
<template>
<section :class="$style.projects">
<h2>My Projects</h2>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quaerat natus
perspiciatis ad voluptatum nisi saepe, molestiae autem dolores est modi
quod inventore similique omnis asperiores. Qui beatae magnam ab
consequatur.
</p>
</section>
</template>
<script lang="ts" setup></script>
<style lang="scss" module>
.projects {
text-align: center;
}
</style>

View file

@ -1,30 +0,0 @@
<template>
<div>
<h1>Skills</h1>
<div :class="$style.list">
<SkillsCard
:title="skill.title"
:level="skill.level"
:description="skill.description ?? ''"
:key="skill.title"
v-for="skill in skills"
>
<img :src="skill.icon" width="48" height="48" />
</SkillsCard>
</div>
</div>
</template>
<script lang="ts" setup>
import { skills } from "~~/constants/skills";
</script>
<style lang="scss" module>
.list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
row-gap: 20px;
column-gap: 20px;
}
</style>

View file

@ -1,35 +0,0 @@
<script lang="ts" setup>
interface Props {
href?: string;
blank?: boolean;
}
withDefaults(defineProps<Props>(), {
href: "",
blank: false,
});
const isInternalLink = (link: string) => !link.match(/^\w+\:\/\//);
const transformInternalLinks = (href: string) => {
if (!isInternalLink(href)) {
return href;
}
return href
.toLowerCase()
.replaceAll("%20", " ")
.replace(/\d+/g, "")
.trim()
.replaceAll(" ", "-");
};
</script>
<template>
<NuxtLink
:href="transformInternalLinks(href)"
:target="isInternalLink(href) ? '' : '_blank'"
>
<slot
/></NuxtLink>
</template>

View file

@ -1,81 +0,0 @@
<script lang="ts" setup>
interface Props {
code?: string;
language?: string | null;
filename?: string | null;
highlights?: number[];
}
const props = withDefaults(defineProps<Props>(), {
language: null,
filename: null,
highlights: () => [],
});
const copy = () => {
navigator.clipboard.writeText(props.code);
};
</script>
<template>
<div :class="$style.wrapper">
<button :class="$style.language" @click="copy">
<span :class="$style.icon">
<UiIconCopy width="12" height="12" fill="currentColor" />
</span>
<span v-if="language">{{ language }}</span>
</button>
<slot />
</div>
</template>
<style>
pre code .line {
display: block;
min-height: 1rem;
}
</style>
<style lang="scss" module>
.wrapper {
position: relative;
}
.icon {
margin: 0 4px -2px 0;
}
.language {
cursor: pointer;
position: absolute;
right: 0;
top: 0;
padding: 4px 8px;
border-radius: 0 4px 0 4px;
text-transform: uppercase;
font-size: 0.75rem;
font-weight: 400;
background: var(--color-code-language-background);
color: var(--color-code-language-name);
user-select: none;
opacity: 0;
transition: all 250ms;
display: flex;
align-items: center;
justify-content: center;
.wrapper:hover & {
opacity: 0.7;
&:hover {
opacity: 1;
}
}
&:active {
transform: scale(1.1);
opacity: 1;
}
}
</style>

View file

@ -1,34 +0,0 @@
<template>
<masonry-wall
:items="parentItems"
:ssr-columns="1"
:column-width="300"
:gap="10"
>
<template #default="{ item }">
<div :class="$style.row">
<LayoutMainMenuRow
:title="item.title"
:url="item.url"
:children="item.children"
/>
</div>
</template>
</masonry-wall>
</template>
<script setup>
const { data: navigation } = await useAsyncData("navigation", () => {
return fetchContentNavigation();
});
const parentItems = navigation.value.filter(
(it) => it.children && Array.isArray(it.children) && it.children.length > 0
);
</script>
<style lang="scss" module>
.row {
margin-bottom: 10px;
}
</style>

View file

@ -1,110 +0,0 @@
<template>
<section>
<div :class="$style.grid">
<div :class="$style.text">
<h1 :class="$style.title">Welcome to&nbsp;my Obsidian&nbsp;Garden</h1>
<p :class="$style.subtitle">
<img src="~/assets/svg/obsidian.svg" alt="" width="14" height="14" />
<NuxtLink to="https://obsidian.md" target="_blank">Obsidian</NuxtLink>
is a note-taking app, that I use to store chunks of code and technical
documentation.
</p>
<div :class="$style.buttons">
<UiActionButton
href="https://github.com/muerwre"
target="_blank"
variant="outline"
>
Visit my Github
<template v-slot:suffix>
<IconsArrowRight width="22" height="22" fill="currentColor" />
</template>
</UiActionButton>
</div>
</div>
<div :class="$style.image">
<img
src="~/assets/svg/desk.svg"
:class="$style.desk"
alt="It's me, muerwre"
/>
</div>
</div>
</section>
</template>
<style lang="scss" module>
@import "~~/assets/css/mixins";
.grid {
display: grid;
grid-template-columns: 14fr 10fr;
column-gap: 50px;
border-bottom: 2px solid var(--color-line);
@include desktop {
// grid-template-columns: 1fr 1fr;
grid-template-columns: 1fr;
grid-template-rows: auto auto;
column-gap: 20px;
border-bottom: none;
}
}
.image {
display: flex;
align-items: flex-end;
@include desktop {
border-bottom: 2px solid var(--color-line);
justify-content: center;
.desk {
max-width: 400px;
}
}
}
.title {
font-size: 3rem;
line-height: 3.4rem;
margin: 0.8rem 0;
@include phone {
font-size: 2.5rem;
line-height: 3rem;
}
}
.subtitle {
color: var(--color-text-secondary);
max-width: 360px;
@include desktop {
max-width: 100%;
}
}
.text {
padding-bottom: 60px;
display: flex;
flex-direction: column;
justify-content: center;
@include tablet {
text-align: center;
padding-top: 0;
padding-bottom: 40px;
}
}
.desk {
width: 100%;
}
.buttons {
margin-top: 50px;
}
</style>

View file

@ -1,51 +0,0 @@
<template>
<article>
<h1>{{ item?.title }}</h1>
<ul v-if="item?.children?.length" :class="$style.list">
<li v-for="child in item.children" :key="item._id">
<NuxtLink :to="child._path">{{ child.title }}</NuxtLink>
</li>
</ul>
</article>
</template>
<script lang="ts" setup>
import { NavItem } from "@nuxt/content/dist/runtime/types";
interface Props {
url: string;
}
const findDeep = (items: NavItem[], path: string[]) => {
const item = items.find((it) => it._path.endsWith(path[0]));
if (!item || (path.length > 1 && !item.children?.length)) {
return null;
}
return path.length === 1
? item
: findDeep(item.children, path.slice(1, path.length));
};
const props = defineProps<Props>();
const { data: navigation } = await useAsyncData("navigation", () => {
return fetchContentNavigation();
});
const segments = props.url.split("/").filter((it) => it);
const item = findDeep(navigation.value, segments);
</script>
<style lang="scss" module>
.list {
margin: 0;
padding: 0 20px;
li a {
text-decoration: none;
}
}
</style>

View file

@ -1,10 +0,0 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="48"
width="48"
viewBox="0 0 48 48"
>
<path d="m24 40-2.1-2.15L34.25 25.5H8v-3h26.25L21.9 10.15 24 8l16 16Z" />
</svg>
</template>

View file

@ -1,12 +0,0 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="48"
width="48"
viewBox="0 0 48 48"
>
<path
d="m11.65 44 3.25-14.05L4 20.5l14.4-1.25L24 6l5.6 13.25L44 20.5l-10.9 9.45L36.35 44 24 36.55Z"
/>
</svg>
</template>

View file

@ -1,12 +0,0 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="48"
width="48"
viewBox="0 0 48 48"
>
<path
d="M17.85 7.55q-.7 0-1.45.075t-1.3.125q3.05 3.45 4.675 7.6Q21.4 19.5 21.4 24t-1.625 8.65Q18.15 36.8 15.15 40.2q.5.1 1.225.175.725.075 1.525.075 6.8 0 11.6-4.775T34.3 24q0-6.9-4.825-11.675T17.85 7.55Zm.25-1.5q3.6 0 6.85 1.375 3.25 1.375 5.65 3.8 2.4 2.425 3.8 5.7 1.4 3.275 1.4 7.025 0 3.75-1.425 7.05t-3.8 5.75Q28.2 39.2 24.95 40.575t-6.9 1.375q-1.65 0-3.125-.275t-2.675-.725q3.65-3.35 5.65-7.725 2-4.375 2-9.225 0-4.75-2-9.175-2-4.425-5.65-7.775 1.15-.45 2.675-.725Q16.45 6.05 18.1 6.05ZM21.4 24Z"
/>
</svg>
</template>

View file

@ -1,12 +0,0 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="48"
width="48"
viewBox="0 0 48 48"
>
<path
d="M24 30.45q2.65 0 4.55-1.875T30.45 24q0-2.65-1.875-4.55T24 17.55q-2.65 0-4.55 1.875T17.55 24q0 2.65 1.875 4.55T24 30.45ZM24 32q-3.35 0-5.675-2.325Q16 27.35 16 24q0-3.35 2.325-5.675Q20.65 16 24 16q3.35 0 5.675 2.325Q32 20.65 32 24q0 3.35-2.325 5.675Q27.35 32 24 32ZM3.75 24.75q-.3 0-.525-.225Q3 24.3 3 24q0-.35.225-.55.225-.2.525-.2h5.5q.3 0 .525.225Q10 23.7 10 24q0 .35-.225.55-.225.2-.525.2Zm35 0q-.3 0-.525-.225Q38 24.3 38 24q0-.35.225-.55.225-.2.525-.2h5.5q.3 0 .525.225Q45 23.7 45 24q0 .35-.225.55-.225.2-.525.2ZM24 10q-.35 0-.55-.225-.2-.225-.2-.525v-5.5q0-.3.225-.525Q23.7 3 24 3q.35 0 .55.225.2.225.2.525v5.5q0 .3-.225.525Q24.3 10 24 10Zm0 35q-.35 0-.55-.225-.2-.225-.2-.525v-5.5q0-.3.225-.525Q23.7 38 24 38q.35 0 .55.225.2.225.2.525v5.5q0 .3-.225.525Q24.3 45 24 45ZM13.05 14.05l-3.2-3.1q-.25-.2-.225-.525.025-.325.225-.575.25-.25.55-.25.3 0 .55.25L14.1 13q.25.25.25.55 0 .3-.25.55-.2.2-.5.2t-.55-.25Zm24 24.1L33.9 35q-.25-.25-.25-.55 0-.3.3-.55.15-.25.45-.225.3.025.55.275l3.2 3.1q.25.2.225.525-.025.325-.225.575-.25.25-.55.25-.3 0-.55-.25ZM33.9 14.1q-.25-.2-.225-.5.025-.3.275-.55l3.1-3.2q.2-.25.525-.225.325.025.575.225.25.25.25.55 0 .3-.25.55L35 14.1q-.25.25-.55.25-.3 0-.55-.25ZM9.85 38.15q-.25-.25-.25-.55 0-.3.25-.55L13 33.9q.25-.25.55-.25.3 0 .55.25.2.2.2.5t-.25.55l-3.1 3.2q-.25.25-.55.25-.3 0-.55-.25ZM24 24Z"
/>
</svg>
</template>

View file

@ -1,26 +0,0 @@
<template>
<footer :class="[$style.footer, $attrs.class]">
<div>btw, have a nice day</div>
<div :class="$style.filler" />
<div>
(2018 - {{ new Date().getFullYear() }})
<NuxtLink to="https://github.com/muerwre/" target="_blank"
>muerwre</NuxtLink
>
</div>
</footer>
</template>
<style lang="scss" module>
.footer {
color: var(--color-text-secondary);
font-size: 0.8rem;
display: flex;
flex-direction: row;
width: 100%;
}
.filler {
flex: 1;
}
</style>

View file

@ -1,73 +0,0 @@
<template>
<nav>
<div :class="$style.logo">
<NuxtLink to="/">
<div :class="$style.title">Obsidian Garden</div>
<div :class="$style.subtitle">by muerwre</div>
</NuxtLink>
</div>
<div :class="$style.section_title">Cheatsheet</div>
<div v-for="item in parentItems" key="item._path" :class="$style.row">
<LayoutMainMenuRow
:title="item.title"
:url="item._path"
:children="item.children"
/>
</div>
</nav>
</template>
<script setup>
const { data: navigation } = await useAsyncData("navigation", () => {
return fetchContentNavigation();
});
const parentItems = navigation.value.filter(
(it) => it.children && Array.isArray(it.children) && it.children.length > 0
);
</script>
<style lang="scss" module>
.section_title {
font-family: var(--family-roboto-slab);
font-weight: 600;
margin: 2rem 0 1.5rem;
font-size: 1.6rem;
}
.logo {
display: flex;
background: url("~~/assets/svg/logo.svg") no-repeat 50% 50%;
background-size: contain;
padding: 30px 0 15px;
text-shadow: var(--color-menu-background) 3px 3px,
var(--color-menu-background) -3px -3px;
a {
width: 100%;
text-decoration: none;
color: inherit;
}
}
.logo_image {
width: 100%;
}
.row {
margin-bottom: 15px;
}
.title {
font-family: var(--family-roboto-slab);
font-weight: 700;
font-size: 2rem;
}
.subtitle {
font-size: 0.8rem;
color: var(--color-text-secondary);
}
</style>

View file

@ -1,158 +0,0 @@
<template>
<div
v-if="children?.length || !url"
:class="[$style.container, { [$style.secondary]: secondary }]"
>
<div :class="$style.heading">
{{ title }}
</div>
<div :class="$style.children">
<LayoutMainMenuRow
v-for="item in children"
key="item._path"
:title="item.title"
:url="item._path"
:children="item.children"
secondary
/>
</div>
</div>
<div v-else :class="$style.row">
<NuxtLink :to="url" :class="$style.link" :exactActiveClass="$style.active"
>{{ title }}
</NuxtLink>
</div>
</template>
<script lang="ts" setup>
interface Props {
title: string;
url?: string;
children?: Child[];
secondary?: boolean;
}
interface Child {
title: string;
_path: string;
children: Child[];
}
defineProps<Props>();
</script>
<script lang="ts">
export default defineComponent({
mounted() {
const active = document.querySelector(
`.${this.$style.link}.${this.$style.active}`
);
if (!active) return;
active?.scrollIntoView({ block: "center" });
},
});
</script>
<style lang="scss" module>
@mixin tree {
&::before {
content: " ";
background-color: var(--color-menu-line);
width: 10px;
height: 1px;
position: absolute;
top: 0.6em;
left: -17px;
}
}
.container {
position: relative;
&.secondary {
padding: 7px 2px 0;
&::before {
content: " ";
background-color: var(--color-menu-line);
width: 1px;
position: absolute;
top: -22px;
bottom: 13px;
left: -16px;
}
&:first-child::before {
top: -4px;
}
&:last-child::before {
bottom: auto;
height: 40px;
}
}
}
.row {
padding: 3px 2px;
position: relative;
&::before {
content: " ";
background-color: var(--color-menu-line);
width: 1px;
position: absolute;
top: -14px;
bottom: 13px;
left: -16px;
}
&:first-child::before {
top: -4px;
}
&:last-child::before {
bottom: auto;
height: 30px;
}
&:only-child::before {
height: 19px;
}
}
.heading {
font-weight: 600;
display: flex;
align-items: center;
text-transform: uppercase;
position: relative;
color: var(--color-menu-title);
.secondary & {
@include tree;
}
}
.link {
color: var(--color-menu-link);
text-decoration: none;
line-height: 1.4em;
position: relative;
@include tree;
&.active {
color: var(--color-menu-link-active);
font-weight: bold;
}
}
.children {
padding: 0 0 0 16px;
margin: 10px 3px;
position: relative;
}
</style>

View file

@ -1,67 +0,0 @@
<template>
<button :class="[$attrs.class, $style.button]">
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 0 24 24"
width="24"
fill="#ffffff"
:class="[$style.hamburger, { [$style.active]: active }]"
>
<rect x="0" y="3" width="24" height="2" />
<rect x="0" y="11" width="24" height="2" />
<rect x="0" y="19" width="24" height="2" />
</svg>
</button>
</template>
<script lang="ts" setup>
interface Props {
active?: boolean;
}
defineProps<Props>();
</script>
<style lang="scss" module>
.button {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
}
.hamburger {
fill: var(--color-text);
cursor: pointer;
transition: all 250ms;
&:hover {
fill: var(--color-link);
}
& > rect {
transition: transform 250ms;
}
&.active {
& > rect:nth-child(1) {
transform: rotate(45deg);
transform-origin: 2px 8px;
}
& > rect:nth-child(2) {
transform: scaleX(0);
transform-origin: 13px 0;
transition-delay: 100ms;
}
& > rect:nth-child(3) {
transform: rotate(-45deg);
transform-origin: 3px 16px;
transition-delay: 50ms;
}
}
}
</style>

View file

@ -1,52 +0,0 @@
<template>
<button
@click="toggleTheme"
:class="[$attrs.class, $style.button, { [$style.visible]: visible }]"
>
<ClientOnly>
<IconsMoon fill="currentColor" width="32" height="32" v-if="isDark" />
<IconsSun fill="currentColor" width="32" height="32" v-if="!isDark" />
</ClientOnly>
</button>
</template>
<script lang="ts" setup>
const visible = ref(false);
onMounted(() => {
visible.value = true;
});
</script>
<script lang="ts">
export default defineComponent({
methods: {
toggleTheme() {
this.$colorMode.preference =
this.$colorMode.preference === "dark" ? "light" : "dark";
},
},
computed: {
isDark() {
return this.$colorMode.preference === "dark";
},
},
});
</script>
<style lang="scss" module>
.button {
color: var(--color-text-secondary);
cursor: pointer;
transform: scale(0) rotate(180deg);
transition: all 0.25s ease-out;
&.visible {
transform: scale(1) rotate(0);
}
&:hover {
color: var(--color-text);
}
}
</style>

View file

@ -1,77 +0,0 @@
<template>
<div :class="$style.card">
<div :class="$style.grid">
<div :class="$style.icon">
<slot />
</div>
<div :class="$style.text">
<h3 :class="$style.title">{{ title }}</h3>
<UiStars :count="level" />
</div>
</div>
<div :class="$style.description">
{{ description }}
</div>
</div>
</template>
<script lang="ts" setup>
interface Props {
title: string;
description: string;
level: number;
}
defineProps<Props>();
</script>
<style lang="scss" module>
.card {
border-radius: 8px;
border: 1px solid var(--color-line);
transition: all 0.25s;
&:hover {
background-color: var(--color-line);
&,
& > .grid {
border-color: var(--color-text-secondary);
}
}
}
.grid {
display: grid;
grid-template-columns: 48px 1fr;
grid-column-gap: 16px;
align-items: center;
border-bottom: 1px solid var(--color-line);
padding: 10px;
transition: all 0.25s;
}
.title {
margin: 0 0 4px 0;
}
.icon {
display: flex;
align-items: center;
}
.text {
display: flex;
flex-direction: column;
padding-bottom: 4px;
}
.description {
font-size: 0.9rem;
line-height: 1.4rem;
color: var(--color-text-secondary);
padding: 10px;
}
</style>

View file

@ -1,115 +0,0 @@
<template>
<NuxtLink
:class="[
$style.button,
$attrs.style,
$style[`variant-${variant}`],
$style[`size-${size}`],
{
[$style.prefixed]: $slots.prefix,
[$style.suffixed]: $slots.suffix,
},
]"
:to="href"
>
<span v-if="$slots.prefix" :class="$style.prefix">
<slot name="prefix" />
</span>
<span :class="$style.title">
<slot />
</span>
<span v-if="$slots.suffix" :class="$style.suffix">
<slot name="suffix" />
</span>
</NuxtLink>
</template>
<script lang="ts" setup>
interface Props {
href: string;
size?: "md";
variant?: "outline";
}
withDefaults(defineProps<Props>(), {
size: "md",
variant: "outline",
});
</script>
<style lang="scss" module>
.button {
color: white;
border-radius: 8px;
text-decoration: none;
&.variant-outline {
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow: var(--color-text) 0 0 0 1px;
color: var(--color-text);
overflow: hidden;
position: relative;
transition: all 250ms;
&::after {
content: " ";
background: var(--color-primary);
position: absolute;
inset: 0;
transform: scale(0);
opacity: 0;
transition: all 250ms;
z-index: 0;
border-radius: 8px;
}
&:hover {
color: var(--color-background);
box-shadow: var(--color-background) 0 0 0 1px;
&::after {
transform: scale(1);
opacity: 1;
}
}
}
&.size-md {
height: 40px;
padding: 0 30px;
&.prefixed,
&.suffixed {
padding: 0 10px 0 20px;
}
&.suffixed {
padding-right: 0 20px 0 10px;
}
}
}
.title {
z-index: 1;
position: relative;
.size-md.prefixed & {
padding-left: 10px;
}
.size-md.suffixed & {
padding-right: 10px;
}
}
.prefix,
.suffix {
display: inline-flex;
position: relative;
z-index: 2;
}
</style>

View file

@ -1,14 +0,0 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24px"
viewBox="0 0 24 24"
width="24px"
fill="#ffffff"
>
<path d="M0 0h24v24H0z" fill="none" />
<path
d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"
/>
</svg>
</template>

View file

@ -1,51 +0,0 @@
<template>
<div :class="$style.rating">
<IconsIconStar
v-for="i in 5"
:width="22"
height="22"
:class="[$style.star, { [$style.filled]: i > count }]"
/>
</div>
</template>
<script lang="ts" setup>
interface Props {
count: number;
}
defineProps<Props>();
</script>
<style lang="scss" module>
@import "~~/assets/css/mixins";
.rating {
display: flex;
flex-direction: row;
margin-left: -3px;
& > img {
margin-right: 2px;
}
}
.star {
stroke: none;
fill: currentColor;
@include color_per_child(
(
var(--color-rating-1),
var(--color-rating-2),
var(--color-rating-3),
var(--color-rating-4),
var(--color-rating-5)
)
);
&.filled {
fill: var(--color-line);
}
}
</style>

View file

@ -1,100 +0,0 @@
import ReactLogo from "@/assets/svg/react-logo.svg";
import ReactNativeLogo from "@/assets/svg/react-native-logo.svg";
import ReactSSR from "@/assets/svg/react-ssr-logo.svg";
import TsLogo from "@/assets/svg/ts-logo.svg";
import GoLangLogo from "@/assets/svg/golang-logo.svg";
import HTMLLogo from "@/assets/svg/html-logo.svg";
import SVGLogo from "@/assets/svg/svg-logo.svg";
import ArchLogo from "@/assets/svg/arch-logo.svg";
import GQLLogo from "@/assets/svg/gql-logo.svg";
import SwaggerLogo from "@/assets/svg/swagger-logo.svg";
import VueLogo from "@/assets/svg/vue-logo.svg";
import PostgresLogo from "@/assets/svg/postgres-logo.svg";
import DockerLogo from "@/assets/svg/docker-logo.svg";
interface Skill {
title: string;
description?: string;
icon: string;
level: number;
}
export const skills: Skill[] = [
{
title: "React",
description: "Classes and FC-s, hooks, context, redux, redux-saga, mobx",
level: 5,
icon: ReactLogo,
},
{
title: "Typescript",
description:
"For both frontend and backend development: generics, guards, infers",
level: 4,
icon: TsLogo,
},
{
title: "SSR (Next, Gatsby)",
description: `Automated generation, incremental, static, and dynamic rendering`,
level: 4,
icon: ReactSSR,
},
{
title: "Vue.js & Nuxt",
description: `Common SPA-s and SSR blogs like this one, with composition API, and Vuex`,
level: 3,
icon: VueLogo,
},
{
title: "React Native",
description: `Basic developing and releasing experience without native modules`,
level: 3,
icon: ReactNativeLogo,
},
{
title: "Golang",
description: "Monolith and microservice apps with REST, GraphQL, and GRPC",
level: 2,
icon: GoLangLogo,
},
{
title: "Docker",
description:
"Docker, docker-compose, private registries, gitlab-ci, and drone-ci",
level: 4,
icon: DockerLogo as string,
},
{
title: "HTML, CSS, SVG",
description:
"Adaptive markup, all modern techniques, preprocessors, and CSS-in-JS",
level: 5,
icon: HTMLLogo,
},
{
title: "Linux Shell",
description:
"Linux user since 2003, can write scripts to automate my work. BTW, I use Arch!",
level: 4,
icon: ArchLogo as string,
},
{
title: "GraphQL",
description:
"Both server- and client-side. Queries, mutations, cache manipulation",
level: 4,
icon: GQLLogo as string,
},
{
title: "REST API",
description: "With Axios, fetch, express, gorilla-mux, and gin-gonic",
level: 4,
icon: SwaggerLogo as string,
},
{
title: "SQL",
description: "Base queries, JOIN-s, indexes and simpl query optimizations",
level: 2,
icon: PostgresLogo as string,
},
];

View file

@ -1,192 +0,0 @@
- Simple #dapp example for tests: [https://metamask.github.io/test-dapp/](https://metamask.github.io/test-dapp/)
- Interaction with smart contracts described in [Smart contracts](Smart%20contracts.md)
## Connecting to node
If #Metamask extension installed, `Web3.givenProvider` is available in global window. You can use [Infura](https://infura.io) or your node instead:
```typescript
import Web3 from 'web3';
// URL of your node
const PROVIDER_URL = 'https://...';
export const web3 = new Web3(Web3.givenProvider || PROVIDER_URL);
```
## Getting wallet balance
```typescript
const getBalance = async (address: string) => {
return await web3.eth.getBalance(address);
}
```
## Getting wallet address
```typescript
// first we need to authorize
const authorize = async () => {
await web3.currentProvider.request({ method: 'eth_requestAccounts' });
}
// then we can get wallet address
const getCurrentAddressUser = () => {
return web3.currentProvider.selectedAddress;
}
```
## Sending transaction
Sending `value` tokens with `memo` as value:
```typescript
const transfer = async ({
from,
to,
value,
memo,
privateKey,
gasLimit = 44000
}) => {
const nonce = await web3.eth.getTransactionCount(from);
const gasPrice = await web3.eth.getGasPrice();
const rawTx = {
from,
to,
value: web3.utils.toHex(Web3.utils.toWei(value, 'ether')),
gasLimit: web3.utils.toHex(gasLimit),
gasPrice: web3.utils.toHex(gasPrice),
nonce: web3.utils.toHex(nonce),
data: memo,
};
const privateKeyBuffer = EthUtil.toBuffer(privateKey);
const tx = new Transaction(rawTx);
tx.sign(privateKeyBuffer);
const serializedTx = tx.serialize();
return this.web3.eth.sendSignedTransaction(
`0x${serializedTx.toString('hex')}`
);
}
```
## Estimating transaction FEE
Useful to get fixed amount of tokens from user with pre-estimated fee.
```typescript
import { web3 } from '.';
const estimateFee = async ({
from,
to,
value,
memo,
}) => {
const gasPrice = await web3.eth.getGasPrice();
const gasLimit = await web3.eth.estimateGas({
from,
to,
value: web3.utils.toHex(web3.utils.toWei(value, 'ether')),
data: web3.utils.asciiToHex(memo),
}).call();
return web3.utils.fromWei(
BigInt(gasPrice.toString())
.multiply(BigInt(gasLimit.toString()))
.toString()
);
}
```
## Subscribing to wallet address change
```typescript
import { web3 } from '.';
web3.currentProvider.on('accountsChanged', callback);
```
## Watching network change
```typescript
ethereum.on('chainChanged', handler: (chainId: string) => void);
```
## Adding custom token to wallet
```typescript
window.ethereum
.request({
method: 'wallet_watchAsset',
params: {
type: 'ERC20',
options: {
address: '0xb60e8dd61c5d32be8058bb8eb970870f07233155',
symbol: 'FOO',
decimals: 18,
image: 'https://foo.io/token-image.svg',
},
},
})
.then((success) => {
if (success) {
console.log('FOO successfully added to wallet!')
} else {
throw new Error('Something went wrong.')
}
})
.catch(console.error)
```
## Changing network to custom
Checking current chainId:
```typescript
const getChainID = async () => {
return ethereum.request({ method: 'eth_chainId' })
}
```
Asking wallet to change current network:
```typescript
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x03' }], // ropsten chainID (3) in hex
});
} catch (switchError) {
// This error code indicates that the chain has not been added to MetaMask.
if (error.code === 4902) {
try {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [{
chainId: '0x03', // ropsten chainID (3) in hex
chainName: 'Ropsten Test Network',
nativeCurrency: {
name: 'ETH',
symbol: 'ETH',
decimals: 18
},
rpcUrls: ['https://ropsten.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161'],
blockExplorerUrls: ['https://ropsten.etherscan.io']
}] ,
});
} catch (addError) {
// handle "add" error
}
}
// handle other "switch" errors
}
```

View file

@ -1,248 +0,0 @@
For common functions see [Common typescript examples](Common%20typescript%20examples.md).
## Getting smart contract instance
Useful for calling smart contract methods:
```typescript
import { Contract } from 'web3-eth-contract';
import { web3 } from '.';
const getContract = (abi: object, address?: string): Contract => {
const abiFromJson = JSON.parse(JSON.stringify(abi));
return new web3.eth.Contract(abiFromJson, address);
};
export default getContract;
```
## Executing contract method
Contract has **read** and **write** methods. To get a list of methods, you can paste contract address on [https://etherscan.io/ ETH](https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7#readContract) or any other service.
**Read** methods doesn't require spending **gas**. **Write** methods cost some amount of **gas**, hence they will be executed with confirmation from user.
### Example for #Metamask without private key
```typescript
// see example below
import { getContract } from '.';
// ABI of contract
const CONTRACT_ABI = { /* ... */ };
// address for contract
const CONTRACT_ADDRESS = '0xdea164f67df4dbfe675d5271c9d404e0260f33bb';
export const executeContractMethod = async ({}) => {
// getting contract
const contract = getContract(CONTRACT_ABI, CONTRACT_ADDRESS);
// Calling write method
try {
// authorizing with Metamask
await web3.currentProvider.request({ method: 'eth_requestAccounts' });
// getting wallet address
const addressUser = web3.currentProvider.selectedAddress;
// calling "store" store method for contract
// payload should include `from` address, that matches
// current user's wallet
await contract.methods.store(0, 'Parameter').send({
from: addressUser,
});
} catch (e) {
throw new Error(e);
}
// calling read method
try {
// this method can return data
const result = await contract.methods.retrieve().call();
} catch (e) {
throw new Error(e);
}
}
```
### Node.js and React Native example
```typescript
// see example below
import { getContract } from '.';
// ABI контракта
const CONTRACT_ABI = { /* ... */ };
// contract address
const CONTRACT_ADDRESS = '0xdea164f67df4dbfe675d5271c9d404e0260f33bb';
// getting contract
const contract = getContract(CONTRACT_ABI, CONTRACT_ADDRESS);
// account's private key
const privateKey = '...';
// write-methods requires private key
const executeContractMethod = async (val: number) => {
const transaction = contract.methods.store(val);
const account = web3.eth.accounts.privateKeyToAccount(privateKey);
const options = {
to: CONTRACT_ADDRESS,
data: transaction.encodeABI(),
gas: await transaction.estimateGas({ from: account.address }),
gasPrice: await web3.eth.getGasPrice(),
};
const signed = await web3.eth.accounts.signTransaction(
options,
privateKey,
);
await web3.eth.sendSignedTransaction(signed.rawTransaction!);
};
```
### Calling a batch of contract's methods
Function calls batch of requests, returning array of results. For example:
```typescript
const requests = [
contract.method.balanceOf().call,
contract.method.getStaked().call
]
const result = await makeBatchRequest(request);
```
```typescript
const web3 = new Web3(Web3.givenProvider || PROVIDER_URL);
const makeBatchRequest = (calls: any[]) => {
try {
const web3 = getWeb3NoAccount();
const batch = new web3.BatchRequest();
const promises = calls.map((call) => {
return new Promise((resolve, reject) => {
batch.add(
call.request({}, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
})
);
});
});
batch.execute();
return Promise.all(promises);
} catch {
return null;
}
};
export default makeBatchRequest;
```
## Subscribing to smart contract events
There're different ways to subscribe for contract events. For all of them you will need following variables:
```typescript
import Web3 from 'web3';
const web3 = new Web3('YOUR_RPC_ENDPOINT_HERE');
const ABI = 'YOUR ABI HERE';
const CONTRACT_ADDRESS = 'YOUR CONTRACT ADDRESS HERE';
const myContract = new Web3.Contract(ABI, CONTRACT_ADDRESS);
```
### By accessing contract.events
```typescript
referralProgramContract.events
.RegisterUser()
.on('connected', (subscriptionId: string) => {
console.log(`| UserRegistered | events | ${subscriptionId}`);
})
.on(
'data',
async (event: {
removed: boolean;
returnValues: RegisterUserResponseInterface;
}) => {
try {
if (event.removed) {
return;
}
const { user, referrer } = event.returnValues;
console.log(user, referrer);
} catch (e) {
console.log(`| ONCE | ${e}`);
}
},
)
.on('error', (error: ErrnoException) => {
console.log(error);
});
```
### With filtering
We're listening to `Transfer` event here:
```typescript
let options = {
filter: {
value: [],
},
fromBlock: 0
};
myContract.events.Transfer(options)
.on('data', event => console.log(event))
.on('changed', changed => console.log(changed))
.on('error', err => throw err)
.on('connected', str => console.log(str))
```
### Common Subscribe method
Filtering options can also be specified:
```typescript
let options = {
fromBlock: 0,
address: ['address-1', 'address-2'], //Only get events from specific addresses
topics: [] //What topics to subscribe to
};
let subscription = ('logs', options, (err,event) => {
if (!err)
console.log(event)
});
subscription.on('data', event => console.log(event))
subscription.on('changed', changed => console.log(changed))
subscription.on('error', err => { throw err })
subscription.on('connected', nr => console.log(nr))
```
### Getting event history
Getting history for `Transfer` events for specific values. More info can be found [here](https://web3js.readthedocs.io/en/v1.2.11/web3-eth-subscribe.html#)
```typescript
//example options(optional)
let options = {
filter: {
// only get events where transfer value was 1000 or 1337
value: ['1000', '1337']
},
// number | "earliest" | "pending" | "latest"
fromBlock: 0,
toBlock: 'latest'
};
myContract.getPastEvents('Transfer', options)
.then(results => console.log(results))
.catch(err => throw err);
```

View file

@ -1,45 +0,0 @@
Grid, that places items by density. Pure #css solution. Can be used with items, that take different amount of rows/columns.
```scss
$cell: 250px;
$gap: 20px;
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax($cell, 1fr));
grid-auto-rows: 256px;
grid-auto-flow: row dense;
grid-column-gap: $gap;
grid-row-gap: $gap;
}
```
### Basic elements with double height or width
```scss
.h-2 { // takes 2 columns
grid-column-end: span 2;
}
.v-2 { // takes 2 rows
grid-row-end: span 2;
}
```
### Header, that fills all columns
```scss
.full-width {
grid-row: 1 / 2; // height: 1 row
grid-column: 1 / -1;
}
```
### Stamp element, that takes 3 rows in the top right corner
```scss
.top-right {
grid-row: 1 / 3; // height here
grid-column: -2 / -1; // width here
}
```

View file

@ -1,19 +0,0 @@
Say, we need to color `n` items by specific colors, which depend on its position. #SCSS supports [iteration over lists](https://sass-lang.com/documentation/at-rules/control/each) for that purposes:
```scss
@mixin color-per-child($colors) {
@each $color in $colors {
&:nth-child(#{index(($colors), ($color))}) {
color: $color;
}
}
}
```
Usage is simple:
```scss
.item {
@include color_per_child((#ded187, #dbde87, #bade87, #9cde87, #87deaa));
}
```

View file

@ -1,20 +0,0 @@
To test if browser supports some #CSS rules, do following:
```css
@supports (backdrop-filter: blur(5px)) {
backdrop-filter: blur(5px);
}
```
This `@mixin` will only apply rule if browser support backdrop filtering:
```scss
@mixin can_backdrop {
@supports (
(-webkit-backdrop-filter: blur(5px)) or
(backdrop-filter: blur(5px))
) {
@content;
}
}
```

View file

@ -1,15 +0,0 @@
Sample #Dockerfile for static Typescript builds such a #nextjs, #gatsby or #nuxt:
```Dockerfile
FROM node:16-alpine as builder
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn
COPY . .
# your generate command here
RUN yarn generate
FROM nginx
COPY --from=builder /app/dist /usr/share/nginx/html
```

View file

@ -1,98 +0,0 @@
Can be used with [Private docker registry](Private%20docker%20registry.md) to deploy things using #docker.
## Pushing to private docker_registry
You should specify `global_docker_login`, `global_docker_password`, `global_docker_registry` organizations variables in your **drone**. And `docker_repo` variable for your repo as `docker.yourdomain.com/your-image`.
This is example of `.droneci` for [private docker registry](Private%20docker%20registry.md):
```yaml
kind: pipeline
name: build
type: docker
platform:
os: linux
arch: amd64
steps:
- name: build-master
image: plugins/docker
when:
branch:
- master
settings:
dockerfile: Dockerfile
tag:
- ${DRONE_BRANCH}
username:
from_secret: global_docker_login
password:
from_secret: global_docker_password
registry:
from_secret: global_docker_registry
repo:
from_secret: docker_repo
```
## Docker-compose file for drone-ci
The `drone` service is ui itself and `drone-agent` is runner for builds, that can be started on different machine (or machines).
Change `secret_id`, `rpc_secret` and `drone.url` to something you like.
```yaml
version: "3"
services:
drone:
container_name: drone
image: drone/drone:latest
environment:
- DRONE_GITHUB_CLIENT_ID=secret_id
- DRONE_GITHUB_CLIENT_SECRET=client_secret
- DRONE_RPC_SECRET=rpc_secret
- DRONE_SERVER_HOST=drone.url
- DRONE_USER_CREATE="username:user,admin:true"
- DRONE_SERVER_PROTO=https
- DRONE_TLS_AUTOCERT=false
- DRONE_GIT_ALWAYS_AUTH=false
- DRONE_LOGS_DEBUG=true
- DRONE_LOGS_TRACE=true
restart: always
volumes:
- ./data:/data
ports:
- 8090:80
drone-agent:
container_name: drone__agent
image: drone/agent:latest
command: agent
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- DRONE_RPC_SERVER=https://drone.url
- DRONE_RPC_SECRET=rpc_secret
```
## Caching builds
Haven't checked that yet, but there's a [manual](https://laszlo.cloud/the-ultimate-droneci-caching-guide) from [Laszlo Fogas](https://laszlo.cloud/) about that.
## Get user info
```shell
export DRONE_SERVER=https://drone.url
export DRONE_TOKEN=password
drone info
```
## Mark user as trusted
Sometimes it won't help, then connect to drone database with sqlite and change user's trusted flag to `1`.
```shell
drone repo update $1 --trusted=true && drone repo info $1
```

View file

@ -1,44 +0,0 @@
To deploy github pages with [Drone-ci](Drone-ci.md) you will need `.drone.yml` as specified below. You also should define secrets `github_username` and `github_token` (get it [here](https://github.com/settings/tokens)) in your drone's repository setup.
Github repository should be named as `yourname.github.io` and it could be accessed at https://yourname.github.io/. Otherwise it'll be available at https://yourname.github.io/repo-name/, what you might not like.
You should create branch named `gh-pages` in that repo and setup GH Pages at `https://github.com/<yourusername>/<yourusername>.github.io/settings/pages`.
This config will update `gh-pages` branch in your project, which will contain only generated content. I know, that's bad, but there's no better way to do that with generic drone plugins.
```yaml
kind: pipeline
name: build
type: docker
platform:
os: linux
arch: amd64
steps:
- name: build
image: node:16
commands:
- yarn
- yarn generate
- rm -rf ./docs
- mv ./.output/public ./docs
- touch ./docs/.nojekyll
- name: publish
image: plugins/gh-pages
settings:
target_branch: gh-pages
username:
from_secret: github_username
password:
from_secret: github_token
```
Here we're moving `./.output/public` to `./docs`, because #nuxt creates symlink for `docs` and git can't work with that.
Also we create `.nojekyll` at the root of repo, so github's internal engine won't [ignore files that start with underscore](https://github.blog/2009-12-29-bypassing-jekyll-on-github-pages/).
## Additional reading
- [Drone Github Pages Documentation](https://plugins.drone.io/plugins/gh-pages)
- [Bypassing Jekyll on GitHub Pages](https://github.blog/2009-12-29-bypassing-jekyll-on-github-pages/)

View file

@ -1,71 +0,0 @@
Suitable to work with [Drone-ci](Drone-ci.md) for hosting private #docker images.
## Sample docker-compose for custom docker registry
This one brings up private docker registry with ui. First you'll need to generate password for it:
```shell
docker run \
--entrypoint htpasswd registry:2 \
-Bbn user mypassword > auth/registry.password
```
```yaml
version: "3"
services:
registry:
container_name: docker__registry
image: registry:2
ports:
- 5000:5000
restart: always
environment:
- REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/data
- REGISTRY_AUTH=htpasswd
- REGISTRY_AUTH_HTPASSWD_REALM=Registry
- REGISTRY_AUTH_HTPASSWD_PATH=/auth/registry.password
- REGISTRY_HTTP_SECRET=password
- REGISTRY_STORAGE_DELETE_ENABLED=true
volumes:
- ./registry/auth:/auth
- ./registry/data:/data
ui:
container_name: docker__ui
image: parabuzzle/craneoperator:latest
ports:
- 80:80
restart: always
environment:
- REGISTRY_HOST=registry
- REGISTRY_PORT=5000
- REGISTRY_PROTOCOL=http
- ALLOW_REGISTRY_LOGIN=true
- REGISTRY_ALLOW_DELETE=true
- USERNAME=registry
- PASSWORD=password
```
## Squash layers on registry
Sometimes you need to squash all layers in docker registry to free up disk space.
1. Run this command to mark oldest layers
```shell
# Try this first
docker run \
--rm anoxis/registry-cli \
-r https://registry.url \
-l user:password \
--delete \
--num 2
# Then this
docker run -it \
-v /path/to/registry/data:/registry \
-e REGISTRY_URL=https://registry.url \
-e DRY_RUN="false" \
-e REGISTRY_AUTH="user:password" \
mortensrasmussen/docker-registry-manifest-cleanup
```

View file

@ -1,17 +0,0 @@
## Setting up watchtower
[Watchtower](https://containrrr.dev/watchtower/) will automatically pull updated #docker containers. Can be used with [Private docker registry](Private%20docker%20registry.md) and [Drone-ci](Drone-ci.md).
```yaml
version: "3"
services:
watchtower:
container_name: docker__watchtower
image: v2tec/watchtower
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /home/user/.docker/config.json:/config.json
command: --interval 60 image_1 image_2
```

View file

@ -1,15 +0,0 @@
If you need to seed `.sql` dump in #docker container, just run this command. Also you can try to [rsync file with SSH](/linux/Rsync%20file%20with%20SSH) to get it from remote host.
```shell
#####
# usage: ./script.sh "/path/to/dump.sql"
#####
DUMP_PATH=$1
CONTAINER="db"
USER=root
PASSWORD=password
DB=database
cat "$DUMP_PATH" | docker exec -i $CONTAINER mysql -u$USER -p$PASSWORD $DB
```

View file

@ -1,29 +0,0 @@
[wait-for-it.sh](https://github.com/vishnubob/wait-for-it) doing a great job of waiting for different services to become alive, but on #MacOs #docker is binding port on container start, seconds before #mysql is ready to accept connections
This script waits for first successful query from database or exits with non-zero status after timeout.
Don't forget to change `$query` for the actually working one.
```shell
# Waits for mysql to become actually available
wait_for_mysql() {
query="SELECT count(*) FROM users"
timeout=180 # 3 minutes limit
i=0
while ! docker exec -it "$1" mysql --user="$2" --password="$3" -e "$query" $4 >/dev/null 2>&1; do
sleep 1;
i=$(($i+1))
if [[ ${i} -ge ${timeout} ]]; then
echo "[Error] can't properly query MySQL after ${i} secs"
exit 1;
fi
done
}
# usage: wait_for_mysql miin-mysql-dev root password database
```
[Wait for redis](Wait%20for%20redis.md)

View file

@ -1,24 +0,0 @@
[wait-for-it.sh](https://github.com/vishnubob/wait-for-it) doing a great job of waiting for different services to become alive, but on #MacOs #docker is binding port on container start, seconds before redis is ready to accept connections
This script waits for first successful ping or exits with non-zero status after 3 minutes.
```shell
# Waits for redis to become actually available
wait_for_redis() {
timeout=180 # 3 minutes
i=0
while ! docker exec -it "$1" redis-cli -h localhost -p 6379 -a "$2" ping | grep "PONG" >/dev/null 2>&1; do
sleep 1;
i=$(($i+1))
if [[ ${i} -ge ${timeout} ]]; then
echo "[Error] can't properly ping Redis container after ${i} secs"
exit 1;
fi
done
}
# usage: wait_for_redis miin-redis-dev password
```
[Wait for mysql](Wait%20for%20mysql.md)

View file

@ -1,48 +0,0 @@
Use #oauth2 login with React-Native
## Common OAuth2 providers
Can be handled by [react-native-app-auth](react-native-app-auth) by redirecting to url `com.yourapp://oauth2provider`.
### Example for #Google
```typescript
import { authorize } from 'react-native-app-auth';
const GOOGLE_OAUTH_CLIENT = '...';
// ...
const authState = await authorize({
issuer: 'https://accounts.google.com',
clientId: `${GOOGLE_OAUTH_CLIENT}.apps.googleusercontent.com`,
redirectUrl: `com.yourapp:/oauth2redirect/google`,
scopes: ['openid', 'profile'],
dangerouslyAllowInsecureHttpRequests: true,
});
```
### Example for #Yandex
```typescript
const YANDEX_OAUTH_CLIENT = '...';
const YANDEX_OAUTH_SECRET = '...'; // better hide it somehow
const APP_ID = 'com.yourapp';
const authState = await authorize({
serviceConfiguration: {
authorizationEndpoint: `https://oauth.yandex.ru/authorize?response_type=code&client_id=${YANDEX_OAUTH_CLIENT}&redirect_uri=${APP_ID}:/oauth2redirect`,
// TODO: replace it with your own backend to secure client_secret:
tokenEndpoint: `https://oauth.yandex.ru/token?grant_type=authorization_code&client_id=${YANDEX_OAUTH_CLIENT}&client_secret=${YANDEX_OAUTH_SECRET}`,
},
clientId: YANDEX_OAUTH_CLIENT,
redirectUrl: `${APP_ID}:/oauth2redirect`,
scopes: ['login:info', 'login:avatar'],
dangerouslyAllowInsecureHttpRequests: true,
});
callback(authState.accessToken);
```
## Apple ID login
[react-native-apple-authentication](https://github.com/invertase/react-native-apple-authentication) has its own [documentation](https://github.com/invertase/react-native-apple-authentication/tree/main/docs) on setting up OAuth using Apple ID.

View file

@ -1,60 +0,0 @@
Sometimes you need to keep scroll position of `FlatList` in React Native after some user interactions.
```typescript
// interact() is doing some stuff, that changes FlatList scroll size
type Props = { interact: () => void; }
const SomeList: FC<Props> = ({ interact }) => {
const scrollPosition = useRef(0);
const scrollHeight = useRef(0);
// set it to `true` before interaction and back to `false` right after
const shouldKeepScrollPosition = useRef(false);
const onScroll = useCallback(
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
scrollPosition.current = event.nativeEvent.contentOffset.y;
},
[],
);
const onContentSizeChange = useCallback((_: number, h: number) => {
if (!shouldKeepScrollPosition.current) {
scrollHeight.current = h;
return;
}
ref.current?.scrollToOffset({
offset: scrollPosition.current + (h - scrollHeight.current),
animated: false,
});
scrollHeight.current = h;
}, []);
// onInteraction wraps interaction to preserve scroll position
const onInteraction = useCallback(
() => {
shouldKeepScrollPosition.current = true;
setTimeout(() => {
interact();
}, 0);
setTimeout(() => {
shouldKeepScrollPosition.current = false;
}, 500);
},
[setSelectedSubThemes],
);
return (
<FlatList
// ...required FlatList options
ref={ref}
onContentSizeChange={onContentSizeChange}
onRefresh={onRefresh}
onScroll={onScroll}
/>
)
}

View file

@ -1,65 +0,0 @@
## Show android logcat
```shell
adb logcat com.application:I "*:S"
```
## Get .apk's SHA-256
```bash
keytool -printcert -jarfile "$1"
```
## Assemble debug release on Android
Packages release with bundled resources.
```shell
npx react-native bundle \
--platform android \
--dev false \
--entry-file index.js \
--bundle-output android/app/src/main/assets/index.android.bundle \
--assets-dest android/app/src/main/res/
cd android && ./gradlew assembleDebug
# do your stuff
./gradlew clean
```
## Send release to Android device
```shell
cd ./android \
&& ./gradlew assembleRelease \
&& adb install ./app/build/outputs/apk/release/app-release.apk
```
## Deep links
- https://zarah.dev/2022/02/08/android12-deeplinks.html
- https://developer.android.com/training/app-links/verify-site-associations#invoke-domain-verification
- https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://miin.ru&relation=delegate_permission/common.handle_all_urls
### Open deep links
```shell
# ios
xcrun simctl openurl booted $1
# android
adb shell am start -W -a android.intent.action.VIEW -d $1 \
com.application
```
### Reverify links on Android
```shell
PACKAGE="com.application"
adb shell pm set-app-links --package $PACKAGE 0 all && \
adb shell pm verify-app-links --re-verify $PACKAGE
```

View file

@ -1,106 +0,0 @@
`<ApiProvider />` component, that will handle token refresh if needed. Refresh function should, probably, be passed through component props.
```typescript
import axios from "axios";
import React, {
createContext,
FC,
PropsWithChildren,
useCallback,
useContext,
useEffect,
useRef,
} from "react";
interface APIProviderProps extends PropsWithChildren {
tokens: {
access: string;
refresh: string;
};
logout: () => void;
}
const APIContext = createContext({
client: axios.create({
baseURL: process.env.NEXT_PUBLIC_API_ENDPOINT,
}),
});
const APIProvider: FC<APIProviderProps> = ({
tokens,
logout,
children,
}) => {
const client = useRef(
axios.create({
baseURL: process.env.NEXT_PUBLIC_API_ENDPOINT,
})
).current;
const refreshTokens = useCallback<() => string>(() => {
// TODO: implement me
throw new Error("not implemented");
}, []);
useEffect(() => {
if (!tokens.access) {
return;
}
// append `access` token to all requests
const req = client.interceptors.request.use(
async (config) => {
config.headers = {
Authorization: `Bearer ${tokens.access}`,
};
return config;
},
(error) => {
Promise.reject(error);
}
);
// refreshing interceptor
const resp = client.interceptors.response.use(
(response) => {
return response;
},
async function (error) {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const newToken = refreshTokens;
return axios({
...originalRequest,
headers: {
...originalRequest.headers,
Authorization: "Bearer " + newToken,
},
});
}
logout();
return Promise.reject(error);
}
);
return () => {
axios.interceptors.request.eject(req);
axios.interceptors.request.eject(resp);
};
}, [client, tokens.access, tokens.refresh, refreshTokens, logout]);
return (
<APIContext.Provider value={{ client }}>
{children}
</APIContext.Provider>
);
};
export const useAPI = () => useContext(APIContext).client;
export { APIProvider };
```

View file

@ -1,32 +0,0 @@
If you need to cancel some request, use [axios with AbortController](https://axios-http.com/docs/cancellation). Previously axios used cancellation token, but now it's deprecated.
`AbortController` can be used with a multiple requests to cancel them at once.
```typescript
import { useCallback, useRef } from "react";
import axios from 'axios';
const client = axios.create();
export const useGetUsers = () => {
const controller = useRef(new AbortController());
const get = useCallback(async () => {
const result = await client.get("/", {
// params and props here
signal: controller.current.signal,
});
return result.data;
}, []);
const cancel = useCallback(() => {
controller.current.abort();
// controller should be rewritten or all requests will fail
controller.current = new AbortController();
}, [controller]);
return { get, cancel };
};
```

View file

@ -1,17 +0,0 @@
The topic's fully covered in the [official documentation](https://vuejs.org/guide/typescript/options-api.html#augmenting-global-properties) and in [Add global variable to window](Add%20global%20variable%20to%20window.md).
For example, you want to add global `$http` and `$translate` services to all of project's components:
```typescript
// ~/index.d.ts or ~/custom.d.ts
import axios from 'axios'
declare module 'vue' {
interface ComponentCustomProperties {
$http: typeof axios
$translate: (key: string) => string
}
}
```

View file

@ -1,16 +0,0 @@
By default [Nuxt Content Plugin](https://content.nuxtjs.org) not handling `==highlight==` links. To fix that we will create `Nitro` plugin:
```typescript
// ~/server/plugins/highlight.ts
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook("content:file:beforeParse", (file) => {
if (file._id.endsWith(".md")) {
file.body = file.body.replace(
/==([^=]+)==/gs,
`<span class="highlight">$1</span>`
);
}
});
});
```

View file

@ -1,8 +0,0 @@
Forces #git to use https even if remote url is #SSH. Useful for the networks with blocked #ssh protocol.
Put this inside your `~/.gitconfig`:
```c
[url "https://github.com"]
insteadOf = git://github.com
```

View file

@ -1,30 +0,0 @@
Shorthands for #git commands can be specified. Should be placed at `~/.gitconfig`.
```c
[alias]
flush = git clean-branches branch | grep -v master | xargs git branch -D
lol = log --oneline --graph
l = lol
c = commit -am
cv = commit --no-verify -am
p = push
pf = p --force-with-lease
ignore-now = update-index --skip-worktree
```
| **command** | **description** |
|---|---|
| `git flush` | drops all branches, except master |
| `git lol` | shows log |
|`git c` | commits with message |
| `git cv` | commits without hooks |
| `git p` | pushes |
| `git pf` | push with --force and additional check |
| `git ignore-now` | starts ignoring file from now on |

Some files were not shown because too many files have changed in this diff Show more