diff --git a/.env.example b/.env.example index 21175f57..c4ac1a10 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,11 @@ +# If using GitHub provider STUDIO_GITHUB_CLIENT_ID= STUDIO_GITHUB_CLIENT_SECRET= +# If using GitLab provider +STUDIO_GITLAB_APPLICATION_ID= +STUDIO_GITLAB_APPLICATION_SECRET= + STUDIO_GOOGLE_CLIENT_ID= STUDIO_GOOGLE_CLIENT_SECRET= diff --git a/package.json b/package.json index cf761516..0d6899cf 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "unstorage": "1.17.1" }, "devDependencies": { + "@gitbeaker/core": "^43.8.0", "@iconify-json/simple-icons": "^1.2.57", "@nuxt/content": "^3.8.0", "@nuxt/eslint-config": "^1.10.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ae1d610..1b057b0b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,9 @@ importers: specifier: 1.17.1 version: 1.17.1(db0@0.3.4(better-sqlite3@12.4.1))(idb-keyval@6.2.2)(ioredis@5.8.2) devDependencies: + '@gitbeaker/core': + specifier: ^43.8.0 + version: 43.8.0 '@iconify-json/simple-icons': specifier: ^1.2.57 version: 1.2.58 @@ -588,6 +591,14 @@ packages: '@floating-ui/vue@1.1.9': resolution: {integrity: sha512-BfNqNW6KA83Nexspgb9DZuz578R7HT8MZw1CfK9I6Ah4QReNWEJsXWHN+SdmOVLNGmTPDi+fDT535Df5PzMLbQ==} + '@gitbeaker/core@43.8.0': + resolution: {integrity: sha512-H+LfKuf4dExBinb79c+CXViRBvTVQNf5BYLNSizm2SiqdED5JruhKX88payefleY0szp7G/mySlFSXPyGRH1dQ==} + engines: {node: '>=18.20.0'} + + '@gitbeaker/requester-utils@43.8.0': + resolution: {integrity: sha512-d/SiJdxijc+aH5ZBQOw83XLxNSXqsBZNm5k3nPu1EHxGxK0fajXmxdMl0/vNXbKRggnIquFCxURkrQSEzfjqxQ==} + engines: {node: '>=18.20.0'} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -2990,6 +3001,14 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -3525,6 +3544,10 @@ packages: resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} engines: {node: '>=12'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -3629,9 +3652,21 @@ packages: errx@0.1.0: resolution: {integrity: sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -3983,9 +4018,17 @@ packages: resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} engines: {node: '>=18'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-port-please@3.2.0: resolution: {integrity: sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -4059,6 +4102,10 @@ packages: resolution: {integrity: sha512-oB4vkQGqlMl682wL1IlWd02tXCbquGWM4voPEI85QmNKCaw8zGTm1f1rubFgkg3Eli2PtKlFgrnmUqasbQWlkw==} engines: {node: '>=20'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -4081,6 +4128,10 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -4690,6 +4741,10 @@ packages: marky@1.3.0: resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -5129,6 +5184,10 @@ packages: object-deep-merge@2.0.0: resolution: {integrity: sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + ofetch@1.5.1: resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} @@ -5313,6 +5372,10 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch-browser@2.2.6: + resolution: {integrity: sha512-0ypsOQt9D4e3hziV8O4elD9uN0z/jtUEfxVRtNaAAtXIyUx9m/SzlO020i8YNL2aL/E6blOvvHQcin6HZlFy/w==} + engines: {node: '>=8.6'} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -5579,6 +5642,10 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} @@ -5595,6 +5662,9 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} + rate-limiter-flexible@8.2.1: + resolution: {integrity: sha512-QreyZSG7jJepD7wbMVG0wvkMx8MZaCKdnpUIffoQ/xavM7PKzpSfzrs6DISVb00LXckV1lQ3NSkc4xL3ZaUVLg==} + rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} @@ -5881,6 +5951,22 @@ packages: shiki@3.15.0: resolution: {integrity: sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw==} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -6929,6 +7015,9 @@ packages: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} + xcase@2.0.1: + resolution: {integrity: sha512-UmFXIPU+9Eg3E9m/728Bii0lAIuoc+6nbrNUKaRPJOFp91ih44qqGlWtxMB6kXFrRD6po+86ksHM5XHCfk6iPw==} + xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} @@ -7451,6 +7540,19 @@ snapshots: - '@vue/composition-api' - vue + '@gitbeaker/core@43.8.0': + dependencies: + '@gitbeaker/requester-utils': 43.8.0 + qs: 6.14.0 + xcase: 2.0.1 + + '@gitbeaker/requester-utils@43.8.0': + dependencies: + picomatch-browser: 2.2.6 + qs: 6.14.0 + rate-limiter-flexible: 8.2.1 + xcase: 2.0.1 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -10320,6 +10422,16 @@ snapshots: cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} camelize@1.0.1: {} @@ -10905,6 +11017,12 @@ snapshots: dotenv@17.2.3: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + duplexer@0.1.2: {} eastasianwidth@0.2.0: {} @@ -10993,8 +11111,16 @@ snapshots: errx@0.1.0: {} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -11419,8 +11545,26 @@ snapshots: get-east-asian-width@1.4.0: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + get-port-please@3.2.0: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-stream@8.0.1: {} get-stream@9.0.1: @@ -11514,6 +11658,8 @@ snapshots: slash: 5.1.0 unicorn-magic: 0.3.0 + gopd@1.2.0: {} + graceful-fs@4.2.11: {} graphemer@1.4.0: {} @@ -11545,6 +11691,8 @@ snapshots: has-flag@4.0.0: {} + has-symbols@1.1.0: {} + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -12220,6 +12368,8 @@ snapshots: marky@1.3.0: {} + math-intrinsics@1.1.0: {} + mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -13055,6 +13205,8 @@ snapshots: object-deep-merge@2.0.0: {} + object-inspect@1.13.4: {} + ofetch@1.5.1: dependencies: destr: 2.0.5 @@ -13343,6 +13495,8 @@ snapshots: picocolors@1.1.1: {} + picomatch-browser@2.2.6: {} + picomatch@2.3.1: {} picomatch@4.0.3: {} @@ -13602,6 +13756,10 @@ snapshots: punycode@2.3.1: {} + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + quansync@0.2.11: {} queue-microtask@1.2.3: {} @@ -13614,6 +13772,8 @@ snapshots: range-parser@1.2.1: {} + rate-limiter-flexible@8.2.1: {} + rc9@2.1.2: dependencies: defu: 6.1.4 @@ -14064,6 +14224,34 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -15134,6 +15322,8 @@ snapshots: dependencies: is-wsl: 3.1.0 + xcase@2.0.1: {} + xml-name-validator@4.0.0: {} xmlhttprequest-ssl@2.1.2: {} diff --git a/src/app/src/components/AppFooter.vue b/src/app/src/components/AppFooter.vue index d6817538..14c533bd 100644 --- a/src/app/src/components/AppFooter.vue +++ b/src/app/src/components/AppFooter.vue @@ -3,9 +3,9 @@ import { computed } from 'vue' import { useI18n } from 'vue-i18n' import { useStudio } from '../composables/useStudio' import { useStudioState } from '../composables/useStudioState' -import type { DropdownMenuItem } from '@nuxt/ui' +import type { DropdownMenuItem } from '@nuxt/ui/runtime/components/DropdownMenu.vue.d.ts' -const { ui, host, git } = useStudio() +const { ui, host, gitProvider } = useStudio() const { devMode, preferences, updatePreference, unsetActiveLocation } = useStudioState() const user = host.user.get() const { t } = useI18n() @@ -17,7 +17,7 @@ const { t } = useI18n() // }, // }) -const repositoryUrl = computed(() => git.getBranchUrl()) +const repositoryUrl = computed(() => gitProvider.api.getBranchUrl()) const userMenuItems = computed(() => [ repositoryUrl.value ? [ @@ -26,7 +26,7 @@ const userMenuItems = computed(() => [ // } { label: `${host.repository.owner}/${host.repository.repo}`, - icon: 'i-simple-icons:github', + icon: gitProvider.icon, to: repositoryUrl.value, target: '_blank', }, diff --git a/src/app/src/components/content/ContentCardReview.vue b/src/app/src/components/content/ContentCardReview.vue index d4ae7096..62dcd49f 100644 --- a/src/app/src/components/content/ContentCardReview.vue +++ b/src/app/src/components/content/ContentCardReview.vue @@ -91,17 +91,21 @@ async function initializeEditor() { const generateContentFromDocument = host.document.generate.contentFromDocument const localOriginal = props.draftItem.original ? await generateContentFromDocument(props.draftItem.original as DatabaseItem) : null - const gitHubOriginal = props.draftItem.githubFile?.content ? fromBase64ToUTF8(props.draftItem.githubFile.content) : null + const remoteOriginal = props.draftItem.remoteFile?.content + ? (props.draftItem.remoteFile.encoding === 'base64' + ? fromBase64ToUTF8(props.draftItem.remoteFile.content) + : props.draftItem.remoteFile.content) + : null const modified = props.draftItem.modified ? await generateContentFromDocument(props.draftItem.modified as DatabasePageItem) : null - isAutomaticFormattingDetected.value = !areContentEqual(localOriginal, gitHubOriginal) + isAutomaticFormattingDetected.value = !areContentEqual(localOriginal, remoteOriginal) // Wait for DOM to update before initializing Monaco await nextTick() if (props.draftItem.status === DraftStatus.Updated) { useMonacoDiff(diffEditorRef, { - original: gitHubOriginal!, + original: remoteOriginal!, modified: modified!, language: language.value, colorMode: ui.colorMode, @@ -115,7 +119,7 @@ async function initializeEditor() { else if ([DraftStatus.Created, DraftStatus.Deleted].includes(props.draftItem.status)) { useMonaco(editorRef, { language, - initialContent: modified! || gitHubOriginal!, + initialContent: modified! || remoteOriginal!, readOnly: true, colorMode: ui.colorMode, }) diff --git a/src/app/src/components/content/ContentEditorCode.vue b/src/app/src/components/content/ContentEditorCode.vue index 96a7595f..b63b4857 100644 --- a/src/app/src/components/content/ContentEditorCode.vue +++ b/src/app/src/components/content/ContentEditorCode.vue @@ -136,13 +136,13 @@ async function setContent(document: DatabasePageItem) { currentDocumentId.value = document.id isAutomaticFormattingDetected.value = false - if (props.draftItem.original && props.draftItem.githubFile?.content) { + if (props.draftItem.original && props.draftItem.remoteFile?.content) { const localOriginal = await contentFromDocument(props.draftItem.original as DatabaseItem) as string - const gitHubOriginal = fromBase64ToUTF8(props.draftItem.githubFile.content) + const remoteOriginal = props.draftItem.remoteFile.encoding === 'base64' ? fromBase64ToUTF8(props.draftItem.remoteFile.content!) : props.draftItem.remoteFile.content! - isAutomaticFormattingDetected.value = !areContentEqual(localOriginal, gitHubOriginal) + isAutomaticFormattingDetected.value = !areContentEqual(localOriginal, remoteOriginal) if (isAutomaticFormattingDetected.value) { - originalContent.value = gitHubOriginal + originalContent.value = remoteOriginal formattedContent.value = localOriginal } } diff --git a/src/app/src/components/content/ContentEditorConflict.vue b/src/app/src/components/content/ContentEditorConflict.vue index b5635660..57e05a9e 100644 --- a/src/app/src/components/content/ContentEditorConflict.vue +++ b/src/app/src/components/content/ContentEditorConflict.vue @@ -13,13 +13,13 @@ const props = defineProps({ }, }) -const { ui, git } = useStudio() +const { ui, gitProvider } = useStudio() const diffEditorRef = ref() const conflict = computed(() => props.draftItem.conflict!) -const repositoryInfo = computed(() => git.getRepositoryInfo()) -const fileGitHubUrl = computed(() => joinURL(git.getContentRootDirUrl(), props.draftItem.fsPath)) +const repositoryInfo = computed(() => gitProvider.api.getRepositoryInfo()) +const fileRemoteUrl = computed(() => joinURL(gitProvider.api.getContentRootDirUrl(), props.draftItem.fsPath)) const language = computed(() => { switch (props.draftItem.fsPath.split('.').pop()) { @@ -36,7 +36,7 @@ const language = computed(() => { }) useMonacoDiff(diffEditorRef, { - original: conflict.value?.githubContent || '', + original: conflict.value?.remoteContent || '', modified: conflict.value?.localContent || '', language: language.value, colorMode: ui.colorMode, @@ -49,7 +49,7 @@ useMonacoDiff(diffEditorRef, {

@@ -60,7 +60,7 @@ useMonacoDiff(diffEditorRef, {
{{ $t('studio.conflict.repository') }} @@ -68,7 +68,7 @@ useMonacoDiff(diffEditorRef, {

- {{ $t('studio.conflict.description') }} + {{ $t('studio.conflict.description', { providerName: gitProvider.name }) }}

@@ -127,10 +127,10 @@ useMonacoDiff(diffEditorRef, {
- {{ $t('studio.conflict.githubVersion') }} + {{ gitProvider.name }}
import { computed, type PropType } from 'vue' -import type { MediaItem, DraftStatus, GithubFile } from '../../types' +import type { MediaItem, DraftStatus, GitFile } from '../../types' import { isImageFile, isVideoFile, isAudioFile } from '../../utils/file' const props = defineProps({ @@ -8,8 +8,8 @@ const props = defineProps({ type: Object as PropType, required: true, }, - githubFile: { - type: Object as PropType, + remoteFile: { + type: Object as PropType, default: null, }, status: { @@ -28,7 +28,7 @@ const isAudio = computed(() => isAudioFile(props.mediaItem?.path || '')) import { ref, computed, onMounted } from 'vue' import { formatBytes, getFileExtension } from '../../utils/file' -import type { MediaItem, GithubFile } from '../../types' +import type { MediaItem, GitFile } from '../../types' import type { PropType } from 'vue' import { useStudio } from '../../composables/useStudio' import { joinURL } from 'ufo' @@ -12,13 +12,13 @@ const props = defineProps({ type: Object as PropType, required: true, }, - githubFile: { - type: Object as PropType, + remoteFile: { + type: Object as PropType, default: null, }, }) -const { git } = useStudio() +const { gitProvider } = useStudio() const { t } = useI18n() const imageRef = ref(null) @@ -46,8 +46,8 @@ const imageInfo = computed(() => { { label: t('studio.media.metaType'), value: fileExtension.value }, ] - if (props.githubFile) { - info.push({ label: t('studio.media.metaSize'), value: formatBytes(props.githubFile.size) }) + if (props.remoteFile) { + info.push({ label: t('studio.media.metaSize'), value: formatBytes(props.remoteFile.size) }) } return info @@ -63,12 +63,12 @@ const previewBackground = computed(() => { }) const markdownCode = computed(() => { - const name = props.githubFile?.name || 'image' + const name = props.remoteFile?.name || 'image' return `![${name}](${props.mediaItem.path})` }) -const githubPath = computed(() => { - return joinURL(git.getBranchUrl(), props.githubFile.path!) +const remotePath = computed(() => { + return joinURL(gitProvider.api.getBranchUrl(), props.remoteFile.path!) }) @@ -102,7 +102,7 @@ const githubPath = computed(() => {
@@ -113,7 +113,7 @@ const githubPath = computed(() => { {{ $t('studio.media.fileName') }}

- {{ githubFile.name }} + {{ remoteFile.name }}

@@ -134,21 +134,21 @@ const githubPath = computed(() => {
- +
- {{ $t('studio.media.githubPath') }} + {{ $t('studio.media.providerPath', { providerName: gitProvider.name }) }}

- {{ githubFile.path }} + {{ remoteFile.path }}

diff --git a/src/app/src/components/shared/item/ItemActionsDropdown.vue b/src/app/src/components/shared/item/ItemActionsDropdown.vue index 2cc2172c..a7c500b0 100644 --- a/src/app/src/components/shared/item/ItemActionsDropdown.vue +++ b/src/app/src/components/shared/item/ItemActionsDropdown.vue @@ -4,7 +4,7 @@ import { computed, ref, watch, type PropType } from 'vue' import { StudioItemActionId } from '../../../types' import type { TreeItem, StudioAction } from '../../../types' import { useStudio } from '../../../composables/useStudio' -import type { DropdownMenuItem } from '@nuxt/ui/runtime/components/DropdownMenu.vue.js' +import type { DropdownMenuItem } from '@nuxt/ui/runtime/components/DropdownMenu.vue.d.ts' import { useI18n } from 'vue-i18n' const { context } = useStudio() diff --git a/src/app/src/composables/useContext.ts b/src/app/src/composables/useContext.ts index 337d363a..7a59e26c 100644 --- a/src/app/src/composables/useContext.ts +++ b/src/app/src/composables/useContext.ts @@ -19,7 +19,7 @@ import type { import { VirtualMediaCollectionName, generateStemFromFsPath } from '../utils/media' import { oneStepActions, STUDIO_ITEM_ACTION_DEFINITIONS, twoStepActions, STUDIO_BRANCH_ACTION_DEFINITIONS } from '../utils/context' import type { useTree } from './useTree' -import type { useGit } from './useGit' +import type { useGitProvider } from './useGitProvider' import type { useDraftMedias } from './useDraftMedias' import { useRoute, useRouter } from 'vue-router' import { findDescendantsFileItemsFromFsPath } from '../utils/tree' @@ -28,7 +28,7 @@ import { upperFirst } from 'scule' export const useContext = createSharedComposable(( host: StudioHost, - git: ReturnType, + gitProvider: ReturnType, documentTree: ReturnType, mediaTree: ReturnType, ) => { @@ -221,7 +221,7 @@ export const useContext = createSharedComposable(( const { commitMessage } = params const documentFiles = await documentTree.draft.listAsRawFiles() const mediaFiles = await mediaTree.draft.listAsRawFiles() - await git.commitFiles([...documentFiles, ...mediaFiles], commitMessage) + await gitProvider.api.commitFiles([...documentFiles, ...mediaFiles], commitMessage) // @ts-expect-error params is null await itemActionHandler[StudioItemActionId.RevertAllItems]() diff --git a/src/app/src/composables/useDraftBase.ts b/src/app/src/composables/useDraftBase.ts index a3eed97f..d429d279 100644 --- a/src/app/src/composables/useDraftBase.ts +++ b/src/app/src/composables/useDraftBase.ts @@ -1,10 +1,10 @@ import type { Storage } from 'unstorage' import { joinURL } from 'ufo' -import type { DraftItem, StudioHost, GithubFile, DatabaseItem, MediaItem, BaseItem } from '../types' +import type { DraftItem, StudioHost, GitFile, DatabaseItem, MediaItem, BaseItem } from '../types' import { ContentFileExtension } from '../types' import { DraftStatus } from '../types/draft' import { checkConflict, findDescendantsFromFsPath } from '../utils/draft' -import type { useGit } from './useGit' +import type { useGitProvider } from './useGitProvider' import { useHooks } from './useHooks' import { ref } from 'vue' import { useStudioState } from './useStudioState' @@ -12,14 +12,14 @@ import { useStudioState } from './useStudioState' export function useDraftBase( type: 'media' | 'document', host: StudioHost, - git: ReturnType, + gitProvider: ReturnType, storage: Storage>, ) { const isLoading = ref(false) const list = ref[]>([]) const current = ref | null>(null) - const ghPathPrefix = type === 'media' ? 'public' : 'content' + const remotePathPrefix = type === 'media' ? 'public' : 'content' const hostDb = type === 'media' ? host.media : host.document.db const hookName = `studio:draft:${type}:updated` as const const areDocumentsEqual = host.document.utils.areEqual @@ -37,11 +37,11 @@ export function useDraftBase( throw new Error(`Draft file already exists for document at ${fsPath}`) } - const githubFile = await git.fetchFile(joinURL(ghPathPrefix, fsPath), { cached: true }) as GithubFile + const remoteFile = await gitProvider.api.fetchFile(joinURL(remotePathPrefix, fsPath), { cached: true }) as GitFile const draftItem: DraftItem = { fsPath, - githubFile, + remoteFile, status: getStatus(item, original!), modified: item, } @@ -83,14 +83,14 @@ export function useDraftBase( list.value = list.value.filter(item => item.fsPath !== fsPath) } else { - // TODO: check if gh file has been updated - const githubFile = await git.fetchFile(joinURL('content', fsPath), { cached: true }) as GithubFile + // TODO: check if remote file has been updated + const remoteFile = await gitProvider.api.fetchFile(joinURL('content', fsPath), { cached: true }) as GitFile deleteDraftItem = { fsPath: existingDraftItem.fsPath, status: DraftStatus.Deleted, original: existingDraftItem.original, - githubFile, + remoteFile, } list.value = list.value.map(item => item.fsPath === fsPath ? deleteDraftItem! : item) as DraftItem[] @@ -98,13 +98,13 @@ export function useDraftBase( } else { // TODO: check if gh file has been updated - const githubFile = await git.fetchFile(joinURL('content', fsPath), { cached: true }) as GithubFile + const remoteFile = await gitProvider.api.fetchFile(joinURL('content', fsPath), { cached: true }) as GitFile deleteDraftItem = { fsPath, status: DraftStatus.Deleted, original: originalDbItem, - githubFile, + remoteFile, } list.value.push(deleteDraftItem) diff --git a/src/app/src/composables/useDraftDocuments.ts b/src/app/src/composables/useDraftDocuments.ts index 8794dfa1..39068a31 100644 --- a/src/app/src/composables/useDraftDocuments.ts +++ b/src/app/src/composables/useDraftDocuments.ts @@ -1,6 +1,6 @@ import type { DatabaseItem, DraftItem, StudioHost, RawFile } from '../types' import { DraftStatus } from '../types/draft' -import type { useGit } from './useGit' +import type { useGitProvider } from './useGitProvider' import { createSharedComposable } from '@vueuse/core' import { useHooks } from './useHooks' import { joinURL } from 'ufo' @@ -8,7 +8,7 @@ import { documentStorage as storage } from '../utils/storage' import { getFileExtension } from '../utils/file' import { useDraftBase } from './useDraftBase' -export const useDraftDocuments = createSharedComposable((host: StudioHost, git: ReturnType) => { +export const useDraftDocuments = createSharedComposable((host: StudioHost, gitProvider: ReturnType) => { const { isLoading, list, @@ -22,7 +22,7 @@ export const useDraftDocuments = createSharedComposable((host: StudioHost, git: unselect, load, getStatus, - } = useDraftBase('document', host, git, storage) + } = useDraftBase('document', host, gitProvider, storage) const hooks = useHooks() const hostDb = host.document.db diff --git a/src/app/src/composables/useDraftMedias.ts b/src/app/src/composables/useDraftMedias.ts index 1f862ab0..7d3971c1 100644 --- a/src/app/src/composables/useDraftMedias.ts +++ b/src/app/src/composables/useDraftMedias.ts @@ -2,7 +2,7 @@ import { joinURL, withLeadingSlash } from 'ufo' import type { DraftItem, StudioHost, MediaItem, RawFile } from '../types' import { VirtualMediaCollectionName, generateStemFromFsPath } from '../utils/media' import { DraftStatus } from '../types/draft' -import type { useGit } from './useGit' +import type { useGitProvider } from './useGitProvider' import { createSharedComposable } from '@vueuse/core' import { useDraftBase } from './useDraftBase' import { mediaStorage as storage } from '../utils/storage' @@ -11,7 +11,7 @@ import { useHooks } from './useHooks' const hooks = useHooks() -export const useDraftMedias = createSharedComposable((host: StudioHost, git: ReturnType) => { +export const useDraftMedias = createSharedComposable((host: StudioHost, gitProvider: ReturnType) => { const { isLoading, list, @@ -25,7 +25,7 @@ export const useDraftMedias = createSharedComposable((host: StudioHost, git: Ret unselect, load, getStatus, - } = useDraftBase('media', host, git, storage) + } = useDraftBase('media', host, gitProvider, storage) async function upload(parentFsPath: string, file: File) { const draftItem = await fileToDraftItem(parentFsPath, file) @@ -40,7 +40,7 @@ export const useDraftMedias = createSharedComposable((host: StudioHost, git: Ret return { fsPath, - githubFile: undefined, + remoteFile: undefined, status: DraftStatus.Created, modified: { id: joinURL(VirtualMediaCollectionName, fsPath), diff --git a/src/app/src/composables/useGitProvider.ts b/src/app/src/composables/useGitProvider.ts new file mode 100644 index 00000000..9a77942b --- /dev/null +++ b/src/app/src/composables/useGitProvider.ts @@ -0,0 +1,46 @@ +import { createSharedComposable } from '@vueuse/core' +import type { GitOptions, GitProviderAPI, GitProviderType } from '../types' +import { createGitHubProvider, createGitLabProvider, createNullProvider } from '../utils/providers' + +function getProviderIcon(provider: GitProviderType | null): string { + switch (provider) { + case 'github': + return 'i-simple-icons:github' + case 'gitlab': + return 'i-simple-icons:gitlab' + default: + return 'i-simple-icons:git' + } +} + +function getProviderName(provider: GitProviderType | null): string { + switch (provider) { + case 'github': + return 'GitHub' + case 'gitlab': + return 'GitLab' + default: + return 'Local' + } +} + +function createProvider(provider: GitProviderType | null, options: GitOptions): GitProviderAPI { + switch (provider) { + case 'gitlab': + return createGitLabProvider(options) + case 'github': + return createGitHubProvider(options) + default: + return createNullProvider(options) + } +} + +export const useGitProvider = createSharedComposable((options: GitOptions, devMode: boolean = false) => { + const provider = devMode ? null : options.provider + + return { + name: getProviderName(provider), + icon: getProviderIcon(provider), + api: createProvider(provider, options), + } +}) diff --git a/src/app/src/composables/useStudio.ts b/src/app/src/composables/useStudio.ts index cd8d74c4..94bce3b0 100644 --- a/src/app/src/composables/useStudio.ts +++ b/src/app/src/composables/useStudio.ts @@ -1,5 +1,5 @@ import { createSharedComposable } from '@vueuse/core' -import { useDevelopmentGit, useGit } from './useGit' +import { useGitProvider } from './useGitProvider' import { useUI } from './useUI' import { useContext } from './useContext' import { useDraftDocuments } from './useDraftDocuments' @@ -23,22 +23,24 @@ export const useStudio = createSharedComposable(() => { } const gitOptions: GitOptions = { + provider: host.repository.provider, owner: host.repository.owner, repo: host.repository.repo, branch: host.repository.branch, rootDir: host.repository.rootDir, - token: host.user.get().githubToken, + token: host.user.get().accessToken, authorName: host.user.get().name, authorEmail: host.user.get().email, + instanceUrl: host.repository.instanceUrl, } - const git = devMode.value ? useDevelopmentGit(gitOptions) : useGit(gitOptions) + const gitProvider = useGitProvider(gitOptions, devMode.value) const ui = useUI(host) - const draftDocuments = useDraftDocuments(host, git) + const draftDocuments = useDraftDocuments(host, gitProvider) const documentTree = useTree(StudioFeature.Content, host, draftDocuments) - const draftMedias = useDraftMedias(host, git) + const draftMedias = useDraftMedias(host, gitProvider) const mediaTree = useTree(StudioFeature.Media, host, draftMedias) - const context = useContext(host, git, documentTree, mediaTree) + const context = useContext(host, gitProvider, documentTree, mediaTree) ui.setLocale(host.meta.defaultLocale) @@ -73,7 +75,7 @@ export const useStudio = createSharedComposable(() => { return { isReady, host, - git, + gitProvider, ui, context, documentTree, diff --git a/src/app/src/locales/en.json b/src/app/src/locales/en.json index 4349e8a3..dd9cb801 100644 --- a/src/app/src/locales/en.json +++ b/src/app/src/locales/en.json @@ -32,7 +32,7 @@ "publish": { "failedTitle": "Publish Failed", "summary": "on {branch} of {repo} repository", - "errorTitle": "Error during GitHub publish", + "errorTitle": "Error during {providerName} publish", "failedGeneric": "Failed to publish changes" }, "review": { @@ -65,8 +65,7 @@ "repository": "Repository", "branch": "Branch", "file": "File", - "description": "The content on GitHub differs from your website version. Ensure your latest changes are deployed and refresh the page.", - "githubVersion": "GitHub", + "description": "The content on {providerName} differs from your website version. Ensure your latest changes are deployed and refresh the page.", "websiteVersion": "Website" }, "nav": { @@ -98,7 +97,7 @@ "metaSize": "Size", "fileName": "File name", "publicPath": "Public path", - "githubPath": "GitHub path", + "providerPath": "{providerName} path", "markdown": "Markdown" }, "aria": { diff --git a/src/app/src/locales/fr.json b/src/app/src/locales/fr.json index 99266d19..17813b57 100644 --- a/src/app/src/locales/fr.json +++ b/src/app/src/locales/fr.json @@ -32,7 +32,7 @@ "publish": { "failedTitle": "Échec de la publication", "summary": "sur la branche {branch} du dépôt {repo}", - "errorTitle": "Erreur lors de la publication sur GitHub", + "errorTitle": "Erreur lors de la publication sur {providerName}", "failedGeneric": "Échec de la publication des changements" }, "review": { @@ -65,8 +65,7 @@ "repository": "Dépôt", "branch": "Branche", "file": "Fichier", - "description": "Le contenu sur GitHub diffère de la version de votre site web. Assurez-vous que vos dernières modifications sont déployées et actualisez la page.", - "githubVersion": "GitHub", + "description": "Le contenu sur {providerName} diffère de la version de votre site web. Assurez-vous que vos dernières modifications sont déployées et actualisez la page.", "websiteVersion": "Site Web" }, "nav": { @@ -98,7 +97,7 @@ "metaSize": "Taille", "fileName": "Nom du fichier", "publicPath": "Chemin public", - "githubPath": "Chemin GitHub", + "providerPath": "Chemin {providerName}", "markdown": "Markdown" }, "aria": { diff --git a/src/app/src/pages/error.vue b/src/app/src/pages/error.vue index f1f6cbb3..e73302ac 100644 --- a/src/app/src/pages/error.vue +++ b/src/app/src/pages/error.vue @@ -7,13 +7,13 @@ import { useI18n } from 'vue-i18n' const { t } = useI18n() const route = useRoute() const router = useRouter() -const { git } = useStudio() +const { gitProvider } = useStudio() const errorMessage = computed(() => { return (route.query.error as string) || t('studio.notifications.error.unknown') }) -const repositoryInfo = computed(() => git.getRepositoryInfo()) +const repositoryInfo = computed(() => gitProvider.api.getRepositoryInfo()) function retry() { router.push('/review') @@ -39,13 +39,13 @@ function retry() {