mirror of
https://gitee.com/mafgwo/stackedit
synced 2024-11-15 19:22:27 +08:00
主空间修改为gitee
This commit is contained in:
parent
b94898960a
commit
f0612d1120
|
@ -14,7 +14,7 @@ StackEdit的作者可能因为什么原因,已经很久不维护了,Github
|
|||
### TODO: 关于后续的一些想法
|
||||
- 支持**Gitea**、**Gogs**两个轻量级且适于自建的Git仓库(毕竟Gitlab对机器配置要求较高)。想支持这两个主要也是考虑到其实很多公司已经禁用了Github或Gitee仓库,在公司都没法连上自己的Git仓库。
|
||||
- 汉化,毕竟大家最熟悉的还是母语,并且该编辑器功能页面也不多,汉化工作量并不会很大。
|
||||
- 替换主工作区为Gitee(原版本主工作区是Google Drive,国内只有fan墙才可以用)
|
||||
- 替换主文档空间为Gitee(原版本主文档空间是Google Drive,国内只有fan墙才可以用)
|
||||
- 引入mdnice,右边预览增加mdnice预览选项,主要含选主题(含mdnice常用20多个主题)、支持自定义主题、复制到公众号、复制到知乎、复制到稀土掘金等基本功能,便于喜欢写公众号、博客的同学可以更好更快的排版。
|
||||
- ... 另外,朋友们有好的想法也可以在Issue或者加我微信 qicoding 提给我。
|
||||
|
||||
|
@ -27,7 +27,7 @@ StackEdit的作者可能因为什么原因,已经很久不维护了,Github
|
|||
|
||||
**已汉化主要功能部分(2022-06-01)**
|
||||
|
||||
**接下来修改主工作区为Gitee**
|
||||
**接下来修改主文档空间为Gitee**
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "StackEdit中文版",
|
||||
"description": "浏览器内 Markdown 编辑器",
|
||||
"description": "支持Gitee仓库的浏览器内 Markdown 编辑器",
|
||||
"version": "1.0.13",
|
||||
"manifest_version": 2,
|
||||
"container" : "GOOGLE_DRIVE",
|
||||
|
|
196
server/pandoc.js
196
server/pandoc.js
|
@ -2,7 +2,6 @@
|
|||
const { spawn } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const tmp = require('tmp');
|
||||
const user = require('./user');
|
||||
const conf = require('./conf');
|
||||
|
||||
const outputFormats = {
|
||||
|
@ -42,108 +41,101 @@ exports.generate = (req, res) => {
|
|||
const outputFormat = Object.prototype.hasOwnProperty.call(outputFormats, req.query.format)
|
||||
? req.query.format
|
||||
: 'pdf';
|
||||
user.checkSponsor(req.query.idToken)
|
||||
.then((isSponsor) => {
|
||||
if (!isSponsor) {
|
||||
throw new Error('unauthorized');
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
tmp.file({
|
||||
postfix: `.${outputFormat}`,
|
||||
}, (err, filePath, fd, cleanupCallback) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({
|
||||
filePath,
|
||||
cleanupCallback,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
|
||||
const options = readJson(req.query.options);
|
||||
const metadata = readJson(req.query.metadata);
|
||||
const params = [];
|
||||
|
||||
params.push('--latex-engine=xelatex');
|
||||
params.push('--webtex=http://chart.apis.google.com/chart?cht=tx&chf=bg,s,FFFFFF00&chco=000000&chl=');
|
||||
if (options.toc) {
|
||||
params.push('--toc');
|
||||
}
|
||||
options.tocDepth = parseInt(options.tocDepth, 10);
|
||||
if (!Number.isNaN(options.tocDepth)) {
|
||||
params.push('--toc-depth', options.tocDepth);
|
||||
}
|
||||
options.highlightStyle = highlightStyles.includes(options.highlightStyle) ? options.highlightStyle : 'kate';
|
||||
params.push('--highlight-style', options.highlightStyle);
|
||||
Object.keys(metadata).forEach((key) => {
|
||||
params.push('-M', `${key}=${metadata[key]}`);
|
||||
});
|
||||
|
||||
let finished = false;
|
||||
|
||||
function onError(error) {
|
||||
finished = true;
|
||||
cleanupCallback();
|
||||
reject(error);
|
||||
}
|
||||
|
||||
const format = outputFormat === 'pdf' ? 'latex' : outputFormat;
|
||||
params.push('-f', 'json', '-t', format, '-o', filePath);
|
||||
const pandoc = spawn(conf.values.pandocPath, params, {
|
||||
stdio: [
|
||||
'pipe',
|
||||
'ignore',
|
||||
'pipe',
|
||||
],
|
||||
});
|
||||
let timeoutId = setTimeout(() => {
|
||||
timeoutId = null;
|
||||
pandoc.kill();
|
||||
}, 50000);
|
||||
pandoc.on('error', onError);
|
||||
pandoc.stdin.on('error', onError);
|
||||
pandoc.stderr.on('data', (data) => {
|
||||
pandocError += `${data}`;
|
||||
});
|
||||
pandoc.on('close', (code) => {
|
||||
if (!finished) {
|
||||
clearTimeout(timeoutId);
|
||||
if (!timeoutId) {
|
||||
res.statusCode = 408;
|
||||
cleanupCallback();
|
||||
reject(new Error('timeout'));
|
||||
} else if (code) {
|
||||
cleanupCallback();
|
||||
reject();
|
||||
} else {
|
||||
res.set('Content-Type', outputFormats[outputFormat]);
|
||||
const readStream = fs.createReadStream(filePath);
|
||||
readStream.on('open', () => readStream.pipe(res));
|
||||
readStream.on('close', () => cleanupCallback());
|
||||
readStream.on('error', () => {
|
||||
cleanupCallback();
|
||||
reject();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
req.pipe(pandoc.stdin);
|
||||
}))
|
||||
.catch((err) => {
|
||||
const message = err && err.message;
|
||||
if (message === 'unauthorized') {
|
||||
res.statusCode = 401;
|
||||
res.end('Unauthorized.');
|
||||
} else if (message === 'timeout') {
|
||||
res.statusCode = 408;
|
||||
res.end('Request timeout.');
|
||||
new Promise((resolve, reject) => {
|
||||
tmp.file({
|
||||
postfix: `.${outputFormat}`,
|
||||
}, (err, filePath, fd, cleanupCallback) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
res.statusCode = 400;
|
||||
res.end(pandocError || 'Unknown error.');
|
||||
resolve({
|
||||
filePath,
|
||||
cleanupCallback,
|
||||
});
|
||||
}
|
||||
});
|
||||
}).then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
|
||||
const options = readJson(req.query.options);
|
||||
const metadata = readJson(req.query.metadata);
|
||||
const params = [];
|
||||
|
||||
params.push('--pdf-engine=xelatex');
|
||||
params.push('--webtex=http://chart.apis.google.com/chart?cht=tx&chf=bg,s,FFFFFF00&chco=000000&chl=');
|
||||
if (options.toc) {
|
||||
params.push('--toc');
|
||||
}
|
||||
options.tocDepth = parseInt(options.tocDepth, 10);
|
||||
if (!Number.isNaN(options.tocDepth)) {
|
||||
params.push('--toc-depth', options.tocDepth);
|
||||
}
|
||||
options.highlightStyle = highlightStyles.includes(options.highlightStyle) ? options.highlightStyle : 'kate';
|
||||
params.push('--highlight-style', options.highlightStyle);
|
||||
Object.keys(metadata).forEach((key) => {
|
||||
params.push('-M', `${key}=${metadata[key]}`);
|
||||
});
|
||||
|
||||
let finished = false;
|
||||
|
||||
function onError(error) {
|
||||
finished = true;
|
||||
cleanupCallback();
|
||||
reject(error);
|
||||
}
|
||||
|
||||
const format = outputFormat === 'pdf' ? 'latex' : outputFormat;
|
||||
params.push('-f', 'json', '-t', format, '-o', filePath);
|
||||
const pandoc = spawn(conf.values.pandocPath, params, {
|
||||
stdio: [
|
||||
'pipe',
|
||||
'ignore',
|
||||
'pipe',
|
||||
],
|
||||
});
|
||||
let timeoutId = setTimeout(() => {
|
||||
timeoutId = null;
|
||||
pandoc.kill();
|
||||
}, 50000);
|
||||
pandoc.on('error', onError);
|
||||
pandoc.stdin.on('error', onError);
|
||||
pandoc.stderr.on('data', (data) => {
|
||||
pandocError += `${data}`;
|
||||
});
|
||||
pandoc.on('close', (code) => {
|
||||
if (!finished) {
|
||||
clearTimeout(timeoutId);
|
||||
if (!timeoutId) {
|
||||
res.statusCode = 408;
|
||||
cleanupCallback();
|
||||
reject(new Error('timeout'));
|
||||
} else if (code) {
|
||||
cleanupCallback();
|
||||
reject();
|
||||
} else {
|
||||
res.set('Content-Type', outputFormats[outputFormat]);
|
||||
const readStream = fs.createReadStream(filePath);
|
||||
readStream.on('open', () => readStream.pipe(res));
|
||||
readStream.on('close', () => cleanupCallback());
|
||||
readStream.on('error', () => {
|
||||
cleanupCallback();
|
||||
reject();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
req.pipe(pandoc.stdin);
|
||||
}))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
const message = err && err.message;
|
||||
if (message === 'unauthorized') {
|
||||
res.statusCode = 401;
|
||||
res.end('Unauthorized.');
|
||||
} else if (message === 'timeout') {
|
||||
res.statusCode = 408;
|
||||
res.end('Request timeout.');
|
||||
} else {
|
||||
res.statusCode = 400;
|
||||
res.end(pandocError || 'Unknown error.');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
249
server/pdf.js
249
server/pdf.js
|
@ -2,7 +2,6 @@
|
|||
const { spawn } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const tmp = require('tmp');
|
||||
const user = require('./user');
|
||||
const conf = require('./conf');
|
||||
|
||||
/* eslint-disable no-var, prefer-arrow-callback, func-names */
|
||||
|
@ -51,135 +50,129 @@ const readJson = (str) => {
|
|||
|
||||
exports.generate = (req, res) => {
|
||||
let wkhtmltopdfError = '';
|
||||
user.checkSponsor(req.query.idToken)
|
||||
.then((isSponsor) => {
|
||||
if (!isSponsor) {
|
||||
throw new Error('unauthorized');
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
tmp.file((err, filePath, fd, cleanupCallback) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({
|
||||
filePath,
|
||||
cleanupCallback,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
|
||||
let finished = false;
|
||||
|
||||
function onError(err) {
|
||||
finished = true;
|
||||
cleanupCallback();
|
||||
new Promise((resolve, reject) => {
|
||||
tmp.file((err, filePath, fd, cleanupCallback) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
const options = readJson(req.query.options);
|
||||
const params = [];
|
||||
|
||||
// Margins
|
||||
const marginTop = parseInt(`${options.marginTop}`, 10);
|
||||
params.push('-T', Number.isNaN(marginTop) ? 25 : marginTop);
|
||||
const marginRight = parseInt(`${options.marginRight}`, 10);
|
||||
params.push('-R', Number.isNaN(marginRight) ? 25 : marginRight);
|
||||
const marginBottom = parseInt(`${options.marginBottom}`, 10);
|
||||
params.push('-B', Number.isNaN(marginBottom) ? 25 : marginBottom);
|
||||
const marginLeft = parseInt(`${options.marginLeft}`, 10);
|
||||
params.push('-L', Number.isNaN(marginLeft) ? 25 : marginLeft);
|
||||
|
||||
// Header
|
||||
if (options.headerCenter) {
|
||||
params.push('--header-center', `${options.headerCenter}`);
|
||||
}
|
||||
if (options.headerLeft) {
|
||||
params.push('--header-left', `${options.headerLeft}`);
|
||||
}
|
||||
if (options.headerRight) {
|
||||
params.push('--header-right', `${options.headerRight}`);
|
||||
}
|
||||
if (options.headerFontName) {
|
||||
params.push('--header-font-name', `${options.headerFontName}`);
|
||||
}
|
||||
if (options.headerFontSize) {
|
||||
params.push('--header-font-size', `${options.headerFontSize}`);
|
||||
}
|
||||
|
||||
// Footer
|
||||
if (options.footerCenter) {
|
||||
params.push('--footer-center', `${options.footerCenter}`);
|
||||
}
|
||||
if (options.footerLeft) {
|
||||
params.push('--footer-left', `${options.footerLeft}`);
|
||||
}
|
||||
if (options.footerRight) {
|
||||
params.push('--footer-right', `${options.footerRight}`);
|
||||
}
|
||||
if (options.footerFontName) {
|
||||
params.push('--footer-font-name', `${options.footerFontName}`);
|
||||
}
|
||||
if (options.footerFontSize) {
|
||||
params.push('--footer-font-size', `${options.footerFontSize}`);
|
||||
}
|
||||
|
||||
// Page size
|
||||
params.push('--page-size', !authorizedPageSizes.includes(options.pageSize) ? 'A4' : options.pageSize);
|
||||
|
||||
// Use a temp file as wkhtmltopdf can't access /dev/stdout on Amazon EC2 for some reason
|
||||
params.push('--run-script', `${waitForJavaScript.toString()}waitForJavaScript()`);
|
||||
params.push('--window-status', 'done');
|
||||
const wkhtmltopdf = spawn(conf.values.wkhtmltopdfPath, params.concat('-', filePath), {
|
||||
stdio: [
|
||||
'pipe',
|
||||
'ignore',
|
||||
'pipe',
|
||||
],
|
||||
});
|
||||
let timeoutId = setTimeout(function () {
|
||||
timeoutId = null;
|
||||
wkhtmltopdf.kill();
|
||||
}, 50000);
|
||||
wkhtmltopdf.on('error', onError);
|
||||
wkhtmltopdf.stdin.on('error', onError);
|
||||
wkhtmltopdf.stderr.on('data', (data) => {
|
||||
wkhtmltopdfError += `${data}`;
|
||||
});
|
||||
wkhtmltopdf.on('close', (code) => {
|
||||
if (!finished) {
|
||||
clearTimeout(timeoutId);
|
||||
if (!timeoutId) {
|
||||
cleanupCallback();
|
||||
reject(new Error('timeout'));
|
||||
} else if (code) {
|
||||
cleanupCallback();
|
||||
reject();
|
||||
} else {
|
||||
res.set('Content-Type', 'application/pdf');
|
||||
const readStream = fs.createReadStream(filePath);
|
||||
readStream.on('open', () => readStream.pipe(res));
|
||||
readStream.on('close', () => cleanupCallback());
|
||||
readStream.on('error', () => {
|
||||
cleanupCallback();
|
||||
reject();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
req.pipe(wkhtmltopdf.stdin);
|
||||
}))
|
||||
.catch((err) => {
|
||||
const message = err && err.message;
|
||||
if (message === 'unauthorized') {
|
||||
res.statusCode = 401;
|
||||
res.end('Unauthorized.');
|
||||
} else if (message === 'timeout') {
|
||||
res.statusCode = 408;
|
||||
res.end('Request timeout.');
|
||||
} else {
|
||||
res.statusCode = 400;
|
||||
res.end(wkhtmltopdfError || 'Unknown error.');
|
||||
resolve({
|
||||
filePath,
|
||||
cleanupCallback,
|
||||
});
|
||||
}
|
||||
});
|
||||
}).then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
|
||||
let finished = false;
|
||||
|
||||
function onError(err) {
|
||||
finished = true;
|
||||
cleanupCallback();
|
||||
reject(err);
|
||||
}
|
||||
const options = readJson(req.query.options);
|
||||
const params = [];
|
||||
|
||||
// Margins
|
||||
const marginTop = parseInt(`${options.marginTop}`, 10);
|
||||
params.push('-T', Number.isNaN(marginTop) ? 25 : marginTop);
|
||||
const marginRight = parseInt(`${options.marginRight}`, 10);
|
||||
params.push('-R', Number.isNaN(marginRight) ? 25 : marginRight);
|
||||
const marginBottom = parseInt(`${options.marginBottom}`, 10);
|
||||
params.push('-B', Number.isNaN(marginBottom) ? 25 : marginBottom);
|
||||
const marginLeft = parseInt(`${options.marginLeft}`, 10);
|
||||
params.push('-L', Number.isNaN(marginLeft) ? 25 : marginLeft);
|
||||
|
||||
// Header
|
||||
if (options.headerCenter) {
|
||||
params.push('--header-center', `${options.headerCenter}`);
|
||||
}
|
||||
if (options.headerLeft) {
|
||||
params.push('--header-left', `${options.headerLeft}`);
|
||||
}
|
||||
if (options.headerRight) {
|
||||
params.push('--header-right', `${options.headerRight}`);
|
||||
}
|
||||
if (options.headerFontName) {
|
||||
params.push('--header-font-name', `${options.headerFontName}`);
|
||||
}
|
||||
if (options.headerFontSize) {
|
||||
params.push('--header-font-size', `${options.headerFontSize}`);
|
||||
}
|
||||
|
||||
// Footer
|
||||
if (options.footerCenter) {
|
||||
params.push('--footer-center', `${options.footerCenter}`);
|
||||
}
|
||||
if (options.footerLeft) {
|
||||
params.push('--footer-left', `${options.footerLeft}`);
|
||||
}
|
||||
if (options.footerRight) {
|
||||
params.push('--footer-right', `${options.footerRight}`);
|
||||
}
|
||||
if (options.footerFontName) {
|
||||
params.push('--footer-font-name', `${options.footerFontName}`);
|
||||
}
|
||||
if (options.footerFontSize) {
|
||||
params.push('--footer-font-size', `${options.footerFontSize}`);
|
||||
}
|
||||
|
||||
// Page size
|
||||
params.push('--page-size', !authorizedPageSizes.includes(options.pageSize) ? 'A4' : options.pageSize);
|
||||
|
||||
// Use a temp file as wkhtmltopdf can't access /dev/stdout on Amazon EC2 for some reason
|
||||
params.push('--run-script', `${waitForJavaScript.toString()}waitForJavaScript()`);
|
||||
params.push('--window-status', 'done');
|
||||
const wkhtmltopdf = spawn(conf.values.wkhtmltopdfPath, params.concat('-', filePath), {
|
||||
stdio: [
|
||||
'pipe',
|
||||
'ignore',
|
||||
'pipe',
|
||||
],
|
||||
});
|
||||
let timeoutId = setTimeout(function () {
|
||||
timeoutId = null;
|
||||
wkhtmltopdf.kill();
|
||||
}, 50000);
|
||||
wkhtmltopdf.on('error', onError);
|
||||
wkhtmltopdf.stdin.on('error', onError);
|
||||
wkhtmltopdf.stderr.on('data', (data) => {
|
||||
wkhtmltopdfError += `${data}`;
|
||||
});
|
||||
wkhtmltopdf.on('close', (code) => {
|
||||
if (!finished) {
|
||||
clearTimeout(timeoutId);
|
||||
if (!timeoutId) {
|
||||
cleanupCallback();
|
||||
reject(new Error('timeout'));
|
||||
} else if (code) {
|
||||
cleanupCallback();
|
||||
reject();
|
||||
} else {
|
||||
res.set('Content-Type', 'application/pdf');
|
||||
const readStream = fs.createReadStream(filePath);
|
||||
readStream.on('open', () => readStream.pipe(res));
|
||||
readStream.on('close', () => cleanupCallback());
|
||||
readStream.on('error', () => {
|
||||
cleanupCallback();
|
||||
reject();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
req.pipe(wkhtmltopdf.stdin);
|
||||
}))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
const message = err && err.message;
|
||||
if (message === 'unauthorized') {
|
||||
res.statusCode = 401;
|
||||
res.end('Unauthorized.');
|
||||
} else if (message === 'timeout') {
|
||||
res.statusCode = 408;
|
||||
res.end('Request timeout.');
|
||||
} else {
|
||||
res.statusCode = 400;
|
||||
res.end(wkhtmltopdfError || 'Unknown error.');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div class="modal" v-if="config" @keydown.esc.stop="onEscape" @keydown.tab="onTab" @focusin="onFocusInOut" @focusout="onFocusInOut">
|
||||
<div class="modal__sponsor-banner" v-if="!isSponsor">
|
||||
<!-- <div class="modal__sponsor-banner" v-if="!isSponsor">
|
||||
StackEdit is <a class="not-tabbable" target="_blank" href="https://github.com/mafgwo/stackedit/">open source</a>, please consider
|
||||
<a class="not-tabbable" href="javascript:void(0)" @click="sponsor">sponsoring</a> for just $5.
|
||||
</div>
|
||||
</div> -->
|
||||
<component v-if="currentModalComponent" :is="currentModalComponent"></component>
|
||||
<modal-inner v-else aria-label="Dialog">
|
||||
<div class="modal__content" v-html="simpleModal.contentHtml(config)"></div>
|
||||
|
@ -20,7 +20,7 @@ import { mapGetters } from 'vuex';
|
|||
import simpleModals from '../data/simpleModals';
|
||||
import editorSvc from '../services/editorSvc';
|
||||
import syncSvc from '../services/syncSvc';
|
||||
import googleHelper from '../services/providers/helpers/googleHelper';
|
||||
import giteeHelper from '../services/providers/helpers/giteeHelper';
|
||||
import store from '../store';
|
||||
|
||||
import ModalInner from './modals/common/ModalInner';
|
||||
|
@ -168,7 +168,7 @@ export default {
|
|||
if (!store.getters['workspace/sponsorToken']) {
|
||||
// User has to sign in
|
||||
await store.dispatch('modal/open', 'signInForSponsorship');
|
||||
await googleHelper.signin();
|
||||
await giteeHelper.signin();
|
||||
syncSvc.requestSync();
|
||||
}
|
||||
if (!store.getters.isSponsor) {
|
||||
|
|
|
@ -47,14 +47,14 @@ import store from '../store';
|
|||
|
||||
const panelNames = {
|
||||
menu: '菜单',
|
||||
workspaces: '工作区',
|
||||
workspaces: '文档空间',
|
||||
help: 'Markdown 帮助',
|
||||
toc: '目录',
|
||||
sync: '同步',
|
||||
publish: '发布',
|
||||
history: '文件历史',
|
||||
importExport: '导入/导出',
|
||||
workspaceBackups: '工作区备份',
|
||||
workspaceBackups: '文档空间备份',
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<span v-for="stat in textStats" :key="stat.id">
|
||||
<span class="stat-panel__value">{{stat.value}}</span> {{stat.name}}
|
||||
</span>
|
||||
<span class="stat-panel__value">Ln {{line}}, Col {{column}}</span>
|
||||
<span class="stat-panel__value">第 {{line}} 行, 第 {{column}} 列</span>
|
||||
</div>
|
||||
<div class="stat-panel__block stat-panel__block--right">
|
||||
<span class="stat-panel__block-name">
|
||||
|
@ -43,14 +43,13 @@ export default {
|
|||
line: 0,
|
||||
column: 0,
|
||||
textStats: [
|
||||
new Stat('bytes', '[\\s\\S]'),
|
||||
new Stat('words', '\\S+'),
|
||||
new Stat('lines', '\n'),
|
||||
new Stat('字符', '[\\s\\S]'),
|
||||
new Stat('字数', '\\S'),
|
||||
new Stat('行数', '\n'),
|
||||
],
|
||||
htmlStats: [
|
||||
new Stat('characters', '\\S'),
|
||||
new Stat('words', '\\S+'),
|
||||
new Stat('paragraphs', '\\S.*'),
|
||||
new Stat('字数', '\\S'),
|
||||
new Stat('段落', '\\S.*'),
|
||||
],
|
||||
}),
|
||||
computed: mapGetters('layout', [
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
</div>
|
||||
<div class="tour-step__inner" v-else-if="step === 'explorer'">
|
||||
<h2>文件资源管理器</h2>
|
||||
<p>StackEdit可以管理工作区中的多个文件和文件夹。</p>
|
||||
<p>StackEdit可以管理文档空间中的多个文件和文件夹。</p>
|
||||
<p>点击 <icon-folder></icon-folder> 打开文件资源管理器。</p>
|
||||
<div class="tour-step__button-bar">
|
||||
<button class="button" @click="finish">跳过</button>
|
||||
|
@ -30,7 +30,7 @@
|
|||
</div>
|
||||
<div class="tour-step__inner" v-else-if="step === 'menu'">
|
||||
<h2>更多!</h2>
|
||||
<p>StackEdit还可以同步和发布文件,管理协作工作区...</p>
|
||||
<p>StackEdit还可以同步和发布文件,管理协作文档空间...</p>
|
||||
<p>点击 <icon-provider provider-id="stackedit"></icon-provider> 浏览菜单。</p>
|
||||
<div class="tour-step__button-bar">
|
||||
<button class="button" @click="finish">跳过</button>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</option>
|
||||
</select>
|
||||
</p>
|
||||
<p v-if="!historyContext">同步 <b>{{currentFileName}}</b> 以启用修订历史 或者 <a href="javascript:void(0)" @click="signin">登录 Google</a> 以同步您的主工作区。</p>
|
||||
<p v-if="!historyContext">同步 <b>{{currentFileName}}</b> 以启用修订历史 或者 <a href="javascript:void(0)" @click="signin">登录 Gitee</a> 以同步您的主文档空间。</p>
|
||||
<p v-else-if="loading">历史版本加载中…</p>
|
||||
<p v-else-if="!revisionsWithSpacer.length"><b>{{currentFileName}}</b> 没有历史版本.</p>
|
||||
<div class="menu-entry menu-entry--info flex flex--row flex--align-center" v-else>
|
||||
|
@ -53,7 +53,7 @@ import UserName from '../UserName';
|
|||
import EditorClassApplier from '../common/EditorClassApplier';
|
||||
import PreviewClassApplier from '../common/PreviewClassApplier';
|
||||
import utils from '../../services/utils';
|
||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
import giteeHelper from '../../services/providers/helpers/giteeHelper';
|
||||
import syncSvc from '../../services/syncSvc';
|
||||
import store from '../../store';
|
||||
import badgeSvc from '../../services/badgeSvc';
|
||||
|
@ -166,7 +166,7 @@ export default {
|
|||
]),
|
||||
async signin() {
|
||||
try {
|
||||
await googleHelper.signin();
|
||||
await giteeHelper.signin();
|
||||
syncSvc.requestSync();
|
||||
} catch (e) {
|
||||
// Cancel
|
||||
|
|
|
@ -33,14 +33,14 @@
|
|||
</menu-entry>
|
||||
<menu-entry @click.native="exportPdf">
|
||||
<icon-download slot="icon"></icon-download>
|
||||
<div><div class="menu-entry__label" :class="{'menu-entry__label--warning': !isSponsor}">赞助商</div> 导出为 HTML PDF</div>
|
||||
<div>导出为 HTML PDF</div>
|
||||
<span>从HTML模板生成PDF。</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="exportPandoc">
|
||||
<!-- <menu-entry @click.native="exportPandoc">
|
||||
<icon-download slot="icon"></icon-download>
|
||||
<div><div class="menu-entry__label" :class="{'menu-entry__label--warning': !isSponsor}">赞助商</div> 导出为 HTML Pandoc</div>
|
||||
<div>导出为 HTML Pandoc</div>
|
||||
<span>转换为PDF、Word、EPUB...</span>
|
||||
</menu-entry>
|
||||
</menu-entry> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
<div class="menu-entry__icon menu-entry__icon--image">
|
||||
<icon-provider :provider-id="currentWorkspace.providerId"></icon-provider>
|
||||
</div>
|
||||
<span v-if="currentWorkspace.providerId === 'googleDriveAppData'">
|
||||
<b>{{currentWorkspace.name}}</b> 与您的 Google Drive 应用数据文件夹同步。
|
||||
<span v-if="currentWorkspace.providerId === 'giteeAppData'">
|
||||
<b>{{currentWorkspace.name}}</b> 与您的 Gitee 默认文档空间仓库同步。
|
||||
</span>
|
||||
<span v-else-if="currentWorkspace.providerId === 'googleDriveWorkspace'">
|
||||
<b>{{currentWorkspace.name}}</b> 与 <a :href="workspaceLocationUrl" target="_blank">Google Drive 文件夹</a>同步。
|
||||
|
@ -42,13 +42,13 @@
|
|||
</div>
|
||||
<menu-entry v-if="!loginToken" @click.native="signin">
|
||||
<icon-login slot="icon"></icon-login>
|
||||
<div>使用 Google 登录</div>
|
||||
<span>同步您的主工作区并解锁功能。</span>
|
||||
<div>使用 Gitee 登录</div>
|
||||
<span>同步您的主文档空间并解锁功能。</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="setPanel('workspaces')">
|
||||
<icon-database slot="icon"></icon-database>
|
||||
<div><div class="menu-entry__label menu-entry__label--count" v-if="workspaceCount">{{workspaceCount}}</div> 工作区</div>
|
||||
<span>切换到另一个工作区。</span>
|
||||
<div><div class="menu-entry__label menu-entry__label--count" v-if="workspaceCount">{{workspaceCount}}</div> 文档空间</div>
|
||||
<span>切换到另一个文档空间。</span>
|
||||
</menu-entry>
|
||||
<hr>
|
||||
<menu-entry @click.native="setPanel('sync')">
|
||||
|
@ -113,7 +113,7 @@
|
|||
<hr>
|
||||
<menu-entry @click.native="setPanel('workspaceBackups')">
|
||||
<icon-content-save slot="icon"></icon-content-save>
|
||||
工作区备份
|
||||
文档空间备份
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="reset">
|
||||
<icon-logout slot="icon"></icon-logout>
|
||||
|
@ -131,7 +131,7 @@ import { mapGetters, mapActions } from 'vuex';
|
|||
import MenuEntry from './common/MenuEntry';
|
||||
import providerRegistry from '../../services/providers/common/providerRegistry';
|
||||
import UserImage from '../UserImage';
|
||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
import giteeHelper from '../../services/providers/helpers/giteeHelper';
|
||||
import syncSvc from '../../services/syncSvc';
|
||||
import userSvc from '../../services/userSvc';
|
||||
import store from '../../store';
|
||||
|
@ -183,7 +183,7 @@ export default {
|
|||
}),
|
||||
async signin() {
|
||||
try {
|
||||
await googleHelper.signin();
|
||||
await giteeHelper.signin();
|
||||
syncSvc.requestSync();
|
||||
} catch (e) {
|
||||
// Cancel
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
<icon-content-save></icon-content-save>
|
||||
</div>
|
||||
<div class="flex flex--column">
|
||||
导入工作区备份
|
||||
导入文档空间备份
|
||||
</div>
|
||||
</label>
|
||||
<menu-entry @click.native="exportWorkspace">
|
||||
<icon-content-save slot="icon"></icon-content-save>
|
||||
导出工作区备份
|
||||
导出文档空间备份
|
||||
</menu-entry>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<div class="side-bar__panel side-bar__panel--menu">
|
||||
<menu-entry @click.native="manageWorkspaces">
|
||||
<icon-database slot="icon"></icon-database>
|
||||
<div><div class="menu-entry__label menu-entry__label--count">{{workspaceCount}}</div> 管理工作区</div>
|
||||
<span>列出、重命名、删除工作区</span>
|
||||
<div><div class="menu-entry__label menu-entry__label--count">{{workspaceCount}}</div> 管理文档空间</div>
|
||||
<span>列出、重命名、删除文档空间</span>
|
||||
</menu-entry>
|
||||
<hr>
|
||||
<div class="workspace" v-for="(workspace, id) in workspacesById" :key="id">
|
||||
|
@ -15,27 +15,27 @@
|
|||
<hr>
|
||||
<menu-entry @click.native="addGithubWorkspace">
|
||||
<icon-provider slot="icon" provider-id="githubWorkspace"></icon-provider>
|
||||
<span>新增 <b>GitHub</b> 工作区</span>
|
||||
<span>新增 <b>GitHub</b> 文档空间</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addGiteeWorkspace">
|
||||
<icon-provider slot="icon" provider-id="giteeWorkspace"></icon-provider>
|
||||
<span>新增 <b>Gitee</b> 工作区</span>
|
||||
<span>新增 <b>Gitee</b> 文档空间</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addGitlabWorkspace">
|
||||
<icon-provider slot="icon" provider-id="gitlabWorkspace"></icon-provider>
|
||||
<span>新增 <b>GitLab</b> 工作区</span>
|
||||
<span>新增 <b>GitLab</b> 文档空间</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addGiteaWorkspace">
|
||||
<icon-provider slot="icon" provider-id="giteaWorkspace"></icon-provider>
|
||||
<span>新增 <b>Gitea</b> 工作区</span>
|
||||
<span>新增 <b>Gitea</b> 文档空间</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addGoogleDriveWorkspace">
|
||||
<icon-provider slot="icon" provider-id="googleDriveWorkspace"></icon-provider>
|
||||
<span>新增 <b>Google Drive</b> 工作区</span>
|
||||
<span>新增 <b>Google Drive</b> 文档空间</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addCouchdbWorkspace">
|
||||
<icon-provider slot="icon" provider-id="couchdbWorkspace"></icon-provider>
|
||||
<span>新增 <b>CouchDB</b> 工作区</span>
|
||||
<span>新增 <b>CouchDB</b> 文档空间</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
import FileSaver from 'file-saver';
|
||||
import networkSvc from '../../services/networkSvc';
|
||||
import editorSvc from '../../services/editorSvc';
|
||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
import modalTemplate from './common/modalTemplate';
|
||||
import store from '../../store';
|
||||
import badgeSvc from '../../services/badgeSvc';
|
||||
|
@ -45,15 +44,11 @@ export default modalTemplate({
|
|||
const currentContent = store.getters['content/current'];
|
||||
const { selectedFormat } = this;
|
||||
store.dispatch('queue/enqueue', async () => {
|
||||
const tokenToRefresh = store.getters['workspace/sponsorToken'];
|
||||
const sponsorToken = tokenToRefresh && await googleHelper.refreshToken(tokenToRefresh);
|
||||
|
||||
try {
|
||||
const { body } = await networkSvc.request({
|
||||
method: 'POST',
|
||||
url: 'pandocExport',
|
||||
params: {
|
||||
idToken: sponsorToken && sponsorToken.idToken,
|
||||
format: selectedFormat,
|
||||
options: JSON.stringify(store.getters['data/computedSettings'].pandoc),
|
||||
metadata: JSON.stringify(currentContent.properties),
|
||||
|
@ -65,12 +60,8 @@ export default modalTemplate({
|
|||
FileSaver.saveAs(body, `${currentFile.name}.${selectedFormat}`);
|
||||
badgeSvc.addBadge('exportPandoc');
|
||||
} catch (err) {
|
||||
if (err.status === 401) {
|
||||
store.dispatch('modal/open', 'sponsorOnly');
|
||||
} else {
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
store.dispatch('notification/error', err);
|
||||
}
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
store.dispatch('notification/error', err);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
import FileSaver from 'file-saver';
|
||||
import exportSvc from '../../services/exportSvc';
|
||||
import networkSvc from '../../services/networkSvc';
|
||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
import modalTemplate from './common/modalTemplate';
|
||||
import store from '../../store';
|
||||
import badgeSvc from '../../services/badgeSvc';
|
||||
|
@ -38,24 +37,17 @@ export default modalTemplate({
|
|||
this.config.resolve();
|
||||
const currentFile = store.getters['file/current'];
|
||||
store.dispatch('queue/enqueue', async () => {
|
||||
const [sponsorToken, html] = await Promise.all([
|
||||
Promise.resolve().then(() => {
|
||||
const tokenToRefresh = store.getters['workspace/sponsorToken'];
|
||||
return tokenToRefresh && googleHelper.refreshToken(tokenToRefresh);
|
||||
}),
|
||||
exportSvc.applyTemplate(
|
||||
currentFile.id,
|
||||
this.allTemplatesById[this.selectedTemplate],
|
||||
true,
|
||||
),
|
||||
]);
|
||||
const html = await exportSvc.applyTemplate(
|
||||
currentFile.id,
|
||||
this.allTemplatesById[this.selectedTemplate],
|
||||
true,
|
||||
);
|
||||
|
||||
try {
|
||||
const { body } = await networkSvc.request({
|
||||
method: 'POST',
|
||||
url: 'pdfExport',
|
||||
params: {
|
||||
idToken: sponsorToken && sponsorToken.idToken,
|
||||
options: JSON.stringify(store.getters['data/computedSettings'].wkhtmltopdf),
|
||||
},
|
||||
body: html,
|
||||
|
@ -65,12 +57,8 @@ export default modalTemplate({
|
|||
FileSaver.saveAs(body, `${currentFile.name}.pdf`);
|
||||
badgeSvc.addBadge('exportPdf');
|
||||
} catch (err) {
|
||||
if (err.status === 401) {
|
||||
store.dispatch('modal/open', 'sponsorOnly');
|
||||
} else {
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
store.dispatch('notification/error', err);
|
||||
}
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
store.dispatch('notification/error', err);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
<div class="modal__image">
|
||||
<icon-sync></icon-sync>
|
||||
</div>
|
||||
<p v-if="syncLocations.length"><b>{{currentFileName}}</b> is synchronized with the following location(s):</p>
|
||||
<p v-else><b>{{currentFileName}}</b> is not synchronized yet.</p>
|
||||
<p v-if="syncLocations.length"><b>{{currentFileName}}</b> 与以下位置同步:</p>
|
||||
<p v-else><b>{{currentFileName}}</b>尚未同步。</p>
|
||||
<div>
|
||||
<div class="sync-entry flex flex--column" v-for="location in syncLocations" :key="location.id">
|
||||
<div class="sync-entry__header flex flex--row flex--align-center">
|
||||
|
@ -23,7 +23,7 @@
|
|||
</div>
|
||||
<div class="sync-entry__row flex flex--row flex--align-center">
|
||||
<div class="sync-entry__url">
|
||||
{{location.url || 'Google Drive app data'}}
|
||||
{{location.url || 'Gitee app data'}}
|
||||
</div>
|
||||
<div class="sync-entry__buttons flex flex--row flex--center" v-if="location.url">
|
||||
<button class="sync-entry__button button" v-clipboard="location.url" @click="info('位置URL复制到剪贴板!')" v-title="'复制URL'">
|
||||
|
@ -37,11 +37,11 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="modal__info" v-if="syncLocations.length">
|
||||
<b>Tip:</b> Removing a location won't delete any file.
|
||||
<b>提示:</b> 删除位置不会删除任何文件。
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button button--resolve" @click="config.resolve()">Close</button>
|
||||
<button class="button button--resolve" @click="config.resolve()">关闭</button>
|
||||
</div>
|
||||
</modal-inner>
|
||||
</template>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<modal-inner class="modal__inner-1--workspace-management" aria-label="管理工作区">
|
||||
<modal-inner class="modal__inner-1--workspace-management" aria-label="管理文档空间">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-database></icon-database>
|
||||
</div>
|
||||
<p><br>可以访问以下工作区:</p>
|
||||
<p><br>可以访问以下文档空间:</p>
|
||||
<div class="workspace-entry flex flex--column" v-for="(workspace, id) in workspacesById" :key="id">
|
||||
<div class="flex flex--column">
|
||||
<div class="workspace-entry__header flex flex--row flex--align-center">
|
||||
|
@ -27,10 +27,10 @@
|
|||
{{workspace.url}}
|
||||
</div>
|
||||
<div class="workspace-entry__buttons flex flex--row">
|
||||
<button class="workspace-entry__button button" v-clipboard="workspace.url" @click="info('工作区URL已复制到剪贴板!')" v-title="'复制URL'">
|
||||
<button class="workspace-entry__button button" v-clipboard="workspace.url" @click="info('文档空间URL已复制到剪贴板!')" v-title="'复制URL'">
|
||||
<icon-content-copy></icon-content-copy>
|
||||
</button>
|
||||
<a class="workspace-entry__button button" :href="workspace.url" target="_blank" v-title="'打开工作区'">
|
||||
<a class="workspace-entry__button button" :href="workspace.url" target="_blank" v-title="'打开文档空间'">
|
||||
<icon-open-in-new></icon-open-in-new>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -40,10 +40,10 @@
|
|||
{{workspace.locationUrl}}
|
||||
</div>
|
||||
<div class="workspace-entry__buttons flex flex--row">
|
||||
<button class="workspace-entry__button button" v-clipboard="workspace.locationUrl" @click="info('工作区URL已复制到剪贴板!')" v-title="'复制URL'">
|
||||
<button class="workspace-entry__button button" v-clipboard="workspace.locationUrl" @click="info('文档空间URL已复制到剪贴板!')" v-title="'复制URL'">
|
||||
<icon-content-copy></icon-content-copy>
|
||||
</button>
|
||||
<a class="workspace-entry__button button" :href="workspace.locationUrl" target="_blank" v-title="'打开工作区位置'">
|
||||
<a class="workspace-entry__button button" :href="workspace.locationUrl" target="_blank" v-title="'打开文档空间位置'">
|
||||
<icon-open-in-new></icon-open-in-new>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -117,9 +117,9 @@ export default {
|
|||
},
|
||||
async remove(id) {
|
||||
if (id === this.mainWorkspace.id) {
|
||||
this.info('您的主工作区无法删除。');
|
||||
this.info('您的主文档空间无法删除。');
|
||||
} else if (id === this.currentWorkspace.id) {
|
||||
this.info('请先关闭工作区,然后再将其删除。');
|
||||
this.info('请先关闭文档空间,然后再将其删除。');
|
||||
} else {
|
||||
try {
|
||||
await store.dispatch('modal/open', 'removeWorkspace');
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<modal-inner aria-label="增加CouchDB工作区">
|
||||
<modal-inner aria-label="增加CouchDB文档空间">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="couchdb"></icon-provider>
|
||||
</div>
|
||||
<p>创建一个与<b>CouchDB</b>数据库同步的工作区。</p>
|
||||
<p>创建一个与<b>CouchDB</b>数据库同步的文档空间。</p>
|
||||
<form-entry label="Database URL" error="dbUrl">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="dbUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="modal__image">
|
||||
<icon-provider provider-id="gitea"></icon-provider>
|
||||
</div>
|
||||
<p>创建一个与<b> Gitea </b>项目文件夹同步的工作区。</p>
|
||||
<p>创建一个与<b> Gitea </b>项目文件夹同步的文档空间。</p>
|
||||
<form-entry label="Project URL" error="projectUrl">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="modal__image">
|
||||
<icon-provider provider-id="gitee"></icon-provider>
|
||||
</div>
|
||||
<p>创建一个与<b>Gitee</b>仓库文件夹同步的工作区。</p>
|
||||
<p>创建一个与<b>Gitee</b>仓库文件夹同步的文档空间。</p>
|
||||
<form-entry label="仓库URL" error="repoUrl">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="modal__image">
|
||||
<icon-provider provider-id="github"></icon-provider>
|
||||
</div>
|
||||
<p>创建一个与<b>GitHub</b>仓库文件夹同步的工作区。</p>
|
||||
<p>创建一个与<b>GitHub</b>仓库文件夹同步的文档空间。</p>
|
||||
<form-entry label="仓库URL" error="repoUrl">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="modal__image">
|
||||
<icon-provider provider-id="gitlab"></icon-provider>
|
||||
</div>
|
||||
<p>创建一个与<b>GitLab</b>仓库文件夹同步的工作区。</p>
|
||||
<p>创建一个与<b>GitLab</b>仓库文件夹同步的文档空间。</p>
|
||||
<form-entry label="Project URL" error="projectUrl">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<modal-inner aria-label="添加Google Drive工作区">
|
||||
<modal-inner aria-label="添加Google Drive文档空间">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="googleDrive"></icon-provider>
|
||||
</div>
|
||||
<p>创建一个与<b> Google Drive </b>文件夹同步的工作区。</p>
|
||||
<p>创建一个与<b> Google Drive </b>文件夹同步的文档空间。</p>
|
||||
<form-entry label="Folder ID" info="可选的">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="folderId" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export default () => ({
|
||||
main: {
|
||||
id: 'main',
|
||||
name: '主工作区',
|
||||
name: '主文档空间',
|
||||
// The rest will be filled by the workspace/workspacesById getter
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
**我的数据存储在哪里?**
|
||||
|
||||
如果您的工作区没有同步,则文件存储在浏览器中,无处可寻。
|
||||
如果您的文档空间没有同步,则文件存储在浏览器中,无处可寻。
|
||||
|
||||
我们建议同步您的工作区,以确保在清除浏览器数据的情况下不会丢失文件。自托管Gitea后端非常适合保证隐私。
|
||||
我们建议同步您的文档空间,以确保在清除浏览器数据的情况下不会丢失文件。自托管Gitea后端非常适合保证隐私。
|
||||
|
||||
**StackEdit可以访问我的数据而不告诉我吗?**
|
||||
|
||||
|
|
|
@ -54,22 +54,22 @@ export default [
|
|||
new Feature(
|
||||
'explorer',
|
||||
'资源管理器',
|
||||
'使用文件资源管理器管理工作区中的文件和文件夹。',
|
||||
'使用文件资源管理器管理文档空间中的文件和文件夹。',
|
||||
[
|
||||
new Feature(
|
||||
'createFile',
|
||||
'文件创建',
|
||||
'使用文件资源管理器在工作区中创建一个新文件。',
|
||||
'使用文件资源管理器在文档空间中创建一个新文件。',
|
||||
),
|
||||
new Feature(
|
||||
'switchFile',
|
||||
'文件切换',
|
||||
'使用文件资源管理器在工作区中从一个文件切换到另一个文件。',
|
||||
'使用文件资源管理器在文档空间中从一个文件切换到另一个文件。',
|
||||
),
|
||||
new Feature(
|
||||
'createFolder',
|
||||
'文件夹创建',
|
||||
'使用文件资源管理器在工作区中创建一个新文件夹。',
|
||||
'使用文件资源管理器在文档空间中创建一个新文件夹。',
|
||||
),
|
||||
new Feature(
|
||||
'moveFile',
|
||||
|
@ -84,22 +84,22 @@ export default [
|
|||
new Feature(
|
||||
'renameFile',
|
||||
'文件重命名',
|
||||
'使用文件资源管理器重命名工作区中的文件。',
|
||||
'使用文件资源管理器重命名文档空间中的文件。',
|
||||
),
|
||||
new Feature(
|
||||
'renameFolder',
|
||||
'文件夹重命名',
|
||||
'使用文件资源管理器重命名工作区中的文件夹。',
|
||||
'使用文件资源管理器重命名文档空间中的文件夹。',
|
||||
),
|
||||
new Feature(
|
||||
'removeFile',
|
||||
'文件删除',
|
||||
'使用文件资源管理器删除工作区中的文件。',
|
||||
'使用文件资源管理器删除文档空间中的文件。',
|
||||
),
|
||||
new Feature(
|
||||
'removeFolder',
|
||||
'文件夹删除',
|
||||
'使用文件资源管理器删除工作区中的文件夹。',
|
||||
'使用文件资源管理器删除文档空间中的文件夹。',
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -143,64 +143,64 @@ export default [
|
|||
new Feature(
|
||||
'signIn',
|
||||
'登录',
|
||||
'使用 Google 登录,同步您的主工作区并解锁功能。',
|
||||
'使用 Gitee 登录,同步您的主文档空间并解锁功能。',
|
||||
[
|
||||
new Feature(
|
||||
'syncMainWorkspace',
|
||||
'主工作区已同步',
|
||||
'使用 Google 登录以将您的主工作区与您的 Google Drive 应用数据文件夹同步。',
|
||||
'主文档空间已同步',
|
||||
'使用 Gitee 登录以将您的主文档空间与您的默认空间stackedit-app-data仓库数据同步。',
|
||||
),
|
||||
new Feature(
|
||||
'sponsor',
|
||||
'赞助',
|
||||
'使用 Google 登录并赞助 StackEdit 以解锁 PDF 和 Pandoc 导出。',
|
||||
'使用 Google 登录并赞助 StackEdit 以解锁 PDF 和 Pandoc 导出。(暂不支持赞助)',
|
||||
),
|
||||
],
|
||||
),
|
||||
new Feature(
|
||||
'workspaces',
|
||||
'工作区菜单',
|
||||
'使用工作区菜单创建各种工作区并对其进行管理。',
|
||||
'文档空间菜单',
|
||||
'使用文档空间菜单创建各种文档空间并对其进行管理。',
|
||||
[
|
||||
new Feature(
|
||||
'addCouchdbWorkspace',
|
||||
'创建CouchDB工作区',
|
||||
'使用工作区菜单创建CouchDB工作区。',
|
||||
'创建CouchDB文档空间',
|
||||
'使用文档空间菜单创建CouchDB文档空间。',
|
||||
),
|
||||
new Feature(
|
||||
'addGithubWorkspace',
|
||||
'创建GitHub工作区',
|
||||
'使用工作区菜单创建GitHub工作区。',
|
||||
'创建GitHub文档空间',
|
||||
'使用文档空间菜单创建GitHub文档空间。',
|
||||
),
|
||||
new Feature(
|
||||
'addGiteeWorkspace',
|
||||
'创建Gitee工作区',
|
||||
'使用工作区菜单创建Gitee工作区。',
|
||||
'创建Gitee文档空间',
|
||||
'使用文档空间菜单创建Gitee文档空间。',
|
||||
),
|
||||
new Feature(
|
||||
'addGitlabWorkspace',
|
||||
'创建Gitlab工作区',
|
||||
'使用工作区菜单创建GitLab工作区。',
|
||||
'创建Gitlab文档空间',
|
||||
'使用文档空间菜单创建GitLab文档空间。',
|
||||
),
|
||||
new Feature(
|
||||
'addGiteaWorkspace',
|
||||
'创建Gitea工作区',
|
||||
'使用工作区菜单创建Gitea工作区。',
|
||||
'创建Gitea文档空间',
|
||||
'使用文档空间菜单创建Gitea文档空间。',
|
||||
),
|
||||
new Feature(
|
||||
'addGoogleDriveWorkspace',
|
||||
'创建Google Drive工作区',
|
||||
'使用工作区菜单创建Google Drive工作区。',
|
||||
'创建Google Drive文档空间',
|
||||
'使用文档空间菜单创建Google Drive文档空间。',
|
||||
),
|
||||
new Feature(
|
||||
'renameWorkspace',
|
||||
'工作区重命名',
|
||||
'使用“管理工作区”对话框重命名工作区。',
|
||||
'文档空间重命名',
|
||||
'使用“管理文档空间”对话框重命名文档空间。',
|
||||
),
|
||||
new Feature(
|
||||
'removeWorkspace',
|
||||
'工作区删除',
|
||||
'使用“管理工作区”对话框在本地删除工作区。',
|
||||
'文档空间删除',
|
||||
'使用“管理文档空间”对话框在本地删除文档空间。',
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -7,33 +7,33 @@ const simpleModal = (contentHtml, rejectText, resolveText) => ({
|
|||
/* eslint sort-keys: "error" */
|
||||
export default {
|
||||
commentDeletion: simpleModal(
|
||||
'<p>You are about to delete a comment. Are you sure?</p>',
|
||||
'No',
|
||||
'Yes, delete',
|
||||
'<p>您将要删除评论。你确定吗?</p>',
|
||||
'取消',
|
||||
'确认删除',
|
||||
),
|
||||
discussionDeletion: simpleModal(
|
||||
'<p>You are about to delete a discussion. Are you sure?</p>',
|
||||
'No',
|
||||
'Yes, delete',
|
||||
'<p>您将要删除讨论。你确定吗?</p>',
|
||||
'取消',
|
||||
'确认删除',
|
||||
),
|
||||
fileRestoration: simpleModal(
|
||||
'<p>You are about to revert some changes. Are you sure?</p>',
|
||||
'No',
|
||||
'Yes, revert',
|
||||
'<p>您将要恢复一些更改。你确定吗?</p>',
|
||||
'取消',
|
||||
'确认恢复',
|
||||
),
|
||||
folderDeletion: simpleModal(
|
||||
config => `<p>You are about to delete the folder <b>${config.item.name}</b>. Its files will be moved to Trash. Are you sure?</p>`,
|
||||
'No',
|
||||
'Yes, delete',
|
||||
config => `<p>您将删除文件夹<b>${config.item.name}</b>。它的文件将移至回收站。你确定吗?</p>`,
|
||||
'取消',
|
||||
'确认删除',
|
||||
),
|
||||
pathConflict: simpleModal(
|
||||
config => `<p><b>${config.item.name}</b> already exists. Do you want to add a suffix?</p>`,
|
||||
'No',
|
||||
'Yes, add suffix',
|
||||
config => `<p><b>${config.item.name}</b>已经存在。您要添加后缀吗?</p>`,
|
||||
'取消',
|
||||
'确认添加',
|
||||
),
|
||||
paymentSuccess: simpleModal(
|
||||
'<h3>Thank you for your payment!</h3><p>Your sponsorship will be active in a minute.</p>',
|
||||
'Ok',
|
||||
'<h3>感谢您的付款!</h3> <p>您的赞助将在一分钟内活跃。</p>',
|
||||
'好的',
|
||||
),
|
||||
providerRedirection: simpleModal(
|
||||
config => `<p>您将跳转到 <b>${config.name}</b> 授权页面。</p>`,
|
||||
|
@ -41,57 +41,57 @@ export default {
|
|||
'确认跳转',
|
||||
),
|
||||
removeWorkspace: simpleModal(
|
||||
'<p>You are about to remove a workspace locally. Are you sure?</p>',
|
||||
'No',
|
||||
'Yes, remove',
|
||||
'<p>您将要在本地删除文档空间ß。你确定吗?</p>',
|
||||
'取消',
|
||||
'确认删除',
|
||||
),
|
||||
reset: simpleModal(
|
||||
'<p>这将在本地清理所有工作区,你确定吗?</p>',
|
||||
'<p>这将在本地清理所有文档空间,你确定吗?</p>',
|
||||
'取消',
|
||||
'确认清理',
|
||||
),
|
||||
signInForComment: simpleModal(
|
||||
`<p>您必须使用 Google 登录才能开始评论。</p>
|
||||
<div class="modal__info"><b>注意:</b> 这将同步您的主工作区。</div>`,
|
||||
<div class="modal__info"><b>注意:</b> 这将同步您的主文档空间。</div>`,
|
||||
'取消',
|
||||
'确认登录',
|
||||
),
|
||||
signInForSponsorship: simpleModal(
|
||||
`<p>您必须使用 Google 登录才能赞助。</p>
|
||||
<div class="modal__info"><b>注意:</b> 这将同步您的主工作区。</div>`,
|
||||
<div class="modal__info"><b>注意:</b> 这将同步您的主文档空间。</div>`,
|
||||
'取消',
|
||||
'确认登录',
|
||||
),
|
||||
sponsorOnly: simpleModal(
|
||||
'<p>This feature is restricted to sponsors as it relies on server resources.</p>',
|
||||
'Ok, I understand',
|
||||
'<p>此功能仅限于赞助商,因为它依赖于服务器资源。</p>',
|
||||
'好的,我明白了',
|
||||
),
|
||||
stripName: simpleModal(
|
||||
config => `<p><b>${config.item.name}</b> contains illegal characters. Do you want to strip them?</p>`,
|
||||
'No',
|
||||
'Yes, strip',
|
||||
config => `<p><b>${config.item.name}</b>包含非法字符。你想剥离它们吗?</p>`,
|
||||
'取消',
|
||||
'确认剥离',
|
||||
),
|
||||
tempFileDeletion: simpleModal(
|
||||
config => `<p>You are about to permanently delete the temporary file <b>${config.item.name}</b>. Are you sure?</p>`,
|
||||
'No',
|
||||
'Yes, delete',
|
||||
config => `<p>您将永久删除临时文件<b>${config.item.name}</b>。你确定吗?</p>`,
|
||||
'取消',
|
||||
'确认删除',
|
||||
),
|
||||
tempFolderDeletion: simpleModal(
|
||||
'<p>You are about to permanently delete all the temporary files. Are you sure?</p>',
|
||||
'No',
|
||||
'Yes, delete all',
|
||||
'<p>您将永久删除所有临时文件。你确定吗?</p>',
|
||||
'取消',
|
||||
'确认删除',
|
||||
),
|
||||
trashDeletion: simpleModal(
|
||||
'<p>Files in the trash are automatically deleted after 7 days of inactivity.</p>',
|
||||
'Ok',
|
||||
'<p>回收站中的文件在不活动7天后会自动删除。</p>',
|
||||
'好的',
|
||||
),
|
||||
unauthorizedName: simpleModal(
|
||||
config => `<p><b>${config.item.name}</b> is an unauthorized name.</p>`,
|
||||
'Ok',
|
||||
config => `<p><b>${config.item.name}</b>>是未经授权的名称。</p>`,
|
||||
'好的',
|
||||
),
|
||||
workspaceGoogleRedirection: simpleModal(
|
||||
'<p>StackEdit needs full Google Drive access to open this workspace.</p>',
|
||||
'Cancel',
|
||||
'Ok, grant',
|
||||
'<p>StackEdit需要完整的Google Drive访问才能打开此文档空间。</p>',
|
||||
'取消',
|
||||
'确认授权',
|
||||
),
|
||||
};
|
||||
|
|
|
@ -30,23 +30,23 @@ StackEdit 将您的文件存储在您的浏览器中,这意味着您的所有
|
|||
|
||||
# 同步
|
||||
|
||||
同步是 StackEdit 的最大特点之一。它使您可以将工作区中的任何文件与存储在**Gitee** 和 **GitHub** 账号中的其他文件同步。这使您可以继续在其他设备上写作,与您共享文件的人协作,轻松集成到您的工作流程中......同步机制在后台每分钟发生一次,下载、合并和上传文件修改。
|
||||
同步是 StackEdit 的最大特点之一。它使您可以将文档空间中的任何文件与存储在**Gitee** 和 **GitHub** 账号中的其他文件同步。这使您可以继续在其他设备上写作,与您共享文件的人协作,轻松集成到您的工作流程中......同步机制在后台每分钟发生一次,下载、合并和上传文件修改。
|
||||
|
||||
有两种类型的同步,它们可以相互补充:
|
||||
|
||||
- 工作区同步将自动同步您的所有文件、文件夹和设置。这将允许您在任何其他设备上获取您的工作区。
|
||||
> 要开始同步您的工作区,只需在菜单中使用 Google 登录。
|
||||
- 文档空间同步将自动同步您的所有文件、文件夹和设置。这将允许您在任何其他设备上获取您的文档空间。
|
||||
> 要开始同步您的文档空间,只需在菜单中使用 Google 登录。
|
||||
|
||||
- 文件同步将保持工作区的一个文件与**Gitee**或**GitHub**中的一个或多个文件同步。
|
||||
- 文件同步将保持文档空间的一个文件与**Gitee**或**GitHub**中的一个或多个文件同步。
|
||||
> 在开始同步文件之前,您必须在**同步**子菜单中链接一个账号。
|
||||
|
||||
## 打开一个文件
|
||||
|
||||
您可以通过打开 **同步** 子菜单并单击 **Open from** 从**Gitee** 或 **GitHub** 打开文件。在工作区中打开后,文件中的任何修改都将自动同步。
|
||||
您可以通过打开 **同步** 子菜单并单击 **Open from** 从**Gitee** 或 **GitHub** 打开文件。在文档空间中打开后,文件中的任何修改都将自动同步。
|
||||
|
||||
## 保存文件
|
||||
|
||||
您可以通过打开 **同步** 子菜单并单击 **Save on** 将工作区的任何文件保存到**Gitee** 或 **GitHub**。即使工作区中的文件已经同步,您也可以将其保存到另一个位置。 StackEdit 可以将一个文件与多个位置和账号同步。
|
||||
您可以通过打开 **同步** 子菜单并单击 **Save on** 将文档空间的任何文件保存到**Gitee** 或 **GitHub**。即使文档空间中的文件已经同步,您也可以将其保存到另一个位置。 StackEdit 可以将一个文件与多个位置和账号同步。
|
||||
|
||||
##同步文件
|
||||
|
||||
|
@ -164,4 +164,4 @@ A[Square Rect] -- 链接文本 --> B((Circle))
|
|||
A --> C(圆角矩形)
|
||||
B --> D{菱形}
|
||||
C --> D
|
||||
```
|
||||
```
|
||||
|
|
|
@ -11,7 +11,6 @@ export default {
|
|||
classState() {
|
||||
switch (this.providerId) {
|
||||
case 'googleDrive':
|
||||
case 'googleDriveAppData':
|
||||
case 'googleDriveWorkspace':
|
||||
return 'google-drive';
|
||||
case 'googlePhotos':
|
||||
|
@ -28,6 +27,7 @@ export default {
|
|||
return 'blogger';
|
||||
case 'couchdbWorkspace':
|
||||
return 'couchdb';
|
||||
case 'giteeAppData':
|
||||
case 'giteeWorkspace':
|
||||
return 'gitee';
|
||||
default:
|
||||
|
|
256
src/services/providers/giteeAppDataProvider.js
Normal file
256
src/services/providers/giteeAppDataProvider.js
Normal file
|
@ -0,0 +1,256 @@
|
|||
import store from '../../store';
|
||||
import giteeHelper from './helpers/giteeHelper';
|
||||
import Provider from './common/Provider';
|
||||
import gitWorkspaceSvc from '../gitWorkspaceSvc';
|
||||
import userSvc from '../userSvc';
|
||||
|
||||
const appDataRepo = 'stackedit-app-data';
|
||||
const appDataBranch = 'master';
|
||||
|
||||
export default new Provider({
|
||||
id: 'giteeAppData',
|
||||
name: 'Gitee应用数据',
|
||||
getToken() {
|
||||
return store.getters['workspace/syncToken'];
|
||||
},
|
||||
getWorkspaceParams() {
|
||||
// No param as it's the main workspace
|
||||
return {};
|
||||
},
|
||||
getWorkspaceLocationUrl() {
|
||||
// No direct link to app data
|
||||
return null;
|
||||
},
|
||||
getSyncDataUrl() {
|
||||
// No direct link to app data
|
||||
return null;
|
||||
},
|
||||
getSyncDataDescription({ id }) {
|
||||
return id;
|
||||
},
|
||||
async initWorkspace() {
|
||||
// Nothing much to do since the main workspace isn't necessarily synchronized
|
||||
// Return the main workspace
|
||||
return store.getters['workspace/workspacesById'].main;
|
||||
},
|
||||
getChanges() {
|
||||
const token = this.getToken();
|
||||
return giteeHelper.getTree({
|
||||
owner: token.name,
|
||||
repo: appDataRepo,
|
||||
branch: appDataBranch,
|
||||
token,
|
||||
});
|
||||
},
|
||||
prepareChanges(tree) {
|
||||
return gitWorkspaceSvc.makeChanges(tree);
|
||||
},
|
||||
async saveWorkspaceItem({ item }) {
|
||||
const syncData = {
|
||||
id: store.getters.gitPathsByItemId[item.id],
|
||||
type: item.type,
|
||||
hash: item.hash,
|
||||
};
|
||||
|
||||
// Files and folders are not in git, only contents
|
||||
if (item.type === 'file' || item.type === 'folder') {
|
||||
return { syncData };
|
||||
}
|
||||
|
||||
// locations are stored as paths, so we upload an empty file
|
||||
const syncToken = store.getters['workspace/syncToken'];
|
||||
await giteeHelper.uploadFile({
|
||||
owner: syncToken.name,
|
||||
repo: appDataRepo,
|
||||
branch: appDataBranch,
|
||||
token: syncToken,
|
||||
path: syncData.id,
|
||||
content: '',
|
||||
sha: gitWorkspaceSvc.shaByPath[syncData.id],
|
||||
});
|
||||
|
||||
// Return sync data to save
|
||||
return { syncData };
|
||||
},
|
||||
async removeWorkspaceItem({ syncData }) {
|
||||
if (gitWorkspaceSvc.shaByPath[syncData.id]) {
|
||||
const syncToken = store.getters['workspace/syncToken'];
|
||||
await giteeHelper.removeFile({
|
||||
owner: syncToken.name,
|
||||
repo: appDataRepo,
|
||||
branch: appDataBranch,
|
||||
token: syncToken,
|
||||
path: syncData.id,
|
||||
sha: gitWorkspaceSvc.shaByPath[syncData.id],
|
||||
});
|
||||
}
|
||||
},
|
||||
async downloadWorkspaceContent({
|
||||
token,
|
||||
contentId,
|
||||
contentSyncData,
|
||||
fileSyncData,
|
||||
}) {
|
||||
const { sha, data } = await giteeHelper.downloadFile({
|
||||
owner: token.name,
|
||||
repo: appDataRepo,
|
||||
branch: appDataBranch,
|
||||
token,
|
||||
path: fileSyncData.id,
|
||||
});
|
||||
gitWorkspaceSvc.shaByPath[fileSyncData.id] = sha;
|
||||
const content = Provider.parseContent(data, contentId);
|
||||
return {
|
||||
content,
|
||||
contentSyncData: {
|
||||
...contentSyncData,
|
||||
hash: content.hash,
|
||||
sha,
|
||||
},
|
||||
};
|
||||
},
|
||||
async downloadWorkspaceData({ token, syncData }) {
|
||||
if (!syncData) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const { sha, data } = await giteeHelper.downloadFile({
|
||||
owner: token.name,
|
||||
repo: appDataRepo,
|
||||
branch: appDataBranch,
|
||||
token,
|
||||
path: syncData.id,
|
||||
});
|
||||
gitWorkspaceSvc.shaByPath[syncData.id] = sha;
|
||||
const item = JSON.parse(data);
|
||||
return {
|
||||
item,
|
||||
syncData: {
|
||||
...syncData,
|
||||
hash: item.hash,
|
||||
sha,
|
||||
},
|
||||
};
|
||||
},
|
||||
async uploadWorkspaceContent({ token, content, file }) {
|
||||
const path = store.getters.gitPathsByItemId[file.id];
|
||||
const res = await giteeHelper.uploadFile({
|
||||
owner: token.name,
|
||||
repo: appDataRepo,
|
||||
branch: appDataBranch,
|
||||
token,
|
||||
path,
|
||||
content: Provider.serializeContent(content),
|
||||
sha: gitWorkspaceSvc.shaByPath[path],
|
||||
});
|
||||
|
||||
// Return new sync data
|
||||
return {
|
||||
contentSyncData: {
|
||||
id: store.getters.gitPathsByItemId[content.id],
|
||||
type: content.type,
|
||||
hash: content.hash,
|
||||
sha: res.content.sha,
|
||||
},
|
||||
fileSyncData: {
|
||||
id: path,
|
||||
type: 'file',
|
||||
hash: file.hash,
|
||||
},
|
||||
};
|
||||
},
|
||||
async uploadWorkspaceData({
|
||||
token,
|
||||
item,
|
||||
}) {
|
||||
const path = store.getters.gitPathsByItemId[item.id];
|
||||
if (!path) {
|
||||
return {
|
||||
syncData: {
|
||||
type: item.type,
|
||||
hash: item.hash,
|
||||
},
|
||||
};
|
||||
}
|
||||
const syncData = {
|
||||
id: path,
|
||||
type: item.type,
|
||||
hash: item.hash,
|
||||
};
|
||||
const res = await giteeHelper.uploadFile({
|
||||
token,
|
||||
owner: token.name,
|
||||
repo: appDataRepo,
|
||||
branch: appDataBranch,
|
||||
path,
|
||||
content: JSON.stringify(item),
|
||||
sha: gitWorkspaceSvc.shaByPath[path],
|
||||
});
|
||||
|
||||
return {
|
||||
syncData: {
|
||||
...syncData,
|
||||
sha: res.content.sha,
|
||||
},
|
||||
};
|
||||
},
|
||||
async listFileRevisions({ token, fileSyncDataId }) {
|
||||
const { owner, repo, branch } = {
|
||||
owner: token.name,
|
||||
repo: appDataRepo,
|
||||
branch: appDataBranch,
|
||||
};
|
||||
const entries = await giteeHelper.getCommits({
|
||||
token,
|
||||
owner,
|
||||
repo,
|
||||
sha: branch,
|
||||
path: fileSyncDataId,
|
||||
});
|
||||
|
||||
return entries.map(({
|
||||
author,
|
||||
committer,
|
||||
commit,
|
||||
sha,
|
||||
}) => {
|
||||
let user;
|
||||
if (author && author.login) {
|
||||
user = author;
|
||||
} else if (committer && committer.login) {
|
||||
user = committer;
|
||||
}
|
||||
const sub = `${giteeHelper.subPrefix}:${user.login}`;
|
||||
if (user.avatar_url && user.avatar_url.endsWith('.png')) {
|
||||
user.avatar_url = `${user.avatar_url}!avatar60`;
|
||||
}
|
||||
userSvc.addUserInfo({ id: sub, name: user.login, imageUrl: user.avatar_url });
|
||||
const date = (commit.author && commit.author.date)
|
||||
|| (commit.committer && commit.committer.date)
|
||||
|| 1;
|
||||
return {
|
||||
id: sha,
|
||||
sub,
|
||||
created: new Date(date).getTime(),
|
||||
};
|
||||
});
|
||||
},
|
||||
async loadFileRevision() {
|
||||
// Revisions are already loaded
|
||||
return false;
|
||||
},
|
||||
async getFileRevisionContent({
|
||||
token,
|
||||
contentId,
|
||||
fileSyncDataId,
|
||||
}) {
|
||||
const { data } = await giteeHelper.downloadFile({
|
||||
owner: token.name,
|
||||
repo: appDataRepo,
|
||||
branch: appDataBranch,
|
||||
token,
|
||||
path: fileSyncDataId,
|
||||
});
|
||||
return Provider.parseContent(data, contentId);
|
||||
},
|
||||
});
|
|
@ -87,7 +87,7 @@ export default new Provider({
|
|||
});
|
||||
}
|
||||
|
||||
badgeSvc.addBadge('addGithubWorkspace');
|
||||
badgeSvc.addBadge('addGiteeWorkspace');
|
||||
return store.getters['workspace/workspacesById'][workspaceId];
|
||||
},
|
||||
getChanges() {
|
||||
|
|
|
@ -7,6 +7,8 @@ import constants from '../../../data/constants';
|
|||
|
||||
const tokenExpirationMargin = 5 * 60 * 1000;
|
||||
|
||||
const appDataRepo = 'stackedit-app-data';
|
||||
|
||||
const request = (token, options) => networkSvc.request({
|
||||
...options,
|
||||
headers: {
|
||||
|
@ -125,6 +127,8 @@ export default {
|
|||
sub: `${user.login}`,
|
||||
};
|
||||
|
||||
// 检查 stackedit-app-data 仓库是否已经存在 如果不存在则创建该仓库
|
||||
await this.checkAndCreateRepo(token);
|
||||
// Add token to gitee tokens
|
||||
store.dispatch('data/addGiteeToken', token);
|
||||
return token;
|
||||
|
@ -162,6 +166,9 @@ export default {
|
|||
return this.startOauth2();
|
||||
}
|
||||
},
|
||||
signin() {
|
||||
return this.startOauth2();
|
||||
},
|
||||
async addAccount() {
|
||||
const token = await this.startOauth2();
|
||||
badgeSvc.addBadge('addGiteeAccount');
|
||||
|
@ -191,6 +198,27 @@ export default {
|
|||
return tree;
|
||||
},
|
||||
|
||||
async checkAndCreateRepo(token) {
|
||||
const url = `https://gitee.com/api/v5/repos/${encodeURIComponent(token.name)}/${encodeURIComponent(appDataRepo)}`;
|
||||
try {
|
||||
await request(token, { url });
|
||||
} catch (err) {
|
||||
// 不存在则创建
|
||||
if (err.status === 404) {
|
||||
await request(token, {
|
||||
method: 'POST',
|
||||
url: 'https://gitee.com/api/v5/user/repos',
|
||||
params: {
|
||||
name: appDataRepo,
|
||||
auto_init: true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* https://developer.gitee.com/v3/repos/commits/#list-commits-on-a-repository
|
||||
*/
|
||||
|
|
|
@ -4,7 +4,7 @@ import utils from './utils';
|
|||
import diffUtils from './diffUtils';
|
||||
import networkSvc from './networkSvc';
|
||||
import providerRegistry from './providers/common/providerRegistry';
|
||||
import googleDriveAppDataProvider from './providers/googleDriveAppDataProvider';
|
||||
import giteeAppDataProvider from './providers/giteeAppDataProvider';
|
||||
import './providers/couchdbWorkspaceProvider';
|
||||
import './providers/githubWorkspaceProvider';
|
||||
import './providers/giteeWorkspaceProvider';
|
||||
|
@ -821,7 +821,7 @@ const requestSync = (addTriggerSyncBadge = false) => {
|
|||
clearInterval(intervalId);
|
||||
if (!isSyncPossible()) {
|
||||
// Cancel sync
|
||||
throw new Error('Sync not possible.');
|
||||
throw new Error('无法同步。');
|
||||
}
|
||||
|
||||
// Determine if we have to clean files
|
||||
|
@ -888,7 +888,7 @@ export default {
|
|||
// Try to find a suitable workspace sync provider
|
||||
workspaceProvider = providerRegistry.providersById[utils.queryParams.providerId];
|
||||
if (!workspaceProvider || !workspaceProvider.initWorkspace) {
|
||||
workspaceProvider = googleDriveAppDataProvider;
|
||||
workspaceProvider = giteeAppDataProvider;
|
||||
}
|
||||
const workspace = await workspaceProvider.initWorkspace();
|
||||
// Fix the URL hash
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import utils from '../services/utils';
|
||||
import googleHelper from '../services/providers/helpers/googleHelper';
|
||||
import giteeHelper from '../services/providers/helpers/giteeHelper';
|
||||
import syncSvc from '../services/syncSvc';
|
||||
|
||||
const idShifter = offset => (state, getters) => {
|
||||
|
@ -137,7 +137,7 @@ export default {
|
|||
if (!loginToken) {
|
||||
try {
|
||||
await dispatch('modal/open', 'signInForComment', { root: true });
|
||||
await googleHelper.signin();
|
||||
await giteeHelper.signin();
|
||||
syncSvc.requestSync();
|
||||
await dispatch('createNewDiscussion', selection);
|
||||
} catch (e) { /* cancel */ }
|
||||
|
|
|
@ -22,7 +22,7 @@ export default {
|
|||
Object.entries(rootGetters['data/workspaces']).forEach(([id, workspace]) => {
|
||||
const sanitizedWorkspace = {
|
||||
id,
|
||||
providerId: 'googleDriveAppData',
|
||||
providerId: 'giteeAppData',
|
||||
sub: mainWorkspaceToken && mainWorkspaceToken.sub,
|
||||
...workspace,
|
||||
};
|
||||
|
@ -46,21 +46,18 @@ export default {
|
|||
currentWorkspace.providerId === 'githubWorkspace'
|
||||
|| currentWorkspace.providerId === 'giteeWorkspace'
|
||||
|| currentWorkspace.providerId === 'gitlabWorkspace'
|
||||
|| currentWorkspace.providerId === 'giteaWorkspace',
|
||||
|| currentWorkspace.providerId === 'giteaWorkspace'
|
||||
|| currentWorkspace.providerId === 'giteeAppData',
|
||||
currentWorkspaceHasUniquePaths: (state, { currentWorkspace }) =>
|
||||
currentWorkspace.providerId === 'githubWorkspace'
|
||||
|| currentWorkspace.providerId === 'giteeWorkspace'
|
||||
|| currentWorkspace.providerId === 'gitlabWorkspace'
|
||||
|| currentWorkspace.providerId === 'giteaWorkspace',
|
||||
|| currentWorkspace.providerId === 'giteaWorkspace'
|
||||
|| currentWorkspace.providerId === 'giteeAppData',
|
||||
lastSyncActivityKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastSyncActivity`,
|
||||
lastFocusKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastWindowFocus`,
|
||||
mainWorkspaceToken: (state, getters, rootState, rootGetters) =>
|
||||
utils.someResult(Object.values(rootGetters['data/googleTokensBySub']), (token) => {
|
||||
if (token.isLogin) {
|
||||
return token;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
utils.someResult(Object.values(rootGetters['data/giteeTokensBySub']), token => token),
|
||||
syncToken: (state, { currentWorkspace, mainWorkspaceToken }, rootState, rootGetters) => {
|
||||
switch (currentWorkspace.providerId) {
|
||||
case 'googleDriveWorkspace':
|
||||
|
@ -82,11 +79,11 @@ export default {
|
|||
loginType: (state, { currentWorkspace }) => {
|
||||
switch (currentWorkspace.providerId) {
|
||||
case 'googleDriveWorkspace':
|
||||
default:
|
||||
return 'google';
|
||||
case 'githubWorkspace':
|
||||
return 'github';
|
||||
case 'giteeWorkspace':
|
||||
default:
|
||||
return 'gitee';
|
||||
case 'gitlabWorkspace':
|
||||
return 'gitlab';
|
||||
|
|
|
@ -353,7 +353,7 @@
|
|||
<div class="column">
|
||||
<div class="feature">
|
||||
<h3>协作</h3>
|
||||
<p>借助 StackEdit,您可以共享协作工作空间,这要归功于同步机制。 如果两个协作者同时处理同一个文件,StackEdit 会负责合并更改。</p>
|
||||
<p>借助 StackEdit,您可以共享协作文档空间,这要归功于同步机制。 如果两个协作者同时处理同一个文件,StackEdit 会负责合并更改。</p>
|
||||
</div>
|
||||
<img class="image" width="300" src="static/landing/workspace.png">
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue
Block a user