Support for google drive actions
This commit is contained in:
parent
2f4781bc5d
commit
d0ea2f7850
@ -16,7 +16,9 @@ module.exports = {
|
||||
],
|
||||
globals: {
|
||||
"NODE_ENV": false,
|
||||
"VERSION": false
|
||||
"VERSION": false,
|
||||
"GOOGLE_CLIENT_ID": false,
|
||||
"GITHUB_CLIENT_ID": false
|
||||
},
|
||||
// check if imports actually resolve
|
||||
'settings': {
|
||||
|
@ -4,6 +4,9 @@ var config = require('../config')
|
||||
if (!process.env.NODE_ENV) {
|
||||
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
|
||||
}
|
||||
if (!process.env.GOOGLE_CLIENT_ID) {
|
||||
process.env.GOOGLE_CLIENT_ID = JSON.parse(config.dev.env.GOOGLE_CLIENT_ID)
|
||||
}
|
||||
|
||||
var opn = require('opn')
|
||||
var path = require('path')
|
||||
|
@ -19,7 +19,9 @@ module.exports = merge(baseWebpackConfig, {
|
||||
devtool: 'source-map',
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
NODE_ENV: config.dev.env.NODE_ENV
|
||||
NODE_ENV: config.dev.env.NODE_ENV,
|
||||
GOOGLE_CLIENT_ID: config.dev.env.GOOGLE_CLIENT_ID,
|
||||
GITHUB_CLIENT_ID: config.dev.env.GITHUB_CLIENT_ID
|
||||
}),
|
||||
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
|
@ -28,7 +28,9 @@ var webpackConfig = merge(baseWebpackConfig, {
|
||||
plugins: [
|
||||
// http://vuejs.github.io/vue-loader/en/workflow/production.html
|
||||
new webpack.DefinePlugin({
|
||||
NODE_ENV: env.NODE_ENV
|
||||
NODE_ENV: env.NODE_ENV,
|
||||
GOOGLE_CLIENT_ID: env.GOOGLE_CLIENT_ID,
|
||||
GITHUB_CLIENT_ID: env.GITHUB_CLIENT_ID
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
|
@ -2,5 +2,7 @@ var merge = require('webpack-merge')
|
||||
var prodEnv = require('./prod.env')
|
||||
|
||||
module.exports = merge(prodEnv, {
|
||||
NODE_ENV: '"development"'
|
||||
NODE_ENV: '"development"',
|
||||
GOOGLE_CLIENT_ID: '"241271498917-c3loeet001r90q6u79q484bsh5clg4fr.apps.googleusercontent.com"',
|
||||
GITHUB_CLIENT_ID: '"cbf0cf25cfd026be23e1"'
|
||||
})
|
||||
|
@ -33,6 +33,7 @@ module.exports = {
|
||||
// (https://github.com/webpack/css-loader#sourcemaps)
|
||||
// In our experience, they generally work as expected,
|
||||
// just be aware of this issue when enabling this option.
|
||||
cssSourceMap: false
|
||||
// cssSourceMap: false
|
||||
cssSourceMap: true
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
module.exports = {
|
||||
NODE_ENV: '"production"'
|
||||
NODE_ENV: '"production"',
|
||||
GOOGLE_CLIENT_ID: '"241271498917-t4t7d07qis7oc0ahaskbif3ft6tk63cd.apps.googleusercontent.com"',
|
||||
GITHUB_CLIENT_ID: '"30c1491057c9ad4dbd56"'
|
||||
}
|
||||
|
9
index.js
9
index.js
@ -1,4 +1,11 @@
|
||||
process.env.NODE_ENV = 'production';
|
||||
const env = require('./config/prod.env');
|
||||
|
||||
if (!process.env.NODE_ENV) {
|
||||
process.env.NODE_ENV = JSON.parse(env.NODE_ENV);
|
||||
}
|
||||
if (!process.env.GOOGLE_CLIENT_ID) {
|
||||
process.env.GOOGLE_CLIENT_ID = JSON.parse(env.GOOGLE_CLIENT_ID);
|
||||
}
|
||||
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
|
@ -44,8 +44,11 @@ module.exports = (app, serveV4) => {
|
||||
/* eslint-enable global-require, import/no-unresolved */
|
||||
}
|
||||
|
||||
// Serve callback.html in /app
|
||||
// Serve callback.html
|
||||
app.get('/oauth2/callback', (req, res) => res.sendFile(resolvePath('static/oauth2/callback.html')));
|
||||
// Google Drive action receiver
|
||||
app.get('/googleDriveAction', (req, res) =>
|
||||
res.redirect(`./app#providerId=googleDrive&state=${encodeURIComponent(req.query.state)}`));
|
||||
|
||||
// Serve static resources
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
|
@ -5,7 +5,7 @@ const verifier = require('google-id-token-verifier');
|
||||
const BUCKET_NAME = process.env.USER_BUCKET_NAME || 'stackedit-users';
|
||||
const PAYPAL_URI = process.env.PAYPAL_URI || 'https://www.paypal.com/cgi-bin/webscr';
|
||||
const PAYPAL_RECEIVER_EMAIL = process.env.PAYPAL_RECEIVER_EMAIL || 'stackedit.project@gmail.com';
|
||||
const GOOGLE_CLIENT_ID = '241271498917-t4t7d07qis7oc0ahaskbif3ft6tk63cd.apps.googleusercontent.com';
|
||||
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
|
||||
const s3Client = new AWS.S3();
|
||||
|
||||
const cb = (resolve, reject) => (err, res) => {
|
||||
|
@ -158,7 +158,7 @@ export default {
|
||||
|
||||
.side-bar__info {
|
||||
padding: 10px;
|
||||
margin: 0 -10px;
|
||||
margin: -10px -10px 0;
|
||||
background-color: $info-bg;
|
||||
|
||||
p {
|
||||
|
@ -88,6 +88,7 @@ export default {
|
||||
|
||||
<style lang="scss">
|
||||
.toc__inner {
|
||||
position: relative;
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
cursor: pointer;
|
||||
font-size: 9px;
|
||||
@ -147,7 +148,7 @@ export default {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
@ -230,9 +230,9 @@ textarea {
|
||||
color: rgba(0, 0, 0, 0.33);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
padding: 1px;
|
||||
padding: 2px 3px 2px 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
height: 21px;
|
||||
line-height: 1;
|
||||
|
||||
&:active,
|
||||
|
@ -101,16 +101,18 @@ export default {
|
||||
start, end,
|
||||
}));
|
||||
|
||||
const isSticky = this.$el.parentNode.classList.contains('sticky-comment');
|
||||
const isVisible = () => isSticky || this.$store.state.discussion.stickyComment === null;
|
||||
|
||||
this.$watch(
|
||||
() => this.$store.state.discussion.currentDiscussionId,
|
||||
() => this.$nextTick(() => {
|
||||
if (this.$store.state.discussion.newCommentFocus) {
|
||||
if (isVisible() && this.$store.state.discussion.newCommentFocus) {
|
||||
clEditor.focus();
|
||||
}
|
||||
}),
|
||||
{ immediate: true });
|
||||
|
||||
const isSticky = this.$el.parentNode.classList.contains('sticky-comment');
|
||||
if (isSticky) {
|
||||
let scrollerMirrorElt;
|
||||
const getScrollerMirrorElt = () => {
|
||||
@ -128,10 +130,10 @@ export default {
|
||||
} else {
|
||||
// Maintain the state with the sticky comment
|
||||
this.$watch(
|
||||
() => this.$store.state.discussion.stickyComment === null,
|
||||
(isVisible) => {
|
||||
clEditor.toggleEditable(isVisible);
|
||||
if (isVisible) {
|
||||
() => isVisible(),
|
||||
(visible) => {
|
||||
clEditor.toggleEditable(visible);
|
||||
if (visible) {
|
||||
const text = this.$store.state.discussion.newCommentText;
|
||||
clEditor.setContent(text);
|
||||
const selection = this.$store.state.discussion.newCommentSelection;
|
||||
|
@ -1,19 +1,14 @@
|
||||
<template>
|
||||
<div class="side-bar__panel side-bar__panel--menu">
|
||||
<div v-if="!loginToken">
|
||||
<div class="menu-info-entries" v-if="!loginToken">
|
||||
<div class="menu-entry menu-entry--info flex flex--row flex--align-center">
|
||||
<div class="menu-entry__icon menu-entry__icon--disabled">
|
||||
<icon-sync-off></icon-sync-off>
|
||||
</div>
|
||||
<span><b>{{currentWorkspace.name}}</b> not synced.</span>
|
||||
</div>
|
||||
<menu-entry @click.native="signin">
|
||||
<icon-login slot="icon"></icon-login>
|
||||
<div>Sign in with Google</div>
|
||||
<span>Back up and sync all your files, folders and settings.</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="menu-info-entries" v-else>
|
||||
<div class="menu-entry menu-entry--info flex flex--row flex--align-center">
|
||||
<div class="menu-entry__icon menu-entry__icon--image">
|
||||
<user-image :user-id="loginToken.sub"></user-image>
|
||||
@ -27,6 +22,11 @@
|
||||
<span><b>{{currentWorkspace.name}}</b> synced.</span>
|
||||
</div>
|
||||
</div>
|
||||
<menu-entry v-if="!loginToken" @click.native="signin">
|
||||
<icon-login slot="icon"></icon-login>
|
||||
<div>Sign in with Google</div>
|
||||
<span>Back up and sync your main workspace.</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="setPanel('workspaces')">
|
||||
<icon-database slot="icon"></icon-database>
|
||||
<div>Workspaces</div>
|
||||
|
@ -30,18 +30,14 @@
|
||||
</div>
|
||||
</label>
|
||||
<hr>
|
||||
<menu-entry @click.native="about">
|
||||
<icon-help-circle slot="icon"></icon-help-circle>
|
||||
<span>About StackEdit</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="welcomeFile">
|
||||
<icon-file slot="icon"></icon-file>
|
||||
<span>Welcome file</span>
|
||||
</menu-entry>
|
||||
<menu-entry href="editor" target="_blank">
|
||||
<icon-open-in-new slot="icon"></icon-open-in-new>
|
||||
<span>StackEdit 4 (deprecated)</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="about">
|
||||
<icon-help-circle slot="icon"></icon-help-circle>
|
||||
<span>About StackEdit</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -49,7 +45,6 @@
|
||||
import MenuEntry from './common/MenuEntry';
|
||||
import backupSvc from '../../services/backupSvc';
|
||||
import utils from '../../services/utils';
|
||||
import welcomeFile from '../../data/welcomeFile.md';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -104,13 +99,6 @@ export default {
|
||||
location.reload();
|
||||
});
|
||||
},
|
||||
welcomeFile() {
|
||||
return this.$store.dispatch('createFile', {
|
||||
name: 'Welcome file',
|
||||
text: welcomeFile,
|
||||
})
|
||||
.then(createdFile => this.$store.commit('file/setCurrentId', createdFile.id));
|
||||
},
|
||||
about() {
|
||||
return this.$store.dispatch('modal/open', 'about');
|
||||
},
|
||||
|
@ -42,10 +42,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
.menu-info-entries {
|
||||
padding: 10px;
|
||||
margin: -10px -10px 10px;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.menu-entry--info {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
margin: 10px 0;
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
.menu-entry__icon {
|
||||
|
@ -36,8 +36,8 @@ export default modalTemplate({
|
||||
}),
|
||||
computed: {
|
||||
googlePhotosTokens() {
|
||||
const googleToken = this.$store.getters['data/googleTokens'];
|
||||
return Object.entries(googleToken)
|
||||
const googleTokens = this.$store.getters['data/googleTokens'];
|
||||
return Object.entries(googleTokens)
|
||||
.filter(([, token]) => token.isPhotos)
|
||||
.sort(([, token1], [, token2]) => token1.name.localeCompare(token2.name));
|
||||
},
|
||||
|
@ -18,7 +18,7 @@
|
||||
<button class="workspace-entry__button button" @click="edit(id)">
|
||||
<icon-pen></icon-pen>
|
||||
</button>
|
||||
<button class="workspace-entry__button button" v-if="workspace !== currentWorkspace && workspace !== mainWorkspace" @click="remove(id)">
|
||||
<button class="workspace-entry__button button" v-if="id !== currentWorkspace.id && id !== mainWorkspace.id" @click="remove(id)">
|
||||
<icon-delete></icon-delete>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
export default () => ({
|
||||
main: {
|
||||
name: 'Main workspace',
|
||||
// The rest will be filled by the data/workspaces getter
|
||||
// The rest will be filled by the data/sanitizedWorkspaces getter
|
||||
},
|
||||
});
|
||||
|
@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
|
||||
<path d="M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20C4,21.1 4.9,22 6,22H18C19.1,22 20,21.1 20,20V8L14,2H6Z" />
|
||||
</svg>
|
||||
</template>
|
@ -40,7 +40,6 @@ import Information from './Information';
|
||||
import Alert from './Alert';
|
||||
import SignalOff from './SignalOff';
|
||||
import Folder from './Folder';
|
||||
import File from './File';
|
||||
import ScrollSync from './ScrollSync';
|
||||
import Printer from './Printer';
|
||||
import Undo from './Undo';
|
||||
@ -91,7 +90,6 @@ Vue.component('iconInformation', Information);
|
||||
Vue.component('iconAlert', Alert);
|
||||
Vue.component('iconSignalOff', SignalOff);
|
||||
Vue.component('iconFolder', Folder);
|
||||
Vue.component('iconFile', File);
|
||||
Vue.component('iconScrollSync', ScrollSync);
|
||||
Vue.component('iconPrinter', Printer);
|
||||
Vue.component('iconUndo', Undo);
|
||||
|
@ -158,12 +158,18 @@ function mergeContent(serverContent, clientContent, lastMergedContent = {}) {
|
||||
const serverText = makePatchableText(serverContent, markerKeys, markerIdxMap);
|
||||
const clientText = makePatchableText(clientContent, markerKeys, markerIdxMap);
|
||||
const isServerTextChanges = lastMergedText !== serverText;
|
||||
const isClientTextChanges = lastMergedText !== clientText;
|
||||
const isTextSynchronized = serverText === clientText;
|
||||
let text = clientText;
|
||||
if (!isTextSynchronized && isServerTextChanges) {
|
||||
text = serverText;
|
||||
if (isClientTextChanges) {
|
||||
text = mergeText(serverText, clientText, lastMergedText);
|
||||
}
|
||||
}
|
||||
|
||||
const result = {
|
||||
text: isTextSynchronized || !isServerTextChanges
|
||||
? clientText
|
||||
: mergeText(serverText, clientText, lastMergedText),
|
||||
text,
|
||||
properties: mergeValues(
|
||||
serverContent.properties,
|
||||
clientContent.properties,
|
||||
|
@ -6,6 +6,7 @@ import welcomeFile from '../data/welcomeFile.md';
|
||||
const dbVersion = 1;
|
||||
const dbStoreName = 'objects';
|
||||
const exportWorkspace = utils.queryParams.exportWorkspace;
|
||||
const resetApp = utils.queryParams.reset;
|
||||
const deleteMarkerMaxAge = 1000;
|
||||
const checkSponsorshipAfter = (5 * 60 * 1000) + (30 * 1000); // tokenExpirationMargin + 30 sec
|
||||
|
||||
@ -105,7 +106,7 @@ const localDbSvc = {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
// Reset the app if reset flag was passed
|
||||
if (utils.queryParams.reset) {
|
||||
if (resetApp) {
|
||||
return Promise.all(
|
||||
Object.keys(store.getters['data/workspaces'])
|
||||
.map(workspaceId => localDbSvc.removeWorkspace(workspaceId)),
|
||||
@ -115,7 +116,7 @@ const localDbSvc = {
|
||||
localStorage.removeItem(`data/${id}`);
|
||||
}))
|
||||
.then(() => {
|
||||
location.replace(utils.resolveUrl('app'));
|
||||
location.reload();
|
||||
throw new Error('reload');
|
||||
});
|
||||
}
|
||||
@ -177,33 +178,29 @@ const localDbSvc = {
|
||||
// Sync local DB periodically
|
||||
utils.setInterval(() => localDbSvc.sync(), 1000);
|
||||
|
||||
const ifNoId = cb => (obj) => {
|
||||
if (obj.id) {
|
||||
return obj;
|
||||
}
|
||||
return cb();
|
||||
};
|
||||
|
||||
// watch current file changing
|
||||
store.watch(
|
||||
() => store.getters['file/current'].id,
|
||||
() => Promise.resolve(store.getters['file/current'])
|
||||
() => {
|
||||
// See if currentFile is real, ie it has an ID
|
||||
const currentFile = store.getters['file/current'];
|
||||
// If current file has no ID, get the most recent file
|
||||
.then(ifNoId(() => store.getters['file/lastOpened']))
|
||||
// If still no ID, create a new file
|
||||
.then(ifNoId(() => store.dispatch('createFile', {
|
||||
name: 'Welcome file',
|
||||
text: welcomeFile,
|
||||
})))
|
||||
.then((currentFile) => {
|
||||
// Fix current file ID
|
||||
if (store.getters['file/current'].id !== currentFile.id) {
|
||||
store.commit('file/setCurrentId', currentFile.id);
|
||||
// Wait for the next watch tick
|
||||
return null;
|
||||
if (!currentFile.id) {
|
||||
const recentFile = store.getters['file/lastOpened'];
|
||||
// Set it as the current file
|
||||
if (recentFile.id) {
|
||||
store.commit('file/setCurrentId', recentFile.id);
|
||||
} else {
|
||||
// If still no ID, create a new file
|
||||
store.dispatch('createFile', {
|
||||
name: 'Welcome file',
|
||||
text: welcomeFile,
|
||||
})
|
||||
// Set it as the current file
|
||||
.then(newFile => store.commit('file/setCurrentId', newFile.id));
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
Promise.resolve()
|
||||
// Load contentState from DB
|
||||
.then(() => localDbSvc.loadContentState(currentFile.id))
|
||||
// Load syncedContent from DB
|
||||
@ -226,13 +223,13 @@ const localDbSvc = {
|
||||
store.commit('file/setCurrentId', lastOpenedFile.id);
|
||||
throw err;
|
||||
},
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
store.dispatch('notification/error', err);
|
||||
}),
|
||||
{
|
||||
)
|
||||
.catch((err) => {
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
store.dispatch('notification/error', err);
|
||||
});
|
||||
}
|
||||
}, {
|
||||
immediate: true,
|
||||
});
|
||||
});
|
||||
|
@ -99,11 +99,14 @@ function enterKeyHandler(evt, state) {
|
||||
}
|
||||
|
||||
evt.preventDefault();
|
||||
const lf = state.before.lastIndexOf('\n') + 1;
|
||||
const previousLine = state.before.slice(lf);
|
||||
const indentMatch = previousLine.match(indentRegexp) || [''];
|
||||
|
||||
// Get the last line before the selection
|
||||
const lastLf = state.before.lastIndexOf('\n') + 1;
|
||||
const lastLine = state.before.slice(lastLf);
|
||||
// See if the line is indented
|
||||
const indentMatch = lastLine.match(indentRegexp) || [''];
|
||||
if (clearNewline && !state.selection && state.before.length === lastSelection) {
|
||||
state.before = state.before.substring(0, lf);
|
||||
state.before = state.before.substring(0, lastLf);
|
||||
state.selection = '';
|
||||
clearNewline = false;
|
||||
fixNumberedList(state, indentMatch[1]);
|
||||
@ -135,13 +138,14 @@ function tabKeyHandler(evt, state) {
|
||||
|
||||
evt.preventDefault();
|
||||
const isInverse = evt.shiftKey;
|
||||
const lf = state.before.lastIndexOf('\n') + 1;
|
||||
const previousLine = state.before.slice(lf) + state.selection + state.after;
|
||||
const indentMatch = previousLine.match(indentRegexp);
|
||||
const lastLf = state.before.lastIndexOf('\n') + 1;
|
||||
const lastLine = state.before.slice(lastLf);
|
||||
const currentLine = lastLine + state.selection + state.after;
|
||||
const indentMatch = currentLine.match(indentRegexp);
|
||||
if (isInverse) {
|
||||
const previousChar = state.before.slice(-1);
|
||||
if (/\s/.test(state.before.charAt(lf))) {
|
||||
state.before = strSplice(state.before, lf, 1);
|
||||
if (/\s/.test(state.before.charAt(lastLf))) {
|
||||
state.before = strSplice(state.before, lastLf, 1);
|
||||
if (indentMatch) {
|
||||
fixNumberedList(state, indentMatch[1]);
|
||||
if (indentMatch[1]) {
|
||||
@ -154,8 +158,13 @@ function tabKeyHandler(evt, state) {
|
||||
if (previousChar) {
|
||||
state.selection = state.selection.slice(1);
|
||||
}
|
||||
} else if (state.selection || indentMatch) {
|
||||
state.before = strSplice(state.before, lf, 0, '\t');
|
||||
} else if (
|
||||
// If selection is not empty
|
||||
state.selection
|
||||
// Or we are in an indented paragraph and the cursor is over the indentation characters
|
||||
|| (indentMatch && indentMatch[0].length >= lastLine.length)
|
||||
) {
|
||||
state.before = strSplice(state.before, lastLf, 0, '\t');
|
||||
state.selection = state.selection.replace(/\n(?=.)/g, '\n\t');
|
||||
if (indentMatch) {
|
||||
fixNumberedList(state, indentMatch[1]);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import store from '../../store';
|
||||
import googleHelper from './helpers/googleHelper';
|
||||
import providerRegistry from './providerRegistry';
|
||||
import utils from '../utils';
|
||||
|
||||
export default providerRegistry.register({
|
||||
id: 'googleDriveAppData',
|
||||
@ -8,8 +9,14 @@ export default providerRegistry.register({
|
||||
return store.getters['workspace/syncToken'];
|
||||
},
|
||||
initWorkspace() {
|
||||
// Nothing to do since the main workspace isn't necessarily synchronized
|
||||
return Promise.resolve(store.getters['data/workspaces'].main);
|
||||
// Nothing much to do since the main workspace isn't necessarily synchronized
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
// Remove the URL hash
|
||||
utils.setQueryParams();
|
||||
// Return the main workspace
|
||||
return store.getters['data/workspaces'].main;
|
||||
});
|
||||
},
|
||||
getChanges() {
|
||||
const syncToken = store.getters['workspace/syncToken'];
|
||||
|
@ -17,6 +17,94 @@ export default providerRegistry.register({
|
||||
const token = this.getToken(location);
|
||||
return `${location.driveFileId} — ${token.name}`;
|
||||
},
|
||||
initAction() {
|
||||
const state = googleHelper.driveState || {};
|
||||
return state.userId && Promise.resolve()
|
||||
.then(() => {
|
||||
// Try to find the token corresponding to the user ID
|
||||
const token = store.getters['data/googleTokens'][state.userId];
|
||||
// If not found or not enough permission, popup an OAuth2 window
|
||||
return token && token.isDrive ? token : store.dispatch('modal/open', {
|
||||
type: 'googleDriveAccount',
|
||||
onResolve: () => googleHelper.addDriveAccount(
|
||||
!store.getters['data/localSettings'].googleDriveRestrictedAccess,
|
||||
state.userId,
|
||||
),
|
||||
});
|
||||
})
|
||||
.then((token) => {
|
||||
switch (state.action) {
|
||||
case 'create':
|
||||
default:
|
||||
// See if folder is part of a workspace we can open
|
||||
return googleHelper.getFile(token, state.folderId)
|
||||
.then((folder) => {
|
||||
folder.appProperties = folder.appProperties || {};
|
||||
googleHelper.driveActionFolder = folder;
|
||||
if (folder.appProperties.folderId) {
|
||||
// Change current URL to workspace URL
|
||||
utils.setQueryParams({
|
||||
providerId: 'googleDriveWorkspace',
|
||||
folderId: folder.appProperties.folderId,
|
||||
sub: state.userId,
|
||||
});
|
||||
}
|
||||
}, (err) => {
|
||||
if (!err || err.status !== 404) {
|
||||
throw err;
|
||||
}
|
||||
// We received a 404 error meaning we have no permission to read the folder
|
||||
googleHelper.driveActionFolder = { id: state.folderId };
|
||||
});
|
||||
|
||||
case 'open': {
|
||||
const getOneFile = (ids) => {
|
||||
const id = ids.shift();
|
||||
return id && googleHelper.getFile(token, id)
|
||||
.then((file) => {
|
||||
file.appProperties = file.appProperties || {};
|
||||
googleHelper.driveActionFiles.push(file);
|
||||
return getOneFile(ids);
|
||||
});
|
||||
};
|
||||
|
||||
return getOneFile(state.ids || [])
|
||||
.then(() => {
|
||||
// Check if first file is part of a workspace
|
||||
const firstFile = googleHelper.driveActionFiles[0];
|
||||
if (firstFile && firstFile.appProperties && firstFile.appProperties.folderId) {
|
||||
// Change current URL to workspace URL
|
||||
utils.setQueryParams({
|
||||
providerId: 'googleDriveWorkspace',
|
||||
folderId: firstFile.appProperties.folderId,
|
||||
sub: state.userId,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
performAction() {
|
||||
const state = googleHelper.driveState || {};
|
||||
const token = store.getters['data/googleTokens'][state.userId];
|
||||
return token && Promise.resolve()
|
||||
.then(() => {
|
||||
switch (state.action) {
|
||||
case 'create':
|
||||
default:
|
||||
return store.dispatch('createFile')
|
||||
.then((file) => {
|
||||
store.commit('file/setCurrentId', file.id);
|
||||
// Return a new syncLocation
|
||||
return this.makeLocation(token, null, googleHelper.driveActionFolder.id);
|
||||
});
|
||||
case 'open':
|
||||
return store.dispatch('queue/enqueue',
|
||||
() => this.openFiles(token, googleHelper.driveActionFiles));
|
||||
}
|
||||
});
|
||||
},
|
||||
downloadContent(token, syncLocation) {
|
||||
return googleHelper.downloadFile(token, syncLocation.driveFileId)
|
||||
.then(content => providerUtils.parseContent(content, syncLocation));
|
||||
@ -60,7 +148,7 @@ export default providerRegistry.register({
|
||||
},
|
||||
openFiles(token, driveFiles) {
|
||||
const openOneFile = () => {
|
||||
const driveFile = driveFiles.pop();
|
||||
const driveFile = driveFiles.shift();
|
||||
if (!driveFile) {
|
||||
return null;
|
||||
}
|
||||
|
@ -4,18 +4,24 @@ import providerRegistry from './providerRegistry';
|
||||
import providerUtils from './providerUtils';
|
||||
import utils from '../utils';
|
||||
|
||||
let fileIdToOpen;
|
||||
|
||||
export default providerRegistry.register({
|
||||
id: 'googleDriveWorkspace',
|
||||
getToken() {
|
||||
return store.getters['workspace/syncToken'];
|
||||
},
|
||||
initWorkspace() {
|
||||
const makeWorkspaceId = folderId => folderId && Math.abs(utils.hash(utils.serializeObject({
|
||||
const makeWorkspaceIdParams = folderId => ({
|
||||
providerId: this.id,
|
||||
folderId,
|
||||
}))).toString(36);
|
||||
});
|
||||
|
||||
const getWorkspace = folderId => store.getters['data/workspaces'][makeWorkspaceId(folderId)];
|
||||
const makeWorkspaceId = folderId => folderId && utils.makeWorkspaceId(
|
||||
makeWorkspaceIdParams(folderId));
|
||||
|
||||
const getWorkspace = folderId =>
|
||||
store.getters['data/sanitizedWorkspaces'][makeWorkspaceId(folderId)];
|
||||
|
||||
const initFolder = (token, folder) => Promise.resolve({
|
||||
folderId: folder.id,
|
||||
@ -78,12 +84,6 @@ export default providerRegistry.register({
|
||||
.then(() => properties);
|
||||
})
|
||||
.then((properties) => {
|
||||
// Fix the current url hash
|
||||
const hash = `#providerId=${this.id}&folderId=${folder.id}`;
|
||||
if (location.hash !== hash) {
|
||||
location.hash = hash;
|
||||
}
|
||||
|
||||
// Update workspace in the store
|
||||
const workspaceId = makeWorkspaceId(folder.id);
|
||||
store.dispatch('data/patchWorkspaces', {
|
||||
@ -92,7 +92,7 @@ export default providerRegistry.register({
|
||||
sub: token.sub,
|
||||
name: folder.name,
|
||||
providerId: this.id,
|
||||
url: utils.resolveUrl(hash),
|
||||
url: location.href,
|
||||
folderId: folder.id,
|
||||
dataFolderId: properties.dataFolderId,
|
||||
trashFolderId: properties.trashFolderId,
|
||||
@ -103,9 +103,9 @@ export default providerRegistry.register({
|
||||
return getWorkspace(folder.id);
|
||||
});
|
||||
|
||||
const workspace = getWorkspace(utils.queryParams.folderId);
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
const workspace = getWorkspace(utils.queryParams.folderId);
|
||||
// See if we already have a token
|
||||
const googleTokens = store.getters['data/googleTokens'];
|
||||
// Token sub is in the workspace or in the url if workspace is about to be created
|
||||
@ -115,7 +115,7 @@ export default providerRegistry.register({
|
||||
}
|
||||
// If no token has been found, popup an authorize window and get one
|
||||
return store.dispatch('modal/workspaceGoogleRedirection', {
|
||||
onResolve: () => googleHelper.addDriveAccount(true),
|
||||
onResolve: () => googleHelper.addDriveAccount(true, utils.queryParams.sub),
|
||||
});
|
||||
})
|
||||
.then(token => Promise.resolve()
|
||||
@ -139,12 +139,70 @@ export default providerRegistry.register({
|
||||
folder.appProperties = folder.appProperties || {};
|
||||
const folderIdProperty = folder.appProperties.folderId;
|
||||
if (folderIdProperty && folderIdProperty !== folderId) {
|
||||
throw new Error(`Google Drive folder ${folderId} is part of another workspace.`);
|
||||
throw new Error(`Folder ${folderId} is part of another workspace.`);
|
||||
}
|
||||
return initFolder(token, folder);
|
||||
}, () => {
|
||||
throw new Error(`Folder ${folderId} is not accessible. Make sure you have the right permissions.`);
|
||||
})));
|
||||
}))
|
||||
.then((workspace) => {
|
||||
// Fix the URL hash
|
||||
utils.setQueryParams(makeWorkspaceIdParams(workspace.folderId));
|
||||
return workspace;
|
||||
}));
|
||||
},
|
||||
performAction() {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
const state = googleHelper.driveState || {};
|
||||
const token = this.getToken();
|
||||
switch (token && state.action) {
|
||||
case 'create':
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
const driveFolder = googleHelper.driveActionFolder;
|
||||
let syncData = store.getters['data/syncData'][driveFolder.id];
|
||||
if (!syncData && driveFolder.appProperties.id) {
|
||||
// Create folder if not already synced
|
||||
store.commit('folder/setItem', {
|
||||
id: driveFolder.appProperties.id,
|
||||
name: driveFolder.name,
|
||||
});
|
||||
const item = store.state.folder.itemMap[driveFolder.appProperties.id];
|
||||
syncData = {
|
||||
id: driveFolder.id,
|
||||
itemId: item.id,
|
||||
type: item.type,
|
||||
hash: item.hash,
|
||||
};
|
||||
store.dispatch('data/patchSyncData', {
|
||||
[syncData.id]: syncData,
|
||||
});
|
||||
}
|
||||
return store.dispatch('createFile', {
|
||||
parentId: syncData && syncData.itemId,
|
||||
})
|
||||
.then((file) => {
|
||||
store.commit('file/setCurrentId', file.id);
|
||||
// File will be created on next workspace sync
|
||||
});
|
||||
});
|
||||
case 'open':
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
// open first file only
|
||||
const firstFile = googleHelper.driveActionFiles[0];
|
||||
const syncData = store.getters['data/syncData'][firstFile.id];
|
||||
if (!syncData) {
|
||||
fileIdToOpen = firstFile.id;
|
||||
} else {
|
||||
store.commit('file/setCurrentId', syncData.itemId);
|
||||
}
|
||||
});
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
},
|
||||
getChanges() {
|
||||
const workspace = store.getters['workspace/currentWorkspace'];
|
||||
@ -178,8 +236,8 @@ export default providerRegistry.register({
|
||||
let contentChange;
|
||||
if (change.file) {
|
||||
// Ignore changes in files that are not in the workspace
|
||||
const properties = change.file.appProperties;
|
||||
if (!properties || properties.folderId !== workspace.folderId
|
||||
const appProperties = change.file.appProperties;
|
||||
if (!appProperties || appProperties.folderId !== workspace.folderId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -198,7 +256,7 @@ export default providerRegistry.register({
|
||||
? 'folder'
|
||||
: 'file';
|
||||
const item = {
|
||||
id: properties.id,
|
||||
id: appProperties.id,
|
||||
type,
|
||||
name: change.file.name,
|
||||
parentId: null,
|
||||
@ -222,14 +280,14 @@ export default providerRegistry.register({
|
||||
// create a fake change as a file content change
|
||||
contentChange = {
|
||||
item: {
|
||||
id: `${properties.id}/content`,
|
||||
id: `${appProperties.id}/content`,
|
||||
type: 'content',
|
||||
// Need a truthy value to force saving sync data
|
||||
hash: 1,
|
||||
},
|
||||
syncData: {
|
||||
id: `${change.fileId}/content`,
|
||||
itemId: `${properties.id}/content`,
|
||||
itemId: `${appProperties.id}/content`,
|
||||
type: 'content',
|
||||
// Need a truthy value to force downloading the content
|
||||
hash: 1,
|
||||
@ -341,6 +399,14 @@ export default providerRegistry.register({
|
||||
},
|
||||
});
|
||||
}
|
||||
// Open the file requested by action if it was to synced yet
|
||||
if (fileIdToOpen && fileIdToOpen === syncData.id) {
|
||||
fileIdToOpen = null;
|
||||
// Open the file once downloaded content has been stored
|
||||
setTimeout(() => {
|
||||
store.commit('file/setCurrentId', syncData.itemId);
|
||||
}, 10);
|
||||
}
|
||||
return item;
|
||||
});
|
||||
},
|
||||
|
@ -2,10 +2,7 @@ import utils from '../../utils';
|
||||
import networkSvc from '../../networkSvc';
|
||||
import store from '../../../store';
|
||||
|
||||
let clientId = 'cbf0cf25cfd026be23e1';
|
||||
if (utils.origin === 'https://stackedit.io') {
|
||||
clientId = '30c1491057c9ad4dbd56';
|
||||
}
|
||||
const clientId = GITHUB_CLIENT_ID;
|
||||
const getScopes = token => [token.repoFullAccess ? 'repo' : 'public_repo', 'gist'];
|
||||
|
||||
const request = (token, options) => networkSvc.request({
|
||||
|
@ -2,7 +2,7 @@ import utils from '../../utils';
|
||||
import networkSvc from '../../networkSvc';
|
||||
import store from '../../../store';
|
||||
|
||||
const clientId = '241271498917-t4t7d07qis7oc0ahaskbif3ft6tk63cd.apps.googleusercontent.com';
|
||||
const clientId = GOOGLE_CLIENT_ID;
|
||||
const apiKey = 'AIzaSyC_M4RA9pY6XmM9pmFxlT59UPMO7aHr9kk';
|
||||
const appsDomain = null;
|
||||
const tokenExpirationMargin = 5 * 60 * 1000; // 5 min (Google tokens expire after 1h)
|
||||
@ -29,8 +29,20 @@ const checkIdToken = (idToken) => {
|
||||
}
|
||||
};
|
||||
|
||||
let driveState;
|
||||
if (utils.queryParams.providerId === 'googleDrive') {
|
||||
try {
|
||||
driveState = JSON.parse(utils.queryParams.state);
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
folderMimeType: 'application/vnd.google-apps.folder',
|
||||
driveState,
|
||||
driveActionFolder: null,
|
||||
driveActionFiles: [],
|
||||
request(token, options) {
|
||||
return networkSvc.request({
|
||||
...options,
|
||||
@ -336,8 +348,8 @@ export default {
|
||||
signin() {
|
||||
return this.startOauth2(driveAppDataScopes);
|
||||
},
|
||||
addDriveAccount(fullAccess = false) {
|
||||
return this.startOauth2(getDriveScopes({ driveFullAccess: fullAccess }));
|
||||
addDriveAccount(fullAccess = false, sub = null) {
|
||||
return this.startOauth2(getDriveScopes({ driveFullAccess: fullAccess }), sub);
|
||||
},
|
||||
addBloggerAccount() {
|
||||
return this.startOauth2(bloggerScopes);
|
||||
|
@ -11,7 +11,8 @@ const inactivityThreshold = 3 * 1000; // 3 sec
|
||||
const restartSyncAfter = 30 * 1000; // 30 sec
|
||||
const minAutoSyncEvery = 60 * 1000; // 60 sec
|
||||
|
||||
let syncProvider;
|
||||
let actionProvider;
|
||||
let workspaceProvider;
|
||||
|
||||
/**
|
||||
* Use a lock in the local storage to prevent multiple windows concurrency.
|
||||
@ -221,7 +222,7 @@ function syncFile(fileId, syncContext = new SyncContext()) {
|
||||
...store.getters['syncLocation/groupedByFileId'][fileId] || [],
|
||||
];
|
||||
if (isWorkspaceSyncPossible()) {
|
||||
syncLocations.unshift({ id: 'main', providerId: syncProvider.id, fileId });
|
||||
syncLocations.unshift({ id: 'main', providerId: workspaceProvider.id, fileId });
|
||||
}
|
||||
let result;
|
||||
syncLocations.some((syncLocation) => {
|
||||
@ -355,7 +356,7 @@ function syncFile(fileId, syncContext = new SyncContext()) {
|
||||
}
|
||||
|
||||
// If content was just created, restart sync to create the file as well
|
||||
if (provider === syncProvider &&
|
||||
if (provider === workspaceProvider &&
|
||||
!store.getters['data/syncDataByItemId'][fileId]
|
||||
) {
|
||||
syncContext.restart = true;
|
||||
@ -409,7 +410,7 @@ function syncDataItem(dataId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return syncProvider.downloadData(dataId)
|
||||
return workspaceProvider.downloadData(dataId)
|
||||
.then((serverItem = null) => {
|
||||
const dataSyncData = store.getters['data/dataSyncData'][dataId];
|
||||
let mergedItem = (() => {
|
||||
@ -455,7 +456,7 @@ function syncDataItem(dataId) {
|
||||
if (serverItem && serverItem.hash === mergedItem.hash) {
|
||||
return null;
|
||||
}
|
||||
return syncProvider.uploadData(mergedItem, dataId);
|
||||
return workspaceProvider.uploadData(mergedItem, dataId);
|
||||
})
|
||||
.then(() => {
|
||||
store.dispatch('data/patchDataSyncData', {
|
||||
@ -485,11 +486,11 @@ function syncWorkspace() {
|
||||
throw new Error('Synchronization failed due to token inconsistency.');
|
||||
}
|
||||
})
|
||||
.then(() => syncProvider.getChanges())
|
||||
.then(() => workspaceProvider.getChanges())
|
||||
.then((changes) => {
|
||||
// Apply changes
|
||||
applyChanges(changes);
|
||||
syncProvider.setAppliedChanges(changes);
|
||||
workspaceProvider.setAppliedChanges(changes);
|
||||
|
||||
// Prevent from sending items too long after changes have been retrieved
|
||||
const syncStartTime = Date.now();
|
||||
@ -519,7 +520,7 @@ function syncWorkspace() {
|
||||
// Add file if content has been added
|
||||
&& (item.type !== 'file' || syncDataByItemId[`${id}/content`])
|
||||
) {
|
||||
promise = syncProvider.saveSimpleItem(
|
||||
promise = workspaceProvider.saveSimpleItem(
|
||||
// Use deepCopy to freeze objects
|
||||
utils.deepCopy(item),
|
||||
utils.deepCopy(existingSyncData),
|
||||
@ -555,7 +556,7 @@ function syncWorkspace() {
|
||||
) {
|
||||
// Use deepCopy to freeze objects
|
||||
const syncDataToRemove = utils.deepCopy(existingSyncData);
|
||||
promise = syncProvider
|
||||
promise = workspaceProvider
|
||||
.removeItem(syncDataToRemove, ifNotTooLate)
|
||||
.then(() => {
|
||||
const syncDataCopy = { ...store.getters['data/syncData'] };
|
||||
@ -707,25 +708,38 @@ function requestSync() {
|
||||
|
||||
export default {
|
||||
init() {
|
||||
// Load workspaces and tokens from localStorage
|
||||
localDbSvc.syncLocalStorage();
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
// Load workspaces and tokens from localStorage
|
||||
localDbSvc.syncLocalStorage();
|
||||
|
||||
// Try to find a suitable workspace sync provider
|
||||
syncProvider = providerRegistry.providers[utils.queryParams.providerId];
|
||||
if (!syncProvider || !syncProvider.initWorkspace) {
|
||||
syncProvider = googleDriveAppDataProvider;
|
||||
}
|
||||
|
||||
return syncProvider.initWorkspace()
|
||||
// Try to find a suitable action provider
|
||||
actionProvider = providerRegistry.providers[utils.queryParams.providerId];
|
||||
return actionProvider && actionProvider.initAction && actionProvider.initAction();
|
||||
})
|
||||
.then(() => {
|
||||
// Try to find a suitable workspace sync provider
|
||||
workspaceProvider = providerRegistry.providers[utils.queryParams.providerId];
|
||||
if (!workspaceProvider || !workspaceProvider.initWorkspace) {
|
||||
workspaceProvider = googleDriveAppDataProvider;
|
||||
}
|
||||
return workspaceProvider.initWorkspace();
|
||||
})
|
||||
.then(workspace => store.dispatch('workspace/setCurrentWorkspaceId', workspace.id))
|
||||
.then(() => localDbSvc.init())
|
||||
.then(() => {
|
||||
// Try to find a suitable action provider
|
||||
actionProvider = providerRegistry.providers[utils.queryParams.providerId] || actionProvider;
|
||||
return actionProvider && actionProvider.performAction && actionProvider.performAction()
|
||||
.then(newSyncLocation => newSyncLocation && this.createSyncLocation(newSyncLocation));
|
||||
})
|
||||
.then(() => {
|
||||
// Sync periodically
|
||||
utils.setInterval(() => {
|
||||
if (isSyncPossible() &&
|
||||
networkSvc.isUserActive() &&
|
||||
isSyncWindow() &&
|
||||
isAutoSyncReady()
|
||||
if (isSyncPossible()
|
||||
&& networkSvc.isUserActive()
|
||||
&& isSyncWindow()
|
||||
&& isAutoSyncReady()
|
||||
) {
|
||||
requestSync();
|
||||
}
|
||||
|
@ -24,13 +24,22 @@ const parseQueryParams = (params) => {
|
||||
};
|
||||
|
||||
// For utils.addQueryParams()
|
||||
const urlParser = window.document.createElement('a');
|
||||
const urlParser = document.createElement('a');
|
||||
|
||||
export default {
|
||||
origin,
|
||||
queryParams: parseQueryParams(location.hash.slice(1)),
|
||||
oauth2RedirectUri: `${origin}/oauth2/callback`,
|
||||
cleanTrashAfter: 7 * 24 * 60 * 60 * 1000, // 7 days
|
||||
origin,
|
||||
oauth2RedirectUri: `${origin}/oauth2/callback`,
|
||||
queryParams: parseQueryParams(location.hash.slice(1)),
|
||||
setQueryParams(params = {}) {
|
||||
this.queryParams = params;
|
||||
const serializedParams = Object.entries(this.queryParams).map(([key, value]) =>
|
||||
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join('&');
|
||||
const hash = serializedParams && `#${serializedParams}`;
|
||||
if (location.hash !== hash) {
|
||||
location.hash = hash;
|
||||
}
|
||||
},
|
||||
types: [
|
||||
'contentState',
|
||||
'syncedContent',
|
||||
@ -96,6 +105,9 @@ export default {
|
||||
})),
|
||||
};
|
||||
},
|
||||
makeWorkspaceId(params) {
|
||||
return Math.abs(this.hash(this.serializeObject(params))).toString(36);
|
||||
},
|
||||
encodeBase64(str) {
|
||||
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
|
||||
(match, p1) => String.fromCharCode(`0x${p1}`)));
|
||||
|
@ -117,11 +117,11 @@ export default {
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
workspaces: (state, getters, rootState, rootGetters) => {
|
||||
const workspaces = (state.lsItemMap.workspaces || {}).data || empty('workspaces').data;
|
||||
workspaces: getter('workspaces'),
|
||||
sanitizedWorkspaces: (state, getters, rootState, rootGetters) => {
|
||||
const sanitizedWorkspaces = {};
|
||||
const mainWorkspaceToken = rootGetters['workspace/mainWorkspaceToken'];
|
||||
Object.entries(workspaces).forEach(([id, workspace]) => {
|
||||
Object.entries(getters.workspaces).forEach(([id, workspace]) => {
|
||||
const sanitizedWorkspace = {
|
||||
id,
|
||||
providerId: mainWorkspaceToken && 'googleDriveAppData',
|
||||
|
@ -89,7 +89,7 @@ const store = new Vuex.Store({
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
createFile({ state, getters, commit }, desc) {
|
||||
createFile({ state, getters, commit }, desc = {}) {
|
||||
const id = utils.uid();
|
||||
commit('content/setItem', {
|
||||
id: `${id}/content`,
|
||||
|
@ -23,6 +23,7 @@ export default {
|
||||
config.resolve = (result) => {
|
||||
clean();
|
||||
if (config.onResolve) {
|
||||
// Call onResolve immediately (mostly to prevent browsers from blocking popup windows)
|
||||
config.onResolve(result)
|
||||
.then(res => resolve(res));
|
||||
} else {
|
||||
@ -92,8 +93,8 @@ export default {
|
||||
onResolve,
|
||||
}),
|
||||
workspaceGoogleRedirection: ({ dispatch }, { onResolve }) => dispatch('open', {
|
||||
content: '<p>You have to sign in with Google to access this workspace.</p>',
|
||||
resolveText: 'Ok, sign in',
|
||||
content: '<p>StackEdit needs full Google Drive access to open this workspace.</p>',
|
||||
resolveText: 'Ok, grant',
|
||||
rejectText: 'Cancel',
|
||||
onResolve,
|
||||
}),
|
||||
@ -107,14 +108,14 @@ export default {
|
||||
}),
|
||||
signInForComment: ({ dispatch }, { onResolve }) => dispatch('open', {
|
||||
content: `<p>You have to sign in with Google to start commenting.</p>
|
||||
<div class="modal__info"><b>Note:</b> This will sync all your files and settings.</div>`,
|
||||
<div class="modal__info"><b>Note:</b> This will sync your main workspace.</div>`,
|
||||
resolveText: 'Ok, sign in',
|
||||
rejectText: 'Cancel',
|
||||
onResolve,
|
||||
}),
|
||||
signInForHistory: ({ dispatch }, { onResolve }) => dispatch('open', {
|
||||
content: `<p>You have to sign in with Google to enable revision history.</p>
|
||||
<div class="modal__info"><b>Note:</b> This will sync all your files and settings.</div>`,
|
||||
<div class="modal__info"><b>Note:</b> This will sync your main workspace.</div>`,
|
||||
resolveText: 'Ok, sign in',
|
||||
rejectText: 'Cancel',
|
||||
onResolve,
|
||||
|
@ -14,11 +14,11 @@ export default {
|
||||
},
|
||||
getters: {
|
||||
mainWorkspace: (state, getters, rootState, rootGetters) => {
|
||||
const workspaces = rootGetters['data/workspaces'];
|
||||
const workspaces = rootGetters['data/sanitizedWorkspaces'];
|
||||
return workspaces.main;
|
||||
},
|
||||
currentWorkspace: (state, getters, rootState, rootGetters) => {
|
||||
const workspaces = rootGetters['data/workspaces'];
|
||||
const workspaces = rootGetters['data/sanitizedWorkspaces'];
|
||||
return workspaces[state.currentWorkspaceId] || getters.mainWorkspace;
|
||||
},
|
||||
lastSyncActivityKey: (state, getters) => `${getters.currentWorkspace.id}/lastSyncActivity`,
|
||||
|
Loading…
Reference in New Issue
Block a user