Workspaces (part 3)
This commit is contained in:
parent
abbe1804e2
commit
32aa259790
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="explorer-node" :class="{'explorer-node--selected': isSelected, 'explorer-node--open': isOpen, 'explorer-node--drag-target': isDragTargetFolder}" @dragover.prevent @dragenter.stop="setDragTarget(node.item.id)" @dragleave.stop="isDragTarget && setDragTargetId()" @drop.prevent.stop="onDrop">
|
<div class="explorer-node" :class="{'explorer-node--selected': isSelected, 'explorer-node--open': isOpen, 'explorer-node--drag-target': isDragTargetFolder}" @dragover.prevent @dragenter.stop="setDragTarget(node.item.id)" @dragleave.stop="isDragTarget && setDragTargetId()" @drop.prevent.stop="onDrop">
|
||||||
<div class="explorer-node__item-editor" v-if="isEditing" :class="['explorer-node__item-editor--' + node.item.type]" :style="{paddingLeft: leftPadding}">
|
<div class="explorer-node__item-editor" v-if="isEditing" :class="['explorer-node__item-editor--' + node.item.type]" :style="{paddingLeft: leftPadding}" draggable="true" @dragstart.stop.prevent>
|
||||||
<input type="text" class="text-input" v-focus @blur="submitEdit()" @keyup.enter="submitEdit()" @keyup.esc="submitEdit(true)" v-model="editingNodeName">
|
<input type="text" class="text-input" v-focus @blur="submitEdit()" @keyup.enter="submitEdit()" @keyup.esc="submitEdit(true)" v-model="editingNodeName">
|
||||||
</div>
|
</div>
|
||||||
<div class="explorer-node__item" v-else :class="['explorer-node__item--' + node.item.type]" :style="{paddingLeft: leftPadding}" @click="select(node.item.id)" draggable="true" @dragstart.stop="setDragSourceId" @dragend.stop="setDragTargetId()">
|
<div class="explorer-node__item" v-else :class="['explorer-node__item--' + node.item.type]" :style="{paddingLeft: leftPadding}" @click="select(node.item.id)" draggable="true" @dragstart.stop="setDragSourceId" @dragend.stop="setDragTargetId()">
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
<sponsor-modal v-else-if="config.type === 'sponsor'"></sponsor-modal>
|
<sponsor-modal v-else-if="config.type === 'sponsor'"></sponsor-modal>
|
||||||
<!-- Providers -->
|
<!-- Providers -->
|
||||||
<google-photo-modal v-else-if="config.type === 'googlePhoto'"></google-photo-modal>
|
<google-photo-modal v-else-if="config.type === 'googlePhoto'"></google-photo-modal>
|
||||||
|
<google-drive-account-modal v-else-if="config.type === 'googleDriveAccount'"></google-drive-account-modal>
|
||||||
<google-drive-save-modal v-else-if="config.type === 'googleDriveSave'"></google-drive-save-modal>
|
<google-drive-save-modal v-else-if="config.type === 'googleDriveSave'"></google-drive-save-modal>
|
||||||
<google-drive-workspace-modal v-else-if="config.type === 'googleDriveWorkspace'"></google-drive-workspace-modal>
|
<google-drive-workspace-modal v-else-if="config.type === 'googleDriveWorkspace'"></google-drive-workspace-modal>
|
||||||
<google-drive-publish-modal v-else-if="config.type === 'googleDrivePublish'"></google-drive-publish-modal>
|
<google-drive-publish-modal v-else-if="config.type === 'googleDrivePublish'"></google-drive-publish-modal>
|
||||||
@ -62,6 +63,7 @@ import SponsorModal from './modals/SponsorModal';
|
|||||||
|
|
||||||
// Providers
|
// Providers
|
||||||
import GooglePhotoModal from './modals/providers/GooglePhotoModal';
|
import GooglePhotoModal from './modals/providers/GooglePhotoModal';
|
||||||
|
import GoogleDriveAccountModal from './modals/providers/GoogleDriveAccountModal';
|
||||||
import GoogleDriveSaveModal from './modals/providers/GoogleDriveSaveModal';
|
import GoogleDriveSaveModal from './modals/providers/GoogleDriveSaveModal';
|
||||||
import GoogleDriveWorkspaceModal from './modals/providers/GoogleDriveWorkspaceModal';
|
import GoogleDriveWorkspaceModal from './modals/providers/GoogleDriveWorkspaceModal';
|
||||||
import GoogleDrivePublishModal from './modals/providers/GoogleDrivePublishModal';
|
import GoogleDrivePublishModal from './modals/providers/GoogleDrivePublishModal';
|
||||||
@ -102,6 +104,7 @@ export default {
|
|||||||
SponsorModal,
|
SponsorModal,
|
||||||
// Providers
|
// Providers
|
||||||
GooglePhotoModal,
|
GooglePhotoModal,
|
||||||
|
GoogleDriveAccountModal,
|
||||||
GoogleDriveSaveModal,
|
GoogleDriveSaveModal,
|
||||||
GoogleDriveWorkspaceModal,
|
GoogleDriveWorkspaceModal,
|
||||||
GoogleDrivePublishModal,
|
GoogleDrivePublishModal,
|
||||||
|
@ -120,6 +120,12 @@ export default {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-bar__panel--hidden {
|
.side-bar__panel--hidden {
|
||||||
@ -127,11 +133,11 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.side-bar__panel--menu {
|
.side-bar__panel--menu {
|
||||||
padding: 10px 10px 50px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-bar__panel--help {
|
.side-bar__panel--help {
|
||||||
padding: 0 10px 40px 20px;
|
padding: 0 10px 0 20px;
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapMutations } from 'vuex';
|
import { mapMutations } from 'vuex';
|
||||||
import googleDriveAppDataProvider from '../../services/providers/googleDriveAppDataProvider';
|
import providerRegistry from '../../services/providers/providerRegistry';
|
||||||
import MenuEntry from './common/MenuEntry';
|
import MenuEntry from './common/MenuEntry';
|
||||||
import UserImage from '../UserImage';
|
import UserImage from '../UserImage';
|
||||||
import UserName from '../UserName';
|
import UserName from '../UserName';
|
||||||
@ -83,7 +83,7 @@ export default {
|
|||||||
const currentFile = this.$store.getters['file/current'];
|
const currentFile = this.$store.getters['file/current'];
|
||||||
this.$store.dispatch('queue/enqueue',
|
this.$store.dispatch('queue/enqueue',
|
||||||
() => Promise.resolve()
|
() => Promise.resolve()
|
||||||
.then(() => googleDriveAppDataProvider.getRevisionContent(
|
.then(() => this.workspaceProvider.getRevisionContent(
|
||||||
loginToken, currentFile.id, revision.id))
|
loginToken, currentFile.id, revision.id))
|
||||||
.then(resolve, reject));
|
.then(resolve, reject));
|
||||||
});
|
});
|
||||||
@ -123,6 +123,10 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
// Find the workspace provider
|
||||||
|
const workspace = this.$store.getters['workspace/currentWorkspace'];
|
||||||
|
this.workspaceProvider = providerRegistry.providers[workspace.providerId];
|
||||||
|
|
||||||
// Watch file changes
|
// Watch file changes
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.$store.getters['file/current'].id,
|
() => this.$store.getters['file/current'].id,
|
||||||
@ -138,7 +142,7 @@ export default {
|
|||||||
revisionsPromise = new Promise((resolve, reject) => {
|
revisionsPromise = new Promise((resolve, reject) => {
|
||||||
this.$store.dispatch('queue/enqueue',
|
this.$store.dispatch('queue/enqueue',
|
||||||
() => Promise.resolve()
|
() => Promise.resolve()
|
||||||
.then(() => googleDriveAppDataProvider.listRevisions(loginToken, currentFile.id))
|
.then(() => this.workspaceProvider.listRevisions(loginToken, currentFile.id))
|
||||||
.then((revisions) => {
|
.then((revisions) => {
|
||||||
resolve(revisions.sort(
|
resolve(revisions.sort(
|
||||||
(revision1, revision2) => revision2.created - revision1.created));
|
(revision1, revision2) => revision2.created - revision1.created));
|
||||||
|
@ -158,7 +158,10 @@ export default {
|
|||||||
return this.$store.dispatch('modal/open', 'publishManagement');
|
return this.$store.dispatch('modal/open', 'publishManagement');
|
||||||
},
|
},
|
||||||
addGoogleDriveAccount() {
|
addGoogleDriveAccount() {
|
||||||
return googleHelper.addDriveAccount()
|
return this.$store.dispatch('modal/open', {
|
||||||
|
type: 'googleDriveAccount',
|
||||||
|
onResolve: () => googleHelper.addDriveAccount(!store.getters['data/localSettings'].googleDriveRestrictedAccess),
|
||||||
|
})
|
||||||
.catch(() => {}); // Cancel
|
.catch(() => {}); // Cancel
|
||||||
},
|
},
|
||||||
addDropboxAccount() {
|
addDropboxAccount() {
|
||||||
|
@ -139,7 +139,10 @@ export default {
|
|||||||
return this.$store.dispatch('modal/open', 'syncManagement');
|
return this.$store.dispatch('modal/open', 'syncManagement');
|
||||||
},
|
},
|
||||||
addGoogleDriveAccount() {
|
addGoogleDriveAccount() {
|
||||||
return googleHelper.addDriveAccount()
|
return this.$store.dispatch('modal/open', {
|
||||||
|
type: 'googleDriveAccount',
|
||||||
|
onResolve: () => googleHelper.addDriveAccount(!store.getters['data/localSettings'].googleDriveRestrictedAccess),
|
||||||
|
})
|
||||||
.catch(() => {}); // Cancel
|
.catch(() => {}); // Cancel
|
||||||
},
|
},
|
||||||
addDropboxAccount() {
|
addDropboxAccount() {
|
||||||
|
@ -37,7 +37,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addGoogleDriveWorkspace() {
|
addGoogleDriveWorkspace() {
|
||||||
return googleHelper.addDriveAccount()
|
return googleHelper.addDriveAccount(true)
|
||||||
.then(token => this.$store.dispatch('modal/open', {
|
.then(token => this.$store.dispatch('modal/open', {
|
||||||
type: 'googleDriveWorkspace',
|
type: 'googleDriveWorkspace',
|
||||||
token,
|
token,
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="dropbox"></icon-provider>
|
<icon-provider provider-id="dropbox"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will link your <b>Dropbox</b> account to your <b>StackEdit</b> workspace.</p>
|
<p>This will link your <b>Dropbox</b> account to <b>StackEdit</b>.</p>
|
||||||
<div class="form-entry">
|
<div class="form-entry">
|
||||||
<div class="form-entry__checkbox">
|
<div class="form-entry__checkbox">
|
||||||
<label>
|
<label>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="github"></icon-provider>
|
<icon-provider provider-id="github"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will link your <b>GitHub</b> account to your <b>StackEdit</b> workspace.</p>
|
<p>This will link your <b>GitHub</b> account to <b>StackEdit</b>.</p>
|
||||||
<div class="form-entry">
|
<div class="form-entry">
|
||||||
<div class="form-entry__checkbox">
|
<div class="form-entry__checkbox">
|
||||||
<label>
|
<label>
|
||||||
|
34
src/components/modals/providers/GoogleDriveAccountModal.vue
Normal file
34
src/components/modals/providers/GoogleDriveAccountModal.vue
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<modal-inner aria-label="Link Google Drive account">
|
||||||
|
<div class="modal__content">
|
||||||
|
<div class="modal__image">
|
||||||
|
<icon-provider provider-id="googleDrive"></icon-provider>
|
||||||
|
</div>
|
||||||
|
<p>This will link your <b>Google Drive</b> account to <b>StackEdit</b>.</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 files that you have opened or created with <b>StackEdit</b>.
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
</modal-inner>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import modalTemplate from '../common/modalTemplate';
|
||||||
|
|
||||||
|
export default modalTemplate({
|
||||||
|
computedLocalSettings: {
|
||||||
|
restrictedAccess: 'googleDriveRestrictedAccess',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
@ -4,7 +4,7 @@
|
|||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="zendesk"></icon-provider>
|
<icon-provider provider-id="zendesk"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will link your <b>Zendesk</b> account to your <b>StackEdit</b> workspace.</p>
|
<p>This will link your <b>Zendesk</b> account to <b>StackEdit</b>.</p>
|
||||||
<form-entry label="Site URL" error="siteUrl">
|
<form-entry label="Site URL" error="siteUrl">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="siteUrl" @keyup.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="siteUrl" @keyup.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
|
@ -3,6 +3,7 @@ export default () => ({
|
|||||||
htmlExportTemplate: 'styledHtml',
|
htmlExportTemplate: 'styledHtml',
|
||||||
pdfExportTemplate: 'styledHtml',
|
pdfExportTemplate: 'styledHtml',
|
||||||
pandocExportFormat: 'pdf',
|
pandocExportFormat: 'pdf',
|
||||||
|
googleDriveRestrictedAccess: false,
|
||||||
googleDriveFolderId: '',
|
googleDriveFolderId: '',
|
||||||
googleDriveWorkspaceFolderId: '',
|
googleDriveWorkspaceFolderId: '',
|
||||||
googleDrivePublishFormat: 'markdown',
|
googleDrivePublishFormat: 'markdown',
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Auto-sync frequency (in ms). Minimum is 60000.
|
# Auto-sync frequency (in ms). Minimum is 60000.
|
||||||
autoSyncEvery: 90000
|
autoSyncEvery: 60000
|
||||||
# Adjust font size in editor and preview
|
# Adjust font size in editor and preview
|
||||||
fontSizeFactor: 1
|
fontSizeFactor: 1
|
||||||
# Adjust maximum text width in editor and preview
|
# Adjust maximum text width in editor and preview
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="icon-provider" :class="'icon-provider--' + classState">
|
<div class="icon-provider" :class="'icon-provider--' + classState">
|
||||||
|
<icon-sync-off v-if="!classState"></icon-sync-off>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -11,9 +11,10 @@ export default providerRegistry.register({
|
|||||||
// Nothing to do since the main workspace isn't necessarily synchronized
|
// Nothing to do since the main workspace isn't necessarily synchronized
|
||||||
return Promise.resolve(store.getters['data/workspaces'].main);
|
return Promise.resolve(store.getters['data/workspaces'].main);
|
||||||
},
|
},
|
||||||
getChanges(token) {
|
getChanges() {
|
||||||
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
const startPageToken = store.getters['data/localSettings'].syncStartPageToken;
|
const startPageToken = store.getters['data/localSettings'].syncStartPageToken;
|
||||||
return googleHelper.getChanges(token, startPageToken, 'appDataFolder')
|
return googleHelper.getChanges(syncToken, startPageToken, true)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
const changes = result.changes.filter((change) => {
|
const changes = result.changes.filter((change) => {
|
||||||
if (change.file) {
|
if (change.file) {
|
||||||
@ -30,7 +31,6 @@ export default providerRegistry.register({
|
|||||||
type: change.item.type,
|
type: change.item.type,
|
||||||
hash: change.item.hash,
|
hash: change.item.hash,
|
||||||
};
|
};
|
||||||
change.file = undefined;
|
|
||||||
}
|
}
|
||||||
change.syncDataId = change.fileId;
|
change.syncDataId = change.fileId;
|
||||||
return true;
|
return true;
|
||||||
@ -41,19 +41,18 @@ export default providerRegistry.register({
|
|||||||
},
|
},
|
||||||
setAppliedChanges(changes) {
|
setAppliedChanges(changes) {
|
||||||
store.dispatch('data/patchLocalSettings', {
|
store.dispatch('data/patchLocalSettings', {
|
||||||
workspaceSyncStartPageToken: changes.startPageToken,
|
syncStartPageToken: changes.startPageToken,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
saveItem(token, item, syncData, ifNotTooLate) {
|
saveSimpleItem(item, syncData, ifNotTooLate) {
|
||||||
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
return googleHelper.uploadAppDataFile(
|
return googleHelper.uploadAppDataFile(
|
||||||
token,
|
syncToken,
|
||||||
JSON.stringify(item),
|
JSON.stringify(item),
|
||||||
['appDataFolder'],
|
undefined,
|
||||||
undefined,
|
syncData && syncData.id,
|
||||||
undefined,
|
ifNotTooLate,
|
||||||
syncData && syncData.id,
|
)
|
||||||
ifNotTooLate,
|
|
||||||
)
|
|
||||||
.then(file => ({
|
.then(file => ({
|
||||||
// Build sync data
|
// Build sync data
|
||||||
id: file.id,
|
id: file.id,
|
||||||
@ -62,19 +61,20 @@ export default providerRegistry.register({
|
|||||||
hash: item.hash,
|
hash: item.hash,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
removeItem(token, syncData, ifNotTooLate) {
|
removeItem(syncData, ifNotTooLate) {
|
||||||
return googleHelper.removeAppDataFile(token, syncData.id, ifNotTooLate)
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
.then(() => syncData);
|
return googleHelper.removeAppDataFile(syncToken, syncData.id, ifNotTooLate);
|
||||||
},
|
},
|
||||||
downloadContent(token, syncLocation) {
|
downloadContent(token, syncLocation) {
|
||||||
return this.downloadData(token, `${syncLocation.fileId}/content`);
|
return this.downloadData(`${syncLocation.fileId}/content`);
|
||||||
},
|
},
|
||||||
downloadData(token, dataId) {
|
downloadData(dataId) {
|
||||||
const syncData = store.getters['data/syncDataByItemId'][dataId];
|
const syncData = store.getters['data/syncDataByItemId'][dataId];
|
||||||
if (!syncData) {
|
if (!syncData) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
return googleHelper.downloadAppDataFile(token, syncData.id)
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
|
return googleHelper.downloadAppDataFile(syncToken, syncData.id)
|
||||||
.then((content) => {
|
.then((content) => {
|
||||||
const item = JSON.parse(content);
|
const item = JSON.parse(content);
|
||||||
if (item.hash !== syncData.hash) {
|
if (item.hash !== syncData.hash) {
|
||||||
@ -89,27 +89,26 @@ export default providerRegistry.register({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
uploadContent(token, content, syncLocation, ifNotTooLate) {
|
uploadContent(token, content, syncLocation, ifNotTooLate) {
|
||||||
return this.uploadData(token, content, `${syncLocation.fileId}/content`, ifNotTooLate)
|
return this.uploadData(content, `${syncLocation.fileId}/content`, ifNotTooLate)
|
||||||
.then(() => syncLocation);
|
.then(() => syncLocation);
|
||||||
},
|
},
|
||||||
uploadData(token, item, dataId, ifNotTooLate) {
|
uploadData(item, dataId, ifNotTooLate) {
|
||||||
const syncData = store.getters['data/syncDataByItemId'][dataId];
|
const syncData = store.getters['data/syncDataByItemId'][dataId];
|
||||||
if (syncData && syncData.hash === item.hash) {
|
if (syncData && syncData.hash === item.hash) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
return googleHelper.uploadAppDataFile(
|
return googleHelper.uploadAppDataFile(
|
||||||
token,
|
syncToken,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
type: item.type,
|
type: item.type,
|
||||||
hash: item.hash,
|
hash: item.hash,
|
||||||
}),
|
}),
|
||||||
['appDataFolder'],
|
JSON.stringify(item),
|
||||||
undefined,
|
syncData && syncData.id,
|
||||||
JSON.stringify(item),
|
ifNotTooLate,
|
||||||
syncData && syncData.id,
|
)
|
||||||
ifNotTooLate,
|
|
||||||
)
|
|
||||||
.then(file => store.dispatch('data/patchSyncData', {
|
.then(file => store.dispatch('data/patchSyncData', {
|
||||||
[file.id]: {
|
[file.id]: {
|
||||||
// Build sync data
|
// Build sync data
|
||||||
@ -125,7 +124,7 @@ export default providerRegistry.register({
|
|||||||
if (!syncData) {
|
if (!syncData) {
|
||||||
return Promise.reject(); // No need for a proper error message.
|
return Promise.reject(); // No need for a proper error message.
|
||||||
}
|
}
|
||||||
return googleHelper.getFileRevisions(token, syncData.id)
|
return googleHelper.getAppDataFileRevisions(token, syncData.id)
|
||||||
.then(revisions => revisions.map(revision => ({
|
.then(revisions => revisions.map(revision => ({
|
||||||
id: revision.id,
|
id: revision.id,
|
||||||
sub: revision.lastModifyingUser && revision.lastModifyingUser.permissionId,
|
sub: revision.lastModifyingUser && revision.lastModifyingUser.permissionId,
|
||||||
@ -137,7 +136,7 @@ export default providerRegistry.register({
|
|||||||
if (!syncData) {
|
if (!syncData) {
|
||||||
return Promise.reject(); // No need for a proper error message.
|
return Promise.reject(); // No need for a proper error message.
|
||||||
}
|
}
|
||||||
return googleHelper.downloadFileRevision(token, syncData.id, revisionId)
|
return googleHelper.downloadAppDataFileRevision(token, syncData.id, revisionId)
|
||||||
.then(content => JSON.parse(content));
|
.then(content => JSON.parse(content));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import store from '../../store';
|
import store from '../../store';
|
||||||
import googleHelper from './helpers/googleHelper';
|
import googleHelper from './helpers/googleHelper';
|
||||||
import providerRegistry from './providerRegistry';
|
import providerRegistry from './providerRegistry';
|
||||||
|
import providerUtils from './providerUtils';
|
||||||
import utils from '../utils';
|
import utils from '../utils';
|
||||||
|
|
||||||
export default providerRegistry.register({
|
export default providerRegistry.register({
|
||||||
@ -32,7 +33,7 @@ export default providerRegistry.register({
|
|||||||
[folder.id],
|
[folder.id],
|
||||||
{ folderId: folder.id },
|
{ folderId: folder.id },
|
||||||
undefined,
|
undefined,
|
||||||
'application/vnd.google-apps.folder',
|
googleHelper.folderMimeType,
|
||||||
)
|
)
|
||||||
.then(dataFolder => ({
|
.then(dataFolder => ({
|
||||||
...properties,
|
...properties,
|
||||||
@ -50,7 +51,7 @@ export default providerRegistry.register({
|
|||||||
[folder.id],
|
[folder.id],
|
||||||
{ folderId: folder.id },
|
{ folderId: folder.id },
|
||||||
undefined,
|
undefined,
|
||||||
'application/vnd.google-apps.folder',
|
googleHelper.folderMimeType,
|
||||||
)
|
)
|
||||||
.then(trashFolder => ({
|
.then(trashFolder => ({
|
||||||
...properties,
|
...properties,
|
||||||
@ -71,7 +72,7 @@ export default providerRegistry.register({
|
|||||||
undefined,
|
undefined,
|
||||||
properties,
|
properties,
|
||||||
undefined,
|
undefined,
|
||||||
'application/vnd.google-apps.folder',
|
googleHelper.folderMimeType,
|
||||||
folder.id,
|
folder.id,
|
||||||
)
|
)
|
||||||
.then(() => properties);
|
.then(() => properties);
|
||||||
@ -109,12 +110,12 @@ export default providerRegistry.register({
|
|||||||
const googleTokens = store.getters['data/googleTokens'];
|
const googleTokens = store.getters['data/googleTokens'];
|
||||||
// Token sub is in the workspace or in the url if workspace is about to be created
|
// Token sub is in the workspace or in the url if workspace is about to be created
|
||||||
const token = workspace ? googleTokens[workspace.sub] : googleTokens[utils.queryParams.sub];
|
const token = workspace ? googleTokens[workspace.sub] : googleTokens[utils.queryParams.sub];
|
||||||
if (token && token.isDrive) {
|
if (token && token.isDrive && token.driveFullAccess) {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
// If no token has been found, popup an authorize window and get one
|
// If no token has been found, popup an authorize window and get one
|
||||||
return store.dispatch('modal/workspaceGoogleRedirection', {
|
return store.dispatch('modal/workspaceGoogleRedirection', {
|
||||||
onResolve: () => googleHelper.addDriveAccount(),
|
onResolve: () => googleHelper.addDriveAccount(true),
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(token => Promise.resolve()
|
.then(token => Promise.resolve()
|
||||||
@ -125,7 +126,7 @@ export default providerRegistry.register({
|
|||||||
[],
|
[],
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
'application/vnd.google-apps.folder',
|
googleHelper.folderMimeType,
|
||||||
)
|
)
|
||||||
.then(folder => initFolder(token, {
|
.then(folder => initFolder(token, {
|
||||||
...folder,
|
...folder,
|
||||||
@ -135,27 +136,109 @@ export default providerRegistry.register({
|
|||||||
// If workspace does not exist, initialize one
|
// If workspace does not exist, initialize one
|
||||||
.then(folderId => getWorkspace(folderId) || googleHelper.getFile(token, folderId)
|
.then(folderId => getWorkspace(folderId) || googleHelper.getFile(token, folderId)
|
||||||
.then((folder) => {
|
.then((folder) => {
|
||||||
|
folder.appProperties = folder.appProperties || {};
|
||||||
const folderIdProperty = folder.appProperties.folderId;
|
const folderIdProperty = folder.appProperties.folderId;
|
||||||
if (folderIdProperty && folderIdProperty !== folderId) {
|
if (folderIdProperty && folderIdProperty !== folderId) {
|
||||||
throw new Error(`Google Drive folder ${folderId} is part of another workspace.`);
|
throw new Error(`Google Drive folder ${folderId} is part of another workspace.`);
|
||||||
}
|
}
|
||||||
return initFolder(token, folder);
|
return initFolder(token, folder);
|
||||||
}, () => {
|
}, () => {
|
||||||
throw new Error(`Folder ${folderId} is not accessible. Make sure it's a valid StackEdit workspace folder and you have the right permissions.`);
|
throw new Error(`Folder ${folderId} is not accessible. Make sure you have the right permissions.`);
|
||||||
})));
|
})));
|
||||||
},
|
},
|
||||||
getChanges(token) {
|
getChanges() {
|
||||||
|
const workspace = store.getters['workspace/currentWorkspace'];
|
||||||
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
const startPageToken = store.getters['data/localSettings'].syncStartPageToken;
|
const startPageToken = store.getters['data/localSettings'].syncStartPageToken;
|
||||||
return googleHelper.getChanges(token, startPageToken, 'appDataFolder')
|
return googleHelper.getChanges(syncToken, startPageToken, false)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
const changes = result.changes.filter((change) => {
|
// Collect possible parent IDs
|
||||||
|
const parentIds = {};
|
||||||
|
Object.entries(store.getters['data/syncDataByItemId']).forEach(([id, syncData]) => {
|
||||||
|
parentIds[syncData.id] = id;
|
||||||
|
});
|
||||||
|
result.changes.forEach((change) => {
|
||||||
|
const id = ((change.file || {}).appProperties || {}).id;
|
||||||
|
if (id) {
|
||||||
|
parentIds[change.fileId] = id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Collect changes
|
||||||
|
const changes = [];
|
||||||
|
result.changes.forEach((change) => {
|
||||||
|
// Ignore changes on StackEdit own folders
|
||||||
|
if (change.fileId === workspace.folderId
|
||||||
|
|| change.fileId === workspace.dataFolderId
|
||||||
|
|| change.fileId === workspace.trashFolderId
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let contentChange;
|
||||||
if (change.file) {
|
if (change.file) {
|
||||||
// Parse item from file name
|
// Ignore changes in files that are not in the workspace
|
||||||
try {
|
const properties = change.file.appProperties;
|
||||||
change.item = JSON.parse(change.file.name);
|
if (!properties || properties.folderId !== workspace.folderId
|
||||||
} catch (e) {
|
) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If change is on a data item
|
||||||
|
if (change.file.parents[0] === workspace.dataFolderId) {
|
||||||
|
// Data item has a JSON as a filename
|
||||||
|
try {
|
||||||
|
change.item = JSON.parse(change.file.name);
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Change on a file or folder
|
||||||
|
const type = change.file.mimeType === googleHelper.folderMimeType
|
||||||
|
? 'folder'
|
||||||
|
: 'file';
|
||||||
|
const item = {
|
||||||
|
id: properties.id,
|
||||||
|
type,
|
||||||
|
name: change.file.name,
|
||||||
|
parentId: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fill parentId
|
||||||
|
if (change.file.parents.some(parentId => parentId === workspace.trashFolderId)) {
|
||||||
|
item.parentId = 'trash';
|
||||||
|
} else {
|
||||||
|
change.file.parents.some((parentId) => {
|
||||||
|
if (!parentIds[parentId]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
item.parentId = parentIds[parentId];
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
change.item = utils.addItemHash(item);
|
||||||
|
|
||||||
|
if (type === 'file') {
|
||||||
|
// create a fake change as a file content change
|
||||||
|
contentChange = {
|
||||||
|
item: {
|
||||||
|
id: `${properties.id}/content`,
|
||||||
|
type: 'content',
|
||||||
|
// Need a truthy value to force saving sync data
|
||||||
|
hash: 1,
|
||||||
|
},
|
||||||
|
syncData: {
|
||||||
|
id: `${change.fileId}/content`,
|
||||||
|
itemId: `${properties.id}/content`,
|
||||||
|
type: 'content',
|
||||||
|
// Need a truthy value to force downloading the content
|
||||||
|
hash: 1,
|
||||||
|
},
|
||||||
|
syncDataId: `${change.fileId}/content`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build sync data
|
// Build sync data
|
||||||
change.syncData = {
|
change.syncData = {
|
||||||
id: change.fileId,
|
id: change.fileId,
|
||||||
@ -163,13 +246,237 @@ export default providerRegistry.register({
|
|||||||
type: change.item.type,
|
type: change.item.type,
|
||||||
hash: change.item.hash,
|
hash: change.item.hash,
|
||||||
};
|
};
|
||||||
change.file = undefined;
|
} else {
|
||||||
|
// Item was removed
|
||||||
|
const syncData = store.getters['data/syncData'][change.fileId];
|
||||||
|
if (syncData && syncData.type === 'file') {
|
||||||
|
// create a fake change as a file content change
|
||||||
|
contentChange = {
|
||||||
|
syncDataId: `${change.fileId}/content`,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Push change
|
||||||
change.syncDataId = change.fileId;
|
change.syncDataId = change.fileId;
|
||||||
return true;
|
changes.push(change);
|
||||||
|
if (contentChange) {
|
||||||
|
changes.push(contentChange);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
changes.startPageToken = result.startPageToken;
|
changes.startPageToken = result.startPageToken;
|
||||||
return changes;
|
return changes;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
setAppliedChanges(changes) {
|
||||||
|
store.dispatch('data/patchLocalSettings', {
|
||||||
|
syncStartPageToken: changes.startPageToken,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
saveSimpleItem(item, syncData, ifNotTooLate) {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => {
|
||||||
|
const workspace = store.getters['workspace/currentWorkspace'];
|
||||||
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
|
if (item.type !== 'file' && item.type !== 'folder') {
|
||||||
|
return googleHelper.uploadFile(
|
||||||
|
syncToken,
|
||||||
|
JSON.stringify(item),
|
||||||
|
[workspace.dataFolderId],
|
||||||
|
{
|
||||||
|
folderId: workspace.folderId,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
syncData && syncData.id,
|
||||||
|
ifNotTooLate,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Type `file` or `folder`
|
||||||
|
const parentSyncData = store.getters['data/syncDataByItemId'][item.parentId];
|
||||||
|
return googleHelper.uploadFile(
|
||||||
|
syncToken,
|
||||||
|
item.name,
|
||||||
|
[parentSyncData ? parentSyncData.id : workspace.folderId],
|
||||||
|
{
|
||||||
|
id: item.id,
|
||||||
|
folderId: workspace.folderId,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
item.type === 'folder' ? googleHelper.folderMimeType : undefined,
|
||||||
|
syncData && syncData.id,
|
||||||
|
ifNotTooLate,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.then(file => ({
|
||||||
|
// Build sync data
|
||||||
|
id: file.id,
|
||||||
|
itemId: item.id,
|
||||||
|
type: item.type,
|
||||||
|
hash: item.hash,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
removeItem(syncData, ifNotTooLate) {
|
||||||
|
// Ignore content deletion
|
||||||
|
if (syncData.type === 'content') {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
|
return googleHelper.removeFile(syncToken, syncData.id, ifNotTooLate);
|
||||||
|
},
|
||||||
|
downloadContent(token, syncLocation) {
|
||||||
|
const syncData = store.getters['data/syncDataByItemId'][syncLocation.fileId];
|
||||||
|
const contentSyncData = store.getters['data/syncDataByItemId'][`${syncLocation.fileId}/content`];
|
||||||
|
if (!syncData || !contentSyncData) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return googleHelper.downloadFile(token, syncData.id)
|
||||||
|
.then((content) => {
|
||||||
|
const item = providerUtils.parseContent(content, syncLocation);
|
||||||
|
if (item.hash !== contentSyncData.hash) {
|
||||||
|
store.dispatch('data/patchSyncData', {
|
||||||
|
[contentSyncData.id]: {
|
||||||
|
...contentSyncData,
|
||||||
|
hash: item.hash,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
downloadData(dataId) {
|
||||||
|
const syncData = store.getters['data/syncDataByItemId'][dataId];
|
||||||
|
if (!syncData) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
|
return googleHelper.downloadFile(syncToken, syncData.id)
|
||||||
|
.then((content) => {
|
||||||
|
const item = JSON.parse(content);
|
||||||
|
if (item.hash !== syncData.hash) {
|
||||||
|
store.dispatch('data/patchSyncData', {
|
||||||
|
[syncData.id]: {
|
||||||
|
...syncData,
|
||||||
|
hash: item.hash,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
uploadContent(token, content, syncLocation, ifNotTooLate) {
|
||||||
|
const contentSyncData = store.getters['data/syncDataByItemId'][`${syncLocation.fileId}/content`];
|
||||||
|
if (contentSyncData && contentSyncData.hash === content.hash) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => {
|
||||||
|
const syncData = store.getters['data/syncDataByItemId'][syncLocation.fileId];
|
||||||
|
if (syncData) {
|
||||||
|
// Only update file media
|
||||||
|
return googleHelper.uploadFile(
|
||||||
|
token,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
providerUtils.serializeContent(content),
|
||||||
|
undefined,
|
||||||
|
syncData.id,
|
||||||
|
ifNotTooLate,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Create file with media
|
||||||
|
const workspace = store.getters['workspace/currentWorkspace'];
|
||||||
|
// Use deepCopy to freeze objects
|
||||||
|
const item = utils.deepCopy(store.state.file.itemMap[syncLocation.fileId]);
|
||||||
|
const parentSyncData = store.getters['data/syncDataByItemId'][item.parentId];
|
||||||
|
return googleHelper.uploadFile(
|
||||||
|
token,
|
||||||
|
item.name,
|
||||||
|
[parentSyncData ? parentSyncData.id : workspace.folderId],
|
||||||
|
{
|
||||||
|
id: item.id,
|
||||||
|
folderId: workspace.folderId,
|
||||||
|
},
|
||||||
|
providerUtils.serializeContent(content),
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
ifNotTooLate,
|
||||||
|
)
|
||||||
|
.then((file) => {
|
||||||
|
store.dispatch('data/patchSyncData', {
|
||||||
|
[file.id]: {
|
||||||
|
id: file.id,
|
||||||
|
itemId: item.id,
|
||||||
|
type: item.type,
|
||||||
|
hash: item.hash,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return file;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(file => store.dispatch('data/patchSyncData', {
|
||||||
|
[`${file.id}/content`]: {
|
||||||
|
// Build sync data
|
||||||
|
id: `${file.id}/content`,
|
||||||
|
itemId: content.id,
|
||||||
|
type: content.type,
|
||||||
|
hash: content.hash,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
.then(() => syncLocation);
|
||||||
|
},
|
||||||
|
uploadData(item, dataId, ifNotTooLate) {
|
||||||
|
const syncData = store.getters['data/syncDataByItemId'][dataId];
|
||||||
|
if (syncData && syncData.hash === item.hash) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
const workspace = store.getters['workspace/currentWorkspace'];
|
||||||
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
|
return googleHelper.uploadFile(
|
||||||
|
syncToken,
|
||||||
|
JSON.stringify({
|
||||||
|
id: item.id,
|
||||||
|
type: item.type,
|
||||||
|
hash: item.hash,
|
||||||
|
}),
|
||||||
|
[workspace.dataFolderId],
|
||||||
|
{
|
||||||
|
folderId: workspace.folderId,
|
||||||
|
},
|
||||||
|
JSON.stringify(item),
|
||||||
|
undefined,
|
||||||
|
syncData && syncData.id,
|
||||||
|
ifNotTooLate,
|
||||||
|
)
|
||||||
|
.then(file => store.dispatch('data/patchSyncData', {
|
||||||
|
[file.id]: {
|
||||||
|
// Build sync data
|
||||||
|
id: file.id,
|
||||||
|
itemId: item.id,
|
||||||
|
type: item.type,
|
||||||
|
hash: item.hash,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
listRevisions(token, fileId) {
|
||||||
|
const syncData = store.getters['data/syncDataByItemId'][fileId];
|
||||||
|
if (!syncData) {
|
||||||
|
return Promise.reject(); // No need for a proper error message.
|
||||||
|
}
|
||||||
|
return googleHelper.getFileRevisions(token, syncData.id)
|
||||||
|
.then(revisions => revisions.map(revision => ({
|
||||||
|
id: revision.id,
|
||||||
|
sub: revision.lastModifyingUser && revision.lastModifyingUser.permissionId,
|
||||||
|
created: new Date(revision.modifiedTime).getTime(),
|
||||||
|
})));
|
||||||
|
},
|
||||||
|
getRevisionContent(token, fileId, revisionId) {
|
||||||
|
const syncData = store.getters['data/syncDataByItemId'][fileId];
|
||||||
|
if (!syncData) {
|
||||||
|
return Promise.reject(); // No need for a proper error message.
|
||||||
|
}
|
||||||
|
return googleHelper.downloadFileRevision(token, syncData.id, revisionId)
|
||||||
|
.then(content => providerUtils.parseContent(content));
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -30,6 +30,7 @@ const checkIdToken = (idToken) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
folderMimeType: 'application/vnd.google-apps.folder',
|
||||||
request(token, options) {
|
request(token, options) {
|
||||||
return networkSvc.request({
|
return networkSvc.request({
|
||||||
...options,
|
...options,
|
||||||
@ -120,6 +121,53 @@ export default {
|
|||||||
raw: true,
|
raw: true,
|
||||||
}).then(res => res.body);
|
}).then(res => res.body);
|
||||||
},
|
},
|
||||||
|
removeFileInternal(refreshedToken, id, ifNotTooLate = cb => res => cb(res)) {
|
||||||
|
return Promise.resolve()
|
||||||
|
// Refreshing a token can take a while if an oauth window pops up, so check if it's too late
|
||||||
|
.then(ifNotTooLate(() => this.request(refreshedToken, {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: `https://www.googleapis.com/drive/v3/files/${id}`,
|
||||||
|
})));
|
||||||
|
},
|
||||||
|
getFileRevisionsInternal(refreshedToken, id) {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => {
|
||||||
|
const revisions = [];
|
||||||
|
const getPage = pageToken => this.request(refreshedToken, {
|
||||||
|
method: 'GET',
|
||||||
|
url: `https://www.googleapis.com/drive/v3/files/${id}/revisions`,
|
||||||
|
params: {
|
||||||
|
pageToken,
|
||||||
|
pageSize: 1000,
|
||||||
|
fields: 'nextPageToken,revisions(id,modifiedTime,lastModifyingUser/permissionId,lastModifyingUser/displayName,lastModifyingUser/photoLink)',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
res.body.revisions.forEach((revision) => {
|
||||||
|
store.commit('userInfo/addItem', {
|
||||||
|
id: revision.lastModifyingUser.permissionId,
|
||||||
|
name: revision.lastModifyingUser.displayName,
|
||||||
|
imageUrl: revision.lastModifyingUser.photoLink,
|
||||||
|
});
|
||||||
|
revisions.push(revision);
|
||||||
|
});
|
||||||
|
if (res.body.nextPageToken) {
|
||||||
|
return getPage(res.body.nextPageToken);
|
||||||
|
}
|
||||||
|
return revisions;
|
||||||
|
});
|
||||||
|
|
||||||
|
return getPage();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
downloadFileRevisionInternal(refreshedToken, fileId, revisionId) {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => this.request(refreshedToken, {
|
||||||
|
method: 'GET',
|
||||||
|
url: `https://www.googleapis.com/drive/v3/files/${fileId}/revisions/${revisionId}?alt=media`,
|
||||||
|
raw: true,
|
||||||
|
}).then(res => res.body));
|
||||||
|
},
|
||||||
getUser(userId) {
|
getUser(userId) {
|
||||||
return networkSvc.request({
|
return networkSvc.request({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -168,7 +216,7 @@ export default {
|
|||||||
expiresOn: Date.now() + (data.expiresIn * 1000),
|
expiresOn: Date.now() + (data.expiresIn * 1000),
|
||||||
idToken: data.idToken,
|
idToken: data.idToken,
|
||||||
sub: `${res.body.sub}`,
|
sub: `${res.body.sub}`,
|
||||||
isLogin: !store.getters['workspace/loginToken'] &&
|
isLogin: !store.getters['workspace/mainWorkspaceToken'] &&
|
||||||
scopes.indexOf('https://www.googleapis.com/auth/drive.appdata') !== -1,
|
scopes.indexOf('https://www.googleapis.com/auth/drive.appdata') !== -1,
|
||||||
isSponsor: false,
|
isSponsor: false,
|
||||||
isDrive: scopes.indexOf('https://www.googleapis.com/auth/drive') !== -1 ||
|
isDrive: scopes.indexOf('https://www.googleapis.com/auth/drive') !== -1 ||
|
||||||
@ -297,20 +345,24 @@ export default {
|
|||||||
addPhotosAccount() {
|
addPhotosAccount() {
|
||||||
return this.startOauth2(photosScopes);
|
return this.startOauth2(photosScopes);
|
||||||
},
|
},
|
||||||
getChanges(token, startPageToken, spaces) {
|
getChanges(token, startPageToken, isAppData) {
|
||||||
const result = {
|
const result = {
|
||||||
changes: [],
|
changes: [],
|
||||||
};
|
};
|
||||||
return this.refreshToken(token, driveAppDataScopes)
|
let fileFields = 'file/name';
|
||||||
|
if (!isAppData) {
|
||||||
|
fileFields += ',file/parents,file/mimeType,file/appProperties';
|
||||||
|
}
|
||||||
|
return this.refreshToken(token, isAppData ? driveAppDataScopes : getDriveScopes(token))
|
||||||
.then((refreshedToken) => {
|
.then((refreshedToken) => {
|
||||||
const getPage = (pageToken = '1') => this.request(refreshedToken, {
|
const getPage = (pageToken = '1') => this.request(refreshedToken, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: 'https://www.googleapis.com/drive/v3/changes',
|
url: 'https://www.googleapis.com/drive/v3/changes',
|
||||||
params: {
|
params: {
|
||||||
pageToken,
|
pageToken,
|
||||||
spaces,
|
spaces: isAppData ? 'appDataFolder' : 'drive',
|
||||||
pageSize: 1000,
|
pageSize: 1000,
|
||||||
fields: 'nextPageToken,newStartPageToken,changes(fileId,removed,file/name,file/appProperties)',
|
fields: `nextPageToken,newStartPageToken,changes(fileId,${fileFields})`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@ -328,12 +380,28 @@ export default {
|
|||||||
uploadFile(token, name, parents, appProperties, media, mediaType, fileId, ifNotTooLate) {
|
uploadFile(token, name, parents, appProperties, media, mediaType, fileId, ifNotTooLate) {
|
||||||
return this.refreshToken(token, getDriveScopes(token))
|
return this.refreshToken(token, getDriveScopes(token))
|
||||||
.then(refreshedToken => this.uploadFileInternal(
|
.then(refreshedToken => this.uploadFileInternal(
|
||||||
refreshedToken, name, parents, appProperties, media, mediaType, fileId, ifNotTooLate));
|
refreshedToken,
|
||||||
|
name,
|
||||||
|
parents,
|
||||||
|
appProperties,
|
||||||
|
media,
|
||||||
|
mediaType,
|
||||||
|
fileId,
|
||||||
|
ifNotTooLate,
|
||||||
|
));
|
||||||
},
|
},
|
||||||
uploadAppDataFile(token, name, parents, appProperties, media, fileId, ifNotTooLate) {
|
uploadAppDataFile(token, name, media, fileId, ifNotTooLate) {
|
||||||
return this.refreshToken(token, driveAppDataScopes)
|
return this.refreshToken(token, driveAppDataScopes)
|
||||||
.then(refreshedToken => this.uploadFileInternal(
|
.then(refreshedToken => this.uploadFileInternal(
|
||||||
refreshedToken, name, parents, appProperties, media, undefined, fileId, ifNotTooLate));
|
refreshedToken,
|
||||||
|
name,
|
||||||
|
['appDataFolder'],
|
||||||
|
undefined,
|
||||||
|
media,
|
||||||
|
undefined,
|
||||||
|
fileId,
|
||||||
|
ifNotTooLate,
|
||||||
|
));
|
||||||
},
|
},
|
||||||
getFile(token, id) {
|
getFile(token, id) {
|
||||||
return this.refreshToken(token, getDriveScopes(token))
|
return this.refreshToken(token, getDriveScopes(token))
|
||||||
@ -354,52 +422,31 @@ export default {
|
|||||||
return this.refreshToken(token, driveAppDataScopes)
|
return this.refreshToken(token, driveAppDataScopes)
|
||||||
.then(refreshedToken => this.downloadFileInternal(refreshedToken, id));
|
.then(refreshedToken => this.downloadFileInternal(refreshedToken, id));
|
||||||
},
|
},
|
||||||
|
removeFile(token, id, ifNotTooLate) {
|
||||||
|
return this.refreshToken(token, getDriveScopes(token))
|
||||||
|
.then(refreshedToken => this.removeFileInternal(refreshedToken, id, ifNotTooLate));
|
||||||
|
},
|
||||||
removeAppDataFile(token, id, ifNotTooLate = cb => res => cb(res)) {
|
removeAppDataFile(token, id, ifNotTooLate = cb => res => cb(res)) {
|
||||||
return this.refreshToken(token, driveAppDataScopes)
|
return this.refreshToken(token, driveAppDataScopes)
|
||||||
// Refreshing a token can take a while if an oauth window pops up, so check if it's too late
|
.then(refreshedToken => this.removeFileInternal(refreshedToken, id, ifNotTooLate));
|
||||||
.then(ifNotTooLate(refreshedToken => this.request(refreshedToken, {
|
|
||||||
method: 'DELETE',
|
|
||||||
url: `https://www.googleapis.com/drive/v3/files/${id}`,
|
|
||||||
})));
|
|
||||||
},
|
},
|
||||||
getFileRevisions(token, id) {
|
getFileRevisions(token, id) {
|
||||||
|
return this.refreshToken(token, getDriveScopes(token))
|
||||||
|
.then(refreshedToken => this.getFileRevisionsInternal(refreshedToken, id));
|
||||||
|
},
|
||||||
|
getAppDataFileRevisions(token, id) {
|
||||||
return this.refreshToken(token, driveAppDataScopes)
|
return this.refreshToken(token, driveAppDataScopes)
|
||||||
.then((refreshedToken) => {
|
.then(refreshedToken => this.getFileRevisionsInternal(refreshedToken, id));
|
||||||
const revisions = [];
|
|
||||||
const getPage = pageToken => this.request(refreshedToken, {
|
|
||||||
method: 'GET',
|
|
||||||
url: `https://www.googleapis.com/drive/v3/files/${id}/revisions`,
|
|
||||||
params: {
|
|
||||||
pageToken,
|
|
||||||
pageSize: 1000,
|
|
||||||
fields: 'nextPageToken,revisions(id,modifiedTime,lastModifyingUser/permissionId,lastModifyingUser/displayName,lastModifyingUser/photoLink)',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
res.body.revisions.forEach((revision) => {
|
|
||||||
store.commit('userInfo/addItem', {
|
|
||||||
id: revision.lastModifyingUser.permissionId,
|
|
||||||
name: revision.lastModifyingUser.displayName,
|
|
||||||
imageUrl: revision.lastModifyingUser.photoLink,
|
|
||||||
});
|
|
||||||
revisions.push(revision);
|
|
||||||
});
|
|
||||||
if (res.body.nextPageToken) {
|
|
||||||
return getPage(res.body.nextPageToken);
|
|
||||||
}
|
|
||||||
return revisions;
|
|
||||||
});
|
|
||||||
|
|
||||||
return getPage();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
downloadFileRevision(token, fileId, revisionId) {
|
downloadFileRevision(token, fileId, revisionId) {
|
||||||
|
return this.refreshToken(token, getDriveScopes(token))
|
||||||
|
.then(refreshedToken => this.downloadFileRevisionInternal(
|
||||||
|
refreshedToken, fileId, revisionId));
|
||||||
|
},
|
||||||
|
downloadAppDataFileRevision(token, fileId, revisionId) {
|
||||||
return this.refreshToken(token, driveAppDataScopes)
|
return this.refreshToken(token, driveAppDataScopes)
|
||||||
.then(refreshedToken => this.request(refreshedToken, {
|
.then(refreshedToken => this.downloadFileRevisionInternal(
|
||||||
method: 'GET',
|
refreshedToken, fileId, revisionId));
|
||||||
url: `https://www.googleapis.com/drive/v3/files/${fileId}/revisions/${revisionId}?alt=media`,
|
|
||||||
raw: true,
|
|
||||||
}).then(res => res.body));
|
|
||||||
},
|
},
|
||||||
uploadBlogger(
|
uploadBlogger(
|
||||||
token, blogUrl, blogId, postId, title, content, labels, isDraft, published, isPage,
|
token, blogUrl, blogId, postId, title, content, labels, isDraft, published, isPage,
|
||||||
@ -475,6 +522,7 @@ export default {
|
|||||||
let picker;
|
let picker;
|
||||||
const pickerBuilder = new google.picker.PickerBuilder()
|
const pickerBuilder = new google.picker.PickerBuilder()
|
||||||
.setOAuthToken(refreshedToken.accessToken)
|
.setOAuthToken(refreshedToken.accessToken)
|
||||||
|
.hideTitleBar()
|
||||||
.setCallback((data) => {
|
.setCallback((data) => {
|
||||||
switch (data[google.picker.Response.ACTION]) {
|
switch (data[google.picker.Response.ACTION]) {
|
||||||
case google.picker.Action.PICKED:
|
case google.picker.Action.PICKED:
|
||||||
@ -488,27 +536,35 @@ export default {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
default:
|
default:
|
||||||
case 'doc': {
|
case 'doc': {
|
||||||
const view = new google.picker.DocsView(google.picker.ViewId.DOCS);
|
const addView = (hasRootParent) => {
|
||||||
view.setParent('root');
|
const view = new google.picker.DocsView(google.picker.ViewId.DOCS);
|
||||||
view.setIncludeFolders(true);
|
if (hasRootParent) {
|
||||||
view.setMimeTypes([
|
view.setParent('root');
|
||||||
'text/plain',
|
}
|
||||||
'text/x-markdown',
|
view.setMimeTypes([
|
||||||
'application/octet-stream',
|
'text/plain',
|
||||||
].join(','));
|
'text/x-markdown',
|
||||||
pickerBuilder.enableFeature(google.picker.Feature.NAV_HIDDEN);
|
'application/octet-stream',
|
||||||
|
].join(','));
|
||||||
|
pickerBuilder.addView(view);
|
||||||
|
};
|
||||||
pickerBuilder.enableFeature(google.picker.Feature.MULTISELECT_ENABLED);
|
pickerBuilder.enableFeature(google.picker.Feature.MULTISELECT_ENABLED);
|
||||||
pickerBuilder.addView(view);
|
addView(false);
|
||||||
|
// addView(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'folder': {
|
case 'folder': {
|
||||||
const view = new google.picker.DocsView(google.picker.ViewId.FOLDERS);
|
const addView = (hasRootParent) => {
|
||||||
view.setParent('root');
|
const view = new google.picker.DocsView(google.picker.ViewId.FOLDERS);
|
||||||
view.setIncludeFolders(true);
|
if (hasRootParent) {
|
||||||
view.setSelectFolderEnabled(true);
|
view.setParent('root');
|
||||||
view.setMimeTypes('application/vnd.google-apps.folder');
|
}
|
||||||
pickerBuilder.enableFeature(google.picker.Feature.NAV_HIDDEN);
|
view.setSelectFolderEnabled(true);
|
||||||
pickerBuilder.addView(view);
|
view.setMimeTypes(this.folderMimeType);
|
||||||
|
pickerBuilder.addView(view);
|
||||||
|
};
|
||||||
|
addView(false);
|
||||||
|
// addView(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'img': {
|
case 'img': {
|
||||||
|
@ -51,11 +51,7 @@ export default {
|
|||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.hash = utils.hash(utils.serializeObject({
|
return utils.addItemHash(result);
|
||||||
...result,
|
|
||||||
hash: undefined,
|
|
||||||
}));
|
|
||||||
return result;
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Find and open a file location that fits the criteria
|
* Find and open a file location that fits the criteria
|
||||||
|
@ -92,35 +92,39 @@ function cleanSyncedContent(syncedContent) {
|
|||||||
function applyChanges(changes) {
|
function applyChanges(changes) {
|
||||||
const storeItemMap = { ...store.getters.allItemMap };
|
const storeItemMap = { ...store.getters.allItemMap };
|
||||||
const syncData = { ...store.getters['data/syncData'] };
|
const syncData = { ...store.getters['data/syncData'] };
|
||||||
let syncDataChanged = false;
|
let saveSyncData = false;
|
||||||
|
|
||||||
changes.forEach((change) => {
|
changes.forEach((change) => {
|
||||||
const existingSyncData = syncData[change.syncDataId];
|
const existingSyncData = syncData[change.syncDataId];
|
||||||
const existingItem = existingSyncData && storeItemMap[existingSyncData.itemId];
|
const existingItem = existingSyncData && storeItemMap[existingSyncData.itemId];
|
||||||
if (change.removed && existingSyncData) {
|
if (!change.item && existingSyncData) {
|
||||||
|
// Item was removed
|
||||||
|
delete syncData[change.syncDataId];
|
||||||
|
saveSyncData = true;
|
||||||
if (existingItem) {
|
if (existingItem) {
|
||||||
// Remove object from the store
|
// Remove object from the store
|
||||||
store.commit(`${existingItem.type}/deleteItem`, existingItem.id);
|
store.commit(`${existingItem.type}/deleteItem`, existingItem.id);
|
||||||
delete storeItemMap[existingItem.id];
|
delete storeItemMap[existingItem.id];
|
||||||
}
|
}
|
||||||
delete syncData[change.syncDataId];
|
} else if (change.item && change.item.hash) {
|
||||||
syncDataChanged = true;
|
// Item was modifed
|
||||||
} else if (!change.removed && change.item && change.item.hash) {
|
|
||||||
if (!existingSyncData || (existingSyncData.hash !== change.item.hash && (
|
|
||||||
!existingItem || existingItem.hash !== change.item.hash
|
|
||||||
))) {
|
|
||||||
// Put object in the store, except for content and data which will be merge later
|
|
||||||
if (change.item.type !== 'content' && change.item.type !== 'data') {
|
|
||||||
store.commit(`${change.item.type}/setItem`, change.item);
|
|
||||||
storeItemMap[change.item.id] = change.item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
syncData[change.syncDataId] = change.syncData;
|
syncData[change.syncDataId] = change.syncData;
|
||||||
syncDataChanged = true;
|
saveSyncData = true;
|
||||||
|
if (
|
||||||
|
// If no sync data or existing one is different
|
||||||
|
(existingSyncData || {}).hash !== change.item.hash
|
||||||
|
// And no existing item or existing item is different
|
||||||
|
&& (existingItem || {}).hash !== change.item.hash
|
||||||
|
// And item is not content nor data, which will be merged later
|
||||||
|
&& change.item.type !== 'content' && change.item.type !== 'data'
|
||||||
|
) {
|
||||||
|
store.commit(`${change.item.type}/setItem`, change.item);
|
||||||
|
storeItemMap[change.item.id] = change.item;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (syncDataChanged) {
|
if (saveSyncData) {
|
||||||
store.dispatch('data/setSyncData', syncData);
|
store.dispatch('data/setSyncData', syncData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -405,8 +409,7 @@ function syncDataItem(dataId) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncToken = store.getters['workspace/syncToken'];
|
return syncProvider.downloadData(dataId)
|
||||||
return syncToken && syncProvider.downloadData(syncToken, dataId)
|
|
||||||
.then((serverItem = null) => {
|
.then((serverItem = null) => {
|
||||||
const dataSyncData = store.getters['data/dataSyncData'][dataId];
|
const dataSyncData = store.getters['data/dataSyncData'][dataId];
|
||||||
let mergedItem = (() => {
|
let mergedItem = (() => {
|
||||||
@ -452,11 +455,7 @@ function syncDataItem(dataId) {
|
|||||||
if (serverItem && serverItem.hash === mergedItem.hash) {
|
if (serverItem && serverItem.hash === mergedItem.hash) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return syncProvider.uploadData(
|
return syncProvider.uploadData(mergedItem, dataId);
|
||||||
syncToken,
|
|
||||||
mergedItem,
|
|
||||||
dataId,
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
store.dispatch('data/patchDataSyncData', {
|
store.dispatch('data/patchDataSyncData', {
|
||||||
@ -469,22 +468,24 @@ function syncDataItem(dataId) {
|
|||||||
/**
|
/**
|
||||||
* Sync the whole workspace with the main provider and the current file explicit locations.
|
* Sync the whole workspace with the main provider and the current file explicit locations.
|
||||||
*/
|
*/
|
||||||
function syncWorkspace(workspace, syncToken) {
|
function syncWorkspace() {
|
||||||
|
const workspace = store.getters['workspace/currentWorkspace'];
|
||||||
const syncContext = new SyncContext();
|
const syncContext = new SyncContext();
|
||||||
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Store the sub in the DB since it's not safely stored in the token
|
// Store the sub in the DB since it's not safely stored in the token
|
||||||
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
const localSettings = store.getters['data/localSettings'];
|
const localSettings = store.getters['data/localSettings'];
|
||||||
if (!localSettings.syncSub) {
|
if (!localSettings.syncSub) {
|
||||||
store.dispatch('data/patchLocalSettings', {
|
store.dispatch('data/patchLocalSettings', {
|
||||||
workspaceSyncSub: syncToken.sub,
|
syncSub: syncToken.sub,
|
||||||
});
|
});
|
||||||
} else if (localSettings.syncSub !== syncToken.sub) {
|
} else if (localSettings.syncSub !== syncToken.sub) {
|
||||||
throw new Error('Synchronization failed due to token inconsistency.');
|
throw new Error('Synchronization failed due to token inconsistency.');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(() => syncProvider.getChanges(syncToken))
|
.then(() => syncProvider.getChanges())
|
||||||
.then((changes) => {
|
.then((changes) => {
|
||||||
// Apply changes
|
// Apply changes
|
||||||
applyChanges(changes);
|
applyChanges(changes);
|
||||||
@ -509,15 +510,16 @@ function syncWorkspace(workspace, syncToken) {
|
|||||||
// Deal with contents and data later
|
// Deal with contents and data later
|
||||||
};
|
};
|
||||||
const syncDataByItemId = store.getters['data/syncDataByItemId'];
|
const syncDataByItemId = store.getters['data/syncDataByItemId'];
|
||||||
let result;
|
let promise;
|
||||||
Object.entries(storeItemMap).some(([id, item]) => {
|
Object.entries(storeItemMap).some(([id, item]) => {
|
||||||
const existingSyncData = syncDataByItemId[id];
|
const existingSyncData = syncDataByItemId[id];
|
||||||
if ((!existingSyncData || existingSyncData.hash !== item.hash) &&
|
if ((!existingSyncData || existingSyncData.hash !== item.hash)
|
||||||
// Add file if content has been uploaded
|
// Add file/folder if parent has been added
|
||||||
(item.type !== 'file' || syncDataByItemId[`${id}/content`])
|
&& (!storeItemMap[item.parentId] || syncDataByItemId[item.parentId])
|
||||||
|
// Add file if content has been added
|
||||||
|
&& (item.type !== 'file' || syncDataByItemId[`${id}/content`])
|
||||||
) {
|
) {
|
||||||
result = syncProvider.saveItem(
|
promise = syncProvider.saveSimpleItem(
|
||||||
syncToken,
|
|
||||||
// Use deepCopy to freeze objects
|
// Use deepCopy to freeze objects
|
||||||
utils.deepCopy(item),
|
utils.deepCopy(item),
|
||||||
utils.deepCopy(existingSyncData),
|
utils.deepCopy(existingSyncData),
|
||||||
@ -528,9 +530,9 @@ function syncWorkspace(workspace, syncToken) {
|
|||||||
}))
|
}))
|
||||||
.then(() => saveNextItem());
|
.then(() => saveNextItem());
|
||||||
}
|
}
|
||||||
return result;
|
return promise;
|
||||||
});
|
});
|
||||||
return result;
|
return promise;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Called until no item to remove
|
// Called until no item to remove
|
||||||
@ -543,7 +545,7 @@ function syncWorkspace(workspace, syncToken) {
|
|||||||
...store.state.content.itemMap,
|
...store.state.content.itemMap,
|
||||||
};
|
};
|
||||||
const syncData = store.getters['data/syncData'];
|
const syncData = store.getters['data/syncData'];
|
||||||
let result;
|
let promise;
|
||||||
Object.entries(syncData).some(([, existingSyncData]) => {
|
Object.entries(syncData).some(([, existingSyncData]) => {
|
||||||
if (!storeItemMap[existingSyncData.itemId] &&
|
if (!storeItemMap[existingSyncData.itemId] &&
|
||||||
// We don't want to delete data items, especially on first sync
|
// We don't want to delete data items, especially on first sync
|
||||||
@ -553,8 +555,8 @@ function syncWorkspace(workspace, syncToken) {
|
|||||||
) {
|
) {
|
||||||
// Use deepCopy to freeze objects
|
// Use deepCopy to freeze objects
|
||||||
const syncDataToRemove = utils.deepCopy(existingSyncData);
|
const syncDataToRemove = utils.deepCopy(existingSyncData);
|
||||||
result = syncProvider
|
promise = syncProvider
|
||||||
.removeItem(syncToken, syncDataToRemove, ifNotTooLate)
|
.removeItem(syncDataToRemove, ifNotTooLate)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const syncDataCopy = { ...store.getters['data/syncData'] };
|
const syncDataCopy = { ...store.getters['data/syncData'] };
|
||||||
delete syncDataCopy[syncDataToRemove.id];
|
delete syncDataCopy[syncDataToRemove.id];
|
||||||
@ -562,9 +564,9 @@ function syncWorkspace(workspace, syncToken) {
|
|||||||
})
|
})
|
||||||
.then(() => removeNextItem());
|
.then(() => removeNextItem());
|
||||||
}
|
}
|
||||||
return result;
|
return promise;
|
||||||
});
|
});
|
||||||
return result;
|
return promise;
|
||||||
});
|
});
|
||||||
|
|
||||||
const getOneFileIdToSync = () => {
|
const getOneFileIdToSync = () => {
|
||||||
@ -621,14 +623,14 @@ function syncWorkspace(workspace, syncToken) {
|
|||||||
() => {
|
() => {
|
||||||
if (syncContext.restart) {
|
if (syncContext.restart) {
|
||||||
// Restart sync
|
// Restart sync
|
||||||
return syncWorkspace(workspace, syncToken);
|
return syncWorkspace();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
if (err && err.message === 'TOO_LATE') {
|
if (err && err.message === 'TOO_LATE') {
|
||||||
// Restart sync
|
// Restart sync
|
||||||
return syncWorkspace(workspace, syncToken);
|
return syncWorkspace();
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
@ -676,9 +678,7 @@ function requestSync() {
|
|||||||
Promise.resolve()
|
Promise.resolve()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (isWorkspaceSyncPossible()) {
|
if (isWorkspaceSyncPossible()) {
|
||||||
return syncWorkspace(
|
return syncWorkspace();
|
||||||
store.getters['workspace/currentWorkspace'],
|
|
||||||
store.getters['workspace/syncToken']);
|
|
||||||
}
|
}
|
||||||
if (hasCurrentFileSyncLocations()) {
|
if (hasCurrentFileSyncLocations()) {
|
||||||
// Only sync current file if workspace sync is unavailable.
|
// Only sync current file if workspace sync is unavailable.
|
||||||
|
@ -85,6 +85,17 @@ export default {
|
|||||||
}
|
}
|
||||||
return hash;
|
return hash;
|
||||||
},
|
},
|
||||||
|
addItemHash(item) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
hash: this.hash(this.serializeObject({
|
||||||
|
...item,
|
||||||
|
// These properties must not be part of the hash
|
||||||
|
history: undefined,
|
||||||
|
hash: undefined,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
},
|
||||||
encodeBase64(str) {
|
encodeBase64(str) {
|
||||||
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
|
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
|
||||||
(match, p1) => String.fromCharCode(`0x${p1}`)));
|
(match, p1) => String.fromCharCode(`0x${p1}`)));
|
||||||
|
@ -105,16 +105,12 @@ export default {
|
|||||||
const data = typeof value.data === 'object'
|
const data = typeof value.data === 'object'
|
||||||
? Object.assign(emptyItem.data, value.data)
|
? Object.assign(emptyItem.data, value.data)
|
||||||
: value.data;
|
: value.data;
|
||||||
const item = {
|
|
||||||
|
// Make item with hash
|
||||||
|
const item = utils.addItemHash({
|
||||||
...emptyItem,
|
...emptyItem,
|
||||||
data,
|
data,
|
||||||
};
|
});
|
||||||
|
|
||||||
// Calculate item hash
|
|
||||||
item.hash = utils.hash(utils.serializeObject({
|
|
||||||
...item,
|
|
||||||
hash: undefined,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Store item in itemMap or lsItemMap if its stored in the localStorage
|
// Store item in itemMap or lsItemMap if its stored in the localStorage
|
||||||
Vue.set(lsItemIdSet.has(item.id) ? state.lsItemMap : state.itemMap, item.id, item);
|
Vue.set(lsItemIdSet.has(item.id) ? state.lsItemMap : state.itemMap, item.id, item);
|
||||||
|
@ -76,8 +76,8 @@ export default {
|
|||||||
rejectText: 'No',
|
rejectText: 'No',
|
||||||
}),
|
}),
|
||||||
removeWorkspace: ({ dispatch }) => dispatch('open', {
|
removeWorkspace: ({ dispatch }) => dispatch('open', {
|
||||||
content: '<p>This will clean your workspace locally. Are you sure?</p>',
|
content: '<p>You are about to remove a workspace locally. Are you sure?</p>',
|
||||||
resolveText: 'Yes, clean',
|
resolveText: 'Yes, remove',
|
||||||
rejectText: 'No',
|
rejectText: 'No',
|
||||||
}),
|
}),
|
||||||
reset: ({ dispatch }) => dispatch('open', {
|
reset: ({ dispatch }) => dispatch('open', {
|
||||||
@ -100,7 +100,7 @@ export default {
|
|||||||
signInForSponsorship: ({ dispatch }, { onResolve }) => dispatch('open', {
|
signInForSponsorship: ({ dispatch }, { onResolve }) => dispatch('open', {
|
||||||
type: 'signInForSponsorship',
|
type: 'signInForSponsorship',
|
||||||
content: `<p>You have to sign in with Google to enable your sponsorship.</p>
|
content: `<p>You have to sign in with Google to enable your sponsorship.</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',
|
resolveText: 'Ok, sign in',
|
||||||
rejectText: 'Cancel',
|
rejectText: 'Cancel',
|
||||||
onResolve,
|
onResolve,
|
||||||
|
Loading…
Reference in New Issue
Block a user