图床支持gitea图床

This commit is contained in:
xiaoqi.cxq 2022-07-02 13:09:24 +08:00
parent 698ddb9abd
commit 070206cb5a
13 changed files with 381 additions and 40 deletions

View File

@ -5,7 +5,9 @@ WORKDIR /opt/stackedit
COPY package*json /opt/stackedit/ COPY package*json /opt/stackedit/
COPY gulpfile.js /opt/stackedit/ COPY gulpfile.js /opt/stackedit/
RUN npm install --unsafe-perm \
ENV NODE_TLS_REJECT_UNAUTHORIZED=0
RUN git config --global http.sslVerify false && npm set strict-ssl false && npm install --unsafe-perm \
&& npm cache clean --force && npm cache clean --force
COPY . /opt/stackedit COPY . /opt/stackedit
ENV NODE_ENV production ENV NODE_ENV production

View File

@ -71,6 +71,7 @@ import GiteaOpenModal from './modals/providers/GiteaOpenModal';
import GiteaPublishModal from './modals/providers/GiteaPublishModal'; import GiteaPublishModal from './modals/providers/GiteaPublishModal';
import GiteaSaveModal from './modals/providers/GiteaSaveModal'; import GiteaSaveModal from './modals/providers/GiteaSaveModal';
import GiteaWorkspaceModal from './modals/providers/GiteaWorkspaceModal'; import GiteaWorkspaceModal from './modals/providers/GiteaWorkspaceModal';
import GiteaImgStorageModal from './modals/providers/GiteaImgStorageModal';
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';
@ -133,6 +134,7 @@ export default {
GiteaPublishModal, GiteaPublishModal,
GiteaSaveModal, GiteaSaveModal,
GiteaWorkspaceModal, GiteaWorkspaceModal,
GiteaImgStorageModal,
WordpressPublishModal, WordpressPublishModal,
BloggerPublishModal, BloggerPublishModal,
BloggerPagePublishModal, BloggerPagePublishModal,

View File

@ -0,0 +1,93 @@
<template>
<div class="menu-item flex flex--row flex--align-center">
<div class="menu-item__icon flex flex--column flex--center">
<slot name="icon"></slot>
</div>
<div class="menu-item__text flex flex--column">
<slot></slot>
</div>
</div>
</template>
<style lang="scss">
@import '../../../styles/variables.scss';
.menu-item {
text-align: left;
padding: 10px;
height: auto;
font-size: 17px;
line-height: 1.4;
text-transform: none;
white-space: normal;
span {
display: inline-block;
font-size: 0.75rem;
opacity: 0.67;
line-height: 1.3;
.menu-item__label {
opacity: 1;
}
span {
display: inline;
opacity: 1;
}
}
}
.menu-item--info {
padding-top: 3px;
padding-bottom: 3px;
}
.menu-item__icon {
height: 20px;
width: 20px;
margin-right: 12px;
flex: none;
}
.menu-item__icon--disabled {
opacity: 0.5;
}
.menu-item__icon--image {
border-radius: $border-radius-base;
overflow: hidden;
}
.hidden-file {
position: fixed;
top: -999px;
}
.menu-item__label {
float: right;
font-size: 0.6rem;
font-weight: 600;
line-height: 1;
padding: 0.15em 0.25em;
background-color: #fff;
border-radius: 3px;
opacity: 0.6;
}
.menu-item__label--warning {
color: #fff;
background-color: darken($error-color, 10);
opacity: 1;
}
.menu-item__label--count {
font-size: 0.75rem;
font-weight: 400;
}
.menu-item__text {
width: 100%;
overflow: hidden;
}
</style>

View File

@ -6,30 +6,52 @@
<span v-if="!this.uploading && url"> <span v-if="!this.uploading && url">
<img :src="url"> <img :src="url">
</span> </span>
<span v-if="!this.uploading && !url">图片上传失败如未添加SM.MS账号请先添加并选择之后关闭窗口再重试</span> <span v-if="!this.uploading && !url">图片上传失败如未添加图床请先添加并选择之后关闭窗口再重试</span>
</p> </p>
<p v-if="!hasFile">请为您的图像提供<b> url </b></p> <p v-if="!hasFile">请为您的图像提供<b> url </b></p>
<form-entry v-if="!hasFile" label="URL" error="url"> <form-entry v-if="!hasFile" label="URL" error="url">
<input slot="field" class="textfield" type="text" v-model.trim="url" @keydown.enter="resolve"> <input slot="field" class="textfield" type="text" v-model.trim="url" @keydown.enter="resolve">
</form-entry> </form-entry>
<p>添加并选择图床后可实现粘贴/拖拽自动上传图片</p> <p>添加并选择图床后可实现粘贴/拖拽自动上传图片</p>
<menu-entry @click.native="checkedImgDest(token, 'smms')" v-for="token in smmsTokens" :key="token.sub"> <menu-entry @click.native="checkedImgDest(token.sub, 'smms')" v-for="token in smmsTokens" :key="token.sub">
<icon-check-circle v-if="checkedStorage.sub === token.sub" slot="icon"></icon-check-circle> <icon-check-circle v-if="checkedStorage.sub === token.sub" slot="icon"></icon-check-circle>
<icon-check-circle-un v-if="checkedStorage.sub !== token.sub" slot="icon"></icon-check-circle-un> <icon-check-circle-un v-if="checkedStorage.sub !== token.sub" slot="icon"></icon-check-circle-un>
<menu-entry> <menu-item>
<icon-provider slot="icon" provider-id="smms"></icon-provider> <icon-provider slot="icon" provider-id="smms"></icon-provider>
<div>SM.MS图床</div> <div>
SM.MS图床
<button class="menu-item__button button" @click.stop="remove('smms', token)" v-title="'删除'">
<icon-delete></icon-delete>
</button>
</div>
<span>{{token.name}}</span> <span>{{token.name}}</span>
</menu-entry> </menu-item>
</menu-entry>
<menu-entry @click.native="checkedImgDest(tokenStorage.sid, 'gitea')" v-for="tokenStorage in giteaTokensImgStorages" :key="tokenStorage.sid">
<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>
<menu-item>
<icon-provider slot="icon" provider-id="gitea"></icon-provider>
<div>Gitea图床
<button class="menu-item__button button" @click.stop="remove('gitea', tokenStorage)" v-title="'删除'">
<icon-delete></icon-delete>
</button>
</div>
<span> {{tokenStorage.uname}}, 仓库URL: {{tokenStorage.repoUrl}}, 路径: {{tokenStorage.path}}, 分支: {{tokenStorage.branch}}</span>
</menu-item>
</menu-entry> </menu-entry>
<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>
</menu-entry>
<menu-entry @click.native="addGiteaImgStorage">
<icon-provider slot="icon" provider-id="gitea"></icon-provider>
<span>添加Gitea图床仓库</span>
</menu-entry> </menu-entry>
</div> </div>
<div class="modal__button-bar"> <div class="modal__button-bar">
<button class="button" @click="reject()">取消</button> <button class="button" @click="reject()">取消</button>
<button class="button button--resolve" @click="resolve">确认</button> <button class="button button--resolve" @click="resolve" :disabled="uploading">确认</button>
</div> </div>
</modal-inner> </modal-inner>
</template> </template>
@ -37,12 +59,16 @@
<script> <script>
import modalTemplate from './common/modalTemplate'; import modalTemplate from './common/modalTemplate';
import MenuEntry from '../menus/common/MenuEntry'; import MenuEntry from '../menus/common/MenuEntry';
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 utils from '../../services/utils';
export default modalTemplate({ export default modalTemplate({
components: { components: {
MenuEntry, MenuEntry,
MenuItem,
}, },
data: () => ({ data: () => ({
hasFile: false, hasFile: false,
@ -58,6 +84,25 @@ export default modalTemplate({
return Object.values(smmsTokensBySub) return Object.values(smmsTokensBySub)
.sort((token1, token2) => token1.name.localeCompare(token2.name)); .sort((token1, token2) => token1.name.localeCompare(token2.name));
}, },
giteaTokensImgStorages() {
const giteaTokensBySub = store.getters['data/giteaTokensBySub'];
const imgStorages = [];
Object.values(giteaTokensBySub)
.sort((token1, token2) => token1.name.localeCompare(token2.name))
.forEach((it) => {
if (!it.imgStorages || it.imgStorages.length === 0) {
return;
}
//
it.imgStorages.forEach(storage => imgStorages.push({
...storage,
token: it,
uname: it.name,
repoUrl: `${it.serverUrl}/${storage.repoUri}`,
}));
});
return imgStorages;
},
}, },
async mounted() { async mounted() {
this.hasFile = false; this.hasFile = false;
@ -69,20 +114,52 @@ export default modalTemplate({
// //
// provider smms // provider smms
const currStorage = this.checkedStorage; const currStorage = this.checkedStorage;
if (!currStorage || !currStorage.sub) { if (!currStorage) {
store.dispatch('notification/info', '暂无已选择的图床,未自动上传图片!请选择图床后重新粘贴/拖拽图片!'); store.dispatch('notification/info', '暂无已选择的图床,未自动上传图片!请选择图床后重新粘贴/拖拽图片!');
return; return;
} }
const filterTokens = this.smmsTokens.filter(it => it.sub === currStorage.sub); if (currStorage.provider === 'smms') {
if (!filterTokens.length) { const filterTokens = this.smmsTokens.filter(it => it.sub === currStorage.sub);
if (!filterTokens.length) {
store.dispatch('notification/info', 'SMS图床已失效未自动上传图片请选择图床后重新粘贴/拖拽图片!');
return;
}
const token = filterTokens[0];
this.url = await smmsHelper.uploadFile({
token,
file: imgFile,
});
} else if (currStorage.provider === 'gitea') {
const filterTokenStorages = this.giteaTokensImgStorages
.filter(it => it.sid === currStorage.sub);
if (!filterTokenStorages.length) {
store.dispatch('notification/info', 'Gitea图床已失效未自动上传图片请选择图床后重新粘贴/拖拽图片!');
return;
}
const tokenStorage = filterTokenStorages[0];
const time = new Date();
const date = time.getDate();
const month = time.getMonth();
const year = time.getFullYear();
let path = tokenStorage.path.replace('{YYYY}', year)
.replace('{MM}', `0${month}`.slice(-2)).replace('{DD}', `0${date}`.slice(-2));
path = `${path}${path.endsWith('/') ? '' : '/'}${utils.uid()}.${imgFile.type.split('/')[1]}`;
try {
const result = await giteaHelper.uploadFile({
token: tokenStorage.token,
projectId: tokenStorage.repoUri,
branch: tokenStorage.branch,
path,
content: imgFile,
isFile: true,
});
this.url = result.content.download_url;
} catch (err) {
store.dispatch('notification/error', err);
}
} else {
store.dispatch('notification/info', '暂无已选择的图床,未自动上传图片!请选择图床后重新粘贴/拖拽图片!'); store.dispatch('notification/info', '暂无已选择的图床,未自动上传图片!请选择图床后重新粘贴/拖拽图片!');
return;
} }
const token = filterTokens[0];
this.url = await smmsHelper.uploadFile({
token,
file: imgFile,
});
} finally { } finally {
store.dispatch('img/clearImg'); store.dispatch('img/clearImg');
this.uploading = false; this.uploading = false;
@ -105,15 +182,47 @@ export default modalTemplate({
this.config.reject(); this.config.reject();
callback(null); callback(null);
}, },
async remove(proivderId, item) {
try {
await store.dispatch('modal/open', 'imgStorageDeletion');
if (proivderId === 'smms') {
const tokensBySub = utils.deepCopy(store.getters[`data/${proivderId}TokensBySub`]);
delete tokensBySub[item.sub];
//
await store.dispatch('data/patchTokensByType', {
[proivderId]: tokensBySub,
});
} else if (proivderId === 'gitea') {
giteaHelper.removeTokenImgStorage(item.token, item.sid);
}
} catch (e) {
// Cancel
}
},
async addSmmsAccount() { async addSmmsAccount() {
const { proxyUrl, apiSecretToken } = await store.dispatch('modal/open', { type: 'smmsAccount' }); const { proxyUrl, apiSecretToken } = await store.dispatch('modal/open', { type: 'smmsAccount' });
await smmsHelper.addAccount(proxyUrl, apiSecretToken); await smmsHelper.addAccount(proxyUrl, apiSecretToken);
}, },
async checkedImgDest(token, provider) { async addGiteaImgStorage() {
try {
const { serverUrl, applicationId, applicationSecret } = await store.dispatch('modal/open', { type: 'giteaAccount' });
const token = await giteaHelper.addAccount(serverUrl, applicationId, applicationSecret);
const imgStorageInfo = await store.dispatch('modal/open', {
type: 'giteaImgStorage',
token,
});
giteaHelper.updateToken(token, imgStorageInfo);
} catch (e) { /* Cancel */ }
},
async checkedImgDest(sub, provider) {
let type = 'token';
if (provider === 'gitea') {
type = 'tokenRepo';
}
store.dispatch('img/changeCheckedStorage', { store.dispatch('img/changeCheckedStorage', {
type: 'token', type,
provider, provider,
sub: token.sub, sub,
}); });
// const { callback } = this.config; // const { callback } = this.config;
// this.config.reject(); // this.config.reject();
@ -129,3 +238,12 @@ export default modalTemplate({
}, },
}); });
</script> </script>
<style lang="scss">
.menu-item__button {
width: 30px;
height: 30px;
padding: 4px;
background-color: transparent;
opacity: 0.75;
}
</style>

View File

@ -0,0 +1,76 @@
<template>
<modal-inner aria-label="Gitea 公开仓库图床">
<div class="modal__content">
<div class="modal__image">
<icon-provider provider-id="gitea"></icon-provider>
</div>
<p>创建一个用于存储图片的<b> Gitea </b>公开仓库文件夹图床</p>
<form-entry label="仓库 URL" error="projectUrl">
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
<div class="form-entry__info">
<b>例如:</b> {{config.token.serverUrl}}/{owner}/{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 giteaHelper from '../../../services/providers/helpers/giteaHelper';
import store from '../../../store';
export default modalTemplate({
data: () => ({
branch: '',
path: '',
}),
computedLocalSettings: {
projectUrl: 'giteaImgStorageRepoUrl',
},
methods: {
async resolve() {
const projectPath = utils.parseGiteaProjectPath(this.projectUrl);
if (!projectPath) {
this.setError('projectUrl');
} else {
// URL
try {
const repoInfo = await giteaHelper.getRepoInfo(this.config.token, projectPath);
if (repoInfo.private) {
store.dispatch('notification/error', '作为图床的仓库URL必须是公开仓库');
this.setError('projectUrl');
return;
}
const path = this.path && this.path.replace(/^\//, '');
this.config.resolve({
repoUri: projectPath,
path: path || '{YYYY}/{MM}/{DD}',
branch: this.branch || 'master',
});
} catch (err) {
store.dispatch('notification/error', err);
this.setError('projectUrl');
}
}
},
},
});
</script>

View File

@ -4,11 +4,11 @@
<div class="modal__image"> <div class="modal__image">
<icon-provider provider-id="gitea"></icon-provider> <icon-provider provider-id="gitea"></icon-provider>
</div> </div>
<p>创建一个与<b> Gitea </b>项目文件夹同步的文档空间</p> <p>创建一个与<b> Gitea </b>仓库文件夹同步的文档空间</p>
<form-entry label="Project URL" error="projectUrl"> <form-entry label="仓库 URL" error="projectUrl">
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()"> <input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
<div class="form-entry__info"> <div class="form-entry__info">
<b>例如:</b> {{config.token.serverUrl}}/path/to/project <b>例如:</b> {{config.token.serverUrl}}/{owner}/{repo}
</div> </div>
</form-entry> </form-entry>
<form-entry label="文件夹路径" info="可选的"> <form-entry label="文件夹路径" info="可选的">

View File

@ -26,6 +26,11 @@ export default {
'取消', '取消',
'确认删除', '确认删除',
), ),
imgStorageDeletion: simpleModal(
'<p>您将要删除图床,你确定吗?</p>',
'取消',
'确认删除',
),
pathConflict: simpleModal( pathConflict: simpleModal(
config => `<p><b>${config.item.name}</b>已经存在。您要添加后缀吗?</p>`, config => `<p><b>${config.item.name}</b>已经存在。您要添加后缀吗?</p>`,
'取消', '取消',

5
src/data/versions.js Normal file
View File

@ -0,0 +1,5 @@
export default {
versionsDescription: {
'5.15.7': '支持了粘贴/拖拽图片自动上传SM.MS图床/Gitea图床',
},
};

View File

@ -2,11 +2,6 @@
你好!我是你在 **StackEdit中文版** 中的第一个 Markdown 文件。如果你想了解 StackEdit中文版可以阅读此文章。如果你想玩 Markdown你也可以编辑次文章。另外您可以通过打开导航栏左角的**文件资源管理器**来创建新文件。 你好!我是你在 **StackEdit中文版** 中的第一个 Markdown 文件。如果你想了解 StackEdit中文版可以阅读此文章。如果你想玩 Markdown你也可以编辑次文章。另外您可以通过打开导航栏左角的**文件资源管理器**来创建新文件。
> **新功能**
>
> 已支持直接粘贴/拖拽上传文件目前仅支持了SM.MS图床(2022.07.01)
# 文件 # 文件
StackEdit中文版 将您的文件存储在您的浏览器中,这意味着您的所有文件都会自动保存在本地并且可以**离线访问!** StackEdit中文版 将您的文件存储在您的浏览器中,这意味着您的所有文件都会自动保存在本地并且可以**离线访问!**

View File

@ -8,9 +8,10 @@ import './icons';
import App from './components/App'; import App from './components/App';
import store from './store'; import store from './store';
import localDbSvc from './services/localDbSvc'; import localDbSvc from './services/localDbSvc';
import versionsDescription from './data/versions';
if (!indexedDB) { if (!indexedDB) {
throw new Error('Your browser is not supported. Please upgrade to the latest version.'); throw new Error('不支持您的浏览器,请升级到最新版本。');
} }
OfflinePluginRuntime.install({ OfflinePluginRuntime.install({
@ -29,8 +30,9 @@ OfflinePluginRuntime.install({
}); });
if (localStorage.updated) { if (localStorage.updated) {
store.dispatch('notification/info', 'StackEdit has just updated itself!'); const versionDesc = versionsDescription[VERSION];
setTimeout(() => localStorage.removeItem('updated'), 2000); store.dispatch('notification/info', `StackEdit中文版刚刚更新了${versionDesc}`);
setTimeout(() => localStorage.removeItem('updated'), 3000);
} }
if (!localStorage.installPrompted) { if (!localStorage.installPrompted) {
@ -39,7 +41,7 @@ if (!localStorage.installPrompted) {
promptEvent.preventDefault(); promptEvent.preventDefault();
try { try {
await store.dispatch('notification/confirm', 'Add StackEdit to your home screen?'); await store.dispatch('notification/confirm', '将StackEdit中文版添加到您的主屏幕上');
promptEvent.prompt(); promptEvent.prompt();
await promptEvent.userChoice; await promptEvent.userChoice;
} catch (err) { } catch (err) {

View File

@ -83,7 +83,7 @@ export default new Provider({
} }
if (!workspace) { if (!workspace) {
const projectId = await giteaHelper.getProjectId(workspaceParams); const projectId = await giteaHelper.getProjectId(token, workspaceParams);
const pathEntries = (path || '').split('/'); const pathEntries = (path || '').split('/');
const projectPathEntries = (projectPath || '').split('/'); const projectPathEntries = (projectPath || '').split('/');
const name = pathEntries[pathEntries.length - 2] // path ends with `/` const name = pathEntries[pathEntries.length - 2] // path ends with `/`

View File

@ -58,6 +58,7 @@ 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(
@ -120,6 +121,7 @@ export default {
name: user.username, name: user.username,
applicationId, applicationId,
applicationSecret, applicationSecret,
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,
@ -176,16 +178,48 @@ export default {
badgeSvc.addBadge('addGiteaAccount'); badgeSvc.addBadge('addGiteaAccount');
return token; return token;
}, },
async updateToken(token, imgStorageInfo) {
/** const imgStorages = token.imgStorages || [];
* https://try.gitea.io/api/swagger#/repository/repoGet // 存储仓库唯一标识
*/ const sid = utils.hash(`${imgStorageInfo.repoUri}${imgStorageInfo.path}${imgStorageInfo.branch}`);
async getProjectId({ projectPath, projectId }) { // 查询是否存在 存在则更新
const filterStorages = imgStorages.filter(it => it.sid === sid);
if (filterStorages && filterStorages.length > 0) {
filterStorages.repoUri = imgStorageInfo.repoUri;
filterStorages.path = imgStorageInfo.path;
filterStorages.branch = imgStorageInfo.branch;
} else {
imgStorages.push({
sid,
repoUri: imgStorageInfo.repoUri,
path: imgStorageInfo.path,
branch: imgStorageInfo.branch,
});
token.imgStorages = imgStorages;
}
store.dispatch('data/addGiteaToken', 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/addGiteaToken', token);
},
async getProjectId(token, { projectPath, projectId }) {
if (projectId) { if (projectId) {
return projectId; return projectId;
} }
const repoInfo = await this.getRepoInfo(token, projectPath);
return repoInfo.full_name;
},
/**
* https://try.gitea.io/api/swagger#/repository/repoGet
*/
async getRepoInfo(token, projectPath) {
const [, repoFullName] = projectPath.match(/([^/]+\/[^/]+)$/); const [, repoFullName] = projectPath.match(/([^/]+\/[^/]+)$/);
return repoFullName; const refreshedToken = await this.refreshToken(token);
return request(refreshedToken, { url: `repos/${repoFullName}` });
}, },
/** /**
@ -236,6 +270,7 @@ export default {
path, path,
content, content,
sha, sha,
isFile,
}) { }) {
const refreshedToken = await this.refreshToken(token); const refreshedToken = await this.refreshToken(token);
return request(refreshedToken, { return request(refreshedToken, {
@ -243,7 +278,7 @@ export default {
url: `repos/${projectId}/contents/${encodeURIComponent(path)}`, url: `repos/${projectId}/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,
}, },

View File

@ -182,6 +182,14 @@ export default {
.replace(/\+/g, '-') // Replace `+` with `-` .replace(/\+/g, '-') // Replace `+` with `-`
.replace(/=+$/, ''); // Remove trailing `=` .replace(/=+$/, ''); // Remove trailing `=`
}, },
encodeFiletoBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result.split(',').pop());
reader.onerror = error => reject(error);
});
},
decodeBase64(str) { decodeBase64(str) {
// In case of URL safe base64 // In case of URL safe base64
const sanitizedStr = str.replace(/_/g, '/').replace(/-/g, '+'); const sanitizedStr = str.replace(/_/g, '/').replace(/-/g, '+');