主空间修改为gitee
This commit is contained in:
parent
b94898960a
commit
f0612d1120
@ -14,7 +14,7 @@ StackEdit的作者可能因为什么原因,已经很久不维护了,Github
|
|||||||
### TODO: 关于后续的一些想法
|
### TODO: 关于后续的一些想法
|
||||||
- 支持**Gitea**、**Gogs**两个轻量级且适于自建的Git仓库(毕竟Gitlab对机器配置要求较高)。想支持这两个主要也是考虑到其实很多公司已经禁用了Github或Gitee仓库,在公司都没法连上自己的Git仓库。
|
- 支持**Gitea**、**Gogs**两个轻量级且适于自建的Git仓库(毕竟Gitlab对机器配置要求较高)。想支持这两个主要也是考虑到其实很多公司已经禁用了Github或Gitee仓库,在公司都没法连上自己的Git仓库。
|
||||||
- 汉化,毕竟大家最熟悉的还是母语,并且该编辑器功能页面也不多,汉化工作量并不会很大。
|
- 汉化,毕竟大家最熟悉的还是母语,并且该编辑器功能页面也不多,汉化工作量并不会很大。
|
||||||
- 替换主工作区为Gitee(原版本主工作区是Google Drive,国内只有fan墙才可以用)
|
- 替换主文档空间为Gitee(原版本主文档空间是Google Drive,国内只有fan墙才可以用)
|
||||||
- 引入mdnice,右边预览增加mdnice预览选项,主要含选主题(含mdnice常用20多个主题)、支持自定义主题、复制到公众号、复制到知乎、复制到稀土掘金等基本功能,便于喜欢写公众号、博客的同学可以更好更快的排版。
|
- 引入mdnice,右边预览增加mdnice预览选项,主要含选主题(含mdnice常用20多个主题)、支持自定义主题、复制到公众号、复制到知乎、复制到稀土掘金等基本功能,便于喜欢写公众号、博客的同学可以更好更快的排版。
|
||||||
- ... 另外,朋友们有好的想法也可以在Issue或者加我微信 qicoding 提给我。
|
- ... 另外,朋友们有好的想法也可以在Issue或者加我微信 qicoding 提给我。
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ StackEdit的作者可能因为什么原因,已经很久不维护了,Github
|
|||||||
|
|
||||||
**已汉化主要功能部分(2022-06-01)**
|
**已汉化主要功能部分(2022-06-01)**
|
||||||
|
|
||||||
**接下来修改主工作区为Gitee**
|
**接下来修改主文档空间为Gitee**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "StackEdit中文版",
|
"name": "StackEdit中文版",
|
||||||
"description": "浏览器内 Markdown 编辑器",
|
"description": "支持Gitee仓库的浏览器内 Markdown 编辑器",
|
||||||
"version": "1.0.13",
|
"version": "1.0.13",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"container" : "GOOGLE_DRIVE",
|
"container" : "GOOGLE_DRIVE",
|
||||||
|
196
server/pandoc.js
196
server/pandoc.js
@ -2,7 +2,6 @@
|
|||||||
const { spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const tmp = require('tmp');
|
const tmp = require('tmp');
|
||||||
const user = require('./user');
|
|
||||||
const conf = require('./conf');
|
const conf = require('./conf');
|
||||||
|
|
||||||
const outputFormats = {
|
const outputFormats = {
|
||||||
@ -42,108 +41,101 @@ exports.generate = (req, res) => {
|
|||||||
const outputFormat = Object.prototype.hasOwnProperty.call(outputFormats, req.query.format)
|
const outputFormat = Object.prototype.hasOwnProperty.call(outputFormats, req.query.format)
|
||||||
? req.query.format
|
? req.query.format
|
||||||
: 'pdf';
|
: 'pdf';
|
||||||
user.checkSponsor(req.query.idToken)
|
new Promise((resolve, reject) => {
|
||||||
.then((isSponsor) => {
|
tmp.file({
|
||||||
if (!isSponsor) {
|
postfix: `.${outputFormat}`,
|
||||||
throw new Error('unauthorized');
|
}, (err, filePath, fd, cleanupCallback) => {
|
||||||
}
|
if (err) {
|
||||||
|
reject(err);
|
||||||
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.');
|
|
||||||
} else {
|
} else {
|
||||||
res.statusCode = 400;
|
resolve({
|
||||||
res.end(pandocError || 'Unknown error.');
|
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 { spawn } = require('child_process');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const tmp = require('tmp');
|
const tmp = require('tmp');
|
||||||
const user = require('./user');
|
|
||||||
const conf = require('./conf');
|
const conf = require('./conf');
|
||||||
|
|
||||||
/* eslint-disable no-var, prefer-arrow-callback, func-names */
|
/* eslint-disable no-var, prefer-arrow-callback, func-names */
|
||||||
@ -51,135 +50,129 @@ const readJson = (str) => {
|
|||||||
|
|
||||||
exports.generate = (req, res) => {
|
exports.generate = (req, res) => {
|
||||||
let wkhtmltopdfError = '';
|
let wkhtmltopdfError = '';
|
||||||
user.checkSponsor(req.query.idToken)
|
new Promise((resolve, reject) => {
|
||||||
.then((isSponsor) => {
|
tmp.file((err, filePath, fd, cleanupCallback) => {
|
||||||
if (!isSponsor) {
|
if (err) {
|
||||||
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();
|
|
||||||
reject(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 {
|
} else {
|
||||||
res.statusCode = 400;
|
resolve({
|
||||||
res.end(wkhtmltopdfError || 'Unknown error.');
|
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>
|
<template>
|
||||||
<div class="modal" v-if="config" @keydown.esc.stop="onEscape" @keydown.tab="onTab" @focusin="onFocusInOut" @focusout="onFocusInOut">
|
<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
|
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.
|
<a class="not-tabbable" href="javascript:void(0)" @click="sponsor">sponsoring</a> for just $5.
|
||||||
</div>
|
</div> -->
|
||||||
<component v-if="currentModalComponent" :is="currentModalComponent"></component>
|
<component v-if="currentModalComponent" :is="currentModalComponent"></component>
|
||||||
<modal-inner v-else aria-label="Dialog">
|
<modal-inner v-else aria-label="Dialog">
|
||||||
<div class="modal__content" v-html="simpleModal.contentHtml(config)"></div>
|
<div class="modal__content" v-html="simpleModal.contentHtml(config)"></div>
|
||||||
@ -20,7 +20,7 @@ import { mapGetters } from 'vuex';
|
|||||||
import simpleModals from '../data/simpleModals';
|
import simpleModals from '../data/simpleModals';
|
||||||
import editorSvc from '../services/editorSvc';
|
import editorSvc from '../services/editorSvc';
|
||||||
import syncSvc from '../services/syncSvc';
|
import syncSvc from '../services/syncSvc';
|
||||||
import googleHelper from '../services/providers/helpers/googleHelper';
|
import giteeHelper from '../services/providers/helpers/giteeHelper';
|
||||||
import store from '../store';
|
import store from '../store';
|
||||||
|
|
||||||
import ModalInner from './modals/common/ModalInner';
|
import ModalInner from './modals/common/ModalInner';
|
||||||
@ -168,7 +168,7 @@ export default {
|
|||||||
if (!store.getters['workspace/sponsorToken']) {
|
if (!store.getters['workspace/sponsorToken']) {
|
||||||
// User has to sign in
|
// User has to sign in
|
||||||
await store.dispatch('modal/open', 'signInForSponsorship');
|
await store.dispatch('modal/open', 'signInForSponsorship');
|
||||||
await googleHelper.signin();
|
await giteeHelper.signin();
|
||||||
syncSvc.requestSync();
|
syncSvc.requestSync();
|
||||||
}
|
}
|
||||||
if (!store.getters.isSponsor) {
|
if (!store.getters.isSponsor) {
|
||||||
|
@ -47,14 +47,14 @@ import store from '../store';
|
|||||||
|
|
||||||
const panelNames = {
|
const panelNames = {
|
||||||
menu: '菜单',
|
menu: '菜单',
|
||||||
workspaces: '工作区',
|
workspaces: '文档空间',
|
||||||
help: 'Markdown 帮助',
|
help: 'Markdown 帮助',
|
||||||
toc: '目录',
|
toc: '目录',
|
||||||
sync: '同步',
|
sync: '同步',
|
||||||
publish: '发布',
|
publish: '发布',
|
||||||
history: '文件历史',
|
history: '文件历史',
|
||||||
importExport: '导入/导出',
|
importExport: '导入/导出',
|
||||||
workspaceBackups: '工作区备份',
|
workspaceBackups: '文档空间备份',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<span v-for="stat in textStats" :key="stat.id">
|
<span v-for="stat in textStats" :key="stat.id">
|
||||||
<span class="stat-panel__value">{{stat.value}}</span> {{stat.name}}
|
<span class="stat-panel__value">{{stat.value}}</span> {{stat.name}}
|
||||||
</span>
|
</span>
|
||||||
<span class="stat-panel__value">Ln {{line}}, Col {{column}}</span>
|
<span class="stat-panel__value">第 {{line}} 行, 第 {{column}} 列</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-panel__block stat-panel__block--right">
|
<div class="stat-panel__block stat-panel__block--right">
|
||||||
<span class="stat-panel__block-name">
|
<span class="stat-panel__block-name">
|
||||||
@ -43,14 +43,13 @@ export default {
|
|||||||
line: 0,
|
line: 0,
|
||||||
column: 0,
|
column: 0,
|
||||||
textStats: [
|
textStats: [
|
||||||
new Stat('bytes', '[\\s\\S]'),
|
new Stat('字符', '[\\s\\S]'),
|
||||||
new Stat('words', '\\S+'),
|
new Stat('字数', '\\S'),
|
||||||
new Stat('lines', '\n'),
|
new Stat('行数', '\n'),
|
||||||
],
|
],
|
||||||
htmlStats: [
|
htmlStats: [
|
||||||
new Stat('characters', '\\S'),
|
new Stat('字数', '\\S'),
|
||||||
new Stat('words', '\\S+'),
|
new Stat('段落', '\\S.*'),
|
||||||
new Stat('paragraphs', '\\S.*'),
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
computed: mapGetters('layout', [
|
computed: mapGetters('layout', [
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="tour-step__inner" v-else-if="step === 'explorer'">
|
<div class="tour-step__inner" v-else-if="step === 'explorer'">
|
||||||
<h2>文件资源管理器</h2>
|
<h2>文件资源管理器</h2>
|
||||||
<p>StackEdit可以管理工作区中的多个文件和文件夹。</p>
|
<p>StackEdit可以管理文档空间中的多个文件和文件夹。</p>
|
||||||
<p>点击 <icon-folder></icon-folder> 打开文件资源管理器。</p>
|
<p>点击 <icon-folder></icon-folder> 打开文件资源管理器。</p>
|
||||||
<div class="tour-step__button-bar">
|
<div class="tour-step__button-bar">
|
||||||
<button class="button" @click="finish">跳过</button>
|
<button class="button" @click="finish">跳过</button>
|
||||||
@ -30,7 +30,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="tour-step__inner" v-else-if="step === 'menu'">
|
<div class="tour-step__inner" v-else-if="step === 'menu'">
|
||||||
<h2>更多!</h2>
|
<h2>更多!</h2>
|
||||||
<p>StackEdit还可以同步和发布文件,管理协作工作区...</p>
|
<p>StackEdit还可以同步和发布文件,管理协作文档空间...</p>
|
||||||
<p>点击 <icon-provider provider-id="stackedit"></icon-provider> 浏览菜单。</p>
|
<p>点击 <icon-provider provider-id="stackedit"></icon-provider> 浏览菜单。</p>
|
||||||
<div class="tour-step__button-bar">
|
<div class="tour-step__button-bar">
|
||||||
<button class="button" @click="finish">跳过</button>
|
<button class="button" @click="finish">跳过</button>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</p>
|
</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="loading">历史版本加载中…</p>
|
||||||
<p v-else-if="!revisionsWithSpacer.length"><b>{{currentFileName}}</b> 没有历史版本.</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>
|
<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 EditorClassApplier from '../common/EditorClassApplier';
|
||||||
import PreviewClassApplier from '../common/PreviewClassApplier';
|
import PreviewClassApplier from '../common/PreviewClassApplier';
|
||||||
import utils from '../../services/utils';
|
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 syncSvc from '../../services/syncSvc';
|
||||||
import store from '../../store';
|
import store from '../../store';
|
||||||
import badgeSvc from '../../services/badgeSvc';
|
import badgeSvc from '../../services/badgeSvc';
|
||||||
@ -166,7 +166,7 @@ export default {
|
|||||||
]),
|
]),
|
||||||
async signin() {
|
async signin() {
|
||||||
try {
|
try {
|
||||||
await googleHelper.signin();
|
await giteeHelper.signin();
|
||||||
syncSvc.requestSync();
|
syncSvc.requestSync();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Cancel
|
// Cancel
|
||||||
|
@ -33,14 +33,14 @@
|
|||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="exportPdf">
|
<menu-entry @click.native="exportPdf">
|
||||||
<icon-download slot="icon"></icon-download>
|
<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>
|
<span>从HTML模板生成PDF。</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="exportPandoc">
|
<!-- <menu-entry @click.native="exportPandoc">
|
||||||
<icon-download slot="icon"></icon-download>
|
<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>
|
<span>转换为PDF、Word、EPUB...</span>
|
||||||
</menu-entry>
|
</menu-entry> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
<div class="menu-entry__icon menu-entry__icon--image">
|
<div class="menu-entry__icon menu-entry__icon--image">
|
||||||
<icon-provider :provider-id="currentWorkspace.providerId"></icon-provider>
|
<icon-provider :provider-id="currentWorkspace.providerId"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="currentWorkspace.providerId === 'googleDriveAppData'">
|
<span v-if="currentWorkspace.providerId === 'giteeAppData'">
|
||||||
<b>{{currentWorkspace.name}}</b> 与您的 Google Drive 应用数据文件夹同步。
|
<b>{{currentWorkspace.name}}</b> 与您的 Gitee 默认文档空间仓库同步。
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="currentWorkspace.providerId === 'googleDriveWorkspace'">
|
<span v-else-if="currentWorkspace.providerId === 'googleDriveWorkspace'">
|
||||||
<b>{{currentWorkspace.name}}</b> 与 <a :href="workspaceLocationUrl" target="_blank">Google Drive 文件夹</a>同步。
|
<b>{{currentWorkspace.name}}</b> 与 <a :href="workspaceLocationUrl" target="_blank">Google Drive 文件夹</a>同步。
|
||||||
@ -42,13 +42,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<menu-entry v-if="!loginToken" @click.native="signin">
|
<menu-entry v-if="!loginToken" @click.native="signin">
|
||||||
<icon-login slot="icon"></icon-login>
|
<icon-login slot="icon"></icon-login>
|
||||||
<div>使用 Google 登录</div>
|
<div>使用 Gitee 登录</div>
|
||||||
<span>同步您的主工作区并解锁功能。</span>
|
<span>同步您的主文档空间并解锁功能。</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="setPanel('workspaces')">
|
<menu-entry @click.native="setPanel('workspaces')">
|
||||||
<icon-database slot="icon"></icon-database>
|
<icon-database slot="icon"></icon-database>
|
||||||
<div><div class="menu-entry__label menu-entry__label--count" v-if="workspaceCount">{{workspaceCount}}</div> 工作区</div>
|
<div><div class="menu-entry__label menu-entry__label--count" v-if="workspaceCount">{{workspaceCount}}</div> 文档空间</div>
|
||||||
<span>切换到另一个工作区。</span>
|
<span>切换到另一个文档空间。</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<hr>
|
<hr>
|
||||||
<menu-entry @click.native="setPanel('sync')">
|
<menu-entry @click.native="setPanel('sync')">
|
||||||
@ -113,7 +113,7 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<menu-entry @click.native="setPanel('workspaceBackups')">
|
<menu-entry @click.native="setPanel('workspaceBackups')">
|
||||||
<icon-content-save slot="icon"></icon-content-save>
|
<icon-content-save slot="icon"></icon-content-save>
|
||||||
工作区备份
|
文档空间备份
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="reset">
|
<menu-entry @click.native="reset">
|
||||||
<icon-logout slot="icon"></icon-logout>
|
<icon-logout slot="icon"></icon-logout>
|
||||||
@ -131,7 +131,7 @@ import { mapGetters, mapActions } from 'vuex';
|
|||||||
import MenuEntry from './common/MenuEntry';
|
import MenuEntry from './common/MenuEntry';
|
||||||
import providerRegistry from '../../services/providers/common/providerRegistry';
|
import providerRegistry from '../../services/providers/common/providerRegistry';
|
||||||
import UserImage from '../UserImage';
|
import UserImage from '../UserImage';
|
||||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
import giteeHelper from '../../services/providers/helpers/giteeHelper';
|
||||||
import syncSvc from '../../services/syncSvc';
|
import syncSvc from '../../services/syncSvc';
|
||||||
import userSvc from '../../services/userSvc';
|
import userSvc from '../../services/userSvc';
|
||||||
import store from '../../store';
|
import store from '../../store';
|
||||||
@ -183,7 +183,7 @@ export default {
|
|||||||
}),
|
}),
|
||||||
async signin() {
|
async signin() {
|
||||||
try {
|
try {
|
||||||
await googleHelper.signin();
|
await giteeHelper.signin();
|
||||||
syncSvc.requestSync();
|
syncSvc.requestSync();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Cancel
|
// Cancel
|
||||||
|
@ -6,12 +6,12 @@
|
|||||||
<icon-content-save></icon-content-save>
|
<icon-content-save></icon-content-save>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex--column">
|
<div class="flex flex--column">
|
||||||
导入工作区备份
|
导入文档空间备份
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<menu-entry @click.native="exportWorkspace">
|
<menu-entry @click.native="exportWorkspace">
|
||||||
<icon-content-save slot="icon"></icon-content-save>
|
<icon-content-save slot="icon"></icon-content-save>
|
||||||
导出工作区备份
|
导出文档空间备份
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
<div class="side-bar__panel side-bar__panel--menu">
|
<div class="side-bar__panel side-bar__panel--menu">
|
||||||
<menu-entry @click.native="manageWorkspaces">
|
<menu-entry @click.native="manageWorkspaces">
|
||||||
<icon-database slot="icon"></icon-database>
|
<icon-database slot="icon"></icon-database>
|
||||||
<div><div class="menu-entry__label menu-entry__label--count">{{workspaceCount}}</div> 管理工作区</div>
|
<div><div class="menu-entry__label menu-entry__label--count">{{workspaceCount}}</div> 管理文档空间</div>
|
||||||
<span>列出、重命名、删除工作区</span>
|
<span>列出、重命名、删除文档空间</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="workspace" v-for="(workspace, id) in workspacesById" :key="id">
|
<div class="workspace" v-for="(workspace, id) in workspacesById" :key="id">
|
||||||
@ -15,27 +15,27 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<menu-entry @click.native="addGithubWorkspace">
|
<menu-entry @click.native="addGithubWorkspace">
|
||||||
<icon-provider slot="icon" provider-id="githubWorkspace"></icon-provider>
|
<icon-provider slot="icon" provider-id="githubWorkspace"></icon-provider>
|
||||||
<span>新增 <b>GitHub</b> 工作区</span>
|
<span>新增 <b>GitHub</b> 文档空间</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="addGiteeWorkspace">
|
<menu-entry @click.native="addGiteeWorkspace">
|
||||||
<icon-provider slot="icon" provider-id="giteeWorkspace"></icon-provider>
|
<icon-provider slot="icon" provider-id="giteeWorkspace"></icon-provider>
|
||||||
<span>新增 <b>Gitee</b> 工作区</span>
|
<span>新增 <b>Gitee</b> 文档空间</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="addGitlabWorkspace">
|
<menu-entry @click.native="addGitlabWorkspace">
|
||||||
<icon-provider slot="icon" provider-id="gitlabWorkspace"></icon-provider>
|
<icon-provider slot="icon" provider-id="gitlabWorkspace"></icon-provider>
|
||||||
<span>新增 <b>GitLab</b> 工作区</span>
|
<span>新增 <b>GitLab</b> 文档空间</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="addGiteaWorkspace">
|
<menu-entry @click.native="addGiteaWorkspace">
|
||||||
<icon-provider slot="icon" provider-id="giteaWorkspace"></icon-provider>
|
<icon-provider slot="icon" provider-id="giteaWorkspace"></icon-provider>
|
||||||
<span>新增 <b>Gitea</b> 工作区</span>
|
<span>新增 <b>Gitea</b> 文档空间</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="addGoogleDriveWorkspace">
|
<menu-entry @click.native="addGoogleDriveWorkspace">
|
||||||
<icon-provider slot="icon" provider-id="googleDriveWorkspace"></icon-provider>
|
<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>
|
||||||
<menu-entry @click.native="addCouchdbWorkspace">
|
<menu-entry @click.native="addCouchdbWorkspace">
|
||||||
<icon-provider slot="icon" provider-id="couchdbWorkspace"></icon-provider>
|
<icon-provider slot="icon" provider-id="couchdbWorkspace"></icon-provider>
|
||||||
<span>新增 <b>CouchDB</b> 工作区</span>
|
<span>新增 <b>CouchDB</b> 文档空间</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -29,7 +29,6 @@
|
|||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import networkSvc from '../../services/networkSvc';
|
import networkSvc from '../../services/networkSvc';
|
||||||
import editorSvc from '../../services/editorSvc';
|
import editorSvc from '../../services/editorSvc';
|
||||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
|
||||||
import modalTemplate from './common/modalTemplate';
|
import modalTemplate from './common/modalTemplate';
|
||||||
import store from '../../store';
|
import store from '../../store';
|
||||||
import badgeSvc from '../../services/badgeSvc';
|
import badgeSvc from '../../services/badgeSvc';
|
||||||
@ -45,15 +44,11 @@ export default modalTemplate({
|
|||||||
const currentContent = store.getters['content/current'];
|
const currentContent = store.getters['content/current'];
|
||||||
const { selectedFormat } = this;
|
const { selectedFormat } = this;
|
||||||
store.dispatch('queue/enqueue', async () => {
|
store.dispatch('queue/enqueue', async () => {
|
||||||
const tokenToRefresh = store.getters['workspace/sponsorToken'];
|
|
||||||
const sponsorToken = tokenToRefresh && await googleHelper.refreshToken(tokenToRefresh);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { body } = await networkSvc.request({
|
const { body } = await networkSvc.request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'pandocExport',
|
url: 'pandocExport',
|
||||||
params: {
|
params: {
|
||||||
idToken: sponsorToken && sponsorToken.idToken,
|
|
||||||
format: selectedFormat,
|
format: selectedFormat,
|
||||||
options: JSON.stringify(store.getters['data/computedSettings'].pandoc),
|
options: JSON.stringify(store.getters['data/computedSettings'].pandoc),
|
||||||
metadata: JSON.stringify(currentContent.properties),
|
metadata: JSON.stringify(currentContent.properties),
|
||||||
@ -65,12 +60,8 @@ export default modalTemplate({
|
|||||||
FileSaver.saveAs(body, `${currentFile.name}.${selectedFormat}`);
|
FileSaver.saveAs(body, `${currentFile.name}.${selectedFormat}`);
|
||||||
badgeSvc.addBadge('exportPandoc');
|
badgeSvc.addBadge('exportPandoc');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.status === 401) {
|
console.error(err); // eslint-disable-line no-console
|
||||||
store.dispatch('modal/open', 'sponsorOnly');
|
store.dispatch('notification/error', err);
|
||||||
} else {
|
|
||||||
console.error(err); // eslint-disable-line no-console
|
|
||||||
store.dispatch('notification/error', err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import exportSvc from '../../services/exportSvc';
|
import exportSvc from '../../services/exportSvc';
|
||||||
import networkSvc from '../../services/networkSvc';
|
import networkSvc from '../../services/networkSvc';
|
||||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
|
||||||
import modalTemplate from './common/modalTemplate';
|
import modalTemplate from './common/modalTemplate';
|
||||||
import store from '../../store';
|
import store from '../../store';
|
||||||
import badgeSvc from '../../services/badgeSvc';
|
import badgeSvc from '../../services/badgeSvc';
|
||||||
@ -38,24 +37,17 @@ export default modalTemplate({
|
|||||||
this.config.resolve();
|
this.config.resolve();
|
||||||
const currentFile = store.getters['file/current'];
|
const currentFile = store.getters['file/current'];
|
||||||
store.dispatch('queue/enqueue', async () => {
|
store.dispatch('queue/enqueue', async () => {
|
||||||
const [sponsorToken, html] = await Promise.all([
|
const html = await exportSvc.applyTemplate(
|
||||||
Promise.resolve().then(() => {
|
currentFile.id,
|
||||||
const tokenToRefresh = store.getters['workspace/sponsorToken'];
|
this.allTemplatesById[this.selectedTemplate],
|
||||||
return tokenToRefresh && googleHelper.refreshToken(tokenToRefresh);
|
true,
|
||||||
}),
|
);
|
||||||
exportSvc.applyTemplate(
|
|
||||||
currentFile.id,
|
|
||||||
this.allTemplatesById[this.selectedTemplate],
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { body } = await networkSvc.request({
|
const { body } = await networkSvc.request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'pdfExport',
|
url: 'pdfExport',
|
||||||
params: {
|
params: {
|
||||||
idToken: sponsorToken && sponsorToken.idToken,
|
|
||||||
options: JSON.stringify(store.getters['data/computedSettings'].wkhtmltopdf),
|
options: JSON.stringify(store.getters['data/computedSettings'].wkhtmltopdf),
|
||||||
},
|
},
|
||||||
body: html,
|
body: html,
|
||||||
@ -65,12 +57,8 @@ export default modalTemplate({
|
|||||||
FileSaver.saveAs(body, `${currentFile.name}.pdf`);
|
FileSaver.saveAs(body, `${currentFile.name}.pdf`);
|
||||||
badgeSvc.addBadge('exportPdf');
|
badgeSvc.addBadge('exportPdf');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.status === 401) {
|
console.error(err); // eslint-disable-line no-console
|
||||||
store.dispatch('modal/open', 'sponsorOnly');
|
store.dispatch('notification/error', err);
|
||||||
} else {
|
|
||||||
console.error(err); // eslint-disable-line no-console
|
|
||||||
store.dispatch('notification/error', err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-sync></icon-sync>
|
<icon-sync></icon-sync>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="syncLocations.length"><b>{{currentFileName}}</b> is synchronized with the following location(s):</p>
|
<p v-if="syncLocations.length"><b>{{currentFileName}}</b> 与以下位置同步:</p>
|
||||||
<p v-else><b>{{currentFileName}}</b> is not synchronized yet.</p>
|
<p v-else><b>{{currentFileName}}</b>尚未同步。</p>
|
||||||
<div>
|
<div>
|
||||||
<div class="sync-entry flex flex--column" v-for="location in syncLocations" :key="location.id">
|
<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">
|
<div class="sync-entry__header flex flex--row flex--align-center">
|
||||||
@ -23,7 +23,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="sync-entry__row flex flex--row flex--align-center">
|
<div class="sync-entry__row flex flex--row flex--align-center">
|
||||||
<div class="sync-entry__url">
|
<div class="sync-entry__url">
|
||||||
{{location.url || 'Google Drive app data'}}
|
{{location.url || 'Gitee app data'}}
|
||||||
</div>
|
</div>
|
||||||
<div class="sync-entry__buttons flex flex--row flex--center" v-if="location.url">
|
<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'">
|
<button class="sync-entry__button button" v-clipboard="location.url" @click="info('位置URL复制到剪贴板!')" v-title="'复制URL'">
|
||||||
@ -37,11 +37,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__info" v-if="syncLocations.length">
|
<div class="modal__info" v-if="syncLocations.length">
|
||||||
<b>Tip:</b> Removing a location won't delete any file.
|
<b>提示:</b> 删除位置不会删除任何文件。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<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>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<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__content">
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-database></icon-database>
|
<icon-database></icon-database>
|
||||||
</div>
|
</div>
|
||||||
<p><br>可以访问以下工作区:</p>
|
<p><br>可以访问以下文档空间:</p>
|
||||||
<div class="workspace-entry flex flex--column" v-for="(workspace, id) in workspacesById" :key="id">
|
<div class="workspace-entry flex flex--column" v-for="(workspace, id) in workspacesById" :key="id">
|
||||||
<div class="flex flex--column">
|
<div class="flex flex--column">
|
||||||
<div class="workspace-entry__header flex flex--row flex--align-center">
|
<div class="workspace-entry__header flex flex--row flex--align-center">
|
||||||
@ -27,10 +27,10 @@
|
|||||||
{{workspace.url}}
|
{{workspace.url}}
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-entry__buttons flex flex--row">
|
<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>
|
<icon-content-copy></icon-content-copy>
|
||||||
</button>
|
</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>
|
<icon-open-in-new></icon-open-in-new>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -40,10 +40,10 @@
|
|||||||
{{workspace.locationUrl}}
|
{{workspace.locationUrl}}
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-entry__buttons flex flex--row">
|
<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>
|
<icon-content-copy></icon-content-copy>
|
||||||
</button>
|
</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>
|
<icon-open-in-new></icon-open-in-new>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -117,9 +117,9 @@ export default {
|
|||||||
},
|
},
|
||||||
async remove(id) {
|
async remove(id) {
|
||||||
if (id === this.mainWorkspace.id) {
|
if (id === this.mainWorkspace.id) {
|
||||||
this.info('您的主工作区无法删除。');
|
this.info('您的主文档空间无法删除。');
|
||||||
} else if (id === this.currentWorkspace.id) {
|
} else if (id === this.currentWorkspace.id) {
|
||||||
this.info('请先关闭工作区,然后再将其删除。');
|
this.info('请先关闭文档空间,然后再将其删除。');
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await store.dispatch('modal/open', 'removeWorkspace');
|
await store.dispatch('modal/open', 'removeWorkspace');
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<modal-inner aria-label="增加CouchDB工作区">
|
<modal-inner aria-label="增加CouchDB文档空间">
|
||||||
<div class="modal__content">
|
<div class="modal__content">
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="couchdb"></icon-provider>
|
<icon-provider provider-id="couchdb"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>创建一个与<b>CouchDB</b>数据库同步的工作区。</p>
|
<p>创建一个与<b>CouchDB</b>数据库同步的文档空间。</p>
|
||||||
<form-entry label="Database URL" error="dbUrl">
|
<form-entry label="Database URL" error="dbUrl">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="dbUrl" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="dbUrl" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="gitea"></icon-provider>
|
<icon-provider provider-id="gitea"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>创建一个与<b> Gitea </b>项目文件夹同步的工作区。</p>
|
<p>创建一个与<b> Gitea </b>项目文件夹同步的文档空间。</p>
|
||||||
<form-entry label="Project URL" error="projectUrl">
|
<form-entry label="Project URL" error="projectUrl">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="gitee"></icon-provider>
|
<icon-provider provider-id="gitee"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>创建一个与<b>Gitee</b>仓库文件夹同步的工作区。</p>
|
<p>创建一个与<b>Gitee</b>仓库文件夹同步的文档空间。</p>
|
||||||
<form-entry label="仓库URL" error="repoUrl">
|
<form-entry label="仓库URL" error="repoUrl">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="github"></icon-provider>
|
<icon-provider provider-id="github"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>创建一个与<b>GitHub</b>仓库文件夹同步的工作区。</p>
|
<p>创建一个与<b>GitHub</b>仓库文件夹同步的文档空间。</p>
|
||||||
<form-entry label="仓库URL" error="repoUrl">
|
<form-entry label="仓库URL" error="repoUrl">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="gitlab"></icon-provider>
|
<icon-provider provider-id="gitlab"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>创建一个与<b>GitLab</b>仓库文件夹同步的工作区。</p>
|
<p>创建一个与<b>GitLab</b>仓库文件夹同步的文档空间。</p>
|
||||||
<form-entry label="Project URL" error="projectUrl">
|
<form-entry label="Project URL" error="projectUrl">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<modal-inner aria-label="添加Google Drive工作区">
|
<modal-inner aria-label="添加Google Drive文档空间">
|
||||||
<div class="modal__content">
|
<div class="modal__content">
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="googleDrive"></icon-provider>
|
<icon-provider provider-id="googleDrive"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>创建一个与<b> Google Drive </b>文件夹同步的工作区。</p>
|
<p>创建一个与<b> Google Drive </b>文件夹同步的文档空间。</p>
|
||||||
<form-entry label="Folder ID" info="可选的">
|
<form-entry label="Folder ID" info="可选的">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="folderId" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="folderId" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
export default () => ({
|
export default () => ({
|
||||||
main: {
|
main: {
|
||||||
id: 'main',
|
id: 'main',
|
||||||
name: '主工作区',
|
name: '主文档空间',
|
||||||
// The rest will be filled by the workspace/workspacesById getter
|
// The rest will be filled by the workspace/workspacesById getter
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
**我的数据存储在哪里?**
|
**我的数据存储在哪里?**
|
||||||
|
|
||||||
如果您的工作区没有同步,则文件存储在浏览器中,无处可寻。
|
如果您的文档空间没有同步,则文件存储在浏览器中,无处可寻。
|
||||||
|
|
||||||
我们建议同步您的工作区,以确保在清除浏览器数据的情况下不会丢失文件。自托管Gitea后端非常适合保证隐私。
|
我们建议同步您的文档空间,以确保在清除浏览器数据的情况下不会丢失文件。自托管Gitea后端非常适合保证隐私。
|
||||||
|
|
||||||
**StackEdit可以访问我的数据而不告诉我吗?**
|
**StackEdit可以访问我的数据而不告诉我吗?**
|
||||||
|
|
||||||
|
@ -54,22 +54,22 @@ export default [
|
|||||||
new Feature(
|
new Feature(
|
||||||
'explorer',
|
'explorer',
|
||||||
'资源管理器',
|
'资源管理器',
|
||||||
'使用文件资源管理器管理工作区中的文件和文件夹。',
|
'使用文件资源管理器管理文档空间中的文件和文件夹。',
|
||||||
[
|
[
|
||||||
new Feature(
|
new Feature(
|
||||||
'createFile',
|
'createFile',
|
||||||
'文件创建',
|
'文件创建',
|
||||||
'使用文件资源管理器在工作区中创建一个新文件。',
|
'使用文件资源管理器在文档空间中创建一个新文件。',
|
||||||
),
|
),
|
||||||
new Feature(
|
new Feature(
|
||||||
'switchFile',
|
'switchFile',
|
||||||
'文件切换',
|
'文件切换',
|
||||||
'使用文件资源管理器在工作区中从一个文件切换到另一个文件。',
|
'使用文件资源管理器在文档空间中从一个文件切换到另一个文件。',
|
||||||
),
|
),
|
||||||
new Feature(
|
new Feature(
|
||||||
'createFolder',
|
'createFolder',
|
||||||
'文件夹创建',
|
'文件夹创建',
|
||||||
'使用文件资源管理器在工作区中创建一个新文件夹。',
|
'使用文件资源管理器在文档空间中创建一个新文件夹。',
|
||||||
),
|
),
|
||||||
new Feature(
|
new Feature(
|
||||||
'moveFile',
|
'moveFile',
|
||||||
@ -84,22 +84,22 @@ export default [
|
|||||||
new Feature(
|
new Feature(
|
||||||
'renameFile',
|
'renameFile',
|
||||||
'文件重命名',
|
'文件重命名',
|
||||||
'使用文件资源管理器重命名工作区中的文件。',
|
'使用文件资源管理器重命名文档空间中的文件。',
|
||||||
),
|
),
|
||||||
new Feature(
|
new Feature(
|
||||||
'renameFolder',
|
'renameFolder',
|
||||||
'文件夹重命名',
|
'文件夹重命名',
|
||||||
'使用文件资源管理器重命名工作区中的文件夹。',
|
'使用文件资源管理器重命名文档空间中的文件夹。',
|
||||||
),
|
),
|
||||||
new Feature(
|
new Feature(
|
||||||
'removeFile',
|
'removeFile',
|
||||||
'文件删除',
|
'文件删除',
|
||||||
'使用文件资源管理器删除工作区中的文件。',
|
'使用文件资源管理器删除文档空间中的文件。',
|
||||||
),
|
),
|
||||||
new Feature(
|
new Feature(
|
||||||
'removeFolder',
|
'removeFolder',
|
||||||
'文件夹删除',
|
'文件夹删除',
|
||||||
'使用文件资源管理器删除工作区中的文件夹。',
|
'使用文件资源管理器删除文档空间中的文件夹。',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -143,64 +143,64 @@ export default [
|
|||||||
new Feature(
|
new Feature(
|
||||||
'signIn',
|
'signIn',
|
||||||
'登录',
|
'登录',
|
||||||
'使用 Google 登录,同步您的主工作区并解锁功能。',
|
'使用 Gitee 登录,同步您的主文档空间并解锁功能。',
|
||||||
[
|
[
|
||||||
new Feature(
|
new Feature(
|
||||||
'syncMainWorkspace',
|
'syncMainWorkspace',
|
||||||
'主工作区已同步',
|
'主文档空间已同步',
|
||||||
'使用 Google 登录以将您的主工作区与您的 Google Drive 应用数据文件夹同步。',
|
'使用 Gitee 登录以将您的主文档空间与您的默认空间stackedit-app-data仓库数据同步。',
|
||||||
),
|
),
|
||||||
new Feature(
|
new Feature(
|
||||||
'sponsor',
|
'sponsor',
|
||||||
'赞助',
|
'赞助',
|
||||||
'使用 Google 登录并赞助 StackEdit 以解锁 PDF 和 Pandoc 导出。',
|
'使用 Google 登录并赞助 StackEdit 以解锁 PDF 和 Pandoc 导出。(暂不支持赞助)',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
new Feature(
|
new Feature(
|
||||||
'workspaces',
|
'workspaces',
|
||||||
'工作区菜单',
|
'文档空间菜单',
|
||||||
'使用工作区菜单创建各种工作区并对其进行管理。',
|
'使用文档空间菜单创建各种文档空间并对其进行管理。',
|
||||||
[
|
[
|
||||||
new Feature(
|
new Feature(
|
||||||
'addCouchdbWorkspace',
|
'addCouchdbWorkspace',
|
||||||
'创建CouchDB工作区',
|
'创建CouchDB文档空间',
|
||||||
'使用工作区菜单创建CouchDB工作区。',
|
'使用文档空间菜单创建CouchDB文档空间。',
|
||||||
),
|
),
|
||||||
new Feature(
|
new Feature(
|
||||||
'addGithubWorkspace',
|
'addGithubWorkspace',
|
||||||
'创建GitHub工作区',
|
'创建GitHub文档空间',
|
||||||
'使用工作区菜单创建GitHub工作区。',
|
'使用文档空间菜单创建GitHub文档空间。',
|
||||||
),
|
),
|
||||||
new Feature(
|
new Feature(
|
||||||
'addGiteeWorkspace',
|
'addGiteeWorkspace',
|
||||||
'创建Gitee工作区',
|
'创建Gitee文档空间',
|
||||||
'使用工作区菜单创建Gitee工作区。',
|
'使用文档空间菜单创建Gitee文档空间。',
|
||||||
),
|
),
|
||||||
new Feature(
|
new Feature(
|
||||||
'addGitlabWorkspace',
|
'addGitlabWorkspace',
|
||||||
'创建Gitlab工作区',
|
'创建Gitlab文档空间',
|
||||||
'使用工作区菜单创建GitLab工作区。',
|
'使用文档空间菜单创建GitLab文档空间。',
|
||||||
),
|
),
|
||||||
new Feature(
|
new Feature(
|
||||||
'addGiteaWorkspace',
|
'addGiteaWorkspace',
|
||||||
'创建Gitea工作区',
|
'创建Gitea文档空间',
|
||||||
'使用工作区菜单创建Gitea工作区。',
|
'使用文档空间菜单创建Gitea文档空间。',
|
||||||
),
|
),
|
||||||
new Feature(
|
new Feature(
|
||||||
'addGoogleDriveWorkspace',
|
'addGoogleDriveWorkspace',
|
||||||
'创建Google Drive工作区',
|
'创建Google Drive文档空间',
|
||||||
'使用工作区菜单创建Google Drive工作区。',
|
'使用文档空间菜单创建Google Drive文档空间。',
|
||||||
),
|
),
|
||||||
new Feature(
|
new Feature(
|
||||||
'renameWorkspace',
|
'renameWorkspace',
|
||||||
'工作区重命名',
|
'文档空间重命名',
|
||||||
'使用“管理工作区”对话框重命名工作区。',
|
'使用“管理文档空间”对话框重命名文档空间。',
|
||||||
),
|
),
|
||||||
new Feature(
|
new Feature(
|
||||||
'removeWorkspace',
|
'removeWorkspace',
|
||||||
'工作区删除',
|
'文档空间删除',
|
||||||
'使用“管理工作区”对话框在本地删除工作区。',
|
'使用“管理文档空间”对话框在本地删除文档空间。',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -7,33 +7,33 @@ const simpleModal = (contentHtml, rejectText, resolveText) => ({
|
|||||||
/* eslint sort-keys: "error" */
|
/* eslint sort-keys: "error" */
|
||||||
export default {
|
export default {
|
||||||
commentDeletion: simpleModal(
|
commentDeletion: simpleModal(
|
||||||
'<p>You are about to delete a comment. Are you sure?</p>',
|
'<p>您将要删除评论。你确定吗?</p>',
|
||||||
'No',
|
'取消',
|
||||||
'Yes, delete',
|
'确认删除',
|
||||||
),
|
),
|
||||||
discussionDeletion: simpleModal(
|
discussionDeletion: simpleModal(
|
||||||
'<p>You are about to delete a discussion. Are you sure?</p>',
|
'<p>您将要删除讨论。你确定吗?</p>',
|
||||||
'No',
|
'取消',
|
||||||
'Yes, delete',
|
'确认删除',
|
||||||
),
|
),
|
||||||
fileRestoration: simpleModal(
|
fileRestoration: simpleModal(
|
||||||
'<p>You are about to revert some changes. Are you sure?</p>',
|
'<p>您将要恢复一些更改。你确定吗?</p>',
|
||||||
'No',
|
'取消',
|
||||||
'Yes, revert',
|
'确认恢复',
|
||||||
),
|
),
|
||||||
folderDeletion: simpleModal(
|
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>`,
|
config => `<p>您将删除文件夹<b>${config.item.name}</b>。它的文件将移至回收站。你确定吗?</p>`,
|
||||||
'No',
|
'取消',
|
||||||
'Yes, delete',
|
'确认删除',
|
||||||
),
|
),
|
||||||
pathConflict: simpleModal(
|
pathConflict: simpleModal(
|
||||||
config => `<p><b>${config.item.name}</b> already exists. Do you want to add a suffix?</p>`,
|
config => `<p><b>${config.item.name}</b>已经存在。您要添加后缀吗?</p>`,
|
||||||
'No',
|
'取消',
|
||||||
'Yes, add suffix',
|
'确认添加',
|
||||||
),
|
),
|
||||||
paymentSuccess: simpleModal(
|
paymentSuccess: simpleModal(
|
||||||
'<h3>Thank you for your payment!</h3><p>Your sponsorship will be active in a minute.</p>',
|
'<h3>感谢您的付款!</h3> <p>您的赞助将在一分钟内活跃。</p>',
|
||||||
'Ok',
|
'好的',
|
||||||
),
|
),
|
||||||
providerRedirection: simpleModal(
|
providerRedirection: simpleModal(
|
||||||
config => `<p>您将跳转到 <b>${config.name}</b> 授权页面。</p>`,
|
config => `<p>您将跳转到 <b>${config.name}</b> 授权页面。</p>`,
|
||||||
@ -41,57 +41,57 @@ export default {
|
|||||||
'确认跳转',
|
'确认跳转',
|
||||||
),
|
),
|
||||||
removeWorkspace: simpleModal(
|
removeWorkspace: simpleModal(
|
||||||
'<p>You are about to remove a workspace locally. Are you sure?</p>',
|
'<p>您将要在本地删除文档空间ß。你确定吗?</p>',
|
||||||
'No',
|
'取消',
|
||||||
'Yes, remove',
|
'确认删除',
|
||||||
),
|
),
|
||||||
reset: simpleModal(
|
reset: simpleModal(
|
||||||
'<p>这将在本地清理所有工作区,你确定吗?</p>',
|
'<p>这将在本地清理所有文档空间,你确定吗?</p>',
|
||||||
'取消',
|
'取消',
|
||||||
'确认清理',
|
'确认清理',
|
||||||
),
|
),
|
||||||
signInForComment: simpleModal(
|
signInForComment: simpleModal(
|
||||||
`<p>您必须使用 Google 登录才能开始评论。</p>
|
`<p>您必须使用 Google 登录才能开始评论。</p>
|
||||||
<div class="modal__info"><b>注意:</b> 这将同步您的主工作区。</div>`,
|
<div class="modal__info"><b>注意:</b> 这将同步您的主文档空间。</div>`,
|
||||||
'取消',
|
'取消',
|
||||||
'确认登录',
|
'确认登录',
|
||||||
),
|
),
|
||||||
signInForSponsorship: simpleModal(
|
signInForSponsorship: simpleModal(
|
||||||
`<p>您必须使用 Google 登录才能赞助。</p>
|
`<p>您必须使用 Google 登录才能赞助。</p>
|
||||||
<div class="modal__info"><b>注意:</b> 这将同步您的主工作区。</div>`,
|
<div class="modal__info"><b>注意:</b> 这将同步您的主文档空间。</div>`,
|
||||||
'取消',
|
'取消',
|
||||||
'确认登录',
|
'确认登录',
|
||||||
),
|
),
|
||||||
sponsorOnly: simpleModal(
|
sponsorOnly: simpleModal(
|
||||||
'<p>This feature is restricted to sponsors as it relies on server resources.</p>',
|
'<p>此功能仅限于赞助商,因为它依赖于服务器资源。</p>',
|
||||||
'Ok, I understand',
|
'好的,我明白了',
|
||||||
),
|
),
|
||||||
stripName: simpleModal(
|
stripName: simpleModal(
|
||||||
config => `<p><b>${config.item.name}</b> contains illegal characters. Do you want to strip them?</p>`,
|
config => `<p><b>${config.item.name}</b>包含非法字符。你想剥离它们吗?</p>`,
|
||||||
'No',
|
'取消',
|
||||||
'Yes, strip',
|
'确认剥离',
|
||||||
),
|
),
|
||||||
tempFileDeletion: simpleModal(
|
tempFileDeletion: simpleModal(
|
||||||
config => `<p>You are about to permanently delete the temporary file <b>${config.item.name}</b>. Are you sure?</p>`,
|
config => `<p>您将永久删除临时文件<b>${config.item.name}</b>。你确定吗?</p>`,
|
||||||
'No',
|
'取消',
|
||||||
'Yes, delete',
|
'确认删除',
|
||||||
),
|
),
|
||||||
tempFolderDeletion: simpleModal(
|
tempFolderDeletion: simpleModal(
|
||||||
'<p>You are about to permanently delete all the temporary files. Are you sure?</p>',
|
'<p>您将永久删除所有临时文件。你确定吗?</p>',
|
||||||
'No',
|
'取消',
|
||||||
'Yes, delete all',
|
'确认删除',
|
||||||
),
|
),
|
||||||
trashDeletion: simpleModal(
|
trashDeletion: simpleModal(
|
||||||
'<p>Files in the trash are automatically deleted after 7 days of inactivity.</p>',
|
'<p>回收站中的文件在不活动7天后会自动删除。</p>',
|
||||||
'Ok',
|
'好的',
|
||||||
),
|
),
|
||||||
unauthorizedName: simpleModal(
|
unauthorizedName: simpleModal(
|
||||||
config => `<p><b>${config.item.name}</b> is an unauthorized name.</p>`,
|
config => `<p><b>${config.item.name}</b>>是未经授权的名称。</p>`,
|
||||||
'Ok',
|
'好的',
|
||||||
),
|
),
|
||||||
workspaceGoogleRedirection: simpleModal(
|
workspaceGoogleRedirection: simpleModal(
|
||||||
'<p>StackEdit needs full Google Drive access to open this workspace.</p>',
|
'<p>StackEdit需要完整的Google Drive访问才能打开此文档空间。</p>',
|
||||||
'Cancel',
|
'取消',
|
||||||
'Ok, grant',
|
'确认授权',
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
@ -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(圆角矩形)
|
A --> C(圆角矩形)
|
||||||
B --> D{菱形}
|
B --> D{菱形}
|
||||||
C --> D
|
C --> D
|
||||||
```
|
```
|
||||||
|
@ -11,7 +11,6 @@ export default {
|
|||||||
classState() {
|
classState() {
|
||||||
switch (this.providerId) {
|
switch (this.providerId) {
|
||||||
case 'googleDrive':
|
case 'googleDrive':
|
||||||
case 'googleDriveAppData':
|
|
||||||
case 'googleDriveWorkspace':
|
case 'googleDriveWorkspace':
|
||||||
return 'google-drive';
|
return 'google-drive';
|
||||||
case 'googlePhotos':
|
case 'googlePhotos':
|
||||||
@ -28,6 +27,7 @@ export default {
|
|||||||
return 'blogger';
|
return 'blogger';
|
||||||
case 'couchdbWorkspace':
|
case 'couchdbWorkspace':
|
||||||
return 'couchdb';
|
return 'couchdb';
|
||||||
|
case 'giteeAppData':
|
||||||
case 'giteeWorkspace':
|
case 'giteeWorkspace':
|
||||||
return 'gitee';
|
return 'gitee';
|
||||||
default:
|
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];
|
return store.getters['workspace/workspacesById'][workspaceId];
|
||||||
},
|
},
|
||||||
getChanges() {
|
getChanges() {
|
||||||
|
@ -7,6 +7,8 @@ import constants from '../../../data/constants';
|
|||||||
|
|
||||||
const tokenExpirationMargin = 5 * 60 * 1000;
|
const tokenExpirationMargin = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
const appDataRepo = 'stackedit-app-data';
|
||||||
|
|
||||||
const request = (token, options) => networkSvc.request({
|
const request = (token, options) => networkSvc.request({
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
@ -125,6 +127,8 @@ export default {
|
|||||||
sub: `${user.login}`,
|
sub: `${user.login}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 检查 stackedit-app-data 仓库是否已经存在 如果不存在则创建该仓库
|
||||||
|
await this.checkAndCreateRepo(token);
|
||||||
// Add token to gitee tokens
|
// Add token to gitee tokens
|
||||||
store.dispatch('data/addGiteeToken', token);
|
store.dispatch('data/addGiteeToken', token);
|
||||||
return token;
|
return token;
|
||||||
@ -162,6 +166,9 @@ export default {
|
|||||||
return this.startOauth2();
|
return this.startOauth2();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
signin() {
|
||||||
|
return this.startOauth2();
|
||||||
|
},
|
||||||
async addAccount() {
|
async addAccount() {
|
||||||
const token = await this.startOauth2();
|
const token = await this.startOauth2();
|
||||||
badgeSvc.addBadge('addGiteeAccount');
|
badgeSvc.addBadge('addGiteeAccount');
|
||||||
@ -191,6 +198,27 @@ export default {
|
|||||||
return tree;
|
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
|
* 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 diffUtils from './diffUtils';
|
||||||
import networkSvc from './networkSvc';
|
import networkSvc from './networkSvc';
|
||||||
import providerRegistry from './providers/common/providerRegistry';
|
import providerRegistry from './providers/common/providerRegistry';
|
||||||
import googleDriveAppDataProvider from './providers/googleDriveAppDataProvider';
|
import giteeAppDataProvider from './providers/giteeAppDataProvider';
|
||||||
import './providers/couchdbWorkspaceProvider';
|
import './providers/couchdbWorkspaceProvider';
|
||||||
import './providers/githubWorkspaceProvider';
|
import './providers/githubWorkspaceProvider';
|
||||||
import './providers/giteeWorkspaceProvider';
|
import './providers/giteeWorkspaceProvider';
|
||||||
@ -821,7 +821,7 @@ const requestSync = (addTriggerSyncBadge = false) => {
|
|||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
if (!isSyncPossible()) {
|
if (!isSyncPossible()) {
|
||||||
// Cancel sync
|
// Cancel sync
|
||||||
throw new Error('Sync not possible.');
|
throw new Error('无法同步。');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if we have to clean files
|
// Determine if we have to clean files
|
||||||
@ -888,7 +888,7 @@ export default {
|
|||||||
// Try to find a suitable workspace sync provider
|
// Try to find a suitable workspace sync provider
|
||||||
workspaceProvider = providerRegistry.providersById[utils.queryParams.providerId];
|
workspaceProvider = providerRegistry.providersById[utils.queryParams.providerId];
|
||||||
if (!workspaceProvider || !workspaceProvider.initWorkspace) {
|
if (!workspaceProvider || !workspaceProvider.initWorkspace) {
|
||||||
workspaceProvider = googleDriveAppDataProvider;
|
workspaceProvider = giteeAppDataProvider;
|
||||||
}
|
}
|
||||||
const workspace = await workspaceProvider.initWorkspace();
|
const workspace = await workspaceProvider.initWorkspace();
|
||||||
// Fix the URL hash
|
// Fix the URL hash
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import utils from '../services/utils';
|
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 syncSvc from '../services/syncSvc';
|
||||||
|
|
||||||
const idShifter = offset => (state, getters) => {
|
const idShifter = offset => (state, getters) => {
|
||||||
@ -137,7 +137,7 @@ export default {
|
|||||||
if (!loginToken) {
|
if (!loginToken) {
|
||||||
try {
|
try {
|
||||||
await dispatch('modal/open', 'signInForComment', { root: true });
|
await dispatch('modal/open', 'signInForComment', { root: true });
|
||||||
await googleHelper.signin();
|
await giteeHelper.signin();
|
||||||
syncSvc.requestSync();
|
syncSvc.requestSync();
|
||||||
await dispatch('createNewDiscussion', selection);
|
await dispatch('createNewDiscussion', selection);
|
||||||
} catch (e) { /* cancel */ }
|
} catch (e) { /* cancel */ }
|
||||||
|
@ -22,7 +22,7 @@ export default {
|
|||||||
Object.entries(rootGetters['data/workspaces']).forEach(([id, workspace]) => {
|
Object.entries(rootGetters['data/workspaces']).forEach(([id, workspace]) => {
|
||||||
const sanitizedWorkspace = {
|
const sanitizedWorkspace = {
|
||||||
id,
|
id,
|
||||||
providerId: 'googleDriveAppData',
|
providerId: 'giteeAppData',
|
||||||
sub: mainWorkspaceToken && mainWorkspaceToken.sub,
|
sub: mainWorkspaceToken && mainWorkspaceToken.sub,
|
||||||
...workspace,
|
...workspace,
|
||||||
};
|
};
|
||||||
@ -46,21 +46,18 @@ export default {
|
|||||||
currentWorkspace.providerId === 'githubWorkspace'
|
currentWorkspace.providerId === 'githubWorkspace'
|
||||||
|| currentWorkspace.providerId === 'giteeWorkspace'
|
|| currentWorkspace.providerId === 'giteeWorkspace'
|
||||||
|| currentWorkspace.providerId === 'gitlabWorkspace'
|
|| currentWorkspace.providerId === 'gitlabWorkspace'
|
||||||
|| currentWorkspace.providerId === 'giteaWorkspace',
|
|| currentWorkspace.providerId === 'giteaWorkspace'
|
||||||
|
|| currentWorkspace.providerId === 'giteeAppData',
|
||||||
currentWorkspaceHasUniquePaths: (state, { currentWorkspace }) =>
|
currentWorkspaceHasUniquePaths: (state, { currentWorkspace }) =>
|
||||||
currentWorkspace.providerId === 'githubWorkspace'
|
currentWorkspace.providerId === 'githubWorkspace'
|
||||||
|| currentWorkspace.providerId === 'giteeWorkspace'
|
|| currentWorkspace.providerId === 'giteeWorkspace'
|
||||||
|| currentWorkspace.providerId === 'gitlabWorkspace'
|
|| currentWorkspace.providerId === 'gitlabWorkspace'
|
||||||
|| currentWorkspace.providerId === 'giteaWorkspace',
|
|| currentWorkspace.providerId === 'giteaWorkspace'
|
||||||
|
|| currentWorkspace.providerId === 'giteeAppData',
|
||||||
lastSyncActivityKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastSyncActivity`,
|
lastSyncActivityKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastSyncActivity`,
|
||||||
lastFocusKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastWindowFocus`,
|
lastFocusKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastWindowFocus`,
|
||||||
mainWorkspaceToken: (state, getters, rootState, rootGetters) =>
|
mainWorkspaceToken: (state, getters, rootState, rootGetters) =>
|
||||||
utils.someResult(Object.values(rootGetters['data/googleTokensBySub']), (token) => {
|
utils.someResult(Object.values(rootGetters['data/giteeTokensBySub']), token => token),
|
||||||
if (token.isLogin) {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}),
|
|
||||||
syncToken: (state, { currentWorkspace, mainWorkspaceToken }, rootState, rootGetters) => {
|
syncToken: (state, { currentWorkspace, mainWorkspaceToken }, rootState, rootGetters) => {
|
||||||
switch (currentWorkspace.providerId) {
|
switch (currentWorkspace.providerId) {
|
||||||
case 'googleDriveWorkspace':
|
case 'googleDriveWorkspace':
|
||||||
@ -82,11 +79,11 @@ export default {
|
|||||||
loginType: (state, { currentWorkspace }) => {
|
loginType: (state, { currentWorkspace }) => {
|
||||||
switch (currentWorkspace.providerId) {
|
switch (currentWorkspace.providerId) {
|
||||||
case 'googleDriveWorkspace':
|
case 'googleDriveWorkspace':
|
||||||
default:
|
|
||||||
return 'google';
|
return 'google';
|
||||||
case 'githubWorkspace':
|
case 'githubWorkspace':
|
||||||
return 'github';
|
return 'github';
|
||||||
case 'giteeWorkspace':
|
case 'giteeWorkspace':
|
||||||
|
default:
|
||||||
return 'gitee';
|
return 'gitee';
|
||||||
case 'gitlabWorkspace':
|
case 'gitlabWorkspace':
|
||||||
return 'gitlab';
|
return 'gitlab';
|
||||||
|
@ -353,7 +353,7 @@
|
|||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="feature">
|
<div class="feature">
|
||||||
<h3>协作</h3>
|
<h3>协作</h3>
|
||||||
<p>借助 StackEdit,您可以共享协作工作空间,这要归功于同步机制。 如果两个协作者同时处理同一个文件,StackEdit 会负责合并更改。</p>
|
<p>借助 StackEdit,您可以共享协作文档空间,这要归功于同步机制。 如果两个协作者同时处理同一个文件,StackEdit 会负责合并更改。</p>
|
||||||
</div>
|
</div>
|
||||||
<img class="image" width="300" src="static/landing/workspace.png">
|
<img class="image" width="300" src="static/landing/workspace.png">
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user