├── .cocorc ├── .design └── logos.ai ├── .eslintrc.json ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── figs ├── issue.png ├── pr.png ├── repo.png └── settings.png ├── package-lock.json ├── package.json ├── src └── themes │ ├── img │ ├── favicon.svg │ ├── loading.svg │ └── logo.svg │ └── scss │ ├── _utils │ └── _color-utils.scss │ ├── _vars.scss │ ├── auto.scss │ ├── dark.scss │ ├── light.scss │ └── theme │ ├── components │ ├── _avatar.scss │ ├── _boxes.scss │ ├── _editor.scss │ ├── _file-content.scss │ ├── _forms.scss │ ├── _labels.scss │ ├── _markup.scss │ ├── button │ │ └── index.scss │ ├── forms │ │ ├── _input.scss │ │ ├── _menu.scss │ │ └── index.scss │ └── index.scss │ ├── index.scss │ ├── modules │ ├── _chroma.scss │ ├── _codemirror.scss │ ├── _monaco.scss │ ├── index.scss │ ├── issues │ │ ├── _issue-sidebar.scss │ │ ├── _issue.scss │ │ ├── _list.scss │ │ └── index.scss │ ├── repo │ │ ├── _file-list.scss │ │ ├── _home.scss │ │ ├── _secondary-navbar.scss │ │ └── index.scss │ └── settings │ │ ├── _pages.scss │ │ ├── index.scss │ │ └── section │ │ ├── _content.scss │ │ ├── _nav.scss │ │ └── index.scss │ └── vars │ ├── _base.scss │ ├── _colors.scss │ └── index.scss ├── tools ├── build.js ├── deploy.js ├── minimize-css.js ├── restart.js ├── serve.js ├── tasks │ ├── copy-to.js │ ├── deploy.js │ ├── fonts.js │ ├── img.js │ ├── optimize-css.js │ ├── restart-service.js │ ├── scss.js │ └── templates.js └── utils │ ├── funcs.js │ ├── logger.js │ └── task-debouncer.js └── 🍵 lugit-theme.code-workspace /.cocorc: -------------------------------------------------------------------------------- 1 | useEmoji: true 2 | askScope: false -------------------------------------------------------------------------------- /.design/logos.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucas-labs/gitea-lugit-theme/981d64f1998fb0f25186560f4907b45979b29f64/.design/logos.ai -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "es2021": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "ecmaVersion": "latest", 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | 7 | jobs: 8 | release: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions/setup-node@v4 13 | with: 14 | node-version: 20 15 | - run: npm ci 16 | - run: npm run build 17 | - name: Create tarball 18 | run: tar czf gitea-lugit-theme.tar.gz --directory=./dist . 19 | - name: Add zips to release 20 | uses: softprops/action-gh-release@v2 21 | with: 22 | files: ./gitea-lugit-theme.tar.gz 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Node ### 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | .pnpm-debug.log* 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Optional stylelint cache 59 | .stylelintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variable files 77 | .env 78 | .env.development.local 79 | .env.test.local 80 | .env.production.local 81 | .env.local 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | .parcel-cache 86 | 87 | # Next.js build output 88 | .next 89 | out 90 | 91 | # Nuxt.js build / generate output 92 | .nuxt 93 | dist 94 | 95 | # Gatsby files 96 | .cache/ 97 | # Comment in the public line in if your project uses Gatsby and not Next.js 98 | # https://nextjs.org/blog/next-9-1#public-directory-support 99 | # public 100 | 101 | # vuepress build output 102 | .vuepress/dist 103 | 104 | # vuepress v2.x temp and cache directory 105 | .temp 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | ### Node Patch ### 133 | # Serverless Webpack directories 134 | .webpack/ 135 | 136 | # Optional stylelint cache 137 | 138 | # SvelteKit build / generate output 139 | .svelte-kit 140 | 141 | ### VisualStudioCode ### 142 | .vscode/* 143 | !.vscode/settings.json 144 | !.vscode/tasks.json 145 | !.vscode/launch.json 146 | !.vscode/extensions.json 147 | !.vscode/*.code-snippets 148 | 149 | # Local History for Visual Studio Code 150 | .history/ 151 | 152 | # Built Visual Studio Code Extensions 153 | *.vsix 154 | 155 | ### VisualStudioCode Patch ### 156 | # Ignore all local history of files 157 | .history 158 | .ionide 159 | 160 | # Support for Project snippet scope 161 | 162 | # End of https://www.toptal.com/developers/gitignore/api/node,visualstudiocode 163 | # Support for Project snippet scope 164 | 165 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | dist/ 4 | bin/ 5 | lib/ 6 | node_modules/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "useTabs": false, 4 | "tabWidth": 4, 5 | "printWidth": 90, 6 | "overrides": [ 7 | { 8 | "files": ["*.yml", "*.yaml"], 9 | "options": { 10 | "tabWidth": 2 11 | } 12 | }, 13 | { 14 | "files": ["*.scss"], 15 | "options": { 16 | "printWidth": 100 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | // 📙 sub-libs 4 | // "tools": true, 5 | ".design": true, 6 | 7 | // ⚙️ config 8 | "**/**/*code-workspace": true, 9 | ".cocorc": true, 10 | // "**/**/package.json": true, 11 | 12 | // 🔄️ CI/CD 13 | ".github": true, 14 | 15 | // 🧼 linters & styles 16 | "**/**/.prettierrc": true, 17 | "**/**/*.eslintrc.*": true, 18 | ".prettierignore": true, 19 | 20 | // 📝 readmes 21 | // "**/**/README.md": true, 22 | "LICENSE": true, 23 | 24 | // 🗑️ 25 | "node_modules": true, 26 | "package-lock.json": true, 27 | // ".gitignore": true, 28 | ".git": true, 29 | // "dist": true, 30 | ".vscode": true, 31 | 32 | // 🧪 tests 33 | "**/**/test": true, 34 | "**/**/tests": true, 35 | "**/**/*.specs.ts": true, 36 | "**/**/*.specs.js": true, 37 | }, 38 | "scss.lint.emptyRules": "ignore" 39 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [0.2.1] - 2024-10-19 6 | 7 | ### Fixes 8 | - fix: 🚑 some css customizations not showing after 1.22.3 gitea update 9 | 10 | ## [0.2.0] - 2024-10-19 11 | 12 | ### Added 13 | - Pinned the project to the Gitea 1.22.3 version. 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 lucas-labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | lucaslabsGitea theme 3 |

4 | 5 | *Theme for `lucaslabs` internal gitea server.* 6 | 7 | > [!IMPORTANT] 8 | > From version `v1.0.0` onwards, `gitea>=1.23` is required. 9 | > In case you're looking for `gitea@1.22.x` support, [`v0.2.2`](https://github.com/lucas-labs/gitea-lugit-theme/releases/tag/v0.2.2) 10 | > is the last version that supports it. 11 | 12 | ## Preview 13 | 14 | ![repository](figs/repo.png) 15 | 16 |
17 | Issue Page 18 | issue 19 |
20 | 21 |
22 | Settings Page 23 | settings 24 |
25 | 26 |
27 | PR Page 28 | pr 29 |
30 | 31 | ## Usage 32 | 33 | 1. Go to the [releases page](https://github.com/lucas-labs/gitea-lugit-theme/releases) and get the latest `gitea-lugit-theme.tar.gz` release file. 34 | 2. Place the `templates` and `public` folder in your `$GITEA_CUSTOM` directory. 35 | 3. Append the themes in your `app.ini` file: 36 | 37 | ```ini 38 | [ui] 39 | THEMES=...,dark,light,auto 40 | DEFAULT_THEME=dark # optional 41 | ``` 42 | 43 | > 💡 You can change the names of the themes by changing the name of the theme files in `public/css/theme-{name}.css` and in the `app.ini` file, accordingly. 44 | 45 | 4. Restart `gitea`. 46 | 47 | ## Credits 48 | 49 | - [`catppuccin/gitea`](https://github.com/catppuccin/gitea), these themes are based on them. 50 | 51 | ## Development 52 | 53 | ### build 54 | ```bash 55 | $ npm install 56 | $ npm run build 57 | ``` 58 | 59 | ### serve 60 | ```bash 61 | $ npm run serve -- --server path/to/gitea/custom 62 | 63 | # e.g. 64 | 65 | # on linux 66 | $ npm run serve -- --server ~/gitea/custom 67 | # on windows 68 | $ npm run serve -- -- --server c:/gitea/custom 69 | ``` 70 | 71 | ## Contributing 72 | 73 | Feel free to open an issue or a pull request. Contributions are welcome! -------------------------------------------------------------------------------- /figs/issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucas-labs/gitea-lugit-theme/981d64f1998fb0f25186560f4907b45979b29f64/figs/issue.png -------------------------------------------------------------------------------- /figs/pr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucas-labs/gitea-lugit-theme/981d64f1998fb0f25186560f4907b45979b29f64/figs/pr.png -------------------------------------------------------------------------------- /figs/repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucas-labs/gitea-lugit-theme/981d64f1998fb0f25186560f4907b45979b29f64/figs/repo.png -------------------------------------------------------------------------------- /figs/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucas-labs/gitea-lugit-theme/981d64f1998fb0f25186560f4907b45979b29f64/figs/settings.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lucas-labs/lugit-theme", 3 | "version": "1.0.1", 4 | "type": "module", 5 | "description": "Custom theme for lucaslabs' internal git server", 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "node tools/build.js", 9 | "serve": "node tools/serve.js", 10 | "deploy": "node tools/deploy.js", 11 | "restart": "node tools/restart.js", 12 | "style:check": "npx prettier -c ." 13 | }, 14 | "author": "lucas-labs", 15 | "license": "MIT", 16 | "devDependencies": { 17 | "browser-sync": "^2.29.3", 18 | "chokidar": "^3.5.3", 19 | "css-tree": "^2.3.1", 20 | "eslint": "^8.43.0", 21 | "fabric": "5.3", 22 | "imagemin-zopfli": "^7.0.0", 23 | "prettier": "^2.8.8", 24 | "sass": "1.63.6", 25 | "svgo": "^3.0.2" 26 | }, 27 | "dependencies": { 28 | "@lucas-labs/lui-micro": "^3.1.4" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/themes/img/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/themes/img/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/themes/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/themes/scss/_utils/_color-utils.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:color'; 2 | @use 'sass:map'; 3 | 4 | @function light-change($color, $amount) { 5 | @return color.adjust($color, $lightness: $amount * -1); 6 | } 7 | 8 | @function alpha-change($color, $alpha) { 9 | @return color.change($color, $alpha: $alpha); 10 | } 11 | 12 | @function color-variants-light($color, $with-base: false) { 13 | $set: ( 14 | '3%': light-change($color, -3%), 15 | '6%': light-change($color, -6%), 16 | '9%': light-change($color, -9%), 17 | '10%': light-change($color, -10%), 18 | '12%': light-change($color, -12%), 19 | '15%': light-change($color, -15%), 20 | '20%': light-change($color, -20%), 21 | '25%': light-change($color, -25%), 22 | '30%': light-change($color, -30%), 23 | '35%': light-change($color, -35%), 24 | '40%': light-change($color, -40%), 25 | '45%': light-change($color, -45%), 26 | '50%': light-change($color, -50%), 27 | '55%': light-change($color, -55%), 28 | '60%': light-change($color, -60%), 29 | '65%': light-change($color, -65%), 30 | '70%': light-change($color, -70%), 31 | '75%': light-change($color, -75%), 32 | '80%': light-change($color, -80%), 33 | '85%': light-change($color, -85%), 34 | '90%': light-change($color, -90%), 35 | '95%': light-change($color, -95%), 36 | '100%': light-change($color, -100%) 37 | ); 38 | 39 | @if $with-base { 40 | $set: map.merge((base: $color), $set); 41 | } 42 | 43 | @return $set; 44 | } 45 | 46 | @function color-variants-dark($color, $with-base: false) { 47 | $set: ( 48 | '3%': light-change($color, 3%), 49 | '6%': light-change($color, 6%), 50 | '9%': light-change($color, 9%), 51 | '10%': light-change($color, 10%), 52 | '12%': light-change($color, 12%), 53 | '15%': light-change($color, 15%), 54 | '20%': light-change($color, 20%), 55 | '25%': light-change($color, 25%), 56 | '30%': light-change($color, 30%), 57 | '35%': light-change($color, 35%), 58 | '40%': light-change($color, 40%), 59 | '45%': light-change($color, 45%), 60 | '50%': light-change($color, 50%), 61 | '55%': light-change($color, 55%), 62 | '60%': light-change($color, 60%), 63 | '65%': light-change($color, 65%), 64 | '70%': light-change($color, 70%), 65 | '75%': light-change($color, 75%), 66 | '80%': light-change($color, 80%), 67 | '85%': light-change($color, 85%), 68 | '90%': light-change($color, 90%), 69 | '95%': light-change($color, 95%), 70 | '100%': light-change($color, 100%) 71 | ); 72 | 73 | @if $with-base { 74 | $set: map.merge((base: $color), $set); 75 | } 76 | 77 | @return $set; 78 | } 79 | 80 | @function variants($color, $light: true, $dark: true, $base: true, $override-base-with: null) { 81 | $set: (); 82 | 83 | @if $light { 84 | $set: map.merge($set, (light: color-variants-light($color))); 85 | } 86 | 87 | @if $dark { 88 | $set: map.merge($set, (dark: color-variants-dark($color))); 89 | } 90 | 91 | @if $base { 92 | @if $override-base-with { 93 | $set: map.merge($set, (base: $override-base-with)); 94 | } @else { 95 | $set: map.merge($set, (base: $color)); 96 | } 97 | } 98 | 99 | @return $set; 100 | } -------------------------------------------------------------------------------- /src/themes/scss/_vars.scss: -------------------------------------------------------------------------------- 1 | $variables: ( 2 | 'font-size': ( 3 | 'xs': .714rem, 4 | 'sm': .857rem, 5 | 'md': 1rem, // 14 6 | 'lg': 1.143rem, // 16 7 | 'xl': 1.286rem, // 18 8 | '2xl': 1.429rem, // 20 9 | '3xl': 1.714rem, // 24 10 | '4xl': 2rem, // 28 11 | ), 12 | 'font-family': '-apple-system, "Segoe UI", system-ui, "SF Pro Text", Inter, Roboto, "Helvetica Neue", Arial, sans-serif', 13 | 'code-font-family': 'ui-monospace, SFMono-Regular, "Source Code Pro", "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace, var(--fonts-emoji)', 14 | 'emoji-font-family': '"Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla"', 15 | 'measure': ( 16 | '.25x': 4px, 17 | '.375x': 6px, 18 | '.5x': 8px, 19 | '.75x': 12px, 20 | '.875x': 14px, 21 | '1x': 16px, 22 | '1.25x': 20px, 23 | '1.5x': 24px, 24 | '2x': 32px, 25 | '2.5x': 40px, 26 | '3x': 48px, 27 | '4x': 64px, 28 | ), 29 | 'navbar-logo': ( 30 | 'width': 56px, 31 | 'height': 20px, 32 | ), 33 | 'repo-home': ( 34 | 'sidebar-width': 296px, 35 | ) 36 | ); -------------------------------------------------------------------------------- /src/themes/scss/auto.scss: -------------------------------------------------------------------------------- 1 | @import "./theme-light.css" (prefers-color-scheme: light); 2 | @import "./theme-dark.css" (prefers-color-scheme: dark); -------------------------------------------------------------------------------- /src/themes/scss/dark.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro' as lui; 2 | @use './_utils/color-utils' as c; 3 | @use './theme' as theme; 4 | @use './vars' as vars; 5 | 6 | $is-dark: true; 7 | $brand: #a6c6f7; 8 | 9 | $colors: ( 10 | primary: c.variants($brand), 11 | secondary: c.variants(hsl(240, 18%, 14%)), // same as elevation/6 12 | text: #e6edf3, 13 | subtle: hsl(240, 10%, 70%), // same as elevation/10 14 | palette: ( 15 | 'red': c.variants(#f38ba8), // red 16 | 'orange': c.variants(#fab387), // peach 17 | 'yellow': c.variants(#f9e2af), // yellow 18 | 'olive': c.variants(#e2f095), 19 | 'green': c.variants(#00E676), // green 20 | 'teal': c.variants(#94e2d5), // teal 21 | 'blue': c.variants(#89b4fa), // blue 22 | 'violet': c.variants(#b4befe), // lavender 23 | 'purple': c.variants(#9b6bf5), // mauve 24 | 'pink': c.variants(#f5c2e7), // pink 25 | 'brown': c.variants(#f2cdcd), // flamingo 26 | 'black': c.variants(#181825), // black 27 | 'white': c.variants(#e6edf3), // white 28 | ), 29 | elevation: ( 30 | '1': hsl(240, 33%, 01%), 31 | '2': hsl(240, 33%, 04%), 32 | '3': hsl(240, 33%, 06%), 33 | '4': hsl(240, 30%, 08%), 34 | '5': hsl(240, 28%, 12%), 35 | '6': hsl(240, 25%, 14%), 36 | '7': hsl(240, 20%, 30%), 37 | '8': hsl(240, 20%, 40%), 38 | '9': hsl(240, 10%, 50%), 39 | '10': hsl(240, 10%, 70%), 40 | '11': hsl(240, 10%, 80%), 41 | '12': hsl(240, 10%, 90%), 42 | ) 43 | ); 44 | 45 | // init lui-micro, css-vars only (no reboot, no basic) 46 | @include lui.init( 47 | $theme: (colors: $colors, variables: vars.$variables), 48 | $options: ( 49 | reboot: false, 50 | basic: false, 51 | fg-var-name: 'text', 52 | bg-var-name: 'elevation/1', 53 | ), 54 | ); 55 | 56 | @include theme.make-theme($is-dark); -------------------------------------------------------------------------------- /src/themes/scss/light.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro' as lui; 2 | @use './_utils/color-utils' as c; 3 | @use './theme' as theme; 4 | @use './vars' as vars; 5 | 6 | $is-dark: false; 7 | $brand: #6296e2; 8 | 9 | $colors: ( 10 | primary: c.variants($brand), 11 | secondary: c.variants(#bcc0cc), 12 | text: #484b60, 13 | subtle: #656c90, // same as elevation/10 14 | palette: ( 15 | 'red': c.variants(#d20f39), // red 16 | 'orange': c.variants(#fe640b), // peach 17 | 'yellow': c.variants(#df8e1d), // yellow 18 | 'olive': c.variants(#e2f095), 19 | 'green': c.variants(#34ac56), // green 20 | 'teal': c.variants(#179299), // teal 21 | 'blue': c.variants(#1e66f5), // blue 22 | 'violet': c.variants(#7287fd), // lavender 23 | 'purple': c.variants(#8652e7), // mauve 24 | 'pink': c.variants(#ea76cb), // pink 25 | 'brown': c.variants(#dd7878), // flamingo 26 | 'black': c.variants(#181825), // black 27 | 'white': c.variants(#e6edf3), // white 28 | ), 29 | elevation: ( 30 | '1': #fcfcfd, // elevation/1 31 | '2': #f6f7f9, // elevation/2 32 | '3': #eff1f5, // elevation/3 33 | '4': #e6e9ef, // elevation/4 34 | '5': #d7dce6, // elevation/5 35 | '6': #bcc0cc, // elevation/6 36 | '7': #9ba7bf, // elevation/7 37 | '8': #838fae, // elevation/8 38 | '9': #717a9f, // elevation/9 39 | '10': #656c90, // elevation/10 40 | '11': #565b77, // elevation/11 41 | '12': #484b60, // elevation/12 42 | ) 43 | ); 44 | 45 | // init lui-micro, css-vars only (no reboot, no basic) 46 | @include lui.init( 47 | $theme: (colors: $colors, variables: vars.$variables), 48 | $options: ( 49 | reboot: false, 50 | basic: false, 51 | fg-var-name: 'text', 52 | bg-var-name: 'elevation/1', 53 | ), 54 | ); 55 | 56 | @include theme.make-theme($is-dark); -------------------------------------------------------------------------------- /src/themes/scss/theme/components/_avatar.scss: -------------------------------------------------------------------------------- 1 | @mixin avatar { 2 | img.ui.avatar, .ui.avatar img, .ui.avatar svg { 3 | &:not(.org-avatar) { 4 | border-radius: 50% !important; 5 | object-fit: fill; 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/themes/scss/theme/components/_boxes.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/var' as var; 2 | @use '@lucas-labs/lui-micro/color' as color; 3 | 4 | @mixin segment { 5 | .ui.segment { 6 | margin: var.get('measure/1x') 0; 7 | padding: var.get('measure/1x'); 8 | border-radius: var(--border-radius); 9 | } 10 | 11 | .ui.segment form >*:first-child { 12 | margin-top: 0; 13 | } 14 | 15 | .ui.segment form >*:last-child { 16 | margin-bottom: 0; 17 | } 18 | 19 | .ui.top.attached.header { 20 | border-radius: var(--border-radius) var(--border-radius) 0 0; 21 | } 22 | 23 | .ui.attached.segment:has(+.ui[class*="top attached"].header), .ui.attached.segment:has(+.page.buttons), .ui.attached.segment:last-child, .ui.segment:has(+.ui.segment:not(.attached)), .ui.attached.segment:has(+.ui.modal) { 24 | border-bottom-left-radius: var(--border-radius); 25 | border-bottom-right-radius: var(--border-radius); 26 | } 27 | 28 | .ui.segments:not(.horizontal)>.segment:first-child, .ui.segments.horizontal>.segment:first-child { 29 | border-radius: var(--border-radius); 30 | } 31 | 32 | .ui.segments:not(.horizontal)>.segment:last-child, .ui.horizontal.segments>.segment:last-child { 33 | border-radius: var(--border-radius); 34 | } 35 | } 36 | 37 | @mixin comments { 38 | .comment { 39 | .content { 40 | background-color: var(--color-box-body); 41 | 42 | >.comment-header, >.ui.segment { 43 | &:before, &:after { 44 | display: none; 45 | } 46 | } 47 | 48 | .comment-header { 49 | padding: var.get('measure/.25x') var.get('measure/.25x') var.get('measure/.25x') var.get('measure/1x') !important; 50 | 51 | .comment-header-left { 52 | .text { 53 | color: color.get('subtle') !important; 54 | 55 | .author { 56 | color: color.get('text') !important; 57 | } 58 | } 59 | } 60 | 61 | .comment-header-right { 62 | * { 63 | color: color.get('subtle') !important; 64 | } 65 | 66 | .label { 67 | height: var.get('measure/1.25x', 1.25rem) !important; 68 | padding: 0px var.get('measure/.375x') !important; 69 | border-radius: var.get('measure/1x') !important; 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | .comment-list .comment>.content>div:last-child { 77 | border-bottom-left-radius: var(--border-radius) !important; 78 | border-bottom-right-radius: var(--border-radius) !important; 79 | } 80 | 81 | .comment-list .comment>.content>div:first-child { 82 | border-top-left-radius: var(--border-radius) !important; 83 | border-top-right-radius: var(--border-radius) !important; 84 | } 85 | 86 | .ui.comments .comment { 87 | margin: var.get('measure/.25x') 0 0; 88 | } 89 | } -------------------------------------------------------------------------------- /src/themes/scss/theme/components/_editor.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/var' as var; 2 | @use '@lucas-labs/lui-micro/color' as color; 3 | 4 | @mixin editor { 5 | .combo-markdown-editor { 6 | .top.tabular.menu { 7 | background-color: var(--color-box-header); 8 | border-radius: var(--border-radius) var(--border-radius) 0 0; 9 | min-height: 0px; 10 | margin-bottom: var.get('measure/1x') !important; 11 | 12 | .item { 13 | margin: -1px 0 0 -1px; // merge borders 14 | border-top-left-radius: var(--border-radius) !important; 15 | border-top-right-radius: var(--border-radius) !important; 16 | padding: var.get('measure/.5x') var.get('measure/.75x') !important; 17 | font-weight: 400; 18 | 19 | &:hover { 20 | color: var(--color-text-light-2); 21 | } 22 | 23 | &.active { 24 | color: var(--color-text); 25 | &:after { 26 | // a hacky 1 pixel "button border" to make the border 27 | // of the whole menu disappear under the active tab 28 | display: block; 29 | content: ''; 30 | position: absolute; 31 | top: 100%; 32 | right: 0; 33 | width: 100%; 34 | height: 1px; 35 | background-color: var(--color-body); 36 | } 37 | } 38 | } 39 | } 40 | 41 | .tab { 42 | .EasyMDEContainer { // legacy editor 43 | border: none !important; 44 | 45 | .editor-toolbar { 46 | border: none !important; 47 | } 48 | } 49 | 50 | markdown-toolbar, .EasyMDEContainer .editor-toolbar { 51 | padding: 0 10px !important; 52 | 53 | .markdown-toolbar-group { 54 | border: none !important; 55 | padding: 0 !important; 56 | 57 | &:not(:last-child) { 58 | &:after { 59 | content: ''; 60 | display: block; 61 | position: relative; 62 | width: 1px; 63 | flex: 1; 64 | margin: var.get('measure/.375x') var.get('measure/.375x') !important; 65 | background-color: color.get('elevation/6'); 66 | } 67 | } 68 | } 69 | 70 | .markdown-toolbar-button, button { 71 | line-height: 0; 72 | display: inline-block; 73 | color: var(--color-text-light-2) !important; 74 | padding: var.get('measure/.375x') !important; 75 | transition: background-color .1s ease; 76 | border-radius: var.get('measure/.375x') !important; 77 | height: auto; 78 | min-width: fit-content; 79 | 80 | &:hover { 81 | background-color: color.get('elevation/5') !important; 82 | } 83 | 84 | // if has attribute level 85 | &[level] { 86 | width: 34px; 87 | } 88 | 89 | // if aria-checked is true 90 | &[aria-checked="true"] { 91 | background-color: color.get('elevation/4'); 92 | } 93 | } 94 | 95 | button { 96 | &:after { 97 | vertical-align: unset !important; 98 | } 99 | } 100 | } 101 | 102 | textarea, .CodeMirror.cm-s-easymde.CodeMirror-wrap { 103 | background-color: transparent !important; 104 | border: none !important; 105 | padding: var.get('measure/1x') var.get('measure/1x') !important; 106 | } 107 | 108 | .editor-statusbar { 109 | margin-bottom: 0 !important; 110 | } 111 | 112 | &.markup { 113 | padding: 0 var.get('measure/1x') var.get('measure/1x'); 114 | } 115 | } 116 | } 117 | 118 | .combo-markdown-editor { 119 | border: 1px solid var(--color-secondary); 120 | border-radius: var(--border-radius); 121 | &:focus-within { 122 | outline: 2px solid var(--color-accent); 123 | border-radius: var(--border-radius); 124 | } 125 | 126 | textarea:focus { 127 | outline: none !important; 128 | } 129 | 130 | .ui.tab.markup[data-tab-panel=markdown-previewer] { 131 | border-bottom: 0px; 132 | } 133 | } 134 | 135 | #comment-form, .edit-content-zone, .comment-form, .comment-code-cloud form { 136 | padding: 0 !important; 137 | border: none !important; 138 | 139 | .field { 140 | margin-bottom: var.get('measure/1x') !important; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/themes/scss/theme/components/_file-content.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/var' as var; 2 | @use '@lucas-labs/lui-micro/color' as color; 3 | 4 | @mixin file-content { 5 | .non-diff-file-content { 6 | .ui.segment.list-header { 7 | border-radius: var(--border-radius); 8 | margin-bottom: var.get('measure/1x') !important; 9 | gap: var.get('measure/.5x'); 10 | padding: var.get('measure/.5x') var.get('measure/.75x'); 11 | 12 | .latest-commit { 13 | gap: var.get('measure/.5x'); 14 | } 15 | 16 | .commit-summary { 17 | color: color.get('subtle'); 18 | } 19 | 20 | .age { 21 | font-size: 12px; 22 | } 23 | } 24 | 25 | .ui.top.attached.header { 26 | border-top-left-radius: var(--border-radius); 27 | border-top-right-radius: var(--border-radius); 28 | 29 | .file-info { 30 | color: color.get('subtle') !important; 31 | } 32 | 33 | .btn-octicon { 34 | --color-text: #{color.get('subtle')}; 35 | } 36 | } 37 | 38 | .ui.attached.segment:has(+.ui[class*="top attached"].header), .ui.attached.segment:has(+.page.buttons), .ui.attached.segment:last-child, .ui.segment:has(+.ui.segment:not(.attached)), .ui.attached.segment:has(+.ui.modal) { 39 | border-top-left-radius: 0; 40 | border-top-right-radius: 0; 41 | border-bottom-left-radius: var(--border-radius); 42 | border-bottom-right-radius: var(--border-radius); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/themes/scss/theme/components/_forms.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/var' as var; 2 | @use '@lucas-labs/lui-micro/color' as color; 3 | @use './forms/' as forms; 4 | 5 | @mixin forms { 6 | @include forms.dropdown-menu; 7 | @include forms.menu; 8 | @include forms.tabular-menu; 9 | @include forms.input; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/themes/scss/theme/components/_labels.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/color' as color; 2 | @use '@lucas-labs/lui-micro/var' as var; 3 | 4 | @mixin label-signed { 5 | .label.isSigned { 6 | font-size: var.get('font-size/sm') !important; 7 | margin: 0px .25em !important; 8 | padding: 0 !important; 9 | display: inline-flex !important; 10 | gap: 0px !important; 11 | border-radius: 16px !important; 12 | 13 | --color-light-border: rgba(#{color.get('palette/green/base', 'rgb')}, 1) !important; 14 | --color-label-bg: none !important; 15 | --color-text: rgba(#{color.get('palette/green/base', 'rgb')}, 1) !important; 16 | --color-green-badge-bg: none !important; 17 | --color-green-badge-hover-bg: rgba(#{color.get('palette/green/base', 'rgb')}, 0.05) !important; 18 | --color-label-hover-bg: rgba(#{color.get('palette/green/base', 'rgb')}, 0.05) !important; 19 | --color-label-text: rgba(#{color.get('palette/green/base', 'rgb')}, 1) !important; 20 | --color-green-badge: rgba(#{color.get('palette/green/base', 'rgb')}, 1) !important; 21 | 22 | .shortsha { 23 | padding: 2px 6px 2px 8px !important; 24 | } 25 | 26 | .ui.detail.icon.button { 27 | opacity: 1 !important; 28 | padding: 2px 8px 2px 6px !important; 29 | margin: 0 !important; 30 | background: none !important; 31 | border-color: rgba(#{color.get('palette/green/base', 'rgb')}, 1) !important; 32 | } 33 | 34 | &.isVerified { 35 | .ui.detail.icon.button { 36 | padding: 2px 8px 2px 6px !important; 37 | } 38 | } 39 | } 40 | } 41 | 42 | @mixin label-default { 43 | .ui.label { 44 | border-radius: var.get('measure/1.25x'); 45 | 46 | &.scope-left { 47 | border-top-right-radius: 0; 48 | border-bottom-right-radius: 0; 49 | } 50 | 51 | &.scope-right { 52 | border-top-left-radius: 0; 53 | border-bottom-left-radius: 0; 54 | } 55 | 56 | &.green { 57 | color: var(--color-green-contrast) !important; 58 | } 59 | 60 | &.red { 61 | color: var(--color-red-contrast) !important; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/themes/scss/theme/components/_markup.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/color' as color; 2 | @use '@lucas-labs/lui-micro/var' as var; 3 | 4 | @mixin markup { 5 | .markup { 6 | .task-list-item input[type=checkbox] { 7 | margin: 0 .5em .25em -1.4em; 8 | } 9 | 10 | input[type=checkbox] { 11 | --border-radius: #{var.get('measure/.25x')}; 12 | width: var.get('measure/1x'); 13 | height: var.get('measure/1x'); 14 | margin-right: 4px; 15 | --color-input-background: #{color.get('elevation/5')}; 16 | 17 | &:checked { 18 | background-color: var(--color-primary); 19 | } 20 | 21 | &:after { 22 | --color-text: var(--color-primary-contrast); 23 | } 24 | 25 | &:not([disabled]):hover, &:not([disabled]):active { 26 | border-color: var(--color-secondary); 27 | background-color: color.get('elevation/6'); 28 | 29 | &:checked { 30 | background-color: var(--color-primary-hover); 31 | } 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/themes/scss/theme/components/button/index.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/var' as var; 2 | @use '@lucas-labs/lui-micro/color' as color; 3 | 4 | @mixin hollow { 5 | --border-radius: #{var.get('measure/.25x')}; 6 | 7 | padding: var.get('measure/.375x') var.get('measure/.5x') !important; 8 | background-color: transparent !important; 9 | font-weight: normal; 10 | 11 | button, .button { 12 | border-color: color.get('elevation/6') !important; 13 | gap: var.get('measure/.5x') !important; 14 | border-radius: var(--border-radius); 15 | 16 | &:hover { 17 | border-color: color.get('elevation/7') !important; 18 | 19 | +.label { 20 | border-left-color: color.get('elevation/7') !important; 21 | } 22 | } 23 | } 24 | 25 | &.labeled { 26 | button, .button { 27 | border-top-right-radius: 0; 28 | border-bottom-right-radius: 0; 29 | } 30 | 31 | .label { 32 | border-color: color.get('elevation/6') !important; 33 | background-color: transparent !important; 34 | 35 | border-top-left-radius: 0; 36 | border-bottom-left-radius: 0; 37 | border-top-right-radius: var(--border-radius); 38 | border-bottom-right-radius: var(--border-radius); 39 | 40 | 41 | &:hover { 42 | border-color: color.get('elevation/7') !important; 43 | } 44 | } 45 | } 46 | 47 | &:hover { 48 | border-color: color.get('elevation/7') !important; 49 | } 50 | } 51 | 52 | @mixin gitea-button { 53 | .ui.icon.buttons .button, .ui.icon.button:not(.compact) { 54 | // padding: 50px; 55 | } 56 | 57 | .code-comment-buttons-buttons { 58 | button:not(.labeled):not(.icon) { 59 | padding: .78571429em !important; 60 | } 61 | } 62 | 63 | // buttons group 64 | .ui.buttons { 65 | &.icon .button, &.icon:not(.compact) { 66 | padding: 50px 50px; 67 | } 68 | 69 | .button { 70 | &:first-child { 71 | border-top-left-radius: var(--border-radius); 72 | border-bottom-left-radius: var(--border-radius); 73 | border-top-right-radius: 0; 74 | border-bottom-right-radius: 0; 75 | } 76 | 77 | &.button:last-child { 78 | border-top-right-radius: var(--border-radius); 79 | border-bottom-right-radius: var(--border-radius); 80 | border-top-left-radius: 0; 81 | border-bottom-left-radius: 0; 82 | } 83 | 84 | border-right: none; 85 | flex: 1 0 auto; 86 | border-radius: 0; 87 | margin: 0; 88 | } 89 | } 90 | 91 | .ui.button { 92 | border-radius: var(--border-radius); 93 | transition: color .1s ease, background-color .1s ease, border-color .1s ease; 94 | padding: calc(#{var.get('measure/.375x')} - 1px) var.get('measure/1x'); 95 | font-size: var.get('font-size/md'); 96 | line-height: 20px; 97 | 98 | &.tiny { 99 | font-size: var.get('font-size/sm'); 100 | line-height: var.get('font-size/sm'); 101 | } 102 | 103 | &.basic { 104 | border-radius: var(--border-radius); 105 | } 106 | 107 | &.red, &.red.basic { 108 | border-color: var(--color-secondary); 109 | background: var(--color-button); 110 | color: var(--color-red); 111 | 112 | &:hover { 113 | background: var(--color-red-dark-2); 114 | color: var(--color-white); 115 | } 116 | 117 | &.basic { 118 | border-color: rgba(#{color.get('palette/red/base', 'rgb')}, 0.4); 119 | } 120 | } 121 | 122 | &.primary, &.red, &.basic { 123 | font-weight: 500; 124 | } 125 | 126 | &.green { 127 | color: var(--color-white); 128 | background: var(--color-green-dark-2); 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /src/themes/scss/theme/components/forms/_input.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/color' as color; 2 | @use '@lucas-labs/lui-micro/var' as var; 3 | 4 | @mixin -all-inputs($state: null) { 5 | input#{$state}, 6 | textarea#{$state}, 7 | .ui.input>input#{$state}, 8 | .ui.form input:not([type])#{$state}, 9 | .ui.form select#{$state}, 10 | .ui.form textarea#{$state}, 11 | .ui.form input[type=date]#{$state}, 12 | .ui.form input[type=datetime-local]#{$state}, 13 | .ui.form input[type=email]#{$state}, 14 | .ui.form input[type=file]#{$state}, 15 | .ui.form input[type=number]#{$state}, 16 | .ui.form input[type=password]#{$state}, 17 | .ui.form input[type=search]#{$state}, 18 | .ui.form input[type=tel]#{$state}, 19 | .ui.form input[type=text]#{$state}, 20 | .ui.form input[type=time]#{$state}, 21 | .ui.form input[type=url]#{$state}, 22 | .ui.selection.dropdown#{$state} { 23 | @content; 24 | } 25 | } 26 | 27 | @mixin input { 28 | @include -all-inputs { 29 | border-color: var(--color-input-border); 30 | color: var(--color-input-text); 31 | border-radius: var(--border-radius); 32 | line-height: 20px; 33 | padding: calc(#{var.get('measure/.375x')} - 1px) var.get('measure/.75x'); 34 | 35 | +.ui.button:last-child { 36 | border-top-right-radius: var(--border-radius); 37 | border-bottom-right-radius: var(--border-radius); 38 | } 39 | } 40 | 41 | @include -all-inputs(':hover') { 42 | border-color: var(--color-input-border); 43 | color: var(--color-input-text) 44 | } 45 | 46 | @include -all-inputs(':focus') { 47 | border-radius: var(--border-radius); 48 | border-color: var(--color-input-border); 49 | color: var(--color-input-text); 50 | outline: 2px solid var(--color-accent); 51 | background-color: color.get('elevation/2'); 52 | } 53 | 54 | @include -all-inputs('.active') { 55 | border-radius: var(--border-radius); 56 | outline: 2px solid var(--color-accent); 57 | 58 | > input.search { 59 | outline: none; 60 | } 61 | } 62 | 63 | .ui.action.input:not([class*="left action"])>input:focus { 64 | border-right-color: var(--color-input-border); 65 | z-index: 1; 66 | } 67 | 68 | .ui.action.input>.dropdown:not(:first-child):not(:last-child), 69 | .ui.action.input>.button:not(:first-child):not(:last-child), 70 | .ui.action.input>.buttons:not(:first-child):not(:last-child)>.button { 71 | border-radius: 0 !important; 72 | } 73 | 74 | .ui.action.input:not([class*="left action"])>input:focus+.ui.dropdown.selection, 75 | .ui.action.input:not([class*="left action"])>input:focus+.ui.dropdown.selection:hover, 76 | .ui.action.input:not([class*="left action"])>input:focus+.button, 77 | .ui.action.input:not([class*="left action"])>input:focus+.button:hover, 78 | .ui.action.input:not([class*="left action"])>input:focus+i.icon+.button, 79 | .ui.action.input:not([class*="left action"])>input:focus+i.icon+.button:hover { 80 | border-left-color: var(--color-input-border); 81 | } 82 | 83 | .ui.form .field>label { 84 | margin: 0 0 var.get('measure/.375x'); 85 | font-size: var.get('font-size/md'); 86 | font-weight: 600; 87 | } 88 | 89 | .ui.form textarea, 90 | .ui.form input:not([type]), 91 | .ui.form input[type=date], 92 | .ui.form input[type=datetime-local], 93 | .ui.form input[type=email], 94 | .ui.form input[type=number], 95 | .ui.form input[type=password], 96 | .ui.form input[type=search], 97 | .ui.form input[type=tel], 98 | .ui.form input[type=time], 99 | .ui.form input[type=text], 100 | .ui.form input[type=file], 101 | .ui.form input[type=url] { 102 | min-height: 2.71428571em; 103 | } 104 | 105 | .ui.selection.dropdown { 106 | min-height: 2.71428571em; 107 | >input { 108 | min-height: 0; 109 | &:focus { 110 | outline: none; 111 | } 112 | } 113 | } 114 | 115 | .ui.multiple.dropdown>.label, .ui.multiple.search.dropdown>input.search { 116 | padding: var.get('measure/.375x') var.get('measure/.75x'); 117 | margin: .14285714rem .28571429rem .14285714rem 0; 118 | } 119 | 120 | .ui.multiple.search.dropdown>input.search { 121 | padding-left: 0; 122 | } 123 | 124 | .ui.checkbox label, .ui.radio.checkbox label { 125 | margin-left: 20px !important; 126 | } 127 | } -------------------------------------------------------------------------------- /src/themes/scss/theme/components/forms/_menu.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/color' as color; 2 | @use '@lucas-labs/lui-micro/var' as var; 3 | 4 | @mixin dropdown-menu { 5 | .ui.dropdown { 6 | // dropdown menu 7 | >.menu { 8 | --border-radius: #{var.get('measure/.75x')}; 9 | 10 | opacity: 0; 11 | overflow-x: hidden; 12 | &.hidden { opacity: 0 !important;} 13 | &.visible, &.show { opacity: 1 !important; } 14 | 15 | border-radius: var(--border-radius) !important; 16 | transition: opacity .2s ease !important; 17 | box-shadow: 0px 6px 12px -3px rgba(var(--color-shadow-rgb), 0.5), 18 | 0px 6px 18px 0px rgba(var(--color-shadow-rgb), 0.1) !important; 19 | 20 | .divider { 21 | margin-top: var.get('measure/.5x') !important; 22 | margin-bottom: var.get('measure/.5x') !important; 23 | } 24 | 25 | .item:not(.comment-reaction-button) { 26 | --border-radius: #{var.get('measure/.375x')}; 27 | 28 | margin: 0 var.get('measure/.5x') !important; 29 | border-radius: var(--border-radius) !important; 30 | padding: var.get('measure/.375x') var.get('measure/.5x') !important; 31 | line-height: 1 !important; 32 | display: flex; 33 | flex-wrap: wrap; 34 | align-content: center; 35 | width: auto; 36 | 37 | &:first-of-type { 38 | margin-top: var.get('measure/.5x') !important; 39 | } 40 | 41 | &:last-of-type { 42 | margin-bottom: var.get('measure/.5x') !important; 43 | } 44 | 45 | &.clear-selection { 46 | margin-bottom: var.get('measure/.5x') !important; 47 | } 48 | } 49 | 50 | input[type="text"], input[name="search"] { 51 | --color-input-background: #{color.get('elevation/1')}; 52 | --color-input-border: #{color.get('elevation/5')}; 53 | 54 | min-height: var.get('measure/2x') !important; 55 | padding: 0 var.get('measure/.5x') !important; 56 | font-size: var.get('font-size/md') !important; 57 | border-radius: var.get('measure/.375x') !important; 58 | 59 | &:focus { 60 | --color-primary: var(--color-accent); 61 | } 62 | } 63 | 64 | .scrolling.menu { 65 | .item { 66 | gap: var.get('measure/.5x') !important; 67 | 68 | &:hover { 69 | &:before { 70 | position: absolute; 71 | top: calc(50% - 12px); 72 | left: -8px; 73 | width: 4px; 74 | height: 24px; 75 | content: ""; 76 | background-color: var(--color-accent); 77 | border-radius: 6px; 78 | } 79 | } 80 | 81 | .item-check-mark { 82 | background-color: color.get('elevation/6'); 83 | visibility: visible !important; 84 | display: inline-flex; 85 | padding: 2px; 86 | border-radius: var.get('measure/.375x'); 87 | border: 1px solid color.get('elevation/8'); 88 | 89 | svg { 90 | visibility: hidden; 91 | } 92 | } 93 | 94 | .item-secondary-info { 95 | flex-basis: 100%; 96 | padding: 0 0 0 var.get('measure/.5x'); 97 | color: color.get('subtle'); 98 | line-height: 100%; 99 | white-space: nowrap; 100 | overflow-x: hidden; 101 | 102 | small { 103 | white-space: nowrap; 104 | overflow: hidden; 105 | text-overflow: ellipsis; 106 | } 107 | } 108 | 109 | &.checked { 110 | .item-check-mark { 111 | background-color: var(--color-accent); 112 | 113 | svg { 114 | visibility: visible; 115 | } 116 | } 117 | } 118 | } 119 | } 120 | } 121 | 122 | // if not .upward 123 | &:not(.upward) { 124 | >.menu { 125 | top: calc(100% + var.get('measure/.5x')) !important; 126 | } 127 | } 128 | 129 | &.upward { 130 | >.menu { 131 | bottom: calc(100% + var.get('measure/.5x')) !important; 132 | } 133 | } 134 | 135 | &.upward.selection.visible, &.active.upward.selection { 136 | border-top-left-radius: var(--border-radius) !important; 137 | border-top-right-radius: var(--border-radius) !important; 138 | border-bottom-left-radius: var(--border-radius) !important; 139 | border-bottom-right-radius: var(--border-radius) !important; 140 | } 141 | 142 | &.selection.active, &.selection.active:hover, &.selection.active .menu, &.selection.active:hover .menu { 143 | border-color: var(--color-secondary) !important; 144 | } 145 | 146 | // select type dropdown 147 | &.selection { 148 | --border-radius: #{var.get('measure/.5x')}; 149 | border-radius: var(--border-radius) !important; 150 | 151 | &:focus, &.active { 152 | border-color: var(--color-secondary) !important; 153 | outline: 2px solid var(--color-accent); 154 | } 155 | 156 | &.active { 157 | border-radius: var(--border-radius) !important; 158 | } 159 | 160 | >.menu { 161 | border-radius: var(--border-radius) !important; 162 | border-top-width: 1px !important; 163 | border-color: var(--color-secondary) !important; 164 | flex-direction: column; 165 | gap: var.get('measure/.25x'); 166 | 167 | &.visible { 168 | display: flex; 169 | } 170 | 171 | .item { 172 | border: none !important; 173 | } 174 | } 175 | } 176 | } 177 | } 178 | 179 | @mixin menu { 180 | .ui.menu { 181 | border-radius: var(--border-radius); 182 | 183 | >.item:first-child { 184 | border-radius: var(--border-radius) 0 0 var(--border-radius); 185 | } 186 | 187 | &.compact:not(.secondary) .item:last-child { 188 | border-radius: 0 var(--border-radius) var(--border-radius) 0; 189 | } 190 | } 191 | 192 | .ui.secondary.menu { 193 | gap: var.get('measure/.25x'); 194 | 195 | &.vertical { 196 | >.item { 197 | border: none; 198 | margin: 0 0 var.get('measure/.25x') 0; 199 | border-radius: var(--border-radius) !important; 200 | } 201 | } 202 | 203 | .dropdown.item.active:hover, a.item.active:hover { 204 | color: var(--color-text); 205 | background: var(--color-hover); 206 | } 207 | } 208 | } 209 | 210 | @mixin tabular-menu { 211 | .ui.tabular.menu .active.item, .ui.tabular.menu .active.item:hover { 212 | border-radius: var(--border-radius) var(--border-radius) 0 0 !important; 213 | } 214 | } -------------------------------------------------------------------------------- /src/themes/scss/theme/components/forms/index.scss: -------------------------------------------------------------------------------- 1 | @forward './menu'; 2 | @forward './input'; -------------------------------------------------------------------------------- /src/themes/scss/theme/components/index.scss: -------------------------------------------------------------------------------- 1 | @forward './labels'; 2 | @forward './editor'; 3 | @forward './forms'; 4 | @forward './file-content'; 5 | @forward './avatar'; 6 | @forward './button'; 7 | @forward './boxes'; 8 | @forward './markup'; -------------------------------------------------------------------------------- /src/themes/scss/theme/index.scss: -------------------------------------------------------------------------------- 1 | @use './vars'; 2 | @use './components'; 3 | @use './modules'; 4 | 5 | @mixin make-theme($is-dark: true) { 6 | @include vars.base; 7 | @include vars.colors($is-dark); 8 | 9 | // components 10 | @include components.label-default; // label for GPG signed commits 11 | @include components.label-signed; // label for GPG signed commits 12 | @include components.editor; // github-style (kinda) markdown editor in comments, issues, etc. 13 | @include components.forms; // github-style dropdown menus 14 | @include components.file-content; // github-style file content 15 | @include components.avatar; // github-style avatars 16 | @include components.gitea-button; // github-style buttons 17 | @include components.segment; 18 | @include components.comments; // github-style comment boxes 19 | @include components.markup; // styling rendered for markdown content 20 | 21 | // code highlighting 22 | @include modules.chroma($is-dark); // syntax highlighting 23 | @include modules.codemirror; // code editor 24 | @include modules.monaco; // vscode-like code editor 25 | @include modules.settings; // settings & profile pages 26 | 27 | // customized pages 28 | @include modules.repo; // repo page 29 | @include modules.issues; // issues page 30 | } 31 | -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/_chroma.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/color' as color; 2 | @use '@lucas-labs/lui-micro/var' as var; 3 | 4 | @mixin chroma($dark: false) { 5 | $orange: if($dark, 'palette/orange/base', 'palette/orange/dark/15%'); 6 | $blue: if($dark, 'palette/blue/base', 'palette/blue/dark/15%'); 7 | $fg: 'text'; 8 | $subtle: 'subtle'; 9 | $more-subtle: if($dark, 'elevation/8', 'elevation/8'); 10 | $brown: if($dark, 'palette/brown/base', 'palette/brown/dark/15%'); 11 | $red: if($dark, 'palette/red/base', 'palette/red/dark/15%'); 12 | $green: if($dark, 'palette/green/base', 'palette/green/dark/10%'); 13 | $teal: if($dark, 'palette/teal/base', 'palette/teal/dark/15%'); 14 | $purple: if($dark, 'palette/purple/base', 'palette/purple/dark/10%'); 15 | $yellow: if($dark, 'palette/yellow/base', 'palette/yellow/dark/25%'); 16 | $violet: if($dark, 'palette/violet/base', 'palette/violet/dark/50%'); 17 | $pink: if($dark, 'palette/pink/base', 'palette/pink/dark/25%'); 18 | $blwh: if($dark, 'palette/white/base', 'palette/black/base'); 19 | 20 | .chroma { 21 | .code-inner { 22 | font: 14px var.get('code-font-family'); 23 | } 24 | 25 | .bp { color: color.get($orange); font-weight: 600;} // NameBuiltinPseudo 26 | .c { color: color.get($subtle); font-style: italic; } // Comment 27 | .c1 { color: color.get($subtle); font-style: italic; } // CommentSingle 28 | .ch { color: color.get($subtle); font-style: italic; } // CommentHashbang 29 | .cm { color: color.get($subtle); font-style: italic; } // CommentMultiline 30 | .cp { color: color.get($blue); } // CommentPreproc 31 | .cpf { color: color.get($blue); } // CommentPreprocFile 32 | .cs { color: color.get($subtle); } // CommentSpecial 33 | .dl { color: color.get($blue); } // LiteralStringDelimiter 34 | .fm {} // NameFunctionMagic 35 | .g {} // Generic 36 | .gd { color: color.get($blwh); background-color: rgba(#{color.get($red, 'rgb')}, .25); } // GenericDeleted 37 | .ge { font-style: italic; } // GenericEmph 38 | .gh { color: color.get($teal); } // GenericHeading 39 | .gi { color: color.get($blwh); background-color: rgba(#{color.get($green, 'rgb')}, 0.3); } // GenericInserted 40 | .gl {} // GenericUnderline 41 | .go { color: color.get($orange); } // GenericOutput 42 | .gp { color: color.get($subtle); font-weight: bold; } // GenericPrompt 43 | .gr { color: color.get($brown); } // GenericError 44 | .gs { font-weight: bold; } // GenericStrong 45 | .gt { color: color.get($brown); } // GenericTraceback 46 | .gu { color: color.get($teal); } // GenericSubheading 47 | .il { color: color.get($orange); } // LiteralNumberIntegerLong 48 | .k { color: color.get($purple); font-weight: 600; } // Keyword 49 | .kc { color: color.get($yellow); } // KeywordConstant 50 | .kd { color: color.get($purple); font-weight: 600; } // KeywordDeclaration 51 | .kn { color: color.get($yellow); } // KeywordNamespace 52 | .kp { color: color.get($purple); font-weight: 600; } // KeywordPseudo 53 | .kr { color: color.get($purple); font-weight: 600;} // KeywordReserved 54 | .kt { color: color.get($yellow); } // KeywordType 55 | .l {} // Literal 56 | .ld {} // LiteralDate 57 | .m { color: color.get($orange); } // LiteralNumber 58 | .mb { color: color.get($orange); } // LiteralNumberBin 59 | .mf { color: color.get($orange); } // LiteralNumberFloat 60 | .mh { color: color.get($orange); } // LiteralNumberHex 61 | .mi { color: color.get($orange); } // LiteralNumberInteger 62 | .mo { color: color.get($orange); } // LiteralNumberOct 63 | .n { color: color.get($violet); } // Name 64 | .na { color: color.get($yellow); } // NameAttribute 65 | .nb { color: color.get($orange); font-weight: 600; } // NameBuiltin 66 | .nc { color: color.get($pink); } // NameClass 67 | .nd { color: color.get($pink); } // NameDecorator 68 | .ne { color: color.get($brown); } // NameException 69 | .nf { color: color.get($blue); } // NameFunction 70 | .ni { color: color.get($pink); } // NameEntity 71 | .nl { color: color.get($yellow); } // NameLabel 72 | .nn { color: color.get($yellow); } // NameNamespace 73 | .no { color: color.get($yellow); } // NameConstant 74 | .nt { color: color.get($purple); } // NameTag 75 | .nv { color: color.get($orange); } // NameVariable 76 | .nx { color: color.get($orange); } // NameOther 77 | .o { color: color.get($teal); } // Operator 78 | .ow { color: color.get($teal); font-weight: bold; } // OperatorWord 79 | .p { color: color.get($subtle); } // Punctuation 80 | .py {} // NameProperty 81 | .s { color: color.get($green); } // LiteralString 82 | .s1 { color: color.get($green); } // LiteralStringSingle 83 | .s2 { color: color.get($green); } // LiteralStringDouble 84 | .sa { color: color.get($green); } // LiteralStringAffix 85 | .sb { color: color.get($green); } // LiteralStringBacktick 86 | .sc { color: color.get($green); } // LiteralStringChar 87 | .sd { color: color.get($green); } // LiteralStringDoc 88 | .se { color: color.get($blue); } // LiteralStringEscape 89 | .sh { color: color.get($green); } // LiteralStringHeredoc 90 | .si { color: color.get($green); } // LiteralStringInterpol 91 | .sr { color: color.get($blue); } // LiteralStringRegex 92 | .ss { color: color.get($green); } // LiteralStringSymbol 93 | .sx { color: color.get($green); } // LiteralStringOther 94 | .vc { color: color.get($yellow); } // NameVariableClass 95 | .vg { color: color.get($orange); } // NameVariableGlobal 96 | .vi { color: color.get($yellow); } // NameVariableInstance 97 | .vm {} // NameVariableMagic 98 | .w { color: color.get($more-subtle); } // TextWhitespace 99 | } 100 | } -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/_codemirror.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/color' as color; 2 | 3 | @mixin codemirror { 4 | .CodeMirror, 5 | .CodeMirror.cm-s-default, 6 | .CodeMirror.cm-s-paper { 7 | .cm-property { 8 | color: color.get('text') 9 | } 10 | 11 | .cm-header { 12 | color: color.get('text') 13 | } 14 | 15 | .cm-quote { 16 | color: color.get('palette/green/base') 17 | } 18 | 19 | .cm-keyword { 20 | color: color.get('palette/purple/base'); 21 | } 22 | 23 | .cm-atom { 24 | color: color.get('palette/red/base'); 25 | } 26 | 27 | .cm-number { 28 | color: color.get('palette/orange/base'); 29 | } 30 | 31 | .cm-def { 32 | color: color.get('text') 33 | } 34 | 35 | .cm-variable-2 { 36 | color: color.get('palette/blue/base'); 37 | } 38 | 39 | .cm-variable-3 { 40 | color: color.get('palette/teal/base'); 41 | } 42 | 43 | .cm-comment { 44 | color: color.get('elevation/6') 45 | } 46 | 47 | .cm-string { 48 | color: color.get('palette/green/base') 49 | } 50 | 51 | .cm-string-2 { 52 | color: color.get('palette/green/base') 53 | } 54 | 55 | .cm-meta { 56 | color: color.get('palette/orange/base'); 57 | } 58 | 59 | .cm-qualifier { 60 | color: color.get('palette/orange/base'); 61 | } 62 | 63 | .cm-builtin { 64 | color: color.get('palette/orange/base'); 65 | } 66 | 67 | .cm-bracket { 68 | color: color.get('text') 69 | } 70 | 71 | .cm-tag { 72 | color: color.get('palette/yellow/base'); 73 | } 74 | 75 | .cm-attribute { 76 | color: color.get('palette/yellow/base'); 77 | } 78 | 79 | .cm-hr { 80 | color: color.get('elevation/9'); 81 | } 82 | 83 | .cm-url { 84 | color: color.get('palette/blue/base'); 85 | } 86 | 87 | .cm-link { 88 | color: color.get('palette/blue/base'); 89 | } 90 | 91 | .cm-error { 92 | color: color.get('palette/red/base'); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/_monaco.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/color' as color; 2 | 3 | @mixin monaco { 4 | $surface0: #{color.get('elevation/4')}; 5 | $subtext0: #{color.get('elevation/10')}; 6 | $overlay2: #{color.get('elevation/9')}; 7 | $mantle: #{color.get('elevation/0')}; 8 | $base: #{color.get('elevation/3')}; 9 | $accent: #{color.get('primary/base')}; 10 | $text: #{color.get('text')}; 11 | $mauve: #{color.get('palette/purple/base')}; 12 | $peach: #{color.get('palette/orange/base')}; 13 | $teal: #{color.get('palette/teal/base')}; 14 | $green: #{color.get('palette/green/base')}; 15 | $blue: #{color.get('palette/blue/base')}; 16 | $pink: #{color.get('palette/pink/base')}; 17 | 18 | .monaco-editor { 19 | --vscode-editor-background: #{$mantle} !important; 20 | --vscode-editorGutter-background: #{$mantle} !important; 21 | 22 | // selected text 23 | .selected-text { 24 | background-color: $surface0 !important; 25 | } 26 | // line numbers 27 | .margin-view-overlays .line-numbers { 28 | color: $subtext0 !important; 29 | } 30 | .line-numbers.active-line-number { 31 | color: $accent !important; 32 | } 33 | 34 | // current / cursor line 35 | .view-overlays .current-line, 36 | .margin-view-overlays .current-line-margin { 37 | background-color: #{color.get('elevation/2')} !important; 38 | } 39 | 40 | // Note: all of the hotpink stuff is there so it's easily visible, since these editor scope mappings are a mess 41 | 42 | // plaintext 43 | .mtk1 { 44 | color: $text !important; 45 | } 46 | .mtk2 { 47 | color: #ff69b4 !important; 48 | } 49 | // decorators 50 | .mtk3 { 51 | color: $peach !important; 52 | } 53 | // shell arguments 54 | .mtk4 { 55 | color: $teal !important; 56 | } 57 | // css constants & pre-defineds 58 | .mtk5 { 59 | color: $text !important; 60 | } 61 | // keywords 62 | .mtk6 { 63 | color: $mauve !important; 64 | } 65 | // numbers 66 | .mtk7 { 67 | color: $peach !important; 68 | } 69 | // comments 70 | .mtk8 { 71 | color: $overlay2 !important; 72 | } 73 | // sometimes a keyword, apparently 74 | .mtk9 { 75 | color: $mauve !important; 76 | } 77 | // braces, brackets, parentheses 78 | .mtk10 { 79 | color: $subtext0 !important; 80 | } 81 | // arrow brackets & equal signs in HTML 82 | .mtk11 { 83 | color: $teal !important; 84 | } 85 | // @ sign in javascript ¯\_(ツ)_/¯ 86 | .mtk12 { 87 | color: $teal !important; 88 | } 89 | .mtk13 { 90 | color: #ff69b4 !important; 91 | } 92 | .mtk14 { 93 | color: #ff69b4 !important; 94 | } 95 | // regex, css classnames, and HTML keywords (huh) 96 | .mtk15 { 97 | color: $mauve !important; 98 | } 99 | // shebangs 100 | .mtk16 { 101 | color: $overlay2 !important; 102 | } 103 | .mtk17 { 104 | color: #ff69b4 !important; 105 | } 106 | .mtk18 { 107 | color: #ff69b4 !important; 108 | } 109 | // glob operator i guess 110 | .mtk19 { 111 | color: $teal !important; 112 | } 113 | .mtk20 { 114 | color: #ff69b4 !important; 115 | } 116 | // strings 117 | .mtk21 { 118 | color: $green !important; 119 | } 120 | .mtk22 { 121 | color: #ff69b4 !important; 122 | } 123 | // functions 124 | .mtk23 { 125 | color: $blue !important; 126 | } 127 | // shell variables 128 | .mtk24 { 129 | color: $peach !important; 130 | } 131 | // weird variables 132 | .mtk25 { 133 | color: $pink !important; 134 | } 135 | 136 | // .bracket-highlighting-0 { 137 | // color: color.mix($text, $red, 40%) !important; 138 | // } 139 | // .bracket-highlighting-1 { 140 | // color: color.mix($text, $peach, 40%) !important; 141 | // } 142 | // .bracket-highlighting-2 { 143 | // color: color.mix($text, $yellow, 40%) !important; 144 | // } 145 | // .bracket-highlighting-3 { 146 | // color: color.mix($text, $green, 40%) !important; 147 | // } 148 | // .bracket-highlighting-4 { 149 | // color: color.mix($text, $blue, 40%) !important; 150 | // } 151 | // .bracket-highlighting-5 { 152 | // color: color.mix($text, $mauve, 40%) !important; 153 | // } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/index.scss: -------------------------------------------------------------------------------- 1 | @forward './repo'; 2 | @forward './issues'; 3 | @forward './chroma'; 4 | @forward './codemirror'; 5 | @forward './monaco'; 6 | @forward './settings'; -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/issues/_issue-sidebar.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/var' as var; 2 | @use '@lucas-labs/lui-micro/color' as color; 3 | @use '../../components/button' as button; 4 | 5 | @mixin sidebar { 6 | --spc: #{var.get('measure/1x')}; // var(--spc) 16px 7 | --spc-7_8: calc(var(--spc) * .875); // 7/8 of the base var(--spc-7_8) 14px 8 | --spc-6_8: calc(var(--spc) * .750); // 6/8 = 3/4 of the base var(--spc-6_8) 12px 9 | --spc-4_8: calc(var(--spc) * .500); // 4/8 = 1/2 of the base var(--spc-4_8) 8px 10 | --spc-3_8: calc(var(--spc) * .375); // 3/8 of the base var(--spc-3_8) 6px 11 | --spc-2_8: calc(var(--spc) * .250); // 2/8 = 1/4 of the base var(--spc-2_8) 4px 12 | 13 | display: flex; 14 | flex-direction: column; 15 | border: none !important; 16 | color: var(--color-text-light-2) !important; 17 | padding: 0 !important; 18 | 19 | // reset padding and margin for all children 20 | >*, .issue-sidebar-combo>*, >text+*, >.ui>* { 21 | margin: unset !important; 22 | padding: unset !important; 23 | font-size: var.get('font-size/sm') !important; 24 | } 25 | 26 | // alignment for main children elements 27 | >.ui, >.text, >.ui { 28 | padding-left: var(--spc-4_8) !important; 29 | padding-right: 0 !important; 30 | } 31 | 32 | .ui.ui.ui.compact.grid>.column:not(.row), .ui.ui.ui.compact.grid>.row>.column { 33 | padding-left: unset; 34 | padding-right: unset; 35 | } 36 | 37 | // reset for all actionable elements 38 | button, input, .dropdown, .ui.grid>.row>[class*="two wide"].column, .ui.grid>.column.row>[class*="two wide"].column, .ui.grid>[class*="two wide"].column, .ui.column.grid>[class*="two wide"].column { 39 | padding: 0 !important; 40 | margin: 0 !important; 41 | min-height: 0 !important; 42 | min-width: 0 !important; 43 | } 44 | 45 | // -------------------------------------------------------------- // 46 | // ---------------------- Elements Styling ---------------------- // 47 | // -------------------------------------------------------------- // 48 | 49 | a:hover { 50 | text-decoration: none !important; 51 | } 52 | 53 | // dividers 54 | >.divider { 55 | width: calc(100% - var(--spc-4_8)) !important; 56 | align-self: flex-end; 57 | margin: var(--spc-7_8) 0 !important; 58 | } 59 | 60 | 61 | // select branch dropdown 62 | .select-branch { 63 | align-self: flex-end; 64 | 65 | .branch-dropdown-button { 66 | @include button.hollow; 67 | } 68 | 69 | +.divider { 70 | border: none !important; 71 | margin: var(--spc-4_8) 0 !important; 72 | } 73 | 74 | .menu { 75 | --color-menu: #{color.get('elevation/3')}; 76 | 77 | .branch-tag-item.active, .reference-list-menu, .reference-list-menu .item { 78 | --color-menu: #{color.get('elevation/4')} !important; 79 | } 80 | 81 | .branch-tag-item { 82 | --border-radius: #{var(--spc-4_8)}; 83 | 84 | &:hover { 85 | color: currentColor !important; 86 | } 87 | } 88 | } 89 | } 90 | 91 | .issue-sidebar-combo { 92 | display: flex; 93 | flex-direction: column; 94 | gap: var(--spc-4_8) !important; 95 | 96 | > * { 97 | padding-left: var(--spc-4_8) !important; 98 | padding-right: var(--spc-4_8) !important; 99 | } 100 | 101 | .ui.dropdown { 102 | padding-top: var(--spc-3_8) !important; 103 | padding-bottom: var(--spc-3_8) !important; 104 | font-size: var.get('font-size/sm'); 105 | transition: background-color .1s ease; 106 | border-radius: var(--border-radius) !important; 107 | 108 | >a { 109 | flex: 1; 110 | display: flex; 111 | justify-content: space-between; 112 | 113 | &:hover { 114 | color: currentColor !important; 115 | text-decoration: none !important; 116 | } 117 | } 118 | 119 | &:hover { 120 | background-color: rgba(#{color.get('elevation/6', 'rgb')}, .5); 121 | } 122 | } 123 | } 124 | 125 | // direct text children 126 | >.text { 127 | // background-color: orange !important; 128 | flex: 1; 129 | display: flex !important; 130 | justify-content: space-between; 131 | font-weight: 500 !important; 132 | margin-bottom: var(--spc-6_8) !important; 133 | 134 | strong { 135 | font-weight: 500 !important; 136 | } 137 | } 138 | 139 | .watching { 140 | display: flex; 141 | flex-direction: column; 142 | gap: var(--spc-6_8) !important; 143 | 144 | button { 145 | font-weight: 500; 146 | padding: var(--spc-4_8) var(--spc-6_8) !important; 147 | border-radius: var(--border-radius) !important; 148 | 149 | svg { 150 | color: color.get('subtle') !important; 151 | } 152 | } 153 | } 154 | 155 | .depending { 156 | // title 157 | >.text { 158 | display: inline-block; 159 | margin-bottom: var(--spc-6_8) !important; 160 | } 161 | 162 | >p { 163 | margin-bottom: var(--spc-4_8) !important; 164 | } 165 | 166 | .divided.list { 167 | display: flex; 168 | flex-direction: column; 169 | gap: var(--spc) !important; 170 | 171 | .dependency { 172 | border: none !important; 173 | 174 | .item-left { 175 | a { // issue 176 | 177 | } 178 | 179 | div.text { // repo 180 | font-size: var.get('font-size/xs') !important; 181 | } 182 | } 183 | 184 | // if not the first dependency in the list, we add a :before element to serve as divider 185 | &:not(:first-child) { 186 | position: relative; 187 | &:before { 188 | content: ''; 189 | display: block; 190 | position: absolute; 191 | top: -8px; 192 | left: 0; 193 | height: 1px; 194 | width: 100%; 195 | background-color: color.get('elevation/4'); 196 | } 197 | } 198 | } 199 | } 200 | 201 | #new-dependency-drop-list { 202 | padding: var(--spc-3_8) var(--spc-6_8) !important; 203 | // make radius to the right be 0 to merge with the add button 204 | border-top-right-radius: 0 !important; 205 | border-bottom-right-radius: 0 !important; 206 | 207 | &.active { 208 | // restore right border to 1px when active 209 | border-top-left-radius: var(--border-radius) !important; 210 | border-bottom-left-radius: var(--border-radius) !important; 211 | border-color: color.get('elevation/5') !important; 212 | outline: 2px solid var(--color-accent); 213 | } 214 | 215 | input { 216 | padding: var(--spc-3_8) var(--spc-6_8) !important; 217 | line-height: 20px !important; 218 | } 219 | } 220 | 221 | button { 222 | padding: var(--spc-4_8) !important; 223 | } 224 | } 225 | 226 | // reference 227 | div.ui.equal.width.compact.grid { 228 | button { 229 | @include button.hollow; 230 | display: flex; 231 | color: color.get('subtle') !important; 232 | width: unset !important; 233 | } 234 | } 235 | 236 | // issue due date form 237 | .issue-due-form { 238 | * { 239 | color: color.get('subtle') !important; 240 | } 241 | 242 | // input of type date 243 | input[type="date"] { 244 | padding: var(--spc-4_8) var(--spc-6_8) !important; 245 | border-top-left-radius: var(--border-radius); 246 | border-bottom-left-radius: var(--border-radius); 247 | } 248 | 249 | button { 250 | padding: var(--spc-4_8) !important; 251 | border-top-right-radius: var(--border-radius) !important; 252 | border-bottom-right-radius: var(--border-radius) !important; 253 | } 254 | } 255 | 256 | // direct form child 257 | >form, .form { 258 | display: flex; 259 | flex-direction: column; 260 | gap: var(--spc-4_8) !important; 261 | } 262 | 263 | // buttons at the end of the sidebar 264 | >form button, >button { 265 | font-size: var.get('font-size/sm') !important; 266 | border: none !important; 267 | background: transparent !important; 268 | padding: var(--spc-3_8) var(--spc-4_8) !important; 269 | justify-content: start !important; 270 | gap: var(--spc-4_8) !important; 271 | margin-bottom: var(--spc-2_8) !important; 272 | border-radius: var(--border-radius) !important; 273 | 274 | svg { 275 | color: color.get('subtle') !important; 276 | } 277 | 278 | &:hover { 279 | background: color.get('elevation/4') !important; 280 | } 281 | 282 | // if the data-modal attr is #sidebar-delete-issue, make the color red 283 | &[data-modal="#sidebar-delete-issue"] { 284 | color: color.get('palette/red/base') !important; 285 | svg { 286 | color: color.get('palette/red/base') !important; 287 | } 288 | 289 | &:hover { 290 | background: rgba(#{color.get('palette/red/dark/15%', 'rgb')}, 0.1) !important; 291 | } 292 | } 293 | } 294 | } -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/issues/_issue.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/var' as var; 2 | @use '@lucas-labs/lui-micro/color' as color; 3 | @use '../../components/button' as button; 4 | @use './issue-sidebar'; 5 | 6 | @mixin apply-styles { 7 | .issue-content-right { 8 | @include issue-sidebar.sidebar; 9 | } 10 | 11 | .issue-title-header { 12 | margin-bottom: var.get('measure/1.5x'); 13 | padding-bottom: var.get('measure/.75x'); 14 | border-bottom: 1px solid color.get('elevation/5'); 15 | 16 | .issue-title { 17 | .index { 18 | font-weight: 300; 19 | } 20 | } 21 | 22 | .issue-title-meta { 23 | color: color.get('subtle'); 24 | } 25 | 26 | &:has(+ .ui.pull.tabs) { // if the next sibling is a tab (e.g. pull request tab selector) 27 | margin-bottom: var.get('measure/1x'); 28 | padding-bottom: var.get('measure/.5x'); 29 | border-bottom: none; 30 | } 31 | } 32 | 33 | .issue-content { 34 | column-gap: var.get('measure/1.5x'); 35 | } 36 | 37 | .issue-content-left { 38 | .timeline-avatar img { 39 | border-radius: 50% !important; 40 | } 41 | } 42 | 43 | .timeline-item { 44 | &.event { 45 | .badge { 46 | // and not contains a tw-bg-* class 47 | &:not([class*="tw-bg-"]) { 48 | color: var(--color-timeline-badge-fg) !important; 49 | } 50 | 51 | &.tw-bg-green.tw-text-white { 52 | color: var(--color-green-contrast) !important; 53 | } 54 | 55 | &.tw-bg-red.tw-text-white { 56 | color: var(--color-red-contrast) !important; 57 | } 58 | } 59 | 60 | .ui.segments.conversation-holder { 61 | margin-left: var.get('measure/.5x'); 62 | border-radius: var(--border-radius); 63 | } 64 | } 65 | 66 | &.comment { 67 | // comment form at the end 68 | &.form:not(.issue-content) { 69 | display: flex; 70 | align-items: flex-start; 71 | background-color: var(--color-body); 72 | gap: var.get('measure/1x'); 73 | left: -68px !important; 74 | width: calc(100% + 68px - 16px) !important; 75 | 76 | @media (max-width: 768px) { 77 | left: 0 !important; 78 | margin-left: -16px !important; 79 | width: auto !important; 80 | } 81 | 82 | .timeline-avatar { 83 | display: block; 84 | position: relative !important; 85 | left: unset !important; 86 | flex: 0 0 auto !important; 87 | } 88 | 89 | .content { 90 | // fake title to mimic github new issue page 91 | &:before { 92 | display: block; 93 | content: 'Add a comment'; 94 | font-weight: 600; 95 | margin-bottom: var.get('measure/1x'); 96 | margin-top: var.get('measure/.5x'); 97 | font-size: var.get('font-size/lg'); 98 | } 99 | 100 | display: block; 101 | position: relative !important; 102 | margin-left: 0 !important; 103 | flex: 1; 104 | 105 | .ui.segment { 106 | padding: 0 !important; 107 | border: none !important; 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | .repository.view.issue { 115 | --avatar-size: 24px; 116 | 117 | .comment-list .ui.comments { 118 | background-color: transparent; 119 | gap: var.get('measure/1x'); 120 | position: relative; 121 | } 122 | 123 | .comment-list { 124 | .ui.comment-code-cloud.segment { 125 | padding: 0 var.get('measure/.5x') var.get('measure/.5x') !important; 126 | 127 | .ui.comments { 128 | gap: var.get('measure/.5x'); 129 | 130 | &:before { 131 | display: block; 132 | content: ""; 133 | position: absolute; 134 | top: 0; 135 | bottom: 0; 136 | left: calc((var(--avatar-size) / 2) - 1px); 137 | top: 16px; 138 | width: 3px; 139 | background-color: var(--color-timeline); 140 | opacity: 50%; 141 | } 142 | 143 | .comment.code-comment .content.comment-container{ 144 | background-color: transparent !important; 145 | 146 | .header.comment-header { 147 | .comment-header-left { 148 | gap: var.get('measure/.5x'); 149 | .avatar img { 150 | z-index: 1 !important; 151 | width: var(--avatar-size) !important; 152 | height: var(--avatar-size) !important; 153 | } 154 | } 155 | } 156 | } 157 | } 158 | 159 | .code-comment-buttons { 160 | margin: 0!important; 161 | } 162 | 163 | .comment-form { 164 | margin-top: var.get('measure/1x'); 165 | 166 | .field.footer { 167 | margin: 0 !important; 168 | padding: 0 !important; 169 | 170 | button { 171 | padding: var.get('measure/.5x') var.get('measure/1x') !important; 172 | } 173 | } 174 | } 175 | } 176 | } 177 | } 178 | 179 | .repository .ui.tabs.divider { 180 | margin-bottom: var.get('measure/1x'); 181 | } 182 | 183 | .repository .diff-detail-box { // diff page detail box 184 | padding: var.get('measure/.5x') 0; 185 | height: 60px; 186 | } 187 | 188 | .repository.view.issue .pull.tabs.container { 189 | + div { 190 | >.diff-detail-box:first-child { 191 | margin: calc(var.get('measure/1x') * -1) 0 0 0; 192 | } 193 | } 194 | } 195 | 196 | .repository #diff-container { 197 | #diff-file-tree { 198 | top: 60px; 199 | .diff-file-tree-items { 200 | margin: 0; 201 | } 202 | } 203 | 204 | column-gap: var.get('measure/1x'); 205 | 206 | .diff-file-body tr.tag-code:last-child td:first-child, .diff-file-body tr.tag-code:last-child td:first-child * { 207 | border-bottom-left-radius: calc(var(--border-radius) - 2px); 208 | } 209 | 210 | .diff-file-body tr.tag-code:last-child td:last-child, .diff-file-body tr.tag-code:last-child td:last-child * { 211 | border-bottom-right-radius: var(--border-radius); 212 | } 213 | 214 | .ui.attached.header.diff-file-header.sticky-2nd-row { 215 | position: sticky; 216 | top: 60px; 217 | z-index: 7; 218 | } 219 | 220 | .add-comment { 221 | border-top: 1px solid var(--color-secondary); 222 | border-bottom: 1px solid var(--color-secondary); 223 | 224 | .conversation-holder { 225 | .comment-code-cloud { 226 | padding: var.get('measure/.75x') !important; 227 | form { 228 | &:not(:first-child) { 229 | margin-top: var.get('measure/.5x'); 230 | } 231 | 232 | .field.footer { 233 | margin: 0 !important; 234 | padding: 0 !important; 235 | 236 | button { 237 | padding: var.get('measure/.5x') var.get('measure/1x') !important; 238 | } 239 | } 240 | } 241 | } 242 | } 243 | } 244 | } 245 | 246 | // new issue page 247 | .page-content.repository.new.issue { 248 | .issue-content-left { 249 | .ui.comments .comment { 250 | .ui.segment.content { 251 | padding: 6px 0 0; 252 | border: none; 253 | 254 | .field { 255 | margin-bottom: var.get('measure/1x'); 256 | } 257 | 258 | &:before, &:after { 259 | display: none; 260 | } 261 | } 262 | } 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/issues/_list.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/var' as var; 2 | @use '@lucas-labs/lui-micro/color' as color; 3 | 4 | @mixin apply-styles { 5 | .page-content.repository.issue-list, .page-content.dashboard.issues { 6 | .secondary-nav { 7 | margin-bottom: var.get('measure/1.5x'); 8 | } 9 | 10 | #issue-filters { 11 | margin: var.get('measure/2x') 0 var.get('measure/1x') 0; 12 | gap: var.get('measure/3x'); 13 | align-items: center; 14 | } 15 | 16 | #issue-list { 17 | .flex-item { 18 | padding: var.get('measure/.75x') 0 var.get('measure/.75x') 0; 19 | 20 | &:first-child { 21 | padding-top: 0; 22 | } 23 | 24 | &:last-child { 25 | padding-bottom: 0; 26 | } 27 | 28 | .branch { 29 | background-color: color.get('elevation/1'); 30 | } 31 | 32 | .flex-item-icon { 33 | svg { 34 | &.green { 35 | color: color.get('palette/green/base') !important; 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | .list-header { 43 | margin-bottom: var.get('measure/1.5x'); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/issues/index.scss: -------------------------------------------------------------------------------- 1 | @use './list'; 2 | @use './issue'; 3 | 4 | @mixin issues { 5 | @include list.apply-styles(); 6 | @include issue.apply-styles(); 7 | } -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/repo/_file-list.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/var' as var; 2 | @use '@lucas-labs/lui-micro/color' as color; 3 | @use '../../components/button' as button; 4 | 5 | @mixin apply-styles { 6 | .repo-button-row-left { 7 | gap: var.get('measure/.5x'); 8 | 9 | .button { 10 | @include button.hollow; 11 | } 12 | 13 | .breadcrumb.repo-path { 14 | font-size: var.get('font-size/lg'); 15 | padding: 0 0 0 var.get('measure/.5x'); 16 | 17 | .section { 18 | &:first-of-type, &.active { 19 | font-weight: 600; 20 | } 21 | } 22 | 23 | .breadcrumb-divider, button { 24 | color: color.get('subtle') !important; 25 | } 26 | 27 | button { 28 | border-radius: var(--border-radius); 29 | padding: var.get('measure/.375x'); 30 | margin: 0 0 0 var.get('measure/.5x') !important; 31 | 32 | svg { 33 | width: var.get('measure/1x'); 34 | height: var.get('measure/1x'); 35 | } 36 | 37 | &:hover { 38 | background-color: var(--color-nav-hover-bg); 39 | } 40 | } 41 | } 42 | } 43 | 44 | .repo-home-filelist { 45 | > div { 46 | margin: 0 !important; 47 | } 48 | 49 | display: flex; 50 | flex-direction: column; 51 | row-gap: var.get('measure/1x'); 52 | 53 | // file list table 54 | #repo-files-table { 55 | // header 56 | .repo-file-last-commit { 57 | padding: var.get('measure/.75x') var.get('measure/1x') !important; 58 | 59 | .commit-summary { 60 | margin: 0; 61 | } 62 | 63 | .latest-commit { 64 | gap: var.get('measure/.5x'); 65 | 66 | img { 67 | // make it a circle (avatar) 68 | border-radius: 50%; 69 | width: var.get('measure/1.25x'); 70 | height: var.get('measure/1.25x'); 71 | } 72 | } 73 | 74 | .label { 75 | // boton con icono y avatar 76 | .detail.icon.button img { 77 | display: none; 78 | } 79 | } 80 | } 81 | 82 | // cells 83 | .repo-file-cell { 84 | padding: var.get('measure/.5x') var.get('measure/1x') !important; 85 | 86 | &.name { 87 | display: flex; 88 | flex-wrap: nowrap; 89 | column-gap: var.get('measure/.5x'); 90 | align-items: center; 91 | } 92 | } 93 | } 94 | 95 | // readme 96 | #readme { 97 | 98 | } 99 | 100 | .repository-summary .sub-menu .item { 101 | height: 30px; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/repo/_home.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/var' as var; 2 | @use '@lucas-labs/lui-micro/color' as color; 3 | 4 | @mixin apply-styles { 5 | // description, labels, info 6 | .repo-home-sidebar-top { 7 | @media (max-width: 768px) { 8 | border-bottom: 1px solid color.get('elevation/6'); 9 | margin-bottom: var.get('measure/1x'); 10 | } 11 | 12 | form { 13 | margin-top: 0 !important; 14 | } 15 | 16 | #repo-topics, #topic_edit, .label-list { 17 | row-gap: var.get('measure/.5x'); 18 | margin-top: 0 !important; 19 | 20 | .label:not(.basic) { 21 | font-size: var.get('font-size/sm') !important; 22 | background-color: rgba(#{color.get('palette/blue/base', 'rgb')}, 0.1); 23 | color: color.get('primary/base'); 24 | border-radius: var.get('measure/2x'); 25 | line-height: 22px !important; 26 | padding-top: 0 !important; 27 | padding-bottom: 0 !important; 28 | 29 | &:hover { 30 | background-color: rgba(#{color.get('palette/blue/base', 'rgb')}, 0.3) !important; 31 | color: color.get('primary/base') !important; 32 | } 33 | } 34 | 35 | .ui.selection.active.dropdown, .ui.selection.active.dropdown .menu { 36 | border-color: color.get('elevation/6') 37 | } 38 | } 39 | 40 | .repo-description { 41 | margin-bottom: var.get('measure/1x'); 42 | 43 | +a.flex-text-block { 44 | svg { 45 | color: color.get('subtle') !important; 46 | } 47 | margin-bottom: var.get('measure/1x'); 48 | } 49 | } 50 | 51 | .flex-text-block { 52 | gap: var.get('measure/.25x') !important; 53 | font-size: var.get('font-size/md') !important; 54 | 55 | svg { 56 | margin-right: var.get('measure/.25x') !important; 57 | } 58 | } 59 | 60 | @media (max-width: 768px) { 61 | .repo-description { 62 | margin-top: 0 !important; 63 | 64 | +a.flex-text-block { 65 | margin-bottom: 0; 66 | } 67 | } 68 | 69 | #repo-topics, #manage_topic, .flex-item-title { 70 | display: none; 71 | } 72 | 73 | .flex-item-body>div { 74 | flex: 1; 75 | } 76 | } 77 | } 78 | 79 | // releases + languages section 80 | .repo-home-sidebar-bottom { 81 | .flex-list .flex-item .flex-item-main { 82 | .flex-item { 83 | padding: 0 !important; 84 | 85 | .flex-item-main { 86 | gap: 0 !important; 87 | 88 | .flex-item-header { 89 | * { 90 | font-size: var.get('font-size/md') !important; 91 | } 92 | 93 | .flex-item-title { 94 | gap: var.get('measure/.5x') !important; 95 | .green.label { 96 | border-radius: var.get('measure/2x'); 97 | font-size: var.get('font-size/sm') !important; 98 | background-color: transparent !important; 99 | border: 1px solid color.get('palette/green/base') !important; 100 | color: color.get('palette/green/base') !important; 101 | } 102 | } 103 | } 104 | 105 | .flex-item-body { 106 | .time { 107 | font-size: var.get('font-size/sm') !important; 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | // languages 115 | .flex-item-body { 116 | gap: 0 !important; 117 | 118 | .language-stats { 119 | margin: 0 !important; 120 | height: var.get('measure/.5x') !important; 121 | margin-bottom: var.get('measure/.5x') !important; 122 | } 123 | 124 | .language-stats-details { 125 | gap: var.get('measure/1x') !important; 126 | .item { 127 | font-size: var.get('font-size/sm') !important; 128 | padding: 0; 129 | gap: var.get('measure/.25x') !important; 130 | 131 | .color-icon { 132 | height: var.get('measure/.5x') !important; 133 | width: var.get('measure/.5x') !important; 134 | margin-right: var.get('measure/.25x') !important; 135 | } 136 | } 137 | } 138 | } 139 | } 140 | 141 | .repo-home-sidebar-bottom, .repo-home-sidebar-top { 142 | padding-left: var.get('measure/1.5x'); 143 | 144 | @media (max-width: 768px) { 145 | padding-left: 0 !important; 146 | } 147 | 148 | .flex-item-title .item { 149 | display: flex; 150 | flex-wrap: nowrap; 151 | gap: var.get('measure/.5x'); 152 | text-decoration: none; 153 | 154 | .small.label { 155 | background-color: color.get('elevation/7'); 156 | border: 1px solid color.get('elevation/7'); 157 | border-radius: var.get('measure/2x'); 158 | color: color.get('text'); 159 | font-weight: var(--base-text-weight-medium, 500); 160 | min-width: 20px; 161 | padding: 2px; 162 | text-align: center; 163 | align-items: center; 164 | justify-content: center; 165 | } 166 | } 167 | 168 | >.flex-list>.flex-item { 169 | padding-top: var.get('measure/1x'); 170 | padding-bottom: var.get('measure/1x'); 171 | 172 | >.flex-item-main { 173 | gap: var.get('measure/1x'); 174 | } 175 | } 176 | } 177 | 178 | button.ui.primary.button.js-btn-clone-panel { 179 | display: flex; 180 | flex-wrap: nowrap; 181 | gap: var.get('measure/.5x'); 182 | align-items: center; 183 | 184 | span { 185 | display: flex; 186 | flex-wrap: nowrap; 187 | gap: var.get('measure/.375x'); 188 | align-items: center; 189 | } 190 | } 191 | 192 | .clone-panel-popup { 193 | .clone-panel-field { 194 | margin: var.get('measure/1x'); 195 | } 196 | 197 | .clone-panel-list { 198 | margin: var.get('measure/1x'); 199 | 200 | .item { 201 | margin: var.get('measure/.5x') 0; 202 | } 203 | } 204 | 205 | .clone-panel-tab { 206 | padding: 0 var.get('measure/1x') var.get('measure/.5x'); 207 | display: flex; 208 | gap: var.get('measure/.5x'); 209 | 210 | button { 211 | padding: var.get('measure/.375x') var.get('measure/.5x'); 212 | font-weight: 600; 213 | 214 | &.active { 215 | border-bottom: 0; 216 | position: relative; 217 | 218 | &:after { 219 | content: ''; 220 | display: block; 221 | position: absolute; 222 | bottom: -7px; 223 | left: 0; 224 | width: 100%; 225 | height: 2px; 226 | background-color: color.get('primary/base'); 227 | } 228 | } 229 | 230 | &:hover { 231 | background-color: var(--color-hover); 232 | border-radius: var(--border-radius); 233 | transition: background 0.12s ease-out; 234 | text-decoration: none; 235 | } 236 | } 237 | } 238 | } 239 | 240 | .tippy-svg-arrow { 241 | display: none; 242 | } 243 | 244 | .repository.file.editor .commit-form-wrapper .commit-form:before, .repository.file.editor .commit-form-wrapper .commit-form:after { 245 | display: none; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/repo/_secondary-navbar.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/var' as var; 2 | @use '@lucas-labs/lui-micro/color' as color; 3 | @use '../../components/button' as button; 4 | 5 | @mixin apply-styles { 6 | .secondary-nav { 7 | margin-bottom: var.get('measure/1.5x') !important; 8 | 9 | .ui.container { 10 | margin: 0 0 0 0 !important; 11 | max-width: unset !important; 12 | width: 100% !important; 13 | padding: 0 var.get('measure/1x'); 14 | } 15 | 16 | // header 17 | .repo-header { 18 | margin: 0 0 var.get('measure/1x') 0; 19 | 20 | // repo image 21 | img.avatar { 22 | width: var.get('measure/2x') !important; 23 | height: var.get('measure/2x') !important; 24 | } 25 | 26 | // repo title (repo "owner / name" text) 27 | .flex-item-title { 28 | gap: var.get('measure/.25x') !important; 29 | font-size: var.get('font-size/md') !important; 30 | font-weight: normal !important; 31 | color: color.get('elevation/10') !important; 32 | 33 | a { 34 | padding: var.get('measure/.375x') var.get('measure/.5x') !important; 35 | border-radius: var.get('measure/.25x'); 36 | 37 | &:hover { 38 | background-color: color.get('elevation/4') !important; 39 | color: color.get('text') !important; 40 | text-decoration: none !important; 41 | } 42 | } 43 | 44 | // repo name only 45 | a:last-child { 46 | font-weight: 600; 47 | } 48 | } 49 | 50 | // "public/private" label 51 | .flex-item-trailing { 52 | .label { 53 | padding: var.get('measure/.25x') var.get('measure/.5x'); 54 | font-size: var.get('font-size/sm'); 55 | border-radius: var.get('measure/1x'); 56 | background-color: transparent; 57 | color: color.get('elevation/10'); 58 | } 59 | 60 | // lock icon 61 | svg { 62 | color: color.get('elevation/10'); 63 | } 64 | } 65 | 66 | @media (max-width: 768px) { 67 | // repo title (repo "owner / name" text) 68 | .flex-item-title { 69 | display: inline-block; 70 | white-space: nowrap; 71 | padding: 0 var.get('measure/.5x') !important; 72 | font-size: var.get('font-size/sm') !important; 73 | font-weight: 500 !important; 74 | 75 | a { 76 | padding: 0 !important; 77 | 78 | &:hover { 79 | background-color: transparent !important; 80 | color: color.get('primary/base') !important; 81 | text-decoration: none !important; 82 | } 83 | } 84 | 85 | a:first-child { 86 | float: left; 87 | margin-right: var.get('measure/.5x'); 88 | color: color.get('elevation/10') !important; 89 | font-weight: 500 !important; 90 | 91 | &:hover { 92 | color: color.get('primary/base') !important; 93 | } 94 | } 95 | 96 | // repo name only 97 | a:last-child { 98 | font-size: var.get('font-size/md') !important; 99 | clear: left; 100 | display: block; 101 | } 102 | } 103 | } 104 | } 105 | 106 | .fork-flag { 107 | margin-top: calc(#{var.get('measure/1x')} * -1) !important; 108 | margin-bottom: var.get('measure/1x') !important; 109 | } 110 | 111 | // header navbar menu 112 | overflow-menu.ui.secondary.pointing.menu { 113 | .overflow-menu-items { 114 | gap: var.get('measure/.5x') !important; 115 | } 116 | 117 | // navbar menu items 118 | .item { 119 | padding: var.get('measure/.375x') var.get('measure/.5x') !important; 120 | margin-bottom: var.get('measure/.5x') !important; 121 | border-radius: var.get('measure/.25x'); 122 | border: none !important; 123 | 124 | // icon 125 | svg { 126 | color: var(--color-text-light-3) !important; 127 | margin-right: var.get('measure/.5x') !important; 128 | } 129 | 130 | &:hover { 131 | background-color: var(--color-secondary-nav-hover-bg) !important; 132 | color: var(--color-text-light-2) !important; 133 | } 134 | 135 | // when the represents the current route 136 | &.active { 137 | border: none !important; 138 | &:after { 139 | content: ''; 140 | display: block; 141 | position: absolute; 142 | width: 100%; 143 | height: 2px; 144 | left: 0; 145 | top: calc(100% + #{var.get('measure/.375x')} - 1px); 146 | background-color: color.get('primary/base'); 147 | transform: unset; 148 | border: none; 149 | } 150 | } 151 | 152 | // numeric badge/label (e.g. indicating the number of issues) 153 | .small.label { 154 | background-color: color.get('elevation/7'); 155 | border: 1px solid color.get('elevation/7'); 156 | border-radius: var.get('measure/2x'); 157 | color: color.get('text'); 158 | font-weight: var(--base-text-weight-medium, 500); 159 | min-width: calc(var.get('measure/1x') + 1px); 160 | padding: 2px; 161 | text-align: center; 162 | align-items: center; 163 | justify-content: center; 164 | } 165 | } 166 | } 167 | 168 | // buttons at the right of the header (unwatch, star, fork, etc.) 169 | .repo-buttons { 170 | .button { 171 | font-size: var.get('font-size/sm') !important; 172 | @include button.hollow; 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/repo/index.scss: -------------------------------------------------------------------------------- 1 | @use './home'; 2 | @use './secondary-navbar'; 3 | @use './file-list'; 4 | 5 | @mixin repo { 6 | @include home.apply-styles(); 7 | @include secondary-navbar.apply-styles(); 8 | @include file-list.apply-styles(); 9 | } -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/settings/_pages.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/var' as var; 2 | @use '@lucas-labs/lui-micro/color' as color; 3 | @use './section'; 4 | 5 | @mixin settings-pages { 6 | .page-content.user.settings, .page-content.admin, .page-content.repository.settings { 7 | .ui.flex-container { 8 | column-gap: 40px; 9 | 10 | .flex-container-nav { 11 | width: 232px; 12 | @include section.nav; 13 | } 14 | 15 | .flex-container-main { 16 | @include section.content; 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/settings/index.scss: -------------------------------------------------------------------------------- 1 | @use './pages'; 2 | 3 | @mixin settings { 4 | @include pages.settings-pages; 5 | } -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/settings/section/_content.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/var' as var; 2 | @use '@lucas-labs/lui-micro/color' as color; 3 | 4 | @mixin content { 5 | .ui.top.attached.header { 6 | background: unset; 7 | border: none; 8 | font-size: var.get('font-size/3xl'); 9 | font-weight: 400; 10 | padding: 0 0 var.get('measure/.5x') 0; 11 | border-bottom: 1px solid var(--color-secondary); 12 | margin-bottom: var.get('measure/1x'); 13 | 14 | // if not the first header, add a margin top 15 | &:not(:first-child) { 16 | margin-top: var.get('measure/2.5x'); 17 | } 18 | 19 | &.error { 20 | border-color: var(--color-secondary) !important; 21 | background: unset !important; 22 | } 23 | } 24 | 25 | .ui.segment { 26 | border-radius: var(--border-radius); 27 | 28 | >*:last-child { 29 | margin-bottom: 0; 30 | padding-bottom: 0; 31 | } 32 | } 33 | 34 | .ui.attached.segment { 35 | background: unset; 36 | padding:0; 37 | border: none; 38 | margin: 0; 39 | 40 | &.bottom { 41 | margin-top: var.get('measure/2x'); 42 | } 43 | 44 | a:not(.button) { 45 | color: var(--color-primary); 46 | text-decoration: underline; 47 | display: inline-flex; 48 | align-items: center; 49 | column-gap: var.get('measure/.375x'); 50 | 51 | svg { 52 | margin-left: var.get('measure/.25x'); 53 | } 54 | } 55 | 56 | .ui.form .field { 57 | margin: 0 0 1em; 58 | } 59 | 60 | .ui.form .fields .fields, .ui.form .field:last-child, .ui.form .fields:last-child .field { 61 | margin-bottom: 0; 62 | } 63 | 64 | .field { 65 | margin: var.get('measure/1x') 0; 66 | } 67 | 68 | .divider { 69 | margin: var.get('measure/1x') 0; 70 | } 71 | 72 | .list { 73 | border: 1px solid var(--color-secondary); 74 | border-radius: var(--border-radius); 75 | 76 | .item { 77 | margin-left: 0; 78 | margin-right: 0; 79 | 80 | &:not(:first-child), &:first-child { 81 | padding: 1rem; 82 | margin: 0rem 0rem 0rem 0rem; 83 | } 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/settings/section/_nav.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/var' as var; 2 | @use '@lucas-labs/lui-micro/color' as color; 3 | 4 | @mixin nav { 5 | .vertical.menu { 6 | --spacing: #{var.get('measure/.375x')} #{var.get('measure/.75x')}; 7 | --min-height: #{var.get('measure/2x')}; 8 | background-color: transparent; 9 | border: none; 10 | 11 | .header.item { 12 | display: flex; 13 | align-items: center; 14 | padding: var(--spacing); 15 | font-size: var.get('font-size/sm'); 16 | font-weight: 600; 17 | color: color.get('subtle'); 18 | background-color: transparent; 19 | min-height: var(--min-height); 20 | } 21 | 22 | a.item, details.item { 23 | min-height: var(--min-height); 24 | &:before { 25 | content: none; 26 | } 27 | 28 | &:not(:last-child) { 29 | margin-bottom: var.get('measure/.25x'); 30 | } 31 | 32 | &.active { 33 | font-weight: 600; 34 | background-color: var(--color-menu); 35 | position: relative; 36 | 37 | &:hover { 38 | background-color: var(--color-hover); 39 | } 40 | 41 | &:after { 42 | background: var(--color-primary); 43 | border-radius: .375rem; 44 | content: ""; 45 | height: 24px; 46 | width: 4px; 47 | position: absolute; 48 | top: 50%; 49 | transform: translateY(-50%); 50 | left: calc(0px - 4px - #{var.get('measure/.375x')}); 51 | } 52 | } 53 | } 54 | 55 | a.item, details.item summary { 56 | padding: var(--spacing); 57 | border-radius: var(--border-radius); 58 | } 59 | 60 | details.item { 61 | --octicon-chevron-down: url('data:image/svg+xml;utf8,'); 62 | 63 | summary { 64 | &:after { 65 | color: color.get('subtle'); 66 | transform: scaleY(1); 67 | transition: transform .12s linear; 68 | mask-image: var(--octicon-chevron-down); 69 | -webkit-mask-image: var(--octicon-chevron-down); 70 | } 71 | } 72 | 73 | &[open] summary { 74 | &:after { 75 | transform: scaleY(-1); 76 | } 77 | } 78 | 79 | .menu { 80 | .item { 81 | display: flex; 82 | align-items: center; 83 | color: color.get('text') !important; 84 | 85 | &:hover { 86 | background-color: var(--color-hover); 87 | } 88 | } 89 | 90 | margin: 0 !important; 91 | } 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /src/themes/scss/theme/modules/settings/section/index.scss: -------------------------------------------------------------------------------- 1 | @forward './nav'; 2 | @forward './content'; -------------------------------------------------------------------------------- /src/themes/scss/theme/vars/_base.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/color' as color; 2 | @use '@lucas-labs/lui-micro/var' as var; 3 | 4 | @mixin base { 5 | :root { 6 | // ┌───────┐ 7 | // │ fonts │ 8 | // └───────┘ 9 | --fonts-proportional: #{var.get('font-family')}; 10 | --fonts-monospace: #{var.get('code-font-family')}; 11 | --fonts-emoji: #{var.get('emoji-font-family')}; 12 | 13 | // ┌───────┬───────────────────┐ 14 | // │ fonts │ weights & heights │ 15 | // └───────┴───────────────────┘ 16 | --font-weight-light: 300; 17 | --font-weight-normal: 400; 18 | --font-weight-medium: 500; 19 | --font-weight-semibold: 600; 20 | --font-weight-bold: 700; 21 | --line-height-default: normal; 22 | 23 | // ┌────────┐ 24 | // │ images │ 25 | // └────────┘ 26 | --checkbox-mask-checked: url('data:image/svg+xml;utf8,'); 27 | --checkbox-mask-indeterminate: url('data:image/svg+xml;utf8,'); 28 | --octicon-chevron-right: url('data:image/svg+xml;utf8,'); 29 | 30 | // ┌────────┐ 31 | // │ radius │ 32 | // └────────┘ 33 | --border-radius: #{var.get('measure/.5x')}; 34 | --border-radius-medium: #{var.get('measure/1x')}; 35 | --border-radius-full: calc(infinity * 1px); 36 | 37 | // ┌────────┐ 38 | // │ others │ 39 | // └────────┘ 40 | --opacity-disabled: 0.55; 41 | --height-loading: 16rem; 42 | --min-height-textarea: 132px; // padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported 43 | --checkbox-size: 16px; // height and width of checkbox and radio inputs 44 | --page-spacing: 24px; // space between page elements 45 | --page-margin-x: 32px; // minimum space on left and right side of page 46 | --tab-size: 4; 47 | } 48 | 49 | @media (min-width: 768px) and (max-width: 1200px) { 50 | :root { 51 | --page-margin-x: 24px; 52 | } 53 | } 54 | 55 | @media (max-width: 767.98px) { 56 | :root { 57 | --page-margin-x: 16px; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/themes/scss/theme/vars/_colors.scss: -------------------------------------------------------------------------------- 1 | @use '@lucas-labs/lui-micro/var' as var; 2 | @use '@lucas-labs/lui-micro/color' as color; 3 | 4 | @mixin colors($is-dark: true) { 5 | $scheme: if($is-dark, 'dark', 'light'); 6 | $scheme-inverted: if($is-dark, 'light', 'dark'); 7 | 8 | :root { 9 | accent-color: var(--color-accent); 10 | 11 | @if $is-dark { 12 | color-scheme: dark; 13 | --is-dark-theme: true; 14 | } @else { 15 | color-scheme: light; 16 | --is-dark-theme: false; 17 | } 18 | 19 | // ┌─────────┐ 20 | // │ primary │ 21 | // └─────────┘ 22 | --color-primary: #{color.get('primary/base')}; 23 | --color-primary-contrast: #{color.get('primary/base', 'contrast')}; 24 | 25 | // dark 26 | --color-primary-dark-1: #{color.get('primary/' + $scheme-inverted + '/3%')}; 27 | --color-primary-dark-2: #{color.get('primary/' + $scheme-inverted + '/6%')}; 28 | --color-primary-dark-3: #{color.get('primary/' + $scheme-inverted + '/9%')}; 29 | --color-primary-dark-4: #{color.get('primary/' + $scheme-inverted + '/10%')}; 30 | --color-primary-dark-5: #{color.get('primary/' + $scheme-inverted + '/20%')}; 31 | --color-primary-dark-6: #{color.get('primary/' + $scheme-inverted + '/25%')}; 32 | --color-primary-dark-7: #{color.get('primary/' + $scheme-inverted + '/30%')}; 33 | 34 | // light 35 | --color-primary-light-1: #{color.get('primary/' + $scheme + '/3%')}; 36 | --color-primary-light-2: #{color.get('primary/' + $scheme + '/6%')}; 37 | --color-primary-light-3: #{color.get('primary/' + $scheme + '/10%')}; 38 | --color-primary-light-4: #{color.get('primary/' + $scheme + '/15%')}; 39 | --color-primary-light-5: #{color.get('primary/' + $scheme + '/20%')}; 40 | --color-primary-light-6: #{color.get('primary/' + $scheme + '/30%')}; 41 | --color-primary-light-7: #{color.get('primary/' + $scheme + '/40%')}; 42 | 43 | // alpha 44 | --color-primary-alpha-10: rgba(#{color.get('primary/base', 'rgb')}, 0.1); 45 | --color-primary-alpha-20: rgba(#{color.get('primary/base', 'rgb')}, 0.2); 46 | --color-primary-alpha-30: rgba(#{color.get('primary/base', 'rgb')}, 0.3); 47 | --color-primary-alpha-40: rgba(#{color.get('primary/base', 'rgb')}, 0.4); 48 | --color-primary-alpha-50: rgba(#{color.get('primary/base', 'rgb')}, 0.5); 49 | --color-primary-alpha-60: rgba(#{color.get('primary/base', 'rgb')}, 0.6); 50 | --color-primary-alpha-70: rgba(#{color.get('primary/base', 'rgb')}, 0.7); 51 | --color-primary-alpha-80: rgba(#{color.get('primary/base', 'rgb')}, 0.8); 52 | --color-primary-alpha-90: rgba(#{color.get('primary/base', 'rgb')}, 0.9); 53 | 54 | // states 55 | --color-primary-hover: var(--color-primary-light-1); 56 | --color-primary-active: var(--color-primary-light-3); 57 | 58 | // ┌───────────┐ 59 | // │ secondary │ » secondary 60 | // └───────────┘ 61 | --color-secondary: #{color.get('secondary/base')}; 62 | 63 | // dark 64 | --color-secondary-dark-1: #{color.get('secondary/' + $scheme-inverted + '/3%')}; 65 | --color-secondary-dark-2: #{color.get('secondary/' + $scheme-inverted + '/6%')}; 66 | --color-secondary-dark-3: #{color.get('secondary/' + $scheme-inverted + '/9%')}; 67 | --color-secondary-dark-4: #{color.get('secondary/' + $scheme-inverted + '/12%')}; 68 | --color-secondary-dark-5: #{color.get('secondary/' + $scheme-inverted + '/15%')}; 69 | --color-secondary-dark-6: #{color.get('secondary/' + $scheme-inverted + '/20%')}; 70 | --color-secondary-dark-7: #{color.get('secondary/' + $scheme-inverted + '/25%')}; 71 | --color-secondary-dark-8: #{color.get('secondary/' + $scheme-inverted + '/30%')}; 72 | --color-secondary-dark-9: #{color.get('secondary/' + $scheme-inverted + '/35%')}; 73 | --color-secondary-dark-10: #{color.get('secondary/' + $scheme-inverted + '/40%')}; 74 | --color-secondary-dark-11: #{color.get('secondary/' + $scheme-inverted + '/45%')}; 75 | --color-secondary-dark-12: #{color.get('secondary/' + $scheme-inverted + '/50%')}; 76 | --color-secondary-dark-13: #{color.get('secondary/' + $scheme-inverted + '/55%')}; 77 | 78 | // light 79 | --color-secondary-light-1: #{color.get('secondary/' + $scheme + '/3%')}; 80 | --color-secondary-light-2: #{color.get('secondary/' + $scheme + '/6%')}; 81 | --color-secondary-light-3: #{color.get('secondary/' + $scheme + '/9%')}; 82 | --color-secondary-light-4: #{color.get('secondary/' + $scheme + '/12%')}; 83 | 84 | // alpha 85 | --color-secondary-alpha-10: rgba(#{color.get('secondary/base', 'rgb')}, 0.1); 86 | --color-secondary-alpha-20: rgba(#{color.get('secondary/base', 'rgb')}, 0.2); 87 | --color-secondary-alpha-30: rgba(#{color.get('secondary/base', 'rgb')}, 0.3); 88 | --color-secondary-alpha-40: rgba(#{color.get('secondary/base', 'rgb')}, 0.4); 89 | --color-secondary-alpha-50: rgba(#{color.get('secondary/base', 'rgb')}, 0.5); 90 | --color-secondary-alpha-60: rgba(#{color.get('secondary/base', 'rgb')}, 0.6); 91 | --color-secondary-alpha-70: rgba(#{color.get('secondary/base', 'rgb')}, 0.7); 92 | --color-secondary-alpha-80: rgba(#{color.get('secondary/base', 'rgb')}, 0.8); 93 | --color-secondary-alpha-90: rgba(#{color.get('secondary/base', 'rgb')}, 0.9); 94 | 95 | // states 96 | --color-secondary-button: var(--color-secondary-dark-4); 97 | --color-secondary-hover: var(--color-secondary-dark-3); 98 | --color-secondary-active: var(--color-secondary-dark-2); 99 | 100 | // ┌─────────┐ 101 | // │ console │ » used for actions console and console files 102 | // └─────────┘ 103 | --color-console-fg: #f7f8f9; 104 | --color-console-fg-subtle: #bdc4cc; 105 | --color-console-bg: #171b1e; 106 | --color-console-border: #2e353b; 107 | --color-console-hover-bg: #272d33; 108 | --color-console-active-bg: #2e353b; 109 | --color-console-menu-bg: #262b31; 110 | --color-console-menu-border: #414b55; 111 | 112 | // ┌───────┐ 113 | // │ named │ 114 | // └───────┘ 115 | --color-red: #{color.get('palette/red/base')}; 116 | --color-orange: #{color.get('palette/orange/base')}; 117 | --color-yellow: #{color.get('palette/yellow/base')}; 118 | --color-olive: #{color.get('palette/olive/base')}; 119 | --color-green: #{color.get('palette/green/base')}; 120 | --color-teal: #{color.get('palette/teal/base')}; 121 | --color-blue: #{color.get('palette/blue/base')}; 122 | --color-violet: #{color.get('palette/violet/base')}; 123 | --color-purple: #{color.get('palette/purple/base')}; 124 | --color-pink: #{color.get('palette/pink/base')}; 125 | --color-brown: #{color.get('palette/brown/base')}; 126 | --color-black: #{color.get('palette/black/base')}; 127 | 128 | // ┌───────┐ 129 | // │ named │ » contrasts (lugit addition) 130 | // └───────┘ 131 | --color-green-contrast: #{if($is-dark, var(--color-black), var(--color-white))}; 132 | --color-red-contrast: #{if($is-dark, var(--color-black), var(--color-white))}; 133 | 134 | 135 | // ┌───────┐ 136 | // │ named │ » light variants 137 | // └───────┘ 138 | --color-red-light: #{color.get('palette/red/light/10%')}; 139 | --color-orange-light: #{color.get('palette/orange/light/10%')}; 140 | --color-yellow-light: #{color.get('palette/yellow/light/10%')}; 141 | --color-olive-light: #{color.get('palette/olive/light/10%')}; 142 | --color-green-light: #{color.get('palette/green/light/10%')}; 143 | --color-teal-light: #{color.get('palette/teal/light/10%')}; 144 | --color-blue-light: #{color.get('palette/blue/light/10%')}; 145 | --color-violet-light: #{color.get('palette/violet/light/10%')}; 146 | --color-purple-light: #{color.get('palette/purple/light/10%')}; 147 | --color-pink-light: #{color.get('palette/pink/light/10%')}; 148 | --color-brown-light: #{color.get('palette/brown/light/10%')}; 149 | --color-black-light: #{if( 150 | $is-dark, 151 | color.get('palette/black/light/10%'), 152 | color.get('palette/white/light/10%') 153 | )}; 154 | 155 | // ┌───────┐ 156 | // │ named │ » dark 1 variants 157 | // └───────┘ 158 | --color-red-dark-1: #{color.get('palette/red/dark/10%')}; 159 | --color-orange-dark-1: #{color.get('palette/orange/dark/10%')}; 160 | --color-yellow-dark-1: #{color.get('palette/yellow/dark/10%')}; 161 | --color-olive-dark-1: #{color.get('palette/olive/dark/10%')}; 162 | --color-green-dark-1: #{color.get('palette/green/dark/10%')}; 163 | --color-teal-dark-1: #{color.get('palette/teal/dark/10%')}; 164 | --color-blue-dark-1: #{color.get('palette/blue/dark/10%')}; 165 | --color-violet-dark-1: #{color.get('palette/violet/dark/10%')}; 166 | --color-purple-dark-1: #{color.get('palette/purple/dark/10%')}; 167 | --color-pink-dark-1: #{color.get('palette/pink/dark/10%')}; 168 | --color-brown-dark-1: #{color.get('palette/brown/dark/10%')}; 169 | --color-black-dark-1: #{if( 170 | $is-dark, 171 | color.get('palette/black/dark/10%'), 172 | color.get('palette/white/dark/10%') 173 | )}; 174 | 175 | // ┌───────┐ 176 | // │ named │ » dark 2 variants 177 | // └───────┘ 178 | --color-red-dark-2: #{color.get('palette/red/dark/20%')}; 179 | --color-orange-dark-2: #{color.get('palette/orange/dark/20%')}; 180 | --color-yellow-dark-2: #{color.get('palette/yellow/dark/20%')}; 181 | --color-olive-dark-2: #{color.get('palette/olive/dark/20%')}; 182 | --color-green-dark-2: #{color.get('palette/green/dark/20%')}; 183 | --color-teal-dark-2: #{color.get('palette/teal/dark/20%')}; 184 | --color-blue-dark-2: #{color.get('palette/blue/dark/20%')}; 185 | --color-violet-dark-2: #{color.get('palette/violet/dark/20%')}; 186 | --color-purple-dark-2: #{color.get('palette/purple/dark/20%')}; 187 | --color-pink-dark-2: #{color.get('palette/pink/dark/20%')}; 188 | --color-brown-dark-2: #{color.get('palette/brown/dark/20%')}; 189 | --color-black-dark-2: #{if( 190 | $is-dark, 191 | color.get('palette/black/dark/20%'), 192 | color.get('palette/white/dark/20%') 193 | )}; 194 | 195 | // ┌──────┐ 196 | // │ ansi │ » for actions console and console files 197 | // └──────┘ 198 | --color-ansi-black: #1e2327; 199 | --color-ansi-red: #cc4848; 200 | --color-ansi-green: #87ab63; 201 | --color-ansi-yellow: #cc9903; 202 | --color-ansi-blue: #3a8ac6; 203 | --color-ansi-magenta: #d22e8b; 204 | --color-ansi-cyan: #00918a; 205 | --color-ansi-white: var(--color-console-fg-subtle); 206 | --color-ansi-bright-black: #424851; 207 | --color-ansi-bright-red: #d15a5a; 208 | --color-ansi-bright-green: #93b373; 209 | --color-ansi-bright-yellow: #eaaf03; 210 | --color-ansi-bright-blue: #4e96cc; 211 | --color-ansi-bright-magenta: #d74397; 212 | --color-ansi-bright-cyan: #00b6ad; 213 | --color-ansi-bright-white: var(--color-console-fg); 214 | 215 | // ┌───────┐ 216 | // │ other │ 217 | // └───────┘ 218 | --color-grey: #{color.get('elevation/9')}; 219 | --color-grey-light: #{color.get('elevation/11')}; 220 | --color-gold: #{color.get('palette/yellow/dark/20%')}; 221 | --color-white: #{color.get('palette/white/base')}; 222 | --color-git: #f05133; 223 | 224 | // ┌──────┐ 225 | // │ diff │ 226 | // └──────┘ 227 | --color-diff-added-linenum-bg: #{if($is-dark, #{color.get('palette/teal/dark/50%')}, rgba(#{color.get('palette/white/base', 'rgb')}, .2))}; 228 | --color-diff-added-row-bg: #{if($is-dark, #{color.get('palette/teal/dark/60%')}, rgba(#{color.get('palette/green/light/20%', 'rgb')}, .25))}; 229 | --color-diff-added-row-border: #{if($is-dark, #{color.get('palette/teal/dark/40%')}, rgba(#{color.get('palette/green/light/25%', 'rgb')}, .25))}; 230 | --color-diff-added-word-bg: #{if($is-dark, #{color.get('palette/teal/dark/50%')}, rgba(#{color.get('palette/green/light/6%', 'rgb')}, .3))}; 231 | --color-diff-moved-row-bg: #{if($is-dark, #{color.get('palette/yellow/dark/75%')}, rgba(#{color.get('palette/yellow/light/20%', 'rgb')}, .25))}; 232 | --color-diff-moved-row-border: #{if($is-dark, #{color.get('palette/yellow/dark/65%')}, rgba(#{color.get('palette/yellow/light/25%', 'rgb')}, .25))}; 233 | --color-diff-removed-linenum-bg: #{if($is-dark, #{color.get('palette/red/dark/60%')}, rgba(#{color.get('palette/white/base', 'rgb')}, .2))}; 234 | --color-diff-removed-row-bg: #{if($is-dark, #{color.get('palette/red/dark/65%')}, rgba(#{color.get('palette/red/light/30%', 'rgb')}, .15))}; 235 | --color-diff-removed-row-border: #{if($is-dark, #{color.get('palette/red/dark/50%')}, rgba(#{color.get('palette/red/light/35%', 'rgb')}, .2))}; 236 | --color-diff-removed-word-bg: #{if($is-dark, #{color.get('palette/red/dark/55%')}, rgba(#{color.get('palette/red/light/25%', 'rgb')}, .2))}; 237 | --color-diff-inactive: #{if($is-dark, #{color.get('elevation/6')}, red)}; 238 | 239 | // ┌────────┐ 240 | // │ status │ 241 | // └────────┘ 242 | --color-error-border: #{color.get('palette/red/base')}; 243 | --color-error-bg: rgba(#{color.get('palette/red/base', 'rgb')}, 0.15); 244 | --color-error-bg-active: #{color.get('palette/red/light/6%')}; 245 | --color-error-bg-hover: #{color.get('palette/red/light/12%')}; 246 | --color-error-text: #{color.get('palette/red/base')}; 247 | --color-success-border: #{color.get('palette/green/light/12%')}; 248 | --color-success-bg: rgba(#{color.get('palette/green/base', 'rgb')}, 0.15); 249 | --color-success-text: #{color.get('text')}; 250 | --color-warning-border: #{color.get('palette/yellow/light/12%')}; 251 | --color-warning-bg: #{color.get('palette/yellow/base')}; 252 | --color-warning-text: #{color.get('elevation/3')}; 253 | --color-info-border: #{color.get('palette/blue/light/12%')}; 254 | --color-info-bg: #{color.get('elevation/3')}; 255 | --color-info-text: #{color.get('text')}; 256 | 257 | // ┌───────┐ 258 | // │ badge │ 259 | // └───────┘ 260 | --color-red-badge: #{color.get('palette/red/dark/20%')}; 261 | --color-red-badge-bg: rgba(#{color.get('palette/red/dark/20%', 'rgb')}, .1); 262 | --color-red-badge-hover-bg: rgba(#{color.get('palette/red/dark/20%', 'rgb')}, .2); 263 | 264 | --color-green-badge: #{color.get('palette/green/base')}; 265 | --color-green-badge-bg: rgba(#{color.get('palette/green/base', 'rgb')}, .1); 266 | --color-green-badge-hover-bg: rgba(#{color.get('palette/green/base', 'rgb')}, .2); 267 | 268 | --color-yellow-badge: #{color.get('palette/yellow/dark/10%')}; 269 | --color-yellow-badge-bg: rgba(#{color.get('palette/yellow/dark/10%', 'rgb')}, .1); 270 | --color-yellow-badge-hover-bg: rgba(#{color.get('palette/yellow/dark/10%', 'rgb')}, .2); 271 | 272 | --color-orange-badge: #{color.get('palette/orange/dark/10%')}; 273 | --color-orange-badge-bg: rgba(#{color.get('palette/orange/dark/10%', 'rgb')}, .1); 274 | --color-orange-badge-hover-bg: rgba(#{color.get('palette/orange/dark/10%', 'rgb')}, .2); 275 | 276 | // ┌──────────────┐ 277 | // │ target-based │ 278 | // └──────────────┘ 279 | 280 | // body colors 281 | --color-body: #{color.get('elevation/3')}; 282 | --color-text-dark: #{color.get('text')}; 283 | --color-text: #{color.get('text')}; 284 | --color-text-light: #{color.get('subtle')}; 285 | --color-text-light-1: #{color.get('subtle')}; 286 | --color-text-light-2: #{color.get('subtle')}; 287 | --color-text-light-3: #{color.get('subtle')}; 288 | --color-footer: rgba(#{color.get('elevation/2', 'rgb')}, 0.2); 289 | --color-timeline: #{color.get('elevation/5')}; 290 | --color-timeline-badge-fg: #{if($is-dark, var(--color-white), var.get('color-text'))}; 291 | 292 | // box 293 | --color-box-header: #{rgba(color.get('elevation/4', 'rgb'), 1)}; 294 | --color-box-body: #{color.get('elevation/3')}; 295 | --color-box-body-highlight: #{color.get('elevation/4')}; 296 | 297 | // input 298 | --color-input-text: var(--color-text-dark); 299 | --color-input-background: #{color.get('elevation/4')}; 300 | --color-input-toggle-background: #{color.get('elevation/4')}; 301 | --color-input-border: #{color.get('elevation/5')}; 302 | --color-input-border-hover: #{color.get('elevation/6')}; 303 | 304 | // light 305 | --color-light: #00001728; 306 | --color-light-mimic-enabled: rgba(0, 0, 0, calc(40 / 255 * 222 / 255 / var(--opacity-disabled))); 307 | --color-light-border: #{color.get('elevation/6')}; 308 | --color-hover: rgba(#{color.get('elevation/6', 'rgb')}, 0.5); 309 | --color-hover-opaque: #{color.get('elevation/6', 'rgb')}; 310 | --color-active: #{color.get('elevation/5')}; 311 | --color-menu: #{color.get('elevation/4')}; 312 | --color-card: #{color.get('elevation/3')}; 313 | --color-markup-table-row: rgba(#{color.get('text', 'rgb')}, 0.02); 314 | --color-markup-code-block: #{color.get('elevation/4')}; 315 | --color-markup-code-inline: #{color.get('elevation/4')}; 316 | --color-button: #{color.get('elevation/4')}; 317 | --color-code-bg: #{color.get('elevation/3')}; 318 | --color-shadow: #{if($is-dark, rgba(#{color.get('elevation/1', 'rgb')}, 0.5), rgba(#{color.get('elevation/6', 'rgb')}, 0.5))}; 319 | --color-shadow-opaque: #{if($is-dark, color.get('elevation/1'), color.get('elevation/6'))}; 320 | 321 | --color-shadow-rgb: #{if($is-dark, color.get('elevation/1', 'rgb'), color.get('elevation/6', 'rgb'))}; 322 | 323 | --color-secondary-bg: #{color.get('elevation/4')}; 324 | --color-expand-button: #{color.get('elevation/6')}; 325 | --color-placeholder-text: #{color.get('elevation/9')}; 326 | --color-editor-line-highlight: var(--color-primary-light-5); 327 | --color-project-column-bg: #{color.get('elevation/1')}; 328 | --color-caret: var(--color-text); 329 | --color-reaction-bg: rgba(#{color.get('primary/dark/30%', 'rgb')}, .1);; 330 | --color-reaction-hover-bg: rgba(#{color.get('primary/dark/30%', 'rgb')}, .2);; 331 | --color-reaction-active-bg: rgba(#{color.get('primary/dark/30%', 'rgb')}, .05);; 332 | --color-tooltip-text: #{if($is-dark, color.get('text'), color.get('elevation/1'))}; 333 | --color-tooltip-bg: #{if($is-dark, color.get('elevation/7'), color.get('elevation/10'))}; 334 | --color-overlay-backdrop: rgba(#{color.get('elevation/1', 'rgb')}, 0.8); 335 | 336 | // navbar 337 | --color-nav-bg: #{color.get('navbar/bg', $default: if($is-dark, color.get('elevation/1'), color.get('elevation/4')))}; 338 | --color-nav-hover-bg: #{color.get('navbar/hover', $default: if($is-dark, color.get('elevation/5'), color.get('elevation/5')))}; 339 | --color-nav-text: #{color.get('navbar/fg', $default: var(--color-text))}; 340 | --color-secondary-nav-bg: #{if($is-dark, color.get('elevation/1'), color.get('elevation/4'))}; 341 | --color-secondary-nav-hover-bg: #{if($is-dark, color.get('elevation/4'), color.get('elevation/5'))}; 342 | 343 | // label 344 | --color-label-text: var(--color-text); 345 | --color-label-bg: #7282924b; 346 | --color-label-hover-bg: #728292a0; 347 | --color-label-active-bg: #728292ff; 348 | 349 | // accents 350 | --color-accent: #{color.get('accent', $default: color.get('palette/blue/dark/25%'))}; 351 | --color-small-accent: #{color.get('accent', $default: var(--color-primary-light-5))}; 352 | --color-highlight-fg: #{color.get('palette/yellow/dark/20%')}; 353 | --color-highlight-bg: #{color.get('palette/yellow/dark/75%')}; 354 | } 355 | 356 | #commits-table { 357 | // even rows color 358 | --color-light: transparent !important; 359 | } 360 | 361 | #navbar { 362 | #navbar-logo { 363 | img { 364 | height: var.get('navbar-logo/height') !important; 365 | width: var.get('navbar-logo/width') !important; 366 | } 367 | } 368 | } 369 | 370 | .markup { 371 | code { 372 | border-radius: var.get('measure/.375x') !important; 373 | } 374 | 375 | p { 376 | line-height: 1.5 !important; 377 | } 378 | } 379 | 380 | ::selection { 381 | background-color: #{color.get('primary/dark/60%')}; 382 | color: #{color.get('primary/dark/60%', 'contrast')}; 383 | } 384 | 385 | // // In dark mode, invert emojis that are hard to read otherwise 386 | @if $is-dark { 387 | .emoji[aria-label="check mark"], 388 | .emoji[aria-label="currency exchange"], 389 | .emoji[aria-label="TOP arrow"], 390 | .emoji[aria-label="END arrow"], 391 | .emoji[aria-label="ON! arrow"], 392 | .emoji[aria-label="SOON arrow"], 393 | .emoji[aria-label="heavy dollar sign"], 394 | .emoji[aria-label="copyright"], 395 | .emoji[aria-label="registered"], 396 | .emoji[aria-label="trade mark"], 397 | .emoji[aria-label="multiply"], 398 | .emoji[aria-label="plus"], 399 | .emoji[aria-label="minus"], 400 | .emoji[aria-label="divide"], 401 | .emoji[aria-label="curly loop"], 402 | .emoji[aria-label="double curly loop"], 403 | .emoji[aria-label="wavy dash"], 404 | .emoji[aria-label="paw prints"], 405 | .emoji[aria-label="musical note"], 406 | .emoji[aria-label="musical notes"] { 407 | filter: invert(100%) hue-rotate(180deg); 408 | } 409 | } 410 | } 411 | -------------------------------------------------------------------------------- /src/themes/scss/theme/vars/index.scss: -------------------------------------------------------------------------------- 1 | @forward './base'; 2 | @forward './colors'; -------------------------------------------------------------------------------- /tools/build.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { cwd } from 'process'; 3 | import { mkdirSync, rmSync, existsSync } from 'fs'; 4 | import { buildScss } from './tasks/scss.js'; 5 | import { buildImg } from './tasks/img.js'; 6 | import { buildTemplates } from './tasks/templates.js'; 7 | import { Logger } from './utils/logger.js'; 8 | import { buildFonts } from './tasks/fonts.js'; 9 | import { optimizeCss } from './tasks/optimize-css.js'; 10 | const srcPath = join(cwd(), 'src'); 11 | const distPath = join(cwd(), 'dist'); 12 | 13 | const logger = new Logger('build', 'info', 'brightMagenta'); 14 | 15 | function exit(err) { 16 | if (err) { 17 | console.error(err); 18 | } else { 19 | console.log('') 20 | logger.info('Build completed successfully'); 21 | } 22 | 23 | process.exit(err ? 1 : 0); 24 | } 25 | 26 | async function build() { 27 | // cleaning, remove dist folder 28 | if (existsSync(distPath)) { 29 | rmSync(distPath, { recursive: true }); 30 | } 31 | 32 | // recreate dist folder 33 | mkdirSync(distPath, { recursive: true }); 34 | 35 | // start building tasks 36 | await Promise.all([ 37 | buildScss(srcPath, distPath).then(() => optimizeCss(distPath)), 38 | buildImg(srcPath, distPath), 39 | buildFonts(srcPath, distPath), 40 | buildTemplates(srcPath, distPath), 41 | ]); 42 | } 43 | 44 | build().then(exit).catch(exit); 45 | -------------------------------------------------------------------------------- /tools/deploy.js: -------------------------------------------------------------------------------- 1 | import { Logger } from './utils/logger.js'; 2 | import { getArg } from './utils/funcs.js'; 3 | import { resolve } from 'path'; 4 | import { cwd } from 'process'; 5 | import { deploy } from './tasks/deploy.js'; 6 | 7 | const logger = new Logger('deploy', 'info', 'brightMagenta'); 8 | 9 | const src = './src'; 10 | const dist = './dist'; 11 | const serviceName = getArg('--service', 'gitea'); 12 | const srcPath = resolve(cwd(), src); 13 | const distPath = resolve(cwd(), dist); 14 | const serverPath = resolve( 15 | cwd(), 16 | getArg('--server', 'd:/users/lucas/Desktop/dev/server/gitea') 17 | ); 18 | 19 | logger.info('Deploy started!'); 20 | logger.info(`Service name: '${serviceName}'`); 21 | logger.info(`Src path: ${srcPath}`); 22 | logger.info(`Dist path: ${distPath}`); 23 | logger.info(`Server path: ${serverPath}`); 24 | 25 | function exit(err) { 26 | err && logger.error(err); 27 | process.exit(err ? 1 : 0); 28 | } 29 | 30 | async function executeDeployTask() { 31 | await deploy(srcPath, distPath, serverPath, serviceName); 32 | } 33 | 34 | executeDeployTask().then(exit).catch(exit); 35 | -------------------------------------------------------------------------------- /tools/minimize-css.js: -------------------------------------------------------------------------------- 1 | import { optimizeCss } from "./tasks/optimize-css.js"; 2 | import { cwd } from 'process'; 3 | import { join } from 'path'; 4 | 5 | const distPath = join(cwd(), 'dist'); 6 | 7 | // run the optimization creating a .min.css file for each .css file in the dist folder 8 | // useful to check issues with the css files optimization 9 | // (in order to use this script, you need to first build the project removing the 10 | // optimization step from the build.js file) 11 | optimizeCss(distPath, false).then(() => { 12 | console.log('Optimization completed'); 13 | }).catch((err) => { 14 | console.error(err); 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /tools/restart.js: -------------------------------------------------------------------------------- 1 | import { getArg } from './utils/funcs.js'; 2 | import { restartService } from './tasks/restart-service.js'; 3 | import { Logger } from './utils/logger.js'; 4 | 5 | const logger = new Logger('restart', 'info', 'brightMagenta'); 6 | const serviceName = getArg('--service', 'gitea'); 7 | 8 | function exit(err) { 9 | if (err) { 10 | console.error(err); 11 | } else { 12 | console.log(''); 13 | logger.info('Build completed successfully'); 14 | } 15 | 16 | process.exit(err ? 1 : 0); 17 | } 18 | 19 | restartService(serviceName).then(exit).catch(exit); 20 | -------------------------------------------------------------------------------- /tools/serve.js: -------------------------------------------------------------------------------- 1 | import { watch } from 'chokidar'; 2 | import { TaskDebouncer } from './utils/task-debouncer.js'; 3 | import { Logger } from './utils/logger.js'; 4 | import { getArg } from './utils/funcs.js'; 5 | import { resolve } from 'path'; 6 | import { cwd } from 'process'; 7 | import { deploy } from './tasks/deploy.js'; 8 | 9 | const src = './src'; 10 | const dist = './dist'; 11 | const serviceName = getArg('--service', 'gitea'); 12 | const srcPath = resolve(cwd(), src); 13 | const distPath = resolve(cwd(), dist); 14 | const serverPath = resolve( 15 | cwd(), 16 | getArg('--server', 'd:/users/lucas/Desktop/dev/server/gitea') 17 | ); 18 | const debouncer = new TaskDebouncer(300); 19 | const logger = new Logger('serve', 'info', 'brightMagenta'); 20 | 21 | logger.info('Serve task started!'); 22 | logger.info('Watching for changes...'); 23 | logger.info(`Service name: '${serviceName}'`); 24 | logger.info(`Src path: ${srcPath}`); 25 | logger.info(`Dist path: ${distPath}`); 26 | logger.info(`Server path: ${serverPath}`); 27 | 28 | const watcher = watch([`${src}/**/*`], { 29 | persistent: true, 30 | ignoreInitial: true, 31 | }); 32 | 33 | watcher.on('change', (file) => debouncer.add( 34 | deploy, 35 | srcPath, 36 | distPath, 37 | serverPath, 38 | serviceName, 39 | file, 40 | true 41 | )); 42 | -------------------------------------------------------------------------------- /tools/tasks/copy-to.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { Logger } from '../utils/logger.js'; 4 | 5 | const logger = new Logger(copyTo.name, 'info', 'brightYellow'); 6 | 7 | export async function copyTo(sourcePath, targetPath) { 8 | logger.info(`Copying ${sourcePath} to ${targetPath}`); 9 | await recursiveCopy(sourcePath, targetPath); 10 | logger.info(`Copy has finished!`); 11 | } 12 | 13 | async function recursiveCopy(sourcePath, targetPath) { 14 | // Create the target directory if it doesn't exist 15 | if (!fs.existsSync(targetPath)) { 16 | fs.mkdirSync(targetPath, { recursive: true }); 17 | } 18 | 19 | // Get all files and directories in the source path 20 | const files = fs.readdirSync(sourcePath, { withFileTypes: true }); 21 | 22 | for (const file of files) { 23 | const sourceFile = path.join(sourcePath, file.name); 24 | const targetFile = path.join(targetPath, file.name); 25 | 26 | if (file.isDirectory()) { 27 | // Recursively copy directories 28 | await recursiveCopy(sourceFile, targetFile); 29 | } else { 30 | // Copy files 31 | fs.copyFileSync(sourceFile, targetFile); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tools/tasks/deploy.js: -------------------------------------------------------------------------------- 1 | import { Logger } from '../utils/logger.js'; 2 | import { buildScss } from './scss.js'; 3 | import { buildFonts } from './fonts.js'; 4 | import { buildTemplates } from './templates.js'; 5 | import { copyTo } from './copy-to.js'; 6 | import { restartService } from './restart-service.js'; 7 | import { extname } from 'path'; 8 | import browsersync from 'browser-sync'; 9 | import { optimizeCss } from './optimize-css.js'; 10 | 11 | const logger = new Logger('deploy', 'info', 'brightMagenta'); 12 | 13 | const sync = browsersync.create('lugit') 14 | 15 | export async function deploy(srcPath, distPath, serverPath, serviceName, file = null, live = false) { 16 | logger.info('Deploying...'); 17 | 18 | if(live && !sync.active) { 19 | sync.init({ 20 | proxy: 'http://lugit.local', 21 | port: 8080, 22 | }) 23 | } 24 | 25 | let shouldRestart = true; 26 | 27 | // check if it's an scss 28 | if (file !== null && file !== undefined && extname(file) === '.scss') { 29 | shouldRestart = false; 30 | } 31 | 32 | try { 33 | await buildScss(srcPath, distPath).then(() => optimizeCss(distPath)); 34 | await buildFonts(srcPath, distPath); 35 | // await buildImg(srcPath, distPath); 36 | await buildTemplates(srcPath, distPath); 37 | await copyTo(distPath, serverPath); 38 | shouldRestart && await restartService(serviceName); 39 | 40 | if(!shouldRestart && live) { 41 | sync.reload(); 42 | } 43 | 44 | logger.info('Deployment successful!'); 45 | } catch (error) { 46 | logger.error(`Deployment failed: ${error}`); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tools/tasks/fonts.js: -------------------------------------------------------------------------------- 1 | import { copyFileSync, mkdirSync, existsSync } from 'fs'; 2 | import { join } from 'path'; 3 | import { readFiles } from '../utils/funcs.js'; 4 | import { Logger } from '../utils/logger.js'; 5 | 6 | const logger = new Logger(buildFonts.name, 'info', 'brightCyan'); 7 | const imgSrc = 'themes/fonts'; 8 | const imgDest = '/public/assets/fonts'; 9 | 10 | export async function buildFonts(srcHome, distHome) { 11 | logger.info('Fonts build has started'); 12 | const fontsSrcPath = join(srcHome, imgSrc); 13 | const fontsDestPath = join(distHome, imgDest); 14 | 15 | // if fontsSrcPath does not exist, return 16 | if (!existsSync(fontsSrcPath)) { 17 | logger.warn(`No fonts found in ${fontsSrcPath} (there's not even a folder there)`); 18 | return; 19 | } 20 | 21 | const files = readFiles(fontsSrcPath, [ 22 | '.woff', 23 | '.woff2', 24 | '.ttf', 25 | '.eot', 26 | '.svg', 27 | '.otf', 28 | ]); 29 | 30 | // if there are no files, return 31 | if (!files.length) { 32 | logger.warn(`No fonts found in ${fontsSrcPath}`); 33 | return; 34 | } 35 | 36 | mkdirSync(fontsDestPath, { recursive: true }); 37 | 38 | for (const file of files) { 39 | // just copy the file 40 | copyFileSync(join(fontsSrcPath, file), join(fontsDestPath, file)); 41 | } 42 | 43 | logger.info('Fonts build has finished'); 44 | } 45 | -------------------------------------------------------------------------------- /tools/tasks/img.js: -------------------------------------------------------------------------------- 1 | import { fabric } from 'fabric'; 2 | import imageminZopfli from 'imagemin-zopfli'; 3 | import { readFile, writeFile } from 'node:fs/promises'; 4 | import { join, basename } from 'path'; 5 | import { optimize } from 'svgo'; 6 | import { readFiles } from '../utils/funcs.js'; 7 | import { Logger } from '../utils/logger.js'; 8 | import { mkdirSync, copyFileSync } from 'fs'; 9 | 10 | const logger = new Logger(buildImg.name, 'info', 'brightGreen'); 11 | const imgSrc = 'themes/img'; 12 | const imgDest = '/public/assets/img'; 13 | 14 | export async function buildImg(srcHome, distHome) { 15 | logger.info('Images build has started'); 16 | const imgSrcPath = join(srcHome, imgSrc); 17 | const imgDestPath = join(distHome, imgDest); 18 | const images = { logos: { logo: undefined, favicon: undefined }, others: [] }; 19 | mkdirSync(imgDestPath, { recursive: true }); 20 | 21 | const files = readFiles(imgSrcPath, ['.svg', '.png', '.jpg', '.webp', '.gif']); 22 | 23 | // Separate logo.svg and favicon.svg from the rest 24 | files.forEach((file) => { 25 | if (file === 'logo.svg') { 26 | images.logos.logo = join(imgSrcPath, file); 27 | } else if (file === 'favicon.svg') { 28 | images.logos.favicon = join(imgSrcPath, file); 29 | } else { 30 | images.others.push(join(imgSrcPath, file)); 31 | } 32 | }); 33 | 34 | await Promise.all([ 35 | processLogos(images.logos, imgDestPath), 36 | processOthers(images.others, imgDestPath), 37 | ]) 38 | 39 | logger.info('Images build has finished'); 40 | } 41 | 42 | async function processLogos(logos, distHome) { 43 | const promises = []; 44 | 45 | if (logos.logo) { 46 | const svg = await readFile(logos.logo, 'utf8'); 47 | promises.push(generate(svg, join(distHome, 'logo.svg'), { size: 32 })); 48 | promises.push(generate(svg, join(distHome, 'logo.png'), { size: 512 })); 49 | } 50 | 51 | if (logos.favicon) { 52 | const svg = await readFile(logos.favicon, 'utf8'); 53 | promises.push( 54 | generate(svg, join(distHome, 'favicon.svg'), { size: 32 }), 55 | generate(svg, join(distHome, 'favicon.png'), { size: 180 }), 56 | generate(svg, join(distHome, 'apple-touch-icon.png'), { size: 180, bg: true }), 57 | generate(svg, join(distHome, 'avatar_default.png'), { size: 200, bg: true }) 58 | ); 59 | } 60 | 61 | await Promise.all(promises); 62 | } 63 | 64 | function loadSvg(svg) { 65 | return new Promise((resolve) => { 66 | fabric.loadSVGFromString(svg, (objects, options) => { 67 | resolve({ objects, options }); 68 | }); 69 | }); 70 | } 71 | 72 | async function generate(svg, path, { size, bg }) { 73 | if (String(path).endsWith('.svg')) { 74 | const { data } = optimize(svg, { 75 | plugins: [ 76 | 'preset-default', 77 | 'removeDimensions', 78 | { 79 | name: 'addAttributesToSVGElement', 80 | params: { attributes: [{ height: size }] }, 81 | }, 82 | ], 83 | }); 84 | await writeFile(path, data); 85 | return; 86 | } 87 | 88 | const { objects, options } = await loadSvg(svg); 89 | const canvas = new fabric.Canvas(); 90 | 91 | 92 | const newWidth = size * options.width / options.height; 93 | canvas.setDimensions({ width: newWidth, height: size }); 94 | const ctx = canvas.getContext('2d'); 95 | ctx.scale( 96 | options.width ? newWidth / options.width : 1, 97 | options.height ? size / options.height : 1 98 | ); 99 | 100 | if (bg) { 101 | canvas.add( 102 | new fabric.Rect({ 103 | left: 0, 104 | top: 0, 105 | height: size * (1 / (size / options.height)), 106 | width: size * (1 / (size / options.width)), 107 | fill: 'black', 108 | }) 109 | ); 110 | } 111 | 112 | canvas.add(fabric.util.groupSVGElements(objects, options)); 113 | canvas.renderAll(); 114 | 115 | let png = Buffer.from([]); 116 | for await (const chunk of canvas.createPNGStream()) { 117 | png = Buffer.concat([png, chunk]); 118 | } 119 | 120 | png = await imageminZopfli({ more: true })(png); 121 | await writeFile(path, png); 122 | } 123 | 124 | async function processOthers(others, distHome) { 125 | // just copy the rest of the images to dist 126 | for (const img of others) { 127 | copyFileSync(img, join(distHome, basename(img))); 128 | } 129 | } -------------------------------------------------------------------------------- /tools/tasks/optimize-css.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { Logger } from '../utils/logger.js'; 3 | import { readFileSync, readdirSync } from 'fs'; 4 | import * as csstree from 'css-tree'; 5 | import { writeFile } from 'fs/promises'; 6 | 7 | const logger = new Logger('build', 'info', 'brightMagenta'); 8 | 9 | function getcCssFiles(distPath, path) { 10 | try { 11 | return readdirSync(join(distPath, path)) 12 | .filter((fn) => fn.endsWith('.css') && !fn.endsWith('.min.css')) 13 | .map((file) => ({ 14 | name: file.replace('.css', ''), 15 | path: join(distPath, path, file), 16 | })); 17 | } catch (err) { 18 | return []; 19 | } 20 | } 21 | 22 | export async function optimizeCss(distPath, replace = true) { 23 | // get the css files in the dist folder 24 | const cssFiles = getcCssFiles(distPath, 'public/assets/css'); 25 | 26 | for (const file of cssFiles) { 27 | logger.info(`Sanitizing ${file.name} css file`); 28 | let usedCssVariables = []; 29 | 30 | // read the css file 31 | const cssContent = readFileSync(file.path, { encoding: 'utf-8' }); 32 | const ast = csstree.parse(cssContent, {}); 33 | 34 | // get the css variables used in the css file 35 | csstree.walk(ast, (node) => { 36 | if (node.type === 'Function' && node.name === 'var') { 37 | // get the variable name 38 | for (const child of node.children) { 39 | if (child.type === 'Identifier') { 40 | usedCssVariables.push(child.name); 41 | } 42 | } 43 | } 44 | 45 | // could also be that the variable is assigned to another variable 46 | if (node.type === 'Declaration' && node.property.startsWith('--')) { 47 | // check if its assigned to another variable 48 | if(node.property.startsWith('--')) { 49 | if (node.value && node.value.type === 'Function' && node.value.name === 'var') { 50 | for (const child of node.value.children) { 51 | if (child.type === 'Identifier') { 52 | if (!usedCssVariables.includes(child.name)) { 53 | usedCssVariables.push(child.name); 54 | } 55 | } 56 | } 57 | } else if (node.value && node.value.type === 'Raw' && node.value.value) { 58 | const val = node.value.value.trimStart(); // var(--v-primary) 59 | 60 | // if starts with var(, then its assigned to another variable or many variables, get everything that's inside 61 | // var(...) using regex capturing 62 | 63 | // get all varname groups 64 | const matches = val.matchAll(/var\((?[^),\s]+)/g); 65 | 66 | for (const match of matches) { 67 | // get the varname group 68 | const varname = match.groups?.varname; 69 | 70 | if (varname) { 71 | if (!usedCssVariables.includes(varname)) { 72 | usedCssVariables.push(varname); 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | if (node.value.children) { 80 | for (const child of node.value.children) { 81 | if (child.type === 'Function' && child.name === 'var') { 82 | for (const varChild of child.children) { 83 | if (varChild.type === 'Identifier') { 84 | usedCssVariables.push(varChild.name); 85 | } 86 | } 87 | } 88 | } 89 | } 90 | } 91 | }); 92 | 93 | // walk to find all variable declarations and remove the unused ones 94 | csstree.walk(ast, (node, item, list) => { 95 | if ( 96 | node.type === 'Declaration' && 97 | (node.property.startsWith('--v-') || node.property.startsWith('--c-')) 98 | ) { 99 | const variable = node.property; 100 | 101 | if (!usedCssVariables.includes(variable)) { 102 | list.remove(item); 103 | } 104 | } 105 | }); 106 | 107 | const finalPath = replace ? file.path : file.path.replace('.css', '.min.css'); 108 | 109 | await writeFile(finalPath, csstree.generate(ast, { mode: 'safe' })); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tools/tasks/restart-service.js: -------------------------------------------------------------------------------- 1 | import { exec } from 'child_process'; 2 | 3 | import { Logger } from '../utils/logger.js'; 4 | const logger = new Logger(restartService.name, 'info', 'brightRed'); 5 | 6 | export async function restartService(serviceName) { 7 | return new Promise((resolve, reject) => { 8 | logger.info(`Restarting '${serviceName}' service...`); 9 | 10 | let command; 11 | let args; 12 | 13 | if (process.platform === 'win32') { 14 | command = 'cmd.exe'; 15 | args = ['/c', 'net', 'stop', serviceName, '&&', 'net', 'start', serviceName]; 16 | } else { 17 | command = 'sudo'; 18 | args = ['systemctl', 'restart', serviceName]; 19 | } 20 | 21 | exec(`${command} ${args.join(' ')}`, (error, stdout) => { 22 | if (error) { 23 | logger.error(`Failed to restart '${serviceName}' service: ${error}`); 24 | reject(error); 25 | } else { 26 | logger.info(`'${serviceName}' service restarted!`); 27 | resolve(stdout); 28 | } 29 | }); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /tools/tasks/scss.js: -------------------------------------------------------------------------------- 1 | import { mkdirSync, readdirSync, writeFileSync } from 'fs'; 2 | import { join } from 'path'; 3 | import { Logger } from '../utils/logger.js'; 4 | import { compile } from 'sass'; 5 | 6 | const logger = new Logger(buildScss.name, 'debug', 'pink'); 7 | const themesSrc = 'themes/scss'; 8 | const cssDistPath = '/public/assets/css'; 9 | 10 | async function buildThemes(srcPath, distPath) { 11 | const themes = getScssFiles(srcPath, themesSrc); 12 | 13 | for (const theme of themes) { 14 | logger.debug(`Building ${theme.name} theme`); 15 | 16 | const result = compile(theme.path, { 17 | loadPaths: [join(srcPath, '../node_modules')], 18 | quietDeps: true, 19 | logger: { 20 | debug: logger.simpleDebug.bind(logger), 21 | info: logger.simpleInfo.bind(logger), 22 | warn: logger.simpleWarn.bind(logger), 23 | error: logger.simpleError.bind(logger), 24 | } 25 | }); 26 | 27 | logger.debug(`Writing ${theme.name} theme to disk`); 28 | 29 | writeFileSync( 30 | join(distPath, cssDistPath, `theme-${theme.name}.css`), 31 | result.css 32 | ); 33 | } 34 | } 35 | 36 | export async function buildScss(srcPath, distPath) { 37 | logger.info('SCSS build has started'); 38 | 39 | mkdirSync(join(distPath, cssDistPath), { recursive: true }); 40 | 41 | await buildThemes(srcPath, distPath); 42 | logger.info('SCSS build has finished'); 43 | } 44 | 45 | function getScssFiles(srcHome, path) { 46 | try { 47 | return readdirSync(join(srcHome, path)).filter( 48 | (fn) => fn.endsWith('.scss') && !fn.startsWith('_') 49 | ).map((file) => ({ 50 | name: file.replace('.scss', ''), 51 | path: join(srcHome, path, file), 52 | })) 53 | } catch (err) { 54 | return []; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tools/tasks/templates.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { existsSync } from 'fs'; 3 | import { copyFolderRecursiveSync } from '../utils/funcs.js'; 4 | import { Logger } from '../utils/logger.js'; 5 | 6 | const logger = new Logger(buildTemplates.name, 'info', 'blue'); 7 | const tmplSrc = 'templates'; 8 | const tmplDest = '/'; 9 | 10 | export async function buildTemplates(srcHome, distHome) { 11 | logger.info('Templates build has started'); 12 | const tmplSrcPath = join(srcHome, tmplSrc); 13 | const tmplDestPath = join(distHome, tmplDest); 14 | 15 | // if src/templates folder doesn't exist, just return 16 | if (!existsSync(tmplSrcPath)) { 17 | logger.info(`No templates found in ${tmplSrcPath}. Skipping templates build`); 18 | return; 19 | } 20 | 21 | // just copy the entire tmplSrcPath to tmplDestPath 22 | copyFolderRecursiveSync(tmplSrcPath, tmplDestPath); 23 | logger.info('Templates build has finished'); 24 | } 25 | -------------------------------------------------------------------------------- /tools/utils/funcs.js: -------------------------------------------------------------------------------- 1 | import { 2 | existsSync, 3 | lstatSync, 4 | mkdirSync, 5 | readFileSync, 6 | readdirSync, 7 | writeFileSync, 8 | } from 'fs'; 9 | import { basename, join } from 'path'; 10 | 11 | export function readFiles(path, extensions) { 12 | return readdirSync(path).filter((file) => 13 | extensions.some((ext) => file.endsWith(ext)) 14 | ); 15 | } 16 | 17 | function copyFileSync(source, target) { 18 | var targetFile = target; 19 | 20 | // If target is a directory, a new file with the same name will be created 21 | if (existsSync(target)) { 22 | if (lstatSync(target).isDirectory()) { 23 | targetFile = join(target, basename(source)); 24 | } 25 | } 26 | 27 | writeFileSync(targetFile, readFileSync(source)); 28 | } 29 | 30 | export function copyFolderRecursiveSync(source, target) { 31 | var files = []; 32 | 33 | var targetFolder = join(target, basename(source)); 34 | if (!existsSync(targetFolder)) { 35 | mkdirSync(targetFolder); 36 | } 37 | 38 | // Copy 39 | if (lstatSync(source).isDirectory()) { 40 | files = readdirSync(source); 41 | files.forEach(function (file) { 42 | var curSource = join(source, file); 43 | if (lstatSync(curSource).isDirectory()) { 44 | copyFolderRecursiveSync(curSource, targetFolder); 45 | } else { 46 | copyFileSync(curSource, targetFolder); 47 | } 48 | }); 49 | } 50 | } 51 | 52 | // https://github.com/bjoerge/debounce-promise/blob/master/index.js 53 | export function debounce(fn, wait = 0, options = {}) { 54 | let lastCallAt; 55 | let deferred; 56 | let timer; 57 | let pendingArgs = []; 58 | return function debounced(...args) { 59 | const currentWait = getWait(wait); 60 | const currentTime = new Date().getTime(); 61 | 62 | const isCold = !lastCallAt || currentTime - lastCallAt > currentWait; 63 | 64 | lastCallAt = currentTime; 65 | 66 | if (isCold && options.leading) { 67 | return options.accumulate 68 | ? Promise.resolve(fn.call(this, [args])).then((result) => result[0]) 69 | : Promise.resolve(fn.call(this, ...args)); 70 | } 71 | 72 | if (deferred) { 73 | clearTimeout(timer); 74 | } else { 75 | deferred = defer(); 76 | } 77 | 78 | pendingArgs.push(args); 79 | timer = setTimeout(flush.bind(this), currentWait); 80 | 81 | if (options.accumulate) { 82 | const argsIndex = pendingArgs.length - 1; 83 | return deferred.promise.then((results) => results[argsIndex]); 84 | } 85 | 86 | return deferred.promise; 87 | }; 88 | 89 | function flush() { 90 | const thisDeferred = deferred; 91 | clearTimeout(timer); 92 | 93 | Promise.resolve( 94 | options.accumulate 95 | ? fn.call(this, pendingArgs) 96 | : fn.apply(this, pendingArgs[pendingArgs.length - 1]) 97 | ).then(thisDeferred.resolve, thisDeferred.reject); 98 | 99 | pendingArgs = []; 100 | deferred = null; 101 | } 102 | } 103 | 104 | function getWait(wait) { 105 | return typeof wait === 'function' ? wait() : wait; 106 | } 107 | 108 | function defer() { 109 | const deferred = {}; 110 | deferred.promise = new Promise((resolve, reject) => { 111 | deferred.resolve = resolve; 112 | deferred.reject = reject; 113 | }); 114 | return deferred; 115 | } 116 | 117 | export async function sequence(tasks) { 118 | const results = []; 119 | for (const task of tasks) { 120 | results.push(await task()); 121 | } 122 | return results; 123 | } 124 | 125 | 126 | async function tasksRunner(tasks, abort) { 127 | let result = null; 128 | for (const task of tasks) { 129 | if (abort.signal.aborted) { 130 | break; 131 | } 132 | 133 | result = await task(result); 134 | } 135 | } 136 | 137 | // each task should return a promise 138 | // each task takes the result of the previous task as an argument 139 | // the output of the last task is the output of the sequence 140 | export async function sequenceStream(tasks) { 141 | const abort = new AbortController(); 142 | 143 | abort.signal.addEventListener('abort', () => { 144 | console.log('sequenceStream aborted'); 145 | }); 146 | 147 | return [ 148 | tasksRunner(tasks, abort), 149 | abort, 150 | ] 151 | } 152 | 153 | export const getArg = (flag, def) => { 154 | const args = process.argv.slice(2); 155 | const flagIndex = args.findIndex(arg => arg === flag); 156 | 157 | if (flagIndex !== -1 && flagIndex + 1 < args.length) { 158 | return args[flagIndex + 1]; 159 | } 160 | 161 | return def || null; 162 | }; -------------------------------------------------------------------------------- /tools/utils/logger.js: -------------------------------------------------------------------------------- 1 | const LOG_LEVEL_MAP = { 2 | debug: 0, 3 | info: 1, 4 | warn: 2, 5 | error: 3, 6 | }; 7 | 8 | const ANSI_COLORS = { 9 | reset: '\x1b[0m', 10 | black: '\x1b[30m', 11 | red: '\x1b[31m', 12 | green: '\x1b[32m', 13 | yellow: '\x1b[33m', 14 | blue: '\x1b[34m', 15 | magenta: '\x1b[35m', 16 | cyan: '\x1b[36m', 17 | white: '\x1b[37m', 18 | gray: '\x1b[90m', 19 | brightRed: '\x1b[91m', 20 | brightGreen: '\x1b[92m', 21 | brightYellow: '\x1b[93m', 22 | brightBlue: '\x1b[94m', 23 | brightMagenta: '\x1b[95m', 24 | brightCyan: '\x1b[96m', 25 | pink: '\x1b[38;2;255;182;193m' 26 | }; 27 | 28 | export class Logger { 29 | /** 30 | * @param {string} ctx 31 | * @param {'debug'|'info'|'warn'|'error'} log_level - default: 'debug' 32 | * @param {'red'|'green'|'yellow'|'blue'|'magenta'|'cyan'|'white'|'gray'|'brightRed'| 33 | * 'brightGreen'|'brightYellow'|'brightBlue'|'brightMagenta'|'brightCyan'|'pink'} color - default: 'magenta' 34 | */ 35 | constructor(ctx, log_level, color = 'magenta') { 36 | this.ctx = ctx; 37 | this.log_level = LOG_LEVEL_MAP[log_level || 'debug']; 38 | this.color = ANSI_COLORS[color] || ANSI_COLORS.reset; 39 | 40 | if (this.log_level === undefined) { 41 | throw new Error(`Invalid log level: ${log_level}`); 42 | } 43 | } 44 | 45 | debug(...args) { 46 | if (!this.#canLog('debug')) return; 47 | this.log('DEBUG', false, ...args); 48 | } 49 | 50 | info(...args) { 51 | if (!this.#canLog('info')) return; 52 | this.log('INFO', false, ...args); 53 | } 54 | 55 | warn(...args) { 56 | if (!this.#canLog('warn')) return; 57 | this.log('WARN', false, ...args); 58 | 59 | } 60 | 61 | error(...args) { 62 | if (!this.#canLog('error')) return; 63 | this.log('ERROR', false, ...args); 64 | } 65 | 66 | log(level, simple, ...args) { 67 | if (simple) { 68 | args = [args[0]]; 69 | } 70 | 71 | if (level === 'ERROR') { 72 | console.error( 73 | `${this.color}[${level}] [${this.ctx}]${ANSI_COLORS.reset}`, 74 | ...args 75 | ); 76 | } else { 77 | console.log('🍵', `${this.color}[${this.ctx}]${ANSI_COLORS.reset}`, ...args); 78 | } 79 | } 80 | 81 | simpleDebug(...args) { 82 | if (!this.#canLog('debug')) return; 83 | this.log('DEBUG', true, ...args); 84 | } 85 | 86 | simpleInfo(...args) { 87 | if (!this.#canLog('info')) return; 88 | this.log('INFO', true, ...args); 89 | } 90 | 91 | simpleWarn(...args) { 92 | if (!this.#canLog('warn')) return; 93 | this.log('WARN', true, ...args); 94 | 95 | } 96 | 97 | simpleError(...args) { 98 | if (!this.#canLog('error')) return; 99 | this.log('ERROR', true, ...args); 100 | } 101 | 102 | #canLog(level) { 103 | return this.log_level <= LOG_LEVEL_MAP[level]; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /tools/utils/task-debouncer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * #### TaskDebouncer 3 | * 4 | * Executes a task after a certain delay, but cancels the execution if 5 | * a new task is sent before the delay expires. Also, if a task is 6 | * already being executed, the new task is queued and executed after 7 | * the current one finishes. It will only execute the task that was 8 | * sent last. 9 | */ 10 | export class TaskDebouncer { 11 | constructor(debounceDelay) { 12 | this.debounceDelay = debounceDelay; 13 | this.queued = undefined; 14 | this.isProcessing = false; 15 | this.timerId = null; 16 | } 17 | 18 | #clearQueue() { 19 | this.queued = undefined; 20 | } 21 | 22 | #enqueue(executor, args) { 23 | this.queued = { executor, args }; 24 | this.#processQueue(); 25 | } 26 | 27 | #setProcessing(value) { 28 | this.isProcessing = value; 29 | } 30 | 31 | async #processQueue() { 32 | if (this.isProcessing || !this.queued) { 33 | return; 34 | } 35 | const { executor, args } = this.queued; 36 | this.#clearQueue(); 37 | 38 | // execute the task 39 | this.#setProcessing(true); 40 | await executor(...args); 41 | this.#setProcessing(false); 42 | 43 | // continue with the next task 44 | this.#continue(); 45 | } 46 | 47 | #continue() { 48 | if (this.queued) { 49 | this.#processQueue(); 50 | } 51 | } 52 | 53 | /** 54 | * Adds a task to the queue. If a task is already being executed, 55 | * the new task is queued and executed after the current one finishes. 56 | * It will only execute the task if no other task is sent before the 57 | * delay expires or before the current task finishes. 58 | * 59 | * IOW, it will only execute the task that was sent last. 60 | */ 61 | add(executor, ...args) { 62 | clearTimeout(this.timerId); 63 | this.timerId = setTimeout(() => { 64 | this.#enqueue(executor, args); 65 | }, this.debounceDelay); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /🍵 lugit-theme.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "files.exclude": { 9 | "**/.git": false, 10 | "**/.svn": false, 11 | "**/.hg": false, 12 | "**/CVS": false, 13 | "**/.DS_Store": false, 14 | "**/Thumbs.db": true, 15 | "**/.project": false, 16 | "**/.classpath": false, 17 | ".vscode": false, 18 | ".settings": false, 19 | ".idea/": false, 20 | ".out/": false, 21 | "out/": false, 22 | "*.iml": false, 23 | "*.iws": false, 24 | "*.ipr": false, 25 | "*.ids": false, 26 | "*.orig": false, 27 | "build/": false, 28 | "bin/": false, 29 | ".gradle": false, 30 | "**/.settings": true, 31 | "**/.factorypath": true 32 | } 33 | 34 | } 35 | } --------------------------------------------------------------------------------