├── .env.example
├── .eslintignore
├── .eslintrc.cjs
├── .github
├── renovate.json
└── workflows
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .stylelintignore
├── .stylelintrc
├── .vscode
├── extensions.json
└── settings.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── assets
├── apple-dark.png
├── apple-light.png
├── dashboard-dark.png
├── dashboard-light.png
├── linux.png
├── logo.png
└── windows.png
├── package.json
├── pnpm-lock.yaml
├── src-tauri
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── Info.plist
├── build.rs
├── icons
│ ├── 128x128.png
│ ├── 128x128@2x.png
│ ├── 32x32.png
│ ├── Square107x107Logo.png
│ ├── Square142x142Logo.png
│ ├── Square150x150Logo.png
│ ├── Square284x284Logo.png
│ ├── Square30x30Logo.png
│ ├── Square310x310Logo.png
│ ├── Square44x44Logo.png
│ ├── Square71x71Logo.png
│ ├── Square89x89Logo.png
│ ├── StoreLogo.png
│ ├── icon.icns
│ ├── icon.ico
│ ├── icon.png
│ ├── tray-base-macos.png
│ ├── tray-base.png
│ ├── tray-new-macos.png
│ └── tray-new.png
├── src
│ ├── commands.rs
│ ├── main.rs
│ └── title_bar.rs
└── tauri.conf.json
├── src
├── app.d.ts
├── app.html
├── lib
│ ├── components
│ │ ├── common
│ │ │ ├── AnimatedLogo.svelte
│ │ │ ├── Button.svelte
│ │ │ ├── DragRegion.svelte
│ │ │ ├── IconButton.svelte
│ │ │ ├── InlineSelect.svelte
│ │ │ ├── Input.svelte
│ │ │ ├── Modal.svelte
│ │ │ ├── ScrollbarContainer.svelte
│ │ │ ├── Select.svelte
│ │ │ ├── ShrinkableWrapper.svelte
│ │ │ ├── Switch.svelte
│ │ │ ├── Tooltip.svelte
│ │ │ └── index.ts
│ │ ├── dashboard
│ │ │ ├── Banner.svelte
│ │ │ ├── LoadingScreen.svelte
│ │ │ ├── Main.svelte
│ │ │ ├── SyncPill.svelte
│ │ │ ├── index.ts
│ │ │ ├── notifications
│ │ │ │ ├── DoneModal.svelte
│ │ │ │ ├── Notification.svelte
│ │ │ │ ├── NotificationColumn.svelte
│ │ │ │ ├── NotificationDescription.svelte
│ │ │ │ ├── NotificationLabels.svelte
│ │ │ │ ├── NotificationList.svelte
│ │ │ │ ├── NotificationPlaceholder.svelte
│ │ │ │ ├── NotificationStatus.svelte
│ │ │ │ └── index.ts
│ │ │ ├── priorities
│ │ │ │ ├── Priorities.svelte
│ │ │ │ ├── PriorityItem.svelte
│ │ │ │ └── index.ts
│ │ │ └── sidebar
│ │ │ │ ├── Sidebar.svelte
│ │ │ │ ├── SidebarProviders.svelte
│ │ │ │ ├── SidebarSearch.svelte
│ │ │ │ ├── SidebarSection.svelte
│ │ │ │ ├── TypeFilters.svelte
│ │ │ │ ├── WatchedPersons.svelte
│ │ │ │ ├── WatchedRepos.svelte
│ │ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── landing
│ │ │ ├── DownloadButton.svelte
│ │ │ └── index.ts
│ │ ├── login
│ │ │ ├── GithubLoginButton.svelte
│ │ │ ├── GitlabLoginButton.svelte
│ │ │ └── index.ts
│ │ └── settings
│ │ │ ├── App.svelte
│ │ │ ├── Preferences.svelte
│ │ │ ├── Settings.svelte
│ │ │ ├── accounts
│ │ │ ├── Account.svelte
│ │ │ ├── Accounts.svelte
│ │ │ ├── LogOutButton.svelte
│ │ │ └── index.ts
│ │ │ ├── github-settings
│ │ │ ├── GithubSettings.svelte
│ │ │ ├── PatItem.svelte
│ │ │ └── index.ts
│ │ │ ├── gitlab-settings
│ │ │ ├── GitlabRepos.svelte
│ │ │ ├── GitlabSettings.svelte
│ │ │ ├── RepoInput.svelte
│ │ │ └── index.ts
│ │ │ └── index.ts
│ ├── features
│ │ ├── createGithubNotificationData.ts
│ │ ├── createGitlabNotificationData.ts
│ │ ├── delayed-hover.ts
│ │ ├── drag-actions.ts
│ │ ├── fetchGithub.ts
│ │ ├── fetchGitlab.ts
│ │ ├── getGithubDiscussionData.ts
│ │ ├── index.ts
│ │ ├── intersection-action.ts
│ │ └── storage.ts
│ ├── helpers
│ │ ├── debounce.ts
│ │ ├── formatRelativeDate.ts
│ │ ├── getAppVersion.ts
│ │ ├── getIcon.ts
│ │ ├── getNotificationIcon.ts
│ │ ├── index.ts
│ │ ├── openDesktopApp.ts
│ │ ├── priorities.ts
│ │ ├── removeMarkdownSymbols.ts
│ │ └── shadeColor.ts
│ ├── icons
│ │ ├── AnsweredDiscussionIcon.svelte
│ │ ├── ArrowRightIcon.svelte
│ │ ├── ArrowUpIcon.svelte
│ │ ├── CheckIcon.svelte
│ │ ├── ClosedIssueIcon.svelte
│ │ ├── ClosedPullRequestIcon.svelte
│ │ ├── CommitIcon.svelte
│ │ ├── CompletedIssueIcon.svelte
│ │ ├── CrossIcon.svelte
│ │ ├── DiscussionIcon.svelte
│ │ ├── DoubleArrowIcon.svelte
│ │ ├── DoubleCheckIcon.svelte
│ │ ├── DraftPullRequestIcon.svelte
│ │ ├── ExclamationMarkIcon.svelte
│ │ ├── ExternalLinkIcon.svelte
│ │ ├── GearIcon.svelte
│ │ ├── GithubIcon.svelte
│ │ ├── GitlabIcon.svelte
│ │ ├── HeartIcon.svelte
│ │ ├── LightningIcon.svelte
│ │ ├── LinuxIcon.svelte
│ │ ├── MacosIcon.svelte
│ │ ├── MergedPullRequestIcon.svelte
│ │ ├── MuteIcon.svelte
│ │ ├── MutedIcon.svelte
│ │ ├── OpenIssueIcon.svelte
│ │ ├── OpenPullRequestIcon.svelte
│ │ ├── PersonIcon.svelte
│ │ ├── PinIcon.svelte
│ │ ├── PlusIcon.svelte
│ │ ├── PriorityDownIcon.svelte
│ │ ├── PriorityIcon.svelte
│ │ ├── PriorityUpIcon.svelte
│ │ ├── RefreshIcon.svelte
│ │ ├── ReleaseIcon.svelte
│ │ ├── RepositoryIcon.svelte
│ │ ├── RestoreIcon.svelte
│ │ ├── SearchIcon.svelte
│ │ ├── SmallArrowIcon.svelte
│ │ ├── SparklesIcon.svelte
│ │ ├── ThreeDotsIcon.svelte
│ │ ├── TrashIcon.svelte
│ │ ├── UnpinIcon.svelte
│ │ ├── UnreadIcon.svelte
│ │ ├── WindowsIcon.svelte
│ │ ├── WorkflowFailIcon.svelte
│ │ ├── WorkflowSuccessIcon.svelte
│ │ ├── XIcon.svelte
│ │ └── index.ts
│ ├── stores
│ │ ├── index.ts
│ │ └── stores.ts
│ └── types
│ │ ├── common-types.ts
│ │ ├── github-types.ts
│ │ ├── gitlab-types.ts
│ │ ├── index.ts
│ │ └── type-helpers.ts
├── routes
│ ├── (app)
│ │ ├── +layout.svelte
│ │ ├── dashboard
│ │ │ └── +page.svelte
│ │ ├── deeplink
│ │ │ └── +page.svelte
│ │ └── login
│ │ │ └── +page.svelte
│ ├── (tray)
│ │ └── tray
│ │ │ └── +page.svelte
│ ├── +layout.ts
│ ├── +page.svelte
│ ├── auth
│ │ ├── github
│ │ │ ├── callback
│ │ │ │ └── +server.ts
│ │ │ └── login
│ │ │ │ └── +server.ts
│ │ └── gitlab
│ │ │ ├── callback
│ │ │ └── +server.ts
│ │ │ ├── login
│ │ │ └── +server.ts
│ │ │ └── refresh
│ │ │ └── +server.ts
│ ├── download
│ │ └── [os]
│ │ │ └── +server.ts
│ └── version
│ │ └── [target]
│ │ └── [version]
│ │ └── +server.ts
└── styles
│ ├── _base.scss
│ ├── _fonts.scss
│ ├── _mixins.scss
│ ├── _reset.scss
│ ├── _screens.scss
│ ├── _themes.scss
│ ├── _typography.scss
│ └── _variables.scss
├── static
├── favicon.ico
├── fonts
│ ├── Inter-Regular.ttf
│ └── Inter-SemiBold.ttf
├── images
│ ├── gitlight-dark.webp
│ ├── gitlight-light.webp
│ └── logo.webp
└── rive
│ └── logo.riv
├── svelte.config.js
├── tsconfig.json
└── vite.config.ts
/.env.example:
--------------------------------------------------------------------------------
1 | AUTH_GITHUB_ID=
2 | AUTH_GITHUB_SECRET=
3 | AUTH_GITLAB_ID=
4 | AUTH_GITLAB_SECRET=
5 | AUTH_SECRET=
6 | PUBLIC_SITE_URL=
7 |
8 | # Only needed to build the desktop app
9 | TAURI_PRIVATE_KEY=
10 | TAURI_KEY_PASSWORD=
11 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 | /src-tauri
10 |
11 | # Ignore files for PNPM, NPM and YARN
12 | pnpm-lock.yaml
13 | package-lock.json
14 | yarn.lock
15 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /** @type { import("eslint").Linter.Config } */
2 | module.exports = {
3 | root: true,
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:svelte/recommended',
8 | 'prettier'
9 | ],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['@typescript-eslint', 'import'],
12 | parserOptions: {
13 | sourceType: 'module',
14 | ecmaVersion: 2020,
15 | extraFileExtensions: ['.svelte']
16 | },
17 | ignorePatterns: ['*.cjs', 'svelte.config.js'],
18 | env: {
19 | browser: true,
20 | es2017: true,
21 | node: true
22 | },
23 | overrides: [
24 | {
25 | files: ['*.svelte'],
26 | parser: 'svelte-eslint-parser',
27 | parserOptions: {
28 | parser: '@typescript-eslint/parser'
29 | }
30 | }
31 | ],
32 | rules: {
33 | '@typescript-eslint/no-unused-vars': [1, { argsIgnorePattern: '^_' }],
34 | 'no-console': ['warn', { allow: ['error'] }],
35 | 'no-self-assign': 'off',
36 | 'import/order': [
37 | 'warn',
38 | {
39 | groups: ['external', 'unknown', 'internal', 'sibling', 'parent'],
40 | pathGroups: [
41 | { pattern: '$.*', group: 'unknown' },
42 | { pattern: '$lib/*', group: 'internal' }
43 | ],
44 | alphabetize: { order: 'asc', caseInsensitive: true },
45 | 'newlines-between': 'never'
46 | }
47 | ]
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": ["config:base", "group:allNonMajor"],
4 | "labels": ["dependency"],
5 | "rangeStrategy": "bump",
6 | "enabled": false
7 | }
8 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push:
4 | branches:
5 | - main
6 | pull_request:
7 | env:
8 | AUTH_GITHUB_ID: ${{ secrets.AUTH_GITHUB_ID }}
9 | AUTH_GITHUB_SECRET: ${{ secrets.AUTH_GITHUB_SECRET }}
10 | AUTH_GITLAB_ID: ${{ secrets.AUTH_GITLAB_ID }}
11 | AUTH_GITLAB_SECRET: ${{ secrets.AUTH_GITLAB_SECRET }}
12 | AUTH_SECRET: ${{ secrets.AUTH_SECRET }}
13 | PUBLIC_SITE_URL: ${{ secrets.PUBLIC_SITE_URL }}
14 | TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
15 | TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
16 | jobs:
17 | lint-ts:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - uses: actions/checkout@v4
21 | - uses: pnpm/action-setup@v4
22 | with:
23 | version: 8
24 | - name: Use Node.js 18
25 | uses: actions/setup-node@v4
26 | with:
27 | node-version: 18
28 | cache: 'pnpm'
29 | - name: Install dependencies
30 | run: pnpm install
31 | - name: Lint TypeScript
32 | run: pnpm lint:ts
33 | lint-rs:
34 | runs-on: ubuntu-latest
35 | steps:
36 | - uses: actions/checkout@v4
37 | - uses: pnpm/action-setup@v4
38 | with:
39 | version: 8
40 | - name: Install Tauri dependencies
41 | run: |
42 | sudo apt-get update
43 | sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev
44 | - name: Rust setup
45 | uses: dtolnay/rust-toolchain@stable
46 | - name: Rust cache
47 | uses: swatinem/rust-cache@v2
48 | with:
49 | workspaces: './src-tauri -> target'
50 | - name: Lint Rust
51 | run: pnpm lint:rs
52 | typecheck:
53 | runs-on: ubuntu-latest
54 | steps:
55 | - uses: actions/checkout@v4
56 | - uses: pnpm/action-setup@v4
57 | with:
58 | version: 8
59 | - name: Use Node.js 18
60 | uses: actions/setup-node@v4
61 | with:
62 | node-version: 18
63 | cache: 'pnpm'
64 | - name: Install dependencies
65 | run: pnpm install
66 | - name: Typecheck
67 | run: pnpm typecheck
68 | build-front:
69 | runs-on: ubuntu-latest
70 | steps:
71 | - uses: actions/checkout@v4
72 | - uses: pnpm/action-setup@v4
73 | with:
74 | version: 8
75 | - name: Use Node.js 18
76 | uses: actions/setup-node@v4
77 | with:
78 | node-version: 18
79 | cache: 'pnpm'
80 | - name: Install dependencies
81 | run: pnpm install
82 | - name: Build
83 | run: pnpm build
84 | build-app:
85 | # Tauri build from forks fails because no access to secrets
86 | if: ${{ !github.event.pull_request.head.repo.fork }}
87 | runs-on: ubuntu-latest
88 | steps:
89 | - uses: actions/checkout@v4
90 | - name: Install Tauri dependencies
91 | run: |
92 | sudo apt-get update
93 | sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev
94 | - name: Rust setup
95 | uses: dtolnay/rust-toolchain@stable
96 | - name: Rust cache
97 | uses: swatinem/rust-cache@v2
98 | with:
99 | workspaces: './src-tauri -> target'
100 | - uses: pnpm/action-setup@v4
101 | with:
102 | version: 8
103 | - name: Use Node.js 18
104 | uses: actions/setup-node@v4
105 | with:
106 | node-version: 18
107 | cache: 'pnpm'
108 | - name: Install frontend dependencies
109 | run: pnpm install
110 | - name: Build app
111 | run: pnpm tauri build
112 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | push:
4 | branches:
5 | - main
6 | workflow_dispatch:
7 | env:
8 | AUTH_GITHUB_ID: ${{ secrets.AUTH_GITHUB_ID }}
9 | AUTH_GITHUB_SECRET: ${{ secrets.AUTH_GITHUB_SECRET }}
10 | AUTH_GITLAB_ID: ${{ secrets.AUTH_GITLAB_ID }}
11 | AUTH_GITLAB_SECRET: ${{ secrets.AUTH_GITLAB_SECRET }}
12 | AUTH_SECRET: ${{ secrets.AUTH_SECRET }}
13 | PUBLIC_SITE_URL: ${{ secrets.PUBLIC_SITE_URL }}
14 | TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
15 | TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
16 | jobs:
17 | release:
18 | if: contains(github.event.head_commit.message, 'release')
19 | strategy:
20 | fail-fast: false
21 | matrix:
22 | include:
23 | - os: macos-latest
24 | target: aarch64-apple-darwin
25 | - os: macos-latest
26 | target: x86_64-apple-darwin
27 | - os: ubuntu-20.04
28 | - os: windows-latest
29 | runs-on: ${{ matrix.os }}
30 | steps:
31 | - uses: actions/checkout@v4
32 | - name: Install Tauri dependencies (ubuntu only)
33 | if: matrix.os == 'ubuntu-20.04'
34 | run: |
35 | sudo apt-get update
36 | sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev
37 | - name: Rust setup
38 | uses: dtolnay/rust-toolchain@stable
39 | - name: Rust cache
40 | uses: swatinem/rust-cache@v2
41 | with:
42 | workspaces: './src-tauri -> target'
43 | - if: matrix.target
44 | run: rustup target add ${{ matrix.target }}
45 | - uses: pnpm/action-setup@v4
46 | with:
47 | version: 8
48 | - name: Use Node.js 18
49 | uses: actions/setup-node@v4
50 | with:
51 | node-version: 18
52 | cache: 'pnpm'
53 | - name: Install frontend dependencies
54 | run: pnpm install
55 | - name: Build app
56 | uses: tauri-apps/tauri-action@v0
57 | env:
58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59 | APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
60 | APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
61 | APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
62 | APPLE_ID: ${{ secrets.APPLE_ID }}
63 | APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
64 | APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
65 | with:
66 | tagName: gitlight-v__VERSION__
67 | releaseName: 'GitLight v__VERSION__'
68 | releaseBody: 'See the assets to download and install this version.'
69 | releaseDraft: true
70 | prerelease: false
71 | args: ${{ matrix.target && format('--target {0}', matrix.target) }}
72 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 | vite.config.js.timestamp-*
10 | vite.config.ts.timestamp-*
11 | .vercel
12 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 | /src-tauri
10 |
11 | # Ignore files for PNPM, NPM and YARN
12 | pnpm-lock.yaml
13 | package-lock.json
14 | yarn.lock
15 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "singleQuote": true,
4 | "trailingComma": "none",
5 | "printWidth": 100,
6 | "plugins": ["prettier-plugin-svelte"],
7 | "overrides": [
8 | { "files": "*.svelte", "options": { "parser": "svelte" } },
9 | { "files": "./src/lib/icons/*.svelte", "options": { "htmlWhitespaceSensitivity": "ignore" } }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 | /src-tauri
10 |
11 | # Ignore files for PNPM, NPM and YARN
12 | pnpm-lock.yaml
13 | package-lock.json
14 | yarn.lock
15 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["stylelint-config-standard-scss", "stylelint-config-idiomatic-order"],
3 | "plugins": ["stylelint-prettier"],
4 | "overrides": [
5 | {
6 | "files": ["*.svelte"],
7 | "extends": "stylelint-config-html/svelte"
8 | }
9 | ],
10 | "rules": {
11 | "color-function-notation": "legacy",
12 | "custom-property-empty-line-before": "never",
13 | "no-descending-specificity": null,
14 | "property-no-vendor-prefix": null,
15 | "selector-pseudo-class-no-unknown": [true, { "ignorePseudoClasses": ["global"] }],
16 | "scss/no-global-function-names": null
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "svelte.svelte-vscode",
4 | "dbaeumer.vscode-eslint",
5 | "esbenp.prettier-vscode",
6 | "stylelint.vscode-stylelint"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.defaultFormatter": "esbenp.prettier-vscode",
3 | "editor.formatOnSave": true,
4 | "editor.codeActionsOnSave": {
5 | "source.fixAll.eslint": "explicit",
6 | "source.fixAll.stylelint": "explicit"
7 | },
8 | "eslint": {
9 | "validate": ["svelte"]
10 | },
11 | "[svelte]": {
12 | "editor.defaultFormatter": "svelte.svelte-vscode"
13 | },
14 | "svelte.enable-ts-plugin": true,
15 | "stylelint.validate": ["scss", "svelte"]
16 | }
17 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to GitLight
2 |
3 | 👋 Hey, thanks for wanting to improve GitLight! Any contribution is welcome and appreciated!
4 |
5 | ---
6 |
7 | ## Before contributing
8 |
9 | The goal of GitLight is to make developers work faster and stay up to date with their git workflow by providing accurate data, filters and more. It is focused on receiving and managing notifications. I'm trying to make the UI more intuitive and easier to use in order to provide the best possible experience.
10 |
11 | ### Roadmap
12 |
13 | [See the roadmap on GitHub](https://github.com/users/colinlienard/projects/1)
14 |
15 | ### Tech Stack
16 |
17 | - UI → [Svelte](https://svelte.dev/)
18 | - Framework → [SvelteKit](https://kit.svelte.dev/)
19 | - Langage → [Typescript](https://www.typescriptlang.org/)
20 | - Desktop app → [Tauri](https://tauri.app/)
21 | - Deployment → [Vercel](https://vercel.com)
22 | - Package manager → [pnpm](https://pnpm.io/)
23 |
24 | ## How to contribute
25 |
26 | ### Feature request
27 |
28 | If you are using GitLight and are missing a feature that you would find helful, please create an issue. Other may also find it missing.
29 |
30 | ### Reporting bugs
31 |
32 | If you hit a bug, you should first check if it's not already reported in the issues, and if not, please create an issue or contact me on Twitter.
33 |
34 | ### Running locally
35 |
36 | #### Desktop app
37 |
38 | > **Note**: Skip this if you don't want to work on the native app
39 |
40 | Just follow the [Tauri prerequisites](https://tauri.app/v1/guides/getting-started/prerequisites).
41 |
42 | #### GitHub OAuth app
43 |
44 | The app needs to authenticate the user to GitHub, so we need to create a new OAuth GitHub application [here](https://github.com/settings/applications/new). Fill the fields and set the **Authorization callback url** to `http://localhost:5173/auth/github/callback`.
45 |
46 | Also create a unique 32 characters code here: https://generate-secret.vercel.app/32
47 |
48 | Then, create a `.env` file at the root of the project:
49 |
50 | ```.env
51 | AUTH_GITHUB_ID={your client ID}
52 | AUTH_GITHUB_SECRET={your client secret}
53 | AUTH_SECRET={your 32 characters code}
54 | PUBLIC_SITE_URL=http://localhost:5173
55 | ```
56 |
57 | #### GitLab application
58 |
59 | The app needs to authenticate the user to GitLab, so we need to create a new GitLab application [here](https://gitlab.com/-/profile/applications) and click on **Add new application**. Fill the fields and set the **Callback url** to `http://localhost:5173/auth/gitlab/callback`. Choose the following scopes:
60 |
61 | - `read_api`
62 | - `read_user`
63 |
64 | Also create a unique 32 characters code here: https://generate-secret.vercel.app/32
65 |
66 | Then, create a `.env` file at the root of the project:
67 |
68 | ```.env
69 | AUTH_GITLAB_ID={your client ID}
70 | AUTH_GITLAB_SECRET={your client secret}
71 | AUTH_SECRET={your 32 characters code}
72 | PUBLIC_SITE_URL=http://localhost:5173
73 | ```
74 |
75 | #### Frontend
76 |
77 | Just install dependencies:
78 |
79 | ```bash
80 | pnpm install
81 | ```
82 |
83 | Finally, run `pnpm dev` or `pnpm dev:tauri` to start the dev server!
84 |
85 | ## Styleguides
86 |
87 | - PR names should follow the [conventionnal commits](https://www.conventionalcommits.org/en/v1.0.0/) guidelines.
88 | - Code should be valid for Eslint and Prettier.
89 | - In css, `rem` should be used instead of `px` (apart from borders).
90 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Colin Lienard
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 |
3 |
4 |
5 | # GitLight
6 |
7 | GitHub & GitLab notifications on your desktop • [gitlight.app](https://gitlight.app)
8 |
9 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | ---
51 |
52 | ## About
53 |
54 | Better GitHub and/or GitLab notifications. Available on **MacOS**, **Windows**, **Linux** and in the **browser**. Free and open-source.
55 |
56 | You can download the app or install it with Homebrew:
57 |
58 | ```bash
59 | brew install gitlight
60 | ```
61 |
62 | ## Features
63 |
64 | - Get push notifications
65 | - Monitor notifications with efficiency thanks to a kanban style interface
66 | - Filter by repository, organization, pull request, issues, commits...
67 | - Get precise notification data
68 | - GitHub and GitLab notifications at the same time
69 | - And more...
70 |
71 | ## Contributing
72 |
73 | [How to contribute](./CONTRIBUTING.md)
74 |
75 | ## License
76 |
77 | [MIT](./LICENSE) © Colin Lienard
78 |
--------------------------------------------------------------------------------
/assets/apple-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/assets/apple-dark.png
--------------------------------------------------------------------------------
/assets/apple-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/assets/apple-light.png
--------------------------------------------------------------------------------
/assets/dashboard-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/assets/dashboard-dark.png
--------------------------------------------------------------------------------
/assets/dashboard-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/assets/dashboard-light.png
--------------------------------------------------------------------------------
/assets/linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/assets/linux.png
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/assets/logo.png
--------------------------------------------------------------------------------
/assets/windows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/assets/windows.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gitlight",
3 | "version": "0.17.6",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "build": "vite build",
8 | "build:tauri": "APP_ENV=tauri tauri build",
9 | "build:vercel": "APP_ENV=vercel vite build",
10 | "dev": "vite dev",
11 | "dev:tauri": "APP_ENV=tauri tauri dev",
12 | "lint-fix": "prettier --write . && ESLINT_USE_FLAT_CONFIG=false eslint --fix . && stylelint --fix \"src/**/*.{scss,svelte}\"",
13 | "lint:rs": "mkdir build && cd src-tauri && cargo clippy -- -Dwarnings --no-deps; cargo fmt -- --check",
14 | "lint:ts": "prettier --check . && ESLINT_USE_FLAT_CONFIG=false eslint --max-warnings=0 . && stylelint --max-warnings=0 \"src/**/*.{scss,svelte}\"",
15 | "prepare": "svelte-kit sync",
16 | "preview": "vite preview",
17 | "typecheck": "svelte-check --tsconfig ./tsconfig.json"
18 | },
19 | "dependencies": {
20 | "@rive-app/canvas": "^2.15.6",
21 | "@tauri-apps/api": "^1.5.6",
22 | "overlayscrollbars": "^2.8.0",
23 | "tauri-plugin-autostart-api": "github:tauri-apps/tauri-plugin-autostart",
24 | "worker-timers": "^7.1.8"
25 | },
26 | "devDependencies": {
27 | "@sveltejs/adapter-static": "^3.0.1",
28 | "@sveltejs/adapter-vercel": "^5.3.0",
29 | "@sveltejs/kit": "^2.5.9",
30 | "@sveltejs/vite-plugin-svelte": "^3.1.0",
31 | "@tauri-apps/cli": "^1.5.14",
32 | "@typescript-eslint/eslint-plugin": "^7.9.0",
33 | "@typescript-eslint/parser": "^7.9.0",
34 | "eslint": "^9.3.0",
35 | "eslint-config-prettier": "^9.1.0",
36 | "eslint-plugin-import": "^2.29.1",
37 | "eslint-plugin-svelte": "^2.39.0",
38 | "prettier": "^3.2.5",
39 | "prettier-plugin-svelte": "^3.2.3",
40 | "sass": "^1.77.2",
41 | "stylelint": "^16.5.0",
42 | "stylelint-config-html": "^1.1.0",
43 | "stylelint-config-idiomatic-order": "^10.0.0",
44 | "stylelint-config-standard-scss": "^13.1.0",
45 | "stylelint-prettier": "^5.0.0",
46 | "svelte": "^4.2.17",
47 | "svelte-check": "^3.7.1",
48 | "tslib": "^2.6.2",
49 | "typescript": "^5.4.5",
50 | "vite": "^5.2.11"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src-tauri/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 |
--------------------------------------------------------------------------------
/src-tauri/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "app"
3 | version = "0.17.6"
4 | description = "A Tauri App"
5 | authors = ["you"]
6 | license = ""
7 | repository = ""
8 | default-run = "app"
9 | edition = "2021"
10 | rust-version = "1.59"
11 |
12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13 |
14 | [build-dependencies]
15 | tauri-build = { version = "1.5.2", features = [] }
16 |
17 | [dependencies]
18 | devtools = "0.3.1"
19 | serde_json = "1.0.117"
20 | serde = { version = "1.0.202", features = ["derive"] }
21 | tauri = { version = "1.6.6", features = [ "system-tray", "os-all", "notification-all", "shell-open", "updater", "window-start-dragging", "icon-png"] }
22 | tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
23 | tauri-plugin-deep-link = "0.1.2"
24 | tauri-plugin-positioner = { version = "1.0.5", features = ["system-tray"] }
25 | tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
26 | time = ">=0.3.35"
27 |
28 | [target.'cfg(target_os = "macos")'.dependencies]
29 | cocoa = "0.25"
30 | objc = "0.2.7"
31 |
32 | [features]
33 | # by default Tauri runs in production mode
34 | # when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
35 | default = ["custom-protocol"]
36 | # this feature is used for production builds where `devPath` points to the filesystem
37 | # DO NOT remove this
38 | custom-protocol = ["tauri/custom-protocol"]
39 |
--------------------------------------------------------------------------------
/src-tauri/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleURLTypes
6 |
7 |
8 | CFBundleURLName
9 | app.gitlight
10 | CFBundleURLSchemes
11 |
12 | gitlight
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src-tauri/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | tauri_build::build()
3 | }
4 |
--------------------------------------------------------------------------------
/src-tauri/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/128x128.png
--------------------------------------------------------------------------------
/src-tauri/icons/128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/128x128@2x.png
--------------------------------------------------------------------------------
/src-tauri/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/32x32.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square107x107Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/Square107x107Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square142x142Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/Square142x142Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square150x150Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/Square150x150Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square284x284Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/Square284x284Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square30x30Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/Square30x30Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square310x310Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/Square310x310Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square44x44Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/Square44x44Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square71x71Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/Square71x71Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square89x89Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/Square89x89Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/StoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/StoreLogo.png
--------------------------------------------------------------------------------
/src-tauri/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/icon.icns
--------------------------------------------------------------------------------
/src-tauri/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/icon.ico
--------------------------------------------------------------------------------
/src-tauri/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/icon.png
--------------------------------------------------------------------------------
/src-tauri/icons/tray-base-macos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/tray-base-macos.png
--------------------------------------------------------------------------------
/src-tauri/icons/tray-base.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/tray-base.png
--------------------------------------------------------------------------------
/src-tauri/icons/tray-new-macos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/tray-new-macos.png
--------------------------------------------------------------------------------
/src-tauri/icons/tray-new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/src-tauri/icons/tray-new.png
--------------------------------------------------------------------------------
/src-tauri/src/commands.rs:
--------------------------------------------------------------------------------
1 | use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu, SystemTrayMenuItem};
2 |
3 | #[tauri::command]
4 | pub fn update_tray(app_handle: tauri::AppHandle, title: Option, new_icon: Option) {
5 | let tray_handle = app_handle.tray_handle_by_id("tray").unwrap();
6 | #[cfg(target_os = "macos")]
7 | if let Some(title) = title {
8 | tray_handle.set_title(&title).unwrap();
9 | }
10 | if let Some(new_icon) = new_icon {
11 | if new_icon {
12 | tray_handle.set_icon(get_raw_tray_icon("new")).unwrap();
13 | } else {
14 | tray_handle.set_icon(get_raw_tray_icon("base")).unwrap();
15 | }
16 | }
17 | }
18 |
19 | #[tauri::command]
20 | pub fn toggle_tray(app_handle: tauri::AppHandle, show: bool) {
21 | let tray_handle = app_handle.tray_handle_by_id("tray");
22 | if let Some(tray_handle) = tray_handle {
23 | tray_handle.destroy().unwrap();
24 | }
25 | if show {
26 | let mut system_tray = SystemTray::new()
27 | .with_id("tray")
28 | .with_icon(get_raw_tray_icon("base"))
29 | .with_menu(
30 | SystemTrayMenu::new()
31 | .add_item(CustomMenuItem::new("dashboard".to_string(), "Dashboard..."))
32 | .add_native_item(SystemTrayMenuItem::Separator)
33 | .add_item(CustomMenuItem::new("quit".to_string(), "Quit")),
34 | );
35 | #[cfg(target_os = "macos")]
36 | {
37 | system_tray = system_tray
38 | .with_icon_as_template(true)
39 | .with_menu_on_left_click(false)
40 | }
41 | system_tray.build(&app_handle).unwrap();
42 | }
43 | }
44 |
45 | fn get_raw_tray_icon(image: &str) -> tauri::Icon {
46 | let is_macos = cfg!(target_os = "macos");
47 | let bytes = match image {
48 | "base" => {
49 | if is_macos {
50 | include_bytes!("../icons/tray-base-macos.png").to_vec()
51 | } else {
52 | include_bytes!("../icons/tray-base.png").to_vec()
53 | }
54 | }
55 | "new" => {
56 | if is_macos {
57 | include_bytes!("../icons/tray-new-macos.png").to_vec()
58 | } else {
59 | include_bytes!("../icons/tray-new.png").to_vec()
60 | }
61 | }
62 | _ => panic!("Unknown tray icon"),
63 | };
64 | tauri::Icon::Raw(bytes)
65 | }
66 |
--------------------------------------------------------------------------------
/src-tauri/src/title_bar.rs:
--------------------------------------------------------------------------------
1 | #[cfg(target_os = "macos")]
2 | use cocoa::appkit::{NSWindow, NSWindowButton};
3 | use tauri::Window;
4 |
5 | #[cfg(target_os = "macos")]
6 | pub fn hide_window_buttons(window: Window) {
7 | unsafe {
8 | let id = window.ns_window().unwrap() as cocoa::base::id;
9 | let close_button = id.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
10 | let min_button = id.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
11 | let zoom_button = id.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
12 | let _: () = msg_send![close_button, setHidden: true];
13 | let _: () = msg_send![min_button, setHidden: true];
14 | let _: () = msg_send![zoom_button, setHidden: true];
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src-tauri/tauri.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../node_modules/@tauri-apps/cli/schema.json",
3 | "build": {
4 | "beforeBuildCommand": "pnpm build",
5 | "beforeDevCommand": "pnpm dev",
6 | "devPath": "http://localhost:5173",
7 | "distDir": "../build"
8 | },
9 | "package": {
10 | "productName": "GitLight",
11 | "version": "0.17.6"
12 | },
13 | "tauri": {
14 | "allowlist": {
15 | "notification": {
16 | "all": true
17 | },
18 | "window": {
19 | "startDragging": true
20 | },
21 | "shell": {
22 | "open": true
23 | },
24 | "os": {
25 | "all": true
26 | }
27 | },
28 | "bundle": {
29 | "active": true,
30 | "category": "DeveloperTool",
31 | "copyright": "",
32 | "deb": {
33 | "depends": []
34 | },
35 | "externalBin": [],
36 | "icon": [
37 | "icons/32x32.png",
38 | "icons/128x128.png",
39 | "icons/128x128@2x.png",
40 | "icons/icon.icns",
41 | "icons/icon.ico"
42 | ],
43 | "identifier": "app.gitlight",
44 | "longDescription": "GitHub & GitLab notifications on your desktop",
45 | "macOS": {
46 | "entitlements": null,
47 | "exceptionDomain": "",
48 | "frameworks": [],
49 | "providerShortName": null,
50 | "signingIdentity": null
51 | },
52 | "resources": [],
53 | "shortDescription": "",
54 | "targets": "all",
55 | "windows": {
56 | "certificateThumbprint": null,
57 | "digestAlgorithm": "sha256",
58 | "timestampUrl": ""
59 | }
60 | },
61 | "security": {
62 | "csp": null
63 | },
64 | "updater": {
65 | "active": true,
66 | "endpoints": ["https://gitlight.app/version/{{target}}/{{current_version}}?arch={{arch}}"],
67 | "dialog": true,
68 | "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDFFN0Y4QzExRTAxMkYyRTEKUldUaDhoTGdFWXgvSGdnNWVFSlQ2Qy9iakRld240cVExdy9xWkdWNmMyQlpDZWZFT0pwSU1xNG0K",
69 | "windows": {
70 | "installMode": "passive"
71 | }
72 | },
73 | "windows": [
74 | {
75 | "label": "main",
76 | "url": "/dashboard",
77 | "width": 1200,
78 | "height": 800,
79 | "title": "GitLight",
80 | "titleBarStyle": "Overlay",
81 | "hiddenTitle": true
82 | },
83 | {
84 | "label": "tray",
85 | "url": "/tray",
86 | "width": 400,
87 | "height": 600,
88 | "title": "GitLight",
89 | "titleBarStyle": "Overlay",
90 | "hiddenTitle": true,
91 | "resizable": false,
92 | "visible": false,
93 | "focus": false
94 | }
95 | ],
96 | "systemTray": {
97 | "iconPath": "icons/tray-base.png",
98 | "iconAsTemplate": true,
99 | "menuOnLeftClick": false
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/app.d.ts:
--------------------------------------------------------------------------------
1 | import type { Session } from './lib/types';
2 |
3 | declare global {
4 | namespace App {
5 | interface Locals {
6 | session?: Session;
7 | }
8 | interface PageData {
9 | session?: Session;
10 | }
11 | }
12 |
13 | interface Window {
14 | __TAURI__: unknown;
15 | }
16 |
17 | const __APP_VERSION__: string;
18 | }
19 |
20 | export {};
21 |
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
37 |
38 |
--------------------------------------------------------------------------------
/src/lib/components/common/AnimatedLogo.svelte:
--------------------------------------------------------------------------------
1 |
15 |
16 |
20 |
21 |
39 |
--------------------------------------------------------------------------------
/src/lib/components/common/Button.svelte:
--------------------------------------------------------------------------------
1 |
23 |
24 | {#if href}
25 |
37 |
38 |
39 |
40 |
41 | {:else}
42 |
52 |
53 |
54 |
55 | {#if loading}
56 |
57 | {/if}
58 |
59 | {/if}
60 |
61 |
152 |
--------------------------------------------------------------------------------
/src/lib/components/common/DragRegion.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/src/lib/components/common/IconButton.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 | dispatch('click', event)}>
12 |
13 | {#if indicator}
14 |
15 | {/if}
16 |
17 |
18 |
58 |
--------------------------------------------------------------------------------
/src/lib/components/common/InlineSelect.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
{label}
19 |
20 | {#each options as option}
21 |
22 | {
26 | value = option;
27 | dispatch('change', event);
28 | }}
29 | type="button"
30 | >
31 | {option}
32 |
33 |
34 | {/each}
35 |
40 |
41 |
42 |
43 |
89 |
--------------------------------------------------------------------------------
/src/lib/components/common/Input.svelte:
--------------------------------------------------------------------------------
1 |
30 |
31 |
32 | {#if label}
33 | {label}
34 | {/if}
35 |
36 | {#if icon}
37 |
38 | {/if}
39 | (focused = true)}
47 | on:blur={() => (focused = false)}
48 | on:input={(e) => dispatch('input', e)}
49 | autocorrect="off"
50 | spellcheck={false}
51 | />
52 |
53 | {#if clearable && value}
54 | (value = '')} type="button">
55 |
56 |
57 | {/if}
58 |
59 |
60 |
61 |
126 |
--------------------------------------------------------------------------------
/src/lib/components/common/Modal.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
58 |
59 | {#if $$slots.trigger}
60 |
61 |
62 |
63 | {/if}
64 |
65 |
66 | {#if open}
67 |
73 |
84 | {/if}
85 |
86 |
87 |
140 |
--------------------------------------------------------------------------------
/src/lib/components/common/ScrollbarContainer.svelte:
--------------------------------------------------------------------------------
1 |
32 |
33 | {#if scroll}
34 |
35 |
36 |
37 | {:else}
38 |
39 | {/if}
40 |
41 |
64 |
--------------------------------------------------------------------------------
/src/lib/components/common/Select.svelte:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 | {#if label}
24 |
{label}
25 | {/if}
26 |
27 |
28 | {#if value}
29 | {displayValue}
30 | {:else}
31 | {placeholder}
32 | {/if}
33 |
34 |
35 |
36 |
37 |
38 |
89 |
--------------------------------------------------------------------------------
/src/lib/components/common/ShrinkableWrapper.svelte:
--------------------------------------------------------------------------------
1 |
23 |
24 |
25 |
31 | {#if !shrinked}
32 |
33 |
34 |
35 | {/if}
36 |
37 |
38 |
75 |
--------------------------------------------------------------------------------
/src/lib/components/common/Switch.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 | dispatch('change', event)}
25 | />
26 |
27 | {#if active}
28 |
29 | {:else}
30 |
31 | {/if}
32 |
33 |
34 | {label}
35 |
36 |
37 |
119 |
--------------------------------------------------------------------------------
/src/lib/components/common/index.ts:
--------------------------------------------------------------------------------
1 | export { default as AnimatedLogo } from './AnimatedLogo.svelte';
2 | export { default as Button } from './Button.svelte';
3 | export { default as DragRegion } from './DragRegion.svelte';
4 | export { default as IconButton } from './IconButton.svelte';
5 | export { default as InlineSelect } from './InlineSelect.svelte';
6 | export { default as Input } from './Input.svelte';
7 | export { default as Modal, modalOpen } from './Modal.svelte';
8 | export { default as ScrollbarContainer } from './ScrollbarContainer.svelte';
9 | export { default as Select } from './Select.svelte';
10 | export { default as ShrinkableWrapper } from './ShrinkableWrapper.svelte';
11 | export { default as Switch } from './Switch.svelte';
12 | export { default as Tooltip, type TooltipContent } from './Tooltip.svelte';
13 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/Banner.svelte:
--------------------------------------------------------------------------------
1 |
76 |
77 | {#if show}
78 |
79 |
80 |
81 |
82 | Download or open the desktop app
83 |
84 |
85 |
86 |
87 |
88 |
89 | {/if}
90 |
91 |
138 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/LoadingScreen.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
Loading your data...
10 |
This can take a bit of time.
11 |
12 |
13 |
14 |
15 |
62 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/SyncPill.svelte:
--------------------------------------------------------------------------------
1 |
33 |
34 | {#if $error}
35 | {#if noInternet}
36 |
37 |
38 | No internet
39 |
40 | {:else}
41 |
42 |
43 |
44 | An error occurred
45 |
46 |
47 | {/if}
48 | {:else}
49 |
50 | {#if synced}
51 |
52 | Synced {syncTime}s ago
53 | {:else}
54 |
55 | Syncing...
56 | {/if}
57 |
58 | {/if}
59 |
60 |
113 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/index.ts:
--------------------------------------------------------------------------------
1 | export * from './notifications';
2 | export * from './priorities';
3 | export * from './sidebar';
4 | export { default as Banner } from './Banner.svelte';
5 | export { default as LoadingScreen } from './LoadingScreen.svelte';
6 | export { default as Main } from './Main.svelte';
7 | export { default as SyncPill } from './SyncPill.svelte';
8 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/notifications/DoneModal.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 | Done ({dones.length})
18 |
19 |
20 |
21 |
22 |
23 | {#if dones.length}
24 |
25 | {#each dones as notification (notification.id)}
26 |
27 |
28 |
29 | {/each}
30 |
31 | {:else}
32 |
33 |
No notifications to display.
34 |
35 | {/if}
36 |
37 |
38 |
39 |
40 |
67 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/notifications/NotificationDescription.svelte:
--------------------------------------------------------------------------------
1 |
73 |
74 |
82 | {#if prefix}
83 | {prefix}
84 | {/if}
85 | {#if author}
86 | {#if author.avatar}
87 |
95 | {/if}
96 | {#if authorUrl}
97 | openUrl(authorUrl)}>
98 | {author.login}
99 |
100 | {:else}
101 | {author.login}
102 | {/if}
103 | {/if}
104 | {#each displayDescription as part}
105 | {#if typeof part === 'string'}
106 | {part}
107 | {:else}
108 | {part.text}
109 | {/if}
110 | {/each}
111 |
112 |
113 |
150 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/notifications/NotificationLabels.svelte:
--------------------------------------------------------------------------------
1 |
31 |
32 | {#if labels && labels.length}
33 |
34 | {#each labels as label}
35 |
36 | {label.name}
37 |
41 |
42 | {/each}
43 | {#if removed}
44 |
45 | +{removed}
46 |
47 |
48 | {/if}
49 |
50 | {/if}
51 |
52 |
85 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/notifications/NotificationList.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 | {#if notifications.length}
17 | {#each displayNotifications as notification (notification)}
18 |
19 |
20 |
21 | {/each}
22 | {:else}
23 |
24 | {/if}
25 |
26 |
27 |
37 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/notifications/NotificationPlaceholder.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
No notifications to display
15 | {#if text}
16 |
{text}
17 | {/if}
18 |
19 |
20 |
71 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/notifications/NotificationStatus.svelte:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
{displayTime}
27 | {#if $settings.viewMode === 'List' || isTrayApp}
28 | {#if status === 'pinned'}
29 |
30 |
31 |
32 | {:else if status === 'unread'}
33 |
34 | {/if}
35 | {/if}
36 |
37 |
38 |
71 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/notifications/index.ts:
--------------------------------------------------------------------------------
1 | export { default as DoneModal } from './DoneModal.svelte';
2 | export { default as Notification } from './Notification.svelte';
3 | export { default as NotificationColumn } from './NotificationColumn.svelte';
4 | export { default as NotificationList } from './NotificationList.svelte';
5 | export { default as NotificationPlaceholder } from './NotificationPlaceholder.svelte';
6 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/priorities/Priorities.svelte:
--------------------------------------------------------------------------------
1 |
49 |
50 | (editing = false)}>
51 |
52 |
53 | Priorities
54 |
55 |
56 |
57 |
58 |
59 |
60 | By enabling priority sorting and specifying priority criterias, you can customize the
61 | importance of notifications and manage them in your own way.
62 |
63 |
64 |
65 | (priorities = defaultPriorities)}>Reset to default
66 | (priorities = [])}>Clear
67 |
68 |
69 |
70 | {#each priorities as priority}
71 |
handleDelete(priority.criteria)}
75 | />
76 | {/each}
77 | {#if editing}
78 | (editing = false)} />
79 | {/if}
80 | {#if !priorities.length && !editing}
81 | No priority criterias yet.
82 | {/if}
83 | (editing = true)}>
84 |
85 | Add a new priority criteria
86 |
87 |
88 |
89 |
90 |
91 |
110 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/priorities/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Priorities } from './Priorities.svelte';
2 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/sidebar/SidebarProviders.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
($settings.providerView = 'github')}
24 | >
25 |
26 | GitHub
27 |
28 |
($settings.providerView = 'gitlab')}
33 | >
34 |
35 | GitLab
36 |
37 |
($settings.providerView = 'both')}
42 | >
43 | Both
44 |
45 |
46 |
47 |
48 |
88 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/sidebar/SidebarSearch.svelte:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/sidebar/SidebarSection.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
{title}
23 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
93 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/sidebar/TypeFilters.svelte:
--------------------------------------------------------------------------------
1 |
55 |
56 | settings.update((previous) => ({ ...previous, showOnlyOpen: value }))
65 | }
66 | ]}
67 | first
68 | >
69 | {#each typeFiltersGitlab as { name, type, number, active } (name)}
70 |
71 |
72 | {name}
73 | {number}
74 |
75 | {/each}
76 |
77 |
78 |
95 |
--------------------------------------------------------------------------------
/src/lib/components/dashboard/sidebar/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Sidebar } from './Sidebar.svelte';
2 |
--------------------------------------------------------------------------------
/src/lib/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './common';
2 | export * from './dashboard';
3 | export * from './landing';
4 | export * from './login';
5 | export * from './settings';
6 |
--------------------------------------------------------------------------------
/src/lib/components/landing/DownloadButton.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 | (open = !open)}
16 | on:mouseleave={() => (open = false)}
17 | role="presentation"
18 | >
19 |
20 | {#if show && open}
21 |
22 |
23 |
24 | Download for Windows
25 |
26 |
27 |
28 | Download for Apple Silicon
29 |
30 |
31 |
32 | Download for Mac Intel
33 |
34 |
35 |
36 | Download for Linux
37 |
38 |
39 | {/if}
40 |
41 |
42 |
76 |
--------------------------------------------------------------------------------
/src/lib/components/landing/index.ts:
--------------------------------------------------------------------------------
1 | export { default as DownloadButton } from './DownloadButton.svelte';
2 |
--------------------------------------------------------------------------------
/src/lib/components/login/GithubLoginButton.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/lib/components/login/index.ts:
--------------------------------------------------------------------------------
1 | export { default as GithubLoginButton } from './GithubLoginButton.svelte';
2 | export { default as GitlabLoginButton } from './GitlabLoginButton.svelte';
3 |
--------------------------------------------------------------------------------
/src/lib/components/settings/App.svelte:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 | {#if updateAvailable}
32 |
GitLight v{updateAvailable} is available!
33 |
Install it now
34 | {:else}
35 |
GitLight v{getAppVersion()}
36 | {#if cannotUpdate}
37 |
No update available
38 | {:else}
39 |
Check for update
40 | {/if}
41 | {/if}
42 |
43 |
44 |
45 | GitHub repository
46 |
47 |
48 |
62 |
--------------------------------------------------------------------------------
/src/lib/components/settings/accounts/Account.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
16 |
17 | {#if user}
18 |
19 |
20 |
21 | Logged in as
22 | {user.name ?? user.login}
23 |
24 |
25 |
26 | {:else}
27 |
Not logged in.
28 | {#if provider === 'github'}
29 |
Log in
30 | {:else if provider === 'gitlab'}
31 |
Log in
32 | {/if}
33 | {/if}
34 |
35 |
36 |
37 |
90 |
--------------------------------------------------------------------------------
/src/lib/components/settings/accounts/Accounts.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
26 |
--------------------------------------------------------------------------------
/src/lib/components/settings/accounts/LogOutButton.svelte:
--------------------------------------------------------------------------------
1 |
40 |
41 |
48 |
49 |
Hold to log out
50 |
51 |
52 |
80 |
--------------------------------------------------------------------------------
/src/lib/components/settings/accounts/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Accounts.svelte';
2 |
--------------------------------------------------------------------------------
/src/lib/components/settings/github-settings/GithubSettings.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 | GitLight settings
19 | dispatchEvent(new CustomEvent('refetch'))}
24 | />
25 |
26 | GitHub notification settings
27 |
28 | In order to receive GitHub notifications in the app, you need to update some settings:
29 |
30 |
31 |
32 | Notification settings
33 |
34 |
35 | Watching and Participating, @mentions and custom must be set to
36 | Notify me: on GitHub .
37 |
38 |
39 | If you want to receive workflow related notifications, set Actions to
40 | Notify me: on GitHub .
41 |
42 |
43 | Organization access
44 |
45 | For private repositories, the GitHub organization that owns these repositories must grant GitLight
46 | access to notifications:
47 |
48 |
53 |
54 | Organization access
55 |
56 |
57 | GitHub PATs
58 |
59 | Fine-grained Personal Access Tokens are required to access data in private repositories. They
60 | enable you to restrict access to the strict minimum (read-only, expiration date, only certain
61 | repositories...). GitLight will only read data in your private repositories . It
62 | will never write or modify anything.
63 |
64 |
65 | Create a fine-grained PAT
66 |
67 |
68 | Create a new fine-grained PAT on GitHub
69 |
70 |
71 | Resource owner : choose the owner of the private repos you want to get
72 | notifications from.
73 |
74 | The owner might have to
78 | approve your token
79 | in order for it to work.
80 |
81 |
82 |
83 | Repository access : choose all repos or select some. You don't need a PAT for
84 | public repositories.
85 |
86 |
87 | Repository permissions : set Contents ,
88 | Discussions , Issues ,
89 | Metadata
90 | and Pull requests to Access: read-only .
91 |
92 | Generate the token.
93 | Finally, add the token below. You can add as many tokens as you want.
94 |
95 | Use PATs in GitLight
96 | {#each $settings.pats as pat}
97 |
98 | {/each}
99 | {#if editing}
100 | (editing = false)} />
101 | {/if}
102 | {#if !$settings.pats.length && !editing}
103 | No PATs yet.
104 | {/if}
105 | (editing = true)}>Use a new PAT
106 |
107 |
142 |
--------------------------------------------------------------------------------
/src/lib/components/settings/github-settings/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './GithubSettings.svelte';
2 |
--------------------------------------------------------------------------------
/src/lib/components/settings/gitlab-settings/GitlabRepos.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
57 |
58 |
59 |
Enter the url of the repositories you want to receive notifications from:
60 | {#each repos as repo, index (index)}
61 |
62 | {/each}
63 |
64 |
65 |
66 | Add more repositories
67 |
68 |
69 |
70 |
71 |
72 | Save
73 |
74 |
75 |
76 |
77 |
101 |
--------------------------------------------------------------------------------
/src/lib/components/settings/gitlab-settings/GitlabSettings.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 | GitLight settings
9 |
13 |
14 | Review applications
15 |
16 | You can review GitLight access
17 | here . More documentation:
19 |
20 |
21 |
22 |
23 | GitLab documentation
24 |
25 |
26 |
27 | Repositories
28 |
29 |
30 |
45 |
--------------------------------------------------------------------------------
/src/lib/components/settings/gitlab-settings/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './GitlabSettings.svelte';
2 | export { default as GitlabRepos } from './GitlabRepos.svelte';
3 |
--------------------------------------------------------------------------------
/src/lib/components/settings/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Settings } from './Settings.svelte';
2 | export { GitlabRepos } from './gitlab-settings';
3 |
--------------------------------------------------------------------------------
/src/lib/features/delayed-hover.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'svelte/action';
2 |
3 | export const delayedHover: Action = (node, className) => {
4 | let timeout: ReturnType;
5 |
6 | function handleMouseEnter() {
7 | timeout = setTimeout(() => {
8 | if (!node.classList.contains(className)) {
9 | node.classList.add(className);
10 | }
11 | }, 200);
12 | }
13 |
14 | function handleMouseLeave() {
15 | node.classList.remove(className);
16 | clearTimeout(timeout);
17 | }
18 |
19 | node.addEventListener('mouseenter', handleMouseEnter);
20 | node.addEventListener('mouseleave', handleMouseLeave);
21 |
22 | return {
23 | destroy() {
24 | node.removeEventListener('mouseenter', handleMouseEnter);
25 | node.removeEventListener('mouseleave', handleMouseLeave);
26 | }
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/src/lib/features/drag-actions.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'svelte/action';
2 | import { settings } from '../stores';
3 |
4 | const lists: Array<{
5 | node: HTMLElement;
6 | hovering: boolean;
7 | onHoverChange: (value: boolean) => void;
8 | }> = [];
9 | let itemId: string;
10 | let dropzone: number;
11 | let dragging = false;
12 |
13 | export const drag: Action void }> = (
14 | node,
15 | { id, onDragStart }
16 | ) => {
17 | let x: number;
18 | let y: number;
19 | let vertical = false;
20 |
21 | function handleMouseDown(e: MouseEvent) {
22 | window.addEventListener('mousemove', handleMouseMove);
23 | window.addEventListener('mouseup', handleMouseUp);
24 |
25 | itemId = id;
26 | x = e.clientX;
27 | y = e.clientY;
28 | settings.subscribe(({ viewMode }) => (vertical = viewMode === 'Kanban (vertical)'));
29 | }
30 |
31 | function handleMouseMove({ clientX, clientY }: MouseEvent) {
32 | if (!dragging && (Math.abs(clientX - x) > 5 || Math.abs(clientY - y) > 5)) {
33 | onDragStart(itemId);
34 | dragging = true;
35 | }
36 |
37 | node.setAttribute(
38 | 'style',
39 | `
40 | transform: translate(${clientX - x}px, ${clientY - y}px);
41 | z-index: 10;
42 | cursor: grabbing;
43 | `
44 | );
45 |
46 | for (const list of lists) {
47 | const listRect = list.node.getBoundingClientRect();
48 | if (
49 | vertical
50 | ? clientY > listRect.top && clientY < listRect.bottom
51 | : clientX > listRect.left && clientX < listRect.right
52 | ) {
53 | const newDropzone = lists.indexOf(list);
54 | if (dropzone !== newDropzone) {
55 | dropzone = newDropzone;
56 | list.hovering = true;
57 | list.onHoverChange(true);
58 | }
59 | } else if (list.hovering) {
60 | list.hovering = false;
61 | list.onHoverChange(false);
62 | }
63 | }
64 | }
65 |
66 | function handleMouseUp({ clientX, clientY }: MouseEvent) {
67 | dragging = false;
68 |
69 | // If the mouse hasn't moved enough, reset the styles
70 | if (Math.abs(clientX - x) <= 5 && Math.abs(clientY - y) <= 5) {
71 | node.setAttribute(
72 | 'style',
73 | `
74 | transform: unset;
75 | z-index: unset;
76 | cursor: grab;
77 | `
78 | );
79 | }
80 |
81 | window.removeEventListener('mousemove', handleMouseMove);
82 | window.removeEventListener('mouseup', handleMouseUp);
83 | }
84 |
85 | node.addEventListener('mousedown', handleMouseDown);
86 |
87 | return {
88 | destroy() {
89 | node.removeEventListener('mousedown', handleMouseDown);
90 | window.removeEventListener('mousemove', handleMouseMove);
91 | window.removeEventListener('mouseup', handleMouseUp);
92 | }
93 | };
94 | };
95 |
96 | export const drop: Action<
97 | HTMLElement,
98 | {
99 | onDrop: (id: string) => void;
100 | onHoverChange: (value: boolean) => void;
101 | }
102 | > = (node, { onDrop, onHoverChange }) => {
103 | const index = lists.length;
104 | lists.push({ node, hovering: false, onHoverChange });
105 |
106 | function handleMouseUp() {
107 | if (dragging && index === dropzone) {
108 | onDrop(itemId);
109 | }
110 | }
111 |
112 | window.addEventListener('mouseup', handleMouseUp);
113 |
114 | return {
115 | destroy() {
116 | window.removeEventListener('mouseup', handleMouseUp);
117 | }
118 | };
119 | };
120 |
--------------------------------------------------------------------------------
/src/lib/features/fetchGithub.ts:
--------------------------------------------------------------------------------
1 | import { page } from '$app/stores';
2 |
3 | type Options = {
4 | noCache?: boolean;
5 | accessToken?: string;
6 | method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
7 | body?: Record;
8 | pat?: string;
9 | };
10 |
11 | export async function fetchGithub(url: string, options?: Options): Promise {
12 | let { accessToken } = options || {};
13 | if (!accessToken) {
14 | page.subscribe(({ data }) => {
15 | accessToken = data.session?.githubAccessToken || '';
16 | });
17 | }
18 |
19 | const response = await fetch(`${url.startsWith('http') ? '' : 'https://api.github.com/'}${url}`, {
20 | headers: {
21 | Accept: 'application/vnd.github+json',
22 | Authorization: options?.pat ? `token ${options.pat}` : `Bearer ${accessToken}`
23 | },
24 | method: options?.method || 'GET',
25 | body: options?.body ? JSON.stringify(options.body) : undefined,
26 | cache: options?.noCache ? 'no-store' : undefined
27 | });
28 |
29 | if (options?.method === 'PATCH') return undefined as T;
30 |
31 | if (response.ok && response.status === 200) {
32 | return await response.json();
33 | }
34 |
35 | if (!response.ok) {
36 | throw new Error(`${response.status}`);
37 | }
38 |
39 | return undefined as T;
40 | }
41 |
--------------------------------------------------------------------------------
/src/lib/features/fetchGitlab.ts:
--------------------------------------------------------------------------------
1 | import { page } from '$app/stores';
2 | import { PUBLIC_SITE_URL } from '$env/static/public';
3 | import { storage } from './storage';
4 |
5 | type Options = {
6 | domain?: string;
7 | noCache?: boolean;
8 | accessToken?: string;
9 | method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
10 | body?: Record;
11 | };
12 |
13 | export async function fetchGitlab(url: string, options?: Options): Promise {
14 | let { accessToken } = options || {};
15 | if (!accessToken) {
16 | if (storage.has('gitlab-pat')) {
17 | accessToken = storage.get('gitlab-pat') as string;
18 | } else if (storage.has('gitlab-access-token')) {
19 | accessToken = storage.get('gitlab-access-token') as string;
20 | } else {
21 | page.subscribe(({ data }) => {
22 | accessToken = data.session?.gitlabAccessToken || '';
23 | });
24 | }
25 | }
26 |
27 | const gitlabOrigin = options?.domain ?? storage.get('gitlab-url') ?? 'https://gitlab.com';
28 |
29 | const newToken = await checkGitlabToken();
30 | if (newToken) accessToken = newToken;
31 |
32 | const response = await fetch(`${url.startsWith('http') ? '' : `${gitlabOrigin}/api/v4/`}${url}`, {
33 | headers: {
34 | Authorization: `Bearer ${accessToken}`
35 | },
36 | method: options?.method || 'GET',
37 | body: options?.body ? JSON.stringify(options.body) : undefined,
38 | cache: options?.noCache ? 'no-store' : undefined
39 | });
40 |
41 | if (options?.method === 'PATCH') return undefined as T;
42 |
43 | if (response.ok) {
44 | return await response.json();
45 | }
46 |
47 | throw new Error(`${response.status}`);
48 | }
49 |
50 | // Refresh the GitLab access token if it has expired
51 | export async function checkGitlabToken(): Promise {
52 | if (storage.has('gitlab-pat')) {
53 | return;
54 | }
55 | const refreshToken = storage.get('gitlab-refresh-token');
56 | const expiration = storage.get('gitlab-expires-in');
57 | if (refreshToken && expiration && new Date().getTime() > parseInt(expiration)) {
58 | try {
59 | const response = await fetch(
60 | `${PUBLIC_SITE_URL}/auth/gitlab/refresh?refresh_token=${refreshToken}`
61 | );
62 | if (response.ok) {
63 | const { access_token, refresh_token, expires_in, created_at } = await response.json();
64 | storage.set('gitlab-access-token', access_token);
65 | storage.set('gitlab-refresh-token', refresh_token);
66 | storage.set('gitlab-expires-in', `${(created_at + expires_in) * 1000}`);
67 | return access_token;
68 | }
69 | throw new Error();
70 | } catch {
71 | throw new Error(
72 | 'An error occurred while refreshing the GitLab access token. Please try again.'
73 | );
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/lib/features/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createGithubNotificationData';
2 | export * from './createGitlabNotificationData';
3 | export * from './delayed-hover';
4 | export * from './drag-actions';
5 | export * from './fetchGithub';
6 | export * from './fetchGitlab';
7 | export * from './getGithubDiscussionData';
8 | export * from './intersection-action';
9 | export * from './storage';
10 |
--------------------------------------------------------------------------------
/src/lib/features/intersection-action.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'svelte/action';
2 |
3 | export const intersect: Action void; active?: boolean }> = (
4 | node,
5 | { callback, active = true }
6 | ) => {
7 | if (!active) return;
8 |
9 | const observer = new IntersectionObserver(
10 | (entries) => {
11 | entries.forEach((entry) => {
12 | if (entry.isIntersecting) {
13 | callback();
14 | }
15 | });
16 | },
17 | { threshold: 1 }
18 | );
19 |
20 | observer.observe(node);
21 |
22 | return {
23 | destroy() {
24 | observer.disconnect();
25 | }
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/src/lib/features/storage.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | Priority,
3 | SavedNotifications,
4 | Settings,
5 | User,
6 | WatchedPerson,
7 | WatchedRepo
8 | } from '../types';
9 |
10 | export type StorageMap = {
11 | 'github-user': User;
12 | 'github-access-token': string;
13 | 'gitlab-user': User;
14 | 'gitlab-access-token': string;
15 | 'gitlab-refresh-token': string;
16 | 'gitlab-expires-in': string;
17 | 'gitlab-url': string;
18 | 'gitlab-pat': string;
19 | settings: Settings;
20 | 'github-notifications': SavedNotifications;
21 | 'gitlab-notifications': SavedNotifications;
22 | 'watched-repos': WatchedRepo[];
23 | 'watched-persons': WatchedPerson[];
24 | 'type-filters': boolean[];
25 | priorities: Priority[];
26 | };
27 |
28 | export const storage = {
29 | get(key: T): StorageMap[T] | null {
30 | if (storage.has(key)) {
31 | const value = localStorage.getItem(key) as string;
32 | try {
33 | return JSON.parse(value);
34 | } catch {
35 | return value as StorageMap[T];
36 | }
37 | }
38 | return null;
39 | },
40 | set(key: T, value: StorageMap[T]) {
41 | localStorage.setItem(key, JSON.stringify(value));
42 | },
43 | has(key: keyof StorageMap): boolean {
44 | return !!localStorage.getItem(key);
45 | },
46 | remove(key: keyof StorageMap) {
47 | localStorage.removeItem(key);
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/src/lib/helpers/debounce.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | export function debounce(callback: (...args: any[]) => void, delay: number): () => void {
3 | let timeout: ReturnType;
4 |
5 | return (...args: any[]) => {
6 | clearTimeout(timeout);
7 | timeout = setTimeout(() => callback(...args), delay);
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/src/lib/helpers/formatRelativeDate.ts:
--------------------------------------------------------------------------------
1 | export function formatRelativeDate(date: string) {
2 | const seconds = Math.floor((new Date().getTime() - new Date(date).getTime()) / 1000);
3 | if (seconds < 60) return `${seconds}s`;
4 |
5 | const minutes = Math.floor(seconds / 60);
6 | if (minutes < 60) return `${minutes}m`;
7 |
8 | const hours = Math.floor(minutes / 60);
9 | if (hours < 24) return `${hours}h`;
10 |
11 | const days = Math.floor(hours / 24);
12 | if (days < 30) return `${days}d`;
13 |
14 | const months = Math.floor(days / 30);
15 | return `${months}mo`;
16 | }
17 |
--------------------------------------------------------------------------------
/src/lib/helpers/getAppVersion.ts:
--------------------------------------------------------------------------------
1 | export function getAppVersion() {
2 | return __APP_VERSION__;
3 | }
4 |
--------------------------------------------------------------------------------
/src/lib/helpers/getIcon.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | GithubIssue,
3 | GithubPullRequest,
4 | GitlabIssue,
5 | GitlabMergeRequest,
6 | NotificationIcon
7 | } from '$lib/types';
8 |
9 | export function getIssueIcon({ state, state_reason }: GithubIssue): NotificationIcon {
10 | switch (state) {
11 | case 'open':
12 | return 'open-issue';
13 | case 'closed':
14 | return state_reason === 'completed' ? 'completed-issue' : 'closed-issue';
15 | default:
16 | throw new Error('Invalid state');
17 | }
18 | }
19 |
20 | export function getPullRequestIcon({ state, merged, draft }: GithubPullRequest): NotificationIcon {
21 | switch (state) {
22 | case 'open':
23 | return draft ? 'draft-pr' : 'open-pr';
24 | case 'closed':
25 | return merged ? 'merged-pr' : 'closed-pr';
26 | default:
27 | throw new Error('Invalid state');
28 | }
29 | }
30 |
31 | export function getGitlabIcon(data: GitlabMergeRequest | GitlabIssue): NotificationIcon {
32 | if ('source_branch' in data) {
33 | switch (data.state) {
34 | case 'opened':
35 | return data.draft ? 'draft-pr' : 'open-pr';
36 | case 'closed':
37 | return 'closed-pr';
38 | case 'merged':
39 | return 'merged-pr';
40 | default:
41 | throw new Error('Invalid state');
42 | }
43 | }
44 |
45 | switch (data.state) {
46 | case 'opened':
47 | return 'open-issue';
48 | case 'closed':
49 | return 'completed-issue';
50 | default:
51 | throw new Error('Invalid state');
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/lib/helpers/getNotificationIcon.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AnsweredDiscussionIcon,
3 | ClosedIssueIcon,
4 | ClosedPullRequestIcon,
5 | CommitIcon,
6 | CompletedIssueIcon,
7 | DiscussionIcon,
8 | DraftPullRequestIcon,
9 | ExclamationMarkIcon,
10 | MergedPullRequestIcon,
11 | OpenIssueIcon,
12 | OpenPullRequestIcon,
13 | ReleaseIcon,
14 | WorkflowFailIcon,
15 | WorkflowSuccessIcon
16 | } from '$lib/icons';
17 | import type { NotificationIcon } from '$lib/types';
18 |
19 | export function getNotificationIcon(icon: NotificationIcon) {
20 | switch (icon) {
21 | case 'closed-issue':
22 | return ClosedIssueIcon;
23 | case 'closed-pr':
24 | return ClosedPullRequestIcon;
25 | case 'commit':
26 | return CommitIcon;
27 | case 'completed-issue':
28 | return CompletedIssueIcon;
29 | case 'discussion':
30 | case 'open-discussion':
31 | return DiscussionIcon;
32 | case 'answered-discussion':
33 | return AnsweredDiscussionIcon;
34 | case 'draft-pr':
35 | return DraftPullRequestIcon;
36 | case 'merged-pr':
37 | return MergedPullRequestIcon;
38 | case 'issue':
39 | case 'open-issue':
40 | return OpenIssueIcon;
41 | case 'pr':
42 | case 'open-pr':
43 | return OpenPullRequestIcon;
44 | case 'release':
45 | return ReleaseIcon;
46 | case 'workflow-fail':
47 | return WorkflowFailIcon;
48 | case 'workflow':
49 | case 'workflow-success':
50 | return WorkflowSuccessIcon;
51 | default:
52 | return ExclamationMarkIcon;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/lib/helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './debounce';
2 | export * from './formatRelativeDate';
3 | export * from './getAppVersion';
4 | export * from './getIcon';
5 | export * from './getNotificationIcon';
6 | export * from './shadeColor';
7 | export * from './openDesktopApp';
8 | export * from './priorities';
9 | export * from './removeMarkdownSymbols';
10 |
--------------------------------------------------------------------------------
/src/lib/helpers/openDesktopApp.ts:
--------------------------------------------------------------------------------
1 | export function openDesktopApp({
2 | githubAccessToken,
3 | gitlabAccessToken,
4 | gitlabRefreshToken,
5 | gitlabExpiresIn,
6 | gitlabUrl,
7 | gitlabPat
8 | }: {
9 | githubAccessToken?: string | null;
10 | gitlabAccessToken?: string | null;
11 | gitlabRefreshToken?: string | null;
12 | gitlabExpiresIn?: string | null;
13 | gitlabUrl?: string | null;
14 | gitlabPat?: string | null;
15 | }) {
16 | const searchParams = new URLSearchParams();
17 | if (githubAccessToken) {
18 | searchParams.set('github_access_token', githubAccessToken);
19 | }
20 | if (gitlabAccessToken && gitlabRefreshToken && gitlabExpiresIn) {
21 | searchParams.set('gitlab_access_token', gitlabAccessToken);
22 | searchParams.set('gitlab_refresh_token', gitlabRefreshToken);
23 | searchParams.set('gitlab_expires_in', gitlabExpiresIn);
24 | }
25 | if (gitlabUrl && gitlabPat) {
26 | searchParams.set('gitlab_url', gitlabUrl);
27 | searchParams.set('gitlab_pat', gitlabPat);
28 | }
29 | window.location.href = `gitlight://${searchParams.toString()}`;
30 | }
31 |
--------------------------------------------------------------------------------
/src/lib/helpers/priorities.ts:
--------------------------------------------------------------------------------
1 | import type { Priority } from '../types';
2 |
3 | export const prioritiesLabel: Record = {
4 | 'many-comments': 'Has many comments',
5 | 'many-reactions': 'Has many reactions',
6 | assigned: 'You are assigned',
7 | mentioned: 'You were mentioned',
8 | 'review-request': 'Review requested',
9 | label: 'Has the label...',
10 | state: 'State is...',
11 | type: 'Type is...'
12 | };
13 |
14 | export const defaultPriorities: Priority[] = [
15 | { criteria: 'mentioned', value: 6 },
16 | { criteria: 'assigned', value: 4 },
17 | { criteria: 'review-request', value: 3 },
18 | { criteria: 'label', value: 2, specifier: 'bug' },
19 | { criteria: 'type', value: -2, specifier: 'commit' },
20 | { criteria: 'state', value: -8, specifier: 'closed' }
21 | ];
22 |
23 | export function cleanSpecifier(string: string) {
24 | const regex = /([A-Z][a-z]+)/g;
25 | const words = string.match(regex);
26 |
27 | if (!words) return string;
28 |
29 | return words.join(' ').toLowerCase();
30 | }
31 |
32 | export function getGrayscale(value: number) {
33 | let grayscale = 1;
34 | if (value > 0) {
35 | grayscale = 1 - value / 5;
36 | } else {
37 | grayscale = 1 + value / 5;
38 | }
39 | return `grayscale(${grayscale})`;
40 | }
41 |
--------------------------------------------------------------------------------
/src/lib/helpers/removeMarkdownSymbols.ts:
--------------------------------------------------------------------------------
1 | const markdownSymbols: Array<[RegExp, string]> = [
2 | [/#+\s/g, ''], // Remove headers (e.g., # Header)
3 | [/(\*{1,2}|_{1,2})(.*?)\1/g, '$2'], // Remove emphasis and bold (e.g., *emphasis* or **bold**)
4 | [/~~(.*?)~~/g, '$1'], // Remove strikethrough (e.g., ~~strikethrough~~)
5 | [/\[(.*?)\]\((.*?)\)/g, '$1'], // Remove links (e.g., [link](url))
6 | [/\n- (.*)/g, '$1'], // Remove unordered list (e.g., - Item)
7 | [/\n\d+\. (.*)/g, '$1'], // Remove ordered list (e.g., 1. Item)
8 | [/^>\s(.*)$/gm, ''], // Remove blockquotes (e.g., > Quote)
9 | [/<[^>]*>/g, ''], // Remove tags (e.g., content )
10 | [/\|.*\|.*\|/g, ''], // Remove tables (e.g., | header | header |)
11 | [/^-{3,}\s*$/gm, ''], // Remove horizontal rule (e.g., ---)
12 | [/^\[vc\]: #[^\r\n]*/g, ''] // Remove vercel comment beginning
13 | ];
14 |
15 | export function removeMarkdownSymbols(markdown: string): string {
16 | markdownSymbols.forEach(([symbol, replacement]) => {
17 | markdown = markdown.replace(symbol, replacement);
18 | });
19 | return markdown.trim();
20 | }
21 |
--------------------------------------------------------------------------------
/src/lib/helpers/shadeColor.ts:
--------------------------------------------------------------------------------
1 | export function shadeColor(hex: string, amount: number) {
2 | const color = hex.replace('#', '');
3 |
4 | const num = parseInt(color, 16);
5 | let r = (num >> 16) & 255;
6 | let g = (num >> 8) & 255;
7 | let b = num & 255;
8 |
9 | r += (amount / 100) * 255;
10 | g += (amount / 100) * 255;
11 | b += (amount / 100) * 255;
12 |
13 | r = Math.min(255, Math.max(0, r));
14 | g = Math.min(255, Math.max(0, g));
15 | b = Math.min(255, Math.max(0, b));
16 |
17 | return `#${(b | (g << 8) | (r << 16)).toString(16).padStart(6, '0')}`;
18 | }
19 |
--------------------------------------------------------------------------------
/src/lib/icons/AnsweredDiscussionIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/src/lib/icons/ArrowRightIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
--------------------------------------------------------------------------------
/src/lib/icons/ArrowUpIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
--------------------------------------------------------------------------------
/src/lib/icons/CheckIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/src/lib/icons/ClosedIssueIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/lib/icons/ClosedPullRequestIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
17 |
18 |
--------------------------------------------------------------------------------
/src/lib/icons/CommitIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/lib/icons/CompletedIssueIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
--------------------------------------------------------------------------------
/src/lib/icons/CrossIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/lib/icons/DiscussionIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/src/lib/icons/DoubleArrowIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/src/lib/icons/DoubleCheckIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/src/lib/icons/DraftPullRequestIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/lib/icons/ExclamationMarkIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/lib/icons/ExternalLinkIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
9 |
15 |
16 |
--------------------------------------------------------------------------------
/src/lib/icons/GearIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/lib/icons/GithubIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/src/lib/icons/GitlabIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/src/lib/icons/HeartIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/src/lib/icons/LightningIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/src/lib/icons/LinuxIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/src/lib/icons/MacosIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/src/lib/icons/MergedPullRequestIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/lib/icons/MuteIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
--------------------------------------------------------------------------------
/src/lib/icons/MutedIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/lib/icons/OpenIssueIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/lib/icons/OpenPullRequestIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
15 |
16 |
--------------------------------------------------------------------------------
/src/lib/icons/PersonIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/lib/icons/PinIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
9 |
15 |
16 |
--------------------------------------------------------------------------------
/src/lib/icons/PlusIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/lib/icons/PriorityDownIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
--------------------------------------------------------------------------------
/src/lib/icons/PriorityIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
--------------------------------------------------------------------------------
/src/lib/icons/PriorityUpIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
--------------------------------------------------------------------------------
/src/lib/icons/RefreshIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
9 |
15 |
16 |
--------------------------------------------------------------------------------
/src/lib/icons/ReleaseIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/lib/icons/RepositoryIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
7 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/lib/icons/RestoreIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
9 |
15 |
16 |
--------------------------------------------------------------------------------
/src/lib/icons/SearchIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
--------------------------------------------------------------------------------
/src/lib/icons/SmallArrowIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/src/lib/icons/SparklesIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
8 |
14 |
15 |
--------------------------------------------------------------------------------
/src/lib/icons/ThreeDotsIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/lib/icons/TrashIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
9 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/lib/icons/UnpinIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/lib/icons/UnreadIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
--------------------------------------------------------------------------------
/src/lib/icons/WindowsIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/src/lib/icons/WorkflowFailIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/lib/icons/WorkflowSuccessIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/src/lib/icons/XIcon.svelte:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/src/lib/icons/index.ts:
--------------------------------------------------------------------------------
1 | export { default as AnsweredDiscussionIcon } from './AnsweredDiscussionIcon.svelte';
2 | export { default as ArrowRightIcon } from './ArrowRightIcon.svelte';
3 | export { default as ArrowUpIcon } from './ArrowUpIcon.svelte';
4 | export { default as CheckIcon } from './CheckIcon.svelte';
5 | export { default as ClosedIssueIcon } from './ClosedIssueIcon.svelte';
6 | export { default as ClosedPullRequestIcon } from './ClosedPullRequestIcon.svelte';
7 | export { default as CommitIcon } from './CommitIcon.svelte';
8 | export { default as CompletedIssueIcon } from './CompletedIssueIcon.svelte';
9 | export { default as CrossIcon } from './CrossIcon.svelte';
10 | export { default as DiscussionIcon } from './DiscussionIcon.svelte';
11 | export { default as DoubleArrowIcon } from './DoubleArrowIcon.svelte';
12 | export { default as DoubleCheckIcon } from './DoubleCheckIcon.svelte';
13 | export { default as DraftPullRequestIcon } from './DraftPullRequestIcon.svelte';
14 | export { default as ExclamationMarkIcon } from './ExclamationMarkIcon.svelte';
15 | export { default as ExternalLinkIcon } from './ExternalLinkIcon.svelte';
16 | export { default as GearIcon } from './GearIcon.svelte';
17 | export { default as GithubIcon } from './GithubIcon.svelte';
18 | export { default as GitlabIcon } from './GitlabIcon.svelte';
19 | export { default as HeartIcon } from './HeartIcon.svelte';
20 | export { default as LightningIcon } from './LightningIcon.svelte';
21 | export { default as LinuxIcon } from './LinuxIcon.svelte';
22 | export { default as MacosIcon } from './MacosIcon.svelte';
23 | export { default as MergedPullRequestIcon } from './MergedPullRequestIcon.svelte';
24 | export { default as OpenIssueIcon } from './OpenIssueIcon.svelte';
25 | export { default as OpenPullRequestIcon } from './OpenPullRequestIcon.svelte';
26 | export { default as PersonIcon } from './PersonIcon.svelte';
27 | export { default as PinIcon } from './PinIcon.svelte';
28 | export { default as PlusIcon } from './PlusIcon.svelte';
29 | export { default as PriorityDownIcon } from './PriorityDownIcon.svelte';
30 | export { default as PriorityIcon } from './PriorityIcon.svelte';
31 | export { default as PriorityUpIcon } from './PriorityUpIcon.svelte';
32 | export { default as RefreshIcon } from './RefreshIcon.svelte';
33 | export { default as ReleaseIcon } from './ReleaseIcon.svelte';
34 | export { default as RepositoryIcon } from './RepositoryIcon.svelte';
35 | export { default as RestoreIcon } from './RestoreIcon.svelte';
36 | export { default as SearchIcon } from './SearchIcon.svelte';
37 | export { default as SmallArrowIcon } from './SmallArrowIcon.svelte';
38 | export { default as SparklesIcon } from './SparklesIcon.svelte';
39 | export { default as ThreeDotsIcon } from './ThreeDotsIcon.svelte';
40 | export { default as TrashIcon } from './TrashIcon.svelte';
41 | export { default as MutedIcon } from './MutedIcon.svelte';
42 | export { default as MuteIcon } from './MuteIcon.svelte';
43 | export { default as UnpinIcon } from './UnpinIcon.svelte';
44 | export { default as UnreadIcon } from './UnreadIcon.svelte';
45 | export { default as WindowsIcon } from './WindowsIcon.svelte';
46 | export { default as WorkflowFailIcon } from './WorkflowFailIcon.svelte';
47 | export { default as WorkflowSuccessIcon } from './WorkflowSuccessIcon.svelte';
48 | export { default as XIcon } from './XIcon.svelte';
49 |
--------------------------------------------------------------------------------
/src/lib/stores/index.ts:
--------------------------------------------------------------------------------
1 | export * from './stores';
2 |
--------------------------------------------------------------------------------
/src/lib/stores/stores.ts:
--------------------------------------------------------------------------------
1 | import { writable } from 'svelte/store';
2 | import type {
3 | NotificationData,
4 | Settings,
5 | TypeFilters,
6 | WatchedPerson,
7 | WatchedRepo
8 | } from '$lib/types';
9 |
10 | export const filteredNotifications = writable([]);
11 |
12 | export const githubNotifications = writable([]);
13 |
14 | export const gitlabNotifications = writable([]);
15 |
16 | export const globalNotifications = writable([]);
17 |
18 | export const typeFilters = writable([
19 | { name: 'Pull requests', type: 'pr', active: true, number: 0 },
20 | { name: 'Issues', type: 'issue', active: true, number: 0 },
21 | { name: 'Commits', type: 'commit', active: true, number: 0 },
22 | { name: 'Workflows', type: 'workflow', active: true, number: 0 },
23 | { name: 'Discussions', type: 'discussion', active: true, number: 0 },
24 | { name: 'Releases', type: 'release', active: true, number: 0 }
25 | ]);
26 |
27 | export const watchedRepos = writable([]);
28 |
29 | export const watchedPersons = writable([]);
30 |
31 | export const loading = writable(true);
32 |
33 | export const settings = writable({
34 | theme: 'System',
35 | activateNotifications: true,
36 | readWhenOpenInBrowser: true,
37 | notificationNumber: 50,
38 | sidebarHidden: false,
39 | showOnlyOpen: false,
40 | pats: [],
41 | prioritySorting: true,
42 | showPriority: true,
43 | providerView: 'both',
44 | applyFiltersForDone: false,
45 | viewMode: 'Kanban',
46 | activeTray: true,
47 | gitlabRepos: [],
48 | gitlabOnlyInvolved: true,
49 | markClosedAsDone: false
50 | });
51 |
52 | export const theme = writable<'light' | 'dark'>('light');
53 |
54 | export const error = writable(null);
55 |
--------------------------------------------------------------------------------
/src/lib/types/common-types.ts:
--------------------------------------------------------------------------------
1 | import type { GithubLabel, GithubRepository } from './github-types';
2 | import type { GitlabEvent } from './gitlab-types';
3 |
4 | export type User = {
5 | name?: string;
6 | login: string;
7 | avatar?: string;
8 | bot?: boolean;
9 | };
10 |
11 | export type Session = {
12 | githubUser?: User;
13 | githubAccessToken?: string;
14 | gitlabUser?: User;
15 | gitlabAccessToken?: string;
16 | };
17 |
18 | export type NotificationIcon =
19 | | 'commit'
20 | | 'issue'
21 | | 'open-issue'
22 | | 'completed-issue'
23 | | 'closed-issue'
24 | | 'pr'
25 | | 'draft-pr'
26 | | 'open-pr'
27 | | 'merged-pr'
28 | | 'closed-pr'
29 | | 'release'
30 | | 'discussion'
31 | | 'open-discussion'
32 | | 'answered-discussion'
33 | | 'workflow'
34 | | 'workflow-fail'
35 | | 'workflow-success'
36 | | 'unsupported';
37 |
38 | export type NotificationType = 'pr' | 'issue' | 'commit' | 'release' | 'discussion' | 'workflow';
39 |
40 | export type NotificationData = {
41 | id: string;
42 | from: 'github' | 'gitlab';
43 | type: NotificationType;
44 | status: 'unread' | 'read' | 'pinned' | 'done';
45 | muted: boolean;
46 | author?: User;
47 | creator?: User;
48 | title: string;
49 | description: string;
50 | priority?: {
51 | label: string;
52 | value: number;
53 | };
54 | time: string;
55 | icon: NotificationIcon;
56 | opened?: boolean;
57 | repository: {
58 | id: number;
59 | url: string;
60 | domain: string;
61 | namespace: string;
62 | };
63 | ownerAvatar?: string;
64 | number?: number;
65 | labels?: GithubLabel[];
66 | url?: string;
67 | ref?: string;
68 | notInvolved?: true;
69 | previously?: {
70 | author?: User;
71 | description: NotificationData['description'];
72 | };
73 | };
74 |
75 | export type TypeFilters = Array<{
76 | name: string;
77 | type: NotificationType;
78 | active: boolean;
79 | number: number;
80 | }>;
81 |
82 | export type Subscription = {
83 | repo: GithubRepository;
84 | active: boolean;
85 | };
86 |
87 | export type SavedNotifications = Array<{
88 | id: string;
89 | author?: User;
90 | description: string;
91 | status: 'unread' | 'read' | 'pinned' | 'done';
92 | muted: boolean;
93 | time: string;
94 | previously?: NotificationData['previously'];
95 | }>;
96 |
97 | export type WatchedRepo = {
98 | id: number;
99 | name: string;
100 | ownerName: string;
101 | ownerAvatar?: string;
102 | number: number;
103 | active: boolean;
104 | muted: boolean;
105 | from: 'github' | 'gitlab';
106 | };
107 |
108 | export type WatchedPerson = {
109 | login: string;
110 | avatar?: string;
111 | number: number;
112 | active: boolean;
113 | muted: boolean;
114 | bot?: boolean;
115 | from: 'github' | 'gitlab';
116 | };
117 |
118 | export type GitlabEventWithRepoData = GitlabEvent & {
119 | repository: {
120 | id: number;
121 | url: string;
122 | domain: string;
123 | namespace: string;
124 | encoded: string;
125 | };
126 | };
127 |
128 | export type Settings = {
129 | theme: 'System' | 'Light' | 'Dark';
130 | activateNotifications: boolean;
131 | readWhenOpenInBrowser: boolean;
132 | notificationNumber: number;
133 | sidebarHidden: boolean;
134 | showOnlyOpen: boolean;
135 | pats: Array<{
136 | owner: string;
137 | token: string;
138 | }>;
139 | prioritySorting: boolean;
140 | showPriority: boolean;
141 | providerView: 'github' | 'gitlab' | 'both';
142 | applyFiltersForDone: boolean;
143 | viewMode: 'List' | 'Kanban' | 'Kanban (vertical)';
144 | activeTray: boolean;
145 | gitlabRepos: Array<{
146 | id: number;
147 | url: string;
148 | }>;
149 | gitlabOnlyInvolved: boolean;
150 | markClosedAsDone: boolean;
151 | };
152 |
153 | export type Priority = {
154 | value: number;
155 | } & (
156 | | {
157 | criteria: 'many-comments' | 'many-reactions' | 'assigned' | 'mentioned' | 'review-request';
158 | }
159 | | {
160 | criteria: 'label';
161 | specifier: string;
162 | }
163 | | {
164 | criteria: 'state';
165 | specifier: 'open' | 'closed';
166 | }
167 | | {
168 | criteria: 'type';
169 | specifier: NotificationType;
170 | }
171 | );
172 |
--------------------------------------------------------------------------------
/src/lib/types/github-types.ts:
--------------------------------------------------------------------------------
1 | export type GithubLabel = {
2 | color: string;
3 | name: string;
4 | };
5 |
6 | export type GithubUser = {
7 | avatar_url: string;
8 | display_login: string;
9 | login: string;
10 | name: string;
11 | url: string;
12 | type: 'User' | 'Bot';
13 | };
14 |
15 | export type GithubRepository = {
16 | id: number;
17 | full_name: string;
18 | owner: GithubUser;
19 | private: boolean;
20 | html_url: string;
21 | };
22 |
23 | export type GithubIssue = {
24 | number: number;
25 | title: string;
26 | url: string;
27 | assignees: GithubUser[];
28 | state: 'open' | 'closed';
29 | state_reason: 'completed' | 'not_planned' | 'reopened' | null;
30 | created_at: string;
31 | closed_at: string | null;
32 | closed_by?: GithubUser;
33 | user: GithubUser;
34 | labels: GithubLabel[];
35 | html_url: string;
36 | comments: number;
37 | comments_url: string;
38 | reactions: {
39 | total_count: number;
40 | };
41 | };
42 |
43 | export type GithubPullRequest = GithubIssue & {
44 | merged: boolean;
45 | merged_at: string | null;
46 | merged_by?: GithubUser;
47 | draft: boolean;
48 | review_comments: number;
49 | review_comments_url: string;
50 | commits: number;
51 | commits_url: string;
52 | };
53 |
54 | export type GithubCommit = {
55 | sha: string;
56 | url: string;
57 | author?: GithubUser;
58 | html_url: string;
59 | commit: {
60 | message: string;
61 | author: {
62 | name: string;
63 | email: string;
64 | date: string;
65 | };
66 | };
67 | };
68 |
69 | export type GithubComment = {
70 | body: string;
71 | html_url: string;
72 | user: GithubUser;
73 | created_at: string;
74 | };
75 |
76 | export type GithubReview = {
77 | body?: string;
78 | html_url: string;
79 | user: GithubUser;
80 | state: 'APPROVED' | 'CHANGES_REQUESTED' | 'COMMENTED' | 'DISMISSED';
81 | submitted_at: string;
82 | };
83 |
84 | export type GithubRelease = {
85 | author: GithubUser;
86 | tag_name: string;
87 | name: string;
88 | body: string;
89 | draft: boolean;
90 | prerelease: boolean;
91 | html_url: string;
92 | assets: Array<{
93 | name: string;
94 | browser_download_url: string;
95 | }>;
96 | published_at: string;
97 | };
98 |
99 | export type GithubItem = GithubIssue | GithubRepository | GithubCommit | GithubRelease;
100 |
101 | export type GithubNotificationType =
102 | | 'PullRequest'
103 | | 'Issue'
104 | | 'Commit'
105 | | 'Release'
106 | | 'Discussion'
107 | | 'CheckSuite';
108 |
109 | export type GithubNotificationReason =
110 | | 'assign'
111 | | 'author'
112 | | 'comment'
113 | | 'ci_activity'
114 | | 'invitation'
115 | | 'manual'
116 | | 'mention'
117 | | 'review_requested'
118 | | 'security_alert'
119 | | 'state_change'
120 | | 'subscribed'
121 | | 'team_mention';
122 |
123 | export type GithubNotification = {
124 | id: string;
125 | last_read_at: string;
126 | reason: GithubNotificationReason;
127 | repository: GithubRepository;
128 | subject: {
129 | latest_comment_url: string | null;
130 | title: string;
131 | type: GithubNotificationType;
132 | url: string | null;
133 | };
134 | unread: boolean;
135 | updated_at: string;
136 | };
137 |
--------------------------------------------------------------------------------
/src/lib/types/gitlab-types.ts:
--------------------------------------------------------------------------------
1 | export type GitlabUser = {
2 | id: number;
3 | name: string;
4 | username: string;
5 | avatar_url: string;
6 | };
7 |
8 | export type GitlabRepository = {
9 | id: number;
10 | };
11 |
12 | export type GitlabBaseItem = {
13 | id: number;
14 | iid: number;
15 | title: string;
16 | description: string;
17 | created_at: string;
18 | updated_at: string;
19 | web_url: string;
20 | author: GitlabUser;
21 | labels: string[];
22 | user_notes_count: number;
23 | };
24 |
25 | export type GitlabIssue = GitlabBaseItem & {
26 | state: 'opened' | 'closed';
27 | };
28 |
29 | export type GitlabMergeRequest = GitlabBaseItem & {
30 | state: 'opened' | 'closed' | 'merged';
31 | source_branch: string;
32 | draft: boolean;
33 | reviewers: GitlabUser[];
34 | assignees: GitlabUser[];
35 | upvotes: number;
36 | downvotes: number;
37 | };
38 |
39 | export type GitlabEvent = {
40 | id: number;
41 | author: GitlabUser;
42 | created_at: string;
43 | project_id: number;
44 | } & (
45 | | {
46 | action_name: 'pushed new' | 'pushed to';
47 | target_id: null;
48 | target_iid: null;
49 | target_type: null;
50 | push_data: {
51 | action: 'created' | 'pushed';
52 | commit_count: number;
53 | commit_title: string | null;
54 | ref: string;
55 | ref_type: 'branch' | 'tag';
56 | };
57 | }
58 | | {
59 | action_name: 'created';
60 | target_id: number;
61 | target_iid: number;
62 | target_type: null;
63 | }
64 | | {
65 | action_name: 'opened';
66 | target_id: number;
67 | target_iid: number;
68 | target_type: 'MergeRequest' | 'Issue' | 'Milestone';
69 | target_title: string;
70 | }
71 | | {
72 | action_name: 'closed';
73 | target_id: number;
74 | target_iid: number;
75 | target_type: 'MergeRequest' | 'Issue';
76 | target_title: string;
77 | }
78 | | {
79 | action_name: 'accepted';
80 | target_id: number;
81 | target_iid: number;
82 | target_type: null;
83 | }
84 | | {
85 | action_name: 'deleted';
86 | target_id: number;
87 | target_iid: number;
88 | target_type: null;
89 | }
90 | | {
91 | action_name: 'joined';
92 | target_id: number;
93 | target_iid: number;
94 | target_type: null;
95 | }
96 | | {
97 | action_name: 'updated';
98 | target_id: number;
99 | target_iid: number;
100 | target_type: null;
101 | }
102 | | {
103 | action_name: 'approved';
104 | target_id: number;
105 | target_iid: number;
106 | target_type: 'MergeRequest';
107 | }
108 | | {
109 | action_name: 'commented on';
110 | target_id: number;
111 | target_iid: number;
112 | target_type: 'Note' | 'DiffNote' | 'DiscussionNote';
113 | note: {
114 | body: string;
115 | noteable_id: number;
116 | noteable_iid: number;
117 | noteable_type: 'MergeRequest' | 'Issue';
118 | };
119 | }
120 | );
121 |
122 | export type GitlabLabel = {
123 | name: string;
124 | color: string;
125 | };
126 |
--------------------------------------------------------------------------------
/src/lib/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './common-types';
2 | export * from './github-types';
3 | export * from './gitlab-types';
4 | export * from './type-helpers';
5 |
--------------------------------------------------------------------------------
/src/lib/types/type-helpers.ts:
--------------------------------------------------------------------------------
1 | export type ObjectEntries = Array<[keyof T, T[keyof T]]>;
2 |
--------------------------------------------------------------------------------
/src/routes/(app)/+layout.svelte:
--------------------------------------------------------------------------------
1 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/src/routes/(app)/deeplink/+page.svelte:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 | GitLight
34 |
35 |
36 |
43 |
44 |
71 |
--------------------------------------------------------------------------------
/src/routes/(app)/login/+page.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 | GitLight • Log in
11 |
12 |
13 |
14 |
15 |
16 | {#if !onTauriApp}
17 |
18 |
19 |
20 |
21 |
22 | {/if}
23 | Log in to start monitoring your notifications
24 | You will be able to log in to the other provider afterward.
25 |
26 |
27 |
28 | Log in to GitHub
29 |
30 |
31 |
32 | Log in to GitLab
33 |
34 |
35 |
36 |
37 |
74 |
--------------------------------------------------------------------------------
/src/routes/(tray)/tray/+page.svelte:
--------------------------------------------------------------------------------
1 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
76 |
--------------------------------------------------------------------------------
/src/routes/+layout.ts:
--------------------------------------------------------------------------------
1 | import { redirect } from '@sveltejs/kit';
2 | import { browser } from '$app/environment';
3 | import { goto } from '$app/navigation';
4 | import { checkGitlabToken, fetchGithub, fetchGitlab, storage } from '$lib/features';
5 | import type { GithubUser, GitlabUser } from '$lib/types';
6 |
7 | import '~/styles/_reset.scss';
8 | import '~/styles/_base.scss';
9 | import '~/styles/_fonts.scss';
10 |
11 | export const prerender = true;
12 | export const ssr = true;
13 |
14 | export async function load({ url }) {
15 | if (!browser || url.pathname === '/tray') return;
16 |
17 | let githubUser = storage.get('github-user');
18 | let githubAccessToken = storage.get('github-access-token');
19 | let gitlabUser = storage.get('gitlab-user');
20 | let gitlabAccessToken = storage.get('gitlab-access-token');
21 | let gitlabRefreshToken = storage.get('gitlab-refresh-token');
22 | let gitlabExpiresIn = storage.get('gitlab-expires-in');
23 | const gitlabPat = storage.get('gitlab-pat');
24 | const gitlabUrl = storage.get('gitlab-url');
25 |
26 | const githubTokenParam = url.searchParams.get('github_access_token');
27 | const gitlabTokenParam = url.searchParams.get('gitlab_access_token');
28 | const gitlabRefreshTokenParam = url.searchParams.get('gitlab_refresh_token');
29 | const gitlabExpiresInParam = url.searchParams.get('gitlab_expires_in');
30 |
31 | // Get GitHub access token
32 | if (githubTokenParam) {
33 | githubAccessToken = githubTokenParam;
34 | storage.set('github-access-token', githubAccessToken);
35 | }
36 |
37 | // Get GitLab access token
38 | if (gitlabTokenParam) {
39 | gitlabAccessToken = gitlabTokenParam;
40 | storage.set('gitlab-access-token', gitlabAccessToken);
41 | }
42 |
43 | // Get GitLab refresh token
44 | if (gitlabRefreshTokenParam) {
45 | gitlabRefreshToken = gitlabRefreshTokenParam;
46 | storage.set('gitlab-refresh-token', gitlabRefreshToken);
47 | }
48 |
49 | // Get GitLab expiration
50 | if (gitlabExpiresInParam) {
51 | gitlabExpiresIn = gitlabExpiresInParam;
52 | storage.set('gitlab-expires-in', gitlabExpiresIn);
53 | }
54 |
55 | // If the GitLab access token has expired, refresh it
56 | if (gitlabAccessToken && gitlabRefreshToken && gitlabExpiresIn) {
57 | try {
58 | await checkGitlabToken();
59 | } catch {
60 | // If this fail, logout
61 | storage.remove(`gitlab-user`);
62 | storage.remove(`gitlab-access-token`);
63 | storage.remove(`gitlab-refresh-token`);
64 | storage.remove(`gitlab-expires-in`);
65 |
66 | if (storage.has('github-user')) {
67 | window.location.reload();
68 | } else {
69 | goto('/login');
70 | }
71 | }
72 | }
73 |
74 | // Remove access tokens from the URL
75 | if (githubTokenParam || gitlabTokenParam) {
76 | history.replaceState({}, '', '/dashboard');
77 | }
78 |
79 | // Set GitHub user data
80 | if (!githubUser && githubAccessToken) {
81 | try {
82 | const { name, login, avatar_url } = await fetchGithub('user', {
83 | accessToken: githubAccessToken
84 | });
85 | githubUser = { name, login, avatar: avatar_url };
86 | storage.set('github-user', githubUser);
87 | } catch {
88 | //
89 | }
90 | }
91 |
92 | // Set GitLab user data
93 | if (!gitlabUser && (gitlabAccessToken || gitlabPat)) {
94 | try {
95 | const { name, username, avatar_url } = await fetchGitlab('user', {
96 | accessToken: gitlabAccessToken ?? (gitlabPat as string),
97 | domain: gitlabUrl ?? undefined
98 | });
99 | gitlabUser = { name, login: username, avatar: avatar_url };
100 | storage.set('gitlab-user', gitlabUser);
101 | } catch {
102 | //
103 | }
104 | }
105 |
106 | const session =
107 | (githubUser && githubAccessToken) ||
108 | (gitlabUser && (gitlabAccessToken || (gitlabPat && gitlabUrl)))
109 | ? { githubUser, githubAccessToken, gitlabUser, gitlabAccessToken }
110 | : null;
111 |
112 | // Handle redirects
113 | const protectedRoute = ['/dashboard', '/deeplink'].includes(url.pathname);
114 | if (protectedRoute && !session) {
115 | redirect(302, '/login');
116 | } else if (!protectedRoute && session) {
117 | redirect(302, '/dashboard');
118 | }
119 |
120 | return { session };
121 | }
122 |
--------------------------------------------------------------------------------
/src/routes/auth/github/callback/+server.ts:
--------------------------------------------------------------------------------
1 | import { redirect } from '@sveltejs/kit';
2 | import { AUTH_SECRET, AUTH_GITHUB_ID, AUTH_GITHUB_SECRET } from '$env/static/private';
3 |
4 | export async function GET({ url }) {
5 | const { searchParams, origin } = url;
6 |
7 | if (
8 | searchParams.has('code') &&
9 | searchParams.has('state') &&
10 | searchParams.get('state') === AUTH_SECRET
11 | ) {
12 | const code = searchParams.get('code');
13 | const response = await fetch('https://github.com/login/oauth/access_token', {
14 | method: 'POST',
15 | headers: {
16 | Accept: 'application/json',
17 | 'Content-Type': 'application/json'
18 | },
19 | body: JSON.stringify({
20 | client_id: AUTH_GITHUB_ID,
21 | client_secret: AUTH_GITHUB_SECRET,
22 | redirect_uri: `${origin}/auth/github/callback`,
23 | code
24 | })
25 | });
26 |
27 | if (response.ok) {
28 | const { access_token } = await response.json();
29 | const params = new URLSearchParams();
30 | params.append('github_access_token', access_token);
31 | if (searchParams.has('from_app')) {
32 | redirect(302, `/deeplink?${params.toString()}`);
33 | }
34 | redirect(302, `/dashboard?${params.toString()}`);
35 | }
36 |
37 | redirect(302, '/');
38 | }
39 |
40 | redirect(302, '/');
41 | }
42 |
--------------------------------------------------------------------------------
/src/routes/auth/github/login/+server.ts:
--------------------------------------------------------------------------------
1 | import { redirect } from '@sveltejs/kit';
2 | import { AUTH_SECRET, AUTH_GITHUB_ID } from '$env/static/private';
3 |
4 | export async function GET({ url }) {
5 | const githubLoginUrl = new URL('https://github.com/login/oauth/authorize');
6 | githubLoginUrl.searchParams.set('scope', 'notifications');
7 | githubLoginUrl.searchParams.set('client_id', AUTH_GITHUB_ID);
8 | githubLoginUrl.searchParams.set(
9 | 'redirect_uri',
10 | `${url.origin}/auth/github/callback${url.search}`
11 | );
12 | githubLoginUrl.searchParams.set('state', AUTH_SECRET);
13 | redirect(302, githubLoginUrl.toString());
14 | }
15 |
--------------------------------------------------------------------------------
/src/routes/auth/gitlab/callback/+server.ts:
--------------------------------------------------------------------------------
1 | import { redirect } from '@sveltejs/kit';
2 | import { AUTH_SECRET, AUTH_GITLAB_ID, AUTH_GITLAB_SECRET } from '$env/static/private';
3 |
4 | export async function GET({ url }) {
5 | const { searchParams, origin } = url;
6 |
7 | if (
8 | searchParams.has('code') &&
9 | searchParams.has('state') &&
10 | searchParams.get('state') === AUTH_SECRET
11 | ) {
12 | const code = searchParams.get('code');
13 | const response = await fetch('https://gitlab.com/oauth/token', {
14 | method: 'POST',
15 | headers: {
16 | Accept: 'application/json',
17 | 'Content-Type': 'application/json'
18 | },
19 | body: JSON.stringify({
20 | client_id: AUTH_GITLAB_ID,
21 | client_secret: AUTH_GITLAB_SECRET,
22 | redirect_uri: `${origin}/auth/gitlab/callback${
23 | searchParams.has('from_app') ? '?from_app=true' : ''
24 | }`,
25 | grant_type: 'authorization_code',
26 | code
27 | })
28 | });
29 |
30 | if (response.ok) {
31 | const { access_token, refresh_token, expires_in, created_at } = await response.json();
32 | const params = new URLSearchParams();
33 | params.append('gitlab_access_token', access_token);
34 | params.append('gitlab_refresh_token', refresh_token);
35 | params.append('gitlab_expires_in', `${(created_at + expires_in) * 1000}`);
36 | if (searchParams.has('from_app')) {
37 | redirect(302, `/deeplink?${params.toString()}`);
38 | }
39 | redirect(302, `/dashboard?${params.toString()}`);
40 | }
41 |
42 | redirect(302, '/');
43 | }
44 |
45 | redirect(302, '/');
46 | }
47 |
--------------------------------------------------------------------------------
/src/routes/auth/gitlab/login/+server.ts:
--------------------------------------------------------------------------------
1 | import { redirect } from '@sveltejs/kit';
2 | import { AUTH_SECRET, AUTH_GITLAB_ID } from '$env/static/private';
3 |
4 | export async function GET({ url }) {
5 | const gitlabLoginUrl = new URL('https://gitlab.com/oauth/authorize');
6 | gitlabLoginUrl.searchParams.set('scope', 'read_api read_user');
7 | gitlabLoginUrl.searchParams.set('client_id', AUTH_GITLAB_ID);
8 | gitlabLoginUrl.searchParams.set(
9 | 'redirect_uri',
10 | `${url.origin}/auth/gitlab/callback${url.search}`
11 | );
12 | gitlabLoginUrl.searchParams.set('state', AUTH_SECRET);
13 | gitlabLoginUrl.searchParams.set('response_type', 'code');
14 | redirect(302, gitlabLoginUrl.toString());
15 | }
16 |
--------------------------------------------------------------------------------
/src/routes/auth/gitlab/refresh/+server.ts:
--------------------------------------------------------------------------------
1 | import { AUTH_GITLAB_ID, AUTH_GITLAB_SECRET } from '$env/static/private';
2 | import { PUBLIC_SITE_URL } from '$env/static/public';
3 |
4 | const headers = {
5 | 'Access-Control-Allow-Origin': '*'
6 | };
7 |
8 | export async function GET({ url }) {
9 | const { searchParams } = url;
10 |
11 | if (!searchParams.has('refresh_token')) {
12 | return new Response('refresh_token not found', { status: 400, headers });
13 | }
14 |
15 | try {
16 | const response = await fetch('https://gitlab.com/oauth/token', {
17 | method: 'POST',
18 | headers: {
19 | Accept: 'application/json',
20 | 'Content-Type': 'application/json'
21 | },
22 | body: JSON.stringify({
23 | client_id: AUTH_GITLAB_ID,
24 | client_secret: AUTH_GITLAB_SECRET,
25 | refresh_token: searchParams.get('refresh_token'),
26 | redirect_uri: `${PUBLIC_SITE_URL}/auth/gitlab/callback`,
27 | grant_type: 'refresh_token'
28 | })
29 | });
30 |
31 | if (response.ok) {
32 | const data = await response.json();
33 | return new Response(JSON.stringify(data), { status: 200, headers });
34 | }
35 |
36 | return new Response(response.statusText, { status: response.status, headers });
37 | } catch (error) {
38 | return new Response(JSON.stringify({ error }), { status: 500, headers });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/routes/download/[os]/+server.ts:
--------------------------------------------------------------------------------
1 | import { redirect } from '@sveltejs/kit';
2 | import type { GithubRelease } from '$lib/types';
3 |
4 | export async function GET({ params }) {
5 | async function getDownloadUrl(os: 'aarch64.dmg' | 'x64.dmg' | '.msi' | '.AppImage') {
6 | const response = await fetch('https://api.github.com/repos/colinlienard/gitlight/releases');
7 | const data = (await response.json()) as GithubRelease[];
8 | const { assets } = data[0];
9 | return assets.find(({ name }) => name.endsWith(os))?.browser_download_url as string;
10 | }
11 |
12 | switch (params.os) {
13 | case 'apple-silicon':
14 | redirect(302, await getDownloadUrl('aarch64.dmg'));
15 | break;
16 |
17 | case 'mac-intel':
18 | redirect(302, await getDownloadUrl('x64.dmg'));
19 | break;
20 |
21 | case 'windows':
22 | redirect(302, await getDownloadUrl('.msi'));
23 | break;
24 |
25 | case 'linux':
26 | redirect(302, await getDownloadUrl('.AppImage'));
27 | break;
28 |
29 | default:
30 | return new Response('Not found', { status: 404 });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/routes/version/[target]/[version]/+server.ts:
--------------------------------------------------------------------------------
1 | import type { GithubRelease } from '$lib/types/github-types.js';
2 |
3 | type Target = 'linux' | 'windows' | 'darwin';
4 |
5 | type MacosArch = 'aarch64' | 'x86_64' | undefined;
6 |
7 | const targetExtensions: Record = {
8 | linux: '.AppImage.tar.gz',
9 | windows: '.msi.zip',
10 | darwin: '.app.tar.gz'
11 | };
12 |
13 | export async function GET({ params, url }) {
14 | try {
15 | const target = params.target as Target;
16 | const { version } = params;
17 | const arch = url.searchParams.get('arch') as MacosArch;
18 |
19 | // Get latest release from Github
20 | const response = await fetch('https://api.github.com/repos/colinlienard/gitlight/releases');
21 | const data = (await response.json()) as GithubRelease[];
22 | const { assets, published_at, body, tag_name } = data[0];
23 | const latestVersion = tag_name.split('v')[1];
24 |
25 | if (version === latestVersion) throw new Error();
26 |
27 | let extension = targetExtensions[target];
28 | if (target === 'darwin') {
29 | extension = arch === 'aarch64' ? 'aarch64.app.tar.gz' : 'x64.app.tar.gz';
30 | }
31 |
32 | if (!extension) throw new Error();
33 |
34 | // Get the asset and its signature file
35 | const updaterAsset = assets.find(({ name }) => name.endsWith(extension));
36 | const signatureAsset = assets.find(({ name }) => name.endsWith(`${extension}.sig`));
37 |
38 | if (!updaterAsset || !signatureAsset) throw new Error();
39 |
40 | // Get the signature from the .sig file
41 | const signatureResponse = await fetch(signatureAsset.browser_download_url);
42 | const signature = await signatureResponse.text();
43 |
44 | const returnValue = {
45 | url: updaterAsset.browser_download_url,
46 | version: latestVersion,
47 | notes: body,
48 | pub_date: published_at,
49 | signature
50 | };
51 |
52 | return new Response(JSON.stringify(returnValue), { status: 200 });
53 | } catch {
54 | return new Response(null, { status: 204 });
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/styles/_base.scss:
--------------------------------------------------------------------------------
1 | html {
2 | --bg-1: #fafafa;
3 | --bg-2: #fff;
4 | --bg-3: #ededed;
5 | --bg-4: #e2e2e2;
6 | --bg-5: #7e7e7e;
7 | --bg-6: #3a3a3a;
8 | --shadow-color: rgb(0, 0, 0, 5%);
9 | --modal-shadow-color: rgb(0, 0, 0, 20%);
10 |
11 | &[data-theme='dark'] {
12 | --bg-1: #171717;
13 | --bg-2: #1b1b1b;
14 | --bg-3: #212020;
15 | --bg-4: #292929;
16 | --bg-5: #888;
17 | --bg-6: #fff;
18 | --shadow-color: rgb(0, 0, 0, 15%);
19 | --modal-shadow-color: rgb(0, 0, 0, 50%);
20 |
21 | color-scheme: dark;
22 | }
23 |
24 | font-size: 15px;
25 |
26 | @include screens.mobile {
27 | font-size: 14px;
28 | }
29 | }
30 |
31 | body {
32 | background-color: variables.$bg-1;
33 | color: variables.$bg-6;
34 | font-family: Inter, sans-serif;
35 |
36 | @include screens.mobile {
37 | min-height: 100svh;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/styles/_fonts.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-display: swap;
3 | font-family: local('Inter');
4 | font-weight: 400;
5 | src: url('/fonts/Inter-Regular.ttf');
6 | }
7 |
8 | @font-face {
9 | font-family: local('Inter');
10 | font-weight: 600;
11 | src: url('/fonts/Inter-SemiBolf.ttf');
12 | }
13 |
--------------------------------------------------------------------------------
/src/styles/_mixins.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:color';
2 | @use './themes';
3 | @use './typography';
4 | @use './variables';
5 |
6 | @mixin shiny($color: 'primary', $radius: variables.$radius) {
7 | position: relative;
8 | border-radius: $radius;
9 | box-shadow: variables.$shadow;
10 | isolation: isolate;
11 |
12 | &::before {
13 | position: absolute;
14 | z-index: -1;
15 | border-radius: calc($radius - 1px);
16 | content: '';
17 | inset: 1px;
18 | pointer-events: none;
19 | }
20 |
21 | @if $color == 'primary' {
22 | background-image: linear-gradient(
23 | color.adjust(variables.$blue, $lightness: 10%),
24 | color.adjust(variables.$blue, $lightness: 5%)
25 | );
26 |
27 | &::before {
28 | background-image: linear-gradient(
29 | color.adjust(variables.$blue, $lightness: 3%),
30 | color.adjust(variables.$blue, $lightness: -1%)
31 | );
32 | }
33 |
34 | &:hover::before {
35 | background-image: linear-gradient(
36 | color.adjust(variables.$blue, $lightness: 1%),
37 | color.adjust(variables.$blue, $lightness: -1%)
38 | );
39 | }
40 | }
41 |
42 | @if $color == 'secondary' {
43 | @include themes.light {
44 | background-image: linear-gradient(variables.$bg-3, variables.$bg-4);
45 |
46 | &::before {
47 | background-color: variables.$bg-2;
48 | }
49 |
50 | &:hover::before {
51 | background-color: variables.$bg-1;
52 | }
53 | }
54 |
55 | @include themes.dark {
56 | background-image: linear-gradient(#2f2f2f, variables.$bg-4);
57 |
58 | &::before {
59 | background-image: linear-gradient(variables.$bg-3, variables.$bg-2);
60 | }
61 |
62 | &:hover::before {
63 | background: variables.$bg-2;
64 | }
65 | }
66 | }
67 | }
68 |
69 | @mixin box($hover: false) {
70 | border: 1px solid variables.$bg-4;
71 | border-radius: variables.$radius;
72 | background-color: variables.$bg-2;
73 |
74 | @if $hover {
75 | @include themes.light {
76 | &:hover {
77 | border-color: rgba(black, 20%);
78 | }
79 | }
80 |
81 | @include themes.dark {
82 | &:hover {
83 | border-color: rgba(white, 15%);
84 | }
85 | }
86 | }
87 | }
88 |
89 | @mixin link() {
90 | @include typography.bold;
91 |
92 | color: variables.$light-blue;
93 |
94 | &:hover {
95 | filter: brightness(130%);
96 | }
97 | }
98 |
99 | @mixin skeleton($width: 100%, $height: 100%) {
100 | position: relative;
101 | overflow: hidden;
102 | width: $width;
103 | height: $height;
104 | flex: 0 0 auto;
105 | border-radius: variables.$radius;
106 | background-color: variables.$bg-3;
107 |
108 | &::before {
109 | position: absolute;
110 | width: max(50%, 2rem);
111 | animation: skeleton 0.8s ease-in-out infinite;
112 | background-image: linear-gradient(to right, transparent, rgba(white, 0.05), transparent);
113 | content: '';
114 | inset: 0 auto;
115 |
116 | @keyframes skeleton {
117 | from {
118 | right: 100%;
119 | }
120 |
121 | to {
122 | right: min(-50%, -2rem);
123 | }
124 | }
125 | }
126 | }
127 |
128 | @mixin blurred-background() {
129 | position: fixed;
130 | z-index: 998;
131 | -webkit-backdrop-filter: blur(0.1rem);
132 | backdrop-filter: blur(0.1rem);
133 | background-color: rgba(black, 0.2);
134 | inset: 0;
135 | }
136 |
137 | @mixin item-list() {
138 | position: relative;
139 | display: flex;
140 | width: 100%;
141 | align-items: center;
142 | gap: 0.5rem;
143 |
144 | &:not(.active) {
145 | filter: saturate(0);
146 | opacity: 0.5;
147 | }
148 |
149 | &::before {
150 | position: absolute;
151 | z-index: -1;
152 | border-radius: variables.$radius;
153 | background-color: variables.$bg-2;
154 | content: '';
155 | inset: -0.25rem -0.5rem;
156 | opacity: 0;
157 | }
158 |
159 | &:hover::before {
160 | opacity: 1;
161 | }
162 | }
163 |
164 | @mixin broken-img() {
165 | position: relative;
166 |
167 | &::before {
168 | position: absolute;
169 | background: variables.$bg-4;
170 | content: '';
171 | inset: 0;
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/src/styles/_reset.scss:
--------------------------------------------------------------------------------
1 | * {
2 | padding: 0;
3 | margin: 0;
4 | -webkit-user-select: none;
5 | user-select: none;
6 | }
7 |
8 | *,
9 | *::before,
10 | *::after {
11 | box-sizing: border-box;
12 | }
13 |
14 | html {
15 | scroll-behavior: smooth;
16 | }
17 |
18 | body {
19 | min-height: 100vh;
20 | font-size: 100%;
21 | -webkit-font-smoothing: antialiased;
22 | line-height: 1;
23 | text-rendering: optimizelegibility;
24 | }
25 |
26 | h1,
27 | h2,
28 | h3,
29 | h4,
30 | h5,
31 | h6 {
32 | font-size: inherit;
33 | font-weight: normal;
34 | }
35 |
36 | a {
37 | color: inherit;
38 | text-decoration: none;
39 | }
40 |
41 | button,
42 | input,
43 | select,
44 | textarea {
45 | border: none;
46 | border-radius: 0;
47 | background-color: transparent;
48 | color: inherit;
49 | font-family: inherit;
50 | font-size: inherit;
51 | outline: none;
52 | }
53 |
54 | textarea {
55 | resize: vertical;
56 | }
57 |
58 | button,
59 | input[type='button'],
60 | input[type='reset'],
61 | input[type='submit'] {
62 | cursor: pointer;
63 | }
64 |
65 | img,
66 | video,
67 | svg {
68 | display: block;
69 | max-width: 100%;
70 | -webkit-user-drag: none;
71 | }
72 |
73 | ul {
74 | list-style: none;
75 | }
76 |
77 | strong {
78 | font-weight: inherit;
79 | }
80 |
--------------------------------------------------------------------------------
/src/styles/_screens.scss:
--------------------------------------------------------------------------------
1 | @mixin mobile() {
2 | @media (width <= 480px) {
3 | @content;
4 | }
5 | }
6 |
7 | @mixin desktop() {
8 | @media (width >= 480px) {
9 | @content;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/styles/_themes.scss:
--------------------------------------------------------------------------------
1 | @mixin light {
2 | @at-root #{selector-append(&, ':where(html[data-theme=light] *)')} {
3 | @content;
4 | }
5 | }
6 |
7 | @mixin dark {
8 | @at-root #{selector-append(&, ':where(html[data-theme=dark] *)')} {
9 | @content;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/styles/_typography.scss:
--------------------------------------------------------------------------------
1 | @mixin bold {
2 | font-weight: 600;
3 | }
4 |
5 | @mixin small {
6 | font-size: 0.875em;
7 | }
8 |
9 | @mixin heading-1 {
10 | @include bold;
11 |
12 | font-size: 1.5em;
13 | }
14 |
15 | @mixin heading-2 {
16 | @include bold;
17 |
18 | font-size: 1.25em;
19 | }
20 |
21 | @mixin base {
22 | line-height: 1.3;
23 | }
24 |
--------------------------------------------------------------------------------
/src/styles/_variables.scss:
--------------------------------------------------------------------------------
1 | // Colors
2 | $bg-1: var(--bg-1);
3 | $bg-2: var(--bg-2);
4 | $bg-3: var(--bg-3);
5 | $bg-4: var(--bg-4);
6 | $bg-5: var(--bg-5);
7 | $bg-6: var(--bg-6);
8 | $blue: #6040ff;
9 | $light-blue: #8585ff;
10 | $green: #22c965;
11 | $red: #e34763;
12 | $purple: #a557ff;
13 | $yellow: #ffa723;
14 |
15 | // Radius
16 | $radius: 0.5rem;
17 | $small-radius: 0.25rem;
18 |
19 | // Transition
20 | $transition: 0.15s ease-in-out;
21 |
22 | // Shadows
23 | $shadow: 0 1px 2px var(--shadow-color);
24 | $modal-shadow: 0 2rem 4rem var(--modal-shadow-color);
25 |
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/static/favicon.ico
--------------------------------------------------------------------------------
/static/fonts/Inter-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/static/fonts/Inter-Regular.ttf
--------------------------------------------------------------------------------
/static/fonts/Inter-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/static/fonts/Inter-SemiBold.ttf
--------------------------------------------------------------------------------
/static/images/gitlight-dark.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/static/images/gitlight-dark.webp
--------------------------------------------------------------------------------
/static/images/gitlight-light.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/static/images/gitlight-light.webp
--------------------------------------------------------------------------------
/static/images/logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/static/images/logo.webp
--------------------------------------------------------------------------------
/static/rive/logo.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colinlienard/gitlight/231c6bb6729e8532abd4477db6211b6e8f52446a/static/rive/logo.riv
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | import staticAdapter from '@sveltejs/adapter-static';
2 | import vercelAdapter from '@sveltejs/adapter-vercel';
3 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
4 |
5 | /** @type {import('@sveltejs/kit').Config} */
6 | const config = {
7 | preprocess: vitePreprocess(),
8 | kit: {
9 | adapter:
10 | process.env.APP_ENV === 'vercel' ? vercelAdapter() : staticAdapter({ fallback: '200.html' }),
11 | alias: {
12 | '~': './src'
13 | }
14 | }
15 | };
16 |
17 | export default config;
18 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import { defineConfig } from 'vite';
3 |
4 | export default defineConfig({
5 | plugins: [sveltekit()],
6 | css: {
7 | preprocessorOptions: {
8 | scss: {
9 | additionalData: `
10 | @use 'sass:color';
11 | @use "~/styles/mixins";
12 | @use "~/styles/screens";
13 | @use "~/styles/themes";
14 | @use "~/styles/typography";
15 | @use "~/styles/variables";
16 | `
17 | }
18 | }
19 | },
20 | define: {
21 | __APP_VERSION__: JSON.stringify(process.env.npm_package_version)
22 | },
23 | preview: {
24 | port: 5173
25 | }
26 | });
27 |
--------------------------------------------------------------------------------