diff --git a/chrome-app/manifest.json b/chrome-app/manifest.json
index 721142f7..038a65c7 100644
--- a/chrome-app/manifest.json
+++ b/chrome-app/manifest.json
@@ -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",
diff --git a/package-lock.json b/package-lock.json
index abdaafe5..16eafb33 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "stackedit",
- "version": "5.15.14",
+ "version": "5.15.15",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index cfafecbe..d95dd2bf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "stackedit",
- "version": "5.15.14",
+ "version": "5.15.15",
"description": "免费, 开源, 功能齐全的 Markdown 编辑器",
"author": "Benoit Schweblin, 豆萁",
"license": "Apache-2.0",
diff --git a/src/components/Editor.vue b/src/components/Editor.vue
index 5aebac3f..6569c0a4 100644
--- a/src/components/Editor.vue
+++ b/src/components/Editor.vue
@@ -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(
diff --git a/src/components/Modal.vue b/src/components/Modal.vue
index 1e3920c8..b3f224bd 100644
--- a/src/components/Modal.vue
+++ b/src/components/Modal.vue
@@ -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,
diff --git a/src/components/modals/ImageModal.vue b/src/components/modals/ImageModal.vue
index 4c5324af..9772fb5c 100644
--- a/src/components/modals/ImageModal.vue
+++ b/src/components/modals/ImageModal.vue
@@ -15,6 +15,21 @@
添加并选择图床后可在编辑区中粘贴/拖拽图片自动上传
+
+
+
+
+
+
+
+ 本文档空间图片路径
+
+
+ 路径:{{path}}
+
+
@@ -45,6 +60,10 @@
{{tokenStorage.uname}}, 仓库URL: {{tokenStorage.repoUrl}}, 路径: {{tokenStorage.path}}, 分支: {{tokenStorage.branch}}
+
+
+ 添加本文档空间图片路径
+
添加SM.MS图床账号
@@ -66,6 +85,7 @@
diff --git a/src/services/editorSvc.js b/src/services/editorSvc.js
index c5f8789a..f11ce180 100644
--- a/src/services/editorSvc.js
+++ b/src/services/editorSvc.js
@@ -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);
diff --git a/src/services/imageSvc.js b/src/services/imageSvc.js
index f5a6c24f..b811349f 100644
--- a/src/services/imageSvc.js
+++ b/src/services/imageSvc.js
@@ -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;
}
diff --git a/src/services/localDbSvc.js b/src/services/localDbSvc.js
index 855e3f7d..11b4fc08 100644
--- a/src/services/localDbSvc.js
+++ b/src/services/localDbSvc.js
@@ -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.
diff --git a/src/services/providers/giteaWorkspaceProvider.js b/src/services/providers/giteaWorkspaceProvider.js
index f97d7723..97138707 100644
--- a/src/services/providers/giteaWorkspaceProvider.js
+++ b/src/services/providers/giteaWorkspaceProvider.js
@@ -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,
diff --git a/src/services/providers/giteeAppDataProvider.js b/src/services/providers/giteeAppDataProvider.js
index 401e1e9f..e0f87db9 100644
--- a/src/services/providers/giteeAppDataProvider.js
+++ b/src/services/providers/giteeAppDataProvider.js
@@ -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: {
diff --git a/src/services/providers/giteeWorkspaceProvider.js b/src/services/providers/giteeWorkspaceProvider.js
index 65551072..b9797ae7 100644
--- a/src/services/providers/giteeWorkspaceProvider.js
+++ b/src/services/providers/giteeWorkspaceProvider.js
@@ -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: {
diff --git a/src/services/providers/githubWorkspaceProvider.js b/src/services/providers/githubWorkspaceProvider.js
index 9a46822e..cc5e9387 100644
--- a/src/services/providers/githubWorkspaceProvider.js
+++ b/src/services/providers/githubWorkspaceProvider.js
@@ -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: {
diff --git a/src/services/providers/helpers/giteaHelper.js b/src/services/providers/helpers/giteaHelper.js
index 7f84b2f9..d9def936 100644
--- a/src/services/providers/helpers/giteaHelper.js
+++ b/src/services/providers/helpers/giteaHelper.js
@@ -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,
};
},
};
diff --git a/src/services/providers/helpers/giteeHelper.js b/src/services/providers/helpers/giteeHelper.js
index f8db1c9c..a6ba57d6 100644
--- a/src/services/providers/helpers/giteeHelper.js
+++ b/src/services/providers/helpers/giteeHelper.js
@@ -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,
diff --git a/src/services/providers/helpers/githubHelper.js b/src/services/providers/helpers/githubHelper.js
index ba61d406..c5285c6b 100644
--- a/src/services/providers/helpers/githubHelper.js
+++ b/src/services/providers/helpers/githubHelper.js
@@ -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,
};
},
/**
diff --git a/src/services/syncSvc.js b/src/services/syncSvc.js
index 3029851f..4f3f1ee7 100644
--- a/src/services/syncSvc.js
+++ b/src/services/syncSvc.js
@@ -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,
diff --git a/src/services/utils.js b/src/services/utils.js
index 88d27d3a..60bfbf36 100644
--- a/src/services/utils.js
+++ b/src/services/utils.js
@@ -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}`;
+ },
};
diff --git a/src/store/img.js b/src/store/img.js
index 7ba4499d..fe40455a 100644
--- a/src/store/img.js
+++ b/src/store/img.js
@@ -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);
},
},
};