store renamings
This commit is contained in:
parent
e05e7717eb
commit
7a87015af1
@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="explorer-node" :class="{'explorer-node--selected': isSelected, 'explorer-node--open': isOpen, 'explorer-node--drag-target': isDragTargetFolder}" @dragover.prevent @dragenter.stop="node.noDrop || setDragTarget(node.item.id)" @dragleave.stop="isDragTarget && setDragTargetId()" @drop.prevent.stop="onDrop" @contextmenu="onContextMenu">
|
<div class="explorer-node" :class="{'explorer-node--selected': isSelected, 'explorer-node--folder': node.isFolder, 'explorer-node--open': isOpen, 'explorer-node--trash': node.isTrash, 'explorer-node--temp': node.isTemp, 'explorer-node--drag-target': isDragTargetFolder}" @dragover.prevent @dragenter.stop="node.noDrop || setDragTarget(node.item.id)" @dragleave.stop="isDragTarget && setDragTargetId()" @drop.prevent.stop="onDrop" @contextmenu="onContextMenu">
|
||||||
<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>
|
<div class="explorer-node__item-editor" v-if="isEditing" :style="{paddingLeft: leftPadding}" draggable="true" @dragstart.stop.prevent>
|
||||||
<input type="text" class="text-input" v-focus @blur="submitEdit()" @keydown.stop @keydown.enter="submitEdit()" @keydown.esc="submitEdit(true)" v-model="editingNodeName">
|
<input type="text" class="text-input" v-focus @blur="submitEdit()" @keydown.stop @keydown.enter="submitEdit()" @keydown.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()" draggable="true" @dragstart.stop="setDragSourceId" @dragend.stop="setDragTargetId()">
|
<div class="explorer-node__item" v-else :style="{paddingLeft: leftPadding}" @click="select()" draggable="true" @dragstart.stop="setDragSourceId" @dragend.stop="setDragTargetId()">
|
||||||
{{node.item.name}}
|
{{node.item.name}}
|
||||||
<icon-provider class="explorer-node__location" v-for="location in node.locations" :key="location.id" :provider-id="location.providerId"></icon-provider>
|
<icon-provider class="explorer-node__location" v-for="location in node.locations" :key="location.id" :provider-id="location.providerId"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<div class="explorer-node__children" v-if="node.isFolder && isOpen">
|
<div class="explorer-node__children" v-if="node.isFolder && isOpen">
|
||||||
<explorer-node v-for="node in node.folders" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
|
<explorer-node v-for="node in node.folders" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
|
||||||
<div v-if="newChild" class="explorer-node__new-child" :class="['explorer-node__new-child--' + newChild.item.type]" :style="{paddingLeft: childLeftPadding}">
|
<div v-if="newChild" class="explorer-node__new-child" :class="{'explorer-node__new-child--folder': newChild.isFolder}" :style="{paddingLeft: childLeftPadding}">
|
||||||
<input type="text" class="text-input" v-focus @blur="submitNewChild()" @keydown.stop @keydown.enter="submitNewChild()" @keydown.esc="submitNewChild(true)" v-model.trim="newChildName">
|
<input type="text" class="text-input" v-focus @blur="submitNewChild()" @keydown.stop @keydown.enter="submitNewChild()" @keydown.esc="submitNewChild(true)" v-model.trim="newChildName">
|
||||||
</div>
|
</div>
|
||||||
<explorer-node v-for="node in node.files" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
|
<explorer-node v-for="node in node.files" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
|
||||||
@ -227,18 +227,26 @@ $item-font-size: 14px;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.explorer-node__item--folder,
|
.explorer-node--trash,
|
||||||
.explorer-node__item-editor--folder,
|
.explorer-node--temp {
|
||||||
|
color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.explorer-node--folder > .explorer-node__item,
|
||||||
|
.explorer-node--folder > .explorer-node__item-editor,
|
||||||
.explorer-node__new-child--folder {
|
.explorer-node__new-child--folder {
|
||||||
&::before {
|
&::before {
|
||||||
content: '▹';
|
content: '▹';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: -13px;
|
margin-left: -13px;
|
||||||
|
|
||||||
.explorer-node--open > & {
|
|
||||||
content: '▾';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.explorer-node--folder.explorer-node--open > .explorer-node__item,
|
||||||
|
.explorer-node--folder.explorer-node--open > .explorer-node__item-editor {
|
||||||
|
&::before {
|
||||||
|
content: '▾';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$new-child-height: 25px;
|
$new-child-height: 25px;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="modal" v-if="config" @keydown.esc="onEscape" @keydown.tab="onTab">
|
<div class="modal" v-if="config" @keydown.esc="onEscape" @keydown.tab="onTab" @focusin="onFocusInOut" @focusout="onFocusInOut">
|
||||||
<component v-if="currentModalComponent" :is="currentModalComponent"></component>
|
<component v-if="currentModalComponent" :is="currentModalComponent"></component>
|
||||||
<modal-inner v-else aria-label="Dialog">
|
<modal-inner v-else aria-label="Dialog">
|
||||||
<div class="modal__content" v-html="simpleModal.contentHtml(config)"></div>
|
<div class="modal__content" v-html="simpleModal.contentHtml(config)"></div>
|
||||||
@ -138,8 +138,8 @@ export default {
|
|||||||
const isFocusIn = evt.type === 'focusin';
|
const isFocusIn = evt.type === 'focusin';
|
||||||
if (evt.target.parentNode && evt.target.parentNode.parentNode) {
|
if (evt.target.parentNode && evt.target.parentNode.parentNode) {
|
||||||
// Focus effect
|
// Focus effect
|
||||||
if (evt.target.parentNode.classList.contains('form-entry__field') &&
|
if (evt.target.parentNode.classList.contains('form-entry__field')
|
||||||
evt.target.parentNode.parentNode.classList.contains('form-entry')) {
|
&& evt.target.parentNode.parentNode.classList.contains('form-entry')) {
|
||||||
evt.target.parentNode.parentNode.classList.toggle('form-entry--focused', isFocusIn);
|
evt.target.parentNode.parentNode.classList.toggle('form-entry--focused', isFocusIn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,19 +159,15 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.config,
|
() => this.config,
|
||||||
() => {
|
(isOpen) => {
|
||||||
if (this.$el) {
|
if (isOpen) {
|
||||||
window.addEventListener('focusin', this.onFocusInOut);
|
|
||||||
window.addEventListener('focusout', this.onFocusInOut);
|
|
||||||
const tabbables = getTabbables(this.$el);
|
const tabbables = getTabbables(this.$el);
|
||||||
if (tabbables[0]) {
|
if (tabbables[0]) {
|
||||||
tabbables[0].focus();
|
tabbables[0].focus();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
window.removeEventListener('focusin', this.onFocusInOut);
|
|
||||||
window.removeEventListener('focusout', this.onFocusInOut);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -10,7 +10,7 @@ export default {
|
|||||||
props: ['userId'],
|
props: ['userId'],
|
||||||
computed: {
|
computed: {
|
||||||
url() {
|
url() {
|
||||||
const userInfo = this.$store.state.userInfo.itemMap[this.userId];
|
const userInfo = this.$store.state.userInfo.itemsById[this.userId];
|
||||||
return userInfo && userInfo.imageUrl && `url('${userInfo.imageUrl}')`;
|
return userInfo && userInfo.imageUrl && `url('${userInfo.imageUrl}')`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -9,7 +9,7 @@ export default {
|
|||||||
props: ['userId'],
|
props: ['userId'],
|
||||||
computed: {
|
computed: {
|
||||||
name() {
|
name() {
|
||||||
const userInfo = this.$store.state.userInfo.itemMap[this.userId];
|
const userInfo = this.$store.state.userInfo.itemsById[this.userId];
|
||||||
return userInfo ? userInfo.name : 'Someone';
|
return userInfo ? userInfo.name : 'Someone';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -89,7 +89,7 @@ export default {
|
|||||||
async templates() {
|
async templates() {
|
||||||
try {
|
try {
|
||||||
const { templates } = await this.$store.dispatch('modal/open', 'templates');
|
const { templates } = await this.$store.dispatch('modal/open', 'templates');
|
||||||
this.$store.dispatch('data/setTemplates', templates);
|
this.$store.dispatch('data/setTemplatesById', templates);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Cancel
|
// Cancel
|
||||||
}
|
}
|
||||||
|
@ -146,22 +146,22 @@ export default {
|
|||||||
return this.$store.getters['file/current'].name;
|
return this.$store.getters['file/current'].name;
|
||||||
},
|
},
|
||||||
googleDriveTokens() {
|
googleDriveTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/googleTokens'], token => token.isDrive);
|
return tokensToArray(this.$store.getters['data/googleTokensBySub'], token => token.isDrive);
|
||||||
},
|
},
|
||||||
dropboxTokens() {
|
dropboxTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/dropboxTokens']);
|
return tokensToArray(this.$store.getters['data/dropboxTokensBySub']);
|
||||||
},
|
},
|
||||||
githubTokens() {
|
githubTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/githubTokens']);
|
return tokensToArray(this.$store.getters['data/githubTokensBySub']);
|
||||||
},
|
},
|
||||||
wordpressTokens() {
|
wordpressTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/wordpressTokens']);
|
return tokensToArray(this.$store.getters['data/wordpressTokensBySub']);
|
||||||
},
|
},
|
||||||
bloggerTokens() {
|
bloggerTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/googleTokens'], token => token.isBlogger);
|
return tokensToArray(this.$store.getters['data/googleTokensBySub'], token => token.isBlogger);
|
||||||
},
|
},
|
||||||
zendeskTokens() {
|
zendeskTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/zendeskTokens']);
|
return tokensToArray(this.$store.getters['data/zendeskTokensBySub']);
|
||||||
},
|
},
|
||||||
noToken() {
|
noToken() {
|
||||||
return !this.googleDriveTokens.length
|
return !this.googleDriveTokens.length
|
||||||
|
@ -122,13 +122,13 @@ export default {
|
|||||||
return this.$store.getters['file/current'].name;
|
return this.$store.getters['file/current'].name;
|
||||||
},
|
},
|
||||||
googleDriveTokens() {
|
googleDriveTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/googleTokens'], token => token.isDrive);
|
return tokensToArray(this.$store.getters['data/googleTokensBySub'], token => token.isDrive);
|
||||||
},
|
},
|
||||||
dropboxTokens() {
|
dropboxTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/dropboxTokens']);
|
return tokensToArray(this.$store.getters['data/dropboxTokensBySub']);
|
||||||
},
|
},
|
||||||
githubTokens() {
|
githubTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/githubTokens']);
|
return tokensToArray(this.$store.getters['data/githubTokensBySub']);
|
||||||
},
|
},
|
||||||
noToken() {
|
noToken() {
|
||||||
return !this.googleDriveTokens.length
|
return !this.googleDriveTokens.length
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="side-bar__panel side-bar__panel--menu">
|
<div class="side-bar__panel side-bar__panel--menu">
|
||||||
<div class="workspace" v-for="(workspace, id) in sanitizedWorkspaces" :key="id">
|
<div class="workspace" v-for="(workspace, id) in sanitizedWorkspacesById" :key="id">
|
||||||
<menu-entry :href="workspace.url" target="_blank">
|
<menu-entry :href="workspace.url" target="_blank">
|
||||||
<icon-provider slot="icon" :provider-id="workspace.providerId"></icon-provider>
|
<icon-provider slot="icon" :provider-id="workspace.providerId"></icon-provider>
|
||||||
<div class="workspace__name"><div class="menu-entry__label" v-if="currentWorkspace === workspace">current</div>{{workspace.name}}</div>
|
<div class="workspace__name"><div class="menu-entry__label" v-if="currentWorkspace === workspace">current</div>{{workspace.name}}</div>
|
||||||
@ -37,7 +37,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('data', [
|
...mapGetters('data', [
|
||||||
'sanitizedWorkspaces',
|
'sanitizedWorkspaceById',
|
||||||
]),
|
]),
|
||||||
...mapGetters('workspace', [
|
...mapGetters('workspace', [
|
||||||
'currentWorkspace',
|
'currentWorkspace',
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<p>Please choose a template for your <b>HTML export</b>.</p>
|
<p>Please choose a template for your <b>HTML export</b>.</p>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select class="textfield" slot="field" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select class="textfield" slot="field" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
@ -41,7 +41,7 @@ export default modalTemplate({
|
|||||||
const currentFile = this.$store.getters['file/current'];
|
const currentFile = this.$store.getters['file/current'];
|
||||||
const html = await exportSvc.applyTemplate(
|
const html = await exportSvc.applyTemplate(
|
||||||
currentFile.id,
|
currentFile.id,
|
||||||
this.allTemplates[selectedTemplate],
|
this.allTemplatesById[selectedTemplate],
|
||||||
);
|
);
|
||||||
this.result = html;
|
this.result = html;
|
||||||
}, 10);
|
}, 10);
|
||||||
@ -60,7 +60,7 @@ export default modalTemplate({
|
|||||||
const { config } = this;
|
const { config } = this;
|
||||||
const currentFile = this.$store.getters['file/current'];
|
const currentFile = this.$store.getters['file/current'];
|
||||||
config.resolve();
|
config.resolve();
|
||||||
exportSvc.exportToDisk(currentFile.id, 'html', this.allTemplates[this.selectedTemplate]);
|
exportSvc.exportToDisk(currentFile.id, 'html', this.allTemplatesById[this.selectedTemplate]);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -36,7 +36,7 @@ export default modalTemplate({
|
|||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
googlePhotosTokens() {
|
googlePhotosTokens() {
|
||||||
const googleTokens = this.$store.getters['data/googleTokens'];
|
const googleTokens = this.$store.getters['data/googleTokensBySub'];
|
||||||
return Object.entries(googleTokens)
|
return Object.entries(googleTokens)
|
||||||
.map(([, token]) => token)
|
.map(([, token]) => token)
|
||||||
.filter(token => token.isPhotos)
|
.filter(token => token.isPhotos)
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<p>Please choose a template for your <b>PDF export</b>.</p>
|
<p>Please choose a template for your <b>PDF export</b>.</p>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select class="textfield" slot="field" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select class="textfield" slot="field" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
@ -45,7 +45,7 @@ export default modalTemplate({
|
|||||||
sponsorSvc.getToken(),
|
sponsorSvc.getToken(),
|
||||||
exportSvc.applyTemplate(
|
exportSvc.applyTemplate(
|
||||||
currentFile.id,
|
currentFile.id,
|
||||||
this.allTemplates[this.selectedTemplate],
|
this.allTemplatesById[this.selectedTemplate],
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
]));
|
]));
|
||||||
|
@ -91,11 +91,11 @@ export default {
|
|||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.$store.getters['data/allTemplates'],
|
() => this.$store.getters['data/allTemplatesById'],
|
||||||
(allTemplates) => {
|
(allTemplatesById) => {
|
||||||
const templates = {};
|
const templates = {};
|
||||||
// Sort templates by name
|
// Sort templates by name
|
||||||
Object.entries(allTemplates)
|
Object.entries(allTemplatesById)
|
||||||
.sort(([, template1], [, template2]) => collator.compare(template1.name, template2.name))
|
.sort(([, template1], [, template2]) => collator.compare(template1.name, template2.name))
|
||||||
.forEach(([id, template]) => {
|
.forEach(([id, template]) => {
|
||||||
const templateClone = utils.deepCopy(template);
|
const templateClone = utils.deepCopy(template);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<modal-inner class="modal__inner-1--workspace-management" aria-label="Manage workspaces">
|
<modal-inner class="modal__inner-1--workspace-management" aria-label="Manage workspaces">
|
||||||
<div class="modal__content">
|
<div class="modal__content">
|
||||||
<div class="workspace-entry flex flex--row flex--align-center" v-for="(workspace, id) in sanitizedWorkspaces" :key="id">
|
<div class="workspace-entry flex flex--row flex--align-center" v-for="(workspace, id) in sanitizedWorkspacesById" :key="id">
|
||||||
<div class="workspace-entry__icon flex flex--column flex--center">
|
<div class="workspace-entry__icon flex flex--column flex--center">
|
||||||
<icon-provider :provider-id="workspace.providerId"></icon-provider>
|
<icon-provider :provider-id="workspace.providerId"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
@ -48,8 +48,8 @@ export default {
|
|||||||
'config',
|
'config',
|
||||||
]),
|
]),
|
||||||
...mapGetters('data', [
|
...mapGetters('data', [
|
||||||
'workspaces',
|
'workspacesById',
|
||||||
'sanitizedWorkspaces',
|
'sanitizedWorkspacesById',
|
||||||
]),
|
]),
|
||||||
...mapGetters('workspace', [
|
...mapGetters('workspace', [
|
||||||
'mainWorkspace',
|
'mainWorkspace',
|
||||||
@ -59,12 +59,12 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
edit(id) {
|
edit(id) {
|
||||||
this.editedId = id;
|
this.editedId = id;
|
||||||
this.editingName = this.workspaces[id].name;
|
this.editingName = this.workspacesById[id].name;
|
||||||
},
|
},
|
||||||
submitEdit(cancel) {
|
submitEdit(cancel) {
|
||||||
const workspace = this.workspaces[this.editedId];
|
const workspace = this.workspacesById[this.editedId];
|
||||||
if (workspace && !cancel && this.editingName) {
|
if (workspace && !cancel && this.editingName) {
|
||||||
this.$store.dispatch('data/patchWorkspaces', {
|
this.$store.dispatch('data/patchWorkspacesById', {
|
||||||
[this.editedId]: {
|
[this.editedId]: {
|
||||||
...workspace,
|
...workspace,
|
||||||
name: this.editingName,
|
name: this.editingName,
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<icon-close></icon-close>
|
<icon-close></icon-close>
|
||||||
</button>
|
</button>
|
||||||
<div class="modal__sponsor-button" v-if="showSponsorButton">
|
<div class="modal__sponsor-button" v-if="showSponsorButton">
|
||||||
StackEdit is <a class="not-tabbable" target="_blank" href="https://github.com/benweet/stackedit/">open source</a>. Please consider
|
StackEdit is <a class="not-tabbable" target="_blank" href="https://github.com/benweet/stackedit/">open source</a>, please consider
|
||||||
<a class="not-tabbable" href="javascript:void(0)" @click="sponsor">sponsoring</a> for just $5.
|
<a class="not-tabbable" href="javascript:void(0)" @click="sponsor">sponsoring</a> for just $5.
|
||||||
</div>
|
</div>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
@ -51,11 +51,11 @@ export default {
|
|||||||
|
|
||||||
.modal__close-button {
|
.modal__close-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 7px;
|
||||||
right: 8px;
|
right: 7px;
|
||||||
color: rgba(0, 0, 0, 0.5);
|
color: rgba(0, 0, 0, 0.5);
|
||||||
width: 30px;
|
width: 28px;
|
||||||
height: 30px;
|
height: 28px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
|
|
||||||
&:active,
|
&:active,
|
||||||
|
@ -52,15 +52,15 @@ export default (desc) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (key === 'selectedTemplate') {
|
if (key === 'selectedTemplate') {
|
||||||
component.computed.allTemplates = () => {
|
component.computed.allTemplatesById = () => {
|
||||||
const allTemplates = store.getters['data/allTemplates'];
|
const allTemplatesById = store.getters['data/allTemplatesById'];
|
||||||
const sortedTemplates = {};
|
const sortedTemplatesById = {};
|
||||||
Object.entries(allTemplates)
|
Object.entries(allTemplatesById)
|
||||||
.sort(([, template1], [, template2]) => collator.compare(template1.name, template2.name))
|
.sort(([, template1], [, template2]) => collator.compare(template1.name, template2.name))
|
||||||
.forEach(([templateId, template]) => {
|
.forEach(([templateId, template]) => {
|
||||||
sortedTemplates[templateId] = template;
|
sortedTemplatesById[templateId] = template;
|
||||||
});
|
});
|
||||||
return sortedTemplates;
|
return sortedTemplatesById;
|
||||||
};
|
};
|
||||||
// Make use of `function` to have `this` bound to the component
|
// Make use of `function` to have `this` bound to the component
|
||||||
component.methods.configureTemplates = async function () { // eslint-disable-line func-names
|
component.methods.configureTemplates = async function () { // eslint-disable-line func-names
|
||||||
@ -68,7 +68,7 @@ export default (desc) => {
|
|||||||
type: 'templates',
|
type: 'templates',
|
||||||
selectedId: this.selectedTemplate,
|
selectedId: this.selectedTemplate,
|
||||||
});
|
});
|
||||||
store.dispatch('data/setTemplates', templates);
|
store.dispatch('data/setTemplatesById', templates);
|
||||||
store.dispatch('data/patchLocalSettings', {
|
store.dispatch('data/patchLocalSettings', {
|
||||||
[id]: selectedId,
|
[id]: selectedId,
|
||||||
});
|
});
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -45,7 +45,7 @@ export default modalTemplate({
|
|||||||
name: this.name,
|
name: this.name,
|
||||||
password: this.password,
|
password: this.password,
|
||||||
};
|
};
|
||||||
this.$store.dispatch('data/setCouchdbToken', token);
|
this.$store.dispatch('data/addCouchdbToken', token);
|
||||||
this.config.resolve();
|
this.config.resolve();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<div class="form-entry">
|
<div class="form-entry">
|
||||||
<div class="form-entry__checkbox">
|
<div class="form-entry__checkbox">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" v-model="repoFullAccess"> Grant access to my <b>private repositories</b>
|
<input type="checkbox" v-model="repoFullAccess"> Grant access to your private repositories
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
export default () => ({
|
export default () => ({
|
||||||
main: {
|
main: {
|
||||||
name: 'Main workspace',
|
name: 'Main workspace',
|
||||||
// The rest will be filled by the data/sanitizedWorkspaces getter
|
// The rest will be filled by the data/sanitizedWorkspacesById getter
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -82,7 +82,7 @@ export default {
|
|||||||
if (doClose) {
|
if (doClose) {
|
||||||
// Close the current file by opening the last opened, not deleted one
|
// Close the current file by opening the last opened, not deleted one
|
||||||
store.getters['data/lastOpenedIds'].some((id) => {
|
store.getters['data/lastOpenedIds'].some((id) => {
|
||||||
const file = store.state.file.itemMap[id];
|
const file = store.state.file.itemsById[id];
|
||||||
if (file.parentId === 'trash') {
|
if (file.parentId === 'trash') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ export default {
|
|||||||
value: '{{{files.0.content.text}}}',
|
value: '{{{files.0.content.text}}}',
|
||||||
helpers: '',
|
helpers: '',
|
||||||
}, pdf = false) {
|
}, pdf = false) {
|
||||||
const file = store.state.file.itemMap[fileId];
|
const file = store.state.file.itemsById[fileId];
|
||||||
const content = await localDbSvc.loadItem(`${fileId}/content`);
|
const content = await localDbSvc.loadItem(`${fileId}/content`);
|
||||||
const properties = utils.computeProperties(content.properties);
|
const properties = utils.computeProperties(content.properties);
|
||||||
const options = extensionSvc.getOptions(properties);
|
const options = extensionSvc.getOptions(properties);
|
||||||
@ -114,7 +114,7 @@ export default {
|
|||||||
* Export a file to disk.
|
* Export a file to disk.
|
||||||
*/
|
*/
|
||||||
async exportToDisk(fileId, type, template) {
|
async exportToDisk(fileId, type, template) {
|
||||||
const file = store.state.file.itemMap[fileId];
|
const file = store.state.file.itemsById[fileId];
|
||||||
const html = await this.applyTemplate(fileId, template);
|
const html = await this.applyTemplate(fileId, template);
|
||||||
const blob = new Blob([html], {
|
const blob = new Blob([html], {
|
||||||
type: 'text/plain;charset=utf-8',
|
type: 'text/plain;charset=utf-8',
|
||||||
|
@ -43,9 +43,9 @@ export default {
|
|||||||
|
|
||||||
// Check if there is already a file with that path
|
// Check if there is already a file with that path
|
||||||
if (workspaceUniquePaths) {
|
if (workspaceUniquePaths) {
|
||||||
const parentPath = store.getters.itemPaths[item.parentId] || '';
|
const parentPath = store.getters.pathsByItemId[item.parentId] || '';
|
||||||
const path = parentPath + item.name;
|
const path = parentPath + item.name;
|
||||||
if (store.getters.pathItems[path]) {
|
if (store.getters.itemsByPath[path]) {
|
||||||
await store.dispatch('modal/open', {
|
await store.dispatch('modal/open', {
|
||||||
type: 'pathConflict',
|
type: 'pathConflict',
|
||||||
item,
|
item,
|
||||||
@ -62,7 +62,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return the new file item
|
// Return the new file item
|
||||||
return store.state.file.itemMap[id];
|
return store.state.file.itemsById[id];
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,12 +88,13 @@ export default {
|
|||||||
item,
|
item,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there is a path conflict
|
// Check if there is a path conflict
|
||||||
if (store.getters['workspace/hasUniquePaths']) {
|
if (store.getters['workspace/hasUniquePaths']) {
|
||||||
const parentPath = store.getters.itemPaths[item.parentId] || '';
|
const parentPath = store.getters.pathsByItemId[item.parentId] || '';
|
||||||
const path = parentPath + sanitizedName;
|
const path = parentPath + sanitizedName;
|
||||||
const pathItems = store.getters.pathItems[path] || [];
|
const items = store.getters.itemsByPath[path] || [];
|
||||||
if (pathItems.some(itemWithSamePath => itemWithSamePath.id !== id)) {
|
if (items.some(itemWithSamePath => itemWithSamePath.id !== id)) {
|
||||||
await store.dispatch('modal/open', {
|
await store.dispatch('modal/open', {
|
||||||
type: 'pathConflict',
|
type: 'pathConflict',
|
||||||
item,
|
item,
|
||||||
@ -112,7 +113,7 @@ export default {
|
|||||||
*/
|
*/
|
||||||
setOrPatchItem(patch) {
|
setOrPatchItem(patch) {
|
||||||
const item = {
|
const item = {
|
||||||
...store.getters.allItemMap[patch.id] || patch,
|
...store.getters.allItemsById[patch.id] || patch,
|
||||||
};
|
};
|
||||||
if (!item.id) {
|
if (!item.id) {
|
||||||
return null;
|
return null;
|
||||||
@ -136,7 +137,7 @@ export default {
|
|||||||
this.makePathUnique(item.id);
|
this.makePathUnique(item.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return store.getters.allItemMap[item.id];
|
return store.getters.allItemsById[item.id];
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -160,12 +161,15 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure two files/folders don't have the same path if the workspace doesn't support it.
|
* Ensure two files/folders don't have the same path if the workspace doesn't allow it.
|
||||||
*/
|
*/
|
||||||
ensureUniquePaths() {
|
ensureUniquePaths(idsToKeep = {}) {
|
||||||
if (store.getters['workspace/hasUniquePaths']) {
|
if (store.getters['workspace/hasUniquePaths']) {
|
||||||
if (Object.keys(store.getters.itemPaths).some(id => this.makePathUnique(id))) {
|
if (Object.keys(store.getters.pathsByItemId)
|
||||||
this.ensureUniquePaths();
|
.some(id => !idsToKeep[id] && this.makePathUnique(id))
|
||||||
|
) {
|
||||||
|
// Just changed one item path, restart
|
||||||
|
this.ensureUniquePaths(idsToKeep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -175,13 +179,13 @@ export default {
|
|||||||
* Add a prefix to its name and return true otherwise.
|
* Add a prefix to its name and return true otherwise.
|
||||||
*/
|
*/
|
||||||
makePathUnique(id) {
|
makePathUnique(id) {
|
||||||
const { pathItems, allItemMap, itemPaths } = store.getters;
|
const { itemsByPath, allItemsById, pathsByItemId } = store.getters;
|
||||||
const item = allItemMap[id];
|
const item = allItemsById[id];
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let path = itemPaths[id];
|
let path = pathsByItemId[id];
|
||||||
if (pathItems[path].length === 1) {
|
if (itemsByPath[path].length === 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const isFolder = item.type === 'folder';
|
const isFolder = item.type === 'folder';
|
||||||
@ -190,11 +194,11 @@ export default {
|
|||||||
path = path.slice(0, -1);
|
path = path.slice(0, -1);
|
||||||
}
|
}
|
||||||
for (let suffix = 1; ; suffix += 1) {
|
for (let suffix = 1; ; suffix += 1) {
|
||||||
let pathWithPrefix = `${path}.${suffix}`;
|
let pathWithSuffix = `${path}.${suffix}`;
|
||||||
if (isFolder) {
|
if (isFolder) {
|
||||||
pathWithPrefix += '/';
|
pathWithSuffix += '/';
|
||||||
}
|
}
|
||||||
if (!pathItems[pathWithPrefix]) {
|
if (!itemsByPath[pathWithSuffix]) {
|
||||||
store.commit(`${item.type}/patchItem`, {
|
store.commit(`${item.type}/patchItem`, {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
name: `${item.name}.${suffix}`,
|
name: `${item.name}.${suffix}`,
|
||||||
|
@ -123,7 +123,7 @@ const localDbSvc = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write item if different from stored one
|
// Write item if different from stored one
|
||||||
const item = store.state.data.lsItemMap[id];
|
const item = store.state.data.lsItemsById[id];
|
||||||
if (item && item.hash !== lsHashMap[id]) {
|
if (item && item.hash !== lsHashMap[id]) {
|
||||||
localStorage.setItem(key, JSON.stringify(item));
|
localStorage.setItem(key, JSON.stringify(item));
|
||||||
lsHashMap[id] = item.hash;
|
lsHashMap[id] = item.hash;
|
||||||
@ -178,7 +178,7 @@ const localDbSvc = {
|
|||||||
changes.push(item);
|
changes.push(item);
|
||||||
cursor.continue();
|
cursor.continue();
|
||||||
} else {
|
} else {
|
||||||
const storeItemMap = { ...store.getters.allItemMap };
|
const storeItemMap = { ...store.getters.allItemsById };
|
||||||
changes.forEach((item) => {
|
changes.forEach((item) => {
|
||||||
this.readDbItem(item, storeItemMap);
|
this.readDbItem(item, storeItemMap);
|
||||||
// If item is an old delete marker, remove it from the DB
|
// If item is an old delete marker, remove it from the DB
|
||||||
@ -213,7 +213,7 @@ const localDbSvc = {
|
|||||||
checker = cb => (id) => {
|
checker = cb => (id) => {
|
||||||
if (!storeItemMap[id]) {
|
if (!storeItemMap[id]) {
|
||||||
const [fileId] = id.split('/');
|
const [fileId] = id.split('/');
|
||||||
if (!store.state.file.itemMap[fileId]) {
|
if (!store.state.file.itemsById[fileId]) {
|
||||||
cb(id);
|
cb(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -277,7 +277,7 @@ const localDbSvc = {
|
|||||||
*/
|
*/
|
||||||
async loadItem(id) {
|
async loadItem(id) {
|
||||||
// Check if item is in the store
|
// Check if item is in the store
|
||||||
const itemInStore = store.getters.allItemMap[id];
|
const itemInStore = store.getters.allItemsById[id];
|
||||||
if (itemInStore) {
|
if (itemInStore) {
|
||||||
// Use deepCopy to freeze item
|
// Use deepCopy to freeze item
|
||||||
return Promise.resolve(itemInStore);
|
return Promise.resolve(itemInStore);
|
||||||
@ -326,11 +326,11 @@ const localDbSvc = {
|
|||||||
* Drop the database and clean the localStorage for the specified workspaceId.
|
* Drop the database and clean the localStorage for the specified workspaceId.
|
||||||
*/
|
*/
|
||||||
async removeWorkspace(id) {
|
async removeWorkspace(id) {
|
||||||
const workspaces = {
|
const workspacesById = {
|
||||||
...store.getters['data/workspaces'],
|
...store.getters['data/workspacesById'],
|
||||||
};
|
};
|
||||||
delete workspaces[id];
|
delete workspacesById[id];
|
||||||
store.dispatch('data/setWorkspaces', workspaces);
|
store.dispatch('data/setWorkspacesById', workspacesById);
|
||||||
this.syncLocalStorage();
|
this.syncLocalStorage();
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
const dbName = getDbName(id);
|
const dbName = getDbName(id);
|
||||||
@ -348,7 +348,7 @@ const localDbSvc = {
|
|||||||
async init() {
|
async init() {
|
||||||
// Reset the app if reset flag was passed
|
// Reset the app if reset flag was passed
|
||||||
if (resetApp) {
|
if (resetApp) {
|
||||||
await Promise.all(Object.keys(store.getters['data/workspaces'])
|
await Promise.all(Object.keys(store.getters['data/workspacesById'])
|
||||||
.map(workspaceId => localDbSvc.removeWorkspace(workspaceId)));
|
.map(workspaceId => localDbSvc.removeWorkspace(workspaceId)));
|
||||||
utils.localStorageDataIds.forEach((id) => {
|
utils.localStorageDataIds.forEach((id) => {
|
||||||
// Clean data stored in localStorage
|
// Clean data stored in localStorage
|
||||||
@ -366,7 +366,7 @@ const localDbSvc = {
|
|||||||
|
|
||||||
// If exportWorkspace parameter was provided
|
// If exportWorkspace parameter was provided
|
||||||
if (exportWorkspace) {
|
if (exportWorkspace) {
|
||||||
const backup = JSON.stringify(store.getters.allItemMap);
|
const backup = JSON.stringify(store.getters.allItemsById);
|
||||||
const blob = new Blob([backup], {
|
const blob = new Blob([backup], {
|
||||||
type: 'text/plain;charset=utf-8',
|
type: 'text/plain;charset=utf-8',
|
||||||
});
|
});
|
||||||
@ -405,7 +405,7 @@ const localDbSvc = {
|
|||||||
// Force check sponsorship after a few seconds
|
// Force check sponsorship after a few seconds
|
||||||
const currentDate = Date.now();
|
const currentDate = Date.now();
|
||||||
if (sponsorToken && sponsorToken.expiresOn > currentDate - checkSponsorshipAfter) {
|
if (sponsorToken && sponsorToken.expiresOn > currentDate - checkSponsorshipAfter) {
|
||||||
store.dispatch('data/setGoogleToken', {
|
store.dispatch('data/addGoogleToken', {
|
||||||
...sponsorToken,
|
...sponsorToken,
|
||||||
expiresOn: currentDate - checkSponsorshipAfter,
|
expiresOn: currentDate - checkSponsorshipAfter,
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,7 @@ import Provider from './common/Provider';
|
|||||||
export default new Provider({
|
export default new Provider({
|
||||||
id: 'bloggerPage',
|
id: 'bloggerPage',
|
||||||
getToken(location) {
|
getToken(location) {
|
||||||
const token = store.getters['data/googleTokens'][location.sub];
|
const token = store.getters['data/googleTokensBySub'][location.sub];
|
||||||
return token && token.isBlogger ? token : null;
|
return token && token.isBlogger ? token : null;
|
||||||
},
|
},
|
||||||
getUrl(location) {
|
getUrl(location) {
|
||||||
|
@ -5,7 +5,7 @@ import Provider from './common/Provider';
|
|||||||
export default new Provider({
|
export default new Provider({
|
||||||
id: 'blogger',
|
id: 'blogger',
|
||||||
getToken(location) {
|
getToken(location) {
|
||||||
const token = store.getters['data/googleTokens'][location.sub];
|
const token = store.getters['data/googleTokensBySub'][location.sub];
|
||||||
return token && token.isBlogger ? token : null;
|
return token && token.isBlogger ? token : null;
|
||||||
},
|
},
|
||||||
getUrl(location) {
|
getUrl(location) {
|
||||||
|
@ -7,6 +7,9 @@ import fileSvc from '../../fileSvc';
|
|||||||
const dataExtractor = /<!--stackedit_data:([A-Za-z0-9+/=\s]+)-->$/;
|
const dataExtractor = /<!--stackedit_data:([A-Za-z0-9+/=\s]+)-->$/;
|
||||||
|
|
||||||
export default class Provider {
|
export default class Provider {
|
||||||
|
prepareChanges = changes => changes
|
||||||
|
onChangesApplied = () => {}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
Object.assign(this, props);
|
Object.assign(this, props);
|
||||||
providerRegistry.register(this);
|
providerRegistry.register(this);
|
||||||
@ -41,7 +44,7 @@ export default class Provider {
|
|||||||
* Parse content serialized with serializeContent()
|
* Parse content serialized with serializeContent()
|
||||||
*/
|
*/
|
||||||
static parseContent(serializedContent, id) {
|
static parseContent(serializedContent, id) {
|
||||||
const result = utils.deepCopy(store.state.content.itemMap[id]) || emptyContent(id);
|
const result = utils.deepCopy(store.state.content.itemsById[id]) || emptyContent(id);
|
||||||
result.text = utils.sanitizeText(serializedContent);
|
result.text = utils.sanitizeText(serializedContent);
|
||||||
result.history = [];
|
result.history = [];
|
||||||
const extractedData = dataExtractor.exec(serializedContent);
|
const extractedData = dataExtractor.exec(serializedContent);
|
||||||
@ -82,7 +85,7 @@ export default class Provider {
|
|||||||
const location = utils.search(allLocations, criteria);
|
const location = utils.search(allLocations, criteria);
|
||||||
if (location) {
|
if (location) {
|
||||||
// Found one, open it if it exists
|
// Found one, open it if it exists
|
||||||
const item = store.state.file.itemMap[location.fileId];
|
const item = store.state.file.itemsById[location.fileId];
|
||||||
if (item) {
|
if (item) {
|
||||||
store.commit('file/setCurrentId', item.id);
|
store.commit('file/setCurrentId', item.id);
|
||||||
// If file is in the trash, restore it
|
// If file is in the trash, restore it
|
||||||
|
@ -17,12 +17,12 @@ export default new Provider({
|
|||||||
dbUrl,
|
dbUrl,
|
||||||
};
|
};
|
||||||
const workspaceId = utils.makeWorkspaceId(workspaceParams);
|
const workspaceId = utils.makeWorkspaceId(workspaceParams);
|
||||||
const getToken = () => store.getters['data/couchdbTokens'][workspaceId];
|
const getToken = () => store.getters['data/couchdbTokensBySub'][workspaceId];
|
||||||
const getWorkspace = () => store.getters['data/sanitizedWorkspaces'][workspaceId];
|
const getWorkspace = () => store.getters['data/sanitizedWorkspacesById'][workspaceId];
|
||||||
|
|
||||||
if (!getToken()) {
|
if (!getToken()) {
|
||||||
// Create token
|
// Create token
|
||||||
store.dispatch('data/setCouchdbToken', {
|
store.dispatch('data/addCouchdbToken', {
|
||||||
sub: workspaceId,
|
sub: workspaceId,
|
||||||
dbUrl,
|
dbUrl,
|
||||||
});
|
});
|
||||||
@ -38,7 +38,7 @@ export default new Provider({
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`${dbUrl} is not accessible. Make sure you have the proper permissions.`);
|
throw new Error(`${dbUrl} is not accessible. Make sure you have the proper permissions.`);
|
||||||
}
|
}
|
||||||
store.dispatch('data/patchWorkspaces', {
|
store.dispatch('data/patchWorkspacesById', {
|
||||||
[workspaceId]: {
|
[workspaceId]: {
|
||||||
id: workspaceId,
|
id: workspaceId,
|
||||||
name: db.db_name,
|
name: db.db_name,
|
||||||
@ -52,7 +52,7 @@ export default new Provider({
|
|||||||
// Fix the URL hash
|
// Fix the URL hash
|
||||||
utils.setQueryParams(workspaceParams);
|
utils.setQueryParams(workspaceParams);
|
||||||
if (workspace.url !== window.location.href) {
|
if (workspace.url !== window.location.href) {
|
||||||
store.dispatch('data/patchWorkspaces', {
|
store.dispatch('data/patchWorkspacesById', {
|
||||||
[workspace.id]: {
|
[workspace.id]: {
|
||||||
...workspace,
|
...workspace,
|
||||||
url: window.location.href,
|
url: window.location.href,
|
||||||
@ -91,7 +91,7 @@ export default new Provider({
|
|||||||
syncLastSeq,
|
syncLastSeq,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async saveWorkspaceItem(item, syncData) {
|
async saveWorkspaceItem({ item, syncData }) {
|
||||||
const syncToken = store.getters['workspace/syncToken'];
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
const { id, rev } = couchdbHelper.uploadDocument({
|
const { id, rev } = couchdbHelper.uploadDocument({
|
||||||
token: syncToken,
|
token: syncToken,
|
||||||
@ -108,24 +108,24 @@ export default new Provider({
|
|||||||
rev,
|
rev,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
removeWorkspaceItem(syncData) {
|
removeWorkspaceItem({ syncData }) {
|
||||||
const syncToken = store.getters['workspace/syncToken'];
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
return couchdbHelper.removeDocument(syncToken, syncData.id, syncData.rev);
|
return couchdbHelper.removeDocument(syncToken, syncData.id, syncData.rev);
|
||||||
},
|
},
|
||||||
async downloadWorkspaceContent(token, syncData) {
|
async downloadWorkspaceContent({ token, contentSyncData }) {
|
||||||
const body = await couchdbHelper.retrieveDocumentWithAttachments(token, syncData.id);
|
const body = await couchdbHelper.retrieveDocumentWithAttachments(token, contentSyncData.id);
|
||||||
const rev = body._rev; // eslint-disable-line no-underscore-dangle
|
const rev = body._rev; // eslint-disable-line no-underscore-dangle
|
||||||
const item = Provider.parseContent(body.attachments.data, body.item.id);
|
const content = Provider.parseContent(body.attachments.data, body.item.id);
|
||||||
return {
|
return {
|
||||||
item,
|
content,
|
||||||
syncData: {
|
contentSyncData: {
|
||||||
...syncData,
|
...contentSyncData,
|
||||||
hash: item.hash,
|
hash: content.hash,
|
||||||
rev,
|
rev,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async downloadWorkspaceData(token, dataId, syncData) {
|
async downloadWorkspaceData({ token, syncData }) {
|
||||||
if (!syncData) {
|
if (!syncData) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -142,30 +142,32 @@ export default new Provider({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async uploadWorkspaceContent(token, item, syncData) {
|
async uploadWorkspaceContent({ token, content, contentSyncData }) {
|
||||||
const res = await couchdbHelper.uploadDocument({
|
const res = await couchdbHelper.uploadDocument({
|
||||||
token,
|
token,
|
||||||
item: {
|
item: {
|
||||||
id: item.id,
|
id: content.id,
|
||||||
type: item.type,
|
type: content.type,
|
||||||
hash: item.hash,
|
hash: content.hash,
|
||||||
},
|
},
|
||||||
data: Provider.serializeContent(item),
|
data: Provider.serializeContent(content),
|
||||||
dataType: 'text/plain',
|
dataType: 'text/plain',
|
||||||
documentId: syncData && syncData.id,
|
documentId: contentSyncData && contentSyncData.id,
|
||||||
rev: syncData && syncData.rev,
|
rev: contentSyncData && contentSyncData.rev,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Return new sync data
|
// Return new sync data
|
||||||
return {
|
return {
|
||||||
|
contentSyncData: {
|
||||||
id: res.id,
|
id: res.id,
|
||||||
itemId: item.id,
|
itemId: content.id,
|
||||||
type: item.type,
|
type: content.type,
|
||||||
hash: item.hash,
|
hash: content.hash,
|
||||||
rev: res.rev,
|
rev: res.rev,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async uploadWorkspaceData(token, item, syncData) {
|
async uploadWorkspaceData({ token, item, syncData }) {
|
||||||
const res = await couchdbHelper.uploadDocument({
|
const res = await couchdbHelper.uploadDocument({
|
||||||
token,
|
token,
|
||||||
item: {
|
item: {
|
||||||
@ -181,11 +183,13 @@ export default new Provider({
|
|||||||
|
|
||||||
// Return new sync data
|
// Return new sync data
|
||||||
return {
|
return {
|
||||||
|
syncData: {
|
||||||
id: res.id,
|
id: res.id,
|
||||||
itemId: item.id,
|
itemId: item.id,
|
||||||
type: item.type,
|
type: item.type,
|
||||||
hash: item.hash,
|
hash: item.hash,
|
||||||
rev: res.rev,
|
rev: res.rev,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async listRevisions(token, fileId) {
|
async listRevisions(token, fileId) {
|
||||||
|
@ -20,7 +20,7 @@ const makePathRelative = (token, path) => {
|
|||||||
export default new Provider({
|
export default new Provider({
|
||||||
id: 'dropbox',
|
id: 'dropbox',
|
||||||
getToken(location) {
|
getToken(location) {
|
||||||
return store.getters['data/dropboxTokens'][location.sub];
|
return store.getters['data/dropboxTokensBySub'][location.sub];
|
||||||
},
|
},
|
||||||
getUrl(location) {
|
getUrl(location) {
|
||||||
const pathComponents = location.path.split('/').map(encodeURIComponent);
|
const pathComponents = location.path.split('/').map(encodeURIComponent);
|
||||||
|
@ -6,7 +6,7 @@ import utils from '../utils';
|
|||||||
export default new Provider({
|
export default new Provider({
|
||||||
id: 'gist',
|
id: 'gist',
|
||||||
getToken(location) {
|
getToken(location) {
|
||||||
return store.getters['data/githubTokens'][location.sub];
|
return store.getters['data/githubTokensBySub'][location.sub];
|
||||||
},
|
},
|
||||||
getUrl(location) {
|
getUrl(location) {
|
||||||
return `https://gist.github.com/${location.gistId}`;
|
return `https://gist.github.com/${location.gistId}`;
|
||||||
@ -23,7 +23,7 @@ export default new Provider({
|
|||||||
return Provider.parseContent(content, `${syncLocation.fileId}/content`);
|
return Provider.parseContent(content, `${syncLocation.fileId}/content`);
|
||||||
},
|
},
|
||||||
async uploadContent(token, content, syncLocation) {
|
async uploadContent(token, content, syncLocation) {
|
||||||
const file = store.state.file.itemMap[syncLocation.fileId];
|
const file = store.state.file.itemsById[syncLocation.fileId];
|
||||||
const description = utils.sanitizeName(file && file.name);
|
const description = utils.sanitizeName(file && file.name);
|
||||||
const gist = await githubHelper.uploadGist({
|
const gist = await githubHelper.uploadGist({
|
||||||
...syncLocation,
|
...syncLocation,
|
||||||
|
@ -9,7 +9,7 @@ const savedSha = {};
|
|||||||
export default new Provider({
|
export default new Provider({
|
||||||
id: 'github',
|
id: 'github',
|
||||||
getToken(location) {
|
getToken(location) {
|
||||||
return store.getters['data/githubTokens'][location.sub];
|
return store.getters['data/githubTokensBySub'][location.sub];
|
||||||
},
|
},
|
||||||
getUrl(location) {
|
getUrl(location) {
|
||||||
return `https://github.com/${encodeURIComponent(location.owner)}/${encodeURIComponent(location.repo)}/blob/${encodeURIComponent(location.branch)}/${encodeURIComponent(location.path)}`;
|
return `https://github.com/${encodeURIComponent(location.owner)}/${encodeURIComponent(location.repo)}/blob/${encodeURIComponent(location.branch)}/${encodeURIComponent(location.path)}`;
|
||||||
@ -20,12 +20,12 @@ export default new Provider({
|
|||||||
},
|
},
|
||||||
async downloadContent(token, syncLocation) {
|
async downloadContent(token, syncLocation) {
|
||||||
try {
|
try {
|
||||||
const { sha, content } = await githubHelper.downloadFile({
|
const { sha, data } = await githubHelper.downloadFile({
|
||||||
...syncLocation,
|
...syncLocation,
|
||||||
token,
|
token,
|
||||||
});
|
});
|
||||||
savedSha[syncLocation.id] = sha;
|
savedSha[syncLocation.id] = sha;
|
||||||
return Provider.parseContent(content, `${syncLocation.fileId}/content`);
|
return Provider.parseContent(data, `${syncLocation.fileId}/content`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Ignore error, upload is going to fail anyway
|
// Ignore error, upload is going to fail anyway
|
||||||
return null;
|
return null;
|
||||||
|
@ -28,7 +28,6 @@ const endsWith = (str, suffix) => str.slice(-suffix.length) === suffix;
|
|||||||
|
|
||||||
export default new Provider({
|
export default new Provider({
|
||||||
id: 'githubWorkspace',
|
id: 'githubWorkspace',
|
||||||
isGit: true,
|
|
||||||
getToken() {
|
getToken() {
|
||||||
return store.getters['workspace/syncToken'];
|
return store.getters['workspace/syncToken'];
|
||||||
},
|
},
|
||||||
@ -47,13 +46,13 @@ export default new Provider({
|
|||||||
workspaceParams.path = path;
|
workspaceParams.path = path;
|
||||||
}
|
}
|
||||||
const workspaceId = utils.makeWorkspaceId(workspaceParams);
|
const workspaceId = utils.makeWorkspaceId(workspaceParams);
|
||||||
let workspace = store.getters['data/sanitizedWorkspaces'][workspaceId];
|
let workspace = store.getters['data/sanitizedWorkspacesById'][workspaceId];
|
||||||
|
|
||||||
// See if we already have a token
|
// See if we already have a token
|
||||||
let token;
|
let token;
|
||||||
if (workspace) {
|
if (workspace) {
|
||||||
// Token sub is in the workspace
|
// Token sub is in the workspace
|
||||||
token = store.getters['data/githubTokens'][workspace.sub];
|
token = store.getters['data/githubTokensBySub'][workspace.sub];
|
||||||
}
|
}
|
||||||
if (!token) {
|
if (!token) {
|
||||||
await store.dispatch('modal/open', { type: 'githubAccount' });
|
await store.dispatch('modal/open', { type: 'githubAccount' });
|
||||||
@ -74,27 +73,27 @@ export default new Provider({
|
|||||||
// Fix the URL hash
|
// Fix the URL hash
|
||||||
utils.setQueryParams(workspaceParams);
|
utils.setQueryParams(workspaceParams);
|
||||||
if (workspace.url !== window.location.href) {
|
if (workspace.url !== window.location.href) {
|
||||||
store.dispatch('data/patchWorkspaces', {
|
store.dispatch('data/patchWorkspacesById', {
|
||||||
[workspaceId]: {
|
[workspaceId]: {
|
||||||
...workspace,
|
...workspace,
|
||||||
url: window.location.href,
|
url: window.location.href,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return store.getters['data/sanitizedWorkspaces'][workspaceId];
|
return store.getters['data/sanitizedWorkspacesById'][workspaceId];
|
||||||
},
|
},
|
||||||
async getChanges() {
|
getChanges() {
|
||||||
const syncToken = store.getters['workspace/syncToken'];
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
const { owner, repo, branch } = getWorkspaceWithOwner();
|
const { owner, repo, branch } = getWorkspaceWithOwner();
|
||||||
const tree = await githubHelper.getTree({
|
return githubHelper.getTree({
|
||||||
token: syncToken,
|
token: syncToken,
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
branch,
|
branch,
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
prepareChanges(tree) {
|
||||||
const workspacePath = store.getters['workspace/currentWorkspace'].path || '';
|
const workspacePath = store.getters['workspace/currentWorkspace'].path || '';
|
||||||
const syncDataByPath = store.getters['data/syncData'];
|
|
||||||
const syncDataByItemId = store.getters['data/syncDataByItemId'];
|
|
||||||
|
|
||||||
// Store all blobs sha
|
// Store all blobs sha
|
||||||
treeShaMap = Object.create(null);
|
treeShaMap = Object.create(null);
|
||||||
@ -136,9 +135,20 @@ export default new Provider({
|
|||||||
const changes = [];
|
const changes = [];
|
||||||
const pathIds = {};
|
const pathIds = {};
|
||||||
const syncDataToKeep = Object.create(null);
|
const syncDataToKeep = Object.create(null);
|
||||||
|
const syncDataByPath = store.getters['data/syncDataById'];
|
||||||
|
const { itemsByGitPath } = store.getters;
|
||||||
const getId = (path) => {
|
const getId = (path) => {
|
||||||
const syncData = syncDataByPath[path];
|
const existingItem = itemsByGitPath[path];
|
||||||
const id = syncData ? syncData.itemId : utils.uid();
|
// Use the item ID only if the item was already synced
|
||||||
|
if (existingItem && syncDataByPath[path]) {
|
||||||
|
pathIds[path] = existingItem.id;
|
||||||
|
return existingItem.id;
|
||||||
|
}
|
||||||
|
// Generate a new ID
|
||||||
|
let id = utils.uid();
|
||||||
|
if (path[0] === '/') {
|
||||||
|
id += '/content';
|
||||||
|
}
|
||||||
pathIds[path] = id;
|
pathIds[path] = id;
|
||||||
return id;
|
return id;
|
||||||
};
|
};
|
||||||
@ -146,9 +156,8 @@ export default new Provider({
|
|||||||
// Folder creations/updates
|
// Folder creations/updates
|
||||||
// Assume map entries are sorted from top to bottom
|
// Assume map entries are sorted from top to bottom
|
||||||
Object.entries(treeFolderMap).forEach(([path, parentPath]) => {
|
Object.entries(treeFolderMap).forEach(([path, parentPath]) => {
|
||||||
const id = getId(path);
|
|
||||||
const item = utils.addItemHash({
|
const item = utils.addItemHash({
|
||||||
id,
|
id: getId(path),
|
||||||
type: 'folder',
|
type: 'folder',
|
||||||
name: path.slice(parentPath.length, -1),
|
name: path.slice(parentPath.length, -1),
|
||||||
parentId: pathIds[parentPath] || null,
|
parentId: pathIds[parentPath] || null,
|
||||||
@ -158,18 +167,22 @@ export default new Provider({
|
|||||||
item,
|
item,
|
||||||
syncData: {
|
syncData: {
|
||||||
id: path,
|
id: path,
|
||||||
itemId: id,
|
|
||||||
type: item.type,
|
type: item.type,
|
||||||
hash: item.hash,
|
hash: item.hash,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// File creations/updates
|
// File/content creations/updates
|
||||||
Object.entries(treeFileMap).forEach(([path, parentPath]) => {
|
Object.entries(treeFileMap).forEach(([path, parentPath]) => {
|
||||||
const id = getId(path);
|
// Look for content sync data as it's created before file sync data
|
||||||
|
const contentPath = `/${path}`;
|
||||||
|
const contentId = getId(contentPath);
|
||||||
|
|
||||||
|
// File creations/updates
|
||||||
|
const [fileId] = contentId.split('/');
|
||||||
const item = utils.addItemHash({
|
const item = utils.addItemHash({
|
||||||
id,
|
id: fileId,
|
||||||
type: 'file',
|
type: 'file',
|
||||||
name: path.slice(parentPath.length, -'.md'.length),
|
name: path.slice(parentPath.length, -'.md'.length),
|
||||||
parentId: pathIds[parentPath] || null,
|
parentId: pathIds[parentPath] || null,
|
||||||
@ -179,31 +192,31 @@ export default new Provider({
|
|||||||
item,
|
item,
|
||||||
syncData: {
|
syncData: {
|
||||||
id: path,
|
id: path,
|
||||||
itemId: id,
|
|
||||||
type: item.type,
|
type: item.type,
|
||||||
hash: item.hash,
|
hash: item.hash,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Content creations/updates
|
// Content creations/updates
|
||||||
const contentSyncData = syncDataByItemId[`${id}/content`];
|
const contentSyncData = syncDataByPath[contentPath];
|
||||||
if (contentSyncData) {
|
if (contentSyncData) {
|
||||||
syncDataToKeep[contentSyncData.id] = true;
|
syncDataToKeep[path] = true;
|
||||||
|
syncDataToKeep[contentPath] = true;
|
||||||
}
|
}
|
||||||
if (!contentSyncData || contentSyncData.sha !== treeShaMap[path]) {
|
if (!contentSyncData || contentSyncData.sha !== treeShaMap[path]) {
|
||||||
|
const type = 'content';
|
||||||
// Use `/` as a prefix to get a unique syncData id
|
// Use `/` as a prefix to get a unique syncData id
|
||||||
changes.push({
|
changes.push({
|
||||||
syncDataId: `/${path}`,
|
syncDataId: contentPath,
|
||||||
item: {
|
item: {
|
||||||
id: `${id}/content`,
|
id: contentId,
|
||||||
type: 'content',
|
type,
|
||||||
// Need a truthy value to force saving sync data
|
// Need a truthy value to force downloading the content
|
||||||
hash: 1,
|
hash: 1,
|
||||||
},
|
},
|
||||||
syncData: {
|
syncData: {
|
||||||
id: `/${path}`,
|
id: contentPath,
|
||||||
itemId: `${id}/content`,
|
type,
|
||||||
type: 'content',
|
|
||||||
// Need a truthy value to force downloading the content
|
// Need a truthy value to force downloading the content
|
||||||
hash: 1,
|
hash: 1,
|
||||||
},
|
},
|
||||||
@ -212,35 +225,34 @@ export default new Provider({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Data creations/updates
|
// Data creations/updates
|
||||||
|
const syncDataByItemId = store.getters['data/syncDataByItemId'];
|
||||||
Object.keys(treeDataMap).forEach((path) => {
|
Object.keys(treeDataMap).forEach((path) => {
|
||||||
try {
|
|
||||||
// Only template data are stored
|
// Only template data are stored
|
||||||
const [, id] = path.match(/^\.stackedit-data\/(templates)\.json$/) || [];
|
const [, id] = path.match(/^\.stackedit-data\/(templates)\.json$/) || [];
|
||||||
|
if (id) {
|
||||||
pathIds[path] = id;
|
pathIds[path] = id;
|
||||||
const syncData = syncDataByItemId[id];
|
const syncData = syncDataByItemId[id];
|
||||||
if (syncData) {
|
if (syncData) {
|
||||||
syncDataToKeep[syncData.id] = true;
|
syncDataToKeep[syncData.id] = true;
|
||||||
}
|
}
|
||||||
if (!syncData || syncData.sha !== treeShaMap[path]) {
|
if (!syncData || syncData.sha !== treeShaMap[path]) {
|
||||||
|
const type = 'data';
|
||||||
changes.push({
|
changes.push({
|
||||||
syncDataId: path,
|
syncDataId: path,
|
||||||
item: {
|
item: {
|
||||||
id,
|
id,
|
||||||
type: 'data',
|
type,
|
||||||
// Need a truthy value to force saving sync data
|
// Need a truthy value to force saving sync data
|
||||||
hash: 1,
|
hash: 1,
|
||||||
},
|
},
|
||||||
syncData: {
|
syncData: {
|
||||||
id: path,
|
id: path,
|
||||||
itemId: id,
|
type,
|
||||||
type: 'data',
|
|
||||||
// Need a truthy value to force downloading the content
|
// Need a truthy value to force downloading the content
|
||||||
hash: 1,
|
hash: 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
// Ignore parsing errors
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -255,12 +267,18 @@ export default new Provider({
|
|||||||
pathMatcher: /^([\s\S]+)\.([\w-]+)\.publish$/,
|
pathMatcher: /^([\s\S]+)\.([\w-]+)\.publish$/,
|
||||||
}]
|
}]
|
||||||
.forEach(({ type, map, pathMatcher }) => Object.keys(map).forEach((path) => {
|
.forEach(({ type, map, pathMatcher }) => Object.keys(map).forEach((path) => {
|
||||||
try {
|
const [, filePath, data] = path.match(pathMatcher) || [];
|
||||||
const [, filePath, data] = path.match(pathMatcher);
|
if (filePath) {
|
||||||
// If there is a corresponding md file in the tree
|
// If there is a corresponding md file in the tree
|
||||||
const fileId = pathIds[`${filePath}.md`];
|
const fileId = pathIds[`${filePath}.md`];
|
||||||
if (fileId) {
|
if (fileId) {
|
||||||
const id = getId(path);
|
// Reuse existing ID or create a new one
|
||||||
|
const existingItem = itemsByGitPath[path];
|
||||||
|
const id = existingItem
|
||||||
|
? existingItem.id
|
||||||
|
: utils.uid();
|
||||||
|
pathIds[path] = id;
|
||||||
|
|
||||||
const item = utils.addItemHash({
|
const item = utils.addItemHash({
|
||||||
...JSON.parse(utils.decodeBase64(data)),
|
...JSON.parse(utils.decodeBase64(data)),
|
||||||
id,
|
id,
|
||||||
@ -272,14 +290,11 @@ export default new Provider({
|
|||||||
item,
|
item,
|
||||||
syncData: {
|
syncData: {
|
||||||
id: path,
|
id: path,
|
||||||
itemId: id,
|
|
||||||
type: item.type,
|
type: item.type,
|
||||||
hash: item.hash,
|
hash: item.hash,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
// Ignore parsing errors
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -292,10 +307,9 @@ export default new Provider({
|
|||||||
|
|
||||||
return changes;
|
return changes;
|
||||||
},
|
},
|
||||||
async saveWorkspaceItem(item) {
|
async saveWorkspaceItem({ item }) {
|
||||||
const syncData = {
|
const syncData = {
|
||||||
id: store.getters.itemGitPaths[item.id],
|
id: store.getters.gitPathsByItemId[item.id],
|
||||||
itemId: item.id,
|
|
||||||
type: item.type,
|
type: item.type,
|
||||||
hash: item.hash,
|
hash: item.hash,
|
||||||
};
|
};
|
||||||
@ -316,7 +330,7 @@ export default new Provider({
|
|||||||
});
|
});
|
||||||
return syncData;
|
return syncData;
|
||||||
},
|
},
|
||||||
async removeWorkspaceItem(syncData) {
|
async removeWorkspaceItem({ syncData }) {
|
||||||
if (treeShaMap[syncData.id]) {
|
if (treeShaMap[syncData.id]) {
|
||||||
const syncToken = store.getters['workspace/syncToken'];
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
await githubHelper.removeFile({
|
await githubHelper.removeFile({
|
||||||
@ -327,41 +341,40 @@ export default new Provider({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async downloadWorkspaceContent(token, contentSyncData) {
|
async downloadWorkspaceContent({
|
||||||
const [fileId] = contentSyncData.itemId.split('/');
|
token,
|
||||||
const path = store.getters.itemGitPaths[fileId];
|
contentId,
|
||||||
const syncData = store.getters['data/syncData'][path];
|
contentSyncData,
|
||||||
if (!syncData) {
|
fileSyncData,
|
||||||
return {};
|
}) {
|
||||||
}
|
const { sha, data } = await githubHelper.downloadFile({
|
||||||
const { sha, content } = await githubHelper.downloadFile({
|
|
||||||
...getWorkspaceWithOwner(),
|
...getWorkspaceWithOwner(),
|
||||||
token,
|
token,
|
||||||
path: getAbsolutePath(syncData),
|
path: getAbsolutePath(fileSyncData),
|
||||||
});
|
});
|
||||||
treeShaMap[path] = sha;
|
treeShaMap[fileSyncData.id] = sha;
|
||||||
const item = Provider.parseContent(content, `${fileId}/content`);
|
const content = Provider.parseContent(data, contentId);
|
||||||
return {
|
return {
|
||||||
item,
|
content,
|
||||||
syncData: {
|
contentSyncData: {
|
||||||
...contentSyncData,
|
...contentSyncData,
|
||||||
hash: item.hash,
|
hash: content.hash,
|
||||||
sha,
|
sha,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async downloadWorkspaceData(token, dataId, syncData) {
|
async downloadWorkspaceData({ token, syncData }) {
|
||||||
if (!syncData) {
|
if (!syncData) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { sha, content } = await githubHelper.downloadFile({
|
const { sha, data } = await githubHelper.downloadFile({
|
||||||
...getWorkspaceWithOwner(),
|
...getWorkspaceWithOwner(),
|
||||||
token,
|
token,
|
||||||
path: getAbsolutePath(syncData),
|
path: getAbsolutePath(syncData),
|
||||||
});
|
});
|
||||||
treeShaMap[syncData.id] = sha;
|
treeShaMap[syncData.id] = sha;
|
||||||
const item = JSON.parse(content);
|
const item = JSON.parse(data);
|
||||||
return {
|
return {
|
||||||
item,
|
item,
|
||||||
syncData: {
|
syncData: {
|
||||||
@ -371,32 +384,36 @@ export default new Provider({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async uploadWorkspaceContent(token, item) {
|
async uploadWorkspaceContent({ token, content, file }) {
|
||||||
const [fileId] = item.id.split('/');
|
const path = store.getters.gitPathsByItemId[file.id];
|
||||||
const path = store.getters.itemGitPaths[fileId];
|
|
||||||
const absolutePath = `${store.getters['workspace/currentWorkspace'].path || ''}${path}`;
|
const absolutePath = `${store.getters['workspace/currentWorkspace'].path || ''}${path}`;
|
||||||
const res = await githubHelper.uploadFile({
|
const res = await githubHelper.uploadFile({
|
||||||
...getWorkspaceWithOwner(),
|
...getWorkspaceWithOwner(),
|
||||||
token,
|
token,
|
||||||
path: absolutePath,
|
path: absolutePath,
|
||||||
content: Provider.serializeContent(item),
|
content: Provider.serializeContent(content),
|
||||||
sha: treeShaMap[path],
|
sha: treeShaMap[path],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Return new sync data
|
// Return new sync data
|
||||||
return {
|
return {
|
||||||
id: store.getters.itemGitPaths[item.id],
|
contentSyncData: {
|
||||||
itemId: item.id,
|
id: store.getters.gitPathsByItemId[content.id],
|
||||||
type: item.type,
|
type: content.type,
|
||||||
hash: item.hash,
|
hash: content.hash,
|
||||||
sha: res.content.sha,
|
sha: res.content.sha,
|
||||||
|
},
|
||||||
|
fileSyncData: {
|
||||||
|
id: path,
|
||||||
|
type: 'file',
|
||||||
|
hash: file.hash,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async uploadWorkspaceData(token, item) {
|
async uploadWorkspaceData({ token, item }) {
|
||||||
const path = store.getters.itemGitPaths[item.id];
|
const path = store.getters.gitPathsByItemId[item.id];
|
||||||
const syncData = {
|
const syncData = {
|
||||||
id: path,
|
id: path,
|
||||||
itemId: item.id,
|
|
||||||
type: item.type,
|
type: item.type,
|
||||||
hash: item.hash,
|
hash: item.hash,
|
||||||
};
|
};
|
||||||
@ -409,8 +426,10 @@ export default new Provider({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
syncData: {
|
||||||
...syncData,
|
...syncData,
|
||||||
sha: res.content.sha,
|
sha: res.content.sha,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
onSyncEnd() {
|
onSyncEnd() {
|
||||||
@ -458,12 +477,12 @@ export default new Provider({
|
|||||||
},
|
},
|
||||||
async getRevisionContent(token, fileId, revisionId) {
|
async getRevisionContent(token, fileId, revisionId) {
|
||||||
const syncData = Provider.getContentSyncData(fileId);
|
const syncData = Provider.getContentSyncData(fileId);
|
||||||
const { content } = await githubHelper.downloadFile({
|
const { data } = await githubHelper.downloadFile({
|
||||||
...getWorkspaceWithOwner(),
|
...getWorkspaceWithOwner(),
|
||||||
token,
|
token,
|
||||||
branch: revisionId,
|
branch: revisionId,
|
||||||
path: getAbsolutePath(syncData),
|
path: getAbsolutePath(syncData),
|
||||||
});
|
});
|
||||||
return Provider.parseContent(content, `${fileId}/content`);
|
return Provider.parseContent(data, `${fileId}/content`);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -15,7 +15,7 @@ export default new Provider({
|
|||||||
// Remove the URL hash
|
// Remove the URL hash
|
||||||
utils.setQueryParams();
|
utils.setQueryParams();
|
||||||
// Return the main workspace
|
// Return the main workspace
|
||||||
return store.getters['data/workspaces'].main;
|
return store.getters['data/workspacesById'].main;
|
||||||
},
|
},
|
||||||
async getChanges() {
|
async getChanges() {
|
||||||
const syncToken = store.getters['workspace/syncToken'];
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
@ -48,7 +48,7 @@ export default new Provider({
|
|||||||
syncStartPageToken,
|
syncStartPageToken,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async saveWorkspaceItem(item, syncData, ifNotTooLate) {
|
async saveWorkspaceItem({ item, syncData, ifNotTooLate }) {
|
||||||
const syncToken = store.getters['workspace/syncToken'];
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
const file = await googleHelper.uploadAppDataFile({
|
const file = await googleHelper.uploadAppDataFile({
|
||||||
token: syncToken,
|
token: syncToken,
|
||||||
@ -64,22 +64,22 @@ export default new Provider({
|
|||||||
hash: item.hash,
|
hash: item.hash,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
removeWorkspaceItem(syncData, ifNotTooLate) {
|
removeWorkspaceItem({ syncData, ifNotTooLate }) {
|
||||||
const syncToken = store.getters['workspace/syncToken'];
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
return googleHelper.removeAppDataFile(syncToken, syncData.id, ifNotTooLate);
|
return googleHelper.removeAppDataFile(syncToken, syncData.id, ifNotTooLate);
|
||||||
},
|
},
|
||||||
async downloadWorkspaceContent(token, syncData) {
|
async downloadWorkspaceContent({ token, contentSyncData }) {
|
||||||
const data = await googleHelper.downloadAppDataFile(token, syncData.id);
|
const data = await googleHelper.downloadAppDataFile(token, contentSyncData.id);
|
||||||
const item = utils.addItemHash(JSON.parse(data));
|
const content = utils.addItemHash(JSON.parse(data));
|
||||||
return {
|
return {
|
||||||
item,
|
content,
|
||||||
syncData: {
|
contentSyncData: {
|
||||||
...syncData,
|
...contentSyncData,
|
||||||
hash: item.hash,
|
hash: content.hash,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async downloadWorkspaceData(token, dataId, syncData) {
|
async downloadWorkspaceData({ token, syncData }) {
|
||||||
if (!syncData) {
|
if (!syncData) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -94,28 +94,40 @@ export default new Provider({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async uploadWorkspaceContent(token, item, syncData, ifNotTooLate) {
|
async uploadWorkspaceContent({
|
||||||
const file = await googleHelper.uploadAppDataFile({
|
token,
|
||||||
|
content,
|
||||||
|
contentSyncData,
|
||||||
|
ifNotTooLate,
|
||||||
|
}) {
|
||||||
|
const gdriveFile = await googleHelper.uploadAppDataFile({
|
||||||
token,
|
token,
|
||||||
name: JSON.stringify({
|
name: JSON.stringify({
|
||||||
id: item.id,
|
id: content.id,
|
||||||
type: item.type,
|
type: content.type,
|
||||||
hash: item.hash,
|
hash: content.hash,
|
||||||
}),
|
}),
|
||||||
media: JSON.stringify(item),
|
media: JSON.stringify(content),
|
||||||
fileId: syncData && syncData.id,
|
fileId: contentSyncData && contentSyncData.id,
|
||||||
ifNotTooLate,
|
ifNotTooLate,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Return new sync data
|
// Return new sync data
|
||||||
return {
|
return {
|
||||||
id: file.id,
|
contentSyncData: {
|
||||||
itemId: item.id,
|
id: gdriveFile.id,
|
||||||
type: item.type,
|
itemId: content.id,
|
||||||
hash: item.hash,
|
type: content.type,
|
||||||
|
hash: content.hash,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async uploadWorkspaceData(token, item, syncData, ifNotTooLate) {
|
async uploadWorkspaceData({
|
||||||
|
token,
|
||||||
|
item,
|
||||||
|
syncData,
|
||||||
|
ifNotTooLate,
|
||||||
|
}) {
|
||||||
const file = await googleHelper.uploadAppDataFile({
|
const file = await googleHelper.uploadAppDataFile({
|
||||||
token,
|
token,
|
||||||
name: JSON.stringify({
|
name: JSON.stringify({
|
||||||
@ -130,10 +142,12 @@ export default new Provider({
|
|||||||
|
|
||||||
// Return new sync data
|
// Return new sync data
|
||||||
return {
|
return {
|
||||||
|
syncData: {
|
||||||
id: file.id,
|
id: file.id,
|
||||||
itemId: item.id,
|
itemId: item.id,
|
||||||
type: item.type,
|
type: item.type,
|
||||||
hash: item.hash,
|
hash: item.hash,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async listRevisions(token, fileId) {
|
async listRevisions(token, fileId) {
|
||||||
|
@ -7,7 +7,7 @@ import fileSvc from '../fileSvc';
|
|||||||
export default new Provider({
|
export default new Provider({
|
||||||
id: 'googleDrive',
|
id: 'googleDrive',
|
||||||
getToken(location) {
|
getToken(location) {
|
||||||
const token = store.getters['data/googleTokens'][location.sub];
|
const token = store.getters['data/googleTokensBySub'][location.sub];
|
||||||
return token && token.isDrive ? token : null;
|
return token && token.isDrive ? token : null;
|
||||||
},
|
},
|
||||||
getUrl(location) {
|
getUrl(location) {
|
||||||
@ -21,7 +21,7 @@ export default new Provider({
|
|||||||
const state = googleHelper.driveState || {};
|
const state = googleHelper.driveState || {};
|
||||||
if (state.userId) {
|
if (state.userId) {
|
||||||
// Try to find the token corresponding to the user ID
|
// Try to find the token corresponding to the user ID
|
||||||
let token = store.getters['data/googleTokens'][state.userId];
|
let token = store.getters['data/googleTokensBySub'][state.userId];
|
||||||
// If not found or not enough permission, popup an OAuth2 window
|
// If not found or not enough permission, popup an OAuth2 window
|
||||||
if (!token || !token.isDrive) {
|
if (!token || !token.isDrive) {
|
||||||
await store.dispatch('modal/open', { type: 'googleDriveAccount' });
|
await store.dispatch('modal/open', { type: 'googleDriveAccount' });
|
||||||
@ -42,7 +42,7 @@ export default new Provider({
|
|||||||
folderId,
|
folderId,
|
||||||
};
|
};
|
||||||
const workspaceId = utils.makeWorkspaceId(workspaceParams);
|
const workspaceId = utils.makeWorkspaceId(workspaceParams);
|
||||||
const workspace = store.getters['data/sanitizedWorkspaces'][workspaceId];
|
const workspace = store.getters['data/sanitizedWorkspacesById'][workspaceId];
|
||||||
// If we have the workspace, open it by changing the current URL
|
// If we have the workspace, open it by changing the current URL
|
||||||
if (workspace) {
|
if (workspace) {
|
||||||
utils.setQueryParams(workspaceParams);
|
utils.setQueryParams(workspaceParams);
|
||||||
@ -83,7 +83,7 @@ export default new Provider({
|
|||||||
},
|
},
|
||||||
async performAction() {
|
async performAction() {
|
||||||
const state = googleHelper.driveState || {};
|
const state = googleHelper.driveState || {};
|
||||||
const token = store.getters['data/googleTokens'][state.userId];
|
const token = store.getters['data/googleTokensBySub'][state.userId];
|
||||||
switch (token && state.action) {
|
switch (token && state.action) {
|
||||||
case 'create': {
|
case 'create': {
|
||||||
const file = await fileSvc.createFile({}, true);
|
const file = await fileSvc.createFile({}, true);
|
||||||
@ -106,7 +106,7 @@ export default new Provider({
|
|||||||
return Provider.parseContent(content, `${syncLocation.fileId}/content`);
|
return Provider.parseContent(content, `${syncLocation.fileId}/content`);
|
||||||
},
|
},
|
||||||
async uploadContent(token, content, syncLocation, ifNotTooLate) {
|
async uploadContent(token, content, syncLocation, ifNotTooLate) {
|
||||||
const file = store.state.file.itemMap[syncLocation.fileId];
|
const file = store.state.file.itemsById[syncLocation.fileId];
|
||||||
const name = utils.sanitizeName(file && file.name);
|
const name = utils.sanitizeName(file && file.name);
|
||||||
const parents = [];
|
const parents = [];
|
||||||
if (syncLocation.driveParentId) {
|
if (syncLocation.driveParentId) {
|
||||||
|
@ -22,7 +22,7 @@ export default new Provider({
|
|||||||
&& utils.makeWorkspaceId(makeWorkspaceParams(folderId));
|
&& utils.makeWorkspaceId(makeWorkspaceParams(folderId));
|
||||||
|
|
||||||
const getWorkspace = folderId =>
|
const getWorkspace = folderId =>
|
||||||
store.getters['data/sanitizedWorkspaces'][makeWorkspaceId(folderId)];
|
store.getters['data/sanitizedWorkspacesById'][makeWorkspaceId(folderId)];
|
||||||
|
|
||||||
const initFolder = async (token, folder) => {
|
const initFolder = async (token, folder) => {
|
||||||
const appProperties = {
|
const appProperties = {
|
||||||
@ -68,7 +68,7 @@ export default new Provider({
|
|||||||
|
|
||||||
// Update workspace in the store
|
// Update workspace in the store
|
||||||
const workspaceId = makeWorkspaceId(folder.id);
|
const workspaceId = makeWorkspaceId(folder.id);
|
||||||
store.dispatch('data/patchWorkspaces', {
|
store.dispatch('data/patchWorkspacesById', {
|
||||||
[workspaceId]: {
|
[workspaceId]: {
|
||||||
id: workspaceId,
|
id: workspaceId,
|
||||||
sub: token.sub,
|
sub: token.sub,
|
||||||
@ -86,7 +86,7 @@ export default new Provider({
|
|||||||
// 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 { sub } = getWorkspace(utils.queryParams.folderId) || utils.queryParams;
|
const { sub } = getWorkspace(utils.queryParams.folderId) || utils.queryParams;
|
||||||
// See if we already have a token
|
// See if we already have a token
|
||||||
let token = store.getters['data/googleTokens'][sub];
|
let token = store.getters['data/googleTokensBySub'][sub];
|
||||||
// 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
|
||||||
if (!token || !token.isDrive || !token.driveFullAccess) {
|
if (!token || !token.isDrive || !token.driveFullAccess) {
|
||||||
await store.dispatch('modal/open', 'workspaceGoogleRedirection');
|
await store.dispatch('modal/open', 'workspaceGoogleRedirection');
|
||||||
@ -130,14 +130,14 @@ export default new Provider({
|
|||||||
// Fix the URL hash
|
// Fix the URL hash
|
||||||
utils.setQueryParams(makeWorkspaceParams(workspace.folderId));
|
utils.setQueryParams(makeWorkspaceParams(workspace.folderId));
|
||||||
if (workspace.url !== window.location.href) {
|
if (workspace.url !== window.location.href) {
|
||||||
store.dispatch('data/patchWorkspaces', {
|
store.dispatch('data/patchWorkspacesById', {
|
||||||
[workspace.id]: {
|
[workspace.id]: {
|
||||||
...workspace,
|
...workspace,
|
||||||
url: window.location.href,
|
url: window.location.href,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return store.getters['data/sanitizedWorkspaces'][workspace.id];
|
return store.getters['data/sanitizedWorkspacesById'][workspace.id];
|
||||||
},
|
},
|
||||||
async performAction() {
|
async performAction() {
|
||||||
const state = googleHelper.driveState || {};
|
const state = googleHelper.driveState || {};
|
||||||
@ -145,21 +145,21 @@ export default new Provider({
|
|||||||
switch (token && state.action) {
|
switch (token && state.action) {
|
||||||
case 'create': {
|
case 'create': {
|
||||||
const driveFolder = googleHelper.driveActionFolder;
|
const driveFolder = googleHelper.driveActionFolder;
|
||||||
let syncData = store.getters['data/syncData'][driveFolder.id];
|
let syncData = store.getters['data/syncDataById'][driveFolder.id];
|
||||||
if (!syncData && driveFolder.appProperties.id) {
|
if (!syncData && driveFolder.appProperties.id) {
|
||||||
// Create folder if not already synced
|
// Create folder if not already synced
|
||||||
store.commit('folder/setItem', {
|
store.commit('folder/setItem', {
|
||||||
id: driveFolder.appProperties.id,
|
id: driveFolder.appProperties.id,
|
||||||
name: driveFolder.name,
|
name: driveFolder.name,
|
||||||
});
|
});
|
||||||
const item = store.state.folder.itemMap[driveFolder.appProperties.id];
|
const item = store.state.folder.itemsById[driveFolder.appProperties.id];
|
||||||
syncData = {
|
syncData = {
|
||||||
id: driveFolder.id,
|
id: driveFolder.id,
|
||||||
itemId: item.id,
|
itemId: item.id,
|
||||||
type: item.type,
|
type: item.type,
|
||||||
hash: item.hash,
|
hash: item.hash,
|
||||||
};
|
};
|
||||||
store.dispatch('data/patchSyncData', {
|
store.dispatch('data/patchSyncDataById', {
|
||||||
[syncData.id]: syncData,
|
[syncData.id]: syncData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -173,7 +173,7 @@ export default new Provider({
|
|||||||
case 'open': {
|
case 'open': {
|
||||||
// open first file only
|
// open first file only
|
||||||
const firstFile = googleHelper.driveActionFiles[0];
|
const firstFile = googleHelper.driveActionFiles[0];
|
||||||
const syncData = store.getters['data/syncData'][firstFile.id];
|
const syncData = store.getters['data/syncDataById'][firstFile.id];
|
||||||
if (!syncData) {
|
if (!syncData) {
|
||||||
fileIdToOpen = firstFile.id;
|
fileIdToOpen = firstFile.id;
|
||||||
} else {
|
} else {
|
||||||
@ -191,6 +191,10 @@ export default new Provider({
|
|||||||
const { changes, startPageToken } = await googleHelper
|
const { changes, startPageToken } = await googleHelper
|
||||||
.getChanges(syncToken, lastStartPageToken, false, workspace.teamDriveId);
|
.getChanges(syncToken, lastStartPageToken, false, workspace.teamDriveId);
|
||||||
|
|
||||||
|
syncStartPageToken = startPageToken;
|
||||||
|
return changes;
|
||||||
|
},
|
||||||
|
prepareChanges(changes) {
|
||||||
// Collect possible parent IDs
|
// Collect possible parent IDs
|
||||||
const parentIds = {};
|
const parentIds = {};
|
||||||
Object.entries(store.getters['data/syncDataByItemId']).forEach(([id, syncData]) => {
|
Object.entries(store.getters['data/syncDataByItemId']).forEach(([id, syncData]) => {
|
||||||
@ -204,6 +208,7 @@ export default new Provider({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Collect changes
|
// Collect changes
|
||||||
|
const workspace = store.getters['workspace/currentWorkspace'];
|
||||||
const result = [];
|
const result = [];
|
||||||
changes.forEach((change) => {
|
changes.forEach((change) => {
|
||||||
// Ignore changes on StackEdit own folders
|
// Ignore changes on StackEdit own folders
|
||||||
@ -259,21 +264,23 @@ export default new Provider({
|
|||||||
|
|
||||||
if (type === 'file') {
|
if (type === 'file') {
|
||||||
// create a fake change as a file content change
|
// create a fake change as a file content change
|
||||||
|
const id = `${appProperties.id}/content`;
|
||||||
|
const syncDataId = `${change.fileId}/content`;
|
||||||
contentChange = {
|
contentChange = {
|
||||||
item: {
|
item: {
|
||||||
id: `${appProperties.id}/content`,
|
id,
|
||||||
type: 'content',
|
type: 'content',
|
||||||
// Need a truthy value to force saving sync data
|
// Need a truthy value to force saving sync data
|
||||||
hash: 1,
|
hash: 1,
|
||||||
},
|
},
|
||||||
syncData: {
|
syncData: {
|
||||||
id: `${change.fileId}/content`,
|
id: syncDataId,
|
||||||
itemId: `${appProperties.id}/content`,
|
itemId: id,
|
||||||
type: 'content',
|
type: 'content',
|
||||||
// Need a truthy value to force downloading the content
|
// Need a truthy value to force downloading the content
|
||||||
hash: 1,
|
hash: 1,
|
||||||
},
|
},
|
||||||
syncDataId: `${change.fileId}/content`,
|
syncDataId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -288,7 +295,7 @@ export default new Provider({
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Item was removed
|
// Item was removed
|
||||||
const syncData = store.getters['data/syncData'][change.fileId];
|
const syncData = store.getters['data/syncDataById'][change.fileId];
|
||||||
if (syncData && syncData.type === 'file') {
|
if (syncData && syncData.type === 'file') {
|
||||||
// create a fake change as a file content change
|
// create a fake change as a file content change
|
||||||
contentChange = {
|
contentChange = {
|
||||||
@ -304,7 +311,7 @@ export default new Provider({
|
|||||||
result.push(contentChange);
|
result.push(contentChange);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
syncStartPageToken = startPageToken;
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
onChangesApplied() {
|
onChangesApplied() {
|
||||||
@ -312,7 +319,7 @@ export default new Provider({
|
|||||||
syncStartPageToken,
|
syncStartPageToken,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async saveWorkspaceItem(item, syncData, ifNotTooLate) {
|
async saveWorkspaceItem({ item, syncData, ifNotTooLate }) {
|
||||||
const workspace = store.getters['workspace/currentWorkspace'];
|
const workspace = store.getters['workspace/currentWorkspace'];
|
||||||
const syncToken = store.getters['workspace/syncToken'];
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
let file;
|
let file;
|
||||||
@ -363,40 +370,35 @@ export default new Provider({
|
|||||||
hash: item.hash,
|
hash: item.hash,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async removeWorkspaceItem(syncData, ifNotTooLate) {
|
async removeWorkspaceItem({ syncData, ifNotTooLate }) {
|
||||||
// Ignore content deletion
|
// Ignore content deletion
|
||||||
if (syncData.type !== 'content') {
|
if (syncData.type !== 'content') {
|
||||||
const syncToken = store.getters['workspace/syncToken'];
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
await googleHelper.removeFile(syncToken, syncData.id, ifNotTooLate);
|
await googleHelper.removeFile(syncToken, syncData.id, ifNotTooLate);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async downloadWorkspaceContent(token, contentSyncData) {
|
async downloadWorkspaceContent({ token, contentSyncData, fileSyncData }) {
|
||||||
const [fileId] = contentSyncData.itemId.split('/');
|
const data = await googleHelper.downloadFile(token, fileSyncData.id);
|
||||||
const syncData = store.getters['data/syncDataByItemId'][fileId];
|
const content = Provider.parseContent(data, contentSyncData.itemId);
|
||||||
if (!syncData) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const content = await googleHelper.downloadFile(token, syncData.id);
|
|
||||||
const item = Provider.parseContent(content, contentSyncData.itemId);
|
|
||||||
|
|
||||||
// Open the file requested by action if it wasn't synced yet
|
// Open the file requested by action if it wasn't synced yet
|
||||||
if (fileIdToOpen && fileIdToOpen === syncData.id) {
|
if (fileIdToOpen && fileIdToOpen === fileSyncData.id) {
|
||||||
fileIdToOpen = null;
|
fileIdToOpen = null;
|
||||||
// Open the file once downloaded content has been stored
|
// Open the file once downloaded content has been stored
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
store.commit('file/setCurrentId', fileId);
|
store.commit('file/setCurrentId', fileSyncData.itemId);
|
||||||
}, 10);
|
}, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
item,
|
content,
|
||||||
syncData: {
|
contentSyncData: {
|
||||||
...contentSyncData,
|
...contentSyncData,
|
||||||
hash: item.hash,
|
hash: content.hash,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async downloadWorkspaceData(token, dataId, syncData) {
|
async downloadWorkspaceData({ token, syncData }) {
|
||||||
if (!syncData) {
|
if (!syncData) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -411,57 +413,66 @@ export default new Provider({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async uploadWorkspaceContent(token, content, contentSyncData, ifNotTooLate) {
|
async uploadWorkspaceContent({
|
||||||
const [fileId] = content.id.split('/');
|
token,
|
||||||
const syncData = store.getters['data/syncDataByItemId'][fileId];
|
content,
|
||||||
let file;
|
file,
|
||||||
|
fileSyncData,
|
||||||
|
ifNotTooLate,
|
||||||
|
}) {
|
||||||
|
let gdriveFile;
|
||||||
|
let newFileSyncData;
|
||||||
|
|
||||||
if (syncData) {
|
if (fileSyncData) {
|
||||||
// Only update file media
|
// Only update file media
|
||||||
file = await googleHelper.uploadFile({
|
gdriveFile = await googleHelper.uploadFile({
|
||||||
token,
|
token,
|
||||||
media: Provider.serializeContent(content),
|
media: Provider.serializeContent(content),
|
||||||
fileId: syncData.id,
|
fileId: fileSyncData.id,
|
||||||
ifNotTooLate,
|
ifNotTooLate,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Create file with media
|
// Create file with media
|
||||||
const workspace = store.getters['workspace/currentWorkspace'];
|
const workspace = store.getters['workspace/currentWorkspace'];
|
||||||
// Use deepCopy to freeze objects
|
const parentSyncData = store.getters['data/syncDataByItemId'][file.parentId];
|
||||||
const item = utils.deepCopy(store.state.file.itemMap[fileId]);
|
gdriveFile = await googleHelper.uploadFile({
|
||||||
const parentSyncData = store.getters['data/syncDataByItemId'][item.parentId];
|
|
||||||
file = await googleHelper.uploadFile({
|
|
||||||
token,
|
token,
|
||||||
name: item.name,
|
name: file.name,
|
||||||
parents: [parentSyncData ? parentSyncData.id : workspace.folderId],
|
parents: [parentSyncData ? parentSyncData.id : workspace.folderId],
|
||||||
appProperties: {
|
appProperties: {
|
||||||
id: item.id,
|
id: file.id,
|
||||||
folderId: workspace.folderId,
|
folderId: workspace.folderId,
|
||||||
},
|
},
|
||||||
media: Provider.serializeContent(content),
|
media: Provider.serializeContent(content),
|
||||||
ifNotTooLate,
|
ifNotTooLate,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create file syncData
|
// Create file sync data
|
||||||
store.dispatch('data/patchSyncData', {
|
newFileSyncData = {
|
||||||
[file.id]: {
|
id: gdriveFile.id,
|
||||||
id: file.id,
|
itemId: file.id,
|
||||||
itemId: item.id,
|
type: file.type,
|
||||||
type: item.type,
|
hash: file.hash,
|
||||||
hash: item.hash,
|
};
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return new sync data
|
// Return new sync data
|
||||||
return {
|
return {
|
||||||
id: `${file.id}/content`,
|
contentSyncData: {
|
||||||
|
id: `${gdriveFile.id}/content`,
|
||||||
itemId: content.id,
|
itemId: content.id,
|
||||||
type: content.type,
|
type: content.type,
|
||||||
hash: content.hash,
|
hash: content.hash,
|
||||||
|
},
|
||||||
|
fileSyncData: newFileSyncData,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async uploadWorkspaceData(token, item, syncData, ifNotTooLate) {
|
async uploadWorkspaceData({
|
||||||
|
token,
|
||||||
|
item,
|
||||||
|
syncData,
|
||||||
|
ifNotTooLate,
|
||||||
|
}) {
|
||||||
const workspace = store.getters['workspace/currentWorkspace'];
|
const workspace = store.getters['workspace/currentWorkspace'];
|
||||||
const file = await googleHelper.uploadFile({
|
const file = await googleHelper.uploadFile({
|
||||||
token,
|
token,
|
||||||
@ -482,10 +493,12 @@ export default new Provider({
|
|||||||
|
|
||||||
// Return new sync data
|
// Return new sync data
|
||||||
return {
|
return {
|
||||||
|
syncData: {
|
||||||
id: file.id,
|
id: file.id,
|
||||||
itemId: item.id,
|
itemId: item.id,
|
||||||
type: item.type,
|
type: item.type,
|
||||||
hash: item.hash,
|
hash: item.hash,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async listRevisions(token, fileId) {
|
async listRevisions(token, fileId) {
|
||||||
|
@ -4,7 +4,7 @@ import store from '../../../store';
|
|||||||
|
|
||||||
const request = async (token, options = {}) => {
|
const request = async (token, options = {}) => {
|
||||||
const baseUrl = `${token.dbUrl}/`;
|
const baseUrl = `${token.dbUrl}/`;
|
||||||
const getLastToken = () => store.getters['data/couchdbTokens'][token.sub];
|
const getLastToken = () => store.getters['data/couchdbTokensBySub'][token.sub];
|
||||||
|
|
||||||
const assertUnauthorized = (err) => {
|
const assertUnauthorized = (err) => {
|
||||||
if (err.status !== 401) {
|
if (err.status !== 401) {
|
||||||
|
@ -53,8 +53,8 @@ export default {
|
|||||||
fullAccess,
|
fullAccess,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add token to dropboxTokens
|
// Add token to dropbox tokens
|
||||||
store.dispatch('data/setDropboxToken', token);
|
store.dispatch('data/addDropboxToken', token);
|
||||||
return token;
|
return token;
|
||||||
},
|
},
|
||||||
addAccount(fullAccess = false) {
|
addAccount(fullAccess = false) {
|
||||||
|
@ -76,8 +76,8 @@ export default {
|
|||||||
repoFullAccess: scopes.indexOf('repo') !== -1,
|
repoFullAccess: scopes.indexOf('repo') !== -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add token to githubTokens
|
// Add token to github tokens
|
||||||
store.dispatch('data/setGithubToken', token);
|
store.dispatch('data/addGithubToken', token);
|
||||||
return token;
|
return token;
|
||||||
},
|
},
|
||||||
async addAccount(repoFullAccess = false) {
|
async addAccount(repoFullAccess = false) {
|
||||||
@ -204,7 +204,7 @@ export default {
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
sha,
|
sha,
|
||||||
content: utils.decodeBase64(content),
|
data: utils.decodeBase64(content),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export default {
|
|||||||
const { reason } = (((err.body || {}).error || {}).errors || [])[0] || {};
|
const { reason } = (((err.body || {}).error || {}).errors || [])[0] || {};
|
||||||
if (reason === 'authError') {
|
if (reason === 'authError') {
|
||||||
// Mark the token as revoked and get a new one
|
// Mark the token as revoked and get a new one
|
||||||
store.dispatch('data/setGoogleToken', {
|
store.dispatch('data/addGoogleToken', {
|
||||||
...token,
|
...token,
|
||||||
expiresOn: 0,
|
expiresOn: 0,
|
||||||
});
|
});
|
||||||
@ -101,7 +101,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build token object including scopes and sub
|
// Build token object including scopes and sub
|
||||||
const existingToken = store.getters['data/googleTokens'][body.sub];
|
const existingToken = store.getters['data/googleTokensBySub'][body.sub];
|
||||||
const token = {
|
const token = {
|
||||||
scopes,
|
scopes,
|
||||||
accessToken,
|
accessToken,
|
||||||
@ -158,13 +158,13 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add token to googleTokens
|
// Add token to google tokens
|
||||||
store.dispatch('data/setGoogleToken', token);
|
store.dispatch('data/addGoogleToken', token);
|
||||||
return token;
|
return token;
|
||||||
},
|
},
|
||||||
async refreshToken(token, scopes = []) {
|
async refreshToken(token, scopes = []) {
|
||||||
const { sub } = token;
|
const { sub } = token;
|
||||||
const lastToken = store.getters['data/googleTokens'][sub];
|
const lastToken = store.getters['data/googleTokensBySub'][sub];
|
||||||
const mergedScopes = [...new Set([
|
const mergedScopes = [...new Set([
|
||||||
...scopes,
|
...scopes,
|
||||||
...lastToken.scopes,
|
...lastToken.scopes,
|
||||||
|
@ -44,13 +44,13 @@ export default {
|
|||||||
name: body.display_name,
|
name: body.display_name,
|
||||||
sub: `${body.ID}`,
|
sub: `${body.ID}`,
|
||||||
};
|
};
|
||||||
// Add token to wordpressTokens
|
// Add token to wordpress tokens
|
||||||
store.dispatch('data/setWordpressToken', token);
|
store.dispatch('data/addWordpressToken', token);
|
||||||
return token;
|
return token;
|
||||||
},
|
},
|
||||||
async refreshToken(token) {
|
async refreshToken(token) {
|
||||||
const { sub } = token;
|
const { sub } = token;
|
||||||
const lastToken = store.getters['data/wordpressTokens'][sub];
|
const lastToken = store.getters['data/wordpressTokensBySub'][sub];
|
||||||
|
|
||||||
if (lastToken.expiresOn > Date.now() + tokenExpirationMargin) {
|
if (lastToken.expiresOn > Date.now() + tokenExpirationMargin) {
|
||||||
return lastToken;
|
return lastToken;
|
||||||
|
@ -45,8 +45,8 @@ export default {
|
|||||||
sub: uniqueSub,
|
sub: uniqueSub,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add token to zendeskTokens
|
// Add token to zendesk tokens
|
||||||
store.dispatch('data/setZendeskToken', token);
|
store.dispatch('data/addZendeskToken', token);
|
||||||
return token;
|
return token;
|
||||||
},
|
},
|
||||||
addAccount(subdomain, clientId) {
|
addAccount(subdomain, clientId) {
|
||||||
|
@ -5,7 +5,7 @@ import Provider from './common/Provider';
|
|||||||
export default new Provider({
|
export default new Provider({
|
||||||
id: 'wordpress',
|
id: 'wordpress',
|
||||||
getToken(location) {
|
getToken(location) {
|
||||||
return store.getters['data/wordpressTokens'][location.sub];
|
return store.getters['data/wordpressTokensBySub'][location.sub];
|
||||||
},
|
},
|
||||||
getUrl(location) {
|
getUrl(location) {
|
||||||
return `https://wordpress.com/post/${location.siteId}/${location.postId}`;
|
return `https://wordpress.com/post/${location.siteId}/${location.postId}`;
|
||||||
|
@ -5,7 +5,7 @@ import Provider from './common/Provider';
|
|||||||
export default new Provider({
|
export default new Provider({
|
||||||
id: 'zendesk',
|
id: 'zendesk',
|
||||||
getToken(location) {
|
getToken(location) {
|
||||||
return store.getters['data/zendeskTokens'][location.sub];
|
return store.getters['data/zendeskTokensBySub'][location.sub];
|
||||||
},
|
},
|
||||||
getUrl(location) {
|
getUrl(location) {
|
||||||
const token = this.getToken(location);
|
const token = this.getToken(location);
|
||||||
|
@ -40,10 +40,10 @@ const ensureDate = (value, defaultValue) => {
|
|||||||
|
|
||||||
const publish = async (publishLocation) => {
|
const publish = async (publishLocation) => {
|
||||||
const { fileId } = publishLocation;
|
const { fileId } = publishLocation;
|
||||||
const template = store.getters['data/allTemplates'][publishLocation.templateId];
|
const template = store.getters['data/allTemplatesById'][publishLocation.templateId];
|
||||||
const html = await exportSvc.applyTemplate(fileId, template);
|
const html = await exportSvc.applyTemplate(fileId, template);
|
||||||
const content = await localDbSvc.loadItem(`${fileId}/content`);
|
const content = await localDbSvc.loadItem(`${fileId}/content`);
|
||||||
const file = store.state.file.itemMap[fileId];
|
const file = store.state.file.itemsById[fileId];
|
||||||
const properties = utils.computeProperties(content.properties);
|
const properties = utils.computeProperties(content.properties);
|
||||||
const provider = providerRegistry.providers[publishLocation.providerId];
|
const provider = providerRegistry.providers[publishLocation.providerId];
|
||||||
const token = provider.getToken(publishLocation);
|
const token = provider.getToken(publishLocation);
|
||||||
@ -90,7 +90,7 @@ const publishFile = async (fileId) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const file = store.state.file.itemMap[fileId];
|
const file = store.state.file.itemsById[fileId];
|
||||||
store.dispatch('notification/info', `"${file.name}" was published to ${counter} location(s).`);
|
store.dispatch('notification/info', `"${file.name}" was published to ${counter} location(s).`);
|
||||||
} finally {
|
} finally {
|
||||||
await localDbSvc.unloadContents();
|
await localDbSvc.unloadContents();
|
||||||
|
@ -108,7 +108,7 @@ const upgradeSyncedContent = (syncedContent) => {
|
|||||||
const cleanSyncedContent = (syncedContent) => {
|
const cleanSyncedContent = (syncedContent) => {
|
||||||
// Clean syncHistory from removed syncLocations
|
// Clean syncHistory from removed syncLocations
|
||||||
Object.keys(syncedContent.syncHistory).forEach((syncLocationId) => {
|
Object.keys(syncedContent.syncHistory).forEach((syncLocationId) => {
|
||||||
if (syncLocationId !== 'main' && !store.state.syncLocation.itemMap[syncLocationId]) {
|
if (syncLocationId !== 'main' && !store.state.syncLocation.itemsById[syncLocationId]) {
|
||||||
delete syncedContent.syncHistory[syncLocationId];
|
delete syncedContent.syncHistory[syncLocationId];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -129,28 +129,43 @@ const cleanSyncedContent = (syncedContent) => {
|
|||||||
* Apply changes retrieved from the main provider. Update sync data accordingly.
|
* Apply changes retrieved from the main provider. Update sync data accordingly.
|
||||||
*/
|
*/
|
||||||
const applyChanges = (changes) => {
|
const applyChanges = (changes) => {
|
||||||
const storeItemMap = { ...store.getters.allItemMap };
|
const allItemsById = { ...store.getters.allItemsById };
|
||||||
const syncData = { ...store.getters['data/syncData'] };
|
const syncDataById = { ...store.getters['data/syncDataById'] };
|
||||||
|
let getExistingItem;
|
||||||
|
if (workspaceProvider.isGit) {
|
||||||
|
const { itemsByGitPath } = store.getters;
|
||||||
|
getExistingItem = (existingSyncData) => {
|
||||||
|
const items = existingSyncData && itemsByGitPath[existingSyncData.id];
|
||||||
|
return items ? items[0] : null;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
getExistingItem = existingSyncData => existingSyncData && allItemsById[existingSyncData.itemId];
|
||||||
|
}
|
||||||
|
|
||||||
|
const idsToKeep = {};
|
||||||
let saveSyncData = false;
|
let saveSyncData = false;
|
||||||
|
|
||||||
|
// Process each change
|
||||||
changes.forEach((change) => {
|
changes.forEach((change) => {
|
||||||
const existingSyncData = syncData[change.syncDataId];
|
const existingSyncData = syncDataById[change.syncDataId];
|
||||||
const existingItem = existingSyncData && storeItemMap[existingSyncData.itemId];
|
const existingItem = getExistingItem(existingSyncData);
|
||||||
|
// If item was removed
|
||||||
if (!change.item && existingSyncData) {
|
if (!change.item && existingSyncData) {
|
||||||
// Item was removed
|
if (syncDataById[change.syncDataId]) {
|
||||||
if (syncData[change.syncDataId]) {
|
delete syncDataById[change.syncDataId];
|
||||||
delete syncData[change.syncDataId];
|
|
||||||
saveSyncData = true;
|
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 allItemsById[existingItem.id];
|
||||||
}
|
}
|
||||||
|
// If item was modified
|
||||||
} else if (change.item && change.item.hash) {
|
} else if (change.item && change.item.hash) {
|
||||||
// Item was modifed
|
idsToKeep[change.item.id] = true;
|
||||||
|
|
||||||
if ((existingSyncData || {}).hash !== change.syncData.hash) {
|
if ((existingSyncData || {}).hash !== change.syncData.hash) {
|
||||||
syncData[change.syncDataId] = change.syncData;
|
syncDataById[change.syncDataId] = change.syncData;
|
||||||
saveSyncData = true;
|
saveSyncData = true;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@ -162,13 +177,14 @@ const applyChanges = (changes) => {
|
|||||||
&& change.item.type !== 'content' && change.item.type !== 'data'
|
&& change.item.type !== 'content' && change.item.type !== 'data'
|
||||||
) {
|
) {
|
||||||
store.commit(`${change.item.type}/setItem`, change.item);
|
store.commit(`${change.item.type}/setItem`, change.item);
|
||||||
storeItemMap[change.item.id] = change.item;
|
allItemsById[change.item.id] = change.item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (saveSyncData) {
|
if (saveSyncData) {
|
||||||
store.dispatch('data/setSyncData', syncData);
|
store.dispatch('data/setSyncDataById', syncDataById);
|
||||||
|
fileSvc.ensureUniquePaths(idsToKeep);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -192,7 +208,7 @@ const createSyncLocation = (syncLocation) => {
|
|||||||
history: [content.hash],
|
history: [content.hash],
|
||||||
}, syncLocation);
|
}, syncLocation);
|
||||||
await localDbSvc.loadSyncedContent(fileId);
|
await localDbSvc.loadSyncedContent(fileId);
|
||||||
const newSyncedContent = utils.deepCopy(upgradeSyncedContent(store.state.syncedContent.itemMap[`${fileId}/syncedContent`]));
|
const newSyncedContent = utils.deepCopy(upgradeSyncedContent(store.state.syncedContent.itemsById[`${fileId}/syncedContent`]));
|
||||||
const newSyncHistoryItem = [];
|
const newSyncHistoryItem = [];
|
||||||
newSyncedContent.syncHistory[syncLocation.id] = newSyncHistoryItem;
|
newSyncedContent.syncHistory[syncLocation.id] = newSyncHistoryItem;
|
||||||
newSyncHistoryItem[LAST_SEEN] = content.hash;
|
newSyncHistoryItem[LAST_SEEN] = content.hash;
|
||||||
@ -220,7 +236,7 @@ const tooLateChecker = (timeout) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if file is in the temp folder or it's a welcome file.
|
* Return true if file is in the temp folder or is a welcome file.
|
||||||
*/
|
*/
|
||||||
const isTempFile = (fileId) => {
|
const isTempFile = (fileId) => {
|
||||||
const contentId = `${fileId}/content`;
|
const contentId = `${fileId}/content`;
|
||||||
@ -228,8 +244,8 @@ const isTempFile = (fileId) => {
|
|||||||
// If file has already been synced, it's not a temp file
|
// If file has already been synced, it's not a temp file
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const file = store.state.file.itemMap[fileId];
|
const file = store.state.file.itemsById[fileId];
|
||||||
const content = store.state.content.itemMap[contentId];
|
const content = store.state.content.itemsById[contentId];
|
||||||
if (!file || !content) {
|
if (!file || !content) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -251,6 +267,24 @@ const isTempFile = (fileId) => {
|
|||||||
return file.name === 'Welcome file' && welcomeFileHashes[hash] && !hasDiscussions;
|
return file.name === 'Welcome file' && welcomeFileHashes[hash] && !hasDiscussions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch sync data if some have changed in the result.
|
||||||
|
*/
|
||||||
|
const updateSyncData = (result) => {
|
||||||
|
['syncData', 'contentSyncData', 'fileSyncData'].forEach((field) => {
|
||||||
|
const syncData = result[field];
|
||||||
|
if (syncData) {
|
||||||
|
const oldSyncData = store.getters['data/syncDataById'][syncData.id];
|
||||||
|
if (utils.serializeObject(oldSyncData) !== utils.serializeObject(syncData)) {
|
||||||
|
store.dispatch('data/patchSyncDataById', {
|
||||||
|
[syncData.id]: syncData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
class SyncContext {
|
class SyncContext {
|
||||||
restart = false;
|
restart = false;
|
||||||
attempted = {};
|
attempted = {};
|
||||||
@ -270,8 +304,7 @@ const syncFile = async (fileId, syncContext = new SyncContext()) => {
|
|||||||
// Item may not exist if content has not been downloaded yet
|
// Item may not exist if content has not been downloaded yet
|
||||||
}
|
}
|
||||||
|
|
||||||
const getContent = () => store.state.content.itemMap[contentId];
|
const getSyncedContent = () => upgradeSyncedContent(store.state.syncedContent.itemsById[`${fileId}/syncedContent`]);
|
||||||
const getSyncedContent = () => upgradeSyncedContent(store.state.syncedContent.itemMap[`${fileId}/syncedContent`]);
|
|
||||||
const getSyncHistoryItem = syncLocationId => getSyncedContent().syncHistory[syncLocationId];
|
const getSyncHistoryItem = syncLocationId => getSyncedContent().syncHistory[syncLocationId];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -303,57 +336,45 @@ const syncFile = async (fileId, syncContext = new SyncContext()) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// On workspace provider, call downloadWorkspaceContent
|
// On workspace provider, call downloadWorkspaceContent
|
||||||
const oldSyncData = provider.isGit
|
const oldContentSyncData = store.getters['data/syncDataByItemId'][contentId];
|
||||||
? store.getters['data/syncData'][store.getters.itemGitPaths[contentId]]
|
const oldFileSyncData = store.getters['data/syncDataByItemId'][fileId];
|
||||||
: store.getters['data/syncDataByItemId'][contentId];
|
if (!oldContentSyncData || !oldFileSyncData) {
|
||||||
if (!oldSyncData) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const { item, syncData } = await provider.downloadWorkspaceContent(token, oldSyncData);
|
|
||||||
if (!item) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update sync data if changed
|
const { content } = updateSyncData(await provider.downloadWorkspaceContent({
|
||||||
if (syncData
|
token,
|
||||||
&& utils.serializeObject(oldSyncData) !== utils.serializeObject(syncData)
|
contentId,
|
||||||
) {
|
contentSyncData: oldContentSyncData,
|
||||||
store.dispatch('data/patchSyncData', {
|
fileSyncData: oldFileSyncData,
|
||||||
[syncData.id]: syncData,
|
}));
|
||||||
});
|
|
||||||
}
|
// Return the downloaded content
|
||||||
return item;
|
return content;
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadContent = async (item, ifNotTooLate) => {
|
const uploadContent = async (content, ifNotTooLate) => {
|
||||||
// On simple provider, call simply uploadContent
|
// On simple provider, call simply uploadContent
|
||||||
if (syncLocation.id !== 'main') {
|
if (syncLocation.id !== 'main') {
|
||||||
return provider.uploadContent(token, item, syncLocation, ifNotTooLate);
|
return provider.uploadContent(token, content, syncLocation, ifNotTooLate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// On workspace provider, call uploadWorkspaceContent
|
// On workspace provider, call uploadWorkspaceContent
|
||||||
const oldSyncData = provider.isGit
|
const oldContentSyncData = store.getters['data/syncDataByItemId'][contentId];
|
||||||
? store.getters['data/syncData'][store.getters.itemGitPaths[contentId]]
|
if (oldContentSyncData && oldContentSyncData.hash === content.hash) {
|
||||||
: store.getters['data/syncDataByItemId'][contentId];
|
|
||||||
if (oldSyncData && oldSyncData.hash === item.hash) {
|
|
||||||
return syncLocation;
|
return syncLocation;
|
||||||
}
|
}
|
||||||
|
const oldFileSyncData = store.getters['data/syncDataByItemId'][fileId];
|
||||||
|
|
||||||
const syncData = await provider.uploadWorkspaceContent(
|
updateSyncData(await provider.uploadWorkspaceContent({
|
||||||
token,
|
token,
|
||||||
item,
|
content,
|
||||||
oldSyncData,
|
// Use deepCopy to freeze item
|
||||||
|
file: utils.deepCopy(store.state.file.itemsById[fileId]),
|
||||||
|
contentSyncData: oldContentSyncData,
|
||||||
|
fileSyncData: oldFileSyncData,
|
||||||
ifNotTooLate,
|
ifNotTooLate,
|
||||||
);
|
}));
|
||||||
|
|
||||||
// Update sync data if changed
|
|
||||||
if (syncData
|
|
||||||
&& utils.serializeObject(oldSyncData) !== utils.serializeObject(syncData)
|
|
||||||
) {
|
|
||||||
store.dispatch('data/patchSyncData', {
|
|
||||||
[syncData.id]: syncData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return syncLocation
|
// Return syncLocation
|
||||||
return syncLocation;
|
return syncLocation;
|
||||||
@ -366,7 +387,7 @@ const syncFile = async (fileId, syncContext = new SyncContext()) => {
|
|||||||
|
|
||||||
// Merge content
|
// Merge content
|
||||||
let mergedContent;
|
let mergedContent;
|
||||||
const clientContent = utils.deepCopy(getContent());
|
const clientContent = utils.deepCopy(store.state.content.itemsById[contentId]);
|
||||||
if (!clientContent) {
|
if (!clientContent) {
|
||||||
mergedContent = utils.deepCopy(serverContent || null);
|
mergedContent = utils.deepCopy(serverContent || null);
|
||||||
} else if (!serverContent // If sync location has not been created yet
|
} else if (!serverContent // If sync location has not been created yet
|
||||||
@ -401,7 +422,7 @@ const syncFile = async (fileId, syncContext = new SyncContext()) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Retrieve content with its new hash value and freeze it
|
// Retrieve content with its new hash value and freeze it
|
||||||
mergedContent = utils.deepCopy(getContent());
|
mergedContent = utils.deepCopy(store.state.content.itemsById[contentId]);
|
||||||
|
|
||||||
// Make merged content history
|
// Make merged content history
|
||||||
const mergedContentHistory = serverContent ? serverContent.history.slice() : [];
|
const mergedContentHistory = serverContent ? serverContent.history.slice() : [];
|
||||||
@ -504,8 +525,8 @@ const syncFile = async (fileId, syncContext = new SyncContext()) => {
|
|||||||
* Sync a data item, typically settings, workspaces and templates.
|
* Sync a data item, typically settings, workspaces and templates.
|
||||||
*/
|
*/
|
||||||
const syncDataItem = async (dataId) => {
|
const syncDataItem = async (dataId) => {
|
||||||
const getItem = () => store.state.data.itemMap[dataId]
|
const getItem = () => store.state.data.itemsById[dataId]
|
||||||
|| store.state.data.lsItemMap[dataId];
|
|| store.state.data.lsItemsById[dataId];
|
||||||
|
|
||||||
const oldItem = getItem();
|
const oldItem = getItem();
|
||||||
const oldSyncData = store.getters['data/syncDataByItemId'][dataId];
|
const oldSyncData = store.getters['data/syncDataByItemId'][dataId];
|
||||||
@ -515,23 +536,13 @@ const syncDataItem = async (dataId) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const token = workspaceProvider.getToken();
|
const token = workspaceProvider.getToken();
|
||||||
const { item, syncData } = await workspaceProvider.downloadWorkspaceData(
|
const { item } = updateSyncData(await workspaceProvider.downloadWorkspaceData({
|
||||||
token,
|
token,
|
||||||
dataId,
|
syncData: oldSyncData,
|
||||||
oldSyncData,
|
}));
|
||||||
);
|
|
||||||
|
|
||||||
// Update sync data if changed
|
|
||||||
if (syncData
|
|
||||||
&& utils.serializeObject(oldSyncData) !== utils.serializeObject(syncData)
|
|
||||||
) {
|
|
||||||
store.dispatch('data/patchSyncData', {
|
|
||||||
[syncData.id]: syncData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const serverItem = item;
|
const serverItem = item;
|
||||||
const dataSyncData = store.getters['data/dataSyncData'][dataId];
|
const dataSyncData = store.getters['data/dataSyncDataById'][dataId];
|
||||||
let mergedItem = (() => {
|
let mergedItem = (() => {
|
||||||
const clientItem = utils.deepCopy(getItem());
|
const clientItem = utils.deepCopy(getItem());
|
||||||
if (!clientItem) {
|
if (!clientItem) {
|
||||||
@ -575,24 +586,15 @@ const syncDataItem = async (dataId) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upload merged data item
|
// Upload merged data item
|
||||||
const newSyncData = await workspaceProvider.uploadWorkspaceData(
|
updateSyncData(await workspaceProvider.uploadWorkspaceData({
|
||||||
token,
|
token,
|
||||||
mergedItem,
|
item: mergedItem,
|
||||||
syncData,
|
syncData: store.getters['data/syncDataByItemId'][dataId],
|
||||||
tooLateChecker(restartContentSyncAfter),
|
ifNotTooLate: tooLateChecker(restartContentSyncAfter),
|
||||||
);
|
}));
|
||||||
|
|
||||||
// Update sync data if changed
|
|
||||||
if (newSyncData
|
|
||||||
&& utils.serializeObject(syncData) !== utils.serializeObject(newSyncData)
|
|
||||||
) {
|
|
||||||
store.dispatch('data/patchSyncData', {
|
|
||||||
[newSyncData.id]: newSyncData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update data sync data
|
// Update data sync data
|
||||||
store.dispatch('data/patchDataSyncData', {
|
store.dispatch('data/patchDataSyncDataById', {
|
||||||
[dataId]: utils.deepCopy(store.getters['data/syncDataByItemId'][dataId]),
|
[dataId]: utils.deepCopy(store.getters['data/syncDataByItemId'][dataId]),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -617,11 +619,10 @@ const syncWorkspace = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const changes = await workspaceProvider.getChanges();
|
const changes = await workspaceProvider.getChanges();
|
||||||
|
|
||||||
// Apply changes
|
// Apply changes
|
||||||
applyChanges(changes);
|
applyChanges(workspaceProvider.prepareChanges(changes));
|
||||||
if (workspaceProvider.onChangesApplied) {
|
|
||||||
workspaceProvider.onChangesApplied();
|
workspaceProvider.onChangesApplied();
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent from sending items too long after changes have been retrieved
|
// Prevent from sending items too long after changes have been retrieved
|
||||||
const ifNotTooLate = tooLateChecker(restartSyncAfter);
|
const ifNotTooLate = tooLateChecker(restartSyncAfter);
|
||||||
@ -629,33 +630,25 @@ const syncWorkspace = async () => {
|
|||||||
// Called until no item to save
|
// Called until no item to save
|
||||||
const saveNextItem = () => ifNotTooLate(async () => {
|
const saveNextItem = () => ifNotTooLate(async () => {
|
||||||
const storeItemMap = {
|
const storeItemMap = {
|
||||||
...store.state.file.itemMap,
|
...store.state.file.itemsById,
|
||||||
...store.state.folder.itemMap,
|
...store.state.folder.itemsById,
|
||||||
...store.state.syncLocation.itemMap,
|
...store.state.syncLocation.itemsById,
|
||||||
...store.state.publishLocation.itemMap,
|
...store.state.publishLocation.itemsById,
|
||||||
// Deal with contents and data later
|
// Deal with contents and data later
|
||||||
};
|
};
|
||||||
|
|
||||||
let getSyncData;
|
|
||||||
if (workspaceProvider.isGit) {
|
|
||||||
const syncData = store.getters['data/syncData'];
|
|
||||||
getSyncData = id => syncData[store.getters.itemGitPaths[id]];
|
|
||||||
} else {
|
|
||||||
const syncDataByItemId = store.getters['data/syncDataByItemId'];
|
const syncDataByItemId = store.getters['data/syncDataByItemId'];
|
||||||
getSyncData = id => syncDataByItemId[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
const [changedItem, syncDataToUpdate] = utils.someResult(
|
const [changedItem, syncDataToUpdate] = utils.someResult(
|
||||||
Object.entries(storeItemMap),
|
Object.entries(storeItemMap),
|
||||||
([id, item]) => {
|
([id, item]) => {
|
||||||
const existingSyncData = getSyncData(id);
|
const syncData = syncDataByItemId[id];
|
||||||
if ((!existingSyncData || existingSyncData.hash !== item.hash)
|
if ((!syncData || syncData.hash !== item.hash)
|
||||||
// Add file/folder if parent has been added
|
// Add file/folder if parent has been added
|
||||||
&& (!storeItemMap[item.parentId] || getSyncData(item.parentId))
|
&& (!storeItemMap[item.parentId] || syncDataByItemId[item.parentId])
|
||||||
// Add file if content has been added
|
// Add file if content has been added
|
||||||
&& (item.type !== 'file' || getSyncData(`${id}/content`))
|
&& (item.type !== 'file' || syncDataByItemId[`${id}/content`])
|
||||||
) {
|
) {
|
||||||
return [item, existingSyncData];
|
return [item, syncData];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
@ -663,13 +656,13 @@ const syncWorkspace = async () => {
|
|||||||
|
|
||||||
if (changedItem) {
|
if (changedItem) {
|
||||||
const resultSyncData = await workspaceProvider
|
const resultSyncData = await workspaceProvider
|
||||||
.saveWorkspaceItem(
|
.saveWorkspaceItem({
|
||||||
// Use deepCopy to freeze objects
|
// Use deepCopy to freeze objects
|
||||||
utils.deepCopy(changedItem),
|
item: utils.deepCopy(changedItem),
|
||||||
utils.deepCopy(syncDataToUpdate),
|
syncData: utils.deepCopy(syncDataToUpdate),
|
||||||
ifNotTooLate,
|
ifNotTooLate,
|
||||||
);
|
});
|
||||||
store.dispatch('data/patchSyncData', {
|
store.dispatch('data/patchSyncDataById', {
|
||||||
[resultSyncData.id]: resultSyncData,
|
[resultSyncData.id]: resultSyncData,
|
||||||
});
|
});
|
||||||
await saveNextItem();
|
await saveNextItem();
|
||||||
@ -682,38 +675,40 @@ const syncWorkspace = async () => {
|
|||||||
let getItem;
|
let getItem;
|
||||||
let getFileItem;
|
let getFileItem;
|
||||||
if (workspaceProvider.isGit) {
|
if (workspaceProvider.isGit) {
|
||||||
const { gitPathItems } = store.getters;
|
const { itemsByGitPath } = store.getters;
|
||||||
getItem = syncData => gitPathItems[syncData.id];
|
getItem = syncData => itemsByGitPath[syncData.id];
|
||||||
getFileItem = syncData => gitPathItems[syncData.id.slice(1)]; // Remove leading /
|
getFileItem = syncData => itemsByGitPath[syncData.id.slice(1)]; // Remove leading /
|
||||||
} else {
|
} else {
|
||||||
const { allItemMap } = store.getters;
|
const { allItemsById } = store.getters;
|
||||||
getItem = syncData => allItemMap[syncData.itemId];
|
getItem = syncData => allItemsById[syncData.itemId];
|
||||||
getFileItem = syncData => allItemMap[syncData.itemId.split('/')[0]];
|
getFileItem = syncData => allItemsById[syncData.itemId.split('/')[0]];
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncData = store.getters['data/syncData'];
|
const syncDataById = store.getters['data/syncDataById'];
|
||||||
const syncDataToRemove = utils.deepCopy(utils.someResult(
|
const syncDataToRemove = utils.deepCopy(utils.someResult(
|
||||||
Object.values(syncData),
|
Object.values(syncDataById),
|
||||||
(existingSyncData) => {
|
(syncData) => {
|
||||||
if (!getItem(existingSyncData)
|
if (!getItem(syncData)
|
||||||
// We don't want to delete data items, especially on first sync
|
// We don't want to delete data items, especially on first sync
|
||||||
&& existingSyncData.type !== 'data'
|
&& syncData.type !== 'data'
|
||||||
// Remove content only if file has been removed
|
// Remove content only if file has been removed
|
||||||
&& (existingSyncData.type !== 'content'
|
&& (syncData.type !== 'content'
|
||||||
|| !getFileItem(existingSyncData))
|
|| !getFileItem(syncData))
|
||||||
) {
|
) {
|
||||||
return existingSyncData;
|
return syncData;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
if (syncDataToRemove) {
|
if (syncDataToRemove) {
|
||||||
// Use deepCopy to freeze objects
|
await workspaceProvider.removeWorkspaceItem({
|
||||||
await workspaceProvider.removeWorkspaceItem(syncDataToRemove, ifNotTooLate);
|
syncData: syncDataToRemove,
|
||||||
const syncDataCopy = { ...store.getters['data/syncData'] };
|
ifNotTooLate,
|
||||||
|
});
|
||||||
|
const syncDataCopy = { ...store.getters['data/syncDataById'] };
|
||||||
delete syncDataCopy[syncDataToRemove.id];
|
delete syncDataCopy[syncDataToRemove.id];
|
||||||
store.dispatch('data/setSyncData', syncDataCopy);
|
store.dispatch('data/setSyncDataById', syncDataCopy);
|
||||||
await removeNextItem();
|
await removeNextItem();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -727,33 +722,38 @@ const syncWorkspace = async () => {
|
|||||||
await syncDataItem('templates');
|
await syncDataItem('templates');
|
||||||
|
|
||||||
const getOneFileIdToSync = () => {
|
const getOneFileIdToSync = () => {
|
||||||
const contentIds = [...new Set([
|
|
||||||
...Object.keys(localDbSvc.hashMap.content),
|
|
||||||
...store.getters['file/items'].map(file => `${file.id}/content`),
|
|
||||||
])];
|
|
||||||
const contentMap = store.state.content.itemMap;
|
|
||||||
const syncDataById = store.getters['data/syncData'];
|
|
||||||
let getSyncData;
|
let getSyncData;
|
||||||
if (workspaceProvider.isGit) {
|
if (workspaceProvider.isGit) {
|
||||||
const { itemGitPaths } = store.getters;
|
const { gitPathsByItemId } = store.getters;
|
||||||
getSyncData = contentId => syncDataById[itemGitPaths[contentId]];
|
const syncDataById = store.getters['data/syncDataById'];
|
||||||
|
// Use file git path as content may not exist or not be loaded
|
||||||
|
getSyncData = fileId => syncDataById[`/${gitPathsByItemId[fileId]}`];
|
||||||
} else {
|
} else {
|
||||||
const syncDataByItemId = store.getters['data/syncDataByItemId'];
|
const syncDataByItemId = store.getters['data/syncDataByItemId'];
|
||||||
getSyncData = contentId => syncDataByItemId[contentId];
|
getSyncData = (fileId, contentId) => syncDataByItemId[contentId];
|
||||||
}
|
}
|
||||||
|
|
||||||
return utils.someResult(contentIds, (contentId) => {
|
// Collect all [fileId, contentId]
|
||||||
// Get content hash from itemMap or from localDbSvc if not loaded
|
const ids = [
|
||||||
|
...Object.keys(localDbSvc.hashMap.content)
|
||||||
|
.map(contentId => [contentId.split('/')[0], contentId]),
|
||||||
|
...store.getters['file/items']
|
||||||
|
.map(file => [file.id, `${file.id}/content`]),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Find the first content out of sync
|
||||||
|
const contentMap = store.state.content.itemsById;
|
||||||
|
return utils.someResult(ids, ([fileId, contentId]) => {
|
||||||
|
// Get content hash from itemsById or from localDbSvc if not loaded
|
||||||
const loadedContent = contentMap[contentId];
|
const loadedContent = contentMap[contentId];
|
||||||
const hash = loadedContent ? loadedContent.hash : localDbSvc.hashMap.content[contentId];
|
const hash = loadedContent ? loadedContent.hash : localDbSvc.hashMap.content[contentId];
|
||||||
const syncData = getSyncData(contentId);
|
const syncData = getSyncData(fileId, contentId);
|
||||||
if (
|
if (
|
||||||
// Sync if content syncing was not attempted yet
|
// Sync if content syncing was not attempted yet
|
||||||
!syncContext.attempted[contentId] &&
|
!syncContext.attempted[contentId] &&
|
||||||
// And if syncData does not exist or if content hash and syncData hash are inconsistent
|
// And if syncData does not exist or if content hash and syncData hash are inconsistent
|
||||||
(!syncData || syncData.hash !== hash)
|
(!syncData || syncData.hash !== hash)
|
||||||
) {
|
) {
|
||||||
const [fileId] = contentId.split('/');
|
|
||||||
return fileId;
|
return fileId;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -843,7 +843,7 @@ const requestSync = () => {
|
|||||||
|
|
||||||
// Clean files
|
// Clean files
|
||||||
Object.entries(fileHashesToClean).forEach(([fileId, fileHash]) => {
|
Object.entries(fileHashesToClean).forEach(([fileId, fileHash]) => {
|
||||||
const file = store.state.file.itemMap[fileId];
|
const file = store.state.file.itemsById[fileId];
|
||||||
if (file && file.hash === fileHash) {
|
if (file && file.hash === fileHash) {
|
||||||
fileSvc.deleteFile(fileId);
|
fileSvc.deleteFile(fileId);
|
||||||
}
|
}
|
||||||
|
@ -38,12 +38,11 @@ export default {
|
|||||||
parentId: 'temp',
|
parentId: 'temp',
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
const fileItemMap = store.state.file.itemMap;
|
|
||||||
|
|
||||||
// Sanitize file creations
|
// Sanitize file creations
|
||||||
const lastCreated = {};
|
const lastCreated = {};
|
||||||
|
const fileItemsById = store.state.file.itemsById;
|
||||||
Object.entries(store.getters['data/lastCreated']).forEach(([id, createdOn]) => {
|
Object.entries(store.getters['data/lastCreated']).forEach(([id, createdOn]) => {
|
||||||
if (fileItemMap[id] && fileItemMap[id].parentId === 'temp') {
|
if (fileItemsById[id] && fileItemsById[id].parentId === 'temp') {
|
||||||
lastCreated[id] = createdOn;
|
lastCreated[id] = createdOn;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -21,7 +21,7 @@ export default {
|
|||||||
const [type, sub] = parseUserId(userId);
|
const [type, sub] = parseUserId(userId);
|
||||||
|
|
||||||
// Try to find a token with this sub
|
// Try to find a token with this sub
|
||||||
const token = store.getters[`data/${type}Tokens`][sub];
|
const token = store.getters[`data/${type}TokensBySub`][sub];
|
||||||
if (token) {
|
if (token) {
|
||||||
store.commit('userInfo/addItem', {
|
store.commit('userInfo/addItem', {
|
||||||
id: userId,
|
id: userId,
|
||||||
|
@ -31,11 +31,11 @@ module.mutations = {
|
|||||||
|
|
||||||
module.getters = {
|
module.getters = {
|
||||||
...module.getters,
|
...module.getters,
|
||||||
current: ({ itemMap, revisionContent }, getters, rootState, rootGetters) => {
|
current: ({ itemsById, revisionContent }, getters, rootState, rootGetters) => {
|
||||||
if (revisionContent) {
|
if (revisionContent) {
|
||||||
return revisionContent;
|
return revisionContent;
|
||||||
}
|
}
|
||||||
return itemMap[`${rootGetters['file/current'].id}/content`] || empty();
|
return itemsById[`${rootGetters['file/current'].id}/content`] || empty();
|
||||||
},
|
},
|
||||||
currentChangeTrigger: (state, getters) => {
|
currentChangeTrigger: (state, getters) => {
|
||||||
const { current } = getters;
|
const { current } = getters;
|
||||||
@ -63,7 +63,7 @@ module.actions = {
|
|||||||
},
|
},
|
||||||
setRevisionContent({ state, rootGetters, commit }, value) {
|
setRevisionContent({ state, rootGetters, commit }, value) {
|
||||||
const currentFile = rootGetters['file/current'];
|
const currentFile = rootGetters['file/current'];
|
||||||
const currentContent = state.itemMap[`${currentFile.id}/content`];
|
const currentContent = state.itemsById[`${currentFile.id}/content`];
|
||||||
if (currentContent) {
|
if (currentContent) {
|
||||||
const diffs = diffMatchPatch.diff_main(currentContent.text, value.text);
|
const diffs = diffMatchPatch.diff_main(currentContent.text, value.text);
|
||||||
diffMatchPatch.diff_cleanupSemantic(diffs);
|
diffMatchPatch.diff_cleanupSemantic(diffs);
|
||||||
|
@ -5,8 +5,8 @@ const module = moduleTemplate(empty, true);
|
|||||||
|
|
||||||
module.getters = {
|
module.getters = {
|
||||||
...module.getters,
|
...module.getters,
|
||||||
current: ({ itemMap }, getters, rootState, rootGetters) =>
|
current: ({ itemsById }, getters, rootState, rootGetters) =>
|
||||||
itemMap[`${rootGetters['file/current'].id}/contentState`] || empty(),
|
itemsById[`${rootGetters['file/current'].id}/contentState`] || empty(),
|
||||||
};
|
};
|
||||||
|
|
||||||
module.actions = {
|
module.actions = {
|
||||||
|
@ -37,13 +37,13 @@ const lsItemIdSet = new Set(utils.localStorageDataIds);
|
|||||||
|
|
||||||
// Getter/setter/patcher factories
|
// Getter/setter/patcher factories
|
||||||
const getter = id => state => ((lsItemIdSet.has(id)
|
const getter = id => state => ((lsItemIdSet.has(id)
|
||||||
? state.lsItemMap
|
? state.lsItemsById
|
||||||
: state.itemMap)[id] || {}).data || empty(id).data;
|
: state.itemsById)[id] || {}).data || empty(id).data;
|
||||||
const setter = id => ({ commit }, data) => commit('setItem', itemTemplate(id, data));
|
const setter = id => ({ commit }, data) => commit('setItem', itemTemplate(id, data));
|
||||||
const patcher = id => ({ state, commit }, data) => {
|
const patcher = id => ({ state, commit }, data) => {
|
||||||
const item = Object.assign(empty(id), (lsItemIdSet.has(id)
|
const item = Object.assign(empty(id), (lsItemIdSet.has(id)
|
||||||
? state.lsItemMap
|
? state.lsItemsById
|
||||||
: state.itemMap)[id]);
|
: state.itemsById)[id]);
|
||||||
commit('setItem', {
|
commit('setItem', {
|
||||||
...empty(id),
|
...empty(id),
|
||||||
data: typeof data === 'object' ? {
|
data: typeof data === 'object' ? {
|
||||||
@ -83,10 +83,10 @@ const additionalTemplates = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// For tokens
|
// For tokens
|
||||||
const tokenSetter = providerId => ({ getters, dispatch }, token) => {
|
const tokenAdder = providerId => ({ getters, dispatch }, token) => {
|
||||||
dispatch('patchTokens', {
|
dispatch('patchTokensByProviderId', {
|
||||||
[providerId]: {
|
[providerId]: {
|
||||||
...getters[`${providerId}Tokens`],
|
...getters[`${providerId}TokensBySub`],
|
||||||
[token.sub]: token,
|
[token.sub]: token,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -99,12 +99,12 @@ export default {
|
|||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {
|
state: {
|
||||||
// Data items stored in the DB
|
// Data items stored in the DB
|
||||||
itemMap: {},
|
itemsById: {},
|
||||||
// Data items stored in the localStorage
|
// Data items stored in the localStorage
|
||||||
lsItemMap: {},
|
lsItemsById: {},
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
setItem: ({ itemMap, lsItemMap }, value) => {
|
setItem: ({ itemsById, lsItemsById }, value) => {
|
||||||
// Create an empty item and override its data field
|
// Create an empty item and override its data field
|
||||||
const emptyItem = empty(value.id);
|
const emptyItem = empty(value.id);
|
||||||
const data = typeof value.data === 'object'
|
const data = typeof value.data === 'object'
|
||||||
@ -117,20 +117,20 @@ export default {
|
|||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Store item in itemMap or lsItemMap if its stored in the localStorage
|
// Store item in itemsById or lsItemsById if its stored in the localStorage
|
||||||
Vue.set(lsItemIdSet.has(item.id) ? lsItemMap : itemMap, item.id, item);
|
Vue.set(lsItemIdSet.has(item.id) ? lsItemsById : itemsById, item.id, item);
|
||||||
},
|
},
|
||||||
deleteItem({ itemMap }, id) {
|
deleteItem({ itemsById }, id) {
|
||||||
// Only used by localDbSvc to clean itemMap from object moved to localStorage
|
// Only used by localDbSvc to clean itemsById from object moved to localStorage
|
||||||
Vue.delete(itemMap, id);
|
Vue.delete(itemsById, id);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
workspaces: getter('workspaces'),
|
workspacesById: getter('workspaces'),
|
||||||
sanitizedWorkspaces: (state, { workspaces }, rootState, rootGetters) => {
|
sanitizedWorkspacesById: (state, { workspacesById }, rootState, rootGetters) => {
|
||||||
const sanitizedWorkspaces = {};
|
const sanitizedWorkspacesById = {};
|
||||||
const mainWorkspaceToken = rootGetters['workspace/mainWorkspaceToken'];
|
const mainWorkspaceToken = rootGetters['workspace/mainWorkspaceToken'];
|
||||||
Object.entries(workspaces).forEach(([id, workspace]) => {
|
Object.entries(workspacesById).forEach(([id, workspace]) => {
|
||||||
const sanitizedWorkspace = {
|
const sanitizedWorkspace = {
|
||||||
id,
|
id,
|
||||||
providerId: mainWorkspaceToken && 'googleDriveAppData',
|
providerId: mainWorkspaceToken && 'googleDriveAppData',
|
||||||
@ -141,9 +141,9 @@ export default {
|
|||||||
urlParser.href = workspace.url || 'app';
|
urlParser.href = workspace.url || 'app';
|
||||||
const params = utils.parseQueryParams(urlParser.hash.slice(1));
|
const params = utils.parseQueryParams(urlParser.hash.slice(1));
|
||||||
sanitizedWorkspace.url = utils.addQueryParams('app', params, true);
|
sanitizedWorkspace.url = utils.addQueryParams('app', params, true);
|
||||||
sanitizedWorkspaces[id] = sanitizedWorkspace;
|
sanitizedWorkspacesById[id] = sanitizedWorkspace;
|
||||||
});
|
});
|
||||||
return sanitizedWorkspaces;
|
return sanitizedWorkspacesById;
|
||||||
},
|
},
|
||||||
settings: getter('settings'),
|
settings: getter('settings'),
|
||||||
computedSettings: (state, { settings }) => {
|
computedSettings: (state, { settings }) => {
|
||||||
@ -170,9 +170,9 @@ export default {
|
|||||||
},
|
},
|
||||||
localSettings: getter('localSettings'),
|
localSettings: getter('localSettings'),
|
||||||
layoutSettings: getter('layoutSettings'),
|
layoutSettings: getter('layoutSettings'),
|
||||||
templates: getter('templates'),
|
templatesById: getter('templates'),
|
||||||
allTemplates: (state, { templates }) => ({
|
allTemplatesById: (state, { templatesById }) => ({
|
||||||
...templates,
|
...templatesById,
|
||||||
...additionalTemplates,
|
...additionalTemplates,
|
||||||
}),
|
}),
|
||||||
lastCreated: getter('lastCreated'),
|
lastCreated: getter('lastCreated'),
|
||||||
@ -186,42 +186,51 @@ export default {
|
|||||||
result[currentFileId] = Date.now();
|
result[currentFileId] = Date.now();
|
||||||
}
|
}
|
||||||
return Object.keys(result)
|
return Object.keys(result)
|
||||||
.filter(id => rootState.file.itemMap[id])
|
.filter(id => rootState.file.itemsById[id])
|
||||||
.sort((id1, id2) => result[id2] - result[id1])
|
.sort((id1, id2) => result[id2] - result[id1])
|
||||||
.slice(0, 20);
|
.slice(0, 20);
|
||||||
},
|
},
|
||||||
syncData: getter('syncData'),
|
syncDataById: getter('syncData'),
|
||||||
syncDataByItemId: (state, { syncData }) => {
|
syncDataByItemId: (state, { syncDataById }, rootState, rootGetters) => {
|
||||||
const result = {};
|
const result = {};
|
||||||
Object.entries(syncData).forEach(([, value]) => {
|
if (rootGetters['workspace/currentWorkspaceIsGit']) {
|
||||||
result[value.itemId] = value;
|
Object.entries(rootGetters.gitPathsByItemId).forEach(([id, path]) => {
|
||||||
|
const syncDataEntry = syncDataById[path];
|
||||||
|
if (syncDataEntry) {
|
||||||
|
result[id] = syncDataEntry;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
Object.entries(syncDataById).forEach(([, syncDataEntry]) => {
|
||||||
|
result[syncDataEntry.itemId] = syncDataEntry;
|
||||||
|
});
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
syncDataByType: (state, { syncData }) => {
|
syncDataByType: (state, { syncDataById }) => {
|
||||||
const result = {};
|
const result = {};
|
||||||
utils.types.forEach((type) => {
|
utils.types.forEach((type) => {
|
||||||
result[type] = {};
|
result[type] = {};
|
||||||
});
|
});
|
||||||
Object.entries(syncData).forEach(([, item]) => {
|
Object.entries(syncDataById).forEach(([, item]) => {
|
||||||
if (result[item.type]) {
|
if (result[item.type]) {
|
||||||
result[item.type][item.itemId] = item;
|
result[item.type][item.itemId] = item;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
dataSyncData: getter('dataSyncData'),
|
dataSyncDataById: getter('dataSyncData'),
|
||||||
tokens: getter('tokens'),
|
tokensByProviderId: getter('tokens'),
|
||||||
googleTokens: (state, { tokens }) => tokens.google || {},
|
googleTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.google || {},
|
||||||
couchdbTokens: (state, { tokens }) => tokens.couchdb || {},
|
couchdbTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.couchdb || {},
|
||||||
dropboxTokens: (state, { tokens }) => tokens.dropbox || {},
|
dropboxTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.dropbox || {},
|
||||||
githubTokens: (state, { tokens }) => tokens.github || {},
|
githubTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.github || {},
|
||||||
wordpressTokens: (state, { tokens }) => tokens.wordpress || {},
|
wordpressTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.wordpress || {},
|
||||||
zendeskTokens: (state, { tokens }) => tokens.zendesk || {},
|
zendeskTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.zendesk || {},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setWorkspaces: setter('workspaces'),
|
setWorkspacesById: setter('workspaces'),
|
||||||
patchWorkspaces: patcher('workspaces'),
|
patchWorkspacesById: patcher('workspaces'),
|
||||||
setSettings: setter('settings'),
|
setSettings: setter('settings'),
|
||||||
patchLocalSettings: patcher('localSettings'),
|
patchLocalSettings: patcher('localSettings'),
|
||||||
patchLayoutSettings: patcher('layoutSettings'),
|
patchLayoutSettings: patcher('layoutSettings'),
|
||||||
@ -257,15 +266,15 @@ export default {
|
|||||||
setSideBarPanel: ({ dispatch }, value) => dispatch('patchLayoutSettings', {
|
setSideBarPanel: ({ dispatch }, value) => dispatch('patchLayoutSettings', {
|
||||||
sideBarPanel: value === undefined ? 'menu' : value,
|
sideBarPanel: value === undefined ? 'menu' : value,
|
||||||
}),
|
}),
|
||||||
setTemplates: ({ commit }, data) => {
|
setTemplatesById: ({ commit }, templatesById) => {
|
||||||
const dataToCommit = {
|
const templatesToCommit = {
|
||||||
...data,
|
...templatesById,
|
||||||
};
|
};
|
||||||
// We don't store additional templates
|
// We don't store additional templates
|
||||||
Object.keys(additionalTemplates).forEach((id) => {
|
Object.keys(additionalTemplates).forEach((id) => {
|
||||||
delete dataToCommit[id];
|
delete templatesToCommit[id];
|
||||||
});
|
});
|
||||||
commit('setItem', itemTemplate('templates', dataToCommit));
|
commit('setItem', itemTemplate('templates', templatesToCommit));
|
||||||
},
|
},
|
||||||
setLastCreated: setter('lastCreated'),
|
setLastCreated: setter('lastCreated'),
|
||||||
setLastOpenedId: ({ getters, commit, rootState }, fileId) => {
|
setLastOpenedId: ({ getters, commit, rootState }, fileId) => {
|
||||||
@ -274,21 +283,21 @@ export default {
|
|||||||
// Remove entries that don't exist anymore
|
// Remove entries that don't exist anymore
|
||||||
const cleanedLastOpened = {};
|
const cleanedLastOpened = {};
|
||||||
Object.entries(lastOpened).forEach(([id, value]) => {
|
Object.entries(lastOpened).forEach(([id, value]) => {
|
||||||
if (rootState.file.itemMap[id]) {
|
if (rootState.file.itemsById[id]) {
|
||||||
cleanedLastOpened[id] = value;
|
cleanedLastOpened[id] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
commit('setItem', itemTemplate('lastOpened', cleanedLastOpened));
|
commit('setItem', itemTemplate('lastOpened', cleanedLastOpened));
|
||||||
},
|
},
|
||||||
setSyncData: setter('syncData'),
|
setSyncDataById: setter('syncData'),
|
||||||
patchSyncData: patcher('syncData'),
|
patchSyncDataById: patcher('syncData'),
|
||||||
patchDataSyncData: patcher('dataSyncData'),
|
patchDataSyncDataById: patcher('dataSyncData'),
|
||||||
patchTokens: patcher('tokens'),
|
patchTokensByProviderId: patcher('tokens'),
|
||||||
setGoogleToken: tokenSetter('google'),
|
addGoogleToken: tokenAdder('google'),
|
||||||
setCouchdbToken: tokenSetter('couchdb'),
|
addCouchdbToken: tokenAdder('couchdb'),
|
||||||
setDropboxToken: tokenSetter('dropbox'),
|
addDropboxToken: tokenAdder('dropbox'),
|
||||||
setGithubToken: tokenSetter('github'),
|
addGithubToken: tokenAdder('github'),
|
||||||
setWordpressToken: tokenSetter('wordpress'),
|
addWordpressToken: tokenAdder('wordpress'),
|
||||||
setZendeskToken: tokenSetter('zendesk'),
|
addZendeskToken: tokenAdder('zendesk'),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -10,10 +10,10 @@ module.state = {
|
|||||||
|
|
||||||
module.getters = {
|
module.getters = {
|
||||||
...module.getters,
|
...module.getters,
|
||||||
current: ({ itemMap, currentId }) => itemMap[currentId] || empty(),
|
current: ({ itemsById, currentId }) => itemsById[currentId] || empty(),
|
||||||
isCurrentTemp: (state, { current }) => current.parentId === 'temp',
|
isCurrentTemp: (state, { current }) => current.parentId === 'temp',
|
||||||
lastOpened: ({ itemMap }, { items }, rootState, rootGetters) =>
|
lastOpened: ({ itemsById }, { items }, rootState, rootGetters) =>
|
||||||
itemMap[rootGetters['data/lastOpenedIds'][0]] || items[0] || empty(),
|
itemsById[rootGetters['data/lastOpenedIds'][0]] || items[0] || empty(),
|
||||||
};
|
};
|
||||||
|
|
||||||
module.mutations = {
|
module.mutations = {
|
||||||
|
@ -75,14 +75,14 @@ const store = new Vuex.Store({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
allItemMap: (state) => {
|
allItemsById: (state) => {
|
||||||
const result = {};
|
const result = {};
|
||||||
utils.types.forEach(type => Object.assign(result, state[type].itemMap));
|
utils.types.forEach(type => Object.assign(result, state[type].itemsById));
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
itemPaths: (state, getters) => {
|
pathsByItemId: (state, getters) => {
|
||||||
const result = {};
|
const result = {};
|
||||||
const folderMap = state.folder.itemMap;
|
const foldersById = state.folder.itemsById;
|
||||||
const getPath = (item) => {
|
const getPath = (item) => {
|
||||||
let itemPath = result[item.id];
|
let itemPath = result[item.id];
|
||||||
if (!itemPath) {
|
if (!itemPath) {
|
||||||
@ -90,10 +90,10 @@ const store = new Vuex.Store({
|
|||||||
itemPath = `.stackedit-trash/${item.name}`;
|
itemPath = `.stackedit-trash/${item.name}`;
|
||||||
} else {
|
} else {
|
||||||
let { name } = item;
|
let { name } = item;
|
||||||
if (folderMap[item.id]) {
|
if (foldersById[item.id]) {
|
||||||
name += '/';
|
name += '/';
|
||||||
}
|
}
|
||||||
const parentFolder = folderMap[item.parentId];
|
const parentFolder = foldersById[item.parentId];
|
||||||
if (parentFolder) {
|
if (parentFolder) {
|
||||||
itemPath = getPath(parentFolder) + name;
|
itemPath = getPath(parentFolder) + name;
|
||||||
} else {
|
} else {
|
||||||
@ -104,33 +104,38 @@ const store = new Vuex.Store({
|
|||||||
result[item.id] = itemPath;
|
result[item.id] = itemPath;
|
||||||
return itemPath;
|
return itemPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
[
|
[
|
||||||
...getters['folder/items'],
|
...getters['folder/items'],
|
||||||
...getters['file/items'],
|
...getters['file/items'],
|
||||||
].forEach(item => getPath(item));
|
].forEach(item => getPath(item));
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
pathItems: (state, { allItemMap, itemPaths }) => {
|
itemsByPath: (state, { allItemsById, pathsByItemId }) => {
|
||||||
const result = {};
|
const result = {};
|
||||||
Object.entries(itemPaths).forEach(([id, path]) => {
|
Object.entries(pathsByItemId).forEach(([id, path]) => {
|
||||||
const items = result[path] || [];
|
const items = result[path] || [];
|
||||||
items.push(allItemMap[id]);
|
items.push(allItemsById[id]);
|
||||||
result[path] = items;
|
result[path] = items;
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
itemGitPaths: (state, { allItemMap, itemPaths }) => {
|
gitPathsByItemId: (state, { allItemsById, pathsByItemId }) => {
|
||||||
const result = {};
|
const result = {};
|
||||||
Object.entries(allItemMap).forEach(([id, item]) => {
|
Object.entries(allItemsById).forEach(([id, item]) => {
|
||||||
if (item.type === 'data') {
|
if (item.type === 'data') {
|
||||||
result[id] = `.stackedit-data/${id}.json`;
|
result[id] = `.stackedit-data/${id}.json`;
|
||||||
} else if (item.type === 'file') {
|
} else if (item.type === 'file') {
|
||||||
result[id] = `${itemPaths[id]}.md`;
|
const filePath = pathsByItemId[id];
|
||||||
|
result[id] = `${filePath}.md`;
|
||||||
|
result[`${id}/content`] = `/${filePath}.md`;
|
||||||
} else if (item.type === 'content') {
|
} else if (item.type === 'content') {
|
||||||
const [fileId] = id.split('/');
|
const [fileId] = id.split('/');
|
||||||
result[id] = `/${itemPaths[fileId]}.md`;
|
const filePath = pathsByItemId[fileId];
|
||||||
|
result[fileId] = `${filePath}.md`;
|
||||||
|
result[id] = `/${filePath}.md`;
|
||||||
} else if (item.type === 'folder') {
|
} else if (item.type === 'folder') {
|
||||||
result[id] = itemPaths[id];
|
result[id] = pathsByItemId[id];
|
||||||
} else if (item.type === 'syncLocation' || item.type === 'publishLocation') {
|
} else if (item.type === 'syncLocation' || item.type === 'publishLocation') {
|
||||||
// locations are stored as paths
|
// locations are stored as paths
|
||||||
const encodedItem = utils.encodeBase64(utils.serializeObject({
|
const encodedItem = utils.encodeBase64(utils.serializeObject({
|
||||||
@ -140,17 +145,20 @@ const store = new Vuex.Store({
|
|||||||
fileId: undefined,
|
fileId: undefined,
|
||||||
}), true);
|
}), true);
|
||||||
const extension = item.type === 'syncLocation' ? 'sync' : 'publish';
|
const extension = item.type === 'syncLocation' ? 'sync' : 'publish';
|
||||||
result[id] = `${itemPaths[item.fileId]}.${encodedItem}.${extension}`;
|
result[id] = `${pathsByItemId[item.fileId]}.${encodedItem}.${extension}`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
gitPathItems: (state, { allItemMap, itemGitPaths }) => {
|
itemsByGitPath: (state, { allItemsById, gitPathsByItemId }) => {
|
||||||
const result = {};
|
const result = {};
|
||||||
Object.entries(itemGitPaths).forEach(([id, path]) => {
|
Object.entries(gitPathsByItemId).forEach(([id, path]) => {
|
||||||
|
const item = allItemsById[id];
|
||||||
|
if (item) {
|
||||||
const items = result[path] || [];
|
const items = result[path] || [];
|
||||||
items.push(allItemMap[id]);
|
items.push(item);
|
||||||
result[path] = items;
|
result[path] = items;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
@ -8,10 +8,10 @@ export default (empty, simpleHash = false) => {
|
|||||||
return {
|
return {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {
|
state: {
|
||||||
itemMap: {},
|
itemsById: {},
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
items: ({ itemMap }) => Object.values(itemMap),
|
items: ({ itemsById }) => Object.values(itemsById),
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
setItem(state, value) {
|
setItem(state, value) {
|
||||||
@ -19,20 +19,20 @@ export default (empty, simpleHash = false) => {
|
|||||||
if (!item.hash || !simpleHash) {
|
if (!item.hash || !simpleHash) {
|
||||||
item.hash = hashFunc(item);
|
item.hash = hashFunc(item);
|
||||||
}
|
}
|
||||||
Vue.set(state.itemMap, item.id, item);
|
Vue.set(state.itemsById, item.id, item);
|
||||||
},
|
},
|
||||||
patchItem(state, patch) {
|
patchItem(state, patch) {
|
||||||
const item = state.itemMap[patch.id];
|
const item = state.itemsById[patch.id];
|
||||||
if (item) {
|
if (item) {
|
||||||
Object.assign(item, patch);
|
Object.assign(item, patch);
|
||||||
item.hash = hashFunc(item);
|
item.hash = hashFunc(item);
|
||||||
Vue.set(state.itemMap, item.id, item);
|
Vue.set(state.itemsById, item.id, item);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
deleteItem(state, id) {
|
deleteItem(state, id) {
|
||||||
Vue.delete(state.itemMap, id);
|
Vue.delete(state.itemsById, id);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {},
|
actions: {},
|
||||||
|
@ -5,8 +5,8 @@ const module = moduleTemplate(empty, true);
|
|||||||
|
|
||||||
module.getters = {
|
module.getters = {
|
||||||
...module.getters,
|
...module.getters,
|
||||||
current: ({ itemMap }, getters, rootState, rootGetters) =>
|
current: ({ itemsById }, getters, rootState, rootGetters) =>
|
||||||
itemMap[`${rootGetters['file/current'].id}/syncedContent`] || empty(),
|
itemsById[`${rootGetters['file/current'].id}/syncedContent`] || empty(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default module;
|
export default module;
|
||||||
|
@ -3,11 +3,11 @@ import Vue from 'vue';
|
|||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {
|
state: {
|
||||||
itemMap: {},
|
itemsById: {},
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
addItem: ({ itemMap }, item) => {
|
addItem: ({ itemsById }, item) => {
|
||||||
Vue.set(itemMap, item.id, item);
|
Vue.set(itemsById, item.id, item);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -16,13 +16,14 @@ export default {
|
|||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
mainWorkspace: (state, getters, rootState, rootGetters) => {
|
mainWorkspace: (state, getters, rootState, rootGetters) => {
|
||||||
const workspaces = rootGetters['data/sanitizedWorkspaces'];
|
const sanitizedWorkspacesById = rootGetters['data/sanitizedWorkspacesById'];
|
||||||
return workspaces.main;
|
return sanitizedWorkspacesById.main;
|
||||||
},
|
},
|
||||||
currentWorkspace: ({ currentWorkspaceId }, { mainWorkspace }, rootState, rootGetters) => {
|
currentWorkspace: ({ currentWorkspaceId }, { mainWorkspace }, rootState, rootGetters) => {
|
||||||
const workspaces = rootGetters['data/sanitizedWorkspaces'];
|
const sanitizedWorkspacesById = rootGetters['data/sanitizedWorkspacesById'];
|
||||||
return workspaces[currentWorkspaceId] || mainWorkspace;
|
return sanitizedWorkspacesById[currentWorkspaceId] || mainWorkspace;
|
||||||
},
|
},
|
||||||
|
currentWorkspaceIsGit: (state, { currentWorkspace }) => currentWorkspace.providerId === 'githubWorkspace',
|
||||||
hasUniquePaths: (state, { currentWorkspace }) =>
|
hasUniquePaths: (state, { currentWorkspace }) =>
|
||||||
currentWorkspace.providerId === 'githubWorkspace',
|
currentWorkspace.providerId === 'githubWorkspace',
|
||||||
lastSyncActivityKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastSyncActivity`,
|
lastSyncActivityKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastSyncActivity`,
|
||||||
|
@ -9,8 +9,8 @@ const select = (id) => {
|
|||||||
store.commit('explorer/setSelectedId', id);
|
store.commit('explorer/setSelectedId', id);
|
||||||
expect(store.getters['explorer/selectedNode'].item.id).toEqual(id);
|
expect(store.getters['explorer/selectedNode'].item.id).toEqual(id);
|
||||||
};
|
};
|
||||||
const ensureExists = file => expect(store.getters.allItemMap).toHaveProperty(file.id);
|
const ensureExists = file => expect(store.getters.allItemsById).toHaveProperty(file.id);
|
||||||
const ensureNotExists = file => expect(store.getters.allItemMap).not.toHaveProperty(file.id);
|
const ensureNotExists = file => expect(store.getters.allItemsById).not.toHaveProperty(file.id);
|
||||||
|
|
||||||
describe('Explorer.vue', () => {
|
describe('Explorer.vue', () => {
|
||||||
it('should create new files in the root folder', () => {
|
it('should create new files in the root folder', () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user