支持Github图床

This commit is contained in:
xiaoqi.cxq 2022-07-31 02:23:59 +08:00
parent 09416c75a4
commit 1352ea0a9a
5 changed files with 198 additions and 32 deletions

View File

@ -54,6 +54,7 @@ import GithubOpenModal from './modals/providers/GithubOpenModal';
import GithubSaveModal from './modals/providers/GithubSaveModal'; import GithubSaveModal from './modals/providers/GithubSaveModal';
import GithubWorkspaceModal from './modals/providers/GithubWorkspaceModal'; import GithubWorkspaceModal from './modals/providers/GithubWorkspaceModal';
import GithubPublishModal from './modals/providers/GithubPublishModal'; import GithubPublishModal from './modals/providers/GithubPublishModal';
import GithubImgStorageModal from './modals/providers/GithubImgStorageModal';
import GistSyncModal from './modals/providers/GistSyncModal'; import GistSyncModal from './modals/providers/GistSyncModal';
import GistPublishModal from './modals/providers/GistPublishModal'; import GistPublishModal from './modals/providers/GistPublishModal';
import GiteeAccountModal from './modals/providers/GiteeAccountModal'; import GiteeAccountModal from './modals/providers/GiteeAccountModal';
@ -118,6 +119,7 @@ export default {
GithubSaveModal, GithubSaveModal,
GithubWorkspaceModal, GithubWorkspaceModal,
GithubPublishModal, GithubPublishModal,
GithubImgStorageModal,
GistSyncModal, GistSyncModal,
GistPublishModal, GistPublishModal,
GiteeAccountModal, GiteeAccountModal,

View File

@ -30,19 +30,25 @@
<span class="line-entry" v-if="token.params">自定义Form参数{{token.params}}</span> <span class="line-entry" v-if="token.params">自定义Form参数{{token.params}}</span>
</menu-item> </menu-item>
</menu-entry> </menu-entry>
<menu-entry @click.native="checkedImgDest(tokenStorage.sid, 'gitea')" v-for="tokenStorage in giteaTokensImgStorages" :key="tokenStorage.sid"> <menu-entry @click.native="checkedImgDest(tokenStorage.sid, tokenStorage.providerId)" v-for="tokenStorage in tokensImgStorages" :key="tokenStorage.sid">
<icon-check-circle v-if="checkedStorage.sub === tokenStorage.sid" slot="icon"></icon-check-circle> <icon-check-circle v-if="checkedStorage.sub === tokenStorage.sid" slot="icon"></icon-check-circle>
<icon-check-circle-un v-if="checkedStorage.sub !== tokenStorage.sid" slot="icon"></icon-check-circle-un> <icon-check-circle-un v-if="checkedStorage.sub !== tokenStorage.sid" slot="icon"></icon-check-circle-un>
<menu-item> <menu-item>
<icon-provider slot="icon" provider-id="gitea"></icon-provider> <icon-provider slot="icon" :provider-id="tokenStorage.providerId"></icon-provider>
<div>Gitea图床 <div>{{tokenStorage.providerName}}
<button class="menu-item__button button" @click.stop="remove('gitea', tokenStorage)" v-title="'删除'"> <button class="menu-item__button button" @click.stop="remove(tokenStorage.providerId, tokenStorage)" v-title="'删除'">
<icon-delete></icon-delete> <icon-delete></icon-delete>
</button> </button>
</div> </div>
<span> {{tokenStorage.uname}}, 仓库URL: {{tokenStorage.repoUrl}}, 路径: {{tokenStorage.path}}, 分支: {{tokenStorage.branch}}</span> <span> {{tokenStorage.uname}}, 仓库URL: {{tokenStorage.repoUrl}}, 路径: {{tokenStorage.path}}, 分支: {{tokenStorage.branch}}</span>
</menu-item> </menu-item>
</menu-entry> </menu-entry>
</div>
<div class="modal__button-bar">
<button class="button" @click="reject()">取消</button>
<button class="button button--resolve" @click="resolve" :disabled="uploading">确认</button>
</div>
<div>
<menu-entry @click.native="addSmmsAccount"> <menu-entry @click.native="addSmmsAccount">
<icon-provider slot="icon" provider-id="smms"></icon-provider> <icon-provider slot="icon" provider-id="smms"></icon-provider>
<span>添加SM.MS图床账号</span> <span>添加SM.MS图床账号</span>
@ -55,10 +61,10 @@
<icon-provider slot="icon" provider-id="gitea"></icon-provider> <icon-provider slot="icon" provider-id="gitea"></icon-provider>
<span>添加Gitea图床仓库</span> <span>添加Gitea图床仓库</span>
</menu-entry> </menu-entry>
</div> <menu-entry @click.native="addGithubImgStorage">
<div class="modal__button-bar"> <icon-provider slot="icon" provider-id="github"></icon-provider>
<button class="button" @click="reject()">取消</button> <span>添加GitHub图床仓库</span>
<button class="button button--resolve" @click="resolve" :disabled="uploading">确认</button> </menu-entry>
</div> </div>
</modal-inner> </modal-inner>
</template> </template>
@ -70,6 +76,7 @@ import MenuItem from '../menus/common/MenuItem';
import smmsHelper from '../../services/providers/helpers/smmsHelper'; import smmsHelper from '../../services/providers/helpers/smmsHelper';
import store from '../../store'; import store from '../../store';
import giteaHelper from '../../services/providers/helpers/giteaHelper'; import giteaHelper from '../../services/providers/helpers/giteaHelper';
import githubHelper from '../../services/providers/helpers/githubHelper';
import customHelper from '../../services/providers/helpers/customHelper'; import customHelper from '../../services/providers/helpers/customHelper';
import utils from '../../services/utils'; import utils from '../../services/utils';
@ -103,21 +110,34 @@ export default modalTemplate({
})), })),
]; ];
}, },
giteaTokensImgStorages() { tokensImgStorages() {
const giteaTokensBySub = store.getters['data/giteaTokensBySub']; const providerTokens = [
...Object.values(store.getters['data/giteaTokensBySub']).map(token => ({
token,
providerId: 'gitea',
providerName: 'Gitea图床',
})),
...Object.values(store.getters['data/githubTokensBySub']).map(token => ({
token,
providerId: 'github',
providerName: 'GitHub图床',
})),
];
const imgStorages = []; const imgStorages = [];
Object.values(giteaTokensBySub) Object.values(providerTokens)
.sort((token1, token2) => token1.name.localeCompare(token2.name)) .sort((item1, item2) => item1.token.name.localeCompare(item2.token.name))
.forEach((it) => { .forEach((it) => {
if (!it.imgStorages || it.imgStorages.length === 0) { if (!it.token.imgStorages || it.token.imgStorages.length === 0) {
return; return;
} }
// //
it.imgStorages.forEach(storage => imgStorages.push({ it.token.imgStorages.forEach(storage => imgStorages.push({
...storage, ...storage,
token: it, token: it.token,
uname: it.name, uname: it.token.name,
repoUrl: `${it.serverUrl}/${storage.repoUri}`, providerId: it.providerId,
providerName: it.providerName,
repoUrl: it.providerId === 'gitea' ? `${it.serverUrl}/${storage.repoUri}` : `${storage.owner}/${storage.repo}`,
})); }));
}); });
return imgStorages; return imgStorages;
@ -153,8 +173,8 @@ export default modalTemplate({
} catch (err) { } catch (err) {
store.dispatch('notification/error', err); store.dispatch('notification/error', err);
} }
} else if (currStorage.provider === 'gitea') { } else if (currStorage.provider === 'gitea' || currStorage.provider === 'github') {
const filterTokenStorages = this.giteaTokensImgStorages const filterTokenStorages = this.tokensImgStorages
.filter(it => it.sid === currStorage.sub); .filter(it => it.sid === currStorage.sub);
if (!filterTokenStorages.length) { if (!filterTokenStorages.length) {
store.dispatch('notification/info', 'Gitea图床已失效未自动上传图片请选择图床后重新粘贴/拖拽图片!'); store.dispatch('notification/info', 'Gitea图床已失效未自动上传图片请选择图床后重新粘贴/拖拽图片!');
@ -169,15 +189,28 @@ export default modalTemplate({
.replace('{MM}', `0${month}`.slice(-2)).replace('{DD}', `0${date}`.slice(-2)); .replace('{MM}', `0${month}`.slice(-2)).replace('{DD}', `0${date}`.slice(-2));
path = `${path}${path.endsWith('/') ? '' : '/'}${utils.uid()}.${imgFile.type.split('/')[1]}`; path = `${path}${path.endsWith('/') ? '' : '/'}${utils.uid()}.${imgFile.type.split('/')[1]}`;
try { try {
const result = await giteaHelper.uploadFile({ if (currStorage.provider === 'gitea') {
token: tokenStorage.token, const result = await giteaHelper.uploadFile({
projectId: tokenStorage.repoUri, token: tokenStorage.token,
branch: tokenStorage.branch, projectId: tokenStorage.repoUri,
path, branch: tokenStorage.branch,
content: imgFile, path,
isFile: true, content: imgFile,
}); isFile: true,
this.url = result.content.download_url; });
this.url = result.content.download_url;
} else if (currStorage.provider === 'github') {
const result = await githubHelper.uploadFile({
token: tokenStorage.token,
owner: tokenStorage.owner,
repo: tokenStorage.repo,
branch: tokenStorage.branch,
path,
content: imgFile,
isFile: true,
});
this.url = result.content.download_url;
}
} catch (err) { } catch (err) {
store.dispatch('notification/error', err); store.dispatch('notification/error', err);
} }
@ -218,6 +251,8 @@ export default modalTemplate({
}); });
} else if (proivderId === 'gitea') { } else if (proivderId === 'gitea') {
giteaHelper.removeTokenImgStorage(item.token, item.sid); giteaHelper.removeTokenImgStorage(item.token, item.sid);
} else if (proivderId === 'github') {
githubHelper.removeTokenImgStorage(item.token, item.sid);
} }
} catch (e) { } catch (e) {
// Cancel // Cancel
@ -242,9 +277,20 @@ export default modalTemplate({
giteaHelper.updateToken(token, imgStorageInfo); giteaHelper.updateToken(token, imgStorageInfo);
} catch (e) { /* Cancel */ } } catch (e) { /* Cancel */ }
}, },
async addGithubImgStorage() {
try {
await store.dispatch('modal/open', { type: 'githubAccount' });
const token = await githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess);
const imgStorageInfo = await store.dispatch('modal/open', {
type: 'githubImgStorage',
token,
});
githubHelper.updateToken(token, imgStorageInfo);
} catch (e) { /* Cancel */ }
},
async checkedImgDest(sub, provider) { async checkedImgDest(sub, provider) {
let type = 'token'; let type = 'token';
if (provider === 'gitea') { if (provider === 'gitea' || provider === 'github') {
type = 'tokenRepo'; type = 'tokenRepo';
} }
store.dispatch('img/changeCheckedStorage', { store.dispatch('img/changeCheckedStorage', {

View File

@ -0,0 +1,77 @@
<template>
<modal-inner aria-label="GitHub 公开仓库图床">
<div class="modal__content">
<div class="modal__image">
<icon-provider provider-id="github"></icon-provider>
</div>
<p>创建一个用于存储图片的<b> GitHub </b>公开仓库文件夹图床</p>
<form-entry label="公开仓库URL" error="repoUrl">
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
<div class="form-entry__info">
<b>例如:</b> https://github.com/owner/my-repo
</div>
</form-entry>
<form-entry label="文件夹路径" info="可选的">
<input slot="field" class="textfield" type="text" placeholder="如imgs/{YYYY}/{MM}" v-model.trim="path" @keydown.enter="resolve()">
<div class="form-entry__info">
如果不提供默认为 {YYYY}/{MM}/{DD} 其中{YYYY}为年变量{MM}为月变量{DD}为日变量
</div>
</form-entry>
<form-entry label="分支" info="可选的">
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
<div class="form-entry__info">
如果未提供将使用<code> master </code>分支
</div>
</form-entry>
</div>
<div class="modal__button-bar">
<button class="button" @click="config.reject()">取消</button>
<button class="button button--resolve" @click="resolve()">确认</button>
</div>
</modal-inner>
</template>
<script>
import utils from '../../../services/utils';
import modalTemplate from '../common/modalTemplate';
import githubHelper from '../../../services/providers/helpers/githubHelper';
import store from '../../../store';
export default modalTemplate({
data: () => ({
branch: '',
path: '',
}),
computedLocalSettings: {
repoUrl: 'githubImgStorageRepoUrl',
},
methods: {
async resolve() {
const { owner, repo } = utils.parseGithubRepoUrl(this.repoUrl);
if (!owner && !repo) {
this.setError('repoUrl');
} else {
// URL
try {
const repoInfo = await githubHelper.getRepoInfo(this.config.token, owner, repo);
if (repoInfo.private) {
store.dispatch('notification/error', '作为图床的仓库URL必须是公开仓库');
this.setError('repoUrl');
return;
}
const path = this.path && this.path.replace(/^\//, '');
this.config.resolve({
owner,
repo,
path: path || '{YYYY}/{MM}/{DD}',
branch: this.branch || 'master',
});
} catch (err) {
store.dispatch('notification/error', err);
this.setError('repoUrl');
}
}
},
},
});
</script>

View File

@ -58,7 +58,6 @@ export default {
sub = null, silent = false, refreshToken, sub = null, silent = false, refreshToken,
) { ) {
let tokenBody; let tokenBody;
const imgStorages = refreshToken && refreshToken.imgStorages;
if (!silent) { if (!silent) {
// Get an OAuth2 code // Get an OAuth2 code
const { code } = await networkSvc.startOauth2( const { code } = await networkSvc.startOauth2(
@ -115,13 +114,14 @@ export default {
throw new Error('Gitea account ID not expected.'); throw new Error('Gitea account ID not expected.');
} }
const oldToken = store.getters['data/giteaTokensBySub'][uniqueSub];
// Build token object including scopes and sub // Build token object including scopes and sub
const token = { const token = {
accessToken, accessToken,
name: user.username, name: user.username,
applicationId, applicationId,
applicationSecret, applicationSecret,
imgStorages, imgStorages: oldToken && oldToken.imgStorages,
refreshToken: tokenBody.refresh_token, refreshToken: tokenBody.refresh_token,
expiresOn: Date.now() + (tokenBody.expires_in * 1000), expiresOn: Date.now() + (tokenBody.expires_in * 1000),
serverUrl, serverUrl,

View File

@ -104,12 +104,14 @@ export default {
throw new Error('GitHub account ID not expected.'); throw new Error('GitHub account ID not expected.');
} }
const oldToken = store.getters['data/githubTokensBySub'][user.id];
// Build token object including scopes and sub // Build token object including scopes and sub
const token = { const token = {
scopes, scopes,
accessToken, accessToken,
name: user.login, name: user.login,
sub: `${user.id}`, sub: `${user.id}`,
imgStorages: oldToken && oldToken.imgStorages,
repoFullAccess: scopes.includes('repo'), repoFullAccess: scopes.includes('repo'),
}; };
@ -173,13 +175,14 @@ export default {
path, path,
content, content,
sha, sha,
isFile,
}) { }) {
return repoRequest(token, owner, repo, { return repoRequest(token, owner, repo, {
method: 'PUT', method: 'PUT',
url: `contents/${encodeURIComponent(path)}`, url: `contents/${encodeURIComponent(path)}`,
body: { body: {
message: getCommitMessage(sha ? 'updateFileMessage' : 'createFileMessage', path), message: getCommitMessage(sha ? 'updateFileMessage' : 'createFileMessage', path),
content: utils.encodeBase64(content), content: isFile ? await utils.encodeFiletoBase64(content) : utils.encodeBase64(content),
sha, sha,
branch, branch,
}, },
@ -227,6 +230,44 @@ export default {
data: utils.decodeBase64(content), data: utils.decodeBase64(content),
}; };
}, },
/**
* 获取仓库信息
*/
async getRepoInfo(token, owner, repo) {
return request(token, {
url: `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,
}).then(res => res.body);
},
async updateToken(token, imgStorageInfo) {
const imgStorages = token.imgStorages || [];
// 存储仓库唯一标识
const sid = utils.hash(`${imgStorageInfo.owner}${imgStorageInfo.repo}${imgStorageInfo.path}${imgStorageInfo.branch}`);
// 查询是否存在 存在则更新
const filterStorages = imgStorages.filter(it => it.sid === sid);
if (filterStorages && filterStorages.length > 0) {
filterStorages.owner = imgStorageInfo.owner;
filterStorages.repo = imgStorageInfo.repo;
filterStorages.path = imgStorageInfo.path;
filterStorages.branch = imgStorageInfo.branch;
} else {
imgStorages.push({
sid,
owner: imgStorageInfo.owner,
repo: imgStorageInfo.repo,
path: imgStorageInfo.path,
branch: imgStorageInfo.branch,
});
token.imgStorages = imgStorages;
}
store.dispatch('data/addGithubToken', token);
},
async removeTokenImgStorage(token, sid) {
if (!token.imgStorages || token.imgStorages.length === 0) {
return;
}
token.imgStorages = token.imgStorages.filter(it => it.sid !== sid);
store.dispatch('data/addGithubToken', token);
},
/** /**
* https://developer.github.com/v3/gists/#create-a-gist * https://developer.github.com/v3/gists/#create-a-gist