图床支持gitea图床
This commit is contained in:
parent
698ddb9abd
commit
070206cb5a
@ -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
|
||||||
|
@ -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,
|
||||||
|
93
src/components/menus/common/MenuItem.vue
Normal file
93
src/components/menus/common/MenuItem.vue
Normal 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>
|
@ -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-item>
|
||||||
</menu-entry>
|
</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,13 +114,14 @@ 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;
|
||||||
}
|
}
|
||||||
|
if (currStorage.provider === 'smms') {
|
||||||
const filterTokens = this.smmsTokens.filter(it => it.sub === currStorage.sub);
|
const filterTokens = this.smmsTokens.filter(it => it.sub === currStorage.sub);
|
||||||
if (!filterTokens.length) {
|
if (!filterTokens.length) {
|
||||||
store.dispatch('notification/info', '暂无已选择的图床,未自动上传图片!请选择图床后重新粘贴/拖拽图片!');
|
store.dispatch('notification/info', 'SMS图床已失效,未自动上传图片!请选择图床后重新粘贴/拖拽图片!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const token = filterTokens[0];
|
const token = filterTokens[0];
|
||||||
@ -83,6 +129,37 @@ export default modalTemplate({
|
|||||||
token,
|
token,
|
||||||
file: imgFile,
|
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', '暂无已选择的图床,未自动上传图片!请选择图床后重新粘贴/拖拽图片!');
|
||||||
|
}
|
||||||
} 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>
|
||||||
|
76
src/components/modals/providers/GiteaImgStorageModal.vue
Normal file
76
src/components/modals/providers/GiteaImgStorageModal.vue
Normal 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>
|
@ -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="可选的">
|
||||||
|
@ -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
5
src/data/versions.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
versionsDescription: {
|
||||||
|
'5.15.7': '支持了粘贴/拖拽图片自动上传SM.MS图床/Gitea图床',
|
||||||
|
},
|
||||||
|
};
|
@ -2,11 +2,6 @@
|
|||||||
|
|
||||||
你好!我是你在 **StackEdit中文版** 中的第一个 Markdown 文件。如果你想了解 StackEdit中文版,可以阅读此文章。如果你想玩 Markdown,你也可以编辑次文章。另外,您可以通过打开导航栏左角的**文件资源管理器**来创建新文件。
|
你好!我是你在 **StackEdit中文版** 中的第一个 Markdown 文件。如果你想了解 StackEdit中文版,可以阅读此文章。如果你想玩 Markdown,你也可以编辑次文章。另外,您可以通过打开导航栏左角的**文件资源管理器**来创建新文件。
|
||||||
|
|
||||||
> **新功能**
|
|
||||||
>
|
|
||||||
> 已支持直接粘贴/拖拽上传文件,目前仅支持了SM.MS图床(2022.07.01)
|
|
||||||
|
|
||||||
|
|
||||||
# 文件
|
# 文件
|
||||||
|
|
||||||
StackEdit中文版 将您的文件存储在您的浏览器中,这意味着您的所有文件都会自动保存在本地并且可以**离线访问!**
|
StackEdit中文版 将您的文件存储在您的浏览器中,这意味着您的所有文件都会自动保存在本地并且可以**离线访问!**
|
||||||
|
10
src/index.js
10
src/index.js
@ -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) {
|
||||||
|
@ -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 `/`
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
|
@ -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, '+');
|
||||||
|
Loading…
Reference in New Issue
Block a user