├── .npmrc ├── src ├── lib │ ├── stores │ │ ├── index.ts │ │ └── stores.ts │ ├── components │ │ ├── settings │ │ │ ├── accounts │ │ │ │ ├── index.ts │ │ │ │ ├── Accounts.svelte │ │ │ │ ├── LogOutButton.svelte │ │ │ │ └── Account.svelte │ │ │ ├── github-settings │ │ │ │ ├── index.ts │ │ │ │ ├── GithubSettings.svelte │ │ │ │ └── PatItem.svelte │ │ │ ├── index.ts │ │ │ ├── gitlab-settings │ │ │ │ ├── index.ts │ │ │ │ ├── GitlabSettings.svelte │ │ │ │ └── GitlabRepos.svelte │ │ │ └── App.svelte │ │ ├── dashboard │ │ │ ├── sidebar │ │ │ │ ├── index.ts │ │ │ │ ├── SidebarSearch.svelte │ │ │ │ ├── SidebarProviders.svelte │ │ │ │ ├── SidebarSection.svelte │ │ │ │ └── TypeFilters.svelte │ │ │ ├── priorities │ │ │ │ ├── index.ts │ │ │ │ └── Priorities.svelte │ │ │ ├── index.ts │ │ │ ├── notifications │ │ │ │ ├── index.ts │ │ │ │ ├── NotificationList.svelte │ │ │ │ ├── NotificationPlaceholder.svelte │ │ │ │ ├── NotificationStatus.svelte │ │ │ │ ├── DoneModal.svelte │ │ │ │ ├── NotificationLabels.svelte │ │ │ │ └── NotificationDescription.svelte │ │ │ ├── LoadingScreen.svelte │ │ │ ├── SyncPill.svelte │ │ │ └── Banner.svelte │ │ ├── landing │ │ │ ├── index.ts │ │ │ └── DownloadButton.svelte │ │ ├── index.ts │ │ ├── login │ │ │ ├── index.ts │ │ │ └── GithubLoginButton.svelte │ │ └── common │ │ │ ├── DragRegion.svelte │ │ │ ├── index.ts │ │ │ ├── AnimatedLogo.svelte │ │ │ ├── IconButton.svelte │ │ │ ├── ScrollbarContainer.svelte │ │ │ ├── ShrinkableWrapper.svelte │ │ │ ├── Select.svelte │ │ │ ├── InlineSelect.svelte │ │ │ ├── Switch.svelte │ │ │ ├── Input.svelte │ │ │ ├── Button.svelte │ │ │ └── Modal.svelte │ ├── helpers │ │ ├── getAppVersion.ts │ │ ├── index.ts │ │ ├── debounce.ts │ │ ├── shadeColor.ts │ │ ├── formatRelativeDate.ts │ │ ├── openDesktopApp.ts │ │ ├── removeMarkdownSymbols.ts │ │ ├── priorities.ts │ │ ├── getIcon.ts │ │ └── getNotificationIcon.ts │ ├── types │ │ ├── type-helpers.ts │ │ ├── index.ts │ │ ├── gitlab-types.ts │ │ ├── github-types.ts │ │ └── common-types.ts │ ├── icons │ │ ├── PlusIcon.svelte │ │ ├── CrossIcon.svelte │ │ ├── WorkflowFailIcon.svelte │ │ ├── SmallArrowIcon.svelte │ │ ├── OpenIssueIcon.svelte │ │ ├── LightningIcon.svelte │ │ ├── CheckIcon.svelte │ │ ├── CommitIcon.svelte │ │ ├── WorkflowSuccessIcon.svelte │ │ ├── DoubleArrowIcon.svelte │ │ ├── PersonIcon.svelte │ │ ├── ClosedIssueIcon.svelte │ │ ├── ThreeDotsIcon.svelte │ │ ├── HeartIcon.svelte │ │ ├── GitlabIcon.svelte │ │ ├── WindowsIcon.svelte │ │ ├── SearchIcon.svelte │ │ ├── XIcon.svelte │ │ ├── ArrowUpIcon.svelte │ │ ├── CompletedIssueIcon.svelte │ │ ├── ArrowRightIcon.svelte │ │ ├── PriorityIcon.svelte │ │ ├── PriorityUpIcon.svelte │ │ ├── PriorityDownIcon.svelte │ │ ├── ReleaseIcon.svelte │ │ ├── UnreadIcon.svelte │ │ ├── ExclamationMarkIcon.svelte │ │ ├── RestoreIcon.svelte │ │ ├── MuteIcon.svelte │ │ ├── DraftPullRequestIcon.svelte │ │ ├── MergedPullRequestIcon.svelte │ │ ├── ExternalLinkIcon.svelte │ │ ├── RefreshIcon.svelte │ │ ├── PinIcon.svelte │ │ ├── OpenPullRequestIcon.svelte │ │ ├── RepositoryIcon.svelte │ │ ├── ClosedPullRequestIcon.svelte │ │ ├── DoubleCheckIcon.svelte │ │ ├── TrashIcon.svelte │ │ ├── SparklesIcon.svelte │ │ ├── MacosIcon.svelte │ │ ├── UnpinIcon.svelte │ │ ├── MutedIcon.svelte │ │ ├── DiscussionIcon.svelte │ │ ├── AnsweredDiscussionIcon.svelte │ │ ├── GithubIcon.svelte │ │ ├── LinuxIcon.svelte │ │ ├── index.ts │ │ └── GearIcon.svelte │ └── features │ │ ├── index.ts │ │ ├── intersection-action.ts │ │ ├── delayed-hover.ts │ │ ├── fetchGithub.ts │ │ ├── storage.ts │ │ ├── fetchGitlab.ts │ │ └── drag-actions.ts ├── styles │ ├── _screens.scss │ ├── _themes.scss │ ├── _fonts.scss │ ├── _typography.scss │ ├── _variables.scss │ ├── _base.scss │ ├── _reset.scss │ └── _mixins.scss ├── app.d.ts ├── routes │ ├── auth │ │ ├── github │ │ │ ├── login │ │ │ │ └── +server.ts │ │ │ └── callback │ │ │ │ └── +server.ts │ │ └── gitlab │ │ │ ├── login │ │ │ └── +server.ts │ │ │ ├── refresh │ │ │ └── +server.ts │ │ │ └── callback │ │ │ └── +server.ts │ ├── download │ │ └── [os] │ │ │ └── +server.ts │ ├── (app) │ │ ├── login │ │ │ └── +page.svelte │ │ ├── deeplink │ │ │ └── +page.svelte │ │ └── +layout.svelte │ ├── version │ │ └── [target] │ │ │ └── [version] │ │ │ └── +server.ts │ ├── (tray) │ │ └── tray │ │ │ └── +page.svelte │ └── +layout.ts └── app.html ├── src-tauri ├── build.rs ├── .gitignore ├── icons │ ├── 32x32.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── 128x128.png │ ├── tray-new.png │ ├── 128x128@2x.png │ ├── StoreLogo.png │ ├── tray-base.png │ ├── Square30x30Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── tray-base-macos.png │ ├── tray-new-macos.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ └── Square310x310Logo.png ├── Info.plist ├── src │ ├── title_bar.rs │ └── commands.rs ├── Cargo.toml └── tauri.conf.json ├── assets ├── logo.png ├── linux.png ├── windows.png ├── apple-dark.png ├── apple-light.png ├── dashboard-dark.png └── dashboard-light.png ├── static ├── favicon.ico ├── rive │ └── logo.riv ├── images │ ├── logo.webp │ ├── gitlight-dark.webp │ └── gitlight-light.webp └── fonts │ ├── Inter-Regular.ttf │ └── Inter-SemiBold.ttf ├── .vscode ├── extensions.json └── settings.json ├── .gitignore ├── .github ├── renovate.json └── workflows │ ├── release.yml │ └── ci.yml ├── .env.example ├── .eslintignore ├── .prettierignore ├── .stylelintignore ├── tsconfig.json ├── .prettierrc ├── svelte.config.js ├── .stylelintrc ├── vite.config.ts ├── LICENSE ├── .eslintrc.cjs ├── package.json ├── README.md └── CONTRIBUTING.md /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /src/lib/stores/index.ts: -------------------------------------------------------------------------------- 1 | export * from './stores'; 2 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/assets/logo.png -------------------------------------------------------------------------------- /assets/linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/assets/linux.png -------------------------------------------------------------------------------- /assets/windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/assets/windows.png -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /src/lib/components/settings/accounts/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Accounts.svelte'; 2 | -------------------------------------------------------------------------------- /static/rive/logo.riv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/static/rive/logo.riv -------------------------------------------------------------------------------- /assets/apple-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/assets/apple-dark.png -------------------------------------------------------------------------------- /assets/apple-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/assets/apple-light.png -------------------------------------------------------------------------------- /static/images/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/static/images/logo.webp -------------------------------------------------------------------------------- /assets/dashboard-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/assets/dashboard-dark.png -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src/lib/components/dashboard/sidebar/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Sidebar } from './Sidebar.svelte'; 2 | -------------------------------------------------------------------------------- /src/lib/components/landing/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DownloadButton } from './DownloadButton.svelte'; 2 | -------------------------------------------------------------------------------- /src/lib/components/settings/github-settings/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './GithubSettings.svelte'; 2 | -------------------------------------------------------------------------------- /src/lib/helpers/getAppVersion.ts: -------------------------------------------------------------------------------- 1 | export function getAppVersion() { 2 | return __APP_VERSION__; 3 | } 4 | -------------------------------------------------------------------------------- /assets/dashboard-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/assets/dashboard-light.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/tray-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/tray-new.png -------------------------------------------------------------------------------- /src/lib/components/dashboard/priorities/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Priorities } from './Priorities.svelte'; 2 | -------------------------------------------------------------------------------- /src/lib/types/type-helpers.ts: -------------------------------------------------------------------------------- 1 | export type ObjectEntries = Array<[keyof T, T[keyof T]]>; 2 | -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/tray-base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/tray-base.png -------------------------------------------------------------------------------- /static/fonts/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/static/fonts/Inter-Regular.ttf -------------------------------------------------------------------------------- /static/fonts/Inter-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/static/fonts/Inter-SemiBold.ttf -------------------------------------------------------------------------------- /static/images/gitlight-dark.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/static/images/gitlight-dark.webp -------------------------------------------------------------------------------- /static/images/gitlight-light.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/static/images/gitlight-light.webp -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/tray-base-macos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/tray-base-macos.png -------------------------------------------------------------------------------- /src-tauri/icons/tray-new-macos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/tray-new-macos.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinlienard/gitlight/HEAD/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src/lib/components/settings/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Settings } from './Settings.svelte'; 2 | export { GitlabRepos } from './gitlab-settings'; 3 | -------------------------------------------------------------------------------- /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/components/settings/gitlab-settings/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './GitlabSettings.svelte'; 2 | export { default as GitlabRepos } from './GitlabRepos.svelte'; 3 | -------------------------------------------------------------------------------- /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/login/index.ts: -------------------------------------------------------------------------------- 1 | export { default as GithubLoginButton } from './GithubLoginButton.svelte'; 2 | export { default as GitlabLoginButton } from './GitlabLoginButton.svelte'; 3 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/lib/icons/PlusIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/icons/CrossIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/icons/WorkflowFailIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/lib/icons/SmallArrowIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/lib/icons/OpenIssueIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/lib/icons/LightningIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /src/lib/components/common/DragRegion.svelte: -------------------------------------------------------------------------------- 1 |
2 | 3 | 11 | -------------------------------------------------------------------------------- /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/lib/icons/CheckIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /src/lib/icons/CommitIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/lib/icons/WorkflowSuccessIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /src/lib/icons/DoubleArrowIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /src/lib/icons/PersonIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/lib/icons/ClosedIssueIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/lib/icons/ThreeDotsIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/lib/icons/HeartIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /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/lib/icons/GitlabIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /src/lib/icons/WindowsIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/lib/icons/SearchIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /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/icons/XIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/lib/icons/ArrowUpIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/icons/CompletedIssueIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /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/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/icons/ArrowRightIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /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/lib/icons/PriorityIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/icons/PriorityUpIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /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/icons/PriorityDownIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/icons/ReleaseIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/lib/icons/UnreadIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /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/icons/ExclamationMarkIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/lib/icons/RestoreIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 9 | 15 | 16 | -------------------------------------------------------------------------------- /src/lib/icons/MuteIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/icons/DraftPullRequestIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/icons/MergedPullRequestIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/lib/icons/ExternalLinkIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 9 | 15 | 16 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/lib/icons/RefreshIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 9 | 15 | 16 | -------------------------------------------------------------------------------- /src/lib/icons/PinIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 9 | 15 | 16 | -------------------------------------------------------------------------------- /src/lib/icons/OpenPullRequestIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 15 | 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/lib/components/login/GithubLoginButton.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | -------------------------------------------------------------------------------- /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/lib/icons/RepositoryIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/icons/ClosedPullRequestIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 17 | 18 | -------------------------------------------------------------------------------- /src/lib/icons/DoubleCheckIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /src/lib/icons/TrashIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 9 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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/lib/icons/SparklesIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/lib/icons/MacosIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/lib/icons/UnpinIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /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/lib/icons/MutedIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/lib/components/settings/accounts/Accounts.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
    11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 26 | -------------------------------------------------------------------------------- /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/lib/icons/DiscussionIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /src/lib/icons/AnsweredDiscussionIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /src/lib/icons/GithubIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /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/common/AnimatedLogo.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 | 18 |
19 |
20 | 21 | 39 | -------------------------------------------------------------------------------- /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/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/components/dashboard/sidebar/SidebarSearch.svelte: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 37 | 38 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 25 |
26 | 27 |

Repositories

28 | 29 | 30 | 45 | -------------------------------------------------------------------------------- /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/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/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/components/common/IconButton.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | 18 | 58 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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/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-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/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/lib/components/common/ScrollbarContainer.svelte: -------------------------------------------------------------------------------- 1 | 32 | 33 | {#if scroll} 34 |
35 | 36 |
37 | {:else} 38 | 39 | {/if} 40 | 41 | 64 | -------------------------------------------------------------------------------- /src/lib/components/settings/App.svelte: -------------------------------------------------------------------------------- 1 | 29 | 30 |
31 | {#if updateAvailable} 32 |

GitLight v{updateAvailable} is available!

33 | 34 | {:else} 35 |

GitLight v{getAppVersion()}

36 | {#if cannotUpdate} 37 | 38 | {:else} 39 | 40 | {/if} 41 | {/if} 42 |
43 | 47 | 48 | 62 | -------------------------------------------------------------------------------- /src/lib/components/common/ShrinkableWrapper.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 |
25 |
26 | 29 | 30 |
31 | {#if !shrinked} 32 |
33 | 34 |
35 | {/if} 36 |
37 | 38 | 75 | -------------------------------------------------------------------------------- /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/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/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/components/dashboard/notifications/DoneModal.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 | 15 | 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/routes/(app)/deeplink/+page.svelte: -------------------------------------------------------------------------------- 1 | 31 | 32 | 33 | GitLight 34 | 35 | 36 |
37 | 38 |
39 |

Redirecting you to the GitLight app...

40 | Or continue in the browser 41 |
42 |
43 | 44 | 71 | -------------------------------------------------------------------------------- /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 | 26 | 30 | 34 | 38 |
39 | {/if} 40 |
41 | 42 | 76 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/lib/components/settings/accounts/LogOutButton.svelte: -------------------------------------------------------------------------------- 1 | 40 | 41 |