Github workspace (part 2)
This commit is contained in:
parent
53ccee0d84
commit
b896a2e086
12
package-lock.json
generated
12
package-lock.json
generated
@ -14498,9 +14498,9 @@
|
||||
}
|
||||
},
|
||||
"vue": {
|
||||
"version": "2.5.13",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.5.13.tgz",
|
||||
"integrity": "sha512-3D+lY7HTkKbtswDM4BBHgqyq+qo8IAEE8lz8va1dz3LLmttjgo0FxairO4r1iN2OBqk8o1FyL4hvzzTFEdQSEw=="
|
||||
"version": "2.5.16",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.5.16.tgz",
|
||||
"integrity": "sha512-/ffmsiVuPC8PsWcFkZngdpas19ABm5mh2wA7iDqcltyCTwlgZjHGeJYOXkBMo422iPwIcviOtrTCUpSfXmToLQ=="
|
||||
},
|
||||
"vue-hot-reload-api": {
|
||||
"version": "2.2.4",
|
||||
@ -14588,9 +14588,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"vuex": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-2.5.0.tgz",
|
||||
"integrity": "sha512-5oJPOJySBgSgSzoeO+gZB/BbN/XsapgIF6tz34UwJqnGZMQurzIO3B4KIBf862gfc9ya+oduY5sSkq+5/oOilQ=="
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.0.1.tgz",
|
||||
"integrity": "sha512-wLoqz0B7DSZtgbWL1ShIBBCjv22GV5U+vcBFox658g6V0s4wZV9P4YjCNyoHSyIBpj1f29JBoNQIqD82cR4O3w=="
|
||||
},
|
||||
"w3c-hr-time": {
|
||||
"version": "1.0.1",
|
||||
|
@ -52,8 +52,8 @@
|
||||
"serve-static": "^1.12.6",
|
||||
"tmp": "^0.0.33",
|
||||
"turndown": "^4.0.1",
|
||||
"vue": "^2.3.3",
|
||||
"vuex": "^2.3.1"
|
||||
"vue": "^2.5.16",
|
||||
"vuex": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.7.2",
|
||||
|
@ -28,6 +28,7 @@
|
||||
<script>
|
||||
import { mapState, mapGetters, mapActions } from 'vuex';
|
||||
import ExplorerNode from './ExplorerNode';
|
||||
import explorerSvc from '../services/explorerSvc';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -49,10 +50,8 @@ export default {
|
||||
...mapActions('data', [
|
||||
'toggleExplorer',
|
||||
]),
|
||||
...mapActions('explorer', [
|
||||
'newItem',
|
||||
'deleteItem',
|
||||
]),
|
||||
newItem: isFolder => explorerSvc.newItem(isFolder),
|
||||
deleteItem: () => explorerSvc.deleteItem(),
|
||||
editItem() {
|
||||
const node = this.selectedNode;
|
||||
if (!node.isTrash && !node.isTemp) {
|
||||
|
@ -19,7 +19,8 @@
|
||||
|
||||
<script>
|
||||
import { mapMutations, mapActions } from 'vuex';
|
||||
import utils from '../services/utils';
|
||||
import fileSvc from '../services/fileSvc';
|
||||
import explorerSvc from '../services/explorerSvc';
|
||||
|
||||
export default {
|
||||
name: 'explorer-node', // Required for recursivity
|
||||
@ -77,8 +78,6 @@ export default {
|
||||
]),
|
||||
...mapActions('explorer', [
|
||||
'setDragTarget',
|
||||
'newItem',
|
||||
'deleteItem',
|
||||
]),
|
||||
select(id = this.node.item.id, doOpen = true) {
|
||||
const node = this.$store.getters['explorer/nodeMap'][id];
|
||||
@ -102,31 +101,26 @@ export default {
|
||||
const newChildNode = this.$store.state.explorer.newChildNode;
|
||||
if (!cancel && !newChildNode.isNil && newChildNode.item.name) {
|
||||
if (newChildNode.isFolder) {
|
||||
const id = utils.uid();
|
||||
this.$store.commit('folder/setItem', {
|
||||
...newChildNode.item,
|
||||
id,
|
||||
name: utils.sanitizeName(newChildNode.item.name),
|
||||
});
|
||||
this.select(id);
|
||||
fileSvc.storeItem(newChildNode.item)
|
||||
.then(item => this.select(item.id), () => { /* cancel */ });
|
||||
} else {
|
||||
this.$store.dispatch('createFile', newChildNode.item)
|
||||
.then(file => this.select(file.id));
|
||||
fileSvc.createFile(newChildNode.item)
|
||||
.then(item => this.select(item.id), () => { /* cancel */ });
|
||||
}
|
||||
}
|
||||
this.$store.commit('explorer/setNewItem', null);
|
||||
},
|
||||
submitEdit(cancel) {
|
||||
const editingNode = this.$store.getters['explorer/editingNode'];
|
||||
const id = editingNode.item.id;
|
||||
const item = this.$store.getters['explorer/editingNode'].item;
|
||||
const value = this.editingValue;
|
||||
if (!cancel && id && value) {
|
||||
this.$store.commit(editingNode.isFolder ? 'folder/patchItem' : 'file/patchItem', {
|
||||
id,
|
||||
name: utils.sanitizeName(value),
|
||||
});
|
||||
}
|
||||
this.setEditingId(null);
|
||||
if (!cancel && item.id && value) {
|
||||
fileSvc.storeItem({
|
||||
...item,
|
||||
name: value,
|
||||
})
|
||||
.catch(() => { /* cancel */ });
|
||||
}
|
||||
},
|
||||
setDragSourceId(evt) {
|
||||
if (this.node.noDrag) {
|
||||
@ -169,11 +163,11 @@ export default {
|
||||
items: [{
|
||||
name: 'New file',
|
||||
disabled: !this.node.isFolder || this.node.isTrash,
|
||||
perform: () => this.newItem(false),
|
||||
perform: () => explorerSvc.newItem(false),
|
||||
}, {
|
||||
name: 'New folder',
|
||||
disabled: !this.node.isFolder || this.node.isTrash || this.node.isTemp,
|
||||
perform: () => this.newItem(true),
|
||||
perform: () => explorerSvc.newItem(true),
|
||||
}, {
|
||||
type: 'separator',
|
||||
}, {
|
||||
@ -182,7 +176,7 @@ export default {
|
||||
perform: () => this.setEditingId(this.node.item.id),
|
||||
}, {
|
||||
name: 'Delete',
|
||||
perform: () => this.deleteItem(),
|
||||
perform: () => explorerSvc.deleteItem(),
|
||||
}],
|
||||
})
|
||||
.then(item => item.perform());
|
||||
|
@ -19,7 +19,7 @@
|
||||
<!-- Title -->
|
||||
<div class="navigation-bar__title navigation-bar__title--fake text-input"></div>
|
||||
<div class="navigation-bar__title navigation-bar__title--text text-input" :style="{width: titleWidth + 'px'}">{{title}}</div>
|
||||
<input class="navigation-bar__title navigation-bar__title--input text-input" :class="{'navigation-bar__title--focus': titleFocus, 'navigation-bar__title--scrolling': titleScrolling}" :style="{width: titleWidth + 'px'}" @focus="editTitle(true)" @blur="editTitle(false)" @keydown.enter="submitTitle()" @keydown.esc="submitTitle(true)" @mouseenter="titleHover = true" @mouseleave="titleHover = false" v-model="title">
|
||||
<input class="navigation-bar__title navigation-bar__title--input text-input" :class="{'navigation-bar__title--focus': titleFocus, 'navigation-bar__title--scrolling': titleScrolling}" :style="{width: titleWidth + 'px'}" @focus="editTitle(true)" @blur="editTitle(false)" @keydown.enter="submitTitle(false)" @keydown.esc="submitTitle(true)" @mouseenter="titleHover = true" @mouseleave="titleHover = false" v-model="title">
|
||||
<!-- Sync/Publish -->
|
||||
<div class="flex flex--row" :class="{'navigation-bar__hidden': styles.hideLocations}">
|
||||
<a class="navigation-bar__button navigation-bar__button--location button" :class="{'navigation-bar__button--blink': location.id === currentLocation.id}" v-for="location in syncLocations" :key="location.id" :href="location.url" target="_blank" v-title="'Synchronized location'"><icon-provider :provider-id="location.providerId"></icon-provider></a>
|
||||
@ -56,6 +56,7 @@ import tempFileSvc from '../services/tempFileSvc';
|
||||
import utils from '../services/utils';
|
||||
import pagedownButtons from '../data/pagedownButtons';
|
||||
import store from '../store';
|
||||
import fileSvc from '../services/fileSvc';
|
||||
|
||||
// According to mousetrap
|
||||
const mod = /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'Meta' : 'Ctrl';
|
||||
@ -151,6 +152,13 @@ export default {
|
||||
}
|
||||
return result;
|
||||
},
|
||||
editCancelTrigger() {
|
||||
const current = this.$store.getters['file/current'];
|
||||
return utils.serializeObject([
|
||||
current.id,
|
||||
current.name,
|
||||
]);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('content', [
|
||||
@ -190,10 +198,13 @@ export default {
|
||||
this.titleInputElt.setSelectionRange(0, this.titleInputElt.value.length);
|
||||
} else {
|
||||
const title = this.title.trim();
|
||||
this.title = this.$store.getters['file/current'].name;
|
||||
if (title) {
|
||||
this.$store.dispatch('file/patchCurrent', { name: utils.sanitizeName(title) });
|
||||
} else {
|
||||
this.title = this.$store.getters['file/current'].name;
|
||||
fileSvc.storeItem({
|
||||
...this.$store.getters['file/current'],
|
||||
name: title,
|
||||
})
|
||||
.catch(() => { /* Cancel */ });
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -209,9 +220,10 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.$watch(
|
||||
() => this.$store.getters['file/current'].name,
|
||||
(name) => {
|
||||
this.title = name;
|
||||
() => this.editCancelTrigger,
|
||||
() => {
|
||||
this.title = '';
|
||||
this.editTitle(false);
|
||||
}, { immediate: true });
|
||||
},
|
||||
mounted() {
|
||||
|
@ -3,8 +3,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import userSvc from '../services/userSvc';
|
||||
|
||||
export default {
|
||||
props: ['providerId'],
|
||||
computed: {
|
||||
@ -15,8 +13,5 @@ export default {
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
userSvc.getInfo(this.userId);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -51,7 +51,7 @@ export default {
|
||||
this.$store.dispatch('modal/commentDeletion')
|
||||
.then(
|
||||
() => this.$store.dispatch('discussion/cleanCurrentFile', { filterComment: this.comment }),
|
||||
() => {}); // Cancel
|
||||
() => { /* Cancel */ });
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
@ -99,7 +99,7 @@ export default {
|
||||
() => this.$store.dispatch('discussion/cleanCurrentFile', {
|
||||
filterDiscussion: this.currentDiscussion,
|
||||
}),
|
||||
() => {}); // Cancel
|
||||
() => { /* Cancel */ });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="comment__header flex flex--row flex--space-between flex--align-center">
|
||||
<div class="comment__user flex flex--row flex--align-center">
|
||||
<div class="comment__user-image">
|
||||
<user-image :user-id="loginToken.sub"></user-image>
|
||||
<user-image :user-id="userId"></user-image>
|
||||
</div>
|
||||
<span class="user-name">{{loginToken.name}}</span>
|
||||
</div>
|
||||
@ -33,9 +33,12 @@ export default {
|
||||
components: {
|
||||
UserImage,
|
||||
},
|
||||
computed: mapGetters('workspace', [
|
||||
'loginToken',
|
||||
]),
|
||||
computed: {
|
||||
...mapGetters('workspace', [
|
||||
'loginToken',
|
||||
'userId',
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('discussion', [
|
||||
'setNewCommentFocus',
|
||||
@ -53,7 +56,7 @@ export default {
|
||||
const discussionId = this.$store.state.discussion.currentDiscussionId;
|
||||
const comment = {
|
||||
discussionId,
|
||||
sub: this.loginToken.sub,
|
||||
sub: this.userId,
|
||||
text,
|
||||
created: Date.now(),
|
||||
};
|
||||
|
@ -35,19 +35,19 @@ export default {
|
||||
exportMarkdown() {
|
||||
const currentFile = this.$store.getters['file/current'];
|
||||
return exportSvc.exportToDisk(currentFile.id, 'md')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(() => { /* Cancel */ });
|
||||
},
|
||||
exportHtml() {
|
||||
return this.$store.dispatch('modal/open', 'htmlExport')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(() => { /* Cancel */ });
|
||||
},
|
||||
exportPdf() {
|
||||
return this.$store.dispatch('modal/open', 'pdfExport')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(() => { /* Cancel */ });
|
||||
},
|
||||
exportPandoc() {
|
||||
return this.$store.dispatch('modal/open', 'pandocExport')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(() => { /* Cancel */ });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -100,7 +100,7 @@ export default {
|
||||
return googleHelper.signin()
|
||||
.then(
|
||||
() => syncSvc.requestSync(),
|
||||
() => {}, // Cancel
|
||||
() => { /* Cancel */ },
|
||||
);
|
||||
},
|
||||
close() {
|
||||
|
@ -29,6 +29,7 @@ import htmlSanitizer from '../../libs/htmlSanitizer';
|
||||
import MenuEntry from './common/MenuEntry';
|
||||
import Provider from '../../services/providers/common/Provider';
|
||||
import store from '../../store';
|
||||
import fileSvc from '../../services/fileSvc';
|
||||
|
||||
const turndownService = new TurndownService(store.getters['data/computedSettings'].turndown);
|
||||
|
||||
@ -55,16 +56,18 @@ export default {
|
||||
onImportMarkdown(evt) {
|
||||
const file = evt.target.files[0];
|
||||
readFile(file)
|
||||
.then(content => this.$store.dispatch('createFile', {
|
||||
.then(content => fileSvc.createFile({
|
||||
...Provider.parseContent(content),
|
||||
name: file.name,
|
||||
})
|
||||
.then(item => this.$store.commit('file/setCurrentId', item.id)));
|
||||
.then(
|
||||
item => this.$store.commit('file/setCurrentId', item.id)),
|
||||
() => { /* Cancel */ });
|
||||
},
|
||||
onImportHtml(evt) {
|
||||
const file = evt.target.files[0];
|
||||
readFile(file)
|
||||
.then(content => this.$store.dispatch('createFile', {
|
||||
.then(content => fileSvc.createFile({
|
||||
...Provider.parseContent(
|
||||
turndownService.turndown(
|
||||
htmlSanitizer.sanitizeHtml(content)
|
||||
@ -72,7 +75,9 @@ export default {
|
||||
)),
|
||||
name: file.name,
|
||||
}))
|
||||
.then(item => this.$store.commit('file/setCurrentId', item.id));
|
||||
.then(
|
||||
item => this.$store.commit('file/setCurrentId', item.id),
|
||||
() => { /* Cancel */ });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="menu-info-entries">
|
||||
<div class="menu-entry menu-entry--info flex flex--row flex--align-center" v-if="loginToken">
|
||||
<div class="menu-entry__icon menu-entry__icon--image">
|
||||
<user-image :user-id="loginToken.sub"></user-image>
|
||||
<user-image :user-id="userId"></user-image>
|
||||
</div>
|
||||
<span>Signed in as <b>{{loginToken.name}}</b>.</span>
|
||||
</div>
|
||||
@ -97,6 +97,7 @@ export default {
|
||||
'currentWorkspace',
|
||||
'syncToken',
|
||||
'loginToken',
|
||||
'userId',
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
@ -107,12 +108,12 @@ export default {
|
||||
return googleHelper.signin()
|
||||
.then(
|
||||
() => syncSvc.requestSync(),
|
||||
() => {}, // Cancel
|
||||
() => { /* Cancel */ },
|
||||
);
|
||||
},
|
||||
fileProperties() {
|
||||
return this.$store.dispatch('modal/open', 'fileProperties')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(() => { /* Cancel */ });
|
||||
},
|
||||
print() {
|
||||
print();
|
||||
|
@ -82,14 +82,14 @@ export default {
|
||||
return this.$store.dispatch('modal/open', 'settings')
|
||||
.then(
|
||||
settings => this.$store.dispatch('data/setSettings', settings),
|
||||
() => {}, // Cancel
|
||||
() => { /* Cancel */ },
|
||||
);
|
||||
},
|
||||
templates() {
|
||||
return this.$store.dispatch('modal/open', 'templates')
|
||||
.then(
|
||||
({ templates }) => this.$store.dispatch('data/setTemplates', templates),
|
||||
() => {}, // Cancel
|
||||
() => { /* Cancel */ },
|
||||
);
|
||||
},
|
||||
reset() {
|
||||
|
@ -123,6 +123,8 @@ const openPublishModal = (token, type) => store.dispatch('modal/open', {
|
||||
token,
|
||||
}).then(publishLocation => publishSvc.createPublishLocation(publishLocation));
|
||||
|
||||
const onCancel = () => {};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MenuEntry,
|
||||
@ -181,68 +183,68 @@ export default {
|
||||
type: 'googleDriveAccount',
|
||||
onResolve: () => googleHelper.addDriveAccount(!store.getters['data/localSettings'].googleDriveRestrictedAccess),
|
||||
})
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
addDropboxAccount() {
|
||||
return this.$store.dispatch('modal/open', {
|
||||
type: 'dropboxAccount',
|
||||
onResolve: () => dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess),
|
||||
})
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
addGithubAccount() {
|
||||
return this.$store.dispatch('modal/open', {
|
||||
type: 'githubAccount',
|
||||
onResolve: () => githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess),
|
||||
})
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
addWordpressAccount() {
|
||||
return wordpressHelper.addAccount()
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
addBloggerAccount() {
|
||||
return googleHelper.addBloggerAccount()
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
addZendeskAccount() {
|
||||
return this.$store.dispatch('modal/open', {
|
||||
type: 'zendeskAccount',
|
||||
onResolve: ({ subdomain, clientId }) => zendeskHelper.addAccount(subdomain, clientId),
|
||||
})
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
publishGoogleDrive(token) {
|
||||
return openPublishModal(token, 'googleDrivePublish')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
publishDropbox(token) {
|
||||
return openPublishModal(token, 'dropboxPublish')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
publishGithub(token) {
|
||||
return openPublishModal(token, 'githubPublish')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
publishGist(token) {
|
||||
return openPublishModal(token, 'gistPublish')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
publishWordpress(token) {
|
||||
return openPublishModal(token, 'wordpressPublish')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
publishBlogger(token) {
|
||||
return openPublishModal(token, 'bloggerPublish')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
publishBloggerPage(token) {
|
||||
return openPublishModal(token, 'bloggerPagePublish')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
publishZendesk(token) {
|
||||
return openPublishModal(token, 'zendeskPublish')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -101,6 +101,8 @@ const openSyncModal = (token, type) => store.dispatch('modal/open', {
|
||||
token,
|
||||
}).then(syncLocation => syncSvc.createSyncLocation(syncLocation));
|
||||
|
||||
const onCancel = () => {};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MenuEntry,
|
||||
@ -150,21 +152,21 @@ export default {
|
||||
type: 'googleDriveAccount',
|
||||
onResolve: () => googleHelper.addDriveAccount(!store.getters['data/localSettings'].googleDriveRestrictedAccess),
|
||||
})
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
addDropboxAccount() {
|
||||
return this.$store.dispatch('modal/open', {
|
||||
type: 'dropboxAccount',
|
||||
onResolve: () => dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess),
|
||||
})
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
addGithubAccount() {
|
||||
return this.$store.dispatch('modal/open', {
|
||||
type: 'githubAccount',
|
||||
onResolve: () => githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess),
|
||||
})
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
openGoogleDrive(token) {
|
||||
return googleHelper.openPicker(token, 'doc')
|
||||
@ -178,11 +180,11 @@ export default {
|
||||
},
|
||||
saveGoogleDrive(token) {
|
||||
return openSyncModal(token, 'googleDriveSave')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
saveDropbox(token) {
|
||||
return openSyncModal(token, 'dropboxSave')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
openGithub(token) {
|
||||
return store.dispatch('modal/open', {
|
||||
@ -194,11 +196,11 @@ export default {
|
||||
},
|
||||
saveGithub(token) {
|
||||
return openSyncModal(token, 'githubSave')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
saveGist(token) {
|
||||
return openSyncModal(token, 'gistSync')
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -31,6 +31,8 @@ import { mapGetters } from 'vuex';
|
||||
import MenuEntry from './common/MenuEntry';
|
||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
|
||||
const onCancel = () => {};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MenuEntry,
|
||||
@ -48,13 +50,13 @@ export default {
|
||||
return this.$store.dispatch('modal/open', {
|
||||
type: 'couchdbWorkspace',
|
||||
})
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
addGithubWorkspace() {
|
||||
return this.$store.dispatch('modal/open', {
|
||||
type: 'githubWorkspace',
|
||||
})
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
addGoogleDriveWorkspace() {
|
||||
return googleHelper.addDriveAccount(true)
|
||||
@ -62,7 +64,7 @@ export default {
|
||||
type: 'googleDriveWorkspace',
|
||||
token,
|
||||
}))
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(onCancel);
|
||||
},
|
||||
manageWorkspaces() {
|
||||
return this.$store.dispatch('modal/open', 'workspaceManagement');
|
||||
|
@ -2,7 +2,7 @@
|
||||
<modal-inner class="modal__inner-1--about-modal" aria-label="About">
|
||||
<div class="modal__content">
|
||||
<div class="logo-background"></div>
|
||||
<small>v{{version}} — © 2018 Dock5 Software</small>
|
||||
<small>v{{version}}<br>© 2013-2018 Dock5 Software</small>
|
||||
<hr>
|
||||
StackEdit on <a target="_blank" href="https://github.com/benweet/stackedit/">GitHub</a>
|
||||
<br>
|
||||
|
@ -70,7 +70,8 @@ export default modalTemplate({
|
||||
if (err.status !== 401) {
|
||||
throw err;
|
||||
}
|
||||
this.$store.dispatch('modal/sponsorOnly');
|
||||
this.$store.dispatch('modal/sponsorOnly')
|
||||
.catch(() => { /* Cancel */ });
|
||||
}))
|
||||
.catch((err) => {
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
|
@ -63,7 +63,8 @@ export default modalTemplate({
|
||||
if (err.status !== 401) {
|
||||
throw err;
|
||||
}
|
||||
this.$store.dispatch('modal/sponsorOnly');
|
||||
this.$store.dispatch('modal/sponsorOnly')
|
||||
.catch(() => { /* Cancel */ });
|
||||
}))
|
||||
.catch((err) => {
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
|
@ -79,7 +79,7 @@ export default {
|
||||
return this.$store.dispatch('modal/removeWorkspace')
|
||||
.then(
|
||||
() => localDbSvc.removeWorkspace(id),
|
||||
() => {}, // Cancel
|
||||
() => { /* Cancel */ },
|
||||
);
|
||||
},
|
||||
},
|
||||
|
@ -42,7 +42,7 @@ export default {
|
||||
this.$store.dispatch('modal/open', 'sponsor');
|
||||
}
|
||||
})
|
||||
.catch(() => {}); // Cancel
|
||||
.catch(() => { /* Cancel */ });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -1,9 +1,10 @@
|
||||
import store from '../store';
|
||||
import fileSvc from './fileSvc';
|
||||
import utils from './utils';
|
||||
|
||||
export default {
|
||||
importBackup(jsonValue) {
|
||||
const nameMap = {};
|
||||
async importBackup(jsonValue) {
|
||||
const fileNameMap = {};
|
||||
const folderNameMap = {};
|
||||
const parentIdMap = {};
|
||||
const textMap = {};
|
||||
const propertiesMap = {};
|
||||
@ -22,24 +23,18 @@ export default {
|
||||
// StackEdit v4 format
|
||||
const [, v4Id, type] = v4Match;
|
||||
if (type === 'title') {
|
||||
nameMap[v4Id] = value;
|
||||
fileNameMap[v4Id] = value;
|
||||
} else if (type === 'content') {
|
||||
textMap[v4Id] = value;
|
||||
}
|
||||
} else if (value.type === 'folder') {
|
||||
// StackEdit v5 folder
|
||||
const folderId = utils.uid();
|
||||
const name = utils.sanitizeName(value.name);
|
||||
const parentId = `${value.parentId || ''}` || null;
|
||||
store.commit('folder/setItem', {
|
||||
id: folderId,
|
||||
name,
|
||||
parentId,
|
||||
});
|
||||
folderIdMap[id] = folderId;
|
||||
folderIdMap[id] = utils.uid();
|
||||
folderNameMap[id] = value.name;
|
||||
parentIdMap[id] = `${value.parentId || ''}`;
|
||||
} else if (value.type === 'file') {
|
||||
// StackEdit v5 file
|
||||
nameMap[id] = utils.sanitizeName(value.name);
|
||||
fileNameMap[id] = value.name;
|
||||
parentIdMap[id] = `${value.parentId || ''}`;
|
||||
} else if (value.type === 'content') {
|
||||
// StackEdit v5 content
|
||||
@ -54,14 +49,20 @@ export default {
|
||||
}
|
||||
});
|
||||
|
||||
// Go through the maps
|
||||
Object.entries(nameMap).forEach(([externalId, name]) => store.dispatch('createFile', {
|
||||
name,
|
||||
await utils.awaitSequence(Object.keys(folderNameMap), async externalId => fileSvc.storeItem({
|
||||
id: folderIdMap[externalId],
|
||||
type: 'folder',
|
||||
name: folderNameMap[externalId],
|
||||
parentId: folderIdMap[parentIdMap[externalId]],
|
||||
}, true));
|
||||
|
||||
await utils.awaitSequence(Object.keys(fileNameMap), async externalId => fileSvc.createFile({
|
||||
name: fileNameMap[externalId],
|
||||
parentId: folderIdMap[parentIdMap[externalId]],
|
||||
text: textMap[externalId],
|
||||
properties: propertiesMap[externalId],
|
||||
discussions: discussionsMap[externalId],
|
||||
comments: commentsMap[externalId],
|
||||
}));
|
||||
}, true));
|
||||
},
|
||||
};
|
||||
|
85
src/services/explorerSvc.js
Normal file
85
src/services/explorerSvc.js
Normal file
@ -0,0 +1,85 @@
|
||||
import store from '../store';
|
||||
import fileSvc from './fileSvc';
|
||||
|
||||
export default {
|
||||
newItem(isFolder = false) {
|
||||
let parentId = store.getters['explorer/selectedNodeFolder'].item.id;
|
||||
if (parentId === 'trash' // Not allowed to create new items in the trash
|
||||
|| (isFolder && parentId === 'temp') // Not allowed to create new folders in the temp folder
|
||||
) {
|
||||
parentId = null;
|
||||
}
|
||||
store.dispatch('explorer/openNode', parentId);
|
||||
store.commit('explorer/setNewItem', {
|
||||
type: isFolder ? 'folder' : 'file',
|
||||
parentId,
|
||||
});
|
||||
},
|
||||
deleteItem() {
|
||||
const selectedNode = store.getters['explorer/selectedNode'];
|
||||
if (selectedNode.isNil) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (selectedNode.isTrash || selectedNode.item.parentId === 'trash') {
|
||||
return store.dispatch('modal/trashDeletion').catch(() => { /* Cancel */ });
|
||||
}
|
||||
|
||||
// See if we have a dialog to show
|
||||
let modalAction;
|
||||
let moveToTrash = true;
|
||||
if (selectedNode.isTemp) {
|
||||
modalAction = 'modal/tempFolderDeletion';
|
||||
moveToTrash = false;
|
||||
} else if (selectedNode.item.parentId === 'temp') {
|
||||
modalAction = 'modal/tempFileDeletion';
|
||||
moveToTrash = false;
|
||||
} else if (selectedNode.isFolder) {
|
||||
modalAction = 'modal/folderDeletion';
|
||||
}
|
||||
|
||||
return (modalAction
|
||||
? store.dispatch(modalAction, selectedNode.item)
|
||||
: Promise.resolve())
|
||||
.then(() => {
|
||||
const deleteFile = (id) => {
|
||||
if (moveToTrash) {
|
||||
store.commit('file/patchItem', {
|
||||
id,
|
||||
parentId: 'trash',
|
||||
});
|
||||
} else {
|
||||
fileSvc.deleteFile(id);
|
||||
}
|
||||
};
|
||||
|
||||
if (selectedNode === store.getters['explorer/selectedNode']) {
|
||||
const currentFileId = store.getters['file/current'].id;
|
||||
let doClose = selectedNode.item.id === currentFileId;
|
||||
if (selectedNode.isFolder) {
|
||||
const recursiveDelete = (folderNode) => {
|
||||
folderNode.folders.forEach(recursiveDelete);
|
||||
folderNode.files.forEach((fileNode) => {
|
||||
doClose = doClose || fileNode.item.id === currentFileId;
|
||||
deleteFile(fileNode.item.id);
|
||||
});
|
||||
store.commit('folder/deleteItem', folderNode.item.id);
|
||||
};
|
||||
recursiveDelete(selectedNode);
|
||||
} else {
|
||||
deleteFile(selectedNode.item.id);
|
||||
}
|
||||
if (doClose) {
|
||||
// Close the current file by opening the last opened, not deleted one
|
||||
store.getters['data/lastOpenedIds'].some((id) => {
|
||||
const file = store.state.file.itemMap[id];
|
||||
if (file.parentId === 'trash') {
|
||||
return false;
|
||||
}
|
||||
store.commit('file/setCurrentId', id);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}, () => { /* Cancel */ });
|
||||
},
|
||||
};
|
162
src/services/fileSvc.js
Normal file
162
src/services/fileSvc.js
Normal file
@ -0,0 +1,162 @@
|
||||
import store from '../store';
|
||||
import utils from './utils';
|
||||
|
||||
const forbiddenFolderNameMatcher = /^\.stackedit-data$|^\.stackedit-trash$|\.md$|\.sync$|\.publish$/;
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Create a file in the store with the specified fields.
|
||||
*/
|
||||
createFile(fields = {}, background = false) {
|
||||
const id = utils.uid();
|
||||
const file = {
|
||||
id,
|
||||
name: utils.sanitizeName(fields.name),
|
||||
parentId: fields.parentId || null,
|
||||
};
|
||||
const content = {
|
||||
id: `${id}/content`,
|
||||
text: utils.sanitizeText(fields.text || store.getters['data/computedSettings'].newFileContent),
|
||||
properties: utils.sanitizeText(
|
||||
fields.properties || store.getters['data/computedSettings'].newFileProperties),
|
||||
discussions: fields.discussions || {},
|
||||
comments: fields.comments || {},
|
||||
};
|
||||
const nameStripped = file.name !== utils.defaultName && file.name !== fields.name;
|
||||
|
||||
// Check if there is a path conflict
|
||||
const workspaceUniquePaths = store.getters['workspace/hasUniquePaths'];
|
||||
let pathConflict;
|
||||
if (workspaceUniquePaths) {
|
||||
const parentPath = store.getters.itemPaths[file.parentId] || '';
|
||||
const path = parentPath + file.name;
|
||||
pathConflict = !!store.getters.pathItems[path];
|
||||
}
|
||||
|
||||
// Show warning dialogs and then save in the store
|
||||
return Promise.resolve()
|
||||
.then(() => !background && nameStripped && store.dispatch('modal/stripName', fields.name))
|
||||
.then(() => !background && pathConflict && store.dispatch('modal/pathConflict', fields.name))
|
||||
.then(() => {
|
||||
store.commit('content/setItem', content);
|
||||
store.commit('file/setItem', file);
|
||||
if (workspaceUniquePaths) {
|
||||
this.makePathUnique(id);
|
||||
}
|
||||
return store.state.file.itemMap[id];
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Make sanity checks and then create/update the folder/file in the store.
|
||||
*/
|
||||
async storeItem(item, background = false) {
|
||||
const id = item.id || utils.uid();
|
||||
const sanitizedName = utils.sanitizeName(item.name);
|
||||
|
||||
if (item.type === 'folder' && forbiddenFolderNameMatcher.exec(sanitizedName)) {
|
||||
if (background) {
|
||||
return null;
|
||||
}
|
||||
await store.dispatch('modal/unauthorizedName', item.name);
|
||||
throw new Error('Unauthorized name.');
|
||||
}
|
||||
|
||||
const workspaceUniquePaths = store.getters['workspace/hasUniquePaths'];
|
||||
|
||||
// Show warning dialogs
|
||||
if (!background) {
|
||||
// If name has been stripped
|
||||
if (sanitizedName !== utils.defaultName && sanitizedName !== item.name) {
|
||||
await store.dispatch('modal/stripName', item.name);
|
||||
}
|
||||
// Check if there is a path conflict
|
||||
if (workspaceUniquePaths) {
|
||||
const parentPath = store.getters.itemPaths[item.parentId] || '';
|
||||
const path = parentPath + sanitizedName;
|
||||
const pathItems = store.getters.pathItems[path] || [];
|
||||
if (pathItems.some(itemWithSamePath => itemWithSamePath.id !== id)) {
|
||||
await store.dispatch('modal/pathConflict', item.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save item in the store
|
||||
store.commit(`${item.type}/setItem`, {
|
||||
id,
|
||||
parentId: item.parentId || null,
|
||||
name: sanitizedName,
|
||||
});
|
||||
|
||||
// Ensure path uniqueness
|
||||
if (workspaceUniquePaths) {
|
||||
this.makePathUnique(id);
|
||||
}
|
||||
return store.getters.allItemMap[id];
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a file in the store and all its related items.
|
||||
*/
|
||||
deleteFile(fileId) {
|
||||
// Delete the file
|
||||
store.commit('file/deleteItem', fileId);
|
||||
// Delete the content
|
||||
store.commit('content/deleteItem', `${fileId}/content`);
|
||||
// Delete the syncedContent
|
||||
store.commit('syncedContent/deleteItem', `${fileId}/syncedContent`);
|
||||
// Delete the contentState
|
||||
store.commit('contentState/deleteItem', `${fileId}/contentState`);
|
||||
// Delete sync locations
|
||||
(store.getters['syncLocation/groupedByFileId'][fileId] || [])
|
||||
.forEach(item => store.commit('syncLocation/deleteItem', item.id));
|
||||
// Delete publish locations
|
||||
(store.getters['publishLocation/groupedByFileId'][fileId] || [])
|
||||
.forEach(item => store.commit('publishLocation/deleteItem', item.id));
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensure two files/folders don't have the same path if the workspace doesn't support it.
|
||||
*/
|
||||
ensureUniquePaths() {
|
||||
if (store.getters['workspace/hasUniquePaths']) {
|
||||
if (Object.keys(store.getters.itemPaths).some(id => this.makePathUnique(id))) {
|
||||
this.ensureUniquePaths();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return false if the file/folder path is unique.
|
||||
* Add a prefix to its name and return true otherwise.
|
||||
*/
|
||||
makePathUnique(id) {
|
||||
const item = store.getters.allItemMap[id];
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
let path = store.getters.itemPaths[id];
|
||||
const pathItems = store.getters.pathItems;
|
||||
if (pathItems[path].length === 1) {
|
||||
return false;
|
||||
}
|
||||
const isFolder = item.type === 'folder';
|
||||
if (isFolder) {
|
||||
// Remove trailing slash
|
||||
path = path.slice(0, -1);
|
||||
}
|
||||
for (let suffix = 1; ; suffix += 1) {
|
||||
let pathWithPrefix = `${path}.${suffix}`;
|
||||
if (isFolder) {
|
||||
pathWithPrefix += '/';
|
||||
}
|
||||
if (!pathItems[pathWithPrefix]) {
|
||||
store.commit(`${item.type}/patchItem`, {
|
||||
id: item.id,
|
||||
name: `${item.name}.${suffix}`,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
@ -2,6 +2,7 @@ import FileSaver from 'file-saver';
|
||||
import utils from './utils';
|
||||
import store from '../store';
|
||||
import welcomeFile from '../data/welcomeFile.md';
|
||||
import fileSvc from './fileSvc';
|
||||
|
||||
const dbVersion = 1;
|
||||
const dbStoreName = 'objects';
|
||||
@ -186,6 +187,7 @@ const localDbSvc = {
|
||||
dbStore.delete(item.id);
|
||||
}
|
||||
});
|
||||
fileSvc.ensureUniquePaths();
|
||||
this.lastTx = lastTx;
|
||||
cb(storeItemMap);
|
||||
}
|
||||
@ -249,20 +251,20 @@ const localDbSvc = {
|
||||
* Read and apply one DB change.
|
||||
*/
|
||||
readDbItem(dbItem, storeItemMap) {
|
||||
const existingStoreItem = storeItemMap[dbItem.id];
|
||||
const storeItem = storeItemMap[dbItem.id];
|
||||
if (!dbItem.hash) {
|
||||
// DB item is a delete marker
|
||||
delete this.hashMap[dbItem.type][dbItem.id];
|
||||
if (existingStoreItem) {
|
||||
if (storeItem) {
|
||||
// Remove item from the store
|
||||
store.commit(`${existingStoreItem.type}/deleteItem`, existingStoreItem.id);
|
||||
delete storeItemMap[existingStoreItem.id];
|
||||
store.commit(`${storeItem.type}/deleteItem`, storeItem.id);
|
||||
delete storeItemMap[storeItem.id];
|
||||
}
|
||||
} else if (this.hashMap[dbItem.type][dbItem.id] !== dbItem.hash) {
|
||||
// DB item is different from the corresponding store item
|
||||
this.hashMap[dbItem.type][dbItem.id] = dbItem.hash;
|
||||
// Update content only if it exists in the store
|
||||
if (existingStoreItem || !contentTypes[dbItem.type] || exportWorkspace) {
|
||||
if (storeItem || !contentTypes[dbItem.type] || exportWorkspace) {
|
||||
// Put item in the store
|
||||
dbItem.tx = undefined;
|
||||
store.commit(`${dbItem.type}/setItem`, dbItem);
|
||||
@ -403,13 +405,14 @@ const localDbSvc = {
|
||||
// Clean files
|
||||
store.getters['file/items']
|
||||
.filter(file => file.parentId === 'trash') // If file is in the trash
|
||||
.forEach(file => store.dispatch('deleteFile', file.id));
|
||||
.forEach(file => fileSvc.deleteFile(file.id));
|
||||
}
|
||||
|
||||
// Enable sponsorship
|
||||
if (utils.queryParams.paymentSuccess) {
|
||||
location.hash = ''; // PaymentSuccess param is always on its own
|
||||
store.dispatch('modal/paymentSuccess');
|
||||
store.dispatch('modal/paymentSuccess')
|
||||
.catch(() => { /* Cancel */ });
|
||||
const sponsorToken = store.getters['workspace/sponsorToken'];
|
||||
// Force check sponsorship after a few seconds
|
||||
const currentDate = Date.now();
|
||||
@ -438,10 +441,10 @@ const localDbSvc = {
|
||||
store.commit('file/setCurrentId', recentFile.id);
|
||||
} else {
|
||||
// If still no ID, create a new file
|
||||
store.dispatch('createFile', {
|
||||
fileSvc.createFile({
|
||||
name: 'Welcome file',
|
||||
text: welcomeFile,
|
||||
})
|
||||
}, true)
|
||||
// Set it as the current file
|
||||
.then(newFile => store.commit('file/setCurrentId', newFile.id));
|
||||
}
|
||||
|
@ -83,7 +83,9 @@ export default class Provider {
|
||||
parentId: null,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import store from '../../store';
|
||||
import dropboxHelper from './helpers/dropboxHelper';
|
||||
import Provider from './common/Provider';
|
||||
import utils from '../utils';
|
||||
import fileSvc from '../fileSvc';
|
||||
|
||||
const makePathAbsolute = (token, path) => {
|
||||
if (!token.fullAccess) {
|
||||
@ -88,12 +89,6 @@ export default new Provider({
|
||||
};
|
||||
return this.downloadContent(token, syncLocation)
|
||||
.then((content) => {
|
||||
const id = utils.uid();
|
||||
delete content.history;
|
||||
store.commit('content/setItem', {
|
||||
...content,
|
||||
id: `${id}/content`,
|
||||
});
|
||||
let name = path;
|
||||
const slashPos = name.lastIndexOf('/');
|
||||
if (slashPos > -1 && slashPos < name.length - 1) {
|
||||
@ -103,25 +98,30 @@ export default new Provider({
|
||||
if (dotPos > 0 && slashPos < name.length) {
|
||||
name = name.slice(0, dotPos);
|
||||
}
|
||||
store.commit('file/setItem', {
|
||||
id,
|
||||
name: utils.sanitizeName(name),
|
||||
return fileSvc.createFile({
|
||||
name,
|
||||
parentId: store.getters['file/current'].parentId,
|
||||
});
|
||||
text: content.text,
|
||||
properties: content.properties,
|
||||
discussions: content.discussions,
|
||||
comments: content.comments,
|
||||
}, true);
|
||||
})
|
||||
.then((item) => {
|
||||
store.commit('file/setCurrentId', item.id);
|
||||
store.commit('syncLocation/setItem', {
|
||||
...syncLocation,
|
||||
id: utils.uid(),
|
||||
fileId: id,
|
||||
fileId: item.id,
|
||||
});
|
||||
store.commit('file/setCurrentId', id);
|
||||
store.dispatch('notification/info', `${store.getters['file/current'].name} was imported from Dropbox.`);
|
||||
}, () => {
|
||||
})
|
||||
.catch(() => {
|
||||
store.dispatch('notification/error', `Could not open file ${path}.`);
|
||||
})
|
||||
.then(() => openOneFile());
|
||||
};
|
||||
return Promise.resolve()
|
||||
.then(() => openOneFile());
|
||||
return Promise.resolve(openOneFile());
|
||||
},
|
||||
makeLocation(token, path) {
|
||||
return {
|
||||
|
@ -2,6 +2,7 @@ import store from '../../store';
|
||||
import githubHelper from './helpers/githubHelper';
|
||||
import Provider from './common/Provider';
|
||||
import utils from '../utils';
|
||||
import fileSvc from '../fileSvc';
|
||||
|
||||
const savedSha = {};
|
||||
|
||||
@ -75,12 +76,6 @@ export default new Provider({
|
||||
// Download content from GitHub and create the file
|
||||
return this.downloadContent(token, syncLocation)
|
||||
.then((content) => {
|
||||
const id = utils.uid();
|
||||
delete content.history;
|
||||
store.commit('content/setItem', {
|
||||
...content,
|
||||
id: `${id}/content`,
|
||||
});
|
||||
let name = syncLocation.path;
|
||||
const slashPos = name.lastIndexOf('/');
|
||||
if (slashPos > -1 && slashPos < name.length - 1) {
|
||||
@ -90,19 +85,25 @@ export default new Provider({
|
||||
if (dotPos > 0 && slashPos < name.length) {
|
||||
name = name.slice(0, dotPos);
|
||||
}
|
||||
store.commit('file/setItem', {
|
||||
id,
|
||||
name: utils.sanitizeName(name),
|
||||
return fileSvc.createFile({
|
||||
name,
|
||||
parentId: store.getters['file/current'].parentId,
|
||||
});
|
||||
text: content.text,
|
||||
properties: content.properties,
|
||||
discussions: content.discussions,
|
||||
comments: content.comments,
|
||||
}, true);
|
||||
})
|
||||
.then((item) => {
|
||||
store.commit('file/setCurrentId', item.id);
|
||||
store.commit('syncLocation/setItem', {
|
||||
...syncLocation,
|
||||
id: utils.uid(),
|
||||
fileId: id,
|
||||
fileId: item.id,
|
||||
});
|
||||
store.commit('file/setCurrentId', id);
|
||||
store.dispatch('notification/info', `${store.getters['file/current'].name} was imported from GitHub.`);
|
||||
}, () => {
|
||||
})
|
||||
.catch(() => {
|
||||
store.dispatch('notification/error', `Could not open file ${syncLocation.path}.`);
|
||||
});
|
||||
});
|
||||
|
@ -479,12 +479,13 @@ export default new Provider({
|
||||
} else if (entry.committer && entry.committer.login) {
|
||||
user = entry.committer;
|
||||
}
|
||||
userSvc.addInfo({ id: user.login, name: user.login, imageUrl: user.avatar_url });
|
||||
const sub = `gh:${user.id}`;
|
||||
userSvc.addInfo({ id: sub, name: user.login, imageUrl: user.avatar_url });
|
||||
const date = (entry.commit.author && entry.commit.author.date)
|
||||
|| (entry.commit.committer && entry.commit.committer.date);
|
||||
return {
|
||||
id: entry.sha,
|
||||
sub: user.login,
|
||||
sub,
|
||||
created: date ? new Date(date).getTime() : 1,
|
||||
};
|
||||
})
|
||||
|
@ -136,7 +136,7 @@ export default new Provider({
|
||||
return googleHelper.getAppDataFileRevisions(token, syncData.id)
|
||||
.then(revisions => revisions.map(revision => ({
|
||||
id: revision.id,
|
||||
sub: revision.lastModifyingUser && revision.lastModifyingUser.permissionId,
|
||||
sub: revision.lastModifyingUser && `go:${revision.lastModifyingUser.permissionId}`,
|
||||
created: new Date(revision.modifiedTime).getTime(),
|
||||
}))
|
||||
.sort((revision1, revision2) => revision2.created - revision1.created));
|
||||
|
@ -2,6 +2,7 @@ import store from '../../store';
|
||||
import googleHelper from './helpers/googleHelper';
|
||||
import Provider from './common/Provider';
|
||||
import utils from '../utils';
|
||||
import fileSvc from '../fileSvc';
|
||||
|
||||
export default new Provider({
|
||||
id: 'googleDrive',
|
||||
@ -93,7 +94,7 @@ export default new Provider({
|
||||
const token = store.getters['data/googleTokens'][state.userId];
|
||||
switch (token && state.action) {
|
||||
case 'create':
|
||||
return store.dispatch('createFile')
|
||||
return fileSvc.createFile({}, true)
|
||||
.then((file) => {
|
||||
store.commit('file/setCurrentId', file.id);
|
||||
// Return a new syncLocation
|
||||
@ -169,32 +170,29 @@ export default new Provider({
|
||||
sub: token.sub,
|
||||
};
|
||||
return this.downloadContent(token, syncLocation)
|
||||
.then((content) => {
|
||||
const id = utils.uid();
|
||||
delete content.history;
|
||||
store.commit('content/setItem', {
|
||||
...content,
|
||||
id: `${id}/content`,
|
||||
});
|
||||
store.commit('file/setItem', {
|
||||
id,
|
||||
name: utils.sanitizeName(driveFile.name),
|
||||
parentId: store.getters['file/current'].parentId,
|
||||
});
|
||||
.then(content => fileSvc.createFile({
|
||||
name,
|
||||
parentId: store.getters['file/current'].parentId,
|
||||
text: content.text,
|
||||
properties: content.properties,
|
||||
discussions: content.discussions,
|
||||
comments: content.comments,
|
||||
}, true))
|
||||
.then((item) => {
|
||||
store.commit('file/setCurrentId', item.id);
|
||||
store.commit('syncLocation/setItem', {
|
||||
...syncLocation,
|
||||
id: utils.uid(),
|
||||
fileId: id,
|
||||
fileId: item.id,
|
||||
});
|
||||
store.commit('file/setCurrentId', id);
|
||||
store.dispatch('notification/info', `${store.getters['file/current'].name} was imported from Google Drive.`);
|
||||
}, () => {
|
||||
})
|
||||
.catch(() => {
|
||||
store.dispatch('notification/error', `Could not open file ${driveFile.id}.`);
|
||||
})
|
||||
.then(() => openOneFile());
|
||||
};
|
||||
return Promise.resolve()
|
||||
.then(() => openOneFile());
|
||||
return Promise.resolve(openOneFile());
|
||||
},
|
||||
makeLocation(token, fileId, folderId) {
|
||||
const location = {
|
||||
|
@ -2,6 +2,7 @@ import store from '../../store';
|
||||
import googleHelper from './helpers/googleHelper';
|
||||
import Provider from './common/Provider';
|
||||
import utils from '../utils';
|
||||
import fileSvc from '../fileSvc';
|
||||
|
||||
const getSyncData = (fileId) => {
|
||||
const syncData = store.getters['data/syncDataByItemId'][`${fileId}/content`];
|
||||
@ -195,9 +196,9 @@ export default new Provider({
|
||||
[syncData.id]: syncData,
|
||||
});
|
||||
}
|
||||
return store.dispatch('createFile', {
|
||||
return fileSvc.createFile({
|
||||
parentId: syncData && syncData.itemId,
|
||||
})
|
||||
}, true)
|
||||
.then((file) => {
|
||||
store.commit('file/setCurrentId', file.id);
|
||||
// File will be created on next workspace sync
|
||||
|
@ -91,9 +91,9 @@ export default {
|
||||
method: 'POST',
|
||||
body: { item, time: Date.now() },
|
||||
};
|
||||
const loginToken = store.getters['workspace/loginToken'];
|
||||
if (loginToken) {
|
||||
options.body.sub = loginToken.sub;
|
||||
const userId = store.getters['workspace/userId'];
|
||||
if (userId) {
|
||||
options.body.sub = userId;
|
||||
}
|
||||
if (documentId) {
|
||||
options.method = 'PUT';
|
||||
|
@ -73,6 +73,22 @@ export default {
|
||||
addAccount(repoFullAccess = false) {
|
||||
return this.startOauth2(getScopes({ repoFullAccess }));
|
||||
},
|
||||
getUser(userId) {
|
||||
return networkSvc.request({
|
||||
url: `https://api.github.com/user/${userId}`,
|
||||
params: {
|
||||
t: Date.now(), // Prevent from caching
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
store.commit('userInfo/addItem', {
|
||||
id: `gh:${res.body.id}`,
|
||||
name: res.body.login,
|
||||
imageUrl: res.body.avatar_url || '',
|
||||
});
|
||||
return res.body;
|
||||
});
|
||||
},
|
||||
getTree(token, owner, repo, sha) {
|
||||
return repoRequest(token, owner, repo, {
|
||||
url: `git/trees/${encodeURIComponent(sha)}?recursive=1`,
|
||||
@ -86,9 +102,9 @@ export default {
|
||||
},
|
||||
getHeadTree(token, owner, repo, branch) {
|
||||
return repoRequest(token, owner, repo, {
|
||||
url: `branches/${encodeURIComponent(branch)}`,
|
||||
url: `commits/${encodeURIComponent(branch)}`,
|
||||
})
|
||||
.then(res => this.getTree(token, owner, repo, res.body.commit.commit.tree.sha));
|
||||
.then(res => this.getTree(token, owner, repo, res.body.commit.tree.sha));
|
||||
},
|
||||
getCommits(token, owner, repo, sha, path) {
|
||||
return repoRequest(token, owner, repo, {
|
||||
|
@ -203,7 +203,7 @@ export default {
|
||||
}, true)
|
||||
.then((res) => {
|
||||
store.commit('userInfo/addItem', {
|
||||
id: res.body.id,
|
||||
id: `go:${res.body.id}`,
|
||||
name: res.body.displayName,
|
||||
imageUrl: (res.body.image.url || '').replace(/\bsz?=\d+$/, 'sz=40'),
|
||||
});
|
||||
|
@ -9,6 +9,7 @@ import './providers/couchdbWorkspaceProvider';
|
||||
import './providers/githubWorkspaceProvider';
|
||||
import './providers/googleDriveWorkspaceProvider';
|
||||
import tempFileSvc from './tempFileSvc';
|
||||
import fileSvc from './fileSvc';
|
||||
|
||||
const minAutoSyncEvery = 60 * 1000; // 60 sec
|
||||
const inactivityThreshold = 3 * 1000; // 3 sec
|
||||
@ -746,7 +747,7 @@ function requestSync() {
|
||||
Object.entries(fileHashesToClean).forEach(([fileId, fileHash]) => {
|
||||
const file = store.state.file.itemMap[fileId];
|
||||
if (file && file.hash === fileHash) {
|
||||
store.dispatch('deleteFile', fileId);
|
||||
fileSvc.deleteFile(fileId);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
@ -2,6 +2,7 @@ import cledit from './cledit';
|
||||
import store from '../store';
|
||||
import utils from './utils';
|
||||
import editorSvc from './editorSvc';
|
||||
import fileSvc from './fileSvc';
|
||||
|
||||
const origin = utils.queryParams.origin;
|
||||
const fileName = utils.queryParams.fileName;
|
||||
@ -29,12 +30,12 @@ export default {
|
||||
|
||||
store.commit('setLight', true);
|
||||
|
||||
return store.dispatch('createFile', {
|
||||
return fileSvc.createFile({
|
||||
name: fileName || utils.getHostname(origin),
|
||||
text: contentText || '\n',
|
||||
properties: contentProperties,
|
||||
parentId: 'temp',
|
||||
})
|
||||
}, true)
|
||||
.then((file) => {
|
||||
const fileItemMap = store.state.file.itemMap;
|
||||
|
||||
@ -57,7 +58,7 @@ export default {
|
||||
.splice(10)
|
||||
.forEach(([id]) => {
|
||||
delete lastCreated[id];
|
||||
store.dispatch('deleteFile', id);
|
||||
fileSvc.deleteFile(id);
|
||||
});
|
||||
|
||||
// Store file creations and open the file
|
||||
|
@ -1,8 +1,16 @@
|
||||
import googleHelper from './providers/helpers/googleHelper';
|
||||
import githubHelper from './providers/helpers/githubHelper';
|
||||
import utils from './utils';
|
||||
import store from '../store';
|
||||
|
||||
const promised = {};
|
||||
|
||||
const parseUserId = (userId) => {
|
||||
const prefix = userId[2] === ':' && userId.slice(0, 2);
|
||||
const type = prefix && utils.userIdPrefixes[prefix];
|
||||
return type ? [type, userId.slice(3)] : ['google', userId];
|
||||
};
|
||||
|
||||
export default {
|
||||
addInfo({ id, name, imageUrl }) {
|
||||
promised[id] = true;
|
||||
@ -10,8 +18,10 @@ export default {
|
||||
},
|
||||
getInfo(userId) {
|
||||
if (!promised[userId]) {
|
||||
const [type, sub] = parseUserId(userId);
|
||||
|
||||
// Try to find a token with this sub
|
||||
const token = store.getters['data/googleTokens'][userId];
|
||||
const token = store.getters[`data/${type}Tokens`][sub];
|
||||
if (token) {
|
||||
store.commit('userInfo/addItem', {
|
||||
id: userId,
|
||||
@ -19,16 +29,31 @@ export default {
|
||||
});
|
||||
}
|
||||
|
||||
// Get user info from Google
|
||||
// Get user info from provider
|
||||
if (!store.state.offline) {
|
||||
promised[userId] = true;
|
||||
googleHelper.getUser(userId)
|
||||
.catch((err) => {
|
||||
if (err.status !== 404) {
|
||||
promised[userId] = false;
|
||||
}
|
||||
});
|
||||
switch (type) {
|
||||
case 'github': {
|
||||
return githubHelper.getUser(sub)
|
||||
.catch((err) => {
|
||||
if (err.status !== 404) {
|
||||
promised[userId] = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
case 'google':
|
||||
default: {
|
||||
return googleHelper.getUser(sub)
|
||||
.catch((err) => {
|
||||
if (err.status !== 404) {
|
||||
promised[userId] = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
@ -23,6 +23,7 @@ const parseQueryParams = (params) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// For utils.computeProperties()
|
||||
const deepOverride = (obj, opt) => {
|
||||
if (obj === undefined) {
|
||||
@ -96,14 +97,23 @@ export default {
|
||||
'layoutSettings',
|
||||
'tokens',
|
||||
],
|
||||
userIdPrefixes: {
|
||||
go: 'google',
|
||||
gh: 'github',
|
||||
},
|
||||
textMaxLength: 250000,
|
||||
sanitizeText(text) {
|
||||
const result = `${text || ''}`.slice(0, this.textMaxLength);
|
||||
// last char must be a `\n`.
|
||||
return `${result}\n`.replace(/\n\n$/, '\n');
|
||||
},
|
||||
defaultName: 'Untitled',
|
||||
sanitizeName(name) {
|
||||
return `${name || ''}`.slice(0, 250) || 'Untitled';
|
||||
return `${name || ''}`
|
||||
// Replace `/`, control characters and other kind of spaces with a space
|
||||
.replace(/[/\x00-\x1F\x7f-\xa0\s]+/g, ' ').trim()
|
||||
// Keep only 250 characters
|
||||
.slice(0, 250) || this.defaultName;
|
||||
},
|
||||
deepCopy,
|
||||
serializeObject(obj) {
|
||||
@ -124,9 +134,8 @@ export default {
|
||||
// If every field fits the criteria
|
||||
if (Object.entries(criteria).every(([key, value]) => value === item[key])) {
|
||||
result = item;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return result;
|
||||
});
|
||||
return result;
|
||||
},
|
||||
@ -199,6 +208,18 @@ export default {
|
||||
setInterval(func, interval) {
|
||||
return setInterval(() => func(), this.randomize(interval));
|
||||
},
|
||||
async awaitSequence(values, asyncFunc) {
|
||||
const results = [];
|
||||
const valuesLeft = values.slice().reverse();
|
||||
const runWithNextValue = async () => {
|
||||
if (!valuesLeft.length) {
|
||||
return results;
|
||||
}
|
||||
results.push(await asyncFunc(valuesLeft.pop()));
|
||||
return runWithNextValue();
|
||||
};
|
||||
return runWithNextValue();
|
||||
},
|
||||
parseQueryParams,
|
||||
addQueryParams(url = '', params = {}, hash = false) {
|
||||
const keys = Object.keys(params).filter(key => params[key] != null);
|
||||
@ -239,9 +260,6 @@ export default {
|
||||
}
|
||||
return result;
|
||||
},
|
||||
concatPaths(...paths) {
|
||||
return paths.join('/').replace(/\/+/g, '/');
|
||||
},
|
||||
getHostname(url) {
|
||||
urlParser.href = url;
|
||||
return urlParser.hostname;
|
||||
|
@ -130,7 +130,7 @@ export default {
|
||||
.then(() => syncSvc.requestSync())
|
||||
.then(() => dispatch('createNewDiscussion', selection)),
|
||||
}, { root: true })
|
||||
.catch(() => { }); // Cancel
|
||||
.catch(() => { /* Cancel */ });
|
||||
} else if (selection) {
|
||||
let text = rootGetters['content/current'].text.slice(selection.start, selection.end).trim();
|
||||
const maxLength = 80;
|
||||
|
@ -190,85 +190,5 @@ export default {
|
||||
commit('setDragTargetId', id);
|
||||
dispatch('openDragTarget');
|
||||
},
|
||||
newItem({ getters, commit, dispatch }, isFolder) {
|
||||
let parentId = getters.selectedNodeFolder.item.id;
|
||||
if (parentId === 'trash' // Not allowed to create new items in the trash
|
||||
|| (isFolder && parentId === 'temp') // Not allowed to create new folders in the temp folder
|
||||
) {
|
||||
parentId = null;
|
||||
}
|
||||
dispatch('openNode', parentId);
|
||||
commit('setNewItem', {
|
||||
type: isFolder ? 'folder' : 'file',
|
||||
parentId,
|
||||
});
|
||||
},
|
||||
deleteItem({ rootState, getters, rootGetters, commit, dispatch }) {
|
||||
const selectedNode = getters.selectedNode;
|
||||
if (selectedNode.isNil) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (selectedNode.isTrash || selectedNode.item.parentId === 'trash') {
|
||||
return dispatch('modal/trashDeletion', null, { root: true });
|
||||
}
|
||||
|
||||
// See if we have a dialog to show
|
||||
let modalAction;
|
||||
let moveToTrash = true;
|
||||
if (selectedNode.isTemp) {
|
||||
modalAction = 'modal/tempFolderDeletion';
|
||||
moveToTrash = false;
|
||||
} else if (selectedNode.item.parentId === 'temp') {
|
||||
modalAction = 'modal/tempFileDeletion';
|
||||
moveToTrash = false;
|
||||
} else if (selectedNode.isFolder) {
|
||||
modalAction = 'modal/folderDeletion';
|
||||
}
|
||||
|
||||
return (modalAction
|
||||
? dispatch(modalAction, selectedNode.item, { root: true })
|
||||
: Promise.resolve())
|
||||
.then(() => {
|
||||
const deleteFile = (id) => {
|
||||
if (moveToTrash) {
|
||||
commit('file/patchItem', {
|
||||
id,
|
||||
parentId: 'trash',
|
||||
}, { root: true });
|
||||
} else {
|
||||
dispatch('deleteFile', id, { root: true });
|
||||
}
|
||||
};
|
||||
|
||||
if (selectedNode === getters.selectedNode) {
|
||||
const currentFileId = rootGetters['file/current'].id;
|
||||
let doClose = selectedNode.item.id === currentFileId;
|
||||
if (selectedNode.isFolder) {
|
||||
const recursiveDelete = (folderNode) => {
|
||||
folderNode.folders.forEach(recursiveDelete);
|
||||
folderNode.files.forEach((fileNode) => {
|
||||
doClose = doClose || fileNode.item.id === currentFileId;
|
||||
deleteFile(fileNode.item.id);
|
||||
});
|
||||
commit('folder/deleteItem', folderNode.item.id, { root: true });
|
||||
};
|
||||
recursiveDelete(selectedNode);
|
||||
} else {
|
||||
deleteFile(selectedNode.item.id);
|
||||
}
|
||||
if (doClose) {
|
||||
// Close the current file by opening the last opened, not deleted one
|
||||
rootGetters['data/lastOpenedIds'].some((id) => {
|
||||
const file = rootState.file.itemMap[id];
|
||||
if (file.parentId === 'trash') {
|
||||
return false;
|
||||
}
|
||||
commit('file/setCurrentId', id, { root: true });
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}, () => {}); // Cancel
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -62,6 +62,7 @@ const store = new Vuex.Store({
|
||||
},
|
||||
itemPaths: (state) => {
|
||||
const result = {};
|
||||
const folderMap = state.folder.itemMap;
|
||||
const getPath = (item) => {
|
||||
let itemPath = result[item.id];
|
||||
if (!itemPath) {
|
||||
@ -69,29 +70,31 @@ const store = new Vuex.Store({
|
||||
itemPath = `.stackedit-trash/${item.name}`;
|
||||
} else {
|
||||
let name = item.name;
|
||||
if (item.type === 'folder') {
|
||||
if (folderMap[item.id]) {
|
||||
name += '/';
|
||||
}
|
||||
const parent = state.folder.itemMap[item.parentId];
|
||||
if (!parent) {
|
||||
itemPath = name;
|
||||
const parentFolder = folderMap[item.parentId];
|
||||
if (parentFolder) {
|
||||
itemPath = getPath(parentFolder) + name;
|
||||
} else {
|
||||
itemPath = getPath(parent) + name;
|
||||
itemPath = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
result[item.id] = itemPath;
|
||||
return itemPath;
|
||||
};
|
||||
|
||||
[...state.folder.items, ...state.file.items].forEach(item => getPath(item));
|
||||
return result;
|
||||
},
|
||||
pathItems: (state, getters) => {
|
||||
const result = {};
|
||||
const itemPaths = getters.itemPaths;
|
||||
const allItemMap = getters.allItemMap;
|
||||
Object.entries(itemPaths).forEach(([id, path]) => {
|
||||
result[path] = allItemMap[id];
|
||||
Object.entries(getters.itemPaths).forEach(([id, path]) => {
|
||||
const items = result[path] || [];
|
||||
items.push(allItemMap[id]);
|
||||
result[path] = items;
|
||||
});
|
||||
return result;
|
||||
},
|
||||
@ -131,33 +134,6 @@ const store = new Vuex.Store({
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
createFile({ state, getters, commit }, desc = {}) {
|
||||
const id = utils.uid();
|
||||
commit('content/setItem', {
|
||||
id: `${id}/content`,
|
||||
text: utils.sanitizeText(desc.text || getters['data/computedSettings'].newFileContent),
|
||||
properties: utils.sanitizeText(
|
||||
desc.properties || getters['data/computedSettings'].newFileProperties),
|
||||
discussions: desc.discussions || {},
|
||||
comments: desc.comments || {},
|
||||
});
|
||||
commit('file/setItem', {
|
||||
id,
|
||||
name: utils.sanitizeName(desc.name),
|
||||
parentId: desc.parentId || null,
|
||||
});
|
||||
return Promise.resolve(state.file.itemMap[id]);
|
||||
},
|
||||
deleteFile({ getters, commit }, fileId) {
|
||||
(getters['syncLocation/groupedByFileId'][fileId] || [])
|
||||
.forEach(item => commit('syncLocation/deleteItem', item.id));
|
||||
(getters['publishLocation/groupedByFileId'][fileId] || [])
|
||||
.forEach(item => commit('publishLocation/deleteItem', item.id));
|
||||
commit('file/deleteItem', fileId);
|
||||
commit('content/deleteItem', `${fileId}/content`);
|
||||
commit('syncedContent/deleteItem', `${fileId}/syncedContent`);
|
||||
commit('contentState/deleteItem', `${fileId}/contentState`);
|
||||
},
|
||||
},
|
||||
strict: debug,
|
||||
plugins: debug ? [createLogger()] : [],
|
||||
|
@ -74,13 +74,27 @@ export default {
|
||||
}),
|
||||
trashDeletion: ({ dispatch }) => dispatch('open', {
|
||||
content: '<p>Files in the trash are automatically deleted after 7 days of inactivity.</p>',
|
||||
resolveText: 'Ok',
|
||||
rejectText: 'Ok',
|
||||
}),
|
||||
fileRestoration: ({ dispatch }) => dispatch('open', {
|
||||
content: '<p>You are about to revert some changes. Are you sure?</p>',
|
||||
resolveText: 'Yes, revert',
|
||||
rejectText: 'No',
|
||||
}),
|
||||
unauthorizedName: ({ dispatch }, name) => dispatch('open', {
|
||||
content: `<p><b>${name}</b> is not an authorized name.</p>`,
|
||||
rejectText: 'Ok',
|
||||
}),
|
||||
stripName: ({ dispatch }, name) => dispatch('open', {
|
||||
content: `<p><b>${name}</b> contains illegal characters. Do you want to strip them?</p>`,
|
||||
resolveText: 'Yes, strip',
|
||||
rejectText: 'No',
|
||||
}),
|
||||
pathConflict: ({ dispatch }, name) => dispatch('open', {
|
||||
content: `<p><b>${name}</b> already exists. Do you want to add a suffix?</p>`,
|
||||
resolveText: 'Yes, add suffix',
|
||||
rejectText: 'No',
|
||||
}),
|
||||
removeWorkspace: ({ dispatch }) => dispatch('open', {
|
||||
content: '<p>You are about to remove a workspace locally. Are you sure?</p>',
|
||||
resolveText: 'Yes, remove',
|
||||
@ -127,11 +141,11 @@ export default {
|
||||
}),
|
||||
sponsorOnly: ({ dispatch }) => dispatch('open', {
|
||||
content: '<p>This feature is restricted to sponsors as it relies on server resources.</p>',
|
||||
resolveText: 'Ok, I understand',
|
||||
rejectText: 'Ok, I understand',
|
||||
}),
|
||||
paymentSuccess: ({ dispatch }) => dispatch('open', {
|
||||
content: '<p>Thank you for your payment! Your sponsorship will be active in a minute.</p>',
|
||||
resolveText: 'Ok',
|
||||
rejectText: 'Ok',
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
@ -11,7 +11,10 @@ export default (empty, simpleHash = false) => {
|
||||
itemMap: {},
|
||||
},
|
||||
getters: {
|
||||
items: state => Object.entries(state.itemMap).map(([, item]) => item),
|
||||
items: (state) => {
|
||||
console.log(state.itemMap);
|
||||
return Object.values(state.itemMap);
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setItem(state, value) {
|
||||
|
@ -1,3 +1,5 @@
|
||||
import utils from '../services/utils';
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
@ -21,6 +23,10 @@ export default {
|
||||
const workspaces = rootGetters['data/sanitizedWorkspaces'];
|
||||
return workspaces[state.currentWorkspaceId] || getters.mainWorkspace;
|
||||
},
|
||||
hasUniquePaths: (state, getters) => {
|
||||
const workspace = getters.currentWorkspace;
|
||||
return workspace.providerId === 'githubWorkspace';
|
||||
},
|
||||
lastSyncActivityKey: (state, getters) => `${getters.currentWorkspace.id}/lastSyncActivity`,
|
||||
lastFocusKey: (state, getters) => `${getters.currentWorkspace.id}/lastWindowFocus`,
|
||||
mainWorkspaceToken: (state, getters, rootState, rootGetters) => {
|
||||
@ -55,10 +61,28 @@ export default {
|
||||
const googleTokens = rootGetters['data/googleTokens'];
|
||||
return googleTokens[workspace.sub];
|
||||
}
|
||||
case 'githubWorkspace': {
|
||||
const githubTokens = rootGetters['data/githubTokens'];
|
||||
return githubTokens[workspace.sub];
|
||||
}
|
||||
default:
|
||||
return getters.mainWorkspaceToken;
|
||||
}
|
||||
},
|
||||
userId: (state, getters, rootState, rootGetters) => {
|
||||
const loginToken = getters.loginToken;
|
||||
if (!loginToken) {
|
||||
return null;
|
||||
}
|
||||
let prefix;
|
||||
Object.entries(utils.userIdPrefixes).some(([key, value]) => {
|
||||
if (rootGetters[`data/${value}Tokens`][loginToken.sub]) {
|
||||
prefix = key;
|
||||
}
|
||||
return prefix;
|
||||
});
|
||||
return prefix ? `${prefix}:${loginToken.sub}` : loginToken.sub;
|
||||
},
|
||||
sponsorToken: (state, getters) => getters.mainWorkspaceToken,
|
||||
},
|
||||
actions: {
|
||||
|
Loading…
Reference in New Issue
Block a user