mirror of
https://gitee.com/mafgwo/stackedit
synced 2024-11-16 11:42:23 +08:00
Modal refactoring
This commit is contained in:
parent
872f557d03
commit
ac66a77741
|
@ -1,3 +1,4 @@
|
|||
build/*.js
|
||||
config/*.js
|
||||
src/libs/*.js
|
||||
./index.js
|
||||
|
|
|
@ -24,7 +24,7 @@ var app = express()
|
|||
var compiler = webpack(webpackConfig)
|
||||
|
||||
// StackEdit custom middlewares
|
||||
require('./server')(app);
|
||||
require('../server')(app);
|
||||
|
||||
var devMiddleware = require('webpack-dev-middleware')(compiler, {
|
||||
publicPath: webpackConfig.output.publicPath,
|
||||
|
@ -62,8 +62,8 @@ app.use(devMiddleware)
|
|||
app.use(hotMiddleware)
|
||||
|
||||
// serve pure static assets
|
||||
// var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
|
||||
app.use(express.static('./static'))
|
||||
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
|
||||
app.use(staticPath, express.static('./static'))
|
||||
|
||||
var uri = 'http://localhost:' + port
|
||||
|
||||
|
|
|
@ -55,7 +55,10 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
test: /\.(ttf|eot|otf|woff2)$/, loader: 'ignore-loader'
|
||||
},
|
||||
{
|
||||
test: /\.woff(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
|
|
|
@ -8,6 +8,7 @@ var CopyWebpackPlugin = require('copy-webpack-plugin')
|
|||
var HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
var ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
var OfflinePlugin = require('offline-plugin');
|
||||
|
||||
var env = config.build.env
|
||||
|
||||
|
@ -90,7 +91,11 @@ var webpackConfig = merge(baseWebpackConfig, {
|
|||
to: config.build.assetsSubDirectory,
|
||||
ignore: ['.*']
|
||||
}
|
||||
])
|
||||
]),
|
||||
new OfflinePlugin({
|
||||
excludes: ['**/.*', '**/*.map', '**/index.html', '**/static/oauth2/callback.html'],
|
||||
externals: ['/app', '/oauth2/callback']
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
|
|
27
index.js
Normal file
27
index.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
var cluster = require('cluster');
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
var path = require('path');
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
|
||||
require('./server')(app);
|
||||
|
||||
var port = process.env.PORT || 8080;
|
||||
if(port === 443) {
|
||||
var fs = require('fs');
|
||||
var credentials = {
|
||||
key: fs.readFileSync(path.join(__dirname, '/../../shared/config/ssl.key'), 'utf8'),
|
||||
cert: fs.readFileSync(path.join(__dirname, '/../../shared/config/ssl.crt'), 'utf8'),
|
||||
ca: fs.readFileSync(path.join(__dirname, '/../../shared/config/ssl.ca'), 'utf8').split('\n\n')
|
||||
};
|
||||
var httpsServer = https.createServer(credentials, app);
|
||||
httpsServer.listen(port, null, function() {
|
||||
console.log('HTTPS server started: https://localhost');
|
||||
});
|
||||
port = 80;
|
||||
}
|
||||
var httpServer = http.createServer(app);
|
||||
httpServer.listen(port, null, function() {
|
||||
console.log('HTTP server started: http://localhost:' + port);
|
||||
});
|
21
package.json
21
package.json
|
@ -1,9 +1,13 @@
|
|||
{
|
||||
"name": "StackEdit",
|
||||
"version": "1.0.0",
|
||||
"description": "A Vue.js project",
|
||||
"author": "",
|
||||
"private": true,
|
||||
"name": "stackedit",
|
||||
"version": "5.0.0",
|
||||
"description": "Free, open-source, full-featured Markdown editor",
|
||||
"author": "Benoit Schweblin",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/benweet/stackedit/issues"
|
||||
},
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"postinstall": "gulp --cwd build build-prism",
|
||||
"start": "node build/dev-server.js",
|
||||
|
@ -14,6 +18,7 @@
|
|||
"bezier-easing": "^1.1.0",
|
||||
"clipboard": "^1.7.1",
|
||||
"clunderscore": "^1.0.3",
|
||||
"compression": "^1.7.0",
|
||||
"diff-match-patch": "^1.0.0",
|
||||
"file-saver": "^1.3.3",
|
||||
"handlebars": "^4.0.10",
|
||||
|
@ -33,6 +38,8 @@
|
|||
"prismjs": "^1.6.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"request": "^2.82.0",
|
||||
"serve-static": "^1.12.6",
|
||||
"stackedit": "^4.3.15",
|
||||
"vue": "^2.3.3",
|
||||
"vuex": "^2.3.1"
|
||||
},
|
||||
|
@ -58,7 +65,7 @@
|
|||
"eslint-plugin-html": "^2.0.0",
|
||||
"eslint-plugin-import": "^2.2.0",
|
||||
"eventsource-polyfill": "^0.9.6",
|
||||
"express": "^4.14.1",
|
||||
"express": "^4.15.5",
|
||||
"extract-text-webpack-plugin": "^2.0.0",
|
||||
"file-loader": "^0.11.1",
|
||||
"friendly-errors-webpack-plugin": "^1.1.3",
|
||||
|
@ -66,7 +73,9 @@
|
|||
"gulp-concat": "^2.6.1",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"http-proxy-middleware": "^0.17.3",
|
||||
"ignore-loader": "^0.1.2",
|
||||
"node-sass": "^4.5.3",
|
||||
"offline-plugin": "^4.8.4",
|
||||
"opn": "^4.0.2",
|
||||
"optimize-css-assets-webpack-plugin": "^1.3.0",
|
||||
"ora": "^1.2.0",
|
||||
|
|
|
@ -15,7 +15,6 @@ function githubToken(clientId, code) {
|
|||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
console.log(body)
|
||||
var token = qs.parse(body).access_token;
|
||||
if (token) {
|
||||
resolve(token);
|
||||
|
@ -26,13 +25,11 @@ function githubToken(clientId, code) {
|
|||
});
|
||||
}
|
||||
|
||||
module.exports = function (app) {
|
||||
app.get('/oauth2/githubToken', function (req, res) {
|
||||
githubToken(req.query.clientId, req.query.code)
|
||||
.then(function (token) {
|
||||
res.send(token);
|
||||
}, function (err) {
|
||||
res.status(400).send(err ? err.message || err.toString() : 'bad_code');
|
||||
});
|
||||
});
|
||||
exports.githubToken = function (req, res) {
|
||||
githubToken(req.query.clientId, req.query.code)
|
||||
.then(function (token) {
|
||||
res.send(token);
|
||||
}, function (err) {
|
||||
res.status(400).send(err ? err.message || err.toString() : 'bad_code');
|
||||
});
|
||||
};
|
50
server/index.js
Normal file
50
server/index.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
var compression = require('compression');
|
||||
var serveStatic = require('serve-static');
|
||||
var path = require('path');
|
||||
|
||||
module.exports = function (app) {
|
||||
// Force HTTPS on stackedit.io
|
||||
app.all('*', function(req, res, next) {
|
||||
if (req.headers.host === 'stackedit.io' && !req.secure && req.headers['x-forwarded-proto'] !== 'https') {
|
||||
return res.redirect('https://stackedit.io' + req.url);
|
||||
}
|
||||
/\.(eot|ttf|woff|svg)$/.test(req.url) && res.header('Access-Control-Allow-Origin', '*');
|
||||
next();
|
||||
});
|
||||
|
||||
// Use gzip compression
|
||||
app.use(compression());
|
||||
|
||||
app.post('/pdfExport', require('./pdf').export);
|
||||
app.get('/oauth2/githubToken', require('./github').githubToken);
|
||||
|
||||
// Serve landing.html in /
|
||||
app.get('/', function(req, res) {
|
||||
res.sendFile(require.resolve('stackedit/views/landing.html'));
|
||||
});
|
||||
// Serve editor.html in /viewer
|
||||
app.get('/editor', function(req, res) {
|
||||
res.sendFile(require.resolve('stackedit/views/editor.html'));
|
||||
});
|
||||
// Serve viewer.html in /viewer
|
||||
app.get('/viewer', function(req, res) {
|
||||
res.sendFile(require.resolve('stackedit/views/viewer.html'));
|
||||
});
|
||||
// Serve index.html in /app
|
||||
app.get('/app', function(req, res) {
|
||||
res.sendFile(path.join(__dirname, '../dist/index.html'));
|
||||
});
|
||||
// Serve callback.html in /app
|
||||
app.get('/oauth2/callback', function(req, res) {
|
||||
res.sendFile(path.join(__dirname, '../dist/static/oauth2/callback.html'));
|
||||
});
|
||||
|
||||
// Serve static resources
|
||||
app.use(serveStatic(path.join(__dirname, '../dist'))); // v5
|
||||
app.use(serveStatic(path.dirname(require.resolve('stackedit/public/cache.manifest')))); // v4
|
||||
|
||||
// Error 404
|
||||
app.use(function(req, res) {
|
||||
res.status(404).sendFile(require.resolve('stackedit/views/error_404.html'));
|
||||
});
|
||||
};
|
145
server/pdf.js
Normal file
145
server/pdf.js
Normal file
|
@ -0,0 +1,145 @@
|
|||
/* global window,MathJax */
|
||||
var spawn = require('child_process').spawn;
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var os = require('os');
|
||||
var request = require('request');
|
||||
|
||||
function waitForJavaScript() {
|
||||
if(window.MathJax) {
|
||||
// Amazon EC2: fix TeX font detection
|
||||
MathJax.Hub.Register.StartupHook("HTML-CSS Jax Startup",function () {
|
||||
var HTMLCSS = MathJax.OutputJax["HTML-CSS"];
|
||||
HTMLCSS.Font.checkWebFont = function (check,font,callback) {
|
||||
if (check.time(callback)) {
|
||||
return;
|
||||
}
|
||||
if (check.total === 0) {
|
||||
HTMLCSS.Font.testFont(font);
|
||||
setTimeout(check,200);
|
||||
} else {
|
||||
callback(check.STATUS.OK);
|
||||
}
|
||||
};
|
||||
});
|
||||
MathJax.Hub.Queue(function () {
|
||||
window.status = 'done';
|
||||
});
|
||||
}
|
||||
else {
|
||||
setTimeout(function() {
|
||||
window.status = 'done';
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
var authorizedPageSizes = [
|
||||
'A3',
|
||||
'A4',
|
||||
'Legal',
|
||||
'Letter'
|
||||
];
|
||||
|
||||
exports.export = function(req, res, next) {
|
||||
function onError(err) {
|
||||
next(err);
|
||||
}
|
||||
function onUnknownError() {
|
||||
res.statusCode = 400;
|
||||
res.end('Unknown error');
|
||||
}
|
||||
function onUnauthorizedError() {
|
||||
res.statusCode = 401;
|
||||
res.end('Unauthorized');
|
||||
}
|
||||
function onTimeout() {
|
||||
res.statusCode = 408;
|
||||
res.end('Request timeout');
|
||||
}
|
||||
request({
|
||||
uri: 'https://monetizejs.com/api/payments',
|
||||
qs: {
|
||||
access_token: req.query.token
|
||||
},
|
||||
json: true
|
||||
}, function (err, paymentsRes, payments) {
|
||||
var authorized = payments && payments.app == 'ESTHdCYOi18iLhhO' && (
|
||||
(payments.chargeOption && payments.chargeOption.alias == 'once') ||
|
||||
(payments.subscriptionOption && payments.subscriptionOption.alias == 'yearly'));
|
||||
if(err || paymentsRes.statusCode != 200 || !authorized) {
|
||||
return onUnauthorizedError();
|
||||
}
|
||||
var options, params = [];
|
||||
try {
|
||||
options = JSON.parse(req.query.options);
|
||||
}
|
||||
catch(e) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
// Margins
|
||||
var marginTop = parseInt(options.marginTop);
|
||||
params.push('-T', isNaN(marginTop) ? 25 : marginTop);
|
||||
var marginRight = parseInt(options.marginRight);
|
||||
params.push('-R', isNaN(marginRight) ? 25 : marginRight);
|
||||
var marginBottom = parseInt(options.marginBottom);
|
||||
params.push('-B', isNaN(marginBottom) ? 25 : marginBottom);
|
||||
var marginLeft = parseInt(options.marginLeft);
|
||||
params.push('-L', isNaN(marginLeft) ? 25 : marginLeft);
|
||||
|
||||
// Header
|
||||
options.headerCenter && params.push('--header-center', options.headerCenter);
|
||||
options.headerLeft && params.push('--header-left', options.headerLeft);
|
||||
options.headerRight && params.push('--header-right', options.headerRight);
|
||||
options.headerFontName && params.push('--header-font-name', options.headerFontName);
|
||||
options.headerFontSize && params.push('--header-font-size', options.headerFontSize);
|
||||
|
||||
// Footer
|
||||
options.footerCenter && params.push('--footer-center', options.footerCenter);
|
||||
options.footerLeft && params.push('--footer-left', options.footerLeft);
|
||||
options.footerRight && params.push('--footer-right', options.footerRight);
|
||||
options.footerFontName && params.push('--footer-font-name', options.footerFontName);
|
||||
options.footerFontSize && params.push('--footer-font-size', options.footerFontSize);
|
||||
|
||||
// Page size
|
||||
params.push('--page-size', authorizedPageSizes.indexOf(options.pageSize) === -1 ? 'A4' : options.pageSize);
|
||||
|
||||
// Use a temp file as wkhtmltopdf can't access /dev/stdout on Amazon EC2 for some reason
|
||||
var filePath = path.join(os.tmpDir(), Date.now() + '.pdf');
|
||||
var binPath = process.env.WKHTMLTOPDF_PATH || 'wkhtmltopdf';
|
||||
params.push('--run-script', waitForJavaScript.toString() + 'waitForJavaScript()');
|
||||
params.push('--window-status', 'done');
|
||||
var wkhtmltopdf = spawn(binPath, params.concat('-', filePath), {
|
||||
stdio: [
|
||||
'pipe',
|
||||
'ignore',
|
||||
'ignore'
|
||||
]
|
||||
});
|
||||
var timeoutId = setTimeout(function() {
|
||||
timeoutId = undefined;
|
||||
wkhtmltopdf.kill();
|
||||
}, 30000);
|
||||
wkhtmltopdf.on('error', onError);
|
||||
wkhtmltopdf.stdin.on('error', onError);
|
||||
wkhtmltopdf.on('close', function(code) {
|
||||
if(!timeoutId) {
|
||||
return onTimeout();
|
||||
}
|
||||
clearTimeout(timeoutId);
|
||||
if(code) {
|
||||
return onUnknownError();
|
||||
}
|
||||
var readStream = fs.createReadStream(filePath);
|
||||
readStream.on('open', function() {
|
||||
readStream.pipe(res);
|
||||
});
|
||||
readStream.on('close', function() {
|
||||
fs.unlink(filePath, function() {
|
||||
});
|
||||
});
|
||||
readStream.on('error', onUnknownError);
|
||||
});
|
||||
req.pipe(wkhtmltopdf.stdin);
|
||||
});
|
||||
};
|
10
src/assets/iconWordpress.svg
Normal file
10
src/assets/iconWordpress.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<g id="_x32__stroke">
|
||||
<g id="Wordpress_1_">
|
||||
<rect clip-rule="evenodd" fill="none" fill-rule="evenodd" height="128" width="128"/>
|
||||
<path clip-rule="evenodd" d="M65.123,69.595l-19.205,55.797 c5.736,1.688,11.8,2.608,18.081,2.608c7.452,0,14.6-1.288,21.253-3.628c-0.168-0.276-0.328-0.564-0.456-0.88L65.123,69.595z M120.16,33.294c0.276,2.04,0.432,4.224,0.432,6.58c0,6.492-1.216,13.792-4.868,22.924l-19.549,56.517 C115.204,108.223,128,87.606,128,63.998C128,52.87,125.156,42.41,120.16,33.294z M107.204,60.769 c0-7.912-2.844-13.388-5.276-17.648c-3.244-5.276-6.288-9.74-6.288-15.012c0-5.884,4.46-11.36,10.748-11.36 c0.284,0,0.552,0.036,0.828,0.052C95.832,6.368,80.659,0,63.999,0C41.638,0,21.969,11.472,10.525,28.844 c1.504,0.048,2.92,0.076,4.12,0.076c6.692,0,17.057-0.812,17.057-0.812c3.448-0.204,3.856,4.868,0.408,5.272 c0,0-3.468,0.408-7.324,0.612l23.305,69.321l14.008-42.005L52.13,33.992c-3.448-0.204-6.716-0.612-6.716-0.612 c-3.448-0.204-3.044-5.476,0.408-5.272c0,0,10.568,0.812,16.857,0.812c6.692,0,17.057-0.812,17.057-0.812 c3.452-0.204,3.856,4.868,0.408,5.272c0,0-3.472,0.408-7.324,0.612l23.129,68.793l6.388-21.328 C105.096,72.601,107.204,66.245,107.204,60.769z M0,63.997c0,25.332,14.72,47.225,36.069,57.597L5.54,37.952 C1.992,45.909,0,54.717,0,63.997z" fill="#00759D" fill-rule="evenodd" id="Wordpress"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
10
src/assets/iconZendesk.svg
Normal file
10
src/assets/iconZendesk.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 152 116">
|
||||
<g>
|
||||
<path d="M70.125,30.375l0,84.675l-70.125,0l70.125,-84.675Z" style="fill:#03363d;fill-rule:nonzero;"/>
|
||||
<path d="M70.125,0c0,19.35 -15.675,35.025 -35.025,35.025c-19.35,0 -35.1,-15.675 -35.1,-35.025l70.125,0Z" style="fill:#03363d;fill-rule:nonzero;"/>
|
||||
<path d="M81.675,115.05c0,-19.35 15.675,-35.025 35.025,-35.025c19.35,0 35.025,15.675 35.025,35.025l-70.05,0Z" style="fill:#03363d;fill-rule:nonzero;"/>
|
||||
<path d="M81.675,84.675l0,-84.675l70.125,0l-70.125,84.675Z" style="fill:#03363d;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 605 B |
|
@ -20,7 +20,6 @@
|
|||
<script>
|
||||
import { mapMutations, mapActions } from 'vuex';
|
||||
import utils from '../services/utils';
|
||||
import defaultContent from '../data/defaultContent.md';
|
||||
|
||||
export default {
|
||||
name: 'explorer-node',
|
||||
|
@ -99,9 +98,14 @@ export default {
|
|||
id,
|
||||
});
|
||||
} else {
|
||||
// Add empty line at the end if needed
|
||||
const ensureFinalNewLine = text => `${text}\n`.replace(/\n\n$/, '\n');
|
||||
const text = ensureFinalNewLine(this.$store.getters['data/computedSettings'].newFileContent);
|
||||
const properties = ensureFinalNewLine(this.$store.getters['data/computedSettings'].newFileProperties);
|
||||
this.$store.commit('content/setItem', {
|
||||
id: `${id}/content`,
|
||||
text: defaultContent,
|
||||
text,
|
||||
properties,
|
||||
});
|
||||
this.$store.commit('file/setItem', {
|
||||
...newChildNode.item,
|
||||
|
|
|
@ -11,14 +11,19 @@
|
|||
<publish-management-modal v-else-if="config.type === 'publishManagement'"></publish-management-modal>
|
||||
<google-drive-sync-modal v-else-if="config.type === 'googleDriveSync'"></google-drive-sync-modal>
|
||||
<google-drive-publish-modal v-else-if="config.type === 'googleDrivePublish'"></google-drive-publish-modal>
|
||||
<dropbox-account-modal v-else-if="config.type === 'dropboxAccount'"></dropbox-account-modal>
|
||||
<dropbox-sync-modal v-else-if="config.type === 'dropboxSync'"></dropbox-sync-modal>
|
||||
<dropbox-publish-modal v-else-if="config.type === 'dropboxPublish'"></dropbox-publish-modal>
|
||||
<github-account-modal v-else-if="config.type === 'githubAccount'"></github-account-modal>
|
||||
<github-sync-modal v-else-if="config.type === 'githubSync'"></github-sync-modal>
|
||||
<github-publish-modal v-else-if="config.type === 'githubPublish'"></github-publish-modal>
|
||||
<gist-sync-modal v-else-if="config.type === 'gistSync'"></gist-sync-modal>
|
||||
<gist-publish-modal v-else-if="config.type === 'gistPublish'"></gist-publish-modal>
|
||||
<wordpress-publish-modal v-else-if="config.type === 'wordpressPublish'"></wordpress-publish-modal>
|
||||
<blogger-publish-modal v-else-if="config.type === 'bloggerPublish'"></blogger-publish-modal>
|
||||
<blogger-page-publish-modal v-else-if="config.type === 'bloggerPagePublish'"></blogger-page-publish-modal>
|
||||
<zendesk-account-modal v-else-if="config.type === 'zendeskAccount'"></zendesk-account-modal>
|
||||
<zendesk-publish-modal v-else-if="config.type === 'zendeskPublish'"></zendesk-publish-modal>
|
||||
<div v-else class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="modal__content" v-html="config.content"></div>
|
||||
|
@ -45,14 +50,19 @@ import SyncManagementModal from './modals/SyncManagementModal';
|
|||
import PublishManagementModal from './modals/PublishManagementModal';
|
||||
import GoogleDriveSyncModal from './modals/GoogleDriveSyncModal';
|
||||
import GoogleDrivePublishModal from './modals/GoogleDrivePublishModal';
|
||||
import DropboxAccountModal from './modals/DropboxAccountModal';
|
||||
import DropboxSyncModal from './modals/DropboxSyncModal';
|
||||
import DropboxPublishModal from './modals/DropboxPublishModal';
|
||||
import GithubAccountModal from './modals/GithubAccountModal';
|
||||
import GithubSyncModal from './modals/GithubSyncModal';
|
||||
import GithubPublishModal from './modals/GithubPublishModal';
|
||||
import GistSyncModal from './modals/GistSyncModal';
|
||||
import GistPublishModal from './modals/GistPublishModal';
|
||||
import WordpressPublishModal from './modals/WordpressPublishModal';
|
||||
import BloggerPublishModal from './modals/BloggerPublishModal';
|
||||
import BloggerPagePublishModal from './modals/BloggerPagePublishModal';
|
||||
import ZendeskAccountModal from './modals/ZendeskAccountModal';
|
||||
import ZendeskPublishModal from './modals/ZendeskPublishModal';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -67,14 +77,19 @@ export default {
|
|||
PublishManagementModal,
|
||||
GoogleDriveSyncModal,
|
||||
GoogleDrivePublishModal,
|
||||
DropboxAccountModal,
|
||||
DropboxSyncModal,
|
||||
DropboxPublishModal,
|
||||
GithubAccountModal,
|
||||
GithubSyncModal,
|
||||
GithubPublishModal,
|
||||
GistSyncModal,
|
||||
GistPublishModal,
|
||||
WordpressPublishModal,
|
||||
BloggerPublishModal,
|
||||
BloggerPagePublishModal,
|
||||
ZendeskAccountModal,
|
||||
ZendeskPublishModal,
|
||||
},
|
||||
computed: mapGetters('modal', [
|
||||
'config',
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
</div>
|
||||
<div class="navigation-bar__inner navigation-bar__inner--right navigation-bar__inner--title flex flex--row">
|
||||
<div class="navigation-bar__spinner">
|
||||
<div v-show="showSpinner" class="spinner"></div>
|
||||
<div v-if="!offline && showSpinner" class="spinner"></div>
|
||||
<icon-sync-off v-if="offline"></icon-sync-off>
|
||||
</div>
|
||||
<div class="navigation-bar__title navigation-bar__title--fake text-input"></div>
|
||||
<div class="navigation-bar__title navigation-bar__title--text text-input" :style="{width: titleWidth + 'px'}">{{title}}</div>
|
||||
|
@ -21,12 +22,9 @@
|
|||
<a class="navigation-bar__button navigation-bar__button--location button" :class="{'navigation-bar__button--blink': location.id === currentLocation.id}" v-for="location in syncLocations" :key="location.id" :href="location.url" target="_blank">
|
||||
<icon-provider :provider-id="location.providerId"></icon-provider>
|
||||
</a>
|
||||
<button class="navigation-bar__button navigation-bar__button--sync button" v-if="!offline && isSyncPossible" :disabled="isSyncRequested" @click="requestSync">
|
||||
<button class="navigation-bar__button navigation-bar__button--sync button" v-if="isSyncPossible" :disabled="isSyncRequested || offline" @click="requestSync">
|
||||
<icon-sync></icon-sync>
|
||||
</button>
|
||||
<button class="navigation-bar__button navigation-bar__button--sync-off button" v-if="offline && isSyncPossible" disabled="disabled">
|
||||
<icon-sync-off></icon-sync-off>
|
||||
</button>
|
||||
<a class="navigation-bar__button navigation-bar__button--location button" :class="{'navigation-bar__button--blink': location.id === currentLocation.id}" v-for="location in publishLocations" :key="location.id" :href="location.url" target="_blank">
|
||||
<icon-provider :provider-id="location.providerId"></icon-provider>
|
||||
</a>
|
||||
|
@ -253,7 +251,7 @@ export default {
|
|||
width: 38px;
|
||||
|
||||
&.navigation-bar__button--stackedit {
|
||||
opacity: 0.8;
|
||||
opacity: 0.85;
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
|
@ -277,7 +275,6 @@ export default {
|
|||
}
|
||||
|
||||
.navigation-bar__button--sync,
|
||||
.navigation-bar__button--sync-off,
|
||||
.navigation-bar__button--publish {
|
||||
padding: 0 6px;
|
||||
margin: 0 5px;
|
||||
|
@ -292,15 +289,6 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.navigation-bar__button--sync-off[disabled] {
|
||||
&,
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
color: $error-color;
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-bar__title--input,
|
||||
.navigation-bar__button {
|
||||
&:active,
|
||||
|
@ -313,14 +301,18 @@ export default {
|
|||
|
||||
.navigation-bar__button--location {
|
||||
width: 20px;
|
||||
padding: 0 2px 12px;
|
||||
height: 20px;
|
||||
border-radius: 10px;
|
||||
padding: 2px;
|
||||
margin-top: 8px;
|
||||
opacity: 0.5;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
opacity: 1;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -328,13 +320,6 @@ export default {
|
|||
animation: blink 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% {
|
||||
opacity: 1;
|
||||
filter: contrast(0.8) brightness(1.25);
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-bar__title--fake {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
|
@ -382,12 +367,18 @@ export default {
|
|||
$r: 9px;
|
||||
$d: $r * 2;
|
||||
$b: $d/10;
|
||||
$t: 1500ms;
|
||||
$t: 3000ms;
|
||||
|
||||
.navigation-bar__spinner {
|
||||
width: $d;
|
||||
margin: 10px 5px 0 10px;
|
||||
width: 22px;
|
||||
margin: 8px 0 0 8px;
|
||||
color: rgba(255, 255, 255, 0.67);
|
||||
|
||||
.icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
color: transparentize($error-color, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.spinner {
|
||||
|
@ -397,6 +388,7 @@ $t: 1500ms;
|
|||
position: relative;
|
||||
border: $b solid currentColor;
|
||||
border-radius: 50%;
|
||||
margin: 2px;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
|
@ -410,11 +402,18 @@ $t: 1500ms;
|
|||
}
|
||||
|
||||
&::before {
|
||||
height: $r * 0.5;
|
||||
height: $r * 0.35;
|
||||
left: $r - $b * 1.5;
|
||||
top: 50%;
|
||||
animation: spin $t linear infinite;
|
||||
}
|
||||
|
||||
&::after {
|
||||
height: $r * 0.5;
|
||||
left: $r - $b * 1.5;
|
||||
top: 50%;
|
||||
animation: spin $t/4 linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
|
@ -422,4 +421,10 @@ $t: 1500ms;
|
|||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -85,7 +85,7 @@ export default {
|
|||
computeHtml() {
|
||||
let text;
|
||||
if (editorSvc.previewSelectionRange) {
|
||||
text = editorSvc.previewSelectionRange.toString();
|
||||
text = `${editorSvc.previewSelectionRange}`;
|
||||
}
|
||||
this.htmlSelection = true;
|
||||
if (!text) {
|
||||
|
|
|
@ -12,7 +12,7 @@ import editorSvc from '../services/editorSvc';
|
|||
|
||||
export default {
|
||||
data: () => ({
|
||||
maskY: -999,
|
||||
maskY: 0,
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('layout', [
|
||||
|
@ -64,9 +64,11 @@ export default {
|
|||
// Change mask postion on scroll
|
||||
const updateMaskY = () => {
|
||||
const scrollPosition = editorSvc.getScrollPosition();
|
||||
const sectionDesc = editorSvc.sectionDescList[scrollPosition.sectionIdx];
|
||||
this.maskY = sectionDesc.tocDimension.startOffset +
|
||||
(scrollPosition.posInSection * sectionDesc.tocDimension.height);
|
||||
if (scrollPosition) {
|
||||
const sectionDesc = editorSvc.sectionDescList[scrollPosition.sectionIdx];
|
||||
this.maskY = sectionDesc.tocDimension.startOffset +
|
||||
(scrollPosition.posInSection * sectionDesc.tocDimension.height);
|
||||
}
|
||||
};
|
||||
|
||||
Vue.nextTick(() => {
|
||||
|
|
|
@ -2,15 +2,18 @@
|
|||
<div class="side-bar__panel side-bar__panel--menu">
|
||||
<menu-entry @click.native="exportMarkdown">
|
||||
<icon-download slot="icon"></icon-download>
|
||||
Export as Markdown
|
||||
<div>Export as Markdown</div>
|
||||
<span>Save file as plain text.</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="exportHtml">
|
||||
<icon-download slot="icon"></icon-download>
|
||||
Export as HTML
|
||||
<div>Export as HTML</div>
|
||||
<span>Generate an HTML page from a template.</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="exportPdf">
|
||||
<icon-download slot="icon"></icon-download>
|
||||
Export as PDF
|
||||
<div>Export as PDF</div>
|
||||
<span>Produce a PDF from an HTML template.</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -73,8 +73,7 @@ export default {
|
|||
return this.$store.dispatch('modal/notImplemented');
|
||||
},
|
||||
fileProperties() {
|
||||
return this.$store.dispatch('modal/open', 'fileProperties')
|
||||
.then(properties => this.$store.dispatch('content/patchCurrent', { properties }));
|
||||
return this.$store.dispatch('modal/open', 'fileProperties');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -40,6 +40,13 @@
|
|||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in wordpressTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishWordpress(token)">
|
||||
<icon-provider slot="icon" provider-id="wordpress"></icon-provider>
|
||||
<div>Publish to WordPress</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in bloggerTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishBlogger(token)">
|
||||
<icon-provider slot="icon" provider-id="blogger"></icon-provider>
|
||||
|
@ -52,6 +59,13 @@
|
|||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in zendeskTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishZendesk(token)">
|
||||
<icon-provider slot="icon" provider-id="zendesk"></icon-provider>
|
||||
<div>Publish to Zendesk Help Center</div>
|
||||
<span>{{token.name}} — {{token.subdomain}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<hr>
|
||||
<menu-entry @click.native="addGoogleDriveAccount">
|
||||
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
|
||||
|
@ -65,10 +79,18 @@
|
|||
<icon-provider slot="icon" provider-id="github"></icon-provider>
|
||||
<span>Add GitHub account</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addWordpressAccount">
|
||||
<icon-provider slot="icon" provider-id="wordpress"></icon-provider>
|
||||
<span>Add WordPress account</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addBloggerAccount">
|
||||
<icon-provider slot="icon" provider-id="blogger"></icon-provider>
|
||||
<span>Add Blogger account</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addZendeskAccount">
|
||||
<icon-provider slot="icon" provider-id="zendesk"></icon-provider>
|
||||
<span>Add Zendesk account</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -78,6 +100,8 @@ import MenuEntry from './MenuEntry';
|
|||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
import dropboxHelper from '../../services/providers/helpers/dropboxHelper';
|
||||
import githubHelper from '../../services/providers/helpers/githubHelper';
|
||||
import wordpressHelper from '../../services/providers/helpers/wordpressHelper';
|
||||
import zendeskHelper from '../../services/providers/helpers/zendeskHelper';
|
||||
import publishSvc from '../../services/publishSvc';
|
||||
import store from '../../store';
|
||||
|
||||
|
@ -117,9 +141,15 @@ export default {
|
|||
githubTokens() {
|
||||
return tokensToArray(this.$store.getters['data/githubTokens']);
|
||||
},
|
||||
wordpressTokens() {
|
||||
return tokensToArray(this.$store.getters['data/wordpressTokens']);
|
||||
},
|
||||
bloggerTokens() {
|
||||
return tokensToArray(this.$store.getters['data/googleTokens'], token => token.isBlogger);
|
||||
},
|
||||
zendeskTokens() {
|
||||
return tokensToArray(this.$store.getters['data/zendeskTokens']);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
requestPublish() {
|
||||
|
@ -134,14 +164,29 @@ export default {
|
|||
return googleHelper.addDriveAccount();
|
||||
},
|
||||
addDropboxAccount() {
|
||||
return dropboxHelper.addAccount();
|
||||
return this.$store.dispatch('modal/open', {
|
||||
type: 'dropboxAccount',
|
||||
onResolve: () => dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess),
|
||||
});
|
||||
},
|
||||
addGithubAccount() {
|
||||
return githubHelper.addAccount();
|
||||
return this.$store.dispatch('modal/open', {
|
||||
type: 'githubAccount',
|
||||
onResolve: () => githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess),
|
||||
});
|
||||
},
|
||||
addWordpressAccount() {
|
||||
return wordpressHelper.addAccount();
|
||||
},
|
||||
addBloggerAccount() {
|
||||
return googleHelper.addBloggerAccount();
|
||||
},
|
||||
addZendeskAccount() {
|
||||
return this.$store.dispatch('modal/open', {
|
||||
type: 'zendeskAccount',
|
||||
onResolve: ({ subdomain, clientId }) => zendeskHelper.addAccount(subdomain, clientId),
|
||||
});
|
||||
},
|
||||
publishGoogleDrive(token) {
|
||||
return openPublishModal(token, 'googleDrivePublish');
|
||||
},
|
||||
|
@ -154,12 +199,18 @@ export default {
|
|||
publishGist(token) {
|
||||
return openPublishModal(token, 'gistPublish');
|
||||
},
|
||||
publishWordpress(token) {
|
||||
return openPublishModal(token, 'wordpressPublish');
|
||||
},
|
||||
publishBlogger(token) {
|
||||
return openPublishModal(token, 'bloggerPublish');
|
||||
},
|
||||
publishBloggerPage(token) {
|
||||
return openPublishModal(token, 'bloggerPagePublish');
|
||||
},
|
||||
publishZendesk(token) {
|
||||
return openPublishModal(token, 'zendeskPublish');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -81,7 +81,6 @@ import dropboxHelper from '../../services/providers/helpers/dropboxHelper';
|
|||
import githubHelper from '../../services/providers/helpers/githubHelper';
|
||||
import googleDriveProvider from '../../services/providers/googleDriveProvider';
|
||||
import dropboxProvider from '../../services/providers/dropboxProvider';
|
||||
import dropboxRestrictedProvider from '../../services/providers/dropboxRestrictedProvider';
|
||||
import syncSvc from '../../services/syncSvc';
|
||||
import store from '../../store';
|
||||
|
||||
|
@ -142,10 +141,16 @@ export default {
|
|||
return googleHelper.addDriveAccount();
|
||||
},
|
||||
addDropboxAccount() {
|
||||
return dropboxHelper.addAccount();
|
||||
return this.$store.dispatch('modal/open', {
|
||||
type: 'dropboxAccount',
|
||||
onResolve: () => dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess),
|
||||
});
|
||||
},
|
||||
addGithubAccount() {
|
||||
return githubHelper.addAccount();
|
||||
return this.$store.dispatch('modal/open', {
|
||||
type: 'githubAccount',
|
||||
onResolve: () => githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess),
|
||||
});
|
||||
},
|
||||
openGoogleDrive(token) {
|
||||
return googleHelper.openPicker(token, 'doc')
|
||||
|
@ -155,12 +160,7 @@ export default {
|
|||
openDropbox(token) {
|
||||
return dropboxHelper.openChooser(token)
|
||||
.then(paths => this.$store.dispatch('queue/enqueue',
|
||||
() => {
|
||||
if (token.fullAccess) {
|
||||
return dropboxProvider.openFiles(token, paths);
|
||||
}
|
||||
return dropboxRestrictedProvider.openFiles(token, paths);
|
||||
}));
|
||||
() => dropboxProvider.openFiles(token, paths)));
|
||||
},
|
||||
saveGoogleDrive(token) {
|
||||
return openSyncModal(token, 'googleDriveSync');
|
||||
|
|
|
@ -1,40 +1,31 @@
|
|||
<template>
|
||||
<div class="modal__inner-1 modal__inner-1--google-drive-sync">
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="bloggerPage"></icon-provider>
|
||||
</div>
|
||||
<p>This will publish <b>{{currentFileName}}</b> to your <b>Blogger Page</b>.</p>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="blogUrl">Blog URL</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="blogUrl" type="text" class="textfield" v-model="blogUrl" @keyup.enter="resolve()">
|
||||
</div>
|
||||
<form-entry label="Blog URL">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="blogUrl" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> http://example.blogger.com/
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="fileId">Existing page ID (optional)</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="fileId" type="text" class="textfield" v-model="pageId" @keyup.enter="resolve()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="template">Template</label>
|
||||
<div class="form-entry__field">
|
||||
<select class="textfield" id="template" v-model="selectedTemplate" @keyup.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplates" :key="id" v-bind:value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Existing page ID (optional)">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="pageId" @keyup.enter="resolve()">
|
||||
</form-entry>
|
||||
<form-entry label="Template">
|
||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keyup.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="form-entry__actions">
|
||||
<a href="javascript:void(0)" @click="configureTemplates">Configure templates</a>
|
||||
</div>
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="modal__tip">
|
||||
<b>Tip:</b> You can provide a value for <code>title</code> in the <b>file properties</b>.
|
||||
<b>ProTip:</b> You can provide a value for <code>title</code> in the <a href="javascript:void(0)" @click="openFileProperties">file properties</a>.
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
|
@ -45,51 +36,18 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import bloggerPageProvider from '../../services/providers/bloggerPageProvider';
|
||||
import store from '../../store';
|
||||
import modalTemplate from './modalTemplate';
|
||||
|
||||
const computedLocalSetting = id => ({
|
||||
get() {
|
||||
return store.getters['data/localSettings'][id];
|
||||
},
|
||||
set(value) {
|
||||
store.dispatch('data/patchLocalSettings', {
|
||||
[id]: value,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
pageId: '',
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'config',
|
||||
]),
|
||||
currentFileName() {
|
||||
return this.$store.getters['file/current'].name;
|
||||
},
|
||||
...mapGetters('data', [
|
||||
'allTemplates',
|
||||
]),
|
||||
blogUrl: computedLocalSetting('bloggerBlogUrl'),
|
||||
selectedTemplate: computedLocalSetting('bloggerPublishTemplate'),
|
||||
computedLocalSettings: {
|
||||
blogUrl: 'bloggerBlogUrl',
|
||||
selectedTemplate: 'bloggerPublishTemplate',
|
||||
},
|
||||
methods: {
|
||||
configureTemplates() {
|
||||
this.$store.dispatch('modal/open', {
|
||||
type: 'templates',
|
||||
selectedId: this.selectedTemplate,
|
||||
})
|
||||
.then(({ templates, selectedId }) => {
|
||||
this.$store.dispatch('data/setTemplates', templates);
|
||||
this.$store.dispatch('data/patchLocalSettings', {
|
||||
bloggerPublishTemplate: selectedId,
|
||||
});
|
||||
});
|
||||
},
|
||||
resolve() {
|
||||
if (this.blogUrl) {
|
||||
// Return new location
|
||||
|
@ -100,5 +58,5 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,41 +1,32 @@
|
|||
<template>
|
||||
<div class="modal__inner-1 modal__inner-1--google-drive-sync">
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="blogger"></icon-provider>
|
||||
</div>
|
||||
<p>This will publish <b>{{currentFileName}}</b> to your <b>Blogger</b> site.</p>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="blogUrl">Blog URL</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="blogUrl" type="text" class="textfield" v-model="blogUrl" @keyup.enter="resolve()">
|
||||
</div>
|
||||
<form-entry label="Blog URL">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="blogUrl" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> http://example.blogger.com/
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="fileId">Existing post ID (optional)</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="fileId" type="text" class="textfield" v-model="postId" @keyup.enter="resolve()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="template">Template</label>
|
||||
<div class="form-entry__field">
|
||||
<select class="textfield" id="template" v-model="selectedTemplate" @keyup.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplates" :key="id" v-bind:value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Existing post ID (optional)">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="postId" @keyup.enter="resolve()">
|
||||
</form-entry>
|
||||
<form-entry label="Template">
|
||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keyup.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="form-entry__actions">
|
||||
<a href="javascript:void(0)" @click="configureTemplates">Configure templates</a>
|
||||
</div>
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="modal__tip">
|
||||
<b>Tip:</b> You can provide values for <code>title</code>, <code>tags</code>,
|
||||
<code>status</code> and <code>date</code> in the <b>file properties</b>.
|
||||
<b>ProTip:</b> You can provide values for <code>title</code>, <code>tags</code>,
|
||||
<code>status</code> and <code>date</code> in the <a href="javascript:void(0)" @click="openFileProperties">file properties</a>.
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
|
@ -46,51 +37,18 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import bloggerProvider from '../../services/providers/bloggerProvider';
|
||||
import store from '../../store';
|
||||
import modalTemplate from './modalTemplate';
|
||||
|
||||
const computedLocalSetting = id => ({
|
||||
get() {
|
||||
return store.getters['data/localSettings'][id];
|
||||
},
|
||||
set(value) {
|
||||
store.dispatch('data/patchLocalSettings', {
|
||||
[id]: value,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
postId: '',
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'config',
|
||||
]),
|
||||
currentFileName() {
|
||||
return this.$store.getters['file/current'].name;
|
||||
},
|
||||
...mapGetters('data', [
|
||||
'allTemplates',
|
||||
]),
|
||||
blogUrl: computedLocalSetting('bloggerBlogUrl'),
|
||||
selectedTemplate: computedLocalSetting('bloggerPublishTemplate'),
|
||||
computedLocalSettings: {
|
||||
blogUrl: 'bloggerBlogUrl',
|
||||
selectedTemplate: 'bloggerPublishTemplate',
|
||||
},
|
||||
methods: {
|
||||
configureTemplates() {
|
||||
this.$store.dispatch('modal/open', {
|
||||
type: 'templates',
|
||||
selectedId: this.selectedTemplate,
|
||||
})
|
||||
.then(({ templates, selectedId }) => {
|
||||
this.$store.dispatch('data/setTemplates', templates);
|
||||
this.$store.dispatch('data/patchLocalSettings', {
|
||||
bloggerPublishTemplate: selectedId,
|
||||
});
|
||||
});
|
||||
},
|
||||
resolve() {
|
||||
if (this.blogUrl) {
|
||||
// Return new location
|
||||
|
@ -101,5 +59,5 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
34
src/components/modals/DropboxAccountModal.vue
Normal file
34
src/components/modals/DropboxAccountModal.vue
Normal file
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="dropbox"></icon-provider>
|
||||
</div>
|
||||
<p>This will link your <b>Dropbox</b> account to your <b>StackEdit</b> workspace.</p>
|
||||
<div class="form-entry">
|
||||
<div class="form-entry__checkbox">
|
||||
<label>
|
||||
<input type="checkbox" v-model="restrictedAccess"> Restrict access
|
||||
</label>
|
||||
<div class="form-entry__info">
|
||||
If checked, access will be restricted to the <b>/Applications/StackEdit (restricted)</b> folder.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button" @click="config.resolve()">Ok</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modalTemplate from './modalTemplate';
|
||||
|
||||
export default modalTemplate({
|
||||
computedLocalSettings: {
|
||||
restrictedAccess: 'dropboxRestrictedAccess',
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,33 +1,27 @@
|
|||
<template>
|
||||
<div class="modal__inner-1 modal__inner-1--google-drive-sync">
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="dropbox"></icon-provider>
|
||||
</div>
|
||||
<p>This will publish <b>{{currentFileName}}</b> to your <b>Dropbox</b>.</p>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="path">File path</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="path" type="text" class="textfield" v-model.trim="path" @keyup.enter="resolve()">
|
||||
</div>
|
||||
<form-entry label="File path">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> /path/to/My Document.html<br>
|
||||
<b>Example:</b> {{config.token.fullAccess ? '' : '/Applications/StackEdit (restricted)'}}/path/to/My Document.html<br>
|
||||
If the file exists, it will be replaced.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="template">Template</label>
|
||||
<div class="form-entry__field">
|
||||
<select class="textfield" id="template" v-model="selectedTemplate" @keyup.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplates" :key="id" v-bind:value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Template">
|
||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keyup.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="form-entry__actions">
|
||||
<a href="javascript:void(0)" @click="configureTemplates">Configure templates</a>
|
||||
</div>
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button" @click="resolve()">Ok</button>
|
||||
|
@ -37,64 +31,28 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import dropboxProvider from '../../services/providers/dropboxProvider';
|
||||
import dropboxRestrictedProvider from '../../services/providers/dropboxRestrictedProvider';
|
||||
import store from '../../store';
|
||||
import modalTemplate from './modalTemplate';
|
||||
|
||||
const computedLocalSetting = id => ({
|
||||
get() {
|
||||
return store.getters['data/localSettings'][id];
|
||||
},
|
||||
set(value) {
|
||||
store.dispatch('data/patchLocalSettings', {
|
||||
[id]: value,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
path: '',
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'config',
|
||||
]),
|
||||
currentFileName() {
|
||||
return this.$store.getters['file/current'].name;
|
||||
},
|
||||
...mapGetters('data', [
|
||||
'allTemplates',
|
||||
]),
|
||||
selectedTemplate: computedLocalSetting('dropboxPublishTemplate'),
|
||||
computedLocalSettings: {
|
||||
selectedTemplate: 'dropboxPublishTemplate',
|
||||
},
|
||||
created() {
|
||||
this.path = `/${this.currentFileName}.html`;
|
||||
},
|
||||
methods: {
|
||||
configureTemplates() {
|
||||
this.$store.dispatch('modal/open', {
|
||||
type: 'templates',
|
||||
selectedId: this.selectedTemplate,
|
||||
})
|
||||
.then(({ templates, selectedId }) => {
|
||||
this.$store.dispatch('data/setTemplates', templates);
|
||||
this.$store.dispatch('data/patchLocalSettings', {
|
||||
dropboxPublishTemplate: selectedId,
|
||||
});
|
||||
});
|
||||
},
|
||||
resolve() {
|
||||
if (dropboxProvider.checkPath(this.path)) {
|
||||
// Return new location
|
||||
const location = this.config.token.fullAccess
|
||||
? dropboxProvider.makeLocation(this.config.token, this.path)
|
||||
: dropboxRestrictedProvider.makeLocation(this.config.token, this.path);
|
||||
const location = dropboxProvider.makeLocation(this.config.token, this.path);
|
||||
location.templateId = this.selectedTemplate;
|
||||
this.config.resolve(location);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
<template>
|
||||
<div class="modal__inner-1 modal__inner-1--google-drive-sync">
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="dropbox"></icon-provider>
|
||||
</div>
|
||||
<p>This will save <b>{{currentFileName}}</b> to your <b>Dropbox</b> and keep it synchronized.</p>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="path">File path</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="path" type="text" class="textfield" v-model.trim="path" @keyup.enter="resolve()">
|
||||
</div>
|
||||
<form-entry label="File path">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> /path/to/My Document.md<br>
|
||||
<b>Example:</b> {{config.token.fullAccess ? '' : '/Applications/StackEdit (restricted)'}}/path/to/My Document.md<br>
|
||||
If the file exists, it will be replaced.
|
||||
</div>
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button" @click="resolve()">Ok</button>
|
||||
|
@ -24,22 +21,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import dropboxProvider from '../../services/providers/dropboxProvider';
|
||||
import dropboxRestrictedProvider from '../../services/providers/dropboxRestrictedProvider';
|
||||
import modalTemplate from './modalTemplate';
|
||||
|
||||
export default {
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
path: '',
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'config',
|
||||
]),
|
||||
currentFileName() {
|
||||
return this.$store.getters['file/current'].name;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.path = `/${this.currentFileName}.md`;
|
||||
},
|
||||
|
@ -47,12 +35,10 @@ export default {
|
|||
resolve() {
|
||||
if (dropboxProvider.checkPath(this.path)) {
|
||||
// Return new location
|
||||
const location = this.config.token.fullAccess
|
||||
? dropboxProvider.makeLocation(this.config.token, this.path)
|
||||
: dropboxRestrictedProvider.makeLocation(this.config.token, this.path);
|
||||
const location = dropboxProvider.makeLocation(this.config.token, this.path);
|
||||
this.config.resolve(location);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<div class="modal__error modal__error--file-properties">{{error}}</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button" @click="!error && config.resolve(strippedCustomProperties)">Ok</button>
|
||||
<button class="button" @click="resolve()">Ok</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -38,6 +38,7 @@ export default {
|
|||
CodeEditor,
|
||||
},
|
||||
data: () => ({
|
||||
contentId: null,
|
||||
tab: 'custom',
|
||||
defaultProperties,
|
||||
customProperties: null,
|
||||
|
@ -52,7 +53,9 @@ export default {
|
|||
},
|
||||
},
|
||||
created() {
|
||||
const properties = this.$store.getters['content/current'].properties;
|
||||
const content = this.$store.getters['content/current'];
|
||||
this.contentId = content.id;
|
||||
const properties = content.properties;
|
||||
this.setCustomProperties(properties === '\n' ? emptyProperties : properties);
|
||||
},
|
||||
methods: {
|
||||
|
@ -65,6 +68,15 @@ export default {
|
|||
this.error = e.message;
|
||||
}
|
||||
},
|
||||
resolve() {
|
||||
if (!this.error) {
|
||||
this.$store.commit('content/patchItem', {
|
||||
id: this.contentId,
|
||||
properties: this.strippedCustomProperties,
|
||||
});
|
||||
this.config.resolve();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
26
src/components/modals/FormEntry.vue
Normal file
26
src/components/modals/FormEntry.vue
Normal file
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" :for="uid">{{label}}</label>
|
||||
<div class="form-entry__field">
|
||||
<slot name="field"></slot>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import utils from '../../services/utils';
|
||||
|
||||
export default {
|
||||
props: ['label'],
|
||||
data: () => ({
|
||||
uid: utils.uid(),
|
||||
}),
|
||||
mounted() {
|
||||
this.$el.querySelector('input,select').id = this.uid;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
|
@ -1,16 +1,13 @@
|
|||
<template>
|
||||
<div class="modal__inner-1 modal__inner-1--google-drive-sync">
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="gist"></icon-provider>
|
||||
</div>
|
||||
<p>This will publish <b>{{currentFileName}}</b> to a <b>Gist</b>.</p>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="filename">Filename</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="filename" type="text" class="textfield" v-model.trim="filename" @keyup.enter="resolve()">
|
||||
</div>
|
||||
</div>
|
||||
<form-entry label="Filename">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="filename" @keyup.enter="resolve()">
|
||||
</form-entry>
|
||||
<div class="form-entry">
|
||||
<div class="form-entry__checkbox">
|
||||
<label>
|
||||
|
@ -18,30 +15,24 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="gistId">Gist ID (optional)</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="gistId" type="text" class="textfield" v-model.trim="gistId" @keyup.enter="resolve()">
|
||||
</div>
|
||||
<form-entry label="Existing Gist ID (optional)">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="gistId" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
If the file exists in the provided Gist, it will be replaced.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="template">Template</label>
|
||||
<div class="form-entry__field">
|
||||
<select class="textfield" id="template" v-model="selectedTemplate" @keyup.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplates" :key="id" v-bind:value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
If the file exists in the Gist, it will be replaced.
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Template">
|
||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keyup.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="form-entry__actions">
|
||||
<a href="javascript:void(0)" @click="configureTemplates">Configure templates</a>
|
||||
</div>
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="modal__tip">
|
||||
<b>Tip:</b> You can provide a value for <code>title</code> in the <b>file properties</b>.
|
||||
<b>ProTip:</b> You can provide a value for <code>title</code> in the <a href="javascript:void(0)" @click="openFileProperties">file properties</a>.
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
|
@ -52,55 +43,22 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import gistProvider from '../../services/providers/gistProvider';
|
||||
import store from '../../store';
|
||||
import modalTemplate from './modalTemplate';
|
||||
|
||||
const computedLocalSetting = id => ({
|
||||
get() {
|
||||
return store.getters['data/localSettings'][id];
|
||||
},
|
||||
set(value) {
|
||||
store.dispatch('data/patchLocalSettings', {
|
||||
[id]: value,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
filename: '',
|
||||
gistId: '',
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'config',
|
||||
]),
|
||||
currentFileName() {
|
||||
return this.$store.getters['file/current'].name;
|
||||
},
|
||||
...mapGetters('data', [
|
||||
'allTemplates',
|
||||
]),
|
||||
isPublic: computedLocalSetting('gistIsPublic'),
|
||||
selectedTemplate: computedLocalSetting('gistPublishTemplate'),
|
||||
computedLocalSettings: {
|
||||
isPublic: 'gistIsPublic',
|
||||
selectedTemplate: 'gistPublishTemplate',
|
||||
},
|
||||
created() {
|
||||
this.filename = `${this.currentFileName}.md`;
|
||||
},
|
||||
methods: {
|
||||
configureTemplates() {
|
||||
this.$store.dispatch('modal/open', {
|
||||
type: 'templates',
|
||||
selectedId: this.selectedTemplate,
|
||||
})
|
||||
.then(({ templates, selectedId }) => {
|
||||
this.$store.dispatch('data/setTemplates', templates);
|
||||
this.$store.dispatch('data/patchLocalSettings', {
|
||||
gistPublishTemplate: selectedId,
|
||||
});
|
||||
});
|
||||
},
|
||||
resolve() {
|
||||
if (this.filename) {
|
||||
// Return new location
|
||||
|
@ -111,5 +69,5 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
<template>
|
||||
<div class="modal__inner-1 modal__inner-1--google-drive-sync">
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="gist"></icon-provider>
|
||||
</div>
|
||||
<p>This will save <b>{{currentFileName}}</b> to a <b>Gist</b> repository and keep it synchronized.</p>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="filename">Filename</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="filename" type="text" class="textfield" v-model.trim="filename" @keyup.enter="resolve()">
|
||||
</div>
|
||||
</div>
|
||||
<p>This will save <b>{{currentFileName}}</b> to a <b>Gist</b> and keep it synchronized.</p>
|
||||
<form-entry label="Filename">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="filename" @keyup.enter="resolve()">
|
||||
</form-entry>
|
||||
<div class="form-entry">
|
||||
<div class="form-entry__checkbox">
|
||||
<label>
|
||||
|
@ -18,15 +15,12 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="gistId">Gist ID (optional)</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="gistId" type="text" class="textfield" v-model.trim="gistId" @keyup.enter="resolve()">
|
||||
</div>
|
||||
<form-entry label="Existing Gist ID (optional)">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="gistId" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
If the file exists in the provided Gist, it will be replaced.
|
||||
If the file exists in the Gist, it will be replaced.
|
||||
</div>
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button" @click="resolve()">Ok</button>
|
||||
|
@ -36,34 +30,16 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import gistProvider from '../../services/providers/gistProvider';
|
||||
import store from '../../store';
|
||||
import modalTemplate from './modalTemplate';
|
||||
|
||||
const computedLocalSetting = id => ({
|
||||
get() {
|
||||
return store.getters['data/localSettings'][id];
|
||||
},
|
||||
set(value) {
|
||||
store.dispatch('data/patchLocalSettings', {
|
||||
[id]: value,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
filename: '',
|
||||
gistId: '',
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'config',
|
||||
]),
|
||||
currentFileName() {
|
||||
return this.$store.getters['file/current'].name;
|
||||
},
|
||||
isPublic: computedLocalSetting('gistIsPublic'),
|
||||
computedLocalSettings: {
|
||||
isPublic: 'gistIsPublic',
|
||||
},
|
||||
created() {
|
||||
this.filename = `${this.currentFileName}.md`;
|
||||
|
@ -78,5 +54,5 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
31
src/components/modals/GithubAccountModal.vue
Normal file
31
src/components/modals/GithubAccountModal.vue
Normal file
|
@ -0,0 +1,31 @@
|
|||
<template>
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="github"></icon-provider>
|
||||
</div>
|
||||
<p>This will link your <b>Github</b> account to your <b>StackEdit</b> workspace.</p>
|
||||
<div class="form-entry">
|
||||
<div class="form-entry__checkbox">
|
||||
<label>
|
||||
<input type="checkbox" v-model="repoFullAccess"> Request access to private repositories
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button" @click="config.resolve()">Ok</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modalTemplate from './modalTemplate';
|
||||
|
||||
export default modalTemplate({
|
||||
computedLocalSettings: {
|
||||
repoFullAccess: 'githubRepoFullAccess',
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,51 +1,39 @@
|
|||
<template>
|
||||
<div class="modal__inner-1 modal__inner-1--google-drive-sync">
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="github"></icon-provider>
|
||||
</div>
|
||||
<p>This will publish <b>{{currentFileName}}</b> to your <b>GitHub</b> repository.</p>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="repo">Repository URL</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="repo" type="text" class="textfield" v-model.trim="repoUrl" @keyup.enter="resolve()">
|
||||
</div>
|
||||
<form-entry label="Repository URL">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> https://github.com/benweet/stackedit
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="branch">Branch (optional)</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="branch" type="text" class="textfield" v-model.trim="branch" @keyup.enter="resolve()">
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Branch (optional)">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
If not provided, the master branch will be used.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="path">File path</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="path" type="text" class="textfield" v-model.trim="path" @keyup.enter="resolve()">
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="File path">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> docs/README.md<br>
|
||||
If the file exists, it will be replaced.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="template">Template</label>
|
||||
<div class="form-entry__field">
|
||||
<select class="textfield" id="template" v-model="selectedTemplate" @keyup.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplates" :key="id" v-bind:value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Template">
|
||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keyup.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="form-entry__actions">
|
||||
<a href="javascript:void(0)" @click="configureTemplates">Configure templates</a>
|
||||
</div>
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button" @click="resolve()">Ok</button>
|
||||
|
@ -55,55 +43,22 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import githubProvider from '../../services/providers/githubProvider';
|
||||
import store from '../../store';
|
||||
import modalTemplate from './modalTemplate';
|
||||
|
||||
const computedLocalSetting = id => ({
|
||||
get() {
|
||||
return store.getters['data/localSettings'][id];
|
||||
},
|
||||
set(value) {
|
||||
store.dispatch('data/patchLocalSettings', {
|
||||
[id]: value,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
branch: '',
|
||||
path: '',
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'config',
|
||||
]),
|
||||
currentFileName() {
|
||||
return this.$store.getters['file/current'].name;
|
||||
},
|
||||
...mapGetters('data', [
|
||||
'allTemplates',
|
||||
]),
|
||||
repoUrl: computedLocalSetting('githubRepoUrl'),
|
||||
selectedTemplate: computedLocalSetting('githubPublishTemplate'),
|
||||
computedLocalSettings: {
|
||||
repoUrl: 'githubRepoUrl',
|
||||
selectedTemplate: 'githubPublishTemplate',
|
||||
},
|
||||
created() {
|
||||
this.path = `${this.currentFileName}.md`;
|
||||
},
|
||||
methods: {
|
||||
configureTemplates() {
|
||||
this.$store.dispatch('modal/open', {
|
||||
type: 'templates',
|
||||
selectedId: this.selectedTemplate,
|
||||
})
|
||||
.then(({ templates, selectedId }) => {
|
||||
this.$store.dispatch('data/setTemplates', templates);
|
||||
this.$store.dispatch('data/patchLocalSettings', {
|
||||
githubPublishTemplate: selectedId,
|
||||
});
|
||||
});
|
||||
},
|
||||
resolve() {
|
||||
if (this.repoUrl && this.path) {
|
||||
const parsedRepo = this.repoUrl.match(/[/:]?([^/:]+)\/([^/]+?)(?:\.git)?$/);
|
||||
|
@ -117,5 +72,5 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,38 +1,29 @@
|
|||
<template>
|
||||
<div class="modal__inner-1 modal__inner-1--google-drive-sync">
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="github"></icon-provider>
|
||||
</div>
|
||||
<p>This will save <b>{{currentFileName}}</b> to your <b>GitHub</b> repository and keep it synchronized.</p>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="repo">Repository URL</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="repo" type="text" class="textfield" v-model.trim="repoUrl" @keyup.enter="resolve()">
|
||||
</div>
|
||||
<form-entry label="Repository URL">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> https://github.com/benweet/stackedit
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="branch">Branch (optional)</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="branch" type="text" class="textfield" v-model.trim="branch" @keyup.enter="resolve()">
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Branch (optional)">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
If not provided, the master branch will be used.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="path">File path</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="path" type="text" class="textfield" v-model.trim="path" @keyup.enter="resolve()">
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="File path">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> docs/README.md<br>
|
||||
If the file exists, it will be replaced.
|
||||
</div>
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button" @click="resolve()">Ok</button>
|
||||
|
@ -42,31 +33,16 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import githubProvider from '../../services/providers/githubProvider';
|
||||
import modalTemplate from './modalTemplate';
|
||||
|
||||
export default {
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
branch: '',
|
||||
path: '',
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'config',
|
||||
]),
|
||||
currentFileName() {
|
||||
return this.$store.getters['file/current'].name;
|
||||
},
|
||||
repoUrl: {
|
||||
get() {
|
||||
return this.$store.getters['data/localSettings'].githubRepoUrl;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.dispatch('data/patchLocalSettings', {
|
||||
githubRepoUrl: value,
|
||||
});
|
||||
},
|
||||
},
|
||||
computedLocalSettings: {
|
||||
repoUrl: 'githubRepoUrl',
|
||||
},
|
||||
created() {
|
||||
this.path = `${this.currentFileName}.md`;
|
||||
|
@ -84,5 +60,5 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
<template>
|
||||
<div class="modal__inner-1 modal__inner-1--google-drive-sync">
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="googleDrive"></icon-provider>
|
||||
</div>
|
||||
<p>This will publish <b>{{currentFileName}}</b> to your <b>Google Drive</b> account.</p>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="fileId">File ID (optional)</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="fileId" type="text" class="textfield" v-model="fileId" @keyup.enter="resolve()">
|
||||
</div>
|
||||
<form-entry label="Folder ID (optional)">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="folderId" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
If no file ID is supplied, a new file will be created in your Google Drive root folder.
|
||||
If no folder ID is supplied, the file will be created in your root folder.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-entry__actions">
|
||||
<a href="javascript:void(0)" @click="openFolder">Choose folder</a>
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Existing file ID (optional)">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="fileId" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
This will overwrite the file on the server.
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="form-entry">
|
||||
<div class="form-entry__radio">
|
||||
<label>
|
||||
|
@ -26,21 +32,18 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="template">Template</label>
|
||||
<div class="form-entry__field">
|
||||
<select class="textfield" id="template" v-model="selectedTemplate" :disabled="format === 'markdown'" @keyup.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplates" :key="id" v-bind:value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<form-entry label="Template">
|
||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keyup.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="form-entry__actions">
|
||||
<a href="javascript:void(0)" @click="configureTemplates">Configure templates</a>
|
||||
</div>
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="modal__tip">
|
||||
<b>Tip:</b> You can provide a value for <code>title</code> in the <b>file properties</b>.
|
||||
<b>ProTip:</b> You can provide a value for <code>title</code> in the <a href="javascript:void(0)" @click="openFileProperties">file properties</a>.
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
|
@ -51,52 +54,20 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
import googleDriveProvider from '../../services/providers/googleDriveProvider';
|
||||
import store from '../../store';
|
||||
import modalTemplate from './modalTemplate';
|
||||
|
||||
const computedLocalSetting = id => ({
|
||||
get() {
|
||||
return store.getters['data/localSettings'][id];
|
||||
},
|
||||
set(value) {
|
||||
store.dispatch('data/patchLocalSettings', {
|
||||
[id]: value,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
fileId: '',
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'config',
|
||||
]),
|
||||
currentFileName() {
|
||||
return this.$store.getters['file/current'].name;
|
||||
},
|
||||
...mapGetters('data', [
|
||||
'allTemplates',
|
||||
]),
|
||||
selectedTemplate: computedLocalSetting('googleDrivePublishTemplate'),
|
||||
format: computedLocalSetting('googleDrivePublishFormat'),
|
||||
computedLocalSettings: {
|
||||
folderId: 'googleDriveFolderId',
|
||||
selectedTemplate: 'googleDrivePublishTemplate',
|
||||
format: 'googleDrivePublishFormat',
|
||||
},
|
||||
methods: {
|
||||
configureTemplates() {
|
||||
this.$store.dispatch('modal/open', {
|
||||
type: 'templates',
|
||||
selectedId: this.selectedTemplate,
|
||||
})
|
||||
.then(({ templates, selectedId }) => {
|
||||
this.$store.dispatch('data/setTemplates', templates);
|
||||
this.$store.dispatch('data/patchLocalSettings', {
|
||||
googleDrivePublishTemplate: selectedId,
|
||||
});
|
||||
});
|
||||
},
|
||||
openFolder() {
|
||||
return this.$store.dispatch(
|
||||
'modal/hideUntil',
|
||||
|
@ -117,5 +88,5 @@ export default {
|
|||
this.config.resolve(location);
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,34 +1,25 @@
|
|||
<template>
|
||||
<div class="modal__inner-1 modal__inner-1--google-drive-sync">
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="googleDrive"></icon-provider>
|
||||
</div>
|
||||
<p>This will save <b>{{currentFileName}}</b> to your <b>Google Drive</b> account and keep it synchronized.</p>
|
||||
<a href="javascript:void(0)" v-if="!showOptions" @click="showOptions = true">See options ▾</a>
|
||||
<div v-else>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="folderId">Folder ID (optional)</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="folderId" type="text" class="textfield" v-model.trim="folderId" @keyup.enter="resolve()">
|
||||
</div>
|
||||
<div class="form-entry__info">
|
||||
If no folder ID is supplied, the file will be created in your root folder.
|
||||
</div>
|
||||
<div class="form-entry__actions">
|
||||
<a href="javascript:void(0)" @click="openFolder">Choose folder</a>
|
||||
</div>
|
||||
<form-entry label="Folder ID (optional)">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="folderId" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
If no folder ID is supplied, the file will be created in your root folder.
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="fileId">File ID (optional)</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="fileId" type="text" class="textfield" v-model="fileId" @keyup.enter="resolve()">
|
||||
</div>
|
||||
<div class="form-entry__info">
|
||||
This will overwrite the existing file on the server.
|
||||
</div>
|
||||
<div class="form-entry__actions">
|
||||
<a href="javascript:void(0)" @click="openFolder">Choose folder</a>
|
||||
</div>
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Existing file ID (optional)">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="fileId" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
This will overwrite the file on the server.
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button" @click="resolve()">Ok</button>
|
||||
|
@ -38,35 +29,16 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
import googleDriveProvider from '../../services/providers/googleDriveProvider';
|
||||
import modalTemplate from './modalTemplate';
|
||||
|
||||
export default {
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
showOptions: false,
|
||||
fileId: '',
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'config',
|
||||
]),
|
||||
currentFileName() {
|
||||
return this.$store.getters['file/current'].name;
|
||||
},
|
||||
folderId: {
|
||||
get() {
|
||||
return this.$store.getters['data/localSettings'].googleDriveFolderId;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.dispatch('data/patchLocalSettings', {
|
||||
googleDriveFolderId: value,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.showOptions = this.folderId || this.fileId;
|
||||
computedLocalSettings: {
|
||||
folderId: 'googleDriveFolderId',
|
||||
},
|
||||
methods: {
|
||||
openFolder() {
|
||||
|
@ -86,5 +58,5 @@ export default {
|
|||
this.config.resolve(location);
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -2,18 +2,12 @@
|
|||
<div class="modal__inner-1 modal__inner-1--google-photo">
|
||||
<div class="modal__inner-2">
|
||||
<div class="google-photo__tumbnail" :style="{'background-image': thumbnailUrl}"></div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="title">Title (optional)</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="title" type="text" class="textfield" v-model.trim="title" @keyup.enter="resolve()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="size">Size limit (optional)</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="size" type="text" class="textfield" v-model="size" @keyup.enter="resolve()">
|
||||
</div>
|
||||
</div>
|
||||
<form-entry label="Title (optional)">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="title" @keyup.enter="resolve()">
|
||||
</form-entry>
|
||||
<form-entry label="Size limit (optional)">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="size" @keyup.enter="resolve()">
|
||||
</form-entry>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="reject()">Cancel</button>
|
||||
<button class="button" @click="resolve()">Ok</button>
|
||||
|
@ -24,10 +18,14 @@
|
|||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import FormEntry from './FormEntry';
|
||||
|
||||
const makeThumbnail = (url, size) => `${url}=s${size}`;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormEntry,
|
||||
},
|
||||
data: () => ({
|
||||
title: '',
|
||||
size: '',
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
<template>
|
||||
<div class="modal__inner-1 modal__inner-1--html-export">
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="template">Template</label>
|
||||
<div class="form-entry__field">
|
||||
<select class="textfield" id="template" v-model="selectedTemplate" @keyup.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplates" :key="id" v-bind:value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<form-entry label="Template">
|
||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keyup.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="form-entry__actions">
|
||||
<a href="javascript:void(0)" @click="configureTemplates">Configure templates</a>
|
||||
</div>
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button button--copy">Copy to clipboard</button>
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
|
@ -24,31 +21,16 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import Clipboard from 'clipboard';
|
||||
import exportSvc from '../../services/exportSvc';
|
||||
import modalTemplate from './modalTemplate';
|
||||
|
||||
export default {
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
result: '',
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'config',
|
||||
]),
|
||||
...mapGetters('data', [
|
||||
'allTemplates',
|
||||
]),
|
||||
selectedTemplate: {
|
||||
get() {
|
||||
return this.$store.getters['data/localSettings'].htmlExportTemplate;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.dispatch('data/patchLocalSettings', {
|
||||
htmlExportTemplate: value,
|
||||
});
|
||||
},
|
||||
},
|
||||
computedLocalSettings: {
|
||||
selectedTemplate: 'htmlExportTemplate',
|
||||
},
|
||||
mounted() {
|
||||
this.$watch('selectedTemplate', (selectedTemplate) => {
|
||||
|
@ -68,23 +50,11 @@ export default {
|
|||
this.clipboard.destroy();
|
||||
},
|
||||
methods: {
|
||||
configureTemplates() {
|
||||
this.$store.dispatch('modal/open', {
|
||||
type: 'templates',
|
||||
selectedId: this.selectedTemplate,
|
||||
})
|
||||
.then(({ templates, selectedId }) => {
|
||||
this.$store.dispatch('data/setTemplates', templates);
|
||||
this.$store.dispatch('data/patchLocalSettings', {
|
||||
htmlExportTemplate: selectedId,
|
||||
});
|
||||
});
|
||||
},
|
||||
resolve() {
|
||||
const currentFile = this.$store.getters['file/current'];
|
||||
exportSvc.exportToDisk(currentFile.id, 'html', this.allTemplates[this.selectedTemplate]);
|
||||
this.config.resolve();
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
<template>
|
||||
<div class="modal__inner-1 modal__inner-1--image">
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<p>Please provide a <b>URL</b> for your image.
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="url">URL</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="url" type="text" class="textfield" v-model="url" @keyup.enter="resolve()">
|
||||
</div>
|
||||
</div>
|
||||
<form-entry label="URL">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="url" @keyup.enter="resolve()">
|
||||
</form-entry>
|
||||
<menu-entry @click.native="openGooglePhotos(token)" v-for="token in googlePhotosTokens" :key="token.sub">
|
||||
<icon-provider slot="icon" provider-id="googlePhotos"></icon-provider>
|
||||
<div>Open from Google Photos</div>
|
||||
|
@ -28,10 +25,12 @@
|
|||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import MenuEntry from '../menus/MenuEntry';
|
||||
import FormEntry from './FormEntry';
|
||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormEntry,
|
||||
MenuEntry,
|
||||
},
|
||||
data: () => ({
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
<template>
|
||||
<div class="modal__inner-1 modal__inner-1--link" @keyup.enter="resolve()">
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<p>Please provide a <b>URL</b> for your link.
|
||||
<div class="form-entry">
|
||||
<label class="form-entry__label" for="url">URL</label>
|
||||
<div class="form-entry__field">
|
||||
<input id="url" type="text" class="textfield" v-model="url">
|
||||
</div>
|
||||
</div>
|
||||
<form-entry label="URL">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="url" @keyup.enter="resolve()">
|
||||
</form-entry>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="reject()">Cancel</button>
|
||||
<button class="button" @click="resolve()">Ok</button>
|
||||
|
@ -18,8 +15,12 @@
|
|||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import FormEntry from './FormEntry';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormEntry,
|
||||
},
|
||||
data: () => ({
|
||||
url: '',
|
||||
}),
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<div class="form-entry__field">
|
||||
<input v-if="isEditing" id="template" type="text" class="textfield" v-focus @blur="submitEdit()" @keyup.enter="submitEdit()" @keyup.esc.stop="submitEdit(true)" v-model="editingName">
|
||||
<select v-else id="template" v-model="selectedId" class="textfield">
|
||||
<option v-for="(template, id) in templates" :key="id" v-bind:value="id">
|
||||
<option v-for="(template, id) in templates" :key="id" :value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
|
|
65
src/components/modals/WordpressPublishModal.vue
Normal file
65
src/components/modals/WordpressPublishModal.vue
Normal file
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="wordpress"></icon-provider>
|
||||
</div>
|
||||
<p>This will publish <b>{{currentFileName}}</b> to your <b>WordPress</b> site.</p>
|
||||
<form-entry label="Site domain">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="domain" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> example.wordpress.com<br>
|
||||
<b>Jetpack plugin</b> is required for self-hosted sites.
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Existing post ID (optional)">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="postId" @keyup.enter="resolve()">
|
||||
</form-entry>
|
||||
<form-entry label="Template">
|
||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keyup.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="form-entry__actions">
|
||||
<a href="javascript:void(0)" @click="configureTemplates">Configure templates</a>
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="modal__tip">
|
||||
<b>ProTip:</b> You can provide values for <code>title</code>, <code>tags</code>,
|
||||
<code>categories</code>, <code>excerpt</code>, <code>author</code>, <code>featuredImage</code>,
|
||||
<code>status</code> and <code>date</code> in the <a href="javascript:void(0)" @click="openFileProperties">file properties</a>.
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button" @click="resolve()">Ok</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import wordpressProvider from '../../services/providers/wordpressProvider';
|
||||
import modalTemplate from './modalTemplate';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
postId: '',
|
||||
}),
|
||||
computedLocalSettings: {
|
||||
domain: 'wordpressDomain',
|
||||
selectedTemplate: 'wordpressPublishTemplate',
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
if (this.domain) {
|
||||
// Return new location
|
||||
const location = wordpressProvider.makeLocation(
|
||||
this.config.token, this.domain, this.postId);
|
||||
location.templateId = this.selectedTemplate;
|
||||
this.config.resolve(location);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
55
src/components/modals/ZendeskAccountModal.vue
Normal file
55
src/components/modals/ZendeskAccountModal.vue
Normal file
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="zendesk"></icon-provider>
|
||||
</div>
|
||||
<p>This will link your <b>Zendesk</b> account to your <b>StackEdit</b> workspace.</p>
|
||||
<form-entry label="Site URL">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="siteUrl" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> https://example.zendesk.com/
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Client Unique Identifier">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="clientId" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
You have to configure an OAuth Client with redirect URL <b>{{redirectUrl}}</b><br>
|
||||
<a href="https://support.zendesk.com/hc/en-us/articles/203663836" target="_blank"><b>More info</b></a>
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button" @click="resolve()">Ok</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modalTemplate from './modalTemplate';
|
||||
import utils from '../../services/utils';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
redirectUrl: utils.oauth2RedirectUri,
|
||||
}),
|
||||
computedLocalSettings: {
|
||||
siteUrl: 'zendeskSiteUrl',
|
||||
clientId: 'zendeskClientId',
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
if (this.siteUrl && this.clientId) {
|
||||
const parsedUrl = this.siteUrl.match(/^https:\/\/([^.]+)\.zendesk\.com/);
|
||||
if (parsedUrl) {
|
||||
this.config.resolve({
|
||||
subdomain: parsedUrl[1],
|
||||
clientId: this.clientId,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
70
src/components/modals/ZendeskPublishModal.vue
Normal file
70
src/components/modals/ZendeskPublishModal.vue
Normal file
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<div class="modal__inner-1">
|
||||
<div class="modal__inner-2">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="zendesk"></icon-provider>
|
||||
</div>
|
||||
<p>This will publish <b>{{currentFileName}}</b> to your <b>Zendesk Help Center</b>.</p>
|
||||
<form-entry label="Section ID">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="sectionId" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
https://example.zendesk.com/hc/en-us/sections/<b>21857469</b>-Section-name
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Existing article ID (optional)">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="articleId" @keyup.enter="resolve()">
|
||||
</form-entry>
|
||||
<form-entry label="Locale (optional)">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="locale" @keyup.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Default:</b> en-us
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Template">
|
||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keyup.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="form-entry__actions">
|
||||
<a href="javascript:void(0)" @click="configureTemplates">Configure templates</a>
|
||||
</div>
|
||||
</form-entry>
|
||||
<div class="modal__tip">
|
||||
<b>ProTip:</b> You can provide values for <code>title</code>, <code>tags</code> and
|
||||
<code>status</code> in the <a href="javascript:void(0)" @click="openFileProperties">file properties</a>.
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button" @click="resolve()">Ok</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import zendeskProvider from '../../services/providers/zendeskProvider';
|
||||
import modalTemplate from './modalTemplate';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
articleId: '',
|
||||
}),
|
||||
computedLocalSettings: {
|
||||
sectionId: 'zendescPublishSectionId',
|
||||
locale: 'zendescPublishLocale',
|
||||
selectedTemplate: 'zendeskPublishTemplate',
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
if (this.sectionId || this.articleId) {
|
||||
// Return new location
|
||||
const location = zendeskProvider.makeLocation(
|
||||
this.config.token, this.sectionId, this.locale || 'en-us', this.articleId);
|
||||
location.templateId = this.selectedTemplate;
|
||||
this.config.resolve(location);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
54
src/components/modals/modalTemplate.js
Normal file
54
src/components/modals/modalTemplate.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
import FormEntry from './FormEntry';
|
||||
import store from '../../store';
|
||||
|
||||
export default (desc) => {
|
||||
const component = {
|
||||
...desc,
|
||||
components: {
|
||||
FormEntry,
|
||||
},
|
||||
computed: {
|
||||
...desc.computed || {},
|
||||
config() {
|
||||
return store.getters['modal/config'];
|
||||
},
|
||||
currentFileName() {
|
||||
return store.getters['file/current'].name;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...desc.methods || {},
|
||||
openFileProperties: () => store.dispatch('modal/open', 'fileProperties'),
|
||||
},
|
||||
};
|
||||
Object.keys(desc.computedLocalSettings || {}).forEach((key) => {
|
||||
const id = desc.computedLocalSettings[key];
|
||||
component.computed[key] = {
|
||||
get() {
|
||||
return store.getters['data/localSettings'][id];
|
||||
},
|
||||
set(value) {
|
||||
store.dispatch('data/patchLocalSettings', {
|
||||
[id]: value,
|
||||
});
|
||||
},
|
||||
};
|
||||
if (key === 'selectedTemplate') {
|
||||
component.computed.allTemplates = () => store.getters['data/allTemplates'];
|
||||
component.methods.configureTemplates = () => {
|
||||
store.dispatch('modal/open', {
|
||||
type: 'templates',
|
||||
selectedId: this.selectedTemplate,
|
||||
})
|
||||
.then(({ templates, selectedId }) => {
|
||||
store.dispatch('data/setTemplates', templates);
|
||||
store.dispatch('data/patchLocalSettings', {
|
||||
[id]: selectedId,
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
component.computedLocalSettings = null;
|
||||
return component;
|
||||
};
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
|
||||
|
||||
> Written with [StackEdit](https://stackedit.io/).
|
|
@ -1,4 +1,5 @@
|
|||
### File properties can contain metadata used for your publications (Wordpress, Blogger...).
|
||||
### For example:
|
||||
#title: My article
|
||||
#author:
|
||||
#tags: Tag 1, Tag 2
|
||||
|
@ -13,6 +14,16 @@ extensions:
|
|||
|
||||
# Markdown extensions
|
||||
markdown:
|
||||
### For strict CommonMark:
|
||||
#abbr: false
|
||||
#deflist: false
|
||||
#del: false
|
||||
#footnote: false
|
||||
#linkify: false
|
||||
#sub: false
|
||||
#sup: false
|
||||
#table: false
|
||||
#typographer: false
|
||||
abbr: true
|
||||
breaks: false
|
||||
deflist: true
|
||||
|
|
|
@ -12,10 +12,19 @@ export default () => ({
|
|||
googleDrivePublishFormat: 'markdown',
|
||||
googleDrivePublishTemplate: 'styledHtml',
|
||||
bloggerBlogUrl: '',
|
||||
bloggerPublishTemplate: 'styledHtml',
|
||||
bloggerPublishTemplate: 'plainHtml',
|
||||
dropboxRestrictedAccess: false,
|
||||
dropboxPublishTemplate: 'styledHtml',
|
||||
githubRepoFullAccess: false,
|
||||
githubRepoUrl: '',
|
||||
githubPublishTemplate: 'jekyllSite',
|
||||
gistIsPublic: false,
|
||||
gistPublishTemplate: 'plainText',
|
||||
wordpressDomain: '',
|
||||
wordpressPublishTemplate: 'plainHtml',
|
||||
zendeskSiteUrl: '',
|
||||
zendeskClientId: '',
|
||||
zendescPublishSectionId: '',
|
||||
zendescPublishLocale: '',
|
||||
zendeskPublishTemplate: 'plainHtml',
|
||||
});
|
||||
|
|
|
@ -75,7 +75,7 @@ newFileContent: |
|
|||
|
||||
# Default properties for newly created files
|
||||
newFileProperties: |
|
||||
#extensions:
|
||||
# markdown:
|
||||
# breaks: true
|
||||
# extensions:
|
||||
# markdown:
|
||||
# breaks: true
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ The following JavaScript context will be passed to the template:
|
|||
}
|
||||
|
||||
You can use Handlebars built-in helpers and some custom StackEdit helpers:
|
||||
{{#tocToHtml files.0.content.toc}}{{/tocToHtml}} will produce a nice TOC.
|
||||
{{#tocToHtml files.0.content.toc}}{{/tocToHtml}} will produce a clickable TOC.
|
||||
{{#tocToHtml files.0.content.toc 3}}{{/tocToHtml}} will limit the TOC depth to 3.
|
||||
-->
|
||||
|
||||
|
|
|
@ -167,7 +167,7 @@ extensionSvc.onInitConverter(0, (markdown, options) => {
|
|||
markdown.renderer.rules.th_open = markdown.renderer.rules.td_open;
|
||||
|
||||
markdown.renderer.rules.footnote_ref = (tokens, idx) => {
|
||||
const n = Number(tokens[idx].meta.id + 1).toString();
|
||||
const n = `${Number(tokens[idx].meta.id + 1)}`;
|
||||
let id = `fnref${n}`;
|
||||
if (tokens[idx].meta.subId > 0) {
|
||||
id += `:${tokens[idx].meta.subId}`;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="100%" height="100%" viewBox="0 0 24.00 24.00">
|
||||
<path fill="#000000" fill-opacity="1" stroke-linejoin="round" d="M 6,2L 18,2C 19.1046,2 20,2.89543 20,4L 20,20C 20,21.1046 19.1046,22 18,22L 6,22C 4.89543,22 4,21.1046 4,20L 4,4C 4,2.89543 4.89543,2 6,2 Z M 12,4.00001C 8.68629,4.00001 5.99999,6.6863 5.99999,10C 5.99999,13.3137 8.68629,16 12.1022,15.9992L 11.2221,13.7674C 10.946,13.2891 11.1099,12.6775 11.5882,12.4013L 12.4542,11.9013C 12.9325,11.6252 13.5441,11.7891 13.8202,12.2674L 15.7446,14.6884C 17.1194,13.5889 18,11.8973 18,10C 18,6.6863 15.3137,4.00001 12,4.00001 Z M 12,9.00001C 12.5523,9.00001 13,9.44773 13,10C 13,10.5523 12.5523,11 12,11C 11.4477,11 11,10.5523 11,10C 11,9.44773 11.4477,9.00001 12,9.00001 Z M 7,18C 6.44771,18 6,18.4477 6,19C 6,19.5523 6.44771,20 7,20C 7.55228,20 8,19.5523 8,19C 8,18.4477 7.55228,18 7,18 Z M 12.0882,13.2674L 14.5757,19.5759L 17.1738,18.0759L 12.9542,12.7674L 12.0882,13.2674 Z "/>
|
||||
<path d="M 6,2L 18,2C 19.1046,2 20,2.89543 20,4L 20,20C 20,21.1046 19.1046,22 18,22L 6,22C 4.89543,22 4,21.1046 4,20L 4,4C 4,2.89543 4.89543,2 6,2 Z M 12,4.00001C 8.68629,4.00001 5.99999,6.6863 5.99999,10C 5.99999,13.3137 8.68629,16 12.1022,15.9992L 11.2221,13.7674C 10.946,13.2891 11.1099,12.6775 11.5882,12.4013L 12.4542,11.9013C 12.9325,11.6252 13.5441,11.7891 13.8202,12.2674L 15.7446,14.6884C 17.1194,13.5889 18,11.8973 18,10C 18,6.6863 15.3137,4.00001 12,4.00001 Z M 12,9.00001C 12.5523,9.00001 13,9.44773 13,10C 13,10.5523 12.5523,11 12,11C 11.4477,11 11,10.5523 11,10C 11,9.44773 11.4477,9.00001 12,9.00001 Z M 7,18C 6.44771,18 6,18.4477 6,19C 6,19.5523 6.44771,20 7,20C 7.55228,20 8,19.5523 8,19C 8,18.4477 7.55228,18 7,18 Z M 12.0882,13.2674L 14.5757,19.5759L 17.1738,18.0759L 12.9542,12.7674L 12.0882,13.2674 Z "/>
|
||||
</svg>
|
||||
</template>
|
||||
|
|
|
@ -40,8 +40,16 @@ export default {
|
|||
background-image: url(../assets/iconDropbox.svg);
|
||||
}
|
||||
|
||||
.icon-provider--wordpress {
|
||||
background-image: url(../assets/iconWordpress.svg);
|
||||
}
|
||||
|
||||
.icon-provider--blogger,
|
||||
.icon-provider--bloggerPage {
|
||||
background-image: url(../assets/iconBlogger.svg);
|
||||
}
|
||||
|
||||
.icon-provider--zendesk {
|
||||
background-image: url(../assets/iconZendesk.svg);
|
||||
}
|
||||
</style>
|
||||
|
|
5
src/icons/SignalOff.vue
Normal file
5
src/icons/SignalOff.vue
Normal file
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="100%" height="100%" viewBox="0 0 24.00 24.00">
|
||||
<path d="M 18,3L 18,16.1777L 21,19.1777L 21,3L 18,3 Z M 4.27734,5L 3,6.2676L 10.7324,14L 8,14L 8,21L 11,21L 11,14.2676L 13,16.2676L 13,21L 16,21L 16,19.2676L 19.7324,23L 21,21.7227L 4.27734,5 Z M 13,9L 13,11.1777L 16,14.1777L 16,9L 13,9 Z M 3,18L 3,21L 6,21L 6,18L 3,18 Z "/>
|
||||
</svg>
|
||||
</template>
|
|
@ -39,6 +39,7 @@ import CodeBraces from './CodeBraces';
|
|||
import OpenInNew from './OpenInNew';
|
||||
import Information from './Information';
|
||||
import Alert from './Alert';
|
||||
import SignalOff from './SignalOff';
|
||||
// Providers
|
||||
import Provider from './Provider';
|
||||
|
||||
|
@ -82,5 +83,6 @@ Vue.component('iconCodeBraces', CodeBraces);
|
|||
Vue.component('iconOpenInNew', OpenInNew);
|
||||
Vue.component('iconInformation', Information);
|
||||
Vue.component('iconAlert', Alert);
|
||||
Vue.component('iconSignalOff', SignalOff);
|
||||
// Providers
|
||||
Vue.component('iconProvider', Provider);
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import Vue from 'vue';
|
||||
import * as OfflinePluginRuntime from 'offline-plugin/runtime';
|
||||
import './extensions/';
|
||||
import './services/optional';
|
||||
import './icons/';
|
||||
import App from './components/App';
|
||||
import store from './store';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
OfflinePluginRuntime.install();
|
||||
}
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
/* eslint-disable no-new */
|
||||
|
|
|
@ -182,5 +182,6 @@ function mergeContent(serverContent, clientContent, lastMergedContent = {}) {
|
|||
export default {
|
||||
makePatchableText,
|
||||
restoreDiscussionOffsets,
|
||||
mergeObjects,
|
||||
mergeContent,
|
||||
};
|
||||
|
|
|
@ -96,9 +96,9 @@ export default {
|
|||
// e.data can contain unsafe data if helpers attempts to call postMessage
|
||||
const [err, result] = e.data;
|
||||
if (err) {
|
||||
reject(err.toString());
|
||||
reject(`${err}`);
|
||||
} else {
|
||||
resolve(result.toString());
|
||||
resolve(`${result}`);
|
||||
}
|
||||
});
|
||||
worker.postMessage([template.value, view, template.helpers]);
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
import Mousetrap from 'mousetrap';
|
||||
import store from '../../store';
|
||||
import editorSvc from '../../services/editorSvc';
|
||||
import editorEngineSvc from '../../services/editorEngineSvc';
|
||||
import syncSvc from '../../services/syncSvc';
|
||||
|
||||
// Skip shortcuts if modal is open or editor is hidden
|
||||
Mousetrap.prototype.stopCallback = () => store.getters['modal/config'] || !store.getters['layout/styles'].showEditor;
|
||||
|
||||
const pagedownHandler = name => () => editorSvc.pagedownEditor.uiManager.doClick(name);
|
||||
const pagedownHandler = name => () => {
|
||||
editorSvc.pagedownEditor.uiManager.doClick(name);
|
||||
return true;
|
||||
};
|
||||
|
||||
const methods = {
|
||||
bold: pagedownHandler('bold'),
|
||||
|
@ -19,7 +23,32 @@ const methods = {
|
|||
ulist: pagedownHandler('ulist'),
|
||||
heading: pagedownHandler('heading'),
|
||||
hr: pagedownHandler('hr'),
|
||||
sync: () => syncSvc.isSyncPossible() && syncSvc.requestSync(),
|
||||
sync() {
|
||||
if (syncSvc.isSyncPossible()) {
|
||||
syncSvc.requestSync();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
expand(param1, param2) {
|
||||
const text = param1 && `${param1}`;
|
||||
const replacement = param2 && `${param2}`;
|
||||
if (text && replacement) {
|
||||
setTimeout(() => {
|
||||
const selectionMgr = editorEngineSvc.clEditor.selectionMgr;
|
||||
let offset = editorEngineSvc.clEditor.selectionMgr.selectionStart;
|
||||
if (offset === selectionMgr.selectionEnd) {
|
||||
const range = selectionMgr.createRange(offset - text.length, offset);
|
||||
if (`${range}` === text) {
|
||||
range.deleteContents();
|
||||
range.insertNode(document.createTextNode(replacement));
|
||||
offset = (offset - text.length) + replacement.length;
|
||||
selectionMgr.setSelectionStartEnd(offset, offset);
|
||||
selectionMgr.updateCursorCoordinates(true);
|
||||
}
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
store.watch(
|
||||
|
@ -36,10 +65,7 @@ store.watch(
|
|||
params = [params];
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(methods, method)) {
|
||||
Mousetrap.bind(shortcut.keys.toString(), () => {
|
||||
methods[method].apply(null, params);
|
||||
return false; // preventDefault
|
||||
});
|
||||
Mousetrap.bind(`${shortcut.keys}`, () => !methods[method].apply(null, params));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -4,77 +4,76 @@ import providerUtils from './providerUtils';
|
|||
import providerRegistry from './providerRegistry';
|
||||
import utils from '../utils';
|
||||
|
||||
const restrictedFolder = '/Applications/StackEdit (restricted)';
|
||||
const restrictedFolderRegexp = /^\/Applications\/StackEdit \(restricted\)/;
|
||||
const makePathAbsolute = (token, path) => {
|
||||
if (!token.fullAccess) {
|
||||
return `/Applications/StackEdit (restricted)${path}`;
|
||||
}
|
||||
return path;
|
||||
};
|
||||
const makePathRelative = (token, path) => {
|
||||
if (!token.fullAccess) {
|
||||
return path.replace(/^\/Applications\/StackEdit \(restricted\)/, '');
|
||||
}
|
||||
return path;
|
||||
};
|
||||
|
||||
export default providerRegistry.register({
|
||||
id: 'dropbox',
|
||||
fullAccess: true,
|
||||
getToken(location) {
|
||||
const token = store.getters['data/dropboxTokens'][location.sub];
|
||||
if (token && !!token.fullAccess === this.fullAccess) {
|
||||
return token;
|
||||
}
|
||||
return null;
|
||||
return store.getters['data/dropboxTokens'][location.sub];
|
||||
},
|
||||
getUrl(location) {
|
||||
const pathComponents = location.path.split('/').map(encodeURIComponent);
|
||||
const filename = pathComponents.pop();
|
||||
let baseUrl = 'https://www.dropbox.com/home';
|
||||
if (!this.fullAccess) {
|
||||
baseUrl += encodeURIComponent(restrictedFolder);
|
||||
}
|
||||
return `${baseUrl}${pathComponents.join('/')}?preview=${filename}`;
|
||||
return `https://www.dropbox.com/home${pathComponents.join('/')}?preview=${filename}`;
|
||||
},
|
||||
getDescription(location) {
|
||||
const token = this.getToken(location);
|
||||
if (this.fullAccess) {
|
||||
return `${location.path} — ${token.name}`;
|
||||
}
|
||||
return `${location.path} — ${token.name} (restricted)`;
|
||||
return `${location.path} — ${location.dropboxFileId} — ${token.name}`;
|
||||
},
|
||||
checkPath(path) {
|
||||
return path && path.match(/^\/[^\\<>:"|?*]+$/);
|
||||
},
|
||||
downloadContent(token, location) {
|
||||
return dropboxHelper.downloadFile(token, location.path, location.dropboxFileId)
|
||||
.then(({ content }) => providerUtils.parseContent(content));
|
||||
downloadContent(token, syncLocation) {
|
||||
return dropboxHelper.downloadFile(
|
||||
token,
|
||||
makePathRelative(token, syncLocation.path),
|
||||
syncLocation.dropboxFileId,
|
||||
)
|
||||
.then(({ content }) => providerUtils.parseContent(content, syncLocation));
|
||||
},
|
||||
uploadContent(token, content, location) {
|
||||
uploadContent(token, content, syncLocation) {
|
||||
return dropboxHelper.uploadFile(
|
||||
token,
|
||||
location.path,
|
||||
makePathRelative(token, syncLocation.path),
|
||||
providerUtils.serializeContent(content),
|
||||
location.dropboxFileId,
|
||||
syncLocation.dropboxFileId,
|
||||
)
|
||||
.then(dropboxFile => ({
|
||||
...location,
|
||||
path: dropboxFile.path_display,
|
||||
...syncLocation,
|
||||
path: makePathAbsolute(token, dropboxFile.path_display),
|
||||
dropboxFileId: dropboxFile.id,
|
||||
}));
|
||||
},
|
||||
publish(token, html, metadata, location) {
|
||||
publish(token, html, metadata, publishLocation) {
|
||||
return dropboxHelper.uploadFile(
|
||||
token,
|
||||
location.path,
|
||||
publishLocation.path,
|
||||
html,
|
||||
location.dropboxFileId,
|
||||
publishLocation.dropboxFileId,
|
||||
)
|
||||
.then(dropboxFile => ({
|
||||
...location,
|
||||
path: dropboxFile.path_display,
|
||||
...publishLocation,
|
||||
path: makePathAbsolute(token, dropboxFile.path_display),
|
||||
dropboxFileId: dropboxFile.id,
|
||||
}));
|
||||
},
|
||||
openFiles(token, paths) {
|
||||
const openOneFile = () => {
|
||||
let path = paths.pop();
|
||||
const path = paths.pop();
|
||||
if (!path) {
|
||||
return null;
|
||||
}
|
||||
if (!token.fullAccess) {
|
||||
path = path.replace(restrictedFolderRegexp, '');
|
||||
}
|
||||
let syncLocation;
|
||||
// Try to find an existing sync location
|
||||
store.getters['syncLocation/items'].some((existingSyncLocation) => {
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import dropboxProvider from './dropboxProvider';
|
||||
import providerRegistry from './providerRegistry';
|
||||
|
||||
export default providerRegistry.register({
|
||||
...dropboxProvider,
|
||||
id: 'dropboxRestricted',
|
||||
fullAccess: false,
|
||||
});
|
|
@ -17,37 +17,37 @@ export default providerRegistry.register({
|
|||
const token = this.getToken(location);
|
||||
return `${location.filename} — ${location.gistId} — ${token.name}`;
|
||||
},
|
||||
downloadContent(token, location) {
|
||||
return githubHelper.downloadGist(token, location.gistId, location.filename)
|
||||
.then(content => providerUtils.parseContent(content));
|
||||
downloadContent(token, syncLocation) {
|
||||
return githubHelper.downloadGist(token, syncLocation.gistId, syncLocation.filename)
|
||||
.then(content => providerUtils.parseContent(content, syncLocation));
|
||||
},
|
||||
uploadContent(token, content, location) {
|
||||
const file = store.state.file.itemMap[location.fileId];
|
||||
uploadContent(token, content, syncLocation) {
|
||||
const file = store.state.file.itemMap[syncLocation.fileId];
|
||||
const description = (file && file.name) || defaultDescription;
|
||||
return githubHelper.uploadGist(
|
||||
token,
|
||||
description,
|
||||
location.filename,
|
||||
syncLocation.filename,
|
||||
providerUtils.serializeContent(content),
|
||||
location.isPublic,
|
||||
location.gistId,
|
||||
syncLocation.isPublic,
|
||||
syncLocation.gistId,
|
||||
)
|
||||
.then(gist => ({
|
||||
...location,
|
||||
...syncLocation,
|
||||
gistId: gist.id,
|
||||
}));
|
||||
},
|
||||
publish(token, html, metadata, location) {
|
||||
publish(token, html, metadata, publishLocation) {
|
||||
return githubHelper.uploadGist(
|
||||
token,
|
||||
metadata.title,
|
||||
location.filename,
|
||||
publishLocation.filename,
|
||||
html,
|
||||
location.isPublic,
|
||||
location.gistId,
|
||||
publishLocation.isPublic,
|
||||
publishLocation.gistId,
|
||||
)
|
||||
.then(gist => ({
|
||||
...location,
|
||||
...publishLocation,
|
||||
gistId: gist.id,
|
||||
}));
|
||||
},
|
||||
|
|
|
@ -17,46 +17,46 @@ export default providerRegistry.register({
|
|||
const token = this.getToken(location);
|
||||
return `${location.path} — ${location.owner}/${location.repo} — ${token.name}`;
|
||||
},
|
||||
downloadContent(token, location) {
|
||||
downloadContent(token, syncLocation) {
|
||||
return githubHelper.downloadFile(
|
||||
token, location.owner, location.repo, location.branch, location.path,
|
||||
token, syncLocation.owner, syncLocation.repo, syncLocation.branch, syncLocation.path,
|
||||
)
|
||||
.then(({ sha, content }) => {
|
||||
savedSha[location.id] = sha;
|
||||
return providerUtils.parseContent(content);
|
||||
savedSha[syncLocation.id] = sha;
|
||||
return providerUtils.parseContent(content, syncLocation);
|
||||
})
|
||||
.catch(() => null); // Ignore error, without the sha upload is going to fail anyway
|
||||
},
|
||||
uploadContent(token, content, location) {
|
||||
const sha = savedSha[location.id];
|
||||
delete savedSha[location.id];
|
||||
uploadContent(token, content, syncLocation) {
|
||||
const sha = savedSha[syncLocation.id];
|
||||
delete savedSha[syncLocation.id];
|
||||
return githubHelper.uploadFile(
|
||||
token,
|
||||
location.owner,
|
||||
location.repo,
|
||||
location.branch,
|
||||
location.path,
|
||||
syncLocation.owner,
|
||||
syncLocation.repo,
|
||||
syncLocation.branch,
|
||||
syncLocation.path,
|
||||
providerUtils.serializeContent(content),
|
||||
sha,
|
||||
)
|
||||
.then(() => location);
|
||||
.then(() => syncLocation);
|
||||
},
|
||||
publish(token, html, metadata, location) {
|
||||
return this.downloadContent(token, location) // Get the last sha
|
||||
publish(token, html, metadata, publishLocation) {
|
||||
return this.downloadContent(token, publishLocation) // Get the last sha
|
||||
.then(() => {
|
||||
const sha = savedSha[location.id];
|
||||
delete savedSha[location.id];
|
||||
const sha = savedSha[publishLocation.id];
|
||||
delete savedSha[publishLocation.id];
|
||||
return githubHelper.uploadFile(
|
||||
token,
|
||||
location.owner,
|
||||
location.repo,
|
||||
location.branch,
|
||||
location.path,
|
||||
publishLocation.owner,
|
||||
publishLocation.repo,
|
||||
publishLocation.branch,
|
||||
publishLocation.path,
|
||||
html,
|
||||
sha,
|
||||
);
|
||||
})
|
||||
.then(() => location);
|
||||
.then(() => publishLocation);
|
||||
},
|
||||
makeLocation(token, owner, repo, branch, path) {
|
||||
return {
|
||||
|
|
|
@ -62,7 +62,11 @@ export default providerRegistry.register({
|
|||
.then(() => syncData);
|
||||
},
|
||||
downloadContent(token, syncLocation) {
|
||||
const syncData = store.getters['data/syncDataByItemId'][`${syncLocation.fileId}/content`];
|
||||
return this.downloadData(token, `${syncLocation.fileId}/content`)
|
||||
.then(() => syncLocation);
|
||||
},
|
||||
downloadData(token, dataId) {
|
||||
const syncData = store.getters['data/syncDataByItemId'][dataId];
|
||||
if (!syncData) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
@ -70,8 +74,7 @@ export default providerRegistry.register({
|
|||
.then((content) => {
|
||||
const item = JSON.parse(content);
|
||||
if (item.hash !== syncData.hash) {
|
||||
store.dispatch('data/setSyncData', {
|
||||
...store.getters['data/syncData'],
|
||||
store.dispatch('data/patchSyncData', {
|
||||
[syncData.id]: {
|
||||
...syncData,
|
||||
hash: item.hash,
|
||||
|
@ -82,29 +85,34 @@ export default providerRegistry.register({
|
|||
});
|
||||
},
|
||||
uploadContent(token, content, syncLocation, ifNotTooLate) {
|
||||
const syncData = store.getters['data/syncDataByItemId'][`${syncLocation.fileId}/content`];
|
||||
if (syncData && syncData.hash === content.hash) {
|
||||
return this.uploadData(token, undefined, content, `${syncLocation.fileId}/content`, ifNotTooLate)
|
||||
.then(() => syncLocation);
|
||||
},
|
||||
uploadData(token, sub, item, dataId, ifNotTooLate) {
|
||||
const syncData = store.getters['data/syncDataByItemId'][dataId];
|
||||
if (syncData && syncData.hash === item.hash) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return googleHelper.uploadAppDataFile(
|
||||
token,
|
||||
JSON.stringify({
|
||||
id: content.id,
|
||||
type: content.type,
|
||||
hash: content.hash,
|
||||
}), ['appDataFolder'],
|
||||
JSON.stringify(content),
|
||||
id: item.id,
|
||||
type: item.type,
|
||||
hash: item.hash,
|
||||
sub,
|
||||
}),
|
||||
['appDataFolder'],
|
||||
JSON.stringify(item),
|
||||
syncData && syncData.id,
|
||||
ifNotTooLate,
|
||||
)
|
||||
.then(file => store.dispatch('data/setSyncData', {
|
||||
...store.getters['data/syncData'],
|
||||
.then(file => store.dispatch('data/patchSyncData', {
|
||||
[file.id]: {
|
||||
// Build sync data
|
||||
id: file.id,
|
||||
itemId: content.id,
|
||||
type: content.type,
|
||||
hash: content.hash,
|
||||
itemId: item.id,
|
||||
type: item.type,
|
||||
hash: item.hash,
|
||||
},
|
||||
}));
|
||||
},
|
||||
|
|
|
@ -21,7 +21,7 @@ export default providerRegistry.register({
|
|||
},
|
||||
downloadContent(token, syncLocation) {
|
||||
return googleHelper.downloadFile(token, syncLocation.driveFileId)
|
||||
.then(content => providerUtils.parseContent(content));
|
||||
.then(content => providerUtils.parseContent(content, syncLocation));
|
||||
},
|
||||
uploadContent(token, content, syncLocation, ifNotTooLate) {
|
||||
const file = store.state.file.itemMap[syncLocation.fileId];
|
||||
|
|
|
@ -13,8 +13,9 @@ const getAppKey = (fullAccess) => {
|
|||
const request = (token, options, args) => utils.request({
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers,
|
||||
'Content-Type': 'application/octet-stream',
|
||||
...options.headers || {},
|
||||
'Content-Type': options.body && (typeof options.body === 'string'
|
||||
? 'application/octet-stream' : 'application/json; charset=utf-8'),
|
||||
'Dropbox-API-Arg': args && JSON.stringify(args),
|
||||
Authorization: `Bearer ${token.accessToken}`,
|
||||
},
|
||||
|
@ -34,17 +35,17 @@ export default {
|
|||
})
|
||||
.then((res) => {
|
||||
// Check the returned sub consistency
|
||||
if (sub && res.body.account_id !== sub) {
|
||||
if (sub && `${res.body.account_id}` !== sub) {
|
||||
throw new Error('Dropbox account ID not expected.');
|
||||
}
|
||||
// Build token object including scopes and sub
|
||||
const token = {
|
||||
accessToken,
|
||||
name: res.body.name.display_name,
|
||||
sub: res.body.account_id,
|
||||
sub: `${res.body.account_id}`,
|
||||
fullAccess,
|
||||
};
|
||||
// Add token to githubTokens
|
||||
// Add token to dropboxTokens
|
||||
store.dispatch('data/setDropboxToken', token);
|
||||
return token;
|
||||
}));
|
||||
|
|
|
@ -10,18 +10,15 @@ const getScopes = token => [token.repoFullAccess ? 'repo' : 'public_repo', 'gist
|
|||
const request = (token, options) => utils.request({
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers,
|
||||
...options.headers || {},
|
||||
Authorization: `token ${token.accessToken}`,
|
||||
},
|
||||
params: {
|
||||
...options.params || {},
|
||||
t: Date.now(), // Prevent from caching
|
||||
},
|
||||
});
|
||||
|
||||
const base64Encode = str => btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
|
||||
(match, p1) => String.fromCharCode(`0x${p1}`),
|
||||
));
|
||||
const base64Decode = str => decodeURIComponent(atob(str).split('').map(
|
||||
c => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`,
|
||||
).join(''));
|
||||
|
||||
export default {
|
||||
startOauth2(scopes, sub = null, silent = false) {
|
||||
return utils.startOauth2(
|
||||
|
@ -49,7 +46,7 @@ export default {
|
|||
})
|
||||
.then((res) => {
|
||||
// Check the returned sub consistency
|
||||
if (sub && res.body.id !== sub) {
|
||||
if (sub && `${res.body.id}` !== sub) {
|
||||
throw new Error('GitHub account ID not expected.');
|
||||
}
|
||||
// Build token object including scopes and sub
|
||||
|
@ -57,7 +54,7 @@ export default {
|
|||
scopes,
|
||||
accessToken,
|
||||
name: res.body.name,
|
||||
sub: res.body.id,
|
||||
sub: `${res.body.id}`,
|
||||
repoFullAccess: scopes.indexOf('repo') !== -1,
|
||||
};
|
||||
// Add token to githubTokens
|
||||
|
@ -74,7 +71,7 @@ export default {
|
|||
url: `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/contents/${encodeURIComponent(path)}`,
|
||||
body: {
|
||||
message: 'Uploaded by https://stackedit.io/',
|
||||
content: base64Encode(content),
|
||||
content: utils.encodeBase64(content),
|
||||
sha,
|
||||
branch,
|
||||
},
|
||||
|
@ -87,7 +84,7 @@ export default {
|
|||
})
|
||||
.then(res => ({
|
||||
sha: res.body.sha,
|
||||
content: base64Decode(res.body.content),
|
||||
content: utils.decodeBase64(res.body.content),
|
||||
}));
|
||||
},
|
||||
uploadGist(token, description, filename, content, isPublic, gistId) {
|
||||
|
|
|
@ -20,7 +20,7 @@ const libraries = ['picker'];
|
|||
const request = (token, options) => utils.request({
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers,
|
||||
...options.headers || {},
|
||||
Authorization: `Bearer ${token.accessToken}`,
|
||||
},
|
||||
});
|
||||
|
@ -106,7 +106,7 @@ export default {
|
|||
throw new Error('Client ID inconsistent.');
|
||||
}
|
||||
// Check the returned sub consistency
|
||||
if (sub && res.body.sub !== sub) {
|
||||
if (sub && `${res.body.sub}` !== sub) {
|
||||
throw new Error('Google account ID not expected.');
|
||||
}
|
||||
// Build token object including scopes and sub
|
||||
|
@ -114,7 +114,7 @@ export default {
|
|||
scopes,
|
||||
accessToken: data.accessToken,
|
||||
expiresOn: Date.now() + (data.expiresIn * 1000),
|
||||
sub: res.body.sub,
|
||||
sub: `${res.body.sub}`,
|
||||
isLogin: !store.getters['data/loginToken'] &&
|
||||
scopes.indexOf('https://www.googleapis.com/auth/drive.appdata') !== -1,
|
||||
isDrive: scopes.indexOf('https://www.googleapis.com/auth/drive') !== -1 ||
|
||||
|
@ -168,8 +168,11 @@ export default {
|
|||
// Try to get a new token in background
|
||||
return this.startOauth2(mergedScopes, sub, true)
|
||||
// If it fails try to popup a window
|
||||
.catch(() => utils.checkOnline()
|
||||
.then(() => this.startOauth2(mergedScopes, sub)));
|
||||
.catch(() => utils.checkOnline() // Check that we are online, silent mode is a hack
|
||||
.then(() => store.dispatch('modal/providerRedirection', {
|
||||
providerName: 'Google',
|
||||
onResolve: () => this.startOauth2(mergedScopes, sub),
|
||||
})));
|
||||
});
|
||||
},
|
||||
loadClientScript() {
|
||||
|
|
97
src/services/providers/helpers/wordpressHelper.js
Normal file
97
src/services/providers/helpers/wordpressHelper.js
Normal file
|
@ -0,0 +1,97 @@
|
|||
import utils from '../../utils';
|
||||
import store from '../../../store';
|
||||
|
||||
const clientId = '23361';
|
||||
const tokenExpirationMargin = 5 * 60 * 1000; // 5 min (WordPress tokens expire after 2 weeks)
|
||||
|
||||
const request = (token, options) => utils.request({
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers || {},
|
||||
Authorization: `Bearer ${token.accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
startOauth2(sub = null, silent = false) {
|
||||
return utils.startOauth2(
|
||||
'https://public-api.wordpress.com/oauth2/authorize', {
|
||||
client_id: clientId,
|
||||
response_type: 'token',
|
||||
scope: 'global',
|
||||
}, silent)
|
||||
// Call the user info endpoint
|
||||
.then(data => request({ accessToken: data.accessToken }, {
|
||||
url: 'https://public-api.wordpress.com/rest/v1.1/me',
|
||||
})
|
||||
.then((res) => {
|
||||
// Check the returned sub consistency
|
||||
if (sub && `${res.body.ID}` !== sub) {
|
||||
throw new Error('WordPress account ID not expected.');
|
||||
}
|
||||
// Build token object including scopes and sub
|
||||
const token = {
|
||||
accessToken: data.accessToken,
|
||||
expiresOn: Date.now() + (data.expiresIn * 1000),
|
||||
name: res.body.display_name,
|
||||
sub: `${res.body.ID}`,
|
||||
};
|
||||
// Add token to wordpressTokens
|
||||
store.dispatch('data/setWordpressToken', token);
|
||||
return token;
|
||||
}));
|
||||
},
|
||||
refreshToken(token) {
|
||||
const sub = token.sub;
|
||||
const lastToken = store.getters['data/wordpressTokens'][sub];
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
if (lastToken.expiresOn > Date.now() + tokenExpirationMargin) {
|
||||
return lastToken;
|
||||
}
|
||||
// Existing token is going to expire.
|
||||
// Try to get a new token in background
|
||||
return store.dispatch('modal/providerRedirection', {
|
||||
providerName: 'WordPress',
|
||||
onResolve: () => this.startOauth2(sub),
|
||||
});
|
||||
});
|
||||
},
|
||||
addAccount(fullAccess = false) {
|
||||
return this.startOauth2(fullAccess);
|
||||
},
|
||||
uploadPost(
|
||||
token,
|
||||
domain,
|
||||
siteId,
|
||||
postId,
|
||||
title,
|
||||
content,
|
||||
tags,
|
||||
categories,
|
||||
excerpt,
|
||||
author,
|
||||
featuredImage,
|
||||
status,
|
||||
date,
|
||||
) {
|
||||
return this.refreshToken(token)
|
||||
.then(refreshedToken => request(refreshedToken, {
|
||||
method: 'POST',
|
||||
url: `https://public-api.wordpress.com/rest/v1.2/sites/${siteId || domain}/posts/${postId || 'new'}`,
|
||||
body: {
|
||||
content,
|
||||
title,
|
||||
tags,
|
||||
categories,
|
||||
excerpt,
|
||||
author,
|
||||
featured_image: featuredImage || '',
|
||||
status,
|
||||
date: date && date.toISOString(),
|
||||
},
|
||||
})
|
||||
.then(res => res.body));
|
||||
},
|
||||
};
|
88
src/services/providers/helpers/zendeskHelper.js
Normal file
88
src/services/providers/helpers/zendeskHelper.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
import utils from '../../utils';
|
||||
import store from '../../../store';
|
||||
|
||||
const request = (token, options) => utils.request({
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers || {},
|
||||
Authorization: `Bearer ${token.accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
startOauth2(subdomain, clientId, sub = null, silent = false) {
|
||||
return utils.startOauth2(
|
||||
`https://${subdomain}.zendesk.com/oauth/authorizations/new`, {
|
||||
client_id: clientId,
|
||||
response_type: 'token',
|
||||
scope: 'read hc:write',
|
||||
}, silent)
|
||||
// Call the user info endpoint
|
||||
.then(({ accessToken }) => request({ accessToken }, {
|
||||
url: `https://${subdomain}.zendesk.com/api/v2/users/me.json`,
|
||||
})
|
||||
.then((res) => {
|
||||
const uniqueSub = `${subdomain}/${res.body.user.id}`;
|
||||
// Check the returned sub consistency
|
||||
if (sub && uniqueSub !== sub) {
|
||||
throw new Error('Zendesk account ID not expected.');
|
||||
}
|
||||
// Build token object including scopes and sub
|
||||
const token = {
|
||||
accessToken,
|
||||
name: res.body.user.name,
|
||||
subdomain,
|
||||
sub: uniqueSub,
|
||||
};
|
||||
// Add token to zendeskTokens
|
||||
store.dispatch('data/setZendeskToken', token);
|
||||
return token;
|
||||
}));
|
||||
},
|
||||
addAccount(subdomain, clientId) {
|
||||
return this.startOauth2(subdomain, clientId);
|
||||
},
|
||||
uploadArticle(
|
||||
token,
|
||||
sectionId,
|
||||
articleId,
|
||||
title,
|
||||
content,
|
||||
labels,
|
||||
locale,
|
||||
isDraft,
|
||||
) {
|
||||
const article = {
|
||||
title,
|
||||
body: content,
|
||||
locale,
|
||||
draft: isDraft,
|
||||
};
|
||||
if (articleId) {
|
||||
return request(token, {
|
||||
method: 'PUT',
|
||||
url: `https://${token.subdomain}.zendesk.com/api/v2/help_center/articles/${articleId}/translations/${locale}.json`,
|
||||
body: { translation: article },
|
||||
})
|
||||
.then(() => labels && request(token, {
|
||||
method: 'PUT',
|
||||
url: `https://${token.subdomain}.zendesk.com/api/v2/help_center/articles/${articleId}.json`,
|
||||
body: {
|
||||
article: {
|
||||
label_names: labels,
|
||||
},
|
||||
},
|
||||
}))
|
||||
.then(() => articleId);
|
||||
}
|
||||
if (labels) {
|
||||
article.label_names = labels;
|
||||
}
|
||||
return request(token, {
|
||||
method: 'POST',
|
||||
url: `https://${token.subdomain}.zendesk.com/api/v2/help_center/sections/${sectionId}/articles.json`,
|
||||
body: { article },
|
||||
})
|
||||
.then(res => `${res.body.article.id}`);
|
||||
},
|
||||
};
|
|
@ -1,13 +1,9 @@
|
|||
import emptyContent from '../../data/emptyContent';
|
||||
import store from '../../store';
|
||||
import utils from '../utils';
|
||||
|
||||
const dataExtractor = /<!--stackedit_data:([A-Za-z0-9+/=\s]+)-->$/;
|
||||
|
||||
// https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|
||||
const b64Encode = str => btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
|
||||
(match, p1) => String.fromCharCode(`0x${p1}`)));
|
||||
const b64Decode = str => decodeURIComponent(atob(str).split('').map(
|
||||
c => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`).join(''));
|
||||
|
||||
export default {
|
||||
serializeContent(content) {
|
||||
let result = content.text;
|
||||
|
@ -25,20 +21,20 @@ export default {
|
|||
data.history = content.history;
|
||||
}
|
||||
if (Object.keys(data).length) {
|
||||
const serializedData = b64Encode(JSON.stringify(data)).replace(/(.{50})/g, '$1\n');
|
||||
const serializedData = utils.encodeBase64(JSON.stringify(data)).replace(/(.{50})/g, '$1\n');
|
||||
result += `<!--stackedit_data:\n${serializedData}\n-->`;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
parseContent(serializedContent) {
|
||||
const result = emptyContent();
|
||||
parseContent(serializedContent, syncLocation) {
|
||||
const result = utils.deepCopy(store.state.content.itemMap[`${syncLocation.fileId}/content`]) || emptyContent();
|
||||
result.text = serializedContent;
|
||||
result.history = [];
|
||||
const extractedData = dataExtractor.exec(serializedContent);
|
||||
if (extractedData) {
|
||||
try {
|
||||
const serializedData = extractedData[1].replace(/\s/g, '');
|
||||
Object.assign(result, JSON.parse(b64Decode(serializedData)));
|
||||
Object.assign(result, JSON.parse(utils.decodeBase64(serializedData)));
|
||||
result.text = serializedContent.slice(0, extractedData.index);
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
|
|
50
src/services/providers/wordpressProvider.js
Normal file
50
src/services/providers/wordpressProvider.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
import store from '../../store';
|
||||
import wordpressHelper from './helpers/wordpressHelper';
|
||||
import providerRegistry from './providerRegistry';
|
||||
|
||||
export default providerRegistry.register({
|
||||
id: 'wordpress',
|
||||
getToken(location) {
|
||||
return store.getters['data/wordpressTokens'][location.sub];
|
||||
},
|
||||
getUrl(location) {
|
||||
return `https://wordpress.com/post/${location.siteId}/${location.postId}`;
|
||||
},
|
||||
getDescription(location) {
|
||||
const token = this.getToken(location);
|
||||
return `${location.postId} — ${location.domain} — ${token.name}`;
|
||||
},
|
||||
publish(token, html, metadata, publishLocation) {
|
||||
return wordpressHelper.uploadPost(
|
||||
token,
|
||||
publishLocation.domain,
|
||||
publishLocation.siteId,
|
||||
publishLocation.postId,
|
||||
metadata.title,
|
||||
html,
|
||||
metadata.tags,
|
||||
metadata.categories,
|
||||
metadata.excerpt,
|
||||
metadata.author,
|
||||
metadata.featuredImage,
|
||||
metadata.status,
|
||||
metadata.date,
|
||||
)
|
||||
.then(post => ({
|
||||
...publishLocation,
|
||||
siteId: `${post.site_ID}`,
|
||||
postId: `${post.ID}`,
|
||||
}));
|
||||
},
|
||||
makeLocation(token, domain, postId) {
|
||||
const location = {
|
||||
providerId: this.id,
|
||||
sub: token.sub,
|
||||
domain,
|
||||
};
|
||||
if (postId) {
|
||||
location.postId = postId;
|
||||
}
|
||||
return location;
|
||||
},
|
||||
});
|
46
src/services/providers/zendeskProvider.js
Normal file
46
src/services/providers/zendeskProvider.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
import store from '../../store';
|
||||
import zendeskHelper from './helpers/zendeskHelper';
|
||||
import providerRegistry from './providerRegistry';
|
||||
|
||||
export default providerRegistry.register({
|
||||
id: 'zendesk',
|
||||
getToken(location) {
|
||||
return store.getters['data/zendeskTokens'][location.sub];
|
||||
},
|
||||
getUrl(location) {
|
||||
const token = this.getToken(location);
|
||||
return `https://${token.subdomain}.zendesk.com/hc/${location.locale}/articles/${location.articleId}`;
|
||||
},
|
||||
getDescription(location) {
|
||||
const token = this.getToken(location);
|
||||
return `${location.articleId} — ${token.name} — ${token.subdomain}`;
|
||||
},
|
||||
publish(token, html, metadata, publishLocation) {
|
||||
return zendeskHelper.uploadArticle(
|
||||
token,
|
||||
publishLocation.sectionId,
|
||||
publishLocation.articleId,
|
||||
metadata.title,
|
||||
html,
|
||||
metadata.tags,
|
||||
publishLocation.locale,
|
||||
metadata.status === 'draft',
|
||||
)
|
||||
.then(articleId => ({
|
||||
...publishLocation,
|
||||
articleId,
|
||||
}));
|
||||
},
|
||||
makeLocation(token, sectionId, locale, articleId) {
|
||||
const location = {
|
||||
providerId: this.id,
|
||||
sub: token.sub,
|
||||
sectionId,
|
||||
locale,
|
||||
};
|
||||
if (articleId) {
|
||||
location.articleId = articleId;
|
||||
}
|
||||
return location;
|
||||
},
|
||||
});
|
|
@ -18,7 +18,7 @@ const ensureArray = (value) => {
|
|||
return [];
|
||||
}
|
||||
if (!Array.isArray(value)) {
|
||||
return value.toString().trim().split(/\s*,\s*/);
|
||||
return `${value}`.trim().split(/\s*,\s*/);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
@ -27,14 +27,14 @@ const ensureString = (value, defaultValue) => {
|
|||
if (!value) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value.toString();
|
||||
return `${value}`;
|
||||
};
|
||||
|
||||
const ensureDate = (value, defaultValue) => {
|
||||
if (!value) {
|
||||
return defaultValue;
|
||||
}
|
||||
return new Date(value.toString());
|
||||
return new Date(`${value}`);
|
||||
};
|
||||
|
||||
function publish(publishLocation) {
|
||||
|
@ -55,7 +55,7 @@ function publish(publishLocation) {
|
|||
excerpt: ensureString(properties.excerpt),
|
||||
featuredImage: ensureString(properties.featuredImage),
|
||||
status: ensureString(properties.status),
|
||||
date: ensureDate(properties.date),
|
||||
date: ensureDate(properties.date, new Date()),
|
||||
};
|
||||
return provider.publish(token, html, metadata, publishLocation);
|
||||
}));
|
||||
|
|
|
@ -64,6 +64,7 @@ const loadSyncedContent = loader('syncedContent');
|
|||
const loadContentState = loader('contentState');
|
||||
|
||||
function applyChanges(changes) {
|
||||
const token = mainProvider.getToken();
|
||||
const storeItemMap = { ...store.getters.allItemMap };
|
||||
const syncData = { ...store.getters['data/syncData'] };
|
||||
let syncDataChanged = false;
|
||||
|
@ -79,7 +80,10 @@ function applyChanges(changes) {
|
|||
}
|
||||
delete syncData[change.fileId];
|
||||
syncDataChanged = true;
|
||||
} else if (!change.removed && change.item && change.item.hash) {
|
||||
} else if (!change.removed && change.item && change.item.hash && (
|
||||
// Ignore items that belong to another user (like settings)
|
||||
!change.item.sub || change.item.sub === token.sub
|
||||
)) {
|
||||
if (!existingSyncData || (existingSyncData.hash !== change.item.hash && (
|
||||
!existingItem || existingItem.hash !== change.item.hash
|
||||
))) {
|
||||
|
@ -202,7 +206,7 @@ function syncFile(fileId) {
|
|||
...mergedContent,
|
||||
});
|
||||
|
||||
// Retrieve content with new `hash` value and freeze it
|
||||
// Retrieve content with new `hash` and freeze it
|
||||
mergedContent = utils.deepCopy(getContent());
|
||||
|
||||
// Make merged content history
|
||||
|
@ -303,13 +307,76 @@ function syncFile(fileId) {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
function syncDataItem(dataId) {
|
||||
const item = store.state.data.itemMap[dataId];
|
||||
const syncData = store.getters['data/syncDataByItemId'][dataId];
|
||||
// Sync if item hash and syncData hash are inconsistent
|
||||
if (syncData && item && item.hash === syncData.hash) {
|
||||
return null;
|
||||
}
|
||||
const token = mainProvider.getToken();
|
||||
return token && mainProvider.downloadData(token, dataId)
|
||||
.then((serverItem = null) => {
|
||||
const dataSyncData = store.getters['data/dataSyncData'][dataId];
|
||||
let mergedItem = (() => {
|
||||
const clientItem = utils.deepCopy(store.getters[`data/${dataId}`]);
|
||||
if (!serverItem) {
|
||||
return clientItem;
|
||||
}
|
||||
if (!dataSyncData) {
|
||||
return serverItem;
|
||||
}
|
||||
if (dataSyncData.hash !== serverItem.hash) {
|
||||
// Server version has changed
|
||||
if (dataSyncData.hash !== clientItem.hash && typeof clientItem.data === 'object') {
|
||||
// Client version has changed as well, merge data objects
|
||||
return {
|
||||
...clientItem,
|
||||
data: diffUtils.mergeObjects(serverItem.data, clientItem.data),
|
||||
};
|
||||
}
|
||||
return serverItem;
|
||||
}
|
||||
return clientItem;
|
||||
})();
|
||||
|
||||
// Update item in store
|
||||
store.commit('data/setItem', {
|
||||
id: dataId,
|
||||
...mergedItem,
|
||||
});
|
||||
|
||||
// Retrieve item with new `hash` and freeze it
|
||||
mergedItem = utils.deepCopy(store.state.data.itemMap[dataId]);
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
if (serverItem && serverItem.hash === mergedItem.hash) {
|
||||
return null;
|
||||
}
|
||||
return mainProvider.uploadData(
|
||||
token,
|
||||
dataId === 'settings' ? token.sub : undefined,
|
||||
mergedItem,
|
||||
dataId,
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
store.dispatch('data/patchDataSyncData', {
|
||||
[dataId]: utils.deepCopy(store.getters['data/syncDataByItemId'][dataId]),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sync() {
|
||||
const googleToken = store.getters['data/loginToken'];
|
||||
return mainProvider.getChanges(googleToken)
|
||||
const mainToken = store.getters['data/loginToken'];
|
||||
return mainProvider.getChanges(mainToken)
|
||||
.then((changes) => {
|
||||
// Apply changes
|
||||
applyChanges(changes);
|
||||
mainProvider.setAppliedChanges(googleToken, changes);
|
||||
mainProvider.setAppliedChanges(mainToken, changes);
|
||||
|
||||
// Prevent from sending items too long after changes have been retrieved
|
||||
const syncStartTime = Date.now();
|
||||
|
@ -327,7 +394,7 @@ function sync() {
|
|||
...store.state.folder.itemMap,
|
||||
...store.state.syncLocation.itemMap,
|
||||
...store.state.publishLocation.itemMap,
|
||||
// Deal with contents later
|
||||
// Deal with contents and data later
|
||||
};
|
||||
const syncDataByItemId = store.getters['data/syncDataByItemId'];
|
||||
let result;
|
||||
|
@ -336,7 +403,7 @@ function sync() {
|
|||
const existingSyncData = syncDataByItemId[id];
|
||||
if (!existingSyncData || existingSyncData.hash !== item.hash) {
|
||||
result = mainProvider.saveItem(
|
||||
googleToken,
|
||||
mainToken,
|
||||
// Use deepCopy to freeze objects
|
||||
utils.deepCopy(item),
|
||||
utils.deepCopy(existingSyncData),
|
||||
|
@ -360,6 +427,7 @@ function sync() {
|
|||
...store.state.syncLocation.itemMap,
|
||||
...store.state.publishLocation.itemMap,
|
||||
...store.state.content.itemMap,
|
||||
...store.state.data.itemMap,
|
||||
};
|
||||
const syncData = store.getters['data/syncData'];
|
||||
let result;
|
||||
|
@ -372,7 +440,7 @@ function sync() {
|
|||
// Use deepCopy to freeze objects
|
||||
const syncDataToRemove = utils.deepCopy(existingSyncData);
|
||||
result = mainProvider
|
||||
.removeItem(googleToken, syncDataToRemove, ifNotTooLate)
|
||||
.removeItem(mainToken, syncDataToRemove, ifNotTooLate)
|
||||
.then(() => {
|
||||
const syncDataCopy = { ...store.getters['data/syncData'] };
|
||||
delete syncDataCopy[syncDataToRemove.id];
|
||||
|
@ -393,7 +461,7 @@ function sync() {
|
|||
const loadedContent = store.state.content.itemMap[contentId];
|
||||
const hash = loadedContent ? loadedContent.hash : localDbSvc.hashMap.content[contentId];
|
||||
const syncData = store.getters['data/syncDataByItemId'][contentId];
|
||||
// Sync if item hash and syncData hash are different
|
||||
// Sync if item hash and syncData hash are inconsistent
|
||||
if (!syncData || hash !== syncData.hash) {
|
||||
[fileId] = contentId.split('/');
|
||||
}
|
||||
|
@ -411,10 +479,13 @@ function sync() {
|
|||
return Promise.resolve()
|
||||
.then(() => saveNextItem())
|
||||
.then(() => removeNextItem())
|
||||
.then(() => syncDataItem('settings'))
|
||||
.then(() => syncDataItem('templates'))
|
||||
.then(() => {
|
||||
if (store.getters['content/current'].id) {
|
||||
const currentFileId = store.getters['content/current'].id;
|
||||
if (currentFileId) {
|
||||
// Sync current file first
|
||||
return syncFile(store.getters['file/current'].id)
|
||||
return syncFile(currentFileId)
|
||||
.then(() => syncNextFile());
|
||||
}
|
||||
return syncNextFile();
|
||||
|
|
|
@ -91,8 +91,8 @@ self.onmessage = (evt) => {
|
|||
const context = evt.data[1];
|
||||
safeEval(evt.data[2]);
|
||||
self.postMessage([null, template(context)]);
|
||||
} catch (e) {
|
||||
self.postMessage([e.toString()]);
|
||||
} catch (err) {
|
||||
self.postMessage([`${err}`]);
|
||||
}
|
||||
close();
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@ import yaml from 'js-yaml';
|
|||
import defaultProperties from '../data/defaultFileProperties.yml';
|
||||
|
||||
const workspaceId = 'main';
|
||||
const origin = `${location.protocol}//${location.host}`;
|
||||
|
||||
// For uid()
|
||||
const uidLength = 16;
|
||||
|
@ -37,9 +38,16 @@ const urlParser = window.document.createElement('a');
|
|||
// For loadScript()
|
||||
const scriptLoadingPromises = Object.create(null);
|
||||
|
||||
// For startOauth2
|
||||
const oauth2AuthorizationTimeout = 120 * 1000; // 2 minutes
|
||||
|
||||
// For checkOnline
|
||||
const checkOnlineTimeout = 15 * 1000; // 15 sec
|
||||
|
||||
export default {
|
||||
workspaceId,
|
||||
origin: `${location.protocol}//${location.host}`,
|
||||
origin,
|
||||
oauth2RedirectUri: `${origin}/oauth2/callback`,
|
||||
types: [
|
||||
'contentState',
|
||||
'syncedContent',
|
||||
|
@ -79,6 +87,14 @@ export default {
|
|||
}
|
||||
return hash;
|
||||
},
|
||||
encodeBase64(str) {
|
||||
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
|
||||
(match, p1) => String.fromCharCode(`0x${p1}`)));
|
||||
},
|
||||
decodeBase64(str) {
|
||||
return decodeURIComponent(atob(str).split('').map(
|
||||
c => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`).join(''));
|
||||
},
|
||||
computeProperties(yamlProperties) {
|
||||
const customProperties = yaml.safeLoad(yamlProperties);
|
||||
const properties = yaml.safeLoad(defaultProperties);
|
||||
|
@ -147,14 +163,16 @@ export default {
|
|||
// Build the authorize URL
|
||||
const state = this.uid();
|
||||
params.state = state;
|
||||
params.redirect_uri = `${this.origin}/oauth2/callback.html`;
|
||||
params.redirect_uri = this.oauth2RedirectUri;
|
||||
const authorizeUrl = this.addQueryParams(url, params);
|
||||
if (silent) {
|
||||
// Use an iframe as wnd for silent mode
|
||||
oauth2Context.iframeElt = document.createElement('iframe');
|
||||
oauth2Context.iframeElt.style.position = 'absolute';
|
||||
oauth2Context.iframeElt.style.left = '-9999px';
|
||||
oauth2Context.closeTimeout = setTimeout(() => oauth2Context.clean('Unknown error.'), 5 * 1000);
|
||||
oauth2Context.closeTimeout = setTimeout(
|
||||
() => oauth2Context.clean('Unknown error.'),
|
||||
checkOnlineTimeout);
|
||||
oauth2Context.iframeElt.onerror = () => oauth2Context.clean('Unknown error.');
|
||||
oauth2Context.iframeElt.src = authorizeUrl;
|
||||
document.body.appendChild(oauth2Context.iframeElt);
|
||||
|
@ -166,7 +184,9 @@ export default {
|
|||
if (!oauth2Context.wnd) {
|
||||
return Promise.reject();
|
||||
}
|
||||
oauth2Context.closeTimeout = setTimeout(() => oauth2Context.clean('Timeout.'), 120 * 1000);
|
||||
oauth2Context.closeTimeout = setTimeout(
|
||||
() => oauth2Context.clean('Timeout.'),
|
||||
oauth2AuthorizationTimeout);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
oauth2Context.clean = (errorMsg) => {
|
||||
|
@ -189,32 +209,43 @@ export default {
|
|||
|
||||
oauth2Context.msgHandler = (event) => {
|
||||
if (event.source === oauth2Context.wnd &&
|
||||
event.origin === this.origin &&
|
||||
event.data &&
|
||||
event.data.state === state
|
||||
event.origin === this.origin
|
||||
) {
|
||||
oauth2Context.clean();
|
||||
if (event.data.accessToken || event.data.code) {
|
||||
resolve(event.data);
|
||||
} else {
|
||||
reject(event.data);
|
||||
const data = {};
|
||||
`${event.data}`.slice(1).split('&').forEach((param) => {
|
||||
const [key, value] = param.split('=').map(decodeURIComponent);
|
||||
if (key === 'state') {
|
||||
data.state = value;
|
||||
} else if (key === 'access_token') {
|
||||
data.accessToken = value;
|
||||
} else if (key === 'code') {
|
||||
data.code = value;
|
||||
} else if (key === 'expires_in') {
|
||||
data.expiresIn = value;
|
||||
}
|
||||
});
|
||||
if (data.state === state) {
|
||||
resolve(data);
|
||||
return;
|
||||
}
|
||||
reject('Could not get required authorization.');
|
||||
}
|
||||
};
|
||||
window.addEventListener('message', oauth2Context.msgHandler);
|
||||
|
||||
oauth2Context.checkClosedInterval = !silent && setInterval(() => {
|
||||
if (oauth2Context.wnd.closed) {
|
||||
oauth2Context.clean('Authorize window was closed');
|
||||
oauth2Context.clean('Authorize window was closed.');
|
||||
}
|
||||
}, 250);
|
||||
});
|
||||
},
|
||||
request(configParam) {
|
||||
const timeout = 30 * 1000; // 30 sec
|
||||
let retryAfter = 500; // 500 ms
|
||||
const maxRetryAfter = 30 * 1000; // 30 sec
|
||||
const config = Object.assign({}, configParam);
|
||||
config.timeout = config.timeout || 30 * 1000; // 30 sec
|
||||
config.headers = Object.assign({}, config.headers);
|
||||
if (config.body && typeof config.body === 'object') {
|
||||
config.body = JSON.stringify(config.body);
|
||||
|
@ -283,13 +314,15 @@ export default {
|
|||
timeoutId = setTimeout(() => {
|
||||
xhr.abort();
|
||||
reject(new Error('Network request timeout.'));
|
||||
}, timeout);
|
||||
}, config.timeout);
|
||||
|
||||
// Add query params to URL
|
||||
const url = this.addQueryParams(config.url, config.params);
|
||||
xhr.open(config.method || 'GET', url);
|
||||
Object.keys(config.headers).forEach((key) => {
|
||||
xhr.setRequestHeader(key, config.headers[key]);
|
||||
const value = config.headers[key];
|
||||
if (value) {
|
||||
xhr.setRequestHeader(key, `${value}`);
|
||||
}
|
||||
});
|
||||
xhr.send(config.body || null);
|
||||
})
|
||||
|
@ -310,23 +343,15 @@ export default {
|
|||
return attempt();
|
||||
},
|
||||
checkOnline() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
let timeout;
|
||||
const cleaner = (cb, res) => () => {
|
||||
clearTimeout(timeout);
|
||||
cb(res);
|
||||
document.head.removeChild(script);
|
||||
};
|
||||
script.onload = cleaner(resolve, 'Online.');
|
||||
script.onerror = cleaner(reject, 'Offline.');
|
||||
script.src = `https://apis.google.com/js/api.js?${Date.now()}`;
|
||||
try {
|
||||
document.head.appendChild(script); // This can fail with bad network
|
||||
timeout = setTimeout(cleaner(reject), 15000); // 15 sec
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
const checkStatus = (res) => {
|
||||
if (!res.status || res.status < 200) {
|
||||
throw new Error('Offline...');
|
||||
}
|
||||
});
|
||||
};
|
||||
return this.request({
|
||||
url: 'https://www.googleapis.com/plus/v1/people/me',
|
||||
timeout: checkOnlineTimeout,
|
||||
})
|
||||
.then(checkStatus, checkStatus);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -24,17 +24,21 @@ const module = moduleTemplate(empty, true);
|
|||
|
||||
module.mutations.setItem = (state, value) => {
|
||||
const emptyItem = empty(value.id);
|
||||
let itemData = value.data || emptyItem.data;
|
||||
if (typeof itemData === 'object') {
|
||||
itemData = Object.assign(emptyItem.data, value.data);
|
||||
}
|
||||
const data = typeof value.data === 'object'
|
||||
? Object.assign(emptyItem.data, value.data)
|
||||
: value.data;
|
||||
const item = {
|
||||
...emptyItem,
|
||||
...value,
|
||||
data: itemData,
|
||||
data,
|
||||
hash: Date.now(),
|
||||
};
|
||||
if (!item.hash) {
|
||||
item.hash = Date.now();
|
||||
if (item.id === 'settings' || item.id === 'templates') {
|
||||
// Use a real hash for synced types
|
||||
item.hash = utils.hash(utils.serializeObject({
|
||||
...item,
|
||||
hash: undefined,
|
||||
}));
|
||||
}
|
||||
Vue.set(state.itemMap, item.id, item);
|
||||
};
|
||||
|
@ -42,13 +46,13 @@ module.mutations.setItem = (state, value) => {
|
|||
const getter = id => state => (state.itemMap[id] || empty(id)).data;
|
||||
const setter = id => ({ commit }, data) => commit('setItem', itemTemplate(id, data));
|
||||
const patcher = id => ({ state, commit }, data) => {
|
||||
const item = state.itemMap[id] || empty(id);
|
||||
commit('patchOrSetItem', {
|
||||
...item,
|
||||
data: {
|
||||
const item = Object.assign(empty(id), state.itemMap[id]);
|
||||
commit('setItem', {
|
||||
...empty(id),
|
||||
data: typeof data === 'object' ? {
|
||||
...item.data,
|
||||
...data,
|
||||
},
|
||||
} : data,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -172,11 +176,17 @@ module.getters.syncDataByType = (state, getters) => {
|
|||
module.actions.patchSyncData = patcher('syncData');
|
||||
module.actions.setSyncData = setter('syncData');
|
||||
|
||||
// Data sync data (used to sync settings and settings)
|
||||
module.getters.dataSyncData = getter('dataSyncData');
|
||||
module.actions.patchDataSyncData = patcher('dataSyncData');
|
||||
|
||||
// Tokens
|
||||
module.getters.tokens = getter('tokens');
|
||||
module.getters.googleTokens = (state, getters) => getters.tokens.google || {};
|
||||
module.getters.dropboxTokens = (state, getters) => getters.tokens.dropbox || {};
|
||||
module.getters.githubTokens = (state, getters) => getters.tokens.github || {};
|
||||
module.getters.wordpressTokens = (state, getters) => getters.tokens.wordpress || {};
|
||||
module.getters.zendeskTokens = (state, getters) => getters.tokens.zendesk || {};
|
||||
module.getters.loginToken = (state, getters) => {
|
||||
// Return the first google token that has the isLogin flag
|
||||
const googleTokens = getters.googleTokens;
|
||||
|
@ -185,29 +195,18 @@ module.getters.loginToken = (state, getters) => {
|
|||
return googleTokens[loginSubs[0]];
|
||||
};
|
||||
module.actions.patchTokens = patcher('tokens');
|
||||
module.actions.setGoogleToken = ({ getters, dispatch }, token) => {
|
||||
const tokenSetter = providerId => ({ getters, dispatch }, token) => {
|
||||
dispatch('patchTokens', {
|
||||
google: {
|
||||
...getters.googleTokens,
|
||||
[token.sub]: token,
|
||||
},
|
||||
});
|
||||
};
|
||||
module.actions.setDropboxToken = ({ getters, dispatch }, token) => {
|
||||
dispatch('patchTokens', {
|
||||
dropbox: {
|
||||
...getters.dropboxTokens,
|
||||
[token.sub]: token,
|
||||
},
|
||||
});
|
||||
};
|
||||
module.actions.setGithubToken = ({ getters, dispatch }, token) => {
|
||||
dispatch('patchTokens', {
|
||||
github: {
|
||||
...getters.githubTokens,
|
||||
[providerId]: {
|
||||
...getters[`${providerId}Tokens`],
|
||||
[token.sub]: token,
|
||||
},
|
||||
});
|
||||
};
|
||||
module.actions.setGoogleToken = tokenSetter('google');
|
||||
module.actions.setDropboxToken = tokenSetter('dropbox');
|
||||
module.actions.setGithubToken = tokenSetter('github');
|
||||
module.actions.setWordpressToken = tokenSetter('wordpress');
|
||||
module.actions.setZendeskToken = tokenSetter('zendesk');
|
||||
|
||||
export default module;
|
||||
|
|
|
@ -5,7 +5,7 @@ const editorTopPadding = 10;
|
|||
const navigationBarEditButtonsWidth = 36 * 12; // 12 buttons
|
||||
const navigationBarLeftButtonWidth = 38 + 4 + 15;
|
||||
const navigationBarRightButtonWidth = 38 + 8;
|
||||
const navigationBarSpinnerWidth = 18 + 15 + 5; // 5 for left margin
|
||||
const navigationBarSpinnerWidth = 22 + 8 + 5; // 5 for left margin
|
||||
const navigationBarLocationWidth = 20;
|
||||
const navigationBarSyncPublishButtonsWidth = 36 + 10;
|
||||
const navigationBarTitleMargin = 8;
|
||||
|
|
|
@ -21,11 +21,13 @@ export default {
|
|||
const config = typeof param === 'object' ? { ...param } : { type: param };
|
||||
const clean = () => commit('setStack', state.stack.filter((otherConfig => otherConfig !== config)));
|
||||
config.resolve = (result) => {
|
||||
if (config.onResolve) {
|
||||
config.onResolve(result);
|
||||
}
|
||||
clean();
|
||||
resolve(result);
|
||||
if (config.onResolve) {
|
||||
config.onResolve(result)
|
||||
.then(res => resolve(res));
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
};
|
||||
config.reject = (error) => {
|
||||
clean();
|
||||
|
@ -63,5 +65,11 @@ export default {
|
|||
resolveText: 'Yes, clean',
|
||||
rejectText: 'No',
|
||||
}),
|
||||
providerRedirection: ({ dispatch }, { providerName, onResolve }) => dispatch('open', {
|
||||
content: `<p>You are about to navigate to the <b>${providerName}</b> authorization page.</p>`,
|
||||
resolveText: 'Ok, go on!',
|
||||
rejectText: 'Cancel',
|
||||
onResolve,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -38,11 +38,6 @@ export default (empty, simpleHash = false) => {
|
|||
mutations: {
|
||||
setItem,
|
||||
patchItem,
|
||||
patchOrSetItem(state, patch) {
|
||||
if (!patchItem(state, patch)) {
|
||||
setItem(state, patch);
|
||||
}
|
||||
},
|
||||
deleteItem(state, id) {
|
||||
Vue.delete(state.itemMap, id);
|
||||
},
|
||||
|
|
|
@ -35,7 +35,7 @@ export default {
|
|||
item.content = `HTTP error ${error.status}.`;
|
||||
}
|
||||
} else {
|
||||
item.content = error.toString();
|
||||
item.content = `${error}`;
|
||||
}
|
||||
}
|
||||
if (!item.content || item.content === '[object Object]') {
|
||||
|
|
|
@ -10,7 +10,8 @@ module.getters = {
|
|||
const result = {};
|
||||
getters.items.forEach((item) => {
|
||||
// Filter items that we can't use
|
||||
if (providerRegistry.providers[item.providerId].getToken(item)) {
|
||||
const provider = providerRegistry.providers[item.providerId];
|
||||
if (provider && provider.getToken(item)) {
|
||||
const list = result[item.fileId] || [];
|
||||
list.push(item);
|
||||
result[item.fileId] = list;
|
||||
|
|
|
@ -20,6 +20,10 @@ export default {
|
|||
},
|
||||
actions: {
|
||||
enqueue({ state, commit, dispatch }, cb) {
|
||||
if (state.offline) {
|
||||
// No need to enqueue
|
||||
return;
|
||||
}
|
||||
const checkOffline = () => {
|
||||
if (state.offline) {
|
||||
// Empty queue
|
||||
|
|
|
@ -10,7 +10,8 @@ module.getters = {
|
|||
const result = {};
|
||||
getters.items.forEach((item) => {
|
||||
// Filter items that we can't use
|
||||
if (providerRegistry.providers[item.providerId].getToken(item)) {
|
||||
const provider = providerRegistry.providers[item.providerId];
|
||||
if (provider && provider.getToken(item)) {
|
||||
const list = result[item.fileId] || [];
|
||||
list.push(item);
|
||||
result[item.fileId] = list;
|
||||
|
|
|
@ -2,35 +2,8 @@
|
|||
<html>
|
||||
<body>
|
||||
<script>
|
||||
var state;
|
||||
var accessToken;
|
||||
var code;
|
||||
var expiresIn;
|
||||
function parse(search) {
|
||||
(search || '').slice(1).split('&').forEach(function (param) {
|
||||
var split = param.split('=');
|
||||
var key = decodeURIComponent(split.shift());
|
||||
var value = decodeURIComponent(split.join('='));
|
||||
if (key === 'state') {
|
||||
state = value;
|
||||
} else if (key === 'access_token') {
|
||||
accessToken = value;
|
||||
} else if (key === 'code') {
|
||||
code = value;
|
||||
} else if (key === 'expires_in') {
|
||||
expiresIn = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
parse(location.search);
|
||||
parse(location.hash);
|
||||
var origin = location.protocol + '//' + location.host;
|
||||
(window.opener || window.parent).postMessage({
|
||||
state: state,
|
||||
accessToken: accessToken,
|
||||
code: code,
|
||||
expiresIn: expiresIn
|
||||
}, origin);
|
||||
(window.opener || window.parent).postMessage(location.hash || location.search, origin);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
369
yarn.lock
369
yarn.lock
|
@ -188,6 +188,14 @@ asn1.js@^4.0.0:
|
|||
inherits "^2.0.1"
|
||||
minimalistic-assert "^1.0.0"
|
||||
|
||||
asn1@0.1.11:
|
||||
version "0.1.11"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.1.11.tgz#559be18376d08a4ec4dbe80877d27818639b2df7"
|
||||
|
||||
asn1@0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.1.tgz#ecc73f75d31ea3c6ed9d47428db35fecc7b2c6dc"
|
||||
|
||||
asn1@~0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
|
||||
|
@ -196,6 +204,10 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
|
|||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
||||
|
||||
assert-plus@^0.1.5:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.1.5.tgz#ee74009413002d84cec7219c6ac811812e723160"
|
||||
|
||||
assert-plus@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234"
|
||||
|
@ -224,6 +236,10 @@ async@^2.1.2, async@^2.1.5:
|
|||
dependencies:
|
||||
lodash "^4.14.0"
|
||||
|
||||
async@~0.9.0:
|
||||
version "0.9.2"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
|
@ -239,6 +255,10 @@ autoprefixer@^6.0.0, autoprefixer@^6.3.1, autoprefixer@^6.7.2:
|
|||
postcss "^5.2.16"
|
||||
postcss-value-parser "^3.2.3"
|
||||
|
||||
aws-sign2@~0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.5.0.tgz#c57103f7a17fc037f02d7c2e64b602ea223f7d63"
|
||||
|
||||
aws-sign2@~0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
|
||||
|
@ -887,6 +907,12 @@ boolbase@~1.0.0:
|
|||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
||||
|
||||
boom@0.4.x:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/boom/-/boom-0.4.2.tgz#7a636e9ded4efcefb19cef4947a3c67dfaee911b"
|
||||
dependencies:
|
||||
hoek "0.9.x"
|
||||
|
||||
boom@2.x.x:
|
||||
version "2.10.1"
|
||||
resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
|
||||
|
@ -905,6 +931,10 @@ boom@5.x.x:
|
|||
dependencies:
|
||||
hoek "4.x.x"
|
||||
|
||||
bower@^1.8.2:
|
||||
version "1.8.2"
|
||||
resolved "https://registry.yarnpkg.com/bower/-/bower-1.8.2.tgz#adf53529c8d4af02ef24fb8d5341c1419d33e2f7"
|
||||
|
||||
brace-expansion@^1.0.0:
|
||||
version "1.1.8"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
|
||||
|
@ -1020,6 +1050,10 @@ builtin-status-codes@^3.0.0:
|
|||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
|
||||
|
||||
bytes@2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.5.0.tgz#4c9423ea2d252c270c41b2bdefeff9bb6b62c06a"
|
||||
|
||||
caller-path@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
|
||||
|
@ -1303,6 +1337,12 @@ combined-stream@^1.0.5, combined-stream@~1.0.5:
|
|||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
combined-stream@~0.0.4:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-0.0.7.tgz#0137e657baa5a7541c57ac37ac5fc07d73b4dc1f"
|
||||
dependencies:
|
||||
delayed-stream "0.0.5"
|
||||
|
||||
commander@2.9.x, commander@^2.9.0, commander@~2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
|
||||
|
@ -1313,6 +1353,24 @@ commondir@^1.0.1:
|
|||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
|
||||
|
||||
compressible@~2.0.10:
|
||||
version "2.0.11"
|
||||
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.11.tgz#16718a75de283ed8e604041625a2064586797d8a"
|
||||
dependencies:
|
||||
mime-db ">= 1.29.0 < 2"
|
||||
|
||||
compression@^1.0.11, compression@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.0.tgz#030c9f198f1643a057d776a738e922da4373012d"
|
||||
dependencies:
|
||||
accepts "~1.3.3"
|
||||
bytes "2.5.0"
|
||||
compressible "~2.0.10"
|
||||
debug "2.6.8"
|
||||
on-headers "~1.0.1"
|
||||
safe-buffer "5.1.1"
|
||||
vary "~1.1.1"
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
|
@ -1453,6 +1511,12 @@ cross-spawn@^3.0.0:
|
|||
lru-cache "^4.0.1"
|
||||
which "^1.2.9"
|
||||
|
||||
cryptiles@0.2.x:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-0.2.2.tgz#ed91ff1f17ad13d3748288594f8a48a0d26f325c"
|
||||
dependencies:
|
||||
boom "0.4.x"
|
||||
|
||||
cryptiles@2.x.x:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
|
||||
|
@ -1592,6 +1656,10 @@ csso@~2.3.1:
|
|||
clap "^1.0.9"
|
||||
source-map "^0.5.3"
|
||||
|
||||
ctype@0.5.3:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/ctype/-/ctype-0.5.3.tgz#82c18c2461f74114ef16c135224ad0b9144ca12f"
|
||||
|
||||
currently-unhandled@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
|
||||
|
@ -1634,17 +1702,23 @@ debug@2.6.7:
|
|||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^2.1.1, debug@^2.2.0, debug@^2.6.0:
|
||||
debug@2.6.8, debug@^2.1.1, debug@^2.2.0, debug@^2.6.0:
|
||||
version "2.6.8"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
|
||||
deep-extend@~0.4.0:
|
||||
deep-extend@^0.4.0, deep-extend@~0.4.0:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
|
||||
|
||||
|
@ -1674,6 +1748,10 @@ del@^2.0.2:
|
|||
pinkie-promise "^2.0.0"
|
||||
rimraf "^2.2.8"
|
||||
|
||||
delayed-stream@0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-0.0.5.tgz#d4b1f43a93e8296dfe02694f4680bc37a313c73f"
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
|
@ -1690,6 +1768,10 @@ depd@1.1.0, depd@~1.1.0:
|
|||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3"
|
||||
|
||||
depd@1.1.1, depd@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
|
||||
|
||||
deprecated@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/deprecated/-/deprecated-0.0.1.tgz#f9c9af5464afa1e7a971458a8bdef2aa94d5bb19"
|
||||
|
@ -1846,10 +1928,18 @@ ee-first@1.1.1:
|
|||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
|
||||
ejs@^2.3.4:
|
||||
version "2.5.7"
|
||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a"
|
||||
|
||||
ejs@^2.5.6:
|
||||
version "2.5.6"
|
||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.6.tgz#479636bfa3fe3b1debd52087f0acb204b4f19c88"
|
||||
|
||||
ejs@~0.8.4:
|
||||
version "0.8.8"
|
||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-0.8.8.tgz#ffdc56dcc35d02926dd50ad13439bbc54061d598"
|
||||
|
||||
electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.11:
|
||||
version "1.3.13"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.13.tgz#1b3a5eace6e087bb5e257a100b0cbfe81b2891fc"
|
||||
|
@ -2152,6 +2242,10 @@ etag@~1.8.0:
|
|||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051"
|
||||
|
||||
etag@~1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||
|
||||
event-emitter@~0.3.5:
|
||||
version "0.3.5"
|
||||
resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
|
||||
|
@ -2211,7 +2305,7 @@ expand-tilde@^2.0.2:
|
|||
dependencies:
|
||||
homedir-polyfill "^1.0.1"
|
||||
|
||||
express@^4.14.1, express@^4.15.2:
|
||||
express@^4.15.2:
|
||||
version "4.15.3"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.15.3.tgz#bab65d0f03aa80c358408972fc700f916944b662"
|
||||
dependencies:
|
||||
|
@ -2244,6 +2338,39 @@ express@^4.14.1, express@^4.15.2:
|
|||
utils-merge "1.0.0"
|
||||
vary "~1.1.1"
|
||||
|
||||
express@^4.15.5, express@^4.8.5:
|
||||
version "4.15.5"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.15.5.tgz#670235ca9598890a5ae8170b83db722b842ed927"
|
||||
dependencies:
|
||||
accepts "~1.3.3"
|
||||
array-flatten "1.1.1"
|
||||
content-disposition "0.5.2"
|
||||
content-type "~1.0.2"
|
||||
cookie "0.3.1"
|
||||
cookie-signature "1.0.6"
|
||||
debug "2.6.9"
|
||||
depd "~1.1.1"
|
||||
encodeurl "~1.0.1"
|
||||
escape-html "~1.0.3"
|
||||
etag "~1.8.0"
|
||||
finalhandler "~1.0.6"
|
||||
fresh "0.5.2"
|
||||
merge-descriptors "1.0.1"
|
||||
methods "~1.1.2"
|
||||
on-finished "~2.3.0"
|
||||
parseurl "~1.3.1"
|
||||
path-to-regexp "0.1.7"
|
||||
proxy-addr "~1.1.5"
|
||||
qs "6.5.0"
|
||||
range-parser "~1.2.0"
|
||||
send "0.15.6"
|
||||
serve-static "1.12.6"
|
||||
setprototypeof "1.0.3"
|
||||
statuses "~1.3.1"
|
||||
type-is "~1.6.15"
|
||||
utils-merge "1.0.0"
|
||||
vary "~1.1.1"
|
||||
|
||||
extend@^3.0.0, extend@~3.0.0, extend@~3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
|
||||
|
@ -2340,6 +2467,18 @@ finalhandler@~1.0.3:
|
|||
statuses "~1.3.1"
|
||||
unpipe "~1.0.0"
|
||||
|
||||
finalhandler@~1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.6.tgz#007aea33d1a4d3e42017f624848ad58d212f814f"
|
||||
dependencies:
|
||||
debug "2.6.9"
|
||||
encodeurl "~1.0.1"
|
||||
escape-html "~1.0.3"
|
||||
on-finished "~2.3.0"
|
||||
parseurl "~1.3.2"
|
||||
statuses "~1.3.1"
|
||||
unpipe "~1.0.0"
|
||||
|
||||
find-cache-dir@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9"
|
||||
|
@ -2429,10 +2568,22 @@ for-own@^1.0.0:
|
|||
dependencies:
|
||||
for-in "^1.0.1"
|
||||
|
||||
forever-agent@~0.5.0:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.5.2.tgz#6d0e09c4921f94a27f63d3b49c5feff1ea4c5130"
|
||||
|
||||
forever-agent@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
|
||||
form-data@~0.1.0:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-0.1.4.tgz#91abd788aba9702b1aabfa8bc01031a2ac9e3b12"
|
||||
dependencies:
|
||||
async "~0.9.0"
|
||||
combined-stream "~0.0.4"
|
||||
mime "~1.2.11"
|
||||
|
||||
form-data@~2.1.1:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1"
|
||||
|
@ -2457,6 +2608,10 @@ fresh@0.5.0:
|
|||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e"
|
||||
|
||||
fresh@0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||
|
||||
friendly-errors-webpack-plugin@^1.1.3:
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.6.1.tgz#e32781c4722f546a06a9b5d7a7cfa28520375d70"
|
||||
|
@ -2868,6 +3023,15 @@ hash.js@^1.0.0, hash.js@^1.0.3:
|
|||
dependencies:
|
||||
inherits "^2.0.1"
|
||||
|
||||
hawk@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/hawk/-/hawk-1.1.1.tgz#87cd491f9b46e4e2aeaca335416766885d2d1ed9"
|
||||
dependencies:
|
||||
boom "0.4.x"
|
||||
cryptiles "0.2.x"
|
||||
hoek "0.9.x"
|
||||
sntp "0.2.x"
|
||||
|
||||
hawk@~3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
|
||||
|
@ -2898,6 +3062,10 @@ hmac-drbg@^1.0.0:
|
|||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
hoek@0.9.x:
|
||||
version "0.9.1"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-0.9.1.tgz#3d322462badf07716ea7eb85baf88079cddce505"
|
||||
|
||||
hoek@2.x.x:
|
||||
version "2.16.3"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
|
||||
|
@ -2988,6 +3156,15 @@ http-errors@~1.6.1:
|
|||
setprototypeof "1.0.3"
|
||||
statuses ">= 1.3.1 < 2"
|
||||
|
||||
http-errors@~1.6.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
|
||||
dependencies:
|
||||
depd "1.1.1"
|
||||
inherits "2.0.3"
|
||||
setprototypeof "1.0.3"
|
||||
statuses ">= 1.3.1 < 2"
|
||||
|
||||
http-proxy-middleware@^0.17.3:
|
||||
version "0.17.4"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833"
|
||||
|
@ -3004,6 +3181,14 @@ http-proxy@^1.16.2:
|
|||
eventemitter3 "1.x.x"
|
||||
requires-port "1.x.x"
|
||||
|
||||
http-signature@~0.10.0:
|
||||
version "0.10.1"
|
||||
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-0.10.1.tgz#4fbdac132559aa8323121e540779c0a012b27e66"
|
||||
dependencies:
|
||||
asn1 "0.1.11"
|
||||
assert-plus "^0.1.5"
|
||||
ctype "0.5.3"
|
||||
|
||||
http-signature@~1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
|
||||
|
@ -3038,6 +3223,10 @@ ieee754@^1.1.4:
|
|||
version "1.1.8"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
|
||||
|
||||
ignore-loader@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ignore-loader/-/ignore-loader-0.1.2.tgz#d81f240376d0ba4f0d778972c3ad25874117a463"
|
||||
|
||||
ignore@^3.2.0:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d"
|
||||
|
@ -3134,6 +3323,10 @@ ipaddr.js@1.3.0:
|
|||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec"
|
||||
|
||||
ipaddr.js@1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.4.0.tgz#296aca878a821816e5b85d0a285a99bcff4582f0"
|
||||
|
||||
irregular-plurals@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.2.0.tgz#38f299834ba8c00c30be9c554e137269752ff3ac"
|
||||
|
@ -3430,7 +3623,7 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
|
|||
dependencies:
|
||||
jsonify "~0.0.0"
|
||||
|
||||
json-stringify-safe@~5.0.1:
|
||||
json-stringify-safe@~5.0.0, json-stringify-safe@~5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||
|
||||
|
@ -3580,7 +3773,7 @@ loader-runner@^2.3.0:
|
|||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
|
||||
|
||||
loader-utils@^0.2.15, loader-utils@^0.2.16:
|
||||
loader-utils@0.2.x, loader-utils@^0.2.15, loader-utils@^0.2.16:
|
||||
version "0.2.17"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348"
|
||||
dependencies:
|
||||
|
@ -3929,20 +4122,24 @@ miller-rabin@^4.0.0:
|
|||
bn.js "^4.0.0"
|
||||
brorand "^1.0.1"
|
||||
|
||||
"mime-db@>= 1.29.0 < 2", mime-db@~1.30.0:
|
||||
version "1.30.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
|
||||
|
||||
mime-db@~1.27.0:
|
||||
version "1.27.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1"
|
||||
|
||||
mime-db@~1.30.0:
|
||||
version "1.30.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
|
||||
|
||||
mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7:
|
||||
version "2.1.15"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed"
|
||||
dependencies:
|
||||
mime-db "~1.27.0"
|
||||
|
||||
mime-types@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-1.0.2.tgz#995ae1392ab8affcbfcb2641dd054e943c0d5dce"
|
||||
|
||||
mime-types@~2.1.17:
|
||||
version "2.1.17"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a"
|
||||
|
@ -3957,6 +4154,10 @@ mime@1.3.x, mime@^1.3.4:
|
|||
version "1.3.6"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0"
|
||||
|
||||
mime@~1.2.11:
|
||||
version "1.2.11"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10"
|
||||
|
||||
mimic-fn@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
|
||||
|
@ -4193,6 +4394,10 @@ node-sass@^4.5.3:
|
|||
sass-graph "^2.1.1"
|
||||
stdout-stream "^1.4.0"
|
||||
|
||||
node-uuid@~1.4.0:
|
||||
version "1.4.8"
|
||||
resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907"
|
||||
|
||||
noop-fn@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/noop-fn/-/noop-fn-1.0.0.tgz#5f33d47f13d2150df93e0cb036699e982f78ffbf"
|
||||
|
@ -4269,6 +4474,10 @@ number-is-nan@^1.0.0:
|
|||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
||||
|
||||
oauth-sign@~0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.3.0.tgz#cb540f93bb2b22a7d5941691a288d60e8ea9386e"
|
||||
|
||||
oauth-sign@~0.8.1, oauth-sign@~0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
|
||||
|
@ -4307,12 +4516,26 @@ object.pick@^1.2.0:
|
|||
dependencies:
|
||||
isobject "^2.1.0"
|
||||
|
||||
offline-plugin@^4.8.4:
|
||||
version "4.8.4"
|
||||
resolved "https://registry.yarnpkg.com/offline-plugin/-/offline-plugin-4.8.4.tgz#1084c59f6606bded5ee5a6bf6208e2b9f5bdd339"
|
||||
dependencies:
|
||||
deep-extend "^0.4.0"
|
||||
ejs "^2.3.4"
|
||||
loader-utils "0.2.x"
|
||||
minimatch "^3.0.3"
|
||||
slash "^1.0.0"
|
||||
|
||||
on-finished@~2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
||||
dependencies:
|
||||
ee-first "1.1.1"
|
||||
|
||||
on-headers@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7"
|
||||
|
||||
once@^1.3.0, once@^1.3.3:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
|
@ -4483,6 +4706,10 @@ parseurl@~1.3.1:
|
|||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56"
|
||||
|
||||
parseurl@~1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
|
||||
|
||||
path-browserify@0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
|
||||
|
@ -4959,6 +5186,13 @@ proxy-addr@~1.1.4:
|
|||
forwarded "~0.1.0"
|
||||
ipaddr.js "1.3.0"
|
||||
|
||||
proxy-addr@~1.1.5:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.5.tgz#71c0ee3b102de3f202f3b64f608d173fcba1a918"
|
||||
dependencies:
|
||||
forwarded "~0.1.0"
|
||||
ipaddr.js "1.4.0"
|
||||
|
||||
prr@~0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
|
||||
|
@ -4993,6 +5227,14 @@ qs@6.4.0, qs@~6.4.0:
|
|||
version "6.4.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
|
||||
|
||||
qs@6.5.0:
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.0.tgz#8d04954d364def3efc55b5a0793e1e2c8b1e6e49"
|
||||
|
||||
qs@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-1.0.2.tgz#50a93e2b5af6691c31bcea5dae78ee6ea1903768"
|
||||
|
||||
qs@~6.5.1:
|
||||
version "6.5.1"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
|
||||
|
@ -5089,6 +5331,15 @@ readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0":
|
|||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
readable-stream@1.0.27-1:
|
||||
version "1.0.27-1"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.27-1.tgz#6b67983c20357cefd07f0165001a16d710d91078"
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
readable-stream@^1.0.33, readable-stream@~1.1.9:
|
||||
version "1.1.14"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
||||
|
@ -5309,6 +5560,25 @@ request@^2.82.0:
|
|||
tunnel-agent "^0.6.0"
|
||||
uuid "^3.1.0"
|
||||
|
||||
request@~2.40.0:
|
||||
version "2.40.0"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.40.0.tgz#4dd670f696f1e6e842e66b4b5e839301ab9beb67"
|
||||
dependencies:
|
||||
forever-agent "~0.5.0"
|
||||
json-stringify-safe "~5.0.0"
|
||||
mime-types "~1.0.1"
|
||||
node-uuid "~1.4.0"
|
||||
qs "~1.0.0"
|
||||
optionalDependencies:
|
||||
aws-sign2 "~0.5.0"
|
||||
form-data "~0.1.0"
|
||||
hawk "1.1.1"
|
||||
http-signature "~0.10.0"
|
||||
oauth-sign "~0.3.0"
|
||||
stringstream "~0.0.4"
|
||||
tough-cookie ">=0.12.0"
|
||||
tunnel-agent "~0.4.0"
|
||||
|
||||
require-directory@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
||||
|
@ -5396,14 +5666,14 @@ rx-lite@^3.1.2:
|
|||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
|
||||
|
||||
safe-buffer@5.1.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@~5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
|
||||
|
||||
safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
|
||||
|
||||
sass-graph@^2.1.1:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49"
|
||||
|
@ -5470,6 +5740,24 @@ send@0.15.3:
|
|||
range-parser "~1.2.0"
|
||||
statuses "~1.3.1"
|
||||
|
||||
send@0.15.6:
|
||||
version "0.15.6"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.15.6.tgz#20f23a9c925b762ab82705fe2f9db252ace47e34"
|
||||
dependencies:
|
||||
debug "2.6.9"
|
||||
depd "~1.1.1"
|
||||
destroy "~1.0.4"
|
||||
encodeurl "~1.0.1"
|
||||
escape-html "~1.0.3"
|
||||
etag "~1.8.1"
|
||||
fresh "0.5.2"
|
||||
http-errors "~1.6.2"
|
||||
mime "1.3.4"
|
||||
ms "2.0.0"
|
||||
on-finished "~2.3.0"
|
||||
range-parser "~1.2.0"
|
||||
statuses "~1.3.1"
|
||||
|
||||
sequencify@~0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/sequencify/-/sequencify-0.0.7.tgz#90cff19d02e07027fd767f5ead3e7b95d1e7380c"
|
||||
|
@ -5483,6 +5771,15 @@ serve-static@1.12.3:
|
|||
parseurl "~1.3.1"
|
||||
send "0.15.3"
|
||||
|
||||
serve-static@1.12.6, serve-static@^1.12.6, serve-static@^1.6.5:
|
||||
version "1.12.6"
|
||||
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.6.tgz#b973773f63449934da54e5beba5e31d9f4211577"
|
||||
dependencies:
|
||||
encodeurl "~1.0.1"
|
||||
escape-html "~1.0.3"
|
||||
parseurl "~1.3.2"
|
||||
send "0.15.6"
|
||||
|
||||
set-blocking@^2.0.0, set-blocking@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
|
@ -5538,6 +5835,12 @@ slice-ansi@0.0.4:
|
|||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
|
||||
|
||||
sntp@0.2.x:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sntp/-/sntp-0.2.4.tgz#fb885f18b0f3aad189f824862536bceeec750900"
|
||||
dependencies:
|
||||
hoek "0.9.x"
|
||||
|
||||
sntp@1.x.x:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
|
||||
|
@ -5619,6 +5922,14 @@ sqlite3@^3.1.3:
|
|||
nan "~2.4.0"
|
||||
node-pre-gyp "~0.6.31"
|
||||
|
||||
ssh2@^0.3.5:
|
||||
version "0.3.6"
|
||||
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.3.6.tgz#49034434aee3821ee5fc22b952081e7801ff92ed"
|
||||
dependencies:
|
||||
asn1 "0.2.1"
|
||||
readable-stream "1.0.27-1"
|
||||
streamsearch "0.1.2"
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.0.tgz#ff2a3e4fd04497555fed97b39a0fd82fafb3a33c"
|
||||
|
@ -5634,6 +5945,18 @@ sshpk@^1.7.0:
|
|||
jsbn "~0.1.0"
|
||||
tweetnacl "~0.14.0"
|
||||
|
||||
stackedit@^4.3.15:
|
||||
version "4.3.15"
|
||||
resolved "https://registry.yarnpkg.com/stackedit/-/stackedit-4.3.15.tgz#0fc3715cd93f6c93f761d797173d6869c0794009"
|
||||
dependencies:
|
||||
bower "^1.8.2"
|
||||
compression "^1.0.11"
|
||||
ejs "~0.8.4"
|
||||
express "^4.8.5"
|
||||
request "~2.40.0"
|
||||
serve-static "^1.6.5"
|
||||
ssh2 "^0.3.5"
|
||||
|
||||
stackframe@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.0.3.tgz#fe64ab20b170e4ce49044b126c119dfa0e5dc7cc"
|
||||
|
@ -5676,6 +5999,10 @@ stream-http@^2.3.1:
|
|||
to-arraybuffer "^1.0.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
streamsearch@0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
|
||||
|
||||
strict-uri-encode@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
|
||||
|
@ -5990,15 +6317,15 @@ toposort@^1.0.0:
|
|||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.3.tgz#f02cd8a74bd8be2fc0e98611c3bacb95a171869c"
|
||||
|
||||
tough-cookie@~2.3.0:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a"
|
||||
tough-cookie@>=0.12.0, tough-cookie@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561"
|
||||
dependencies:
|
||||
punycode "^1.4.1"
|
||||
|
||||
tough-cookie@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561"
|
||||
tough-cookie@~2.3.0:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a"
|
||||
dependencies:
|
||||
punycode "^1.4.1"
|
||||
|
||||
|
@ -6024,6 +6351,10 @@ tunnel-agent@^0.6.0:
|
|||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
tunnel-agent@~0.4.0:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
|
||||
|
||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||
version "0.14.5"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||
|
|
Loading…
Reference in New Issue
Block a user