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, {
NODE_ENV: '"development"',
// 以下配置是开发临时用的配置 随时可能失效 请替换为自己的
GITHUB_CLIENT_ID: '"845b8f75df48f2ee0563"',
GITHUB_CLIENT_SECRET: '"80df676597abded1450926861965cc3f9bead6a0"',
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 conf = require('./conf');
function giteeToken(clientId, code) {
console.log('clientId: ' + clientId);
console.log('code: ' + code);
console.log('client_secret: ' + conf.values.giteeClientSecret);
console.log('redirect_uri: ' + conf.values.giteeCallback);
function giteeToken(clientId, code, oauth2RedirectUri) {
return new Promise((resolve, reject) => {
request({
method: 'POST',
@ -17,7 +13,7 @@ function giteeToken(clientId, code) {
code,
grant_type: 'authorization_code',
scope: 'authorization_code',
redirect_uri: conf.values.giteeCallback,
redirect_uri: oauth2RedirectUri,
},
json: true
}, (err, res, body) => {
@ -35,7 +31,7 @@ function giteeToken(clientId, code) {
}
exports.giteeToken = (req, res) => {
giteeToken(req.query.clientId, req.query.code)
giteeToken(req.query.clientId, req.query.code, req.query.oauth2RedirectUri)
.then(
token => res.send(token),
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 GitlabSaveModal from './modals/providers/GitlabSaveModal';
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 BloggerPublishModal from './modals/providers/BloggerPublishModal';
import BloggerPagePublishModal from './modals/providers/BloggerPagePublishModal';
@ -122,6 +127,11 @@ export default {
GitlabPublishModal,
GitlabSaveModal,
GitlabWorkspaceModal,
GiteaAccountModal,
GiteaOpenModal,
GiteaPublishModal,
GiteaSaveModal,
GiteaWorkspaceModal,
WordpressPublishModal,
BloggerPublishModal,
BloggerPagePublishModal,

View File

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

View File

@ -66,6 +66,13 @@
<span>{{token.name}}</span>
</menu-entry>
</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">
<menu-entry @click.native="publishGoogleDrive(token)">
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
@ -108,6 +115,10 @@
<icon-provider slot="icon" provider-id="gitlab"></icon-provider>
<span>Add GitLab account</span>
</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">
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
<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 giteeHelper from '../../services/providers/helpers/giteeHelper';
import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
import giteaHelper from '../../services/providers/helpers/giteaHelper';
import wordpressHelper from '../../services/providers/helpers/wordpressHelper';
import zendeskHelper from '../../services/providers/helpers/zendeskHelper';
import publishSvc from '../../services/publishSvc';
@ -186,6 +198,9 @@ export default {
gitlabTokens() {
return tokensToArray(store.getters['data/gitlabTokensBySub']);
},
giteaTokens() {
return tokensToArray(store.getters['data/giteaTokensBySub']);
},
googleDriveTokens() {
return tokensToArray(store.getters['data/googleTokensBySub'], token => token.isDrive);
},
@ -199,7 +214,9 @@ export default {
return !this.bloggerTokens.length
&& !this.dropboxTokens.length
&& !this.githubTokens.length
&& !this.giteeTokens.length
&& !this.gitlabTokens.length
&& !this.giteaTokens.length
&& !this.googleDriveTokens.length
&& !this.wordpressTokens.length
&& !this.zendeskTokens.length;
@ -245,6 +262,12 @@ export default {
await gitlabHelper.addAccount(serverUrl, applicationId);
} 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() {
try {
await store.dispatch('modal/open', { type: 'googleDriveAccount' });
@ -269,6 +292,7 @@ export default {
publishGitee: publishModalOpener('giteePublish', 'publishToGitee'),
publishGist: publishModalOpener('gistPublish', 'publishToGist'),
publishGitlab: publishModalOpener('gitlabPublish', 'publishToGitlab'),
publishGitea: publishModalOpener('giteaPublish', 'publishToGitea'),
publishGoogleDrive: publishModalOpener('googleDrivePublish', 'publishToGoogleDrive'),
publishWordpress: publishModalOpener('wordpressPublish', 'publishToWordPress'),
publishZendesk: publishModalOpener('zendeskPublish', 'publishToZendesk'),

View File

@ -74,6 +74,18 @@
<span>{{token.name}}</span>
</menu-entry>
</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">
<menu-entry @click.native="openGoogleDrive(token)">
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
@ -103,6 +115,10 @@
<icon-provider slot="icon" provider-id="gitlab"></icon-provider>
<span>Add GitLab account</span>
</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">
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
<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 giteeHelper from '../../services/providers/helpers/giteeHelper';
import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
import giteaHelper from '../../services/providers/helpers/giteaHelper';
import googleDriveProvider from '../../services/providers/googleDriveProvider';
import dropboxProvider from '../../services/providers/dropboxProvider';
import githubProvider from '../../services/providers/githubProvider';
import giteeProvider from '../../services/providers/giteeProvider';
import gitlabProvider from '../../services/providers/gitlabProvider';
import giteaProvider from '../../services/providers/giteaProvider';
import syncSvc from '../../services/syncSvc';
import store from '../../store';
import badgeSvc from '../../services/badgeSvc';
@ -172,6 +190,9 @@ export default {
gitlabTokens() {
return tokensToArray(store.getters['data/gitlabTokensBySub']);
},
giteaTokens() {
return tokensToArray(store.getters['data/giteaTokensBySub']);
},
googleDriveTokens() {
return tokensToArray(store.getters['data/googleTokensBySub'], token => token.isDrive);
},
@ -217,6 +238,12 @@ export default {
await gitlabHelper.addAccount(serverUrl, applicationId);
} 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() {
try {
await store.dispatch('modal/open', { type: 'googleDriveAccount' });
@ -318,12 +345,33 @@ export default {
);
} 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) {
try {
await openSyncModal(token, 'gitlabSave');
badgeSvc.addBadge('saveOnGitlab');
} catch (e) { /* cancel */ }
},
async saveGitea(token) {
try {
await openSyncModal(token, 'giteaSave');
badgeSvc.addBadge('saveOnGitea');
} catch (e) { /* cancel */ }
},
},
};
</script>

View File

@ -29,6 +29,10 @@
<icon-provider slot="icon" provider-id="gitlabWorkspace"></icon-provider>
<span>Add a <b>GitLab</b> workspace</span>
</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">
<icon-provider slot="icon" provider-id="googleDriveWorkspace"></icon-provider>
<span>Add a <b>Google Drive</b> workspace</span>
@ -41,6 +45,7 @@ import { mapGetters } from 'vuex';
import MenuEntry from './common/MenuEntry';
import googleHelper from '../../services/providers/helpers/googleHelper';
import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
import giteaHelper from '../../services/providers/helpers/giteaHelper';
import store from '../../store';
export default {
@ -88,6 +93,16 @@ export default {
});
} 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() {
try {
const token = await googleHelper.addDriveAccount(true);

View File

@ -57,6 +57,10 @@
<icon-provider slot="icon" provider-id="gitlab"></icon-provider>
<span>Add GitLab account</span>
</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">
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
<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 giteeHelper from '../../services/providers/helpers/giteeHelper';
import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
import giteaHelper from '../../services/providers/helpers/giteaHelper';
import wordpressHelper from '../../services/providers/helpers/wordpressHelper';
import zendeskHelper from '../../services/providers/helpers/zendeskHelper';
import badgeSvc from '../../services/badgeSvc';
@ -148,6 +153,14 @@ export default {
name: token.name,
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 => ({
token,
providerId: 'wordpress',
@ -204,6 +217,12 @@ export default {
await gitlabHelper.addAccount(serverUrl, applicationId);
} 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() {
try {
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',
giteeRepoUrl: '',
giteeWorkspaceRepoUrl: '',
giteePublishTemplate: 'jekyllSite',
gitlabServerUrl: '',
gitlabApplicationId: '',
gitlabProjectUrl: '',
gitlabWorkspaceProjectUrl: '',
gitlabPublishTemplate: 'plainText',
giteaServerUrl: '',
giteaApplicationId: '',
giteaApplicationSecret: '',
giteaProjectUrl: '',
giteaWorkspaceProjectUrl: '',
giteaPublishTemplate: 'plainText',
wordpressDomain: '',
wordpressPublishTemplate: 'plainHtml',
zendeskSiteUrl: '',

View File

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

View File

@ -182,6 +182,11 @@ export default [
'GitLab workspace creator',
'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(
'addGoogleDriveWorkspace',
'Google Drive workspace creator',
@ -229,6 +234,11 @@ export default [
'GitLab user',
'Link your GitLab account to StackEdit.',
),
new Feature(
'addGiteaAccount',
'Gitea user',
'Link your Gitea account to StackEdit.',
),
new Feature(
'addGoogleDriveAccount',
'Google Drive user',
@ -306,6 +316,16 @@ export default [
'GitLab writer',
'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(
'openFromGoogleDrive',
'Google Drive reader',
@ -373,6 +393,11 @@ export default [
'GitLab publisher',
'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(
'publishToGoogleDrive',
'Google Drive publisher',

View File

@ -22,6 +22,8 @@ export default {
return 'github';
case 'gitlabWorkspace':
return 'gitlab';
case 'giteaWorkspace':
return 'gitea';
case 'bloggerPage':
return 'blogger';
case 'couchdbWorkspace':
@ -65,6 +67,10 @@ export default {
background-image: url(../assets/iconGitlab.svg);
}
.icon-provider--gitea {
background-image: url(../assets/iconGitea.svg);
}
.icon-provider--google {
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 userSvc from '../../userSvc';
import badgeSvc from '../../badgeSvc';
import constants from '../../../data/constants';
const request = (token, options) => networkSvc.request({
...options,
@ -81,6 +82,7 @@ export default {
params: {
clientId,
code,
oauth2RedirectUri: constants.oauth2RedirectUri,
},
})).body;

View File

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

View File

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

View File

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

View File

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