Github workspace (part 2)

This commit is contained in:
Benoit Schweblin 2018-05-04 19:07:28 +01:00
parent 53ccee0d84
commit b896a2e086
46 changed files with 588 additions and 319 deletions

12
package-lock.json generated
View File

@ -14498,9 +14498,9 @@
} }
}, },
"vue": { "vue": {
"version": "2.5.13", "version": "2.5.16",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.5.13.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-2.5.16.tgz",
"integrity": "sha512-3D+lY7HTkKbtswDM4BBHgqyq+qo8IAEE8lz8va1dz3LLmttjgo0FxairO4r1iN2OBqk8o1FyL4hvzzTFEdQSEw==" "integrity": "sha512-/ffmsiVuPC8PsWcFkZngdpas19ABm5mh2wA7iDqcltyCTwlgZjHGeJYOXkBMo422iPwIcviOtrTCUpSfXmToLQ=="
}, },
"vue-hot-reload-api": { "vue-hot-reload-api": {
"version": "2.2.4", "version": "2.2.4",
@ -14588,9 +14588,9 @@
"dev": true "dev": true
}, },
"vuex": { "vuex": {
"version": "2.5.0", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-2.5.0.tgz", "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.0.1.tgz",
"integrity": "sha512-5oJPOJySBgSgSzoeO+gZB/BbN/XsapgIF6tz34UwJqnGZMQurzIO3B4KIBf862gfc9ya+oduY5sSkq+5/oOilQ==" "integrity": "sha512-wLoqz0B7DSZtgbWL1ShIBBCjv22GV5U+vcBFox658g6V0s4wZV9P4YjCNyoHSyIBpj1f29JBoNQIqD82cR4O3w=="
}, },
"w3c-hr-time": { "w3c-hr-time": {
"version": "1.0.1", "version": "1.0.1",

View File

@ -52,8 +52,8 @@
"serve-static": "^1.12.6", "serve-static": "^1.12.6",
"tmp": "^0.0.33", "tmp": "^0.0.33",
"turndown": "^4.0.1", "turndown": "^4.0.1",
"vue": "^2.3.3", "vue": "^2.5.16",
"vuex": "^2.3.1" "vuex": "^3.0.1"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^6.7.2", "autoprefixer": "^6.7.2",

View File

@ -28,6 +28,7 @@
<script> <script>
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import ExplorerNode from './ExplorerNode'; import ExplorerNode from './ExplorerNode';
import explorerSvc from '../services/explorerSvc';
export default { export default {
components: { components: {
@ -49,10 +50,8 @@ export default {
...mapActions('data', [ ...mapActions('data', [
'toggleExplorer', 'toggleExplorer',
]), ]),
...mapActions('explorer', [ newItem: isFolder => explorerSvc.newItem(isFolder),
'newItem', deleteItem: () => explorerSvc.deleteItem(),
'deleteItem',
]),
editItem() { editItem() {
const node = this.selectedNode; const node = this.selectedNode;
if (!node.isTrash && !node.isTemp) { if (!node.isTrash && !node.isTemp) {

View File

@ -19,7 +19,8 @@
<script> <script>
import { mapMutations, mapActions } from 'vuex'; import { mapMutations, mapActions } from 'vuex';
import utils from '../services/utils'; import fileSvc from '../services/fileSvc';
import explorerSvc from '../services/explorerSvc';
export default { export default {
name: 'explorer-node', // Required for recursivity name: 'explorer-node', // Required for recursivity
@ -77,8 +78,6 @@ export default {
]), ]),
...mapActions('explorer', [ ...mapActions('explorer', [
'setDragTarget', 'setDragTarget',
'newItem',
'deleteItem',
]), ]),
select(id = this.node.item.id, doOpen = true) { select(id = this.node.item.id, doOpen = true) {
const node = this.$store.getters['explorer/nodeMap'][id]; const node = this.$store.getters['explorer/nodeMap'][id];
@ -102,31 +101,26 @@ export default {
const newChildNode = this.$store.state.explorer.newChildNode; const newChildNode = this.$store.state.explorer.newChildNode;
if (!cancel && !newChildNode.isNil && newChildNode.item.name) { if (!cancel && !newChildNode.isNil && newChildNode.item.name) {
if (newChildNode.isFolder) { if (newChildNode.isFolder) {
const id = utils.uid(); fileSvc.storeItem(newChildNode.item)
this.$store.commit('folder/setItem', { .then(item => this.select(item.id), () => { /* cancel */ });
...newChildNode.item,
id,
name: utils.sanitizeName(newChildNode.item.name),
});
this.select(id);
} else { } else {
this.$store.dispatch('createFile', newChildNode.item) fileSvc.createFile(newChildNode.item)
.then(file => this.select(file.id)); .then(item => this.select(item.id), () => { /* cancel */ });
} }
} }
this.$store.commit('explorer/setNewItem', null); this.$store.commit('explorer/setNewItem', null);
}, },
submitEdit(cancel) { submitEdit(cancel) {
const editingNode = this.$store.getters['explorer/editingNode']; const item = this.$store.getters['explorer/editingNode'].item;
const id = editingNode.item.id;
const value = this.editingValue; 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); this.setEditingId(null);
if (!cancel && item.id && value) {
fileSvc.storeItem({
...item,
name: value,
})
.catch(() => { /* cancel */ });
}
}, },
setDragSourceId(evt) { setDragSourceId(evt) {
if (this.node.noDrag) { if (this.node.noDrag) {
@ -169,11 +163,11 @@ export default {
items: [{ items: [{
name: 'New file', name: 'New file',
disabled: !this.node.isFolder || this.node.isTrash, disabled: !this.node.isFolder || this.node.isTrash,
perform: () => this.newItem(false), perform: () => explorerSvc.newItem(false),
}, { }, {
name: 'New folder', name: 'New folder',
disabled: !this.node.isFolder || this.node.isTrash || this.node.isTemp, disabled: !this.node.isFolder || this.node.isTrash || this.node.isTemp,
perform: () => this.newItem(true), perform: () => explorerSvc.newItem(true),
}, { }, {
type: 'separator', type: 'separator',
}, { }, {
@ -182,7 +176,7 @@ export default {
perform: () => this.setEditingId(this.node.item.id), perform: () => this.setEditingId(this.node.item.id),
}, { }, {
name: 'Delete', name: 'Delete',
perform: () => this.deleteItem(), perform: () => explorerSvc.deleteItem(),
}], }],
}) })
.then(item => item.perform()); .then(item => item.perform());

View File

@ -19,7 +19,7 @@
<!-- Title --> <!-- Title -->
<div class="navigation-bar__title navigation-bar__title--fake text-input"></div> <div class="navigation-bar__title navigation-bar__title--fake text-input"></div>
<div class="navigation-bar__title navigation-bar__title--text text-input" :style="{width: titleWidth + 'px'}">{{title}}</div> <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 --> <!-- Sync/Publish -->
<div class="flex flex--row" :class="{'navigation-bar__hidden': styles.hideLocations}"> <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> <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 utils from '../services/utils';
import pagedownButtons from '../data/pagedownButtons'; import pagedownButtons from '../data/pagedownButtons';
import store from '../store'; import store from '../store';
import fileSvc from '../services/fileSvc';
// According to mousetrap // According to mousetrap
const mod = /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'Meta' : 'Ctrl'; const mod = /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'Meta' : 'Ctrl';
@ -151,6 +152,13 @@ export default {
} }
return result; return result;
}, },
editCancelTrigger() {
const current = this.$store.getters['file/current'];
return utils.serializeObject([
current.id,
current.name,
]);
},
}, },
methods: { methods: {
...mapMutations('content', [ ...mapMutations('content', [
@ -190,10 +198,13 @@ export default {
this.titleInputElt.setSelectionRange(0, this.titleInputElt.value.length); this.titleInputElt.setSelectionRange(0, this.titleInputElt.value.length);
} else { } else {
const title = this.title.trim(); const title = this.title.trim();
this.title = this.$store.getters['file/current'].name;
if (title) { if (title) {
this.$store.dispatch('file/patchCurrent', { name: utils.sanitizeName(title) }); fileSvc.storeItem({
} else { ...this.$store.getters['file/current'],
this.title = this.$store.getters['file/current'].name; name: title,
})
.catch(() => { /* Cancel */ });
} }
} }
}, },
@ -209,9 +220,10 @@ export default {
}, },
created() { created() {
this.$watch( this.$watch(
() => this.$store.getters['file/current'].name, () => this.editCancelTrigger,
(name) => { () => {
this.title = name; this.title = '';
this.editTitle(false);
}, { immediate: true }); }, { immediate: true });
}, },
mounted() { mounted() {

View File

@ -3,8 +3,6 @@
</template> </template>
<script> <script>
import userSvc from '../services/userSvc';
export default { export default {
props: ['providerId'], props: ['providerId'],
computed: { computed: {
@ -15,8 +13,5 @@ export default {
} }
}, },
}, },
created() {
userSvc.getInfo(this.userId);
},
}; };
</script> </script>

View File

@ -51,7 +51,7 @@ export default {
this.$store.dispatch('modal/commentDeletion') this.$store.dispatch('modal/commentDeletion')
.then( .then(
() => this.$store.dispatch('discussion/cleanCurrentFile', { filterComment: this.comment }), () => this.$store.dispatch('discussion/cleanCurrentFile', { filterComment: this.comment }),
() => {}); // Cancel () => { /* Cancel */ });
}, },
}, },
mounted() { mounted() {

View File

@ -99,7 +99,7 @@ export default {
() => this.$store.dispatch('discussion/cleanCurrentFile', { () => this.$store.dispatch('discussion/cleanCurrentFile', {
filterDiscussion: this.currentDiscussion, filterDiscussion: this.currentDiscussion,
}), }),
() => {}); // Cancel () => { /* Cancel */ });
}, },
}, },
}; };

View File

@ -3,7 +3,7 @@
<div class="comment__header flex flex--row flex--space-between flex--align-center"> <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 flex flex--row flex--align-center">
<div class="comment__user-image"> <div class="comment__user-image">
<user-image :user-id="loginToken.sub"></user-image> <user-image :user-id="userId"></user-image>
</div> </div>
<span class="user-name">{{loginToken.name}}</span> <span class="user-name">{{loginToken.name}}</span>
</div> </div>
@ -33,9 +33,12 @@ export default {
components: { components: {
UserImage, UserImage,
}, },
computed: mapGetters('workspace', [ computed: {
'loginToken', ...mapGetters('workspace', [
]), 'loginToken',
'userId',
]),
},
methods: { methods: {
...mapMutations('discussion', [ ...mapMutations('discussion', [
'setNewCommentFocus', 'setNewCommentFocus',
@ -53,7 +56,7 @@ export default {
const discussionId = this.$store.state.discussion.currentDiscussionId; const discussionId = this.$store.state.discussion.currentDiscussionId;
const comment = { const comment = {
discussionId, discussionId,
sub: this.loginToken.sub, sub: this.userId,
text, text,
created: Date.now(), created: Date.now(),
}; };

View File

@ -35,19 +35,19 @@ export default {
exportMarkdown() { exportMarkdown() {
const currentFile = this.$store.getters['file/current']; const currentFile = this.$store.getters['file/current'];
return exportSvc.exportToDisk(currentFile.id, 'md') return exportSvc.exportToDisk(currentFile.id, 'md')
.catch(() => {}); // Cancel .catch(() => { /* Cancel */ });
}, },
exportHtml() { exportHtml() {
return this.$store.dispatch('modal/open', 'htmlExport') return this.$store.dispatch('modal/open', 'htmlExport')
.catch(() => {}); // Cancel .catch(() => { /* Cancel */ });
}, },
exportPdf() { exportPdf() {
return this.$store.dispatch('modal/open', 'pdfExport') return this.$store.dispatch('modal/open', 'pdfExport')
.catch(() => {}); // Cancel .catch(() => { /* Cancel */ });
}, },
exportPandoc() { exportPandoc() {
return this.$store.dispatch('modal/open', 'pandocExport') return this.$store.dispatch('modal/open', 'pandocExport')
.catch(() => {}); // Cancel .catch(() => { /* Cancel */ });
}, },
}, },
}; };

View File

@ -100,7 +100,7 @@ export default {
return googleHelper.signin() return googleHelper.signin()
.then( .then(
() => syncSvc.requestSync(), () => syncSvc.requestSync(),
() => {}, // Cancel () => { /* Cancel */ },
); );
}, },
close() { close() {

View File

@ -29,6 +29,7 @@ import htmlSanitizer from '../../libs/htmlSanitizer';
import MenuEntry from './common/MenuEntry'; import MenuEntry from './common/MenuEntry';
import Provider from '../../services/providers/common/Provider'; import Provider from '../../services/providers/common/Provider';
import store from '../../store'; import store from '../../store';
import fileSvc from '../../services/fileSvc';
const turndownService = new TurndownService(store.getters['data/computedSettings'].turndown); const turndownService = new TurndownService(store.getters['data/computedSettings'].turndown);
@ -55,16 +56,18 @@ export default {
onImportMarkdown(evt) { onImportMarkdown(evt) {
const file = evt.target.files[0]; const file = evt.target.files[0];
readFile(file) readFile(file)
.then(content => this.$store.dispatch('createFile', { .then(content => fileSvc.createFile({
...Provider.parseContent(content), ...Provider.parseContent(content),
name: file.name, name: file.name,
}) })
.then(item => this.$store.commit('file/setCurrentId', item.id))); .then(
item => this.$store.commit('file/setCurrentId', item.id)),
() => { /* Cancel */ });
}, },
onImportHtml(evt) { onImportHtml(evt) {
const file = evt.target.files[0]; const file = evt.target.files[0];
readFile(file) readFile(file)
.then(content => this.$store.dispatch('createFile', { .then(content => fileSvc.createFile({
...Provider.parseContent( ...Provider.parseContent(
turndownService.turndown( turndownService.turndown(
htmlSanitizer.sanitizeHtml(content) htmlSanitizer.sanitizeHtml(content)
@ -72,7 +75,9 @@ export default {
)), )),
name: file.name, name: file.name,
})) }))
.then(item => this.$store.commit('file/setCurrentId', item.id)); .then(
item => this.$store.commit('file/setCurrentId', item.id),
() => { /* Cancel */ });
}, },
}, },
}; };

View File

@ -3,7 +3,7 @@
<div class="menu-info-entries"> <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 menu-entry--info flex flex--row flex--align-center" v-if="loginToken">
<div class="menu-entry__icon menu-entry__icon--image"> <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> </div>
<span>Signed in as <b>{{loginToken.name}}</b>.</span> <span>Signed in as <b>{{loginToken.name}}</b>.</span>
</div> </div>
@ -97,6 +97,7 @@ export default {
'currentWorkspace', 'currentWorkspace',
'syncToken', 'syncToken',
'loginToken', 'loginToken',
'userId',
]), ]),
}, },
methods: { methods: {
@ -107,12 +108,12 @@ export default {
return googleHelper.signin() return googleHelper.signin()
.then( .then(
() => syncSvc.requestSync(), () => syncSvc.requestSync(),
() => {}, // Cancel () => { /* Cancel */ },
); );
}, },
fileProperties() { fileProperties() {
return this.$store.dispatch('modal/open', 'fileProperties') return this.$store.dispatch('modal/open', 'fileProperties')
.catch(() => {}); // Cancel .catch(() => { /* Cancel */ });
}, },
print() { print() {
print(); print();

View File

@ -82,14 +82,14 @@ export default {
return this.$store.dispatch('modal/open', 'settings') return this.$store.dispatch('modal/open', 'settings')
.then( .then(
settings => this.$store.dispatch('data/setSettings', settings), settings => this.$store.dispatch('data/setSettings', settings),
() => {}, // Cancel () => { /* Cancel */ },
); );
}, },
templates() { templates() {
return this.$store.dispatch('modal/open', 'templates') return this.$store.dispatch('modal/open', 'templates')
.then( .then(
({ templates }) => this.$store.dispatch('data/setTemplates', templates), ({ templates }) => this.$store.dispatch('data/setTemplates', templates),
() => {}, // Cancel () => { /* Cancel */ },
); );
}, },
reset() { reset() {

View File

@ -123,6 +123,8 @@ const openPublishModal = (token, type) => store.dispatch('modal/open', {
token, token,
}).then(publishLocation => publishSvc.createPublishLocation(publishLocation)); }).then(publishLocation => publishSvc.createPublishLocation(publishLocation));
const onCancel = () => {};
export default { export default {
components: { components: {
MenuEntry, MenuEntry,
@ -181,68 +183,68 @@ export default {
type: 'googleDriveAccount', type: 'googleDriveAccount',
onResolve: () => googleHelper.addDriveAccount(!store.getters['data/localSettings'].googleDriveRestrictedAccess), onResolve: () => googleHelper.addDriveAccount(!store.getters['data/localSettings'].googleDriveRestrictedAccess),
}) })
.catch(() => {}); // Cancel .catch(onCancel);
}, },
addDropboxAccount() { addDropboxAccount() {
return this.$store.dispatch('modal/open', { return this.$store.dispatch('modal/open', {
type: 'dropboxAccount', type: 'dropboxAccount',
onResolve: () => dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess), onResolve: () => dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess),
}) })
.catch(() => {}); // Cancel .catch(onCancel);
}, },
addGithubAccount() { addGithubAccount() {
return this.$store.dispatch('modal/open', { return this.$store.dispatch('modal/open', {
type: 'githubAccount', type: 'githubAccount',
onResolve: () => githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess), onResolve: () => githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess),
}) })
.catch(() => {}); // Cancel .catch(onCancel);
}, },
addWordpressAccount() { addWordpressAccount() {
return wordpressHelper.addAccount() return wordpressHelper.addAccount()
.catch(() => {}); // Cancel .catch(onCancel);
}, },
addBloggerAccount() { addBloggerAccount() {
return googleHelper.addBloggerAccount() return googleHelper.addBloggerAccount()
.catch(() => {}); // Cancel .catch(onCancel);
}, },
addZendeskAccount() { addZendeskAccount() {
return this.$store.dispatch('modal/open', { return this.$store.dispatch('modal/open', {
type: 'zendeskAccount', type: 'zendeskAccount',
onResolve: ({ subdomain, clientId }) => zendeskHelper.addAccount(subdomain, clientId), onResolve: ({ subdomain, clientId }) => zendeskHelper.addAccount(subdomain, clientId),
}) })
.catch(() => {}); // Cancel .catch(onCancel);
}, },
publishGoogleDrive(token) { publishGoogleDrive(token) {
return openPublishModal(token, 'googleDrivePublish') return openPublishModal(token, 'googleDrivePublish')
.catch(() => {}); // Cancel .catch(onCancel);
}, },
publishDropbox(token) { publishDropbox(token) {
return openPublishModal(token, 'dropboxPublish') return openPublishModal(token, 'dropboxPublish')
.catch(() => {}); // Cancel .catch(onCancel);
}, },
publishGithub(token) { publishGithub(token) {
return openPublishModal(token, 'githubPublish') return openPublishModal(token, 'githubPublish')
.catch(() => {}); // Cancel .catch(onCancel);
}, },
publishGist(token) { publishGist(token) {
return openPublishModal(token, 'gistPublish') return openPublishModal(token, 'gistPublish')
.catch(() => {}); // Cancel .catch(onCancel);
}, },
publishWordpress(token) { publishWordpress(token) {
return openPublishModal(token, 'wordpressPublish') return openPublishModal(token, 'wordpressPublish')
.catch(() => {}); // Cancel .catch(onCancel);
}, },
publishBlogger(token) { publishBlogger(token) {
return openPublishModal(token, 'bloggerPublish') return openPublishModal(token, 'bloggerPublish')
.catch(() => {}); // Cancel .catch(onCancel);
}, },
publishBloggerPage(token) { publishBloggerPage(token) {
return openPublishModal(token, 'bloggerPagePublish') return openPublishModal(token, 'bloggerPagePublish')
.catch(() => {}); // Cancel .catch(onCancel);
}, },
publishZendesk(token) { publishZendesk(token) {
return openPublishModal(token, 'zendeskPublish') return openPublishModal(token, 'zendeskPublish')
.catch(() => {}); // Cancel .catch(onCancel);
}, },
}, },
}; };

View File

@ -101,6 +101,8 @@ const openSyncModal = (token, type) => store.dispatch('modal/open', {
token, token,
}).then(syncLocation => syncSvc.createSyncLocation(syncLocation)); }).then(syncLocation => syncSvc.createSyncLocation(syncLocation));
const onCancel = () => {};
export default { export default {
components: { components: {
MenuEntry, MenuEntry,
@ -150,21 +152,21 @@ export default {
type: 'googleDriveAccount', type: 'googleDriveAccount',
onResolve: () => googleHelper.addDriveAccount(!store.getters['data/localSettings'].googleDriveRestrictedAccess), onResolve: () => googleHelper.addDriveAccount(!store.getters['data/localSettings'].googleDriveRestrictedAccess),
}) })
.catch(() => {}); // Cancel .catch(onCancel);
}, },
addDropboxAccount() { addDropboxAccount() {
return this.$store.dispatch('modal/open', { return this.$store.dispatch('modal/open', {
type: 'dropboxAccount', type: 'dropboxAccount',
onResolve: () => dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess), onResolve: () => dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess),
}) })
.catch(() => {}); // Cancel .catch(onCancel);
}, },
addGithubAccount() { addGithubAccount() {
return this.$store.dispatch('modal/open', { return this.$store.dispatch('modal/open', {
type: 'githubAccount', type: 'githubAccount',
onResolve: () => githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess), onResolve: () => githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess),
}) })
.catch(() => {}); // Cancel .catch(onCancel);
}, },
openGoogleDrive(token) { openGoogleDrive(token) {
return googleHelper.openPicker(token, 'doc') return googleHelper.openPicker(token, 'doc')
@ -178,11 +180,11 @@ export default {
}, },
saveGoogleDrive(token) { saveGoogleDrive(token) {
return openSyncModal(token, 'googleDriveSave') return openSyncModal(token, 'googleDriveSave')
.catch(() => {}); // Cancel .catch(onCancel);
}, },
saveDropbox(token) { saveDropbox(token) {
return openSyncModal(token, 'dropboxSave') return openSyncModal(token, 'dropboxSave')
.catch(() => {}); // Cancel .catch(onCancel);
}, },
openGithub(token) { openGithub(token) {
return store.dispatch('modal/open', { return store.dispatch('modal/open', {
@ -194,11 +196,11 @@ export default {
}, },
saveGithub(token) { saveGithub(token) {
return openSyncModal(token, 'githubSave') return openSyncModal(token, 'githubSave')
.catch(() => {}); // Cancel .catch(onCancel);
}, },
saveGist(token) { saveGist(token) {
return openSyncModal(token, 'gistSync') return openSyncModal(token, 'gistSync')
.catch(() => {}); // Cancel .catch(onCancel);
}, },
}, },
}; };

View File

@ -31,6 +31,8 @@ import { mapGetters } from 'vuex';
import MenuEntry from './common/MenuEntry'; import MenuEntry from './common/MenuEntry';
import googleHelper from '../../services/providers/helpers/googleHelper'; import googleHelper from '../../services/providers/helpers/googleHelper';
const onCancel = () => {};
export default { export default {
components: { components: {
MenuEntry, MenuEntry,
@ -48,13 +50,13 @@ export default {
return this.$store.dispatch('modal/open', { return this.$store.dispatch('modal/open', {
type: 'couchdbWorkspace', type: 'couchdbWorkspace',
}) })
.catch(() => {}); // Cancel .catch(onCancel);
}, },
addGithubWorkspace() { addGithubWorkspace() {
return this.$store.dispatch('modal/open', { return this.$store.dispatch('modal/open', {
type: 'githubWorkspace', type: 'githubWorkspace',
}) })
.catch(() => {}); // Cancel .catch(onCancel);
}, },
addGoogleDriveWorkspace() { addGoogleDriveWorkspace() {
return googleHelper.addDriveAccount(true) return googleHelper.addDriveAccount(true)
@ -62,7 +64,7 @@ export default {
type: 'googleDriveWorkspace', type: 'googleDriveWorkspace',
token, token,
})) }))
.catch(() => {}); // Cancel .catch(onCancel);
}, },
manageWorkspaces() { manageWorkspaces() {
return this.$store.dispatch('modal/open', 'workspaceManagement'); return this.$store.dispatch('modal/open', 'workspaceManagement');

View File

@ -2,7 +2,7 @@
<modal-inner class="modal__inner-1--about-modal" aria-label="About"> <modal-inner class="modal__inner-1--about-modal" aria-label="About">
<div class="modal__content"> <div class="modal__content">
<div class="logo-background"></div> <div class="logo-background"></div>
<small>v{{version}} © 2018 Dock5 Software</small> <small>v{{version}}<br>© 2013-2018 Dock5 Software</small>
<hr> <hr>
StackEdit on <a target="_blank" href="https://github.com/benweet/stackedit/">GitHub</a> StackEdit on <a target="_blank" href="https://github.com/benweet/stackedit/">GitHub</a>
<br> <br>

View File

@ -70,7 +70,8 @@ export default modalTemplate({
if (err.status !== 401) { if (err.status !== 401) {
throw err; throw err;
} }
this.$store.dispatch('modal/sponsorOnly'); this.$store.dispatch('modal/sponsorOnly')
.catch(() => { /* Cancel */ });
})) }))
.catch((err) => { .catch((err) => {
console.error(err); // eslint-disable-line no-console console.error(err); // eslint-disable-line no-console

View File

@ -63,7 +63,8 @@ export default modalTemplate({
if (err.status !== 401) { if (err.status !== 401) {
throw err; throw err;
} }
this.$store.dispatch('modal/sponsorOnly'); this.$store.dispatch('modal/sponsorOnly')
.catch(() => { /* Cancel */ });
})) }))
.catch((err) => { .catch((err) => {
console.error(err); // eslint-disable-line no-console console.error(err); // eslint-disable-line no-console

View File

@ -79,7 +79,7 @@ export default {
return this.$store.dispatch('modal/removeWorkspace') return this.$store.dispatch('modal/removeWorkspace')
.then( .then(
() => localDbSvc.removeWorkspace(id), () => localDbSvc.removeWorkspace(id),
() => {}, // Cancel () => { /* Cancel */ },
); );
}, },
}, },

View File

@ -42,7 +42,7 @@ export default {
this.$store.dispatch('modal/open', 'sponsor'); this.$store.dispatch('modal/open', 'sponsor');
} }
}) })
.catch(() => {}); // Cancel .catch(() => { /* Cancel */ });
}, },
}, },
}; };

View File

@ -1,9 +1,10 @@
import store from '../store'; import fileSvc from './fileSvc';
import utils from './utils'; import utils from './utils';
export default { export default {
importBackup(jsonValue) { async importBackup(jsonValue) {
const nameMap = {}; const fileNameMap = {};
const folderNameMap = {};
const parentIdMap = {}; const parentIdMap = {};
const textMap = {}; const textMap = {};
const propertiesMap = {}; const propertiesMap = {};
@ -22,24 +23,18 @@ export default {
// StackEdit v4 format // StackEdit v4 format
const [, v4Id, type] = v4Match; const [, v4Id, type] = v4Match;
if (type === 'title') { if (type === 'title') {
nameMap[v4Id] = value; fileNameMap[v4Id] = value;
} else if (type === 'content') { } else if (type === 'content') {
textMap[v4Id] = value; textMap[v4Id] = value;
} }
} else if (value.type === 'folder') { } else if (value.type === 'folder') {
// StackEdit v5 folder // StackEdit v5 folder
const folderId = utils.uid(); folderIdMap[id] = utils.uid();
const name = utils.sanitizeName(value.name); folderNameMap[id] = value.name;
const parentId = `${value.parentId || ''}` || null; parentIdMap[id] = `${value.parentId || ''}`;
store.commit('folder/setItem', {
id: folderId,
name,
parentId,
});
folderIdMap[id] = folderId;
} else if (value.type === 'file') { } else if (value.type === 'file') {
// StackEdit v5 file // StackEdit v5 file
nameMap[id] = utils.sanitizeName(value.name); fileNameMap[id] = value.name;
parentIdMap[id] = `${value.parentId || ''}`; parentIdMap[id] = `${value.parentId || ''}`;
} else if (value.type === 'content') { } else if (value.type === 'content') {
// StackEdit v5 content // StackEdit v5 content
@ -54,14 +49,20 @@ export default {
} }
}); });
// Go through the maps await utils.awaitSequence(Object.keys(folderNameMap), async externalId => fileSvc.storeItem({
Object.entries(nameMap).forEach(([externalId, name]) => store.dispatch('createFile', { id: folderIdMap[externalId],
name, 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]], parentId: folderIdMap[parentIdMap[externalId]],
text: textMap[externalId], text: textMap[externalId],
properties: propertiesMap[externalId], properties: propertiesMap[externalId],
discussions: discussionsMap[externalId], discussions: discussionsMap[externalId],
comments: commentsMap[externalId], comments: commentsMap[externalId],
})); }, true));
}, },
}; };

View 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
View 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;
}
}
},
};

View File

@ -2,6 +2,7 @@ import FileSaver from 'file-saver';
import utils from './utils'; import utils from './utils';
import store from '../store'; import store from '../store';
import welcomeFile from '../data/welcomeFile.md'; import welcomeFile from '../data/welcomeFile.md';
import fileSvc from './fileSvc';
const dbVersion = 1; const dbVersion = 1;
const dbStoreName = 'objects'; const dbStoreName = 'objects';
@ -186,6 +187,7 @@ const localDbSvc = {
dbStore.delete(item.id); dbStore.delete(item.id);
} }
}); });
fileSvc.ensureUniquePaths();
this.lastTx = lastTx; this.lastTx = lastTx;
cb(storeItemMap); cb(storeItemMap);
} }
@ -249,20 +251,20 @@ const localDbSvc = {
* Read and apply one DB change. * Read and apply one DB change.
*/ */
readDbItem(dbItem, storeItemMap) { readDbItem(dbItem, storeItemMap) {
const existingStoreItem = storeItemMap[dbItem.id]; const storeItem = storeItemMap[dbItem.id];
if (!dbItem.hash) { if (!dbItem.hash) {
// DB item is a delete marker // DB item is a delete marker
delete this.hashMap[dbItem.type][dbItem.id]; delete this.hashMap[dbItem.type][dbItem.id];
if (existingStoreItem) { if (storeItem) {
// Remove item from the store // Remove item from the store
store.commit(`${existingStoreItem.type}/deleteItem`, existingStoreItem.id); store.commit(`${storeItem.type}/deleteItem`, storeItem.id);
delete storeItemMap[existingStoreItem.id]; delete storeItemMap[storeItem.id];
} }
} else if (this.hashMap[dbItem.type][dbItem.id] !== dbItem.hash) { } else if (this.hashMap[dbItem.type][dbItem.id] !== dbItem.hash) {
// DB item is different from the corresponding store item // DB item is different from the corresponding store item
this.hashMap[dbItem.type][dbItem.id] = dbItem.hash; this.hashMap[dbItem.type][dbItem.id] = dbItem.hash;
// Update content only if it exists in the store // 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 // Put item in the store
dbItem.tx = undefined; dbItem.tx = undefined;
store.commit(`${dbItem.type}/setItem`, dbItem); store.commit(`${dbItem.type}/setItem`, dbItem);
@ -403,13 +405,14 @@ const localDbSvc = {
// Clean files // Clean files
store.getters['file/items'] store.getters['file/items']
.filter(file => file.parentId === 'trash') // If file is in the trash .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 // Enable sponsorship
if (utils.queryParams.paymentSuccess) { if (utils.queryParams.paymentSuccess) {
location.hash = ''; // PaymentSuccess param is always on its own 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']; const sponsorToken = store.getters['workspace/sponsorToken'];
// Force check sponsorship after a few seconds // Force check sponsorship after a few seconds
const currentDate = Date.now(); const currentDate = Date.now();
@ -438,10 +441,10 @@ const localDbSvc = {
store.commit('file/setCurrentId', recentFile.id); store.commit('file/setCurrentId', recentFile.id);
} else { } else {
// If still no ID, create a new file // If still no ID, create a new file
store.dispatch('createFile', { fileSvc.createFile({
name: 'Welcome file', name: 'Welcome file',
text: welcomeFile, text: welcomeFile,
}) }, true)
// Set it as the current file // Set it as the current file
.then(newFile => store.commit('file/setCurrentId', newFile.id)); .then(newFile => store.commit('file/setCurrentId', newFile.id));
} }

View File

@ -83,7 +83,9 @@ export default class Provider {
parentId: null, parentId: null,
}); });
} }
return true;
} }
} }
return false;
} }
} }

View File

@ -2,6 +2,7 @@ import store from '../../store';
import dropboxHelper from './helpers/dropboxHelper'; import dropboxHelper from './helpers/dropboxHelper';
import Provider from './common/Provider'; import Provider from './common/Provider';
import utils from '../utils'; import utils from '../utils';
import fileSvc from '../fileSvc';
const makePathAbsolute = (token, path) => { const makePathAbsolute = (token, path) => {
if (!token.fullAccess) { if (!token.fullAccess) {
@ -88,12 +89,6 @@ export default new Provider({
}; };
return this.downloadContent(token, syncLocation) return this.downloadContent(token, syncLocation)
.then((content) => { .then((content) => {
const id = utils.uid();
delete content.history;
store.commit('content/setItem', {
...content,
id: `${id}/content`,
});
let name = path; let name = path;
const slashPos = name.lastIndexOf('/'); const slashPos = name.lastIndexOf('/');
if (slashPos > -1 && slashPos < name.length - 1) { if (slashPos > -1 && slashPos < name.length - 1) {
@ -103,25 +98,30 @@ export default new Provider({
if (dotPos > 0 && slashPos < name.length) { if (dotPos > 0 && slashPos < name.length) {
name = name.slice(0, dotPos); name = name.slice(0, dotPos);
} }
store.commit('file/setItem', { return fileSvc.createFile({
id, name,
name: utils.sanitizeName(name),
parentId: store.getters['file/current'].parentId, 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', { store.commit('syncLocation/setItem', {
...syncLocation, ...syncLocation,
id: utils.uid(), 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.`); store.dispatch('notification/info', `${store.getters['file/current'].name} was imported from Dropbox.`);
}, () => { })
.catch(() => {
store.dispatch('notification/error', `Could not open file ${path}.`); store.dispatch('notification/error', `Could not open file ${path}.`);
}) })
.then(() => openOneFile()); .then(() => openOneFile());
}; };
return Promise.resolve() return Promise.resolve(openOneFile());
.then(() => openOneFile());
}, },
makeLocation(token, path) { makeLocation(token, path) {
return { return {

View File

@ -2,6 +2,7 @@ import store from '../../store';
import githubHelper from './helpers/githubHelper'; import githubHelper from './helpers/githubHelper';
import Provider from './common/Provider'; import Provider from './common/Provider';
import utils from '../utils'; import utils from '../utils';
import fileSvc from '../fileSvc';
const savedSha = {}; const savedSha = {};
@ -75,12 +76,6 @@ export default new Provider({
// Download content from GitHub and create the file // Download content from GitHub and create the file
return this.downloadContent(token, syncLocation) return this.downloadContent(token, syncLocation)
.then((content) => { .then((content) => {
const id = utils.uid();
delete content.history;
store.commit('content/setItem', {
...content,
id: `${id}/content`,
});
let name = syncLocation.path; let name = syncLocation.path;
const slashPos = name.lastIndexOf('/'); const slashPos = name.lastIndexOf('/');
if (slashPos > -1 && slashPos < name.length - 1) { if (slashPos > -1 && slashPos < name.length - 1) {
@ -90,19 +85,25 @@ export default new Provider({
if (dotPos > 0 && slashPos < name.length) { if (dotPos > 0 && slashPos < name.length) {
name = name.slice(0, dotPos); name = name.slice(0, dotPos);
} }
store.commit('file/setItem', { return fileSvc.createFile({
id, name,
name: utils.sanitizeName(name),
parentId: store.getters['file/current'].parentId, 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', { store.commit('syncLocation/setItem', {
...syncLocation, ...syncLocation,
id: utils.uid(), 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.`); store.dispatch('notification/info', `${store.getters['file/current'].name} was imported from GitHub.`);
}, () => { })
.catch(() => {
store.dispatch('notification/error', `Could not open file ${syncLocation.path}.`); store.dispatch('notification/error', `Could not open file ${syncLocation.path}.`);
}); });
}); });

View File

@ -479,12 +479,13 @@ export default new Provider({
} else if (entry.committer && entry.committer.login) { } else if (entry.committer && entry.committer.login) {
user = entry.committer; 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) const date = (entry.commit.author && entry.commit.author.date)
|| (entry.commit.committer && entry.commit.committer.date); || (entry.commit.committer && entry.commit.committer.date);
return { return {
id: entry.sha, id: entry.sha,
sub: user.login, sub,
created: date ? new Date(date).getTime() : 1, created: date ? new Date(date).getTime() : 1,
}; };
}) })

View File

@ -136,7 +136,7 @@ export default new Provider({
return googleHelper.getAppDataFileRevisions(token, syncData.id) return googleHelper.getAppDataFileRevisions(token, syncData.id)
.then(revisions => revisions.map(revision => ({ .then(revisions => revisions.map(revision => ({
id: revision.id, id: revision.id,
sub: revision.lastModifyingUser && revision.lastModifyingUser.permissionId, sub: revision.lastModifyingUser && `go:${revision.lastModifyingUser.permissionId}`,
created: new Date(revision.modifiedTime).getTime(), created: new Date(revision.modifiedTime).getTime(),
})) }))
.sort((revision1, revision2) => revision2.created - revision1.created)); .sort((revision1, revision2) => revision2.created - revision1.created));

View File

@ -2,6 +2,7 @@ import store from '../../store';
import googleHelper from './helpers/googleHelper'; import googleHelper from './helpers/googleHelper';
import Provider from './common/Provider'; import Provider from './common/Provider';
import utils from '../utils'; import utils from '../utils';
import fileSvc from '../fileSvc';
export default new Provider({ export default new Provider({
id: 'googleDrive', id: 'googleDrive',
@ -93,7 +94,7 @@ export default new Provider({
const token = store.getters['data/googleTokens'][state.userId]; const token = store.getters['data/googleTokens'][state.userId];
switch (token && state.action) { switch (token && state.action) {
case 'create': case 'create':
return store.dispatch('createFile') return fileSvc.createFile({}, true)
.then((file) => { .then((file) => {
store.commit('file/setCurrentId', file.id); store.commit('file/setCurrentId', file.id);
// Return a new syncLocation // Return a new syncLocation
@ -169,32 +170,29 @@ export default new Provider({
sub: token.sub, sub: token.sub,
}; };
return this.downloadContent(token, syncLocation) return this.downloadContent(token, syncLocation)
.then((content) => { .then(content => fileSvc.createFile({
const id = utils.uid(); name,
delete content.history; parentId: store.getters['file/current'].parentId,
store.commit('content/setItem', { text: content.text,
...content, properties: content.properties,
id: `${id}/content`, discussions: content.discussions,
}); comments: content.comments,
store.commit('file/setItem', { }, true))
id, .then((item) => {
name: utils.sanitizeName(driveFile.name), store.commit('file/setCurrentId', item.id);
parentId: store.getters['file/current'].parentId,
});
store.commit('syncLocation/setItem', { store.commit('syncLocation/setItem', {
...syncLocation, ...syncLocation,
id: utils.uid(), 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.`); 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}.`); store.dispatch('notification/error', `Could not open file ${driveFile.id}.`);
}) })
.then(() => openOneFile()); .then(() => openOneFile());
}; };
return Promise.resolve() return Promise.resolve(openOneFile());
.then(() => openOneFile());
}, },
makeLocation(token, fileId, folderId) { makeLocation(token, fileId, folderId) {
const location = { const location = {

View File

@ -2,6 +2,7 @@ import store from '../../store';
import googleHelper from './helpers/googleHelper'; import googleHelper from './helpers/googleHelper';
import Provider from './common/Provider'; import Provider from './common/Provider';
import utils from '../utils'; import utils from '../utils';
import fileSvc from '../fileSvc';
const getSyncData = (fileId) => { const getSyncData = (fileId) => {
const syncData = store.getters['data/syncDataByItemId'][`${fileId}/content`]; const syncData = store.getters['data/syncDataByItemId'][`${fileId}/content`];
@ -195,9 +196,9 @@ export default new Provider({
[syncData.id]: syncData, [syncData.id]: syncData,
}); });
} }
return store.dispatch('createFile', { return fileSvc.createFile({
parentId: syncData && syncData.itemId, parentId: syncData && syncData.itemId,
}) }, true)
.then((file) => { .then((file) => {
store.commit('file/setCurrentId', file.id); store.commit('file/setCurrentId', file.id);
// File will be created on next workspace sync // File will be created on next workspace sync

View File

@ -91,9 +91,9 @@ export default {
method: 'POST', method: 'POST',
body: { item, time: Date.now() }, body: { item, time: Date.now() },
}; };
const loginToken = store.getters['workspace/loginToken']; const userId = store.getters['workspace/userId'];
if (loginToken) { if (userId) {
options.body.sub = loginToken.sub; options.body.sub = userId;
} }
if (documentId) { if (documentId) {
options.method = 'PUT'; options.method = 'PUT';

View File

@ -73,6 +73,22 @@ export default {
addAccount(repoFullAccess = false) { addAccount(repoFullAccess = false) {
return this.startOauth2(getScopes({ repoFullAccess })); 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) { getTree(token, owner, repo, sha) {
return repoRequest(token, owner, repo, { return repoRequest(token, owner, repo, {
url: `git/trees/${encodeURIComponent(sha)}?recursive=1`, url: `git/trees/${encodeURIComponent(sha)}?recursive=1`,
@ -86,9 +102,9 @@ export default {
}, },
getHeadTree(token, owner, repo, branch) { getHeadTree(token, owner, repo, branch) {
return repoRequest(token, owner, repo, { 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) { getCommits(token, owner, repo, sha, path) {
return repoRequest(token, owner, repo, { return repoRequest(token, owner, repo, {

View File

@ -203,7 +203,7 @@ export default {
}, true) }, true)
.then((res) => { .then((res) => {
store.commit('userInfo/addItem', { store.commit('userInfo/addItem', {
id: res.body.id, id: `go:${res.body.id}`,
name: res.body.displayName, name: res.body.displayName,
imageUrl: (res.body.image.url || '').replace(/\bsz?=\d+$/, 'sz=40'), imageUrl: (res.body.image.url || '').replace(/\bsz?=\d+$/, 'sz=40'),
}); });

View File

@ -9,6 +9,7 @@ import './providers/couchdbWorkspaceProvider';
import './providers/githubWorkspaceProvider'; import './providers/githubWorkspaceProvider';
import './providers/googleDriveWorkspaceProvider'; import './providers/googleDriveWorkspaceProvider';
import tempFileSvc from './tempFileSvc'; import tempFileSvc from './tempFileSvc';
import fileSvc from './fileSvc';
const minAutoSyncEvery = 60 * 1000; // 60 sec const minAutoSyncEvery = 60 * 1000; // 60 sec
const inactivityThreshold = 3 * 1000; // 3 sec const inactivityThreshold = 3 * 1000; // 3 sec
@ -746,7 +747,7 @@ function requestSync() {
Object.entries(fileHashesToClean).forEach(([fileId, fileHash]) => { Object.entries(fileHashesToClean).forEach(([fileId, fileHash]) => {
const file = store.state.file.itemMap[fileId]; const file = store.state.file.itemMap[fileId];
if (file && file.hash === fileHash) { if (file && file.hash === fileHash) {
store.dispatch('deleteFile', fileId); fileSvc.deleteFile(fileId);
} }
}); });
}) })

View File

@ -2,6 +2,7 @@ import cledit from './cledit';
import store from '../store'; import store from '../store';
import utils from './utils'; import utils from './utils';
import editorSvc from './editorSvc'; import editorSvc from './editorSvc';
import fileSvc from './fileSvc';
const origin = utils.queryParams.origin; const origin = utils.queryParams.origin;
const fileName = utils.queryParams.fileName; const fileName = utils.queryParams.fileName;
@ -29,12 +30,12 @@ export default {
store.commit('setLight', true); store.commit('setLight', true);
return store.dispatch('createFile', { return fileSvc.createFile({
name: fileName || utils.getHostname(origin), name: fileName || utils.getHostname(origin),
text: contentText || '\n', text: contentText || '\n',
properties: contentProperties, properties: contentProperties,
parentId: 'temp', parentId: 'temp',
}) }, true)
.then((file) => { .then((file) => {
const fileItemMap = store.state.file.itemMap; const fileItemMap = store.state.file.itemMap;
@ -57,7 +58,7 @@ export default {
.splice(10) .splice(10)
.forEach(([id]) => { .forEach(([id]) => {
delete lastCreated[id]; delete lastCreated[id];
store.dispatch('deleteFile', id); fileSvc.deleteFile(id);
}); });
// Store file creations and open the file // Store file creations and open the file

View File

@ -1,8 +1,16 @@
import googleHelper from './providers/helpers/googleHelper'; import googleHelper from './providers/helpers/googleHelper';
import githubHelper from './providers/helpers/githubHelper';
import utils from './utils';
import store from '../store'; import store from '../store';
const promised = {}; 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 { export default {
addInfo({ id, name, imageUrl }) { addInfo({ id, name, imageUrl }) {
promised[id] = true; promised[id] = true;
@ -10,8 +18,10 @@ export default {
}, },
getInfo(userId) { getInfo(userId) {
if (!promised[userId]) { if (!promised[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/googleTokens'][userId]; const token = store.getters[`data/${type}Tokens`][sub];
if (token) { if (token) {
store.commit('userInfo/addItem', { store.commit('userInfo/addItem', {
id: userId, id: userId,
@ -19,16 +29,31 @@ export default {
}); });
} }
// Get user info from Google // Get user info from provider
if (!store.state.offline) { if (!store.state.offline) {
promised[userId] = true; promised[userId] = true;
googleHelper.getUser(userId) switch (type) {
.catch((err) => { case 'github': {
if (err.status !== 404) { return githubHelper.getUser(sub)
promised[userId] = false; .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;
}, },
}; };

View File

@ -23,6 +23,7 @@ const parseQueryParams = (params) => {
return result; return result;
}; };
// For utils.computeProperties() // For utils.computeProperties()
const deepOverride = (obj, opt) => { const deepOverride = (obj, opt) => {
if (obj === undefined) { if (obj === undefined) {
@ -96,14 +97,23 @@ export default {
'layoutSettings', 'layoutSettings',
'tokens', 'tokens',
], ],
userIdPrefixes: {
go: 'google',
gh: 'github',
},
textMaxLength: 250000, textMaxLength: 250000,
sanitizeText(text) { sanitizeText(text) {
const result = `${text || ''}`.slice(0, this.textMaxLength); const result = `${text || ''}`.slice(0, this.textMaxLength);
// last char must be a `\n`. // last char must be a `\n`.
return `${result}\n`.replace(/\n\n$/, '\n'); return `${result}\n`.replace(/\n\n$/, '\n');
}, },
defaultName: 'Untitled',
sanitizeName(name) { 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, deepCopy,
serializeObject(obj) { serializeObject(obj) {
@ -124,9 +134,8 @@ export default {
// If every field fits the criteria // If every field fits the criteria
if (Object.entries(criteria).every(([key, value]) => value === item[key])) { if (Object.entries(criteria).every(([key, value]) => value === item[key])) {
result = item; result = item;
return true;
} }
return false; return result;
}); });
return result; return result;
}, },
@ -199,6 +208,18 @@ export default {
setInterval(func, interval) { setInterval(func, interval) {
return setInterval(() => func(), this.randomize(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, parseQueryParams,
addQueryParams(url = '', params = {}, hash = false) { addQueryParams(url = '', params = {}, hash = false) {
const keys = Object.keys(params).filter(key => params[key] != null); const keys = Object.keys(params).filter(key => params[key] != null);
@ -239,9 +260,6 @@ export default {
} }
return result; return result;
}, },
concatPaths(...paths) {
return paths.join('/').replace(/\/+/g, '/');
},
getHostname(url) { getHostname(url) {
urlParser.href = url; urlParser.href = url;
return urlParser.hostname; return urlParser.hostname;

View File

@ -130,7 +130,7 @@ export default {
.then(() => syncSvc.requestSync()) .then(() => syncSvc.requestSync())
.then(() => dispatch('createNewDiscussion', selection)), .then(() => dispatch('createNewDiscussion', selection)),
}, { root: true }) }, { root: true })
.catch(() => { }); // Cancel .catch(() => { /* Cancel */ });
} else if (selection) { } else if (selection) {
let text = rootGetters['content/current'].text.slice(selection.start, selection.end).trim(); let text = rootGetters['content/current'].text.slice(selection.start, selection.end).trim();
const maxLength = 80; const maxLength = 80;

View File

@ -190,85 +190,5 @@ export default {
commit('setDragTargetId', id); commit('setDragTargetId', id);
dispatch('openDragTarget'); 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
},
}, },
}; };

View File

@ -62,6 +62,7 @@ const store = new Vuex.Store({
}, },
itemPaths: (state) => { itemPaths: (state) => {
const result = {}; const result = {};
const folderMap = state.folder.itemMap;
const getPath = (item) => { const getPath = (item) => {
let itemPath = result[item.id]; let itemPath = result[item.id];
if (!itemPath) { if (!itemPath) {
@ -69,29 +70,31 @@ const store = new Vuex.Store({
itemPath = `.stackedit-trash/${item.name}`; itemPath = `.stackedit-trash/${item.name}`;
} else { } else {
let name = item.name; let name = item.name;
if (item.type === 'folder') { if (folderMap[item.id]) {
name += '/'; name += '/';
} }
const parent = state.folder.itemMap[item.parentId]; const parentFolder = folderMap[item.parentId];
if (!parent) { if (parentFolder) {
itemPath = name; itemPath = getPath(parentFolder) + name;
} else { } else {
itemPath = getPath(parent) + name; itemPath = name;
} }
} }
} }
result[item.id] = itemPath; result[item.id] = itemPath;
return itemPath; return itemPath;
}; };
[...state.folder.items, ...state.file.items].forEach(item => getPath(item)); [...state.folder.items, ...state.file.items].forEach(item => getPath(item));
return result; return result;
}, },
pathItems: (state, getters) => { pathItems: (state, getters) => {
const result = {}; const result = {};
const itemPaths = getters.itemPaths;
const allItemMap = getters.allItemMap; const allItemMap = getters.allItemMap;
Object.entries(itemPaths).forEach(([id, path]) => { Object.entries(getters.itemPaths).forEach(([id, path]) => {
result[path] = allItemMap[id]; const items = result[path] || [];
items.push(allItemMap[id]);
result[path] = items;
}); });
return result; return result;
}, },
@ -131,33 +134,6 @@ const store = new Vuex.Store({
} }
return Promise.resolve(); 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, strict: debug,
plugins: debug ? [createLogger()] : [], plugins: debug ? [createLogger()] : [],

View File

@ -74,13 +74,27 @@ export default {
}), }),
trashDeletion: ({ dispatch }) => dispatch('open', { trashDeletion: ({ dispatch }) => dispatch('open', {
content: '<p>Files in the trash are automatically deleted after 7 days of inactivity.</p>', content: '<p>Files in the trash are automatically deleted after 7 days of inactivity.</p>',
resolveText: 'Ok', rejectText: 'Ok',
}), }),
fileRestoration: ({ dispatch }) => dispatch('open', { fileRestoration: ({ dispatch }) => dispatch('open', {
content: '<p>You are about to revert some changes. Are you sure?</p>', content: '<p>You are about to revert some changes. Are you sure?</p>',
resolveText: 'Yes, revert', resolveText: 'Yes, revert',
rejectText: 'No', 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', { removeWorkspace: ({ dispatch }) => dispatch('open', {
content: '<p>You are about to remove a workspace locally. Are you sure?</p>', content: '<p>You are about to remove a workspace locally. Are you sure?</p>',
resolveText: 'Yes, remove', resolveText: 'Yes, remove',
@ -127,11 +141,11 @@ export default {
}), }),
sponsorOnly: ({ dispatch }) => dispatch('open', { sponsorOnly: ({ dispatch }) => dispatch('open', {
content: '<p>This feature is restricted to sponsors as it relies on server resources.</p>', 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', { paymentSuccess: ({ dispatch }) => dispatch('open', {
content: '<p>Thank you for your payment! Your sponsorship will be active in a minute.</p>', content: '<p>Thank you for your payment! Your sponsorship will be active in a minute.</p>',
resolveText: 'Ok', rejectText: 'Ok',
}), }),
}, },
}; };

View File

@ -11,7 +11,10 @@ export default (empty, simpleHash = false) => {
itemMap: {}, itemMap: {},
}, },
getters: { getters: {
items: state => Object.entries(state.itemMap).map(([, item]) => item), items: (state) => {
console.log(state.itemMap);
return Object.values(state.itemMap);
},
}, },
mutations: { mutations: {
setItem(state, value) { setItem(state, value) {

View File

@ -1,3 +1,5 @@
import utils from '../services/utils';
export default { export default {
namespaced: true, namespaced: true,
state: { state: {
@ -21,6 +23,10 @@ export default {
const workspaces = rootGetters['data/sanitizedWorkspaces']; const workspaces = rootGetters['data/sanitizedWorkspaces'];
return workspaces[state.currentWorkspaceId] || getters.mainWorkspace; return workspaces[state.currentWorkspaceId] || getters.mainWorkspace;
}, },
hasUniquePaths: (state, getters) => {
const workspace = getters.currentWorkspace;
return workspace.providerId === 'githubWorkspace';
},
lastSyncActivityKey: (state, getters) => `${getters.currentWorkspace.id}/lastSyncActivity`, lastSyncActivityKey: (state, getters) => `${getters.currentWorkspace.id}/lastSyncActivity`,
lastFocusKey: (state, getters) => `${getters.currentWorkspace.id}/lastWindowFocus`, lastFocusKey: (state, getters) => `${getters.currentWorkspace.id}/lastWindowFocus`,
mainWorkspaceToken: (state, getters, rootState, rootGetters) => { mainWorkspaceToken: (state, getters, rootState, rootGetters) => {
@ -55,10 +61,28 @@ export default {
const googleTokens = rootGetters['data/googleTokens']; const googleTokens = rootGetters['data/googleTokens'];
return googleTokens[workspace.sub]; return googleTokens[workspace.sub];
} }
case 'githubWorkspace': {
const githubTokens = rootGetters['data/githubTokens'];
return githubTokens[workspace.sub];
}
default: default:
return getters.mainWorkspaceToken; 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, sponsorToken: (state, getters) => getters.mainWorkspaceToken,
}, },
actions: { actions: {