support gitea

This commit is contained in:
xiaoqi.cxq 2022-05-25 13:43:45 +08:00
parent 329d00c707
commit 666db76f3c
26 changed files with 1233 additions and 10 deletions

View File

@ -3,6 +3,7 @@ var prodEnv = require('./prod.env')
module.exports = merge(prodEnv, { module.exports = merge(prodEnv, {
NODE_ENV: '"development"', NODE_ENV: '"development"',
// 以下配置是开发临时用的配置 随时可能失效 请替换为自己的
GITHUB_CLIENT_ID: '"845b8f75df48f2ee0563"', GITHUB_CLIENT_ID: '"845b8f75df48f2ee0563"',
GITHUB_CLIENT_SECRET: '"80df676597abded1450926861965cc3f9bead6a0"', GITHUB_CLIENT_SECRET: '"80df676597abded1450926861965cc3f9bead6a0"',
GITEE_CLIENT_ID: '"925ba7c78b85dec984f7877e4aca5cab10ae333c6d68e761bdb0b9dfb8f55672"', GITEE_CLIENT_ID: '"925ba7c78b85dec984f7877e4aca5cab10ae333c6d68e761bdb0b9dfb8f55672"',

View File

@ -2,11 +2,7 @@ const qs = require('qs'); // eslint-disable-line import/no-extraneous-dependenci
const request = require('request'); const request = require('request');
const conf = require('./conf'); const conf = require('./conf');
function giteeToken(clientId, code) { function giteeToken(clientId, code, oauth2RedirectUri) {
console.log('clientId: ' + clientId);
console.log('code: ' + code);
console.log('client_secret: ' + conf.values.giteeClientSecret);
console.log('redirect_uri: ' + conf.values.giteeCallback);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request({ request({
method: 'POST', method: 'POST',
@ -17,7 +13,7 @@ function giteeToken(clientId, code) {
code, code,
grant_type: 'authorization_code', grant_type: 'authorization_code',
scope: 'authorization_code', scope: 'authorization_code',
redirect_uri: conf.values.giteeCallback, redirect_uri: oauth2RedirectUri,
}, },
json: true json: true
}, (err, res, body) => { }, (err, res, body) => {
@ -35,7 +31,7 @@ function giteeToken(clientId, code) {
} }
exports.giteeToken = (req, res) => { exports.giteeToken = (req, res) => {
giteeToken(req.query.clientId, req.query.code) giteeToken(req.query.clientId, req.query.code, req.query.oauth2RedirectUri)
.then( .then(
token => res.send(token), token => res.send(token),
err => res err => res

5
src/assets/iconGitea.svg Normal file
View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" style="enable-background:new 0 0 640 640" xml:space="preserve" width="32" height="32">
<path style="fill:#fff" d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12z"/>
<path style="fill:#609926" d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6zM125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1zm300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1z"/>
<path style="fill:#609926" d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8-1.9 8 2 16.3 9.1 20 7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3 7.8 4 17.4 1.7 22.5-5.3 5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8l-24.6 50.4z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -66,6 +66,11 @@ import GitlabOpenModal from './modals/providers/GitlabOpenModal';
import GitlabPublishModal from './modals/providers/GitlabPublishModal'; import GitlabPublishModal from './modals/providers/GitlabPublishModal';
import GitlabSaveModal from './modals/providers/GitlabSaveModal'; import GitlabSaveModal from './modals/providers/GitlabSaveModal';
import GitlabWorkspaceModal from './modals/providers/GitlabWorkspaceModal'; import GitlabWorkspaceModal from './modals/providers/GitlabWorkspaceModal';
import GiteaAccountModal from './modals/providers/GiteaAccountModal';
import GiteaOpenModal from './modals/providers/GiteaOpenModal';
import GiteaPublishModal from './modals/providers/GiteaPublishModal';
import GiteaSaveModal from './modals/providers/GiteaSaveModal';
import GiteaWorkspaceModal from './modals/providers/GiteaWorkspaceModal';
import WordpressPublishModal from './modals/providers/WordpressPublishModal'; import WordpressPublishModal from './modals/providers/WordpressPublishModal';
import BloggerPublishModal from './modals/providers/BloggerPublishModal'; import BloggerPublishModal from './modals/providers/BloggerPublishModal';
import BloggerPagePublishModal from './modals/providers/BloggerPagePublishModal'; import BloggerPagePublishModal from './modals/providers/BloggerPagePublishModal';
@ -122,6 +127,11 @@ export default {
GitlabPublishModal, GitlabPublishModal,
GitlabSaveModal, GitlabSaveModal,
GitlabWorkspaceModal, GitlabWorkspaceModal,
GiteaAccountModal,
GiteaOpenModal,
GiteaPublishModal,
GiteaSaveModal,
GiteaWorkspaceModal,
WordpressPublishModal, WordpressPublishModal,
BloggerPublishModal, BloggerPublishModal,
BloggerPagePublishModal, BloggerPagePublishModal,

View File

@ -29,6 +29,9 @@
<span v-else-if="currentWorkspace.providerId === 'gitlabWorkspace'"> <span v-else-if="currentWorkspace.providerId === 'gitlabWorkspace'">
<b>{{currentWorkspace.name}}</b> synced with a <a :href="workspaceLocationUrl" target="_blank">GitLab project</a>. <b>{{currentWorkspace.name}}</b> synced with a <a :href="workspaceLocationUrl" target="_blank">GitLab project</a>.
</span> </span>
<span v-else-if="currentWorkspace.providerId === 'giteaWorkspace'">
<b>{{currentWorkspace.name}}</b> synced with a <a :href="workspaceLocationUrl" target="_blank">Gitea project</a>.
</span>
</div> </div>
<div class="menu-entry menu-entry--info flex flex--row flex--align-center" v-else> <div class="menu-entry menu-entry--info flex flex--row flex--align-center" v-else>
<div class="menu-entry__icon menu-entry__icon--disabled"> <div class="menu-entry__icon menu-entry__icon--disabled">

View File

@ -66,6 +66,13 @@
<span>{{token.name}}</span> <span>{{token.name}}</span>
</menu-entry> </menu-entry>
</div> </div>
<div v-for="token in giteaTokens" :key="token.sub">
<menu-entry @click.native="publishGitea(token)">
<icon-provider slot="icon" provider-id="gitea"></icon-provider>
<div>Publish to Gitea</div>
<span>{{token.name}}</span>
</menu-entry>
</div>
<div v-for="token in googleDriveTokens" :key="token.sub"> <div v-for="token in googleDriveTokens" :key="token.sub">
<menu-entry @click.native="publishGoogleDrive(token)"> <menu-entry @click.native="publishGoogleDrive(token)">
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider> <icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
@ -108,6 +115,10 @@
<icon-provider slot="icon" provider-id="gitlab"></icon-provider> <icon-provider slot="icon" provider-id="gitlab"></icon-provider>
<span>Add GitLab account</span> <span>Add GitLab account</span>
</menu-entry> </menu-entry>
<menu-entry @click.native="addGiteaAccount">
<icon-provider slot="icon" provider-id="gitea"></icon-provider>
<span>Add Gitea account</span>
</menu-entry>
<menu-entry @click.native="addGoogleDriveAccount"> <menu-entry @click.native="addGoogleDriveAccount">
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider> <icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
<span>Add Google Drive account</span> <span>Add Google Drive account</span>
@ -132,6 +143,7 @@ import dropboxHelper from '../../services/providers/helpers/dropboxHelper';
import githubHelper from '../../services/providers/helpers/githubHelper'; import githubHelper from '../../services/providers/helpers/githubHelper';
import giteeHelper from '../../services/providers/helpers/giteeHelper'; import giteeHelper from '../../services/providers/helpers/giteeHelper';
import gitlabHelper from '../../services/providers/helpers/gitlabHelper'; import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
import giteaHelper from '../../services/providers/helpers/giteaHelper';
import wordpressHelper from '../../services/providers/helpers/wordpressHelper'; import wordpressHelper from '../../services/providers/helpers/wordpressHelper';
import zendeskHelper from '../../services/providers/helpers/zendeskHelper'; import zendeskHelper from '../../services/providers/helpers/zendeskHelper';
import publishSvc from '../../services/publishSvc'; import publishSvc from '../../services/publishSvc';
@ -186,6 +198,9 @@ export default {
gitlabTokens() { gitlabTokens() {
return tokensToArray(store.getters['data/gitlabTokensBySub']); return tokensToArray(store.getters['data/gitlabTokensBySub']);
}, },
giteaTokens() {
return tokensToArray(store.getters['data/giteaTokensBySub']);
},
googleDriveTokens() { googleDriveTokens() {
return tokensToArray(store.getters['data/googleTokensBySub'], token => token.isDrive); return tokensToArray(store.getters['data/googleTokensBySub'], token => token.isDrive);
}, },
@ -199,7 +214,9 @@ export default {
return !this.bloggerTokens.length return !this.bloggerTokens.length
&& !this.dropboxTokens.length && !this.dropboxTokens.length
&& !this.githubTokens.length && !this.githubTokens.length
&& !this.giteeTokens.length
&& !this.gitlabTokens.length && !this.gitlabTokens.length
&& !this.giteaTokens.length
&& !this.googleDriveTokens.length && !this.googleDriveTokens.length
&& !this.wordpressTokens.length && !this.wordpressTokens.length
&& !this.zendeskTokens.length; && !this.zendeskTokens.length;
@ -245,6 +262,12 @@ export default {
await gitlabHelper.addAccount(serverUrl, applicationId); await gitlabHelper.addAccount(serverUrl, applicationId);
} catch (e) { /* cancel */ } } catch (e) { /* cancel */ }
}, },
async addGiteaAccount() {
try {
const { serverUrl, applicationId, applicationSecret } = await store.dispatch('modal/open', { type: 'giteaAccount' });
await giteaHelper.addAccount(serverUrl, applicationId, applicationSecret);
} catch (e) { /* cancel */ }
},
async addGoogleDriveAccount() { async addGoogleDriveAccount() {
try { try {
await store.dispatch('modal/open', { type: 'googleDriveAccount' }); await store.dispatch('modal/open', { type: 'googleDriveAccount' });
@ -269,6 +292,7 @@ export default {
publishGitee: publishModalOpener('giteePublish', 'publishToGitee'), publishGitee: publishModalOpener('giteePublish', 'publishToGitee'),
publishGist: publishModalOpener('gistPublish', 'publishToGist'), publishGist: publishModalOpener('gistPublish', 'publishToGist'),
publishGitlab: publishModalOpener('gitlabPublish', 'publishToGitlab'), publishGitlab: publishModalOpener('gitlabPublish', 'publishToGitlab'),
publishGitea: publishModalOpener('giteaPublish', 'publishToGitea'),
publishGoogleDrive: publishModalOpener('googleDrivePublish', 'publishToGoogleDrive'), publishGoogleDrive: publishModalOpener('googleDrivePublish', 'publishToGoogleDrive'),
publishWordpress: publishModalOpener('wordpressPublish', 'publishToWordPress'), publishWordpress: publishModalOpener('wordpressPublish', 'publishToWordPress'),
publishZendesk: publishModalOpener('zendeskPublish', 'publishToZendesk'), publishZendesk: publishModalOpener('zendeskPublish', 'publishToZendesk'),

View File

@ -74,6 +74,18 @@
<span>{{token.name}}</span> <span>{{token.name}}</span>
</menu-entry> </menu-entry>
</div> </div>
<div v-for="token in giteaTokens" :key="token.sub">
<menu-entry @click.native="openGitea(token)">
<icon-provider slot="icon" provider-id="gitea"></icon-provider>
<div>Open from Gitea</div>
<span>{{token.name}}</span>
</menu-entry>
<menu-entry @click.native="saveGitea(token)">
<icon-provider slot="icon" provider-id="gitea"></icon-provider>
<div>Save on Gitea</div>
<span>{{token.name}}</span>
</menu-entry>
</div>
<div v-for="token in googleDriveTokens" :key="token.sub"> <div v-for="token in googleDriveTokens" :key="token.sub">
<menu-entry @click.native="openGoogleDrive(token)"> <menu-entry @click.native="openGoogleDrive(token)">
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider> <icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
@ -103,6 +115,10 @@
<icon-provider slot="icon" provider-id="gitlab"></icon-provider> <icon-provider slot="icon" provider-id="gitlab"></icon-provider>
<span>Add GitLab account</span> <span>Add GitLab account</span>
</menu-entry> </menu-entry>
<menu-entry @click.native="addGiteaAccount">
<icon-provider slot="icon" provider-id="gitea"></icon-provider>
<span>Add Gitea account</span>
</menu-entry>
<menu-entry @click.native="addGoogleDriveAccount"> <menu-entry @click.native="addGoogleDriveAccount">
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider> <icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
<span>Add Google Drive account</span> <span>Add Google Drive account</span>
@ -119,11 +135,13 @@ import dropboxHelper from '../../services/providers/helpers/dropboxHelper';
import githubHelper from '../../services/providers/helpers/githubHelper'; import githubHelper from '../../services/providers/helpers/githubHelper';
import giteeHelper from '../../services/providers/helpers/giteeHelper'; import giteeHelper from '../../services/providers/helpers/giteeHelper';
import gitlabHelper from '../../services/providers/helpers/gitlabHelper'; import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
import giteaHelper from '../../services/providers/helpers/giteaHelper';
import googleDriveProvider from '../../services/providers/googleDriveProvider'; import googleDriveProvider from '../../services/providers/googleDriveProvider';
import dropboxProvider from '../../services/providers/dropboxProvider'; import dropboxProvider from '../../services/providers/dropboxProvider';
import githubProvider from '../../services/providers/githubProvider'; import githubProvider from '../../services/providers/githubProvider';
import giteeProvider from '../../services/providers/giteeProvider'; import giteeProvider from '../../services/providers/giteeProvider';
import gitlabProvider from '../../services/providers/gitlabProvider'; import gitlabProvider from '../../services/providers/gitlabProvider';
import giteaProvider from '../../services/providers/giteaProvider';
import syncSvc from '../../services/syncSvc'; import syncSvc from '../../services/syncSvc';
import store from '../../store'; import store from '../../store';
import badgeSvc from '../../services/badgeSvc'; import badgeSvc from '../../services/badgeSvc';
@ -172,6 +190,9 @@ export default {
gitlabTokens() { gitlabTokens() {
return tokensToArray(store.getters['data/gitlabTokensBySub']); return tokensToArray(store.getters['data/gitlabTokensBySub']);
}, },
giteaTokens() {
return tokensToArray(store.getters['data/giteaTokensBySub']);
},
googleDriveTokens() { googleDriveTokens() {
return tokensToArray(store.getters['data/googleTokensBySub'], token => token.isDrive); return tokensToArray(store.getters['data/googleTokensBySub'], token => token.isDrive);
}, },
@ -217,6 +238,12 @@ export default {
await gitlabHelper.addAccount(serverUrl, applicationId); await gitlabHelper.addAccount(serverUrl, applicationId);
} catch (e) { /* cancel */ } } catch (e) { /* cancel */ }
}, },
async addGiteaAccount() {
try {
const { serverUrl, applicationId, applicationSecret } = await store.dispatch('modal/open', { type: 'giteaAccount' });
await giteaHelper.addAccount(serverUrl, applicationId, applicationSecret);
} catch (e) { /* cancel */ }
},
async addGoogleDriveAccount() { async addGoogleDriveAccount() {
try { try {
await store.dispatch('modal/open', { type: 'googleDriveAccount' }); await store.dispatch('modal/open', { type: 'googleDriveAccount' });
@ -318,12 +345,33 @@ export default {
); );
} catch (e) { /* cancel */ } } catch (e) { /* cancel */ }
}, },
async openGitea(token) {
try {
const syncLocation = await store.dispatch('modal/open', {
type: 'giteaOpen',
token,
});
store.dispatch(
'queue/enqueue',
async () => {
await giteaProvider.openFile(token, syncLocation);
badgeSvc.addBadge('openFromGitea');
},
);
} catch (e) { /* cancel */ }
},
async saveGitlab(token) { async saveGitlab(token) {
try { try {
await openSyncModal(token, 'gitlabSave'); await openSyncModal(token, 'gitlabSave');
badgeSvc.addBadge('saveOnGitlab'); badgeSvc.addBadge('saveOnGitlab');
} catch (e) { /* cancel */ } } catch (e) { /* cancel */ }
}, },
async saveGitea(token) {
try {
await openSyncModal(token, 'giteaSave');
badgeSvc.addBadge('saveOnGitea');
} catch (e) { /* cancel */ }
},
}, },
}; };
</script> </script>

View File

@ -29,6 +29,10 @@
<icon-provider slot="icon" provider-id="gitlabWorkspace"></icon-provider> <icon-provider slot="icon" provider-id="gitlabWorkspace"></icon-provider>
<span>Add a <b>GitLab</b> workspace</span> <span>Add a <b>GitLab</b> workspace</span>
</menu-entry> </menu-entry>
<menu-entry @click.native="addGiteaWorkspace">
<icon-provider slot="icon" provider-id="giteaWorkspace"></icon-provider>
<span>Add a <b>Gitea</b> workspace</span>
</menu-entry>
<menu-entry @click.native="addGoogleDriveWorkspace"> <menu-entry @click.native="addGoogleDriveWorkspace">
<icon-provider slot="icon" provider-id="googleDriveWorkspace"></icon-provider> <icon-provider slot="icon" provider-id="googleDriveWorkspace"></icon-provider>
<span>Add a <b>Google Drive</b> workspace</span> <span>Add a <b>Google Drive</b> workspace</span>
@ -41,6 +45,7 @@ import { mapGetters } from 'vuex';
import MenuEntry from './common/MenuEntry'; import MenuEntry from './common/MenuEntry';
import googleHelper from '../../services/providers/helpers/googleHelper'; import googleHelper from '../../services/providers/helpers/googleHelper';
import gitlabHelper from '../../services/providers/helpers/gitlabHelper'; import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
import giteaHelper from '../../services/providers/helpers/giteaHelper';
import store from '../../store'; import store from '../../store';
export default { export default {
@ -88,6 +93,16 @@ export default {
}); });
} catch (e) { /* Cancel */ } } catch (e) { /* Cancel */ }
}, },
async addGiteaWorkspace() {
try {
const { serverUrl, applicationId, applicationSecret } = await store.dispatch('modal/open', { type: 'giteaAccount' });
const token = await giteaHelper.addAccount(serverUrl, applicationId, applicationSecret);
store.dispatch('modal/open', {
type: 'giteaWorkspace',
token,
});
} catch (e) { /* Cancel */ }
},
async addGoogleDriveWorkspace() { async addGoogleDriveWorkspace() {
try { try {
const token = await googleHelper.addDriveAccount(true); const token = await googleHelper.addDriveAccount(true);

View File

@ -57,6 +57,10 @@
<icon-provider slot="icon" provider-id="gitlab"></icon-provider> <icon-provider slot="icon" provider-id="gitlab"></icon-provider>
<span>Add GitLab account</span> <span>Add GitLab account</span>
</menu-entry> </menu-entry>
<menu-entry @click.native="addGiteaAccount">
<icon-provider slot="icon" provider-id="gitea"></icon-provider>
<span>Add Gitea account</span>
</menu-entry>
<menu-entry @click.native="addGoogleDriveAccount"> <menu-entry @click.native="addGoogleDriveAccount">
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider> <icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
<span>Add Google Drive account</span> <span>Add Google Drive account</span>
@ -91,6 +95,7 @@ import dropboxHelper from '../../services/providers/helpers/dropboxHelper';
import githubHelper from '../../services/providers/helpers/githubHelper'; import githubHelper from '../../services/providers/helpers/githubHelper';
import giteeHelper from '../../services/providers/helpers/giteeHelper'; import giteeHelper from '../../services/providers/helpers/giteeHelper';
import gitlabHelper from '../../services/providers/helpers/gitlabHelper'; import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
import giteaHelper from '../../services/providers/helpers/giteaHelper';
import wordpressHelper from '../../services/providers/helpers/wordpressHelper'; import wordpressHelper from '../../services/providers/helpers/wordpressHelper';
import zendeskHelper from '../../services/providers/helpers/zendeskHelper'; import zendeskHelper from '../../services/providers/helpers/zendeskHelper';
import badgeSvc from '../../services/badgeSvc'; import badgeSvc from '../../services/badgeSvc';
@ -148,6 +153,14 @@ export default {
name: token.name, name: token.name,
scopes: ['api'], scopes: ['api'],
})), })),
...Object.values(store.getters['data/giteaTokensBySub']).map(token => ({
token,
providerId: 'gitea',
url: token.serverUrl,
userId: token.sub,
name: token.name,
scopes: ['api'],
})),
...Object.values(store.getters['data/wordpressTokensBySub']).map(token => ({ ...Object.values(store.getters['data/wordpressTokensBySub']).map(token => ({
token, token,
providerId: 'wordpress', providerId: 'wordpress',
@ -204,6 +217,12 @@ export default {
await gitlabHelper.addAccount(serverUrl, applicationId); await gitlabHelper.addAccount(serverUrl, applicationId);
} catch (e) { /* cancel */ } } catch (e) { /* cancel */ }
}, },
async addGiteaAccount() {
try {
const { serverUrl, applicationId, applicationSecret } = await store.dispatch('modal/open', { type: 'giteaAccount' });
await giteaHelper.addAccount(serverUrl, applicationId, applicationSecret);
} catch (e) { /* cancel */ }
},
async addGoogleDriveAccount() { async addGoogleDriveAccount() {
try { try {
await store.dispatch('modal/open', { type: 'googleDriveAccount' }); await store.dispatch('modal/open', { type: 'googleDriveAccount' });

View File

@ -0,0 +1,75 @@
<template>
<modal-inner aria-label="Gitea account">
<div class="modal__content">
<div class="modal__image">
<icon-provider provider-id="gitea"></icon-provider>
</div>
<p>Link your <b>Gitea</b> account to <b>StackEdit</b>.</p>
<form-entry label="Gitea URL" error="serverUrl">
<input v-if="config.forceServerUrl" slot="field" class="textfield" type="text" disabled="disabled" v-model="config.forceServerUrl">
<input v-else slot="field" class="textfield" type="text" v-model.trim="serverUrl" @keydown.enter="resolve()">
<div class="form-entry__info">
<b>Example:</b> https://gitea.example.com/
</div>
</form-entry>
<form-entry label="Application ID" error="applicationId">
<input slot="field" class="textfield" type="text" v-model.trim="applicationId" @keydown.enter="resolve()">
</form-entry>
<form-entry label="Application Secret" error="applicationSecret">
<input slot="field" class="textfield" type="text" v-model.trim="applicationSecret" @keydown.enter="resolve()">
<div class="form-entry__info">
You have to configure an OAuth2 Application with redirect URL <b>{{redirectUrl}}</b>
</div>
<div class="form-entry__actions">
<a href="https://docs.gitea.io/en-us/oauth2-provider/" target="_blank">More info</a>
</div>
</form-entry>
</div>
<div class="modal__button-bar">
<button class="button" @click="config.reject()">Cancel</button>
<button class="button button--resolve" @click="resolve()">Ok</button>
</div>
</modal-inner>
</template>
<script>
import modalTemplate from '../common/modalTemplate';
import constants from '../../../data/constants';
export default modalTemplate({
data: () => ({
redirectUrl: constants.oauth2RedirectUri,
}),
computedLocalSettings: {
serverUrl: 'giteaServerUrl',
applicationId: 'giteaApplicationId',
applicationSecret: 'giteaApplicationSecret',
},
methods: {
resolve() {
const serverUrl = this.config.forceServerUrl || this.serverUrl;
if (!serverUrl) {
this.setError('serverUrl');
}
if (!this.applicationId) {
this.setError('applicationId');
}
if (!this.applicationSecret) {
this.setError('applicationSecret');
}
if (serverUrl && this.applicationId) {
const parsedUrl = serverUrl.match(/^(https:\/\/[^/]+)/);
if (!parsedUrl) {
this.setError('serverUrl');
} else {
this.config.resolve({
serverUrl: parsedUrl[1],
applicationId: this.applicationId,
applicationSecret: this.applicationSecret,
});
}
}
},
},
});
</script>

View File

@ -0,0 +1,69 @@
<template>
<modal-inner aria-label="Synchronize with Gitea">
<div class="modal__content">
<div class="modal__image">
<icon-provider provider-id="gitea"></icon-provider>
</div>
<p>Open a file from your <b>Gitea</b> project and keep it synced.</p>
<form-entry label="Project URL" error="projectUrl">
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
<div class="form-entry__info">
<b>Example:</b> {{config.token.serverUrl}}/path/to/project
</div>
</form-entry>
<form-entry label="File path" error="path">
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
<div class="form-entry__info">
<b>Example:</b> path/to/README.md
</div>
</form-entry>
<form-entry label="Branch" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
<div class="form-entry__info">
If not supplied, the <code>master</code> branch will be used.
</div>
</form-entry>
</div>
<div class="modal__button-bar">
<button class="button" @click="config.reject()">Cancel</button>
<button class="button button--resolve" @click="resolve()">Ok</button>
</div>
</modal-inner>
</template>
<script>
import giteaProvider from '../../../services/providers/giteaProvider';
import modalTemplate from '../common/modalTemplate';
import utils from '../../../services/utils';
export default modalTemplate({
data: () => ({
branch: '',
path: '',
}),
computedLocalSettings: {
projectUrl: 'giteaProjectUrl',
},
methods: {
resolve() {
const projectPath = utils.parseGiteaProjectPath(this.projectUrl);
if (!projectPath) {
this.setError('projectUrl');
}
if (!this.path) {
this.setError('path');
}
if (projectPath && this.path) {
// Return new location
const location = giteaProvider.makeLocation(
this.config.token,
projectPath,
this.branch || 'master',
this.path,
);
this.config.resolve(location);
}
},
},
});
</script>

View File

@ -0,0 +1,85 @@
<template>
<modal-inner aria-label="Publish to Gitea">
<div class="modal__content">
<div class="modal__image">
<icon-provider provider-id="gitea"></icon-provider>
</div>
<p>Publish <b>{{currentFileName}}</b> to your <b>Gitea</b> project.</p>
<form-entry label="Project URL" error="projectUrl">
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
<div class="form-entry__info">
<b>Example:</b> {{config.token.serverUrl}}/path/to/project
</div>
</form-entry>
<form-entry label="File path" error="path">
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
<div class="form-entry__info">
<b>Example:</b> path/to/README.md<br>
If the file exists, it will be overwritten.
</div>
</form-entry>
<form-entry label="Branch" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
<div class="form-entry__info">
If not supplied, the <code>master</code> branch will be used.
</div>
</form-entry>
<form-entry label="Template">
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
{{ template.name }}
</option>
</select>
<div class="form-entry__actions">
<a href="javascript:void(0)" @click="configureTemplates">Configure templates</a>
</div>
</form-entry>
</div>
<div class="modal__button-bar">
<button class="button" @click="config.reject()">Cancel</button>
<button class="button button--resolve" @click="resolve()">Ok</button>
</div>
</modal-inner>
</template>
<script>
import giteaProvider from '../../../services/providers/giteaProvider';
import modalTemplate from '../common/modalTemplate';
import utils from '../../../services/utils';
export default modalTemplate({
data: () => ({
branch: '',
path: '',
}),
computedLocalSettings: {
projectUrl: 'giteaProjectUrl',
selectedTemplate: 'giteaPublishTemplate',
},
created() {
this.path = `${this.currentFileName}.md`;
},
methods: {
resolve() {
const projectPath = utils.parseGiteaProjectPath(this.projectUrl);
if (!projectPath) {
this.setError('projectUrl');
}
if (!this.path) {
this.setError('path');
}
if (projectPath && this.path) {
// Return new location
const location = giteaProvider.makeLocation(
this.config.token,
projectPath,
this.branch || 'master',
this.path,
);
location.templateId = this.selectedTemplate;
this.config.resolve(location);
}
},
},
});
</script>

View File

@ -0,0 +1,72 @@
<template>
<modal-inner aria-label="Synchronize with Gitea">
<div class="modal__content">
<div class="modal__image">
<icon-provider provider-id="gitea"></icon-provider>
</div>
<p>Save <b>{{currentFileName}}</b> to your <b>Gitea</b> project and keep it synced.</p>
<form-entry label="Project URL" error="projectUrl">
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
<div class="form-entry__info">
<b>Example:</b> {{config.token.serverUrl}}/path/to/project
</div>
</form-entry>
<form-entry label="File path" error="path">
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
<div class="form-entry__info">
<b>Example:</b> path/to/README.md<br>
If the file exists, it will be overwritten.
</div>
</form-entry>
<form-entry label="Branch" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
<div class="form-entry__info">
If not supplied, the <code>master</code> branch will be used.
</div>
</form-entry>
</div>
<div class="modal__button-bar">
<button class="button" @click="config.reject()">Cancel</button>
<button class="button button--resolve" @click="resolve()">Ok</button>
</div>
</modal-inner>
</template>
<script>
import giteaProvider from '../../../services/providers/giteaProvider';
import modalTemplate from '../common/modalTemplate';
import utils from '../../../services/utils';
export default modalTemplate({
data: () => ({
branch: '',
path: '',
}),
computedLocalSettings: {
projectUrl: 'giteaProjectUrl',
},
created() {
this.path = `${this.currentFileName}.md`;
},
methods: {
resolve() {
const projectPath = utils.parseGiteaProjectPath(this.projectUrl);
if (!projectPath) {
this.setError('projectUrl');
}
if (!this.path) {
this.setError('path');
}
if (projectPath && this.path) {
const location = giteaProvider.makeLocation(
this.config.token,
projectPath,
this.branch || 'master',
this.path,
);
this.config.resolve(location);
}
},
},
});
</script>

View File

@ -0,0 +1,67 @@
<template>
<modal-inner aria-label="Synchronize with Gitea">
<div class="modal__content">
<div class="modal__image">
<icon-provider provider-id="gitea"></icon-provider>
</div>
<p>Create a workspace synced with a <b>Gitea</b> project folder.</p>
<form-entry label="Project URL" error="projectUrl">
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
<div class="form-entry__info">
<b>Example:</b> {{config.token.serverUrl}}/path/to/project
</div>
</form-entry>
<form-entry label="Folder path" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
<div class="form-entry__info">
If not supplied, the root folder will be used.
</div>
</form-entry>
<form-entry label="Branch" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
<div class="form-entry__info">
If not supplied, the <code>master</code> branch will be used.
</div>
</form-entry>
</div>
<div class="modal__button-bar">
<button class="button" @click="config.reject()">Cancel</button>
<button class="button button--resolve" @click="resolve()">Ok</button>
</div>
</modal-inner>
</template>
<script>
import utils from '../../../services/utils';
import modalTemplate from '../common/modalTemplate';
export default modalTemplate({
data: () => ({
branch: '',
path: '',
}),
computedLocalSettings: {
projectUrl: 'giteaWorkspaceProjectUrl',
},
methods: {
resolve() {
const projectPath = utils.parseGiteaProjectPath(this.projectUrl);
if (!projectPath) {
this.setError('projectUrl');
} else {
const path = this.path && this.path.replace(/^\//, '');
const url = utils.addQueryParams('app', {
providerId: 'giteaWorkspace',
serverUrl: this.config.token.serverUrl,
projectPath,
branch: this.branch || 'master',
path: path || undefined,
sub: this.config.token.sub,
}, true);
this.config.resolve();
window.open(url);
}
},
},
});
</script>

View File

@ -21,11 +21,18 @@ export default () => ({
gistPublishTemplate: 'plainText', gistPublishTemplate: 'plainText',
giteeRepoUrl: '', giteeRepoUrl: '',
giteeWorkspaceRepoUrl: '', giteeWorkspaceRepoUrl: '',
giteePublishTemplate: 'jekyllSite',
gitlabServerUrl: '', gitlabServerUrl: '',
gitlabApplicationId: '', gitlabApplicationId: '',
gitlabProjectUrl: '', gitlabProjectUrl: '',
gitlabWorkspaceProjectUrl: '', gitlabWorkspaceProjectUrl: '',
gitlabPublishTemplate: 'plainText', gitlabPublishTemplate: 'plainText',
giteaServerUrl: '',
giteaApplicationId: '',
giteaApplicationSecret: '',
giteaProjectUrl: '',
giteaWorkspaceProjectUrl: '',
giteaPublishTemplate: 'plainText',
wordpressDomain: '', wordpressDomain: '',
wordpressPublishTemplate: 'plainHtml', wordpressPublishTemplate: 'plainHtml',
zendeskSiteUrl: '', zendeskSiteUrl: '',

View File

@ -77,7 +77,7 @@ turndown:
linkStyle: inlined linkStyle: inlined
linkReferenceStyle: full linkReferenceStyle: full
# GitHub/GitLab commit messages # GitHub/GitLab/Gitee/Gitea commit messages
git: git:
createFileMessage: '{{path}} created from https://edit.qicoder.com/' createFileMessage: '{{path}} created from https://edit.qicoder.com/'
updateFileMessage: '{{path}} updated from https://edit.qicoder.com/' updateFileMessage: '{{path}} updated from https://edit.qicoder.com/'

View File

@ -182,6 +182,11 @@ export default [
'GitLab workspace creator', 'GitLab workspace creator',
'Use the workspace menu to create a GitLab workspace.', 'Use the workspace menu to create a GitLab workspace.',
), ),
new Feature(
'addGiteaWorkspace',
'Gitea workspace creator',
'Use the workspace menu to create a Gitea workspace.',
),
new Feature( new Feature(
'addGoogleDriveWorkspace', 'addGoogleDriveWorkspace',
'Google Drive workspace creator', 'Google Drive workspace creator',
@ -229,6 +234,11 @@ export default [
'GitLab user', 'GitLab user',
'Link your GitLab account to StackEdit.', 'Link your GitLab account to StackEdit.',
), ),
new Feature(
'addGiteaAccount',
'Gitea user',
'Link your Gitea account to StackEdit.',
),
new Feature( new Feature(
'addGoogleDriveAccount', 'addGoogleDriveAccount',
'Google Drive user', 'Google Drive user',
@ -306,6 +316,16 @@ export default [
'GitLab writer', 'GitLab writer',
'Use the "Synchronize" menu to save a file in a GitLab repository.', 'Use the "Synchronize" menu to save a file in a GitLab repository.',
), ),
new Feature(
'openFromGitea',
'Gitea reader',
'Use the "Synchronize" menu to open a file from a Gitea repository.',
),
new Feature(
'saveOnGitea',
'Gitea writer',
'Use the "Synchronize" menu to save a file in a Gitea repository.',
),
new Feature( new Feature(
'openFromGoogleDrive', 'openFromGoogleDrive',
'Google Drive reader', 'Google Drive reader',
@ -373,6 +393,11 @@ export default [
'GitLab publisher', 'GitLab publisher',
'Use the "Publish" menu to publish a file to a GitLab repository.', 'Use the "Publish" menu to publish a file to a GitLab repository.',
), ),
new Feature(
'publishToGitea',
'Gitea publisher',
'Use the "Publish" menu to publish a file to a Gitea repository.',
),
new Feature( new Feature(
'publishToGoogleDrive', 'publishToGoogleDrive',
'Google Drive publisher', 'Google Drive publisher',

View File

@ -22,6 +22,8 @@ export default {
return 'github'; return 'github';
case 'gitlabWorkspace': case 'gitlabWorkspace':
return 'gitlab'; return 'gitlab';
case 'giteaWorkspace':
return 'gitea';
case 'bloggerPage': case 'bloggerPage':
return 'blogger'; return 'blogger';
case 'couchdbWorkspace': case 'couchdbWorkspace':
@ -65,6 +67,10 @@ export default {
background-image: url(../assets/iconGitlab.svg); background-image: url(../assets/iconGitlab.svg);
} }
.icon-provider--gitea {
background-image: url(../assets/iconGitea.svg);
}
.icon-provider--google { .icon-provider--google {
background-image: url(../assets/iconGoogle.svg); background-image: url(../assets/iconGoogle.svg);
} }

View File

@ -0,0 +1,171 @@
import store from '../../store';
import giteaHelper from './helpers/giteaHelper';
import Provider from './common/Provider';
import utils from '../utils';
import workspaceSvc from '../workspaceSvc';
import userSvc from '../userSvc';
const savedSha = {};
export default new Provider({
id: 'gitea',
name: 'Gitea',
getToken({ sub }) {
return store.getters['data/giteaTokensBySub'][sub];
},
getLocationUrl({
sub,
projectPath,
branch,
path,
}) {
const token = this.getToken({ sub });
return `${token.serverUrl}/${projectPath}/src/branch/${encodeURIComponent(branch)}/${utils.encodeUrlPath(path)}`;
},
getLocationDescription({ path }) {
return path;
},
async downloadContent(token, syncLocation) {
const { sha, data } = await giteaHelper.downloadFile({
...syncLocation,
token,
});
savedSha[syncLocation.id] = sha;
return Provider.parseContent(data, `${syncLocation.fileId}/content`);
},
async uploadContent(token, content, syncLocation) {
const updatedSyncLocation = {
...syncLocation,
projectId: await giteaHelper.getProjectId(syncLocation),
};
if (!savedSha[updatedSyncLocation.id]) {
try {
// Get the last sha
await this.downloadContent(token, updatedSyncLocation);
} catch (e) {
// Ignore error
}
}
const sha = savedSha[updatedSyncLocation.id];
delete savedSha[updatedSyncLocation.id];
await giteaHelper.uploadFile({
...updatedSyncLocation,
token,
content: Provider.serializeContent(content),
sha,
});
return updatedSyncLocation;
},
async publish(token, html, metadata, publishLocation) {
const updatedPublishLocation = {
...publishLocation,
projectId: await giteaHelper.getProjectId(publishLocation),
};
try {
// Get the last sha
await this.downloadContent(token, updatedPublishLocation);
} catch (e) {
// Ignore error
}
const sha = savedSha[updatedPublishLocation.id];
delete savedSha[updatedPublishLocation.id];
await giteaHelper.uploadFile({
...updatedPublishLocation,
token,
content: html,
sha,
});
return updatedPublishLocation;
},
async openFile(token, syncLocation) {
const updatedSyncLocation = {
...syncLocation,
projectId: await giteaHelper.getProjectId(syncLocation),
};
// Check if the file exists and open it
if (!Provider.openFileWithLocation(updatedSyncLocation)) {
// Download content from Gitea
let content;
try {
content = await this.downloadContent(token, updatedSyncLocation);
} catch (e) {
store.dispatch('notification/error', `Could not open file ${updatedSyncLocation.path}.`);
return;
}
// Create the file
let name = updatedSyncLocation.path;
const slashPos = name.lastIndexOf('/');
if (slashPos > -1 && slashPos < name.length - 1) {
name = name.slice(slashPos + 1);
}
const dotPos = name.lastIndexOf('.');
if (dotPos > 0 && slashPos < name.length) {
name = name.slice(0, dotPos);
}
const item = await workspaceSvc.createFile({
name,
parentId: store.getters['file/current'].parentId,
text: content.text,
properties: content.properties,
discussions: content.discussions,
comments: content.comments,
}, true);
store.commit('file/setCurrentId', item.id);
workspaceSvc.addSyncLocation({
...updatedSyncLocation,
fileId: item.id,
});
store.dispatch('notification/info', `${store.getters['file/current'].name} was imported from Gitea.`);
}
},
makeLocation(token, projectPath, branch, path) {
return {
providerId: this.id,
sub: token.sub,
projectPath,
branch,
path,
};
},
async listFileRevisions({ token, syncLocation }) {
const entries = await giteaHelper.getCommits({
...syncLocation,
token,
});
return entries.map((entry) => {
const email = entry.author_email || entry.committer_email;
const sub = `${giteaHelper.subPrefix}:${token.serverUrl}/${email}`;
userSvc.addUserInfo({
id: sub,
name: entry.author_name || entry.committer_name,
imageUrl: '',
});
const date = entry.authored_date || entry.committed_date || 1;
return {
id: entry.id,
sub,
created: date ? new Date(date).getTime() : 1,
};
});
},
async loadFileRevision() {
// Revision are already loaded
return false;
},
async getFileRevisionContent({
token,
contentId,
syncLocation,
revisionId,
}) {
const { data } = await giteaHelper.downloadFile({
...syncLocation,
token,
branch: revisionId,
});
return Provider.parseContent(data, contentId);
},
});

View File

@ -0,0 +1,288 @@
import store from '../../store';
import giteaHelper from './helpers/giteaHelper';
import Provider from './common/Provider';
import utils from '../utils';
import userSvc from '../userSvc';
import gitWorkspaceSvc from '../gitWorkspaceSvc';
import badgeSvc from '../badgeSvc';
const getAbsolutePath = ({ id }) =>
`${store.getters['workspace/currentWorkspace'].path || ''}${id}`;
export default new Provider({
id: 'giteaWorkspace',
name: 'Gitea',
getToken() {
return store.getters['workspace/syncToken'];
},
getWorkspaceParams({
serverUrl,
projectPath,
branch,
path,
}) {
return {
providerId: this.id,
serverUrl,
projectPath,
branch,
path,
};
},
getWorkspaceLocationUrl({
serverUrl,
projectPath,
branch,
path,
}) {
return `${serverUrl}/${projectPath}/src/branch/${encodeURIComponent(branch)}/${utils.encodeUrlPath(path)}`;
},
getSyncDataUrl({ id }) {
const { projectPath, branch } = store.getters['workspace/currentWorkspace'];
const { serverUrl } = this.getToken();
return `${serverUrl}/${projectPath}/src/branch/${encodeURIComponent(branch)}/${utils.encodeUrlPath(getAbsolutePath({ id }))}`;
},
getSyncDataDescription({ id }) {
return getAbsolutePath({ id });
},
async initWorkspace() {
const { serverUrl, branch } = utils.queryParams;
const workspaceParams = this.getWorkspaceParams({ serverUrl, branch });
if (!branch) {
workspaceParams.branch = 'master';
}
// Extract project path param
const projectPath = (utils.queryParams.projectPath || '')
.trim()
.replace(/^\/*/, '') // Remove leading `/`
.replace(/\/*$/, ''); // Remove trailing `/`
workspaceParams.projectPath = projectPath;
// Extract path param
const path = (utils.queryParams.path || '')
.trim()
.replace(/^\/*/, '') // Remove leading `/`
.replace(/\/*$/, '/'); // Add trailing `/`
if (path !== '/') {
workspaceParams.path = path;
}
const workspaceId = utils.makeWorkspaceId(workspaceParams);
const workspace = store.getters['workspace/workspacesById'][workspaceId];
// See if we already have a token
const sub = workspace ? workspace.sub : utils.queryParams.sub;
let token = store.getters['data/giteaTokensBySub'][sub];
if (!token) {
const { applicationId, applicationSecret } = await store.dispatch('modal/open', {
type: 'giteaAccount',
forceServerUrl: serverUrl,
});
token = await giteaHelper.addAccount(serverUrl, applicationId, applicationSecret, sub);
}
if (!workspace) {
const projectId = await giteaHelper.getProjectId(workspaceParams);
const pathEntries = (path || '').split('/');
const projectPathEntries = (projectPath || '').split('/');
const name = pathEntries[pathEntries.length - 2] // path ends with `/`
|| projectPathEntries[projectPathEntries.length - 1];
store.dispatch('workspace/patchWorkspacesById', {
[workspaceId]: {
...workspaceParams,
projectId,
id: workspaceId,
sub: token.sub,
name,
},
});
}
badgeSvc.addBadge('addGiteaWorkspace');
return store.getters['workspace/workspacesById'][workspaceId];
},
getChanges() {
return giteaHelper.getTree({
...store.getters['workspace/currentWorkspace'],
token: this.getToken(),
});
},
prepareChanges(tree) {
return gitWorkspaceSvc.makeChanges(tree.tree.map(entry => ({
...entry,
id: entry.sha,
})));
},
async saveWorkspaceItem({ item }) {
const syncData = {
id: store.getters.gitPathsByItemId[item.id],
type: item.type,
hash: item.hash,
};
// Files and folders are not in git, only contents
if (item.type === 'file' || item.type === 'folder') {
return { syncData };
}
// locations are stored as paths, so we upload an empty file
const syncToken = store.getters['workspace/syncToken'];
await giteaHelper.uploadFile({
...store.getters['workspace/currentWorkspace'],
token: syncToken,
path: getAbsolutePath(syncData),
content: '',
sha: gitWorkspaceSvc.shaByPath[syncData.id],
});
// Return sync data to save
return { syncData };
},
async removeWorkspaceItem({ syncData }) {
if (gitWorkspaceSvc.shaByPath[syncData.id]) {
const syncToken = store.getters['workspace/syncToken'];
await giteaHelper.removeFile({
...store.getters['workspace/currentWorkspace'],
token: syncToken,
path: getAbsolutePath(syncData),
sha: gitWorkspaceSvc.shaByPath[syncData.id],
});
}
},
async downloadWorkspaceContent({
token,
contentId,
contentSyncData,
fileSyncData,
}) {
const { sha, data } = await giteaHelper.downloadFile({
...store.getters['workspace/currentWorkspace'],
token,
path: getAbsolutePath(fileSyncData),
});
gitWorkspaceSvc.shaByPath[fileSyncData.id] = sha;
const content = Provider.parseContent(data, contentId);
return {
content,
contentSyncData: {
...contentSyncData,
hash: content.hash,
sha,
},
};
},
async downloadWorkspaceData({ token, syncData }) {
if (!syncData) {
return {};
}
const { sha, data } = await giteaHelper.downloadFile({
...store.getters['workspace/currentWorkspace'],
token,
path: getAbsolutePath(syncData),
});
gitWorkspaceSvc.shaByPath[syncData.id] = sha;
const item = JSON.parse(data);
return {
item,
syncData: {
...syncData,
hash: item.hash,
sha,
},
};
},
async uploadWorkspaceContent({ token, content, file }) {
const path = store.getters.gitPathsByItemId[file.id];
const absolutePath = `${store.getters['workspace/currentWorkspace'].path || ''}${path}`;
const sha = gitWorkspaceSvc.shaByPath[path];
await giteaHelper.uploadFile({
...store.getters['workspace/currentWorkspace'],
token,
path: absolutePath,
content: Provider.serializeContent(content),
sha,
});
// Return new sync data
return {
contentSyncData: {
id: store.getters.gitPathsByItemId[content.id],
type: content.type,
hash: content.hash,
sha,
},
fileSyncData: {
id: path,
type: 'file',
hash: file.hash,
},
};
},
async uploadWorkspaceData({ token, item }) {
const path = store.getters.gitPathsByItemId[item.id];
const syncData = {
id: path,
type: item.type,
hash: item.hash,
};
const res = await giteaHelper.uploadFile({
...store.getters['workspace/currentWorkspace'],
token,
path: getAbsolutePath(syncData),
content: JSON.stringify(item),
sha: gitWorkspaceSvc.shaByPath[path],
});
return {
syncData: {
...syncData,
sha: res.content.sha,
},
};
},
async listFileRevisions({ token, fileSyncDataId }) {
const { projectId, branch } = store.getters['workspace/currentWorkspace'];
const entries = await giteaHelper.getCommits({
token,
projectId,
sha: branch,
path: getAbsolutePath({ id: fileSyncDataId }),
});
return entries.map((entry) => {
const email = entry.author_email || entry.committer_email;
const sub = `${giteaHelper.subPrefix}:${token.serverUrl}/${email}`;
userSvc.addUserInfo({
id: sub,
name: entry.author_name || entry.committer_name,
imageUrl: '', // No way to get user's avatar url...
});
const date = entry.authored_date || entry.committed_date || 1;
return {
id: entry.id,
sub,
created: date ? new Date(date).getTime() : 1,
};
});
},
async loadFileRevision() {
// Revisions are already loaded
return false;
},
async getFileRevisionContent({
token,
contentId,
fileSyncDataId,
revisionId,
}) {
const { data } = await giteaHelper.downloadFile({
...store.getters['workspace/currentWorkspace'],
token,
branch: revisionId,
path: getAbsolutePath({ id: fileSyncDataId }),
});
return Provider.parseContent(data, contentId);
},
});

View File

@ -0,0 +1,222 @@
import utils from '../../utils';
import networkSvc from '../../networkSvc';
import store from '../../../store';
import userSvc from '../../userSvc';
import badgeSvc from '../../badgeSvc';
import constants from '../../../data/constants';
const request = ({ accessToken, serverUrl }, options) => networkSvc.request({
...options,
url: `${serverUrl}/api/v1/${options.url}`,
headers: {
...options.headers || {},
Authorization: `Bearer ${accessToken}`,
},
})
.then(res => res.body);
const getCommitMessage = (name, path) => {
const message = store.getters['data/computedSettings'].git[name];
return message.replace(/{{path}}/g, path);
};
/**
* https://try.gitea.io/api/swagger#/user/userGet
*/
const subPrefix = 'gt';
userSvc.setInfoResolver('gitea', subPrefix, async (sub) => {
try {
const [, serverUrl, username] = sub.match(/^(.+)\/([^/]+)$/);
const user = (await networkSvc.request({
url: `${serverUrl}/api/v1/users/${username}`,
})).body;
const uniqueSub = `${serverUrl}/${user.username}`;
return {
id: `${subPrefix}:${uniqueSub}`,
name: user.username,
imageUrl: user.avatar_url || '',
};
} catch (err) {
if (err.status !== 404) {
throw new Error('RETRY');
}
throw err;
}
});
export default {
subPrefix,
/**
* https://docs.gitea.io/en-us/oauth2-provider/
*/
async startOauth2(serverUrl, applicationId, applicationSecret, sub = null, silent = false) {
// Get an OAuth2 code
const { code } = await networkSvc.startOauth2(
`${serverUrl}/login/oauth/authorize`,
{
client_id: applicationId,
response_type: 'code',
redirect_uri: constants.oauth2RedirectUri,
},
silent,
);
// Exchange code with token
const accessToken = (await networkSvc.request({
method: 'POST',
url: `${serverUrl}/login/oauth/access_token`,
body: {
client_id: applicationId,
client_secret: applicationSecret,
code,
grant_type: 'authorization_code',
redirect_uri: constants.oauth2RedirectUri,
},
})).body.access_token;
// Call the user info endpoint
const user = await request({ accessToken, serverUrl }, {
url: 'user',
});
const uniqueSub = `${serverUrl}/${user.username}`;
userSvc.addUserInfo({
id: `${subPrefix}:${uniqueSub}`,
name: user.username,
imageUrl: user.avatar_url || '',
});
// Check the returned sub consistency
if (sub && uniqueSub !== sub) {
throw new Error('Gitea account ID not expected.');
}
// Build token object including scopes and sub
const token = {
accessToken,
name: user.username,
serverUrl,
sub: uniqueSub,
};
// Add token to gitea tokens
store.dispatch('data/addGiteaToken', token);
return token;
},
async addAccount(serverUrl, applicationId, applicationSecret, sub = null) {
const token = await this.startOauth2(serverUrl, applicationId, applicationSecret, sub);
badgeSvc.addBadge('addGiteaAccount');
return token;
},
/**
* https://try.gitea.io/api/swagger#/repository/repoGet
*/
async getProjectId({ projectPath, projectId }) {
if (projectId) {
return projectId;
}
const [, repoFullName] = projectPath.match(/([^/]+\/[^/]+)$/);
return repoFullName;
},
/**
* https://try.gitea.io/api/swagger#/repository/GetTree
*/
async getTree({
token,
projectId,
branch,
}) {
return request(token, {
url: `repos/${projectId}/git/trees/${branch}`,
params: {
recursive: true,
per_page: 9999,
},
});
},
/**
* https://try.gitea.io/api/swagger#/repository/repoGetAllCommits
*/
async getCommits({
token,
projectId,
branch,
path,
}) {
return request(token, {
url: `repos/${projectId}/commits`,
params: {
sha: branch,
path,
},
});
},
/**
* https://try.gitea.io/api/swagger#/repository/repoCreateFile
* https://try.gitea.io/api/swagger#/repository/repoUpdateFile
*/
async uploadFile({
token,
projectId,
branch,
path,
content,
sha,
}) {
return request(token, {
method: sha ? 'PUT' : 'POST',
url: `repos/${projectId}/contents/${encodeURIComponent(path)}`,
body: {
message: getCommitMessage(sha ? 'updateFileMessage' : 'createFileMessage', path),
content: utils.encodeBase64(content),
sha,
branch,
},
});
},
/**
* https://try.gitea.io/api/swagger#/repository/repoDeleteFile
*/
async removeFile({
token,
projectId,
branch,
path,
sha,
}) {
return request(token, {
method: 'DELETE',
url: `repos/${projectId}/contents/${encodeURIComponent(path)}`,
body: {
message: getCommitMessage('deleteFileMessage', path),
sha,
branch,
},
});
},
/**
* https://try.gitea.io/api/swagger#/repository/repoGetContents
*/
async downloadFile({
token,
projectId,
branch,
path,
}) {
const { sha, content } = await request(token, {
url: `repos/${projectId}/contents/${encodeURIComponent(path)}`,
params: { ref: branch },
});
return {
sha,
data: utils.decodeBase64(content),
};
},
};

View File

@ -3,6 +3,7 @@ import networkSvc from '../../networkSvc';
import store from '../../../store'; import store from '../../../store';
import userSvc from '../../userSvc'; import userSvc from '../../userSvc';
import badgeSvc from '../../badgeSvc'; import badgeSvc from '../../badgeSvc';
import constants from '../../../data/constants';
const request = (token, options) => networkSvc.request({ const request = (token, options) => networkSvc.request({
...options, ...options,
@ -81,6 +82,7 @@ export default {
params: { params: {
clientId, clientId,
code, code,
oauth2RedirectUri: constants.oauth2RedirectUri,
}, },
})).body; })).body;

View File

@ -9,6 +9,7 @@ import './providers/couchdbWorkspaceProvider';
import './providers/githubWorkspaceProvider'; import './providers/githubWorkspaceProvider';
import './providers/giteeWorkspaceProvider'; import './providers/giteeWorkspaceProvider';
import './providers/gitlabWorkspaceProvider'; import './providers/gitlabWorkspaceProvider';
import './providers/giteaWorkspaceProvider';
import './providers/googleDriveWorkspaceProvider'; import './providers/googleDriveWorkspaceProvider';
import tempFileSvc from './tempFileSvc'; import tempFileSvc from './tempFileSvc';
import workspaceSvc from './workspaceSvc'; import workspaceSvc from './workspaceSvc';

View File

@ -296,6 +296,10 @@ export default {
const parsedProject = url && url.match(/^https:\/\/[^/]+\/(.+?)(?:\.git|\/)?$/); const parsedProject = url && url.match(/^https:\/\/[^/]+\/(.+?)(?:\.git|\/)?$/);
return parsedProject && parsedProject[1]; return parsedProject && parsedProject[1];
}, },
parseGiteaProjectPath(url) {
const parsedProject = url && url.match(/^http[s]?:\/\/[^/]+\/(.+?)(?:\.git|\/)?$/);
return parsedProject && parsedProject[1];
},
createHiddenIframe(url) { createHiddenIframe(url) {
const iframeElt = document.createElement('iframe'); const iframeElt = document.createElement('iframe');
iframeElt.style.position = 'absolute'; iframeElt.style.position = 'absolute';

View File

@ -213,6 +213,7 @@ export default {
githubTokensBySub: (state, { tokensByType }) => tokensByType.github || {}, githubTokensBySub: (state, { tokensByType }) => tokensByType.github || {},
giteeTokensBySub: (state, { tokensByType }) => tokensByType.gitee || {}, giteeTokensBySub: (state, { tokensByType }) => tokensByType.gitee || {},
gitlabTokensBySub: (state, { tokensByType }) => tokensByType.gitlab || {}, gitlabTokensBySub: (state, { tokensByType }) => tokensByType.gitlab || {},
giteaTokensBySub: (state, { tokensByType }) => tokensByType.gitea || {},
wordpressTokensBySub: (state, { tokensByType }) => tokensByType.wordpress || {}, wordpressTokensBySub: (state, { tokensByType }) => tokensByType.wordpress || {},
zendeskTokensBySub: (state, { tokensByType }) => tokensByType.zendesk || {}, zendeskTokensBySub: (state, { tokensByType }) => tokensByType.zendesk || {},
badgeCreations: getter('badgeCreations'), badgeCreations: getter('badgeCreations'),
@ -306,6 +307,7 @@ export default {
addGithubToken: tokenAdder('github'), addGithubToken: tokenAdder('github'),
addGiteeToken: tokenAdder('gitee'), addGiteeToken: tokenAdder('gitee'),
addGitlabToken: tokenAdder('gitlab'), addGitlabToken: tokenAdder('gitlab'),
addGiteaToken: tokenAdder('gitea'),
addWordpressToken: tokenAdder('wordpress'), addWordpressToken: tokenAdder('wordpress'),
addZendeskToken: tokenAdder('zendesk'), addZendeskToken: tokenAdder('zendesk'),
patchBadgeCreations: patcher('badgeCreations'), patchBadgeCreations: patcher('badgeCreations'),

View File

@ -45,11 +45,13 @@ export default {
currentWorkspaceIsGit: (state, { currentWorkspace }) => currentWorkspaceIsGit: (state, { currentWorkspace }) =>
currentWorkspace.providerId === 'githubWorkspace' currentWorkspace.providerId === 'githubWorkspace'
|| currentWorkspace.providerId === 'giteeWorkspace' || currentWorkspace.providerId === 'giteeWorkspace'
|| currentWorkspace.providerId === 'gitlabWorkspace', || currentWorkspace.providerId === 'gitlabWorkspace'
|| currentWorkspace.providerId === 'giteaWorkspace',
currentWorkspaceHasUniquePaths: (state, { currentWorkspace }) => currentWorkspaceHasUniquePaths: (state, { currentWorkspace }) =>
currentWorkspace.providerId === 'githubWorkspace' currentWorkspace.providerId === 'githubWorkspace'
|| currentWorkspace.providerId === 'giteeWorkspace' || currentWorkspace.providerId === 'giteeWorkspace'
|| currentWorkspace.providerId === 'gitlabWorkspace', || currentWorkspace.providerId === 'gitlabWorkspace'
|| currentWorkspace.providerId === 'giteaWorkspace',
lastSyncActivityKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastSyncActivity`, lastSyncActivityKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastSyncActivity`,
lastFocusKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastWindowFocus`, lastFocusKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastWindowFocus`,
mainWorkspaceToken: (state, getters, rootState, rootGetters) => mainWorkspaceToken: (state, getters, rootState, rootGetters) =>
@ -69,6 +71,8 @@ export default {
return rootGetters['data/giteeTokensBySub'][currentWorkspace.sub]; return rootGetters['data/giteeTokensBySub'][currentWorkspace.sub];
case 'gitlabWorkspace': case 'gitlabWorkspace':
return rootGetters['data/gitlabTokensBySub'][currentWorkspace.sub]; return rootGetters['data/gitlabTokensBySub'][currentWorkspace.sub];
case 'giteaWorkspace':
return rootGetters['data/giteaTokensBySub'][currentWorkspace.sub];
case 'couchdbWorkspace': case 'couchdbWorkspace':
return rootGetters['data/couchdbTokensBySub'][currentWorkspace.id]; return rootGetters['data/couchdbTokensBySub'][currentWorkspace.id];
default: default:
@ -86,6 +90,8 @@ export default {
return 'gitee'; return 'gitee';
case 'gitlabWorkspace': case 'gitlabWorkspace':
return 'gitlab'; return 'gitlab';
case 'giteaWorkspace':
return 'gitea';
} }
}, },
loginToken: (state, { loginType, currentWorkspace }, rootState, rootGetters) => { loginToken: (state, { loginType, currentWorkspace }, rootState, rootGetters) => {