图片支持相对本地空间的路径存储
This commit is contained in:
parent
e7450df251
commit
a4ab4b2da1
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "StackEdit中文版",
|
||||
"description": "支持Gitee仓库/粘贴图片自动上传的浏览器内 Markdown 编辑器",
|
||||
"version": "5.15.14",
|
||||
"version": "5.15.15",
|
||||
"manifest_version": 2,
|
||||
"container" : "GITEE",
|
||||
"api_console_project_id" : "241271498917",
|
||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "stackedit",
|
||||
"version": "5.15.14",
|
||||
"version": "5.15.15",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "stackedit",
|
||||
"version": "5.15.14",
|
||||
"version": "5.15.15",
|
||||
"description": "免费, 开源, 功能齐全的 Markdown 编辑器",
|
||||
"author": "Benoit Schweblin, 豆萁",
|
||||
"license": "Apache-2.0",
|
||||
|
@ -34,7 +34,7 @@ export default {
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
async setImgAndDoClick(items) {
|
||||
async processUpload(items) {
|
||||
let file = null;
|
||||
if (!items || items.length === 0) {
|
||||
return;
|
||||
@ -73,6 +73,11 @@ export default {
|
||||
if (currImgStorageStr) {
|
||||
store.commit('img/changeCheckedStorage', JSON.parse(currImgStorageStr));
|
||||
}
|
||||
// 当前本地图片路径配置
|
||||
const workspaceImgPath = localStorage.getItem('img/workspaceImgPath');
|
||||
if (workspaceImgPath) {
|
||||
store.commit('img/setWorkspaceImgPath', JSON.parse(workspaceImgPath));
|
||||
}
|
||||
const editorElt = this.$el.querySelector('.editor__inner');
|
||||
const onDiscussionEvt = cb => (evt) => {
|
||||
let elt = evt.target;
|
||||
@ -100,11 +105,11 @@ export default {
|
||||
|
||||
editorElt.addEventListener('drop', (event) => {
|
||||
const transItems = event.dataTransfer.items;
|
||||
this.setImgAndDoClick(transItems);
|
||||
this.processUpload(transItems);
|
||||
});
|
||||
editorElt.addEventListener('paste', (event) => {
|
||||
const pasteItems = (event.clipboardData || window.clipboardData).items;
|
||||
this.setImgAndDoClick(pasteItems);
|
||||
this.processUpload(pasteItems);
|
||||
});
|
||||
|
||||
this.$watch(
|
||||
|
@ -40,6 +40,7 @@ import AccountManagementModal from './modals/AccountManagementModal';
|
||||
import BadgeManagementModal from './modals/BadgeManagementModal';
|
||||
import SponsorModal from './modals/SponsorModal';
|
||||
import CommitMessageModal from './modals/CommitMessageModal';
|
||||
import WorkspaceImgPathModal from './modals/WorkspaceImgPathModal';
|
||||
|
||||
// Providers
|
||||
import GooglePhotoModal from './modals/providers/GooglePhotoModal';
|
||||
@ -107,6 +108,7 @@ export default {
|
||||
BadgeManagementModal,
|
||||
SponsorModal,
|
||||
CommitMessageModal,
|
||||
WorkspaceImgPathModal,
|
||||
// Providers
|
||||
GooglePhotoModal,
|
||||
GoogleDriveAccountModal,
|
||||
|
@ -15,6 +15,21 @@
|
||||
<div>
|
||||
<hr />
|
||||
<p>添加并选择图床后可在编辑区中粘贴/拖拽图片自动上传</p>
|
||||
|
||||
<menu-entry @click.native="checkedImgDest(path)" v-for="path in workspaceImgPath" :key="path">
|
||||
<icon-check-circle v-if="checkedStorage.sub === path" slot="icon"></icon-check-circle>
|
||||
<icon-check-circle-un v-if="checkedStorage.sub !== path" slot="icon"></icon-check-circle-un>
|
||||
<menu-item>
|
||||
<icon-provider slot="icon" :provider-id="currentWorkspace.providerId"></icon-provider>
|
||||
<div>
|
||||
本文档空间图片路径
|
||||
<button class="menu-item__button button" @click.stop="removeByPath(path)" v-title="'删除'">
|
||||
<icon-delete></icon-delete>
|
||||
</button>
|
||||
</div>
|
||||
<span>路径:{{path}}</span>
|
||||
</menu-item>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="checkedImgDest(token.sub, token.providerId)" v-for="token in imageTokens" :key="token.sub">
|
||||
<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>
|
||||
@ -45,6 +60,10 @@
|
||||
<span> {{tokenStorage.uname}}, 仓库URL: {{tokenStorage.repoUrl}}, 路径: {{tokenStorage.path}}, 分支: {{tokenStorage.branch}}</span>
|
||||
</menu-item>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addWorkspaceImgPath">
|
||||
<icon-provider slot="icon" :provider-id="currentWorkspace.providerId"></icon-provider>
|
||||
<span>添加本文档空间图片路径</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addSmmsAccount">
|
||||
<icon-provider slot="icon" provider-id="smms"></icon-provider>
|
||||
<span>添加SM.MS图床账号</span>
|
||||
@ -66,6 +85,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import modalTemplate from './common/modalTemplate';
|
||||
import MenuEntry from '../menus/common/MenuEntry';
|
||||
import MenuItem from '../menus/common/MenuItem';
|
||||
@ -87,9 +107,16 @@ export default modalTemplate({
|
||||
url: '',
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('workspace', [
|
||||
'currentWorkspace',
|
||||
]),
|
||||
checkedStorage() {
|
||||
return store.getters['img/getCheckedStorage'];
|
||||
},
|
||||
workspaceImgPath() {
|
||||
const workspaceImgPath = store.getters['img/getWorkspaceImgPath'];
|
||||
return Object.keys(workspaceImgPath || {});
|
||||
},
|
||||
imageTokens() {
|
||||
return [
|
||||
...Object.values(store.getters['data/smmsTokensBySub']).map(token => ({
|
||||
@ -195,6 +222,13 @@ export default modalTemplate({
|
||||
// Cancel
|
||||
}
|
||||
},
|
||||
async removeByPath(path) {
|
||||
store.dispatch('img/removeWorkspaceImgPath', path);
|
||||
},
|
||||
async addWorkspaceImgPath() {
|
||||
const { path } = await store.dispatch('modal/open', { type: 'workspaceImgPath' });
|
||||
store.dispatch('img/addWorkspaceImgPath', path);
|
||||
},
|
||||
async addSmmsAccount() {
|
||||
const { proxyUrl, apiSecretToken } = await store.dispatch('modal/open', { type: 'smmsAccount' });
|
||||
await smmsHelper.addAccount(proxyUrl, apiSecretToken);
|
||||
@ -227,7 +261,10 @@ export default modalTemplate({
|
||||
},
|
||||
async checkedImgDest(sub, provider, sid) {
|
||||
let type = 'token';
|
||||
if (provider === 'gitea' || provider === 'github') {
|
||||
// 当前文档空间存储
|
||||
if (!provider) {
|
||||
type = 'workspace';
|
||||
} else if (provider === 'gitea' || provider === 'github') {
|
||||
type = 'tokenRepo';
|
||||
}
|
||||
store.dispatch('img/changeCheckedStorage', {
|
||||
|
45
src/components/modals/WorkspaceImgPathModal.vue
Normal file
45
src/components/modals/WorkspaceImgPathModal.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<modal-inner aria-label="文档空间图片路径">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-provider :provider-id="currentWorkspace.providerId"></icon-provider>
|
||||
</div>
|
||||
<p>在当前文档空间增加图片上传路径。</p>
|
||||
<form-entry label="图片上传路径" error="path">
|
||||
<input slot="field" class="textfield" type="text" placeholder="如:/imgs/{YYYY}-{MM}-{DD}" v-model.trim="path" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
如果不提供,默认为 /imgs/{YYYY}-{MM}-{DD} ,其中{YYYY}为年变量、{MM}为月变量、{DD}为日变量。<br/>
|
||||
支持相对路径,如 ./imgs 或 imgs 都是相对当前编辑中文档的路径,不支持相对上级路径。
|
||||
</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 { mapGetters } from 'vuex';
|
||||
import modalTemplate from './common/modalTemplate';
|
||||
|
||||
export default modalTemplate({
|
||||
computedLocalSettings: {
|
||||
path: '',
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('workspace', [
|
||||
'currentWorkspace',
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
const path = this.path && this.path.replace(/^\//, '');
|
||||
this.config.resolve({
|
||||
path: path || '/imgs/{YYYY}-{MM}-{DD}',
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
@ -2,6 +2,7 @@ import Vue from 'vue';
|
||||
import DiffMatchPatch from 'diff-match-patch';
|
||||
import Prism from 'prismjs';
|
||||
import markdownItPandocRenderer from 'markdown-it-pandoc-renderer';
|
||||
import md5 from 'js-md5';
|
||||
import cledit from './editor/cledit';
|
||||
import pagedown from '../libs/pagedown';
|
||||
import htmlSanitizer from '../libs/htmlSanitizer';
|
||||
@ -13,6 +14,9 @@ import editorSvcDiscussions from './editor/editorSvcDiscussions';
|
||||
import editorSvcUtils from './editor/editorSvcUtils';
|
||||
import utils from './utils';
|
||||
import store from '../store';
|
||||
import syncSvc from './syncSvc';
|
||||
import constants from '../data/constants';
|
||||
import localDbSvc from './localDbSvc';
|
||||
|
||||
const allowDebounce = (action, wait) => {
|
||||
let timeoutId;
|
||||
@ -40,6 +44,39 @@ class SectionDesc {
|
||||
}
|
||||
}
|
||||
|
||||
const pathUrlMap = Object.create(null);
|
||||
|
||||
const getCurrAbsolutePath = () => {
|
||||
const fileId = store.getters['file/current'].id;
|
||||
const fileSyncData = store.getters['data/syncDataByItemId'][fileId] || { id: '' };
|
||||
const fileAbsolutePath = `${store.getters['workspace/currentWorkspace'].path || ''}${fileSyncData.id}`;
|
||||
return fileAbsolutePath.substring(0, fileAbsolutePath.lastIndexOf('/'));
|
||||
};
|
||||
|
||||
const getImgUrl = async (uri) => {
|
||||
if (uri.indexOf('http://') !== 0 && uri.indexOf('https://') !== 0) {
|
||||
const absoluteImgPath = utils.getAbsoluteFilePath(getCurrAbsolutePath(), uri);
|
||||
if (pathUrlMap[absoluteImgPath]) {
|
||||
return pathUrlMap[absoluteImgPath];
|
||||
}
|
||||
const md5Id = md5(absoluteImgPath);
|
||||
let imgItem = await localDbSvc.getImgItem(md5Id);
|
||||
if (!imgItem) {
|
||||
await syncSvc.syncImg(absoluteImgPath);
|
||||
imgItem = await localDbSvc.getImgItem(md5Id);
|
||||
}
|
||||
if (imgItem) {
|
||||
// imgItem 如果不存在 则加载 TODO
|
||||
const imgFile = utils.base64ToBlob(imgItem.content, uri);
|
||||
const url = URL.createObjectURL(imgFile);
|
||||
pathUrlMap[absoluteImgPath] = url;
|
||||
return url;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
return uri;
|
||||
};
|
||||
|
||||
// Use a vue instance as an event bus
|
||||
const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils, {
|
||||
// Elements
|
||||
@ -212,11 +249,18 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
|
||||
this.makeTextToPreviewDiffs();
|
||||
|
||||
// Wait for images to load
|
||||
const loadedPromises = loadingImages.map(imgElt => new Promise((resolve) => {
|
||||
const loadedPromises = loadingImages.map(imgElt => new Promise((resolve, reject) => {
|
||||
if (!imgElt.src) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
if (imgElt.src.indexOf(constants.origin) >= 0) {
|
||||
getImgUrl(imgElt.src.replace(constants.origin, '')).then((newUrl) => {
|
||||
imgElt.src = newUrl;
|
||||
resolve();
|
||||
}, () => reject(new Error('加载本地空间图片出错')));
|
||||
return;
|
||||
}
|
||||
const img = new window.Image();
|
||||
img.onload = resolve;
|
||||
img.onerror = resolve;
|
||||
@ -471,6 +515,7 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
|
||||
let imgEltsToCache = [];
|
||||
if (store.getters['data/computedSettings'].editor.inlineImages) {
|
||||
this.clEditor.highlighter.on('sectionHighlighted', (section) => {
|
||||
const loadImgs = [];
|
||||
section.elt.getElementsByClassName('token img').cl_each((imgTokenElt) => {
|
||||
const srcElt = imgTokenElt.querySelector('.token.cl-src');
|
||||
if (srcElt) {
|
||||
@ -496,6 +541,9 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
|
||||
}
|
||||
}
|
||||
imgEltsToCache.push(imgElt);
|
||||
if (imgElt.src.indexOf(origin) >= 0) {
|
||||
loadImgs.push(imgElt);
|
||||
}
|
||||
}
|
||||
const imgTokenWrapper = document.createElement('span');
|
||||
imgTokenWrapper.className = 'token img-wrapper';
|
||||
@ -504,9 +552,19 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
|
||||
imgTokenWrapper.appendChild(imgTokenElt);
|
||||
}
|
||||
});
|
||||
if (loadImgs.length) {
|
||||
// Wait for images to load
|
||||
const loadWorkspaceImg = loadImgs.map(imgElt => new Promise((resolve, reject) => {
|
||||
const uri = imgElt.src.replace(origin, '');
|
||||
getImgUrl(uri).then((newUrl) => {
|
||||
imgElt.src = newUrl;
|
||||
resolve();
|
||||
}, () => reject(new Error(`加载本地空间图片出错,uri:${uri}`)));
|
||||
}));
|
||||
Promise.all(loadWorkspaceImg).then();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.clEditor.highlighter.on('highlighted', () => {
|
||||
imgEltsToCache.forEach((imgElt) => {
|
||||
const cachedImgElt = getFromImgCache(imgElt);
|
||||
|
@ -1,17 +1,52 @@
|
||||
import md5 from 'js-md5';
|
||||
import store from '../store';
|
||||
import utils from './utils';
|
||||
import localDbSvc from './localDbSvc';
|
||||
import smmsHelper from '../services/providers/helpers/smmsHelper';
|
||||
import giteaHelper from '../services/providers/helpers/giteaHelper';
|
||||
import githubHelper from '../services/providers/helpers/githubHelper';
|
||||
import customHelper from '../services/providers/helpers/customHelper';
|
||||
|
||||
function getCurrAbsolutePath() {
|
||||
const fileId = store.getters['file/current'].id;
|
||||
const fileSyncData = store.getters['data/syncDataByItemId'][fileId] || { id: '' };
|
||||
const fileAbsolutePath = `${store.getters['workspace/currentWorkspace'].path || ''}${fileSyncData.id}`;
|
||||
return fileAbsolutePath.substring(0, fileAbsolutePath.lastIndexOf('/'));
|
||||
}
|
||||
|
||||
function getImagePath(confPath, imgType) {
|
||||
const time = new Date();
|
||||
const date = time.getDate();
|
||||
const month = time.getMonth() + 1;
|
||||
const year = time.getFullYear();
|
||||
const path = confPath.replace('{YYYY}', year)
|
||||
.replace('{MM}', `0${month}`.slice(-2)).replace('{DD}', `0${date}`.slice(-2));
|
||||
return `${path}${path.endsWith('/') ? '' : '/'}${utils.uid()}.${imgType.split('/')[1]}`;
|
||||
}
|
||||
|
||||
export default {
|
||||
// 上传图片 返回图片链接
|
||||
// { url: 'http://xxxx', error: 'xxxxxx'}
|
||||
async updateImg(imgFile) {
|
||||
// 操作图片上传
|
||||
const currStorage = store.getters['img/getCheckedStorage'];
|
||||
if (!currStorage || !currStorage.provider) {
|
||||
if (!currStorage) {
|
||||
return { error: '暂无已选择的图床!' };
|
||||
}
|
||||
// 判断是否文档空间路径
|
||||
if (currStorage.type === 'workspace') {
|
||||
const path = getImagePath(currStorage.sub, imgFile.type);
|
||||
// 保存到indexeddb
|
||||
const base64 = await utils.encodeFiletoBase64(imgFile);
|
||||
const absolutePath = utils.getAbsoluteFilePath(getCurrAbsolutePath(), path);
|
||||
await localDbSvc.saveImg({
|
||||
id: md5(absolutePath),
|
||||
path: absolutePath,
|
||||
content: base64,
|
||||
});
|
||||
return { url: path };
|
||||
}
|
||||
if (!currStorage.provider) {
|
||||
return { error: '暂无已选择的图床!' };
|
||||
}
|
||||
const token = store.getters[`data/${currStorage.provider}TokensBySub`][currStorage.sub];
|
||||
@ -32,13 +67,7 @@ export default {
|
||||
return { error: '暂无已选择的图床!' };
|
||||
}
|
||||
const checkStorage = checkStorages[0];
|
||||
const time = new Date();
|
||||
const date = time.getDate();
|
||||
const month = time.getMonth() + 1;
|
||||
const year = time.getFullYear();
|
||||
let path = checkStorage.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]}`;
|
||||
const path = getImagePath(checkStorage.path, imgFile.type);
|
||||
if (currStorage.provider === 'gitea') {
|
||||
const result = await giteaHelper.uploadFile({
|
||||
token,
|
||||
@ -46,7 +75,7 @@ export default {
|
||||
branch: checkStorage.branch,
|
||||
path,
|
||||
content: imgFile,
|
||||
isFile: true,
|
||||
isImg: true,
|
||||
});
|
||||
url = result.content.download_url;
|
||||
} else if (currStorage.provider === 'github') {
|
||||
@ -57,7 +86,7 @@ export default {
|
||||
branch: checkStorage.branch,
|
||||
path,
|
||||
content: imgFile,
|
||||
isFile: true,
|
||||
isImg: true,
|
||||
});
|
||||
url = result.content.download_url;
|
||||
}
|
||||
|
@ -5,8 +5,10 @@ import workspaceSvc from './workspaceSvc';
|
||||
import constants from '../data/constants';
|
||||
|
||||
const deleteMarkerMaxAge = 1000;
|
||||
const dbVersion = 1;
|
||||
const dbVersion = 3;
|
||||
const dbStoreName = 'objects';
|
||||
const imgDbStoreName = 'imgs';
|
||||
const imgWaitUploadIdsKey = 'waitUploadImgIds';
|
||||
const { silent } = utils.queryParams;
|
||||
const resetApp = localStorage.getItem('resetStackEdit');
|
||||
if (resetApp) {
|
||||
@ -24,7 +26,7 @@ class Connection {
|
||||
const request = indexedDB.open(this.dbName, dbVersion);
|
||||
|
||||
request.onerror = () => {
|
||||
throw new Error("Can't connect to IndexedDB.");
|
||||
throw new Error('无法连接到IndexedDB.');
|
||||
};
|
||||
|
||||
request.onsuccess = (event) => {
|
||||
@ -37,24 +39,21 @@ class Connection {
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const eventDb = event.target.result;
|
||||
const oldVersion = event.oldVersion || 0;
|
||||
|
||||
// We don't use 'break' in this switch statement,
|
||||
// the fall-through behavior is what we want.
|
||||
/* eslint-disable no-fallthrough */
|
||||
switch (oldVersion) {
|
||||
case 0: {
|
||||
// Create store
|
||||
const dbStore = eventDb.createObjectStore(dbStoreName, {
|
||||
keyPath: 'id',
|
||||
});
|
||||
dbStore.createIndex('tx', 'tx', {
|
||||
unique: false,
|
||||
});
|
||||
}
|
||||
default:
|
||||
// const oldVersion = event.oldVersion || 0;
|
||||
if (!eventDb.objectStoreNames.contains(dbStoreName)) {
|
||||
// Create store
|
||||
const dbStore = eventDb.createObjectStore(dbStoreName, {
|
||||
keyPath: 'id',
|
||||
});
|
||||
dbStore.createIndex('tx', 'tx', {
|
||||
unique: false,
|
||||
});
|
||||
}
|
||||
if (!eventDb.objectStoreNames.contains(imgDbStoreName)) {
|
||||
eventDb.createObjectStore(imgDbStoreName, {
|
||||
keyPath: 'id',
|
||||
});
|
||||
}
|
||||
/* eslint-enable no-fallthrough */
|
||||
};
|
||||
}
|
||||
|
||||
@ -193,6 +192,55 @@ const localDbSvc = {
|
||||
cb(storeItemMap);
|
||||
};
|
||||
},
|
||||
async saveImg(imgItem) {
|
||||
await this.writeImgItem(imgItem);
|
||||
const waitUploadIdsItem = (await this.getImgItem(imgWaitUploadIdsKey))
|
||||
|| { id: imgWaitUploadIdsKey, ids: [] };
|
||||
const waitUplodIds = waitUploadIdsItem.ids || [];
|
||||
// 如果已上传
|
||||
if (imgItem.uploaded) {
|
||||
waitUplodIds.splice(waitUplodIds.indexOf(imgItem.id), 1);
|
||||
} else {
|
||||
waitUplodIds.push(imgItem.id);
|
||||
}
|
||||
waitUploadIdsItem.ids = waitUplodIds;
|
||||
await this.writeImgItem(waitUploadIdsItem);
|
||||
},
|
||||
// 获取待上传的图片id
|
||||
async getWaitUploadImgIds() {
|
||||
const waitUploadIdsItem = (await this.getImgItem(imgWaitUploadIdsKey))
|
||||
|| { id: imgWaitUploadIdsKey, ids: [] };
|
||||
return waitUploadIdsItem.ids || [];
|
||||
},
|
||||
/**
|
||||
* 写入图片
|
||||
*/
|
||||
async writeImgItem(imgItem) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Create the DB transaction
|
||||
this.connection.createTx((tx) => {
|
||||
const dbStore = tx.objectStore(imgDbStoreName);
|
||||
dbStore.put(imgItem);
|
||||
resolve();
|
||||
}, () => reject(new Error('保存图片异常')));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 读取图片
|
||||
*/
|
||||
async getImgItem(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Get the item from DB
|
||||
this.connection.createTx((tx) => {
|
||||
const dbStore = tx.objectStore(imgDbStoreName);
|
||||
const request = dbStore.get(id);
|
||||
request.onsuccess = () => {
|
||||
const dbItem = request.result;
|
||||
resolve(dbItem);
|
||||
};
|
||||
}, () => reject(new Error('indexeddb获取图片异常')));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Write all changes from the store since previous transaction.
|
||||
|
@ -173,6 +173,18 @@ export default new Provider({
|
||||
},
|
||||
};
|
||||
},
|
||||
async downloadFile({ token, path }) {
|
||||
const { sha, data } = await giteaHelper.downloadFile({
|
||||
...store.getters['workspace/currentWorkspace'],
|
||||
token,
|
||||
path,
|
||||
isImg: true,
|
||||
});
|
||||
return {
|
||||
content: data,
|
||||
sha,
|
||||
};
|
||||
},
|
||||
async downloadWorkspaceData({ token, syncData }) {
|
||||
if (!syncData) {
|
||||
return {};
|
||||
@ -200,25 +212,32 @@ export default new Provider({
|
||||
file,
|
||||
commitMessage,
|
||||
}) {
|
||||
const path = store.getters.gitPathsByItemId[file.id];
|
||||
const absolutePath = `${store.getters['workspace/currentWorkspace'].path || ''}${path}`;
|
||||
const sha = gitWorkspaceSvc.shaByPath[path];
|
||||
await giteaHelper.uploadFile({
|
||||
const isImg = file.type === 'img';
|
||||
const path = store.getters.gitPathsByItemId[file.id] || '';
|
||||
const absolutePath = !isImg ? `${store.getters['workspace/currentWorkspace'].path || ''}${store.getters.gitPathsByItemId[file.id]}` : file.path;
|
||||
const sha = gitWorkspaceSvc.shaByPath[!isImg ? path : file.path];
|
||||
const res = await giteaHelper.uploadFile({
|
||||
...store.getters['workspace/currentWorkspace'],
|
||||
token,
|
||||
path: absolutePath,
|
||||
content: Provider.serializeContent(content),
|
||||
content: !isImg ? Provider.serializeContent(content) : file.content,
|
||||
sha,
|
||||
isImg,
|
||||
commitMessage,
|
||||
});
|
||||
|
||||
if (isImg) {
|
||||
return {
|
||||
sha: res.content.sha,
|
||||
};
|
||||
}
|
||||
// Return new sync data
|
||||
return {
|
||||
contentSyncData: {
|
||||
id: store.getters.gitPathsByItemId[content.id],
|
||||
type: content.type,
|
||||
hash: content.hash,
|
||||
sha,
|
||||
sha: res.content.sha,
|
||||
},
|
||||
fileSyncData: {
|
||||
id: path,
|
||||
|
@ -110,6 +110,20 @@ export default new Provider({
|
||||
},
|
||||
};
|
||||
},
|
||||
async downloadFile({ token, path }) {
|
||||
const { sha, data } = await giteeHelper.downloadFile({
|
||||
owner: token.name,
|
||||
repo: appDataRepo,
|
||||
branch: appDataBranch,
|
||||
token,
|
||||
path,
|
||||
isImg: true,
|
||||
});
|
||||
return {
|
||||
content: data,
|
||||
sha,
|
||||
};
|
||||
},
|
||||
async downloadWorkspaceData({ token, syncData }) {
|
||||
if (!syncData) {
|
||||
return {};
|
||||
@ -145,18 +159,25 @@ export default new Provider({
|
||||
file,
|
||||
commitMessage,
|
||||
}) {
|
||||
const path = store.getters.gitPathsByItemId[file.id];
|
||||
const isImg = file.type === 'img';
|
||||
const path = !isImg ? store.getters.gitPathsByItemId[file.id] : file.path;
|
||||
const res = await giteeHelper.uploadFile({
|
||||
owner: token.name,
|
||||
repo: appDataRepo,
|
||||
branch: appDataBranch,
|
||||
token,
|
||||
path,
|
||||
content: Provider.serializeContent(content),
|
||||
sha: gitWorkspaceSvc.shaByPath[path],
|
||||
content: !isImg ? Provider.serializeContent(content) : file.content,
|
||||
sha: gitWorkspaceSvc.shaByPath[!isImg ? path : file.path],
|
||||
isImg,
|
||||
commitMessage,
|
||||
});
|
||||
|
||||
if (isImg) {
|
||||
return {
|
||||
sha: res.content.sha,
|
||||
};
|
||||
}
|
||||
// Return new sync data
|
||||
return {
|
||||
contentSyncData: {
|
||||
|
@ -158,6 +158,18 @@ export default new Provider({
|
||||
},
|
||||
};
|
||||
},
|
||||
async downloadFile({ token, path }) {
|
||||
const { sha, data } = await giteeHelper.downloadFile({
|
||||
...store.getters['workspace/currentWorkspace'],
|
||||
token,
|
||||
path,
|
||||
isImg: true,
|
||||
});
|
||||
return {
|
||||
content: data,
|
||||
sha,
|
||||
};
|
||||
},
|
||||
async downloadWorkspaceData({ token, syncData }) {
|
||||
if (!syncData) {
|
||||
return {};
|
||||
@ -185,17 +197,24 @@ export default new Provider({
|
||||
file,
|
||||
commitMessage,
|
||||
}) {
|
||||
const path = store.getters.gitPathsByItemId[file.id];
|
||||
const absolutePath = `${store.getters['workspace/currentWorkspace'].path || ''}${path}`;
|
||||
const isImg = file.type === 'img';
|
||||
const path = store.getters.gitPathsByItemId[file.id] || '';
|
||||
const absolutePath = !isImg ? `${store.getters['workspace/currentWorkspace'].path || ''}${path}` : file.path;
|
||||
const res = await giteeHelper.uploadFile({
|
||||
...store.getters['workspace/currentWorkspace'],
|
||||
token,
|
||||
path: absolutePath,
|
||||
content: Provider.serializeContent(content),
|
||||
sha: gitWorkspaceSvc.shaByPath[path],
|
||||
content: !isImg ? Provider.serializeContent(content) : file.content,
|
||||
sha: gitWorkspaceSvc.shaByPath[!isImg ? path : file.path],
|
||||
isImg,
|
||||
commitMessage,
|
||||
});
|
||||
|
||||
if (isImg) {
|
||||
return {
|
||||
sha: res.content.sha,
|
||||
};
|
||||
}
|
||||
// Return new sync data
|
||||
return {
|
||||
contentSyncData: {
|
||||
|
@ -158,6 +158,18 @@ export default new Provider({
|
||||
},
|
||||
};
|
||||
},
|
||||
async downloadFile({ token, path }) {
|
||||
const { sha, data } = await githubHelper.downloadFile({
|
||||
...store.getters['workspace/currentWorkspace'],
|
||||
token,
|
||||
path,
|
||||
isImg: true,
|
||||
});
|
||||
return {
|
||||
content: data,
|
||||
sha,
|
||||
};
|
||||
},
|
||||
async downloadWorkspaceData({ token, syncData }) {
|
||||
if (!syncData) {
|
||||
return {};
|
||||
@ -185,17 +197,23 @@ export default new Provider({
|
||||
file,
|
||||
commitMessage,
|
||||
}) {
|
||||
const path = store.getters.gitPathsByItemId[file.id];
|
||||
const absolutePath = `${store.getters['workspace/currentWorkspace'].path || ''}${path}`;
|
||||
const isImg = file.type === 'img';
|
||||
const path = store.getters.gitPathsByItemId[file.id] || '';
|
||||
const absolutePath = !isImg ? `${store.getters['workspace/currentWorkspace'].path || ''}${path}` : file.path;
|
||||
const res = await githubHelper.uploadFile({
|
||||
...store.getters['workspace/currentWorkspace'],
|
||||
token,
|
||||
path: absolutePath,
|
||||
content: Provider.serializeContent(content),
|
||||
sha: gitWorkspaceSvc.shaByPath[path],
|
||||
content: !isImg ? Provider.serializeContent(content) : file.content,
|
||||
sha: gitWorkspaceSvc.shaByPath[!isImg ? path : file.path],
|
||||
isImg,
|
||||
commitMessage,
|
||||
});
|
||||
|
||||
if (isImg) {
|
||||
return {
|
||||
sha: res.content.sha,
|
||||
};
|
||||
}
|
||||
// Return new sync data
|
||||
return {
|
||||
contentSyncData: {
|
||||
|
@ -314,16 +314,20 @@ export default {
|
||||
path,
|
||||
content,
|
||||
sha,
|
||||
isFile,
|
||||
isImg,
|
||||
commitMessage,
|
||||
}) {
|
||||
let uploadContent = content;
|
||||
if (isImg && typeof content !== 'string') {
|
||||
uploadContent = await utils.encodeFiletoBase64(content);
|
||||
}
|
||||
const refreshedToken = await this.refreshToken(token);
|
||||
return request(refreshedToken, {
|
||||
method: sha ? 'PUT' : 'POST',
|
||||
url: `repos/${projectId}/contents/${encodeURIComponent(path)}`,
|
||||
body: {
|
||||
message: commitMessage || getCommitMessage(sha ? 'updateFileMessage' : 'createFileMessage', path),
|
||||
content: isFile ? await utils.encodeFiletoBase64(content) : utils.encodeBase64(content),
|
||||
content: isImg ? uploadContent : utils.encodeBase64(content),
|
||||
sha,
|
||||
branch,
|
||||
},
|
||||
@ -360,6 +364,7 @@ export default {
|
||||
projectId,
|
||||
branch,
|
||||
path,
|
||||
isImg,
|
||||
}) {
|
||||
const refreshedToken = await this.refreshToken(token);
|
||||
const { sha, content } = await request(refreshedToken, {
|
||||
@ -368,7 +373,7 @@ export default {
|
||||
});
|
||||
return {
|
||||
sha,
|
||||
data: utils.decodeBase64(content),
|
||||
data: !isImg ? utils.decodeBase64(content) : content,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
@ -276,15 +276,20 @@ export default {
|
||||
path,
|
||||
content,
|
||||
sha,
|
||||
isImg,
|
||||
commitMessage,
|
||||
}) {
|
||||
let uploadContent = content;
|
||||
if (isImg && typeof content !== 'string') {
|
||||
uploadContent = await utils.encodeFiletoBase64(content);
|
||||
}
|
||||
const refreshedToken = await this.refreshToken(token);
|
||||
return repoRequest(refreshedToken, owner, repo, {
|
||||
method: sha ? 'PUT' : 'POST',
|
||||
url: `contents/${encodeURIComponent(path)}`,
|
||||
body: {
|
||||
message: commitMessage || getCommitMessage(sha ? 'updateFileMessage' : 'createFileMessage', path),
|
||||
content: utils.encodeBase64(content || ' '),
|
||||
content: isImg ? uploadContent : utils.encodeBase64(content || ' '),
|
||||
sha,
|
||||
branch,
|
||||
},
|
||||
@ -323,6 +328,7 @@ export default {
|
||||
repo,
|
||||
branch,
|
||||
path,
|
||||
isImg,
|
||||
}) {
|
||||
const refreshedToken = await this.refreshToken(token);
|
||||
const { sha, content } = await repoRequest(refreshedToken, owner, repo, {
|
||||
@ -330,7 +336,7 @@ export default {
|
||||
params: { ref: branch },
|
||||
});
|
||||
if (sha) {
|
||||
const data = utils.decodeBase64(content);
|
||||
const data = !isImg ? utils.decodeBase64(content) : content;
|
||||
return {
|
||||
sha,
|
||||
data: data === ' ' ? '' : data,
|
||||
|
@ -176,15 +176,19 @@ export default {
|
||||
path,
|
||||
content,
|
||||
sha,
|
||||
isFile,
|
||||
isImg,
|
||||
commitMessage,
|
||||
}) {
|
||||
let uploadContent = content;
|
||||
if (isImg && typeof content !== 'string') {
|
||||
uploadContent = await utils.encodeFiletoBase64(content);
|
||||
}
|
||||
return repoRequest(token, owner, repo, {
|
||||
method: 'PUT',
|
||||
url: `contents/${encodeURIComponent(path)}`,
|
||||
body: {
|
||||
message: commitMessage || getCommitMessage(sha ? 'updateFileMessage' : 'createFileMessage', path),
|
||||
content: isFile ? await utils.encodeFiletoBase64(content) : utils.encodeBase64(content),
|
||||
content: isImg ? uploadContent : utils.encodeBase64(content),
|
||||
sha,
|
||||
branch,
|
||||
},
|
||||
@ -222,6 +226,7 @@ export default {
|
||||
repo,
|
||||
branch,
|
||||
path,
|
||||
isImg,
|
||||
}) {
|
||||
const { sha, content } = await repoRequest(token, owner, repo, {
|
||||
url: `contents/${encodeURIComponent(path)}`,
|
||||
@ -229,7 +234,7 @@ export default {
|
||||
});
|
||||
return {
|
||||
sha,
|
||||
data: utils.decodeBase64(content),
|
||||
data: !isImg ? utils.decodeBase64(content) : content,
|
||||
};
|
||||
},
|
||||
/**
|
||||
|
@ -1,3 +1,4 @@
|
||||
import md5 from 'js-md5';
|
||||
import localDbSvc from './localDbSvc';
|
||||
import store from '../store';
|
||||
import utils from './utils';
|
||||
@ -841,6 +842,61 @@ const syncWorkspace = async (skipContents = false) => {
|
||||
}
|
||||
};
|
||||
|
||||
const syncImg = async (absolutePath) => {
|
||||
const token = workspaceProvider.getToken();
|
||||
const path = absolutePath.substring(1, absolutePath.length);
|
||||
const { sha, content } = await workspaceProvider.downloadFile({
|
||||
token,
|
||||
path,
|
||||
});
|
||||
if (!sha || !content) {
|
||||
return;
|
||||
}
|
||||
await localDbSvc.saveImg({
|
||||
id: md5(absolutePath),
|
||||
path: absolutePath,
|
||||
content,
|
||||
uploaded: 1,
|
||||
sha,
|
||||
});
|
||||
};
|
||||
|
||||
const uploadImg = async (imgIds, index = 0) => {
|
||||
if (imgIds.length - 1 < index) {
|
||||
return;
|
||||
}
|
||||
const item = await localDbSvc.getImgItem(imgIds[index]);
|
||||
// 不存在item 或已上传 则跳过
|
||||
if (!item || item.uploaded) {
|
||||
setTimeout(await uploadImg(imgIds, index + 1), 10);
|
||||
return;
|
||||
}
|
||||
const token = workspaceProvider.getToken();
|
||||
const { sha } = await workspaceProvider.uploadWorkspaceContent({
|
||||
token,
|
||||
file: {
|
||||
...utils.deepCopy(item),
|
||||
type: 'img',
|
||||
path: item.path.substring(1, item.path.length),
|
||||
},
|
||||
isImg: true,
|
||||
});
|
||||
await localDbSvc.saveImg({
|
||||
...item,
|
||||
uploaded: 1,
|
||||
sha,
|
||||
});
|
||||
setTimeout(await uploadImg(imgIds, index + 1), 500);
|
||||
};
|
||||
|
||||
const uploadImgs = async () => {
|
||||
// 新增的图片
|
||||
const imgIds = await localDbSvc.getWaitUploadImgIds();
|
||||
if (imgIds.length > 0) {
|
||||
await uploadImg(imgIds);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Enqueue a sync task, if possible.
|
||||
*/
|
||||
@ -888,6 +944,8 @@ const requestSync = (addTriggerSyncBadge = false) => {
|
||||
// all the syncedContent objects.
|
||||
await syncFile(store.getters['file/current'].id);
|
||||
}
|
||||
// 同步图片
|
||||
await uploadImgs();
|
||||
|
||||
// Clean files
|
||||
Object.entries(fileHashesToClean).forEach(([fileId, fileHash]) => {
|
||||
@ -983,6 +1041,7 @@ export default {
|
||||
}, 5000);
|
||||
}
|
||||
},
|
||||
syncImg,
|
||||
isSyncPossible,
|
||||
requestSync,
|
||||
createSyncLocation,
|
||||
|
@ -190,6 +190,19 @@ export default {
|
||||
reader.onerror = error => reject(error);
|
||||
});
|
||||
},
|
||||
base64ToBlob(dataurl, fileName) {
|
||||
const potIdx = fileName.lastIndexOf('.');
|
||||
const suffix = potIdx > -1 ? fileName.substring(potIdx + 1) : 'png';
|
||||
const mime = `image/${suffix}`;
|
||||
const bstr = atob(dataurl);
|
||||
let n = bstr.length;
|
||||
const u8arr = new Uint8Array(n);
|
||||
while (n >= 0) {
|
||||
n -= 1;
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
return new Blob([u8arr], { type: mime });
|
||||
},
|
||||
decodeBase64(str) {
|
||||
// In case of URL safe base64
|
||||
const sanitizedStr = str.replace(/_/g, '/').replace(/-/g, '+');
|
||||
@ -370,4 +383,18 @@ export default {
|
||||
elt.parentNode.removeChild(elt);
|
||||
});
|
||||
},
|
||||
// 根据当前绝对路径 与 文件路径计算出文件绝对路径
|
||||
getAbsoluteFilePath(currAbsolutePath, filePath) {
|
||||
// "/"开头说明已经是绝对路径
|
||||
if (filePath.indexOf('/') === 0) {
|
||||
return filePath;
|
||||
}
|
||||
let path = filePath;
|
||||
if (filePath.indexOf('./') === 0) {
|
||||
path = `${currAbsolutePath}/${path.replace('./', '')}`;
|
||||
} else {
|
||||
path = `${currAbsolutePath}/${path}`;
|
||||
}
|
||||
return path.indexOf('/') === 0 ? path : `/${path}`;
|
||||
},
|
||||
};
|
||||
|
@ -1,23 +1,26 @@
|
||||
const localKey = 'img/checkedStorage';
|
||||
import utils from '../services/utils';
|
||||
|
||||
const checkStorageLocalKey = 'img/checkedStorage';
|
||||
const workspacePathLocalKey = 'img/workspaceImgPath';
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
// 来自粘贴板 或者 拖拽的图片的文件对象
|
||||
currImg: null,
|
||||
// 当前图片ID
|
||||
// 当前图片上传中的临时ID
|
||||
currImgId: null,
|
||||
// 选择的存储图床信息
|
||||
checkedStorage: {
|
||||
type: null, // 目前存储类型分两种 token 与 tokenRepo
|
||||
type: 'workspace', // 目前存储类型分三种 token 与 tokenRepo 、workspace
|
||||
provider: null, // 对应是何种账号
|
||||
sub: null, // 对应 token 中的sub
|
||||
sub: '/imgs/{YYYY}-{MM}-{DD}', // 对应 token 中的sub
|
||||
sid: null,
|
||||
},
|
||||
// 当前仓库图片存储位置 key 为path value 为true
|
||||
workspaceImagePath: {
|
||||
'/imgs/{YYYY}-{MM}-{DD}': true,
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setNewImg: (state, value) => {
|
||||
state.currImg = value;
|
||||
},
|
||||
setCurrImgId: (state, value) => {
|
||||
state.currImgId = value;
|
||||
},
|
||||
@ -41,17 +44,28 @@ export default {
|
||||
};
|
||||
}
|
||||
},
|
||||
setWorkspaceImgPath: (state, value) => {
|
||||
state.workspaceImagePath = value;
|
||||
localStorage.setItem(workspacePathLocalKey, JSON.stringify(state.workspaceImagePath));
|
||||
},
|
||||
addWorkspaceImgPath: (state, value) => {
|
||||
state.workspaceImagePath[value] = true;
|
||||
state.workspaceImagePath = utils.deepCopy(state.workspaceImagePath);
|
||||
localStorage.setItem(workspacePathLocalKey, JSON.stringify(state.workspaceImagePath));
|
||||
},
|
||||
removeWorkspaceImgPath: (state, value) => {
|
||||
delete state.workspaceImagePath[value];
|
||||
state.workspaceImagePath = utils.deepCopy(state.workspaceImagePath);
|
||||
localStorage.setItem(workspacePathLocalKey, JSON.stringify(state.workspaceImagePath));
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
getImg: state => state.currImg,
|
||||
currImgId: state => state.currImgId,
|
||||
getCheckedStorage: state => state.checkedStorage,
|
||||
getCheckedStorageSub: state => state.checkedStorage.sub,
|
||||
getWorkspaceImgPath: state => state.workspaceImagePath,
|
||||
},
|
||||
actions: {
|
||||
setImg({ commit }, img) {
|
||||
commit('setNewImg', img);
|
||||
},
|
||||
setCurrImgId({ commit }, imgId) {
|
||||
commit('setCurrImgId', imgId);
|
||||
},
|
||||
@ -60,7 +74,16 @@ export default {
|
||||
},
|
||||
changeCheckedStorage({ commit }, checkedStorage) {
|
||||
commit('changeCheckedStorage', checkedStorage);
|
||||
localStorage.setItem(localKey, JSON.stringify(checkedStorage));
|
||||
localStorage.setItem(checkStorageLocalKey, JSON.stringify(checkedStorage));
|
||||
},
|
||||
setWorkspaceImgPath({ commit }, workspaceImgPath) {
|
||||
commit('setWorkspaceImgPath', workspaceImgPath);
|
||||
},
|
||||
addWorkspaceImgPath({ commit }, workspaceImgPathValue) {
|
||||
commit('addWorkspaceImgPath', workspaceImgPathValue);
|
||||
},
|
||||
removeWorkspaceImgPath({ commit }, workspaceImgPathValue) {
|
||||
commit('removeWorkspaceImgPath', workspaceImgPathValue);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user