GitLab provider (part 1)
This commit is contained in:
parent
fd6ac907bb
commit
2e832fd766
12
src/assets/iconGitlab.svg
Normal file
12
src/assets/iconGitlab.svg
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
<svg width="100%" height="100%" viewBox="0 0 30 30" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
|
||||
<path d="M14.581,28.019l5.369,-16.526l-10.738,0l5.369,16.526l0,0Z" style="fill:#e24329;"/>
|
||||
<path d="M14.581,28.019l-5.37,-16.526l-7.525,0l12.895,16.526l0,0Z" style="fill:#fc6d26;"/>
|
||||
<path d="M1.686,11.493l-1.632,5.022c-0.148,0.458 0.015,0.96 0.404,1.243l14.123,10.261l-12.895,-16.526l0,0Z" style="fill:#fca326;"/>
|
||||
<path d="M1.686,11.493l7.526,0l-3.235,-9.953c-0.166,-0.512 -0.89,-0.512 -1.057,0l-3.234,9.953l0,0Z" style="fill:#e24329;"/>
|
||||
<path d="M14.581,28.019l5.369,-16.526l7.526,0l-12.895,16.526l0,0Z" style="fill:#fc6d26;"/>
|
||||
<path d="M27.476,11.493l1.631,5.022c0.149,0.458 -0.014,0.96 -0.404,1.243l-14.122,10.261l12.895,-16.526l0,0Z" style="fill:#fca326;"/>
|
||||
<path d="M27.476,11.493l-7.526,0l3.234,-9.953c0.167,-0.512 0.891,-0.512 1.058,0l3.234,9.953l0,0Z" style="fill:#e24329;"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1005 B |
@ -21,6 +21,7 @@ import syncSvc from '../services/syncSvc';
|
||||
import networkSvc from '../services/networkSvc';
|
||||
import sponsorSvc from '../services/sponsorSvc';
|
||||
import tempFileSvc from '../services/tempFileSvc';
|
||||
import store from '../store';
|
||||
import './common/vueGlobals';
|
||||
|
||||
const themeClasses = {
|
||||
@ -41,7 +42,7 @@ export default {
|
||||
}),
|
||||
computed: {
|
||||
classes() {
|
||||
const result = themeClasses[this.$store.getters['data/computedSettings'].colorTheme];
|
||||
const result = themeClasses[store.getters['data/computedSettings'].colorTheme];
|
||||
return Array.isArray(result) ? result : themeClasses.light;
|
||||
},
|
||||
},
|
||||
@ -57,7 +58,7 @@ export default {
|
||||
window.location.reload();
|
||||
} else if (err && err.message !== 'RELOAD') {
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
this.$store.dispatch('notification/error', err);
|
||||
store.dispatch('notification/error', err);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import store from '../store';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
@ -24,7 +25,7 @@ export default {
|
||||
methods: {
|
||||
close(item = null) {
|
||||
this.resolve(item);
|
||||
this.$store.dispatch('contextMenu/close');
|
||||
store.dispatch('contextMenu/close');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -12,6 +12,7 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import CommentList from './gutters/CommentList';
|
||||
import EditorNewDiscussionButton from './gutters/EditorNewDiscussionButton';
|
||||
import store from '../store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -52,11 +53,11 @@ export default {
|
||||
editorElt.addEventListener('mouseover', onDiscussionEvt(classToggler(true)));
|
||||
editorElt.addEventListener('mouseout', onDiscussionEvt(classToggler(false)));
|
||||
editorElt.addEventListener('click', onDiscussionEvt((discussionId) => {
|
||||
this.$store.commit('discussion/setCurrentDiscussionId', discussionId);
|
||||
store.commit('discussion/setCurrentDiscussionId', discussionId);
|
||||
}));
|
||||
|
||||
this.$watch(
|
||||
() => this.$store.state.discussion.currentDiscussionId,
|
||||
() => store.state.discussion.currentDiscussionId,
|
||||
(discussionId, oldDiscussionId) => {
|
||||
if (oldDiscussionId) {
|
||||
editorElt.querySelectorAll(`.discussion-editor-highlighting--${oldDiscussionId}`)
|
||||
|
@ -29,6 +29,7 @@
|
||||
import { mapState, mapGetters, mapActions } from 'vuex';
|
||||
import ExplorerNode from './ExplorerNode';
|
||||
import explorerSvc from '../services/explorerSvc';
|
||||
import store from '../store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -55,16 +56,16 @@ export default {
|
||||
editItem() {
|
||||
const node = this.selectedNode;
|
||||
if (!node.isTrash && !node.isTemp) {
|
||||
this.$store.commit('explorer/setEditingId', node.item.id);
|
||||
store.commit('explorer/setEditingId', node.item.id);
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$watch(
|
||||
() => this.$store.getters['file/current'].id,
|
||||
() => store.getters['file/current'].id,
|
||||
(currentFileId) => {
|
||||
this.$store.commit('explorer/setSelectedId', currentFileId);
|
||||
this.$store.dispatch('explorer/openNode', currentFileId);
|
||||
store.commit('explorer/setSelectedId', currentFileId);
|
||||
store.dispatch('explorer/openNode', currentFileId);
|
||||
}, {
|
||||
immediate: true,
|
||||
},
|
||||
|
@ -21,6 +21,7 @@
|
||||
import { mapMutations, mapActions } from 'vuex';
|
||||
import workspaceSvc from '../services/workspaceSvc';
|
||||
import explorerSvc from '../services/explorerSvc';
|
||||
import store from '../store';
|
||||
|
||||
export default {
|
||||
name: 'explorer-node', // Required for recursivity
|
||||
@ -36,35 +37,35 @@ export default {
|
||||
return `${(this.depth + 1) * 15}px`;
|
||||
},
|
||||
isSelected() {
|
||||
return this.$store.getters['explorer/selectedNode'] === this.node;
|
||||
return store.getters['explorer/selectedNode'] === this.node;
|
||||
},
|
||||
isEditing() {
|
||||
return this.$store.getters['explorer/editingNode'] === this.node;
|
||||
return store.getters['explorer/editingNode'] === this.node;
|
||||
},
|
||||
isDragTarget() {
|
||||
return this.$store.getters['explorer/dragTargetNode'] === this.node;
|
||||
return store.getters['explorer/dragTargetNode'] === this.node;
|
||||
},
|
||||
isDragTargetFolder() {
|
||||
return this.$store.getters['explorer/dragTargetNodeFolder'] === this.node;
|
||||
return store.getters['explorer/dragTargetNodeFolder'] === this.node;
|
||||
},
|
||||
isOpen() {
|
||||
return this.$store.state.explorer.openNodes[this.node.item.id] || this.node.isRoot;
|
||||
return store.state.explorer.openNodes[this.node.item.id] || this.node.isRoot;
|
||||
},
|
||||
newChild() {
|
||||
return this.$store.getters['explorer/newChildNodeParent'] === this.node
|
||||
&& this.$store.state.explorer.newChildNode;
|
||||
return store.getters['explorer/newChildNodeParent'] === this.node
|
||||
&& store.state.explorer.newChildNode;
|
||||
},
|
||||
newChildName: {
|
||||
get() {
|
||||
return this.$store.state.explorer.newChildNode.item.name;
|
||||
return store.state.explorer.newChildNode.item.name;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('explorer/setNewItemName', value);
|
||||
store.commit('explorer/setNewItemName', value);
|
||||
},
|
||||
},
|
||||
editingNodeName: {
|
||||
get() {
|
||||
return this.$store.getters['explorer/editingNode'].item.name;
|
||||
return store.getters['explorer/editingNode'].item.name;
|
||||
},
|
||||
set(value) {
|
||||
this.editingValue = value.trim();
|
||||
@ -79,25 +80,25 @@ export default {
|
||||
'setDragTarget',
|
||||
]),
|
||||
select(id = this.node.item.id, doOpen = true) {
|
||||
const node = this.$store.getters['explorer/nodeMap'][id];
|
||||
const node = store.getters['explorer/nodeMap'][id];
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
this.$store.commit('explorer/setSelectedId', id);
|
||||
store.commit('explorer/setSelectedId', id);
|
||||
if (doOpen) {
|
||||
// Prevent from freezing the UI while loading the file
|
||||
setTimeout(() => {
|
||||
if (node.isFolder) {
|
||||
this.$store.commit('explorer/toggleOpenNode', id);
|
||||
store.commit('explorer/toggleOpenNode', id);
|
||||
} else {
|
||||
this.$store.commit('file/setCurrentId', id);
|
||||
store.commit('file/setCurrentId', id);
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
async submitNewChild(cancel) {
|
||||
const { newChildNode } = this.$store.state.explorer;
|
||||
const { newChildNode } = store.state.explorer;
|
||||
if (!cancel && !newChildNode.isNil && newChildNode.item.name) {
|
||||
try {
|
||||
if (newChildNode.isFolder) {
|
||||
@ -111,10 +112,10 @@ export default {
|
||||
// Cancel
|
||||
}
|
||||
}
|
||||
this.$store.commit('explorer/setNewItem', null);
|
||||
store.commit('explorer/setNewItem', null);
|
||||
},
|
||||
async submitEdit(cancel) {
|
||||
const { item } = this.$store.getters['explorer/editingNode'];
|
||||
const { item } = store.getters['explorer/editingNode'];
|
||||
const value = this.editingValue;
|
||||
this.setEditingId(null);
|
||||
if (!cancel && item.id && value) {
|
||||
@ -133,14 +134,14 @@ export default {
|
||||
evt.preventDefault();
|
||||
return;
|
||||
}
|
||||
this.$store.commit('explorer/setDragSourceId', this.node.item.id);
|
||||
store.commit('explorer/setDragSourceId', this.node.item.id);
|
||||
// Fix for Firefox
|
||||
// See https://stackoverflow.com/a/3977637/1333165
|
||||
evt.dataTransfer.setData('Text', '');
|
||||
},
|
||||
onDrop() {
|
||||
const sourceNode = this.$store.getters['explorer/dragSourceNode'];
|
||||
const targetNode = this.$store.getters['explorer/dragTargetNodeFolder'];
|
||||
const sourceNode = store.getters['explorer/dragSourceNode'];
|
||||
const targetNode = store.getters['explorer/dragTargetNodeFolder'];
|
||||
this.setDragTarget();
|
||||
if (!sourceNode.isNil
|
||||
&& !targetNode.isNil
|
||||
@ -156,7 +157,7 @@ export default {
|
||||
if (this.select(undefined, false)) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
const item = await this.$store.dispatch('contextMenu/open', {
|
||||
const item = await store.dispatch('contextMenu/open', {
|
||||
coordinates: {
|
||||
left: evt.clientX,
|
||||
top: evt.clientY,
|
||||
|
@ -223,7 +223,7 @@ export default {
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$store.commit('findReplace/setType');
|
||||
store.commit('findReplace/setType');
|
||||
},
|
||||
onEscape() {
|
||||
editorSvc.clEditor.focus();
|
||||
@ -260,7 +260,7 @@ export default {
|
||||
this.onKeyup = (evt) => {
|
||||
if (evt.which === 27) {
|
||||
// Esc key
|
||||
this.$store.commit('findReplace/setType');
|
||||
store.commit('findReplace/setType');
|
||||
}
|
||||
};
|
||||
window.addEventListener('keyup', this.onKeyup);
|
||||
|
@ -63,6 +63,7 @@ import CurrentDiscussion from './gutters/CurrentDiscussion';
|
||||
import FindReplace from './FindReplace';
|
||||
import editorSvc from '../services/editorSvc';
|
||||
import markdownConversionSvc from '../services/markdownConversionSvc';
|
||||
import store from '../store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -96,7 +97,7 @@ export default {
|
||||
'layoutSettings',
|
||||
]),
|
||||
showFindReplace() {
|
||||
return !!this.$store.state.findReplace.type;
|
||||
return !!store.state.findReplace.type;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<div class="modal" v-if="config" @keydown.esc="onEscape" @keydown.tab="onTab" @focusin="onFocusInOut" @focusout="onFocusInOut">
|
||||
<div class="modal__sponsor-banner" v-if="!isSponsor">
|
||||
StackEdit is <a class="not-tabbable" target="_blank" href="https://github.com/benweet/stackedit/">open source</a>, please consider
|
||||
<a class="not-tabbable" href="javascript:void(0)" @click="sponsor">sponsoring</a> for just $5.
|
||||
</div>
|
||||
<component v-if="currentModalComponent" :is="currentModalComponent"></component>
|
||||
<modal-inner v-else aria-label="Dialog">
|
||||
<div class="modal__content" v-html="simpleModal.contentHtml(config)"></div>
|
||||
@ -15,6 +19,10 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import simpleModals from '../data/simpleModals';
|
||||
import editorSvc from '../services/editorSvc';
|
||||
import syncSvc from '../services/syncSvc';
|
||||
import googleHelper from '../services/providers/helpers/googleHelper';
|
||||
import store from '../store';
|
||||
|
||||
import ModalInner from './modals/common/ModalInner';
|
||||
import FilePropertiesModal from './modals/FilePropertiesModal';
|
||||
import SettingsModal from './modals/SettingsModal';
|
||||
@ -46,6 +54,11 @@ import GithubWorkspaceModal from './modals/providers/GithubWorkspaceModal';
|
||||
import GithubPublishModal from './modals/providers/GithubPublishModal';
|
||||
import GistSyncModal from './modals/providers/GistSyncModal';
|
||||
import GistPublishModal from './modals/providers/GistPublishModal';
|
||||
import GitlabAccountModal from './modals/providers/GitlabAccountModal';
|
||||
import GitlabOpenModal from './modals/providers/GitlabOpenModal';
|
||||
import GitlabPublishModal from './modals/providers/GitlabPublishModal';
|
||||
import GitlabSaveModal from './modals/providers/GitlabSaveModal';
|
||||
import GitlabWorkspaceModal from './modals/providers/GitlabWorkspaceModal';
|
||||
import WordpressPublishModal from './modals/providers/WordpressPublishModal';
|
||||
import BloggerPublishModal from './modals/providers/BloggerPublishModal';
|
||||
import BloggerPagePublishModal from './modals/providers/BloggerPagePublishModal';
|
||||
@ -54,7 +67,7 @@ import ZendeskPublishModal from './modals/providers/ZendeskPublishModal';
|
||||
import CouchdbWorkspaceModal from './modals/providers/CouchdbWorkspaceModal';
|
||||
import CouchdbCredentialsModal from './modals/providers/CouchdbCredentialsModal';
|
||||
|
||||
const getTabbables = container => container.querySelectorAll('a[href], button, .textfield')
|
||||
const getTabbables = container => container.querySelectorAll('a[href], button, .textfield, input[type=checkbox]')
|
||||
// Filter enabled and visible element
|
||||
.cl_filter(el => !el.disabled && el.offsetParent !== null && !el.classList.contains('not-tabbable'));
|
||||
|
||||
@ -90,6 +103,11 @@ export default {
|
||||
GithubPublishModal,
|
||||
GistSyncModal,
|
||||
GistPublishModal,
|
||||
GitlabAccountModal,
|
||||
GitlabOpenModal,
|
||||
GitlabPublishModal,
|
||||
GitlabSaveModal,
|
||||
GitlabWorkspaceModal,
|
||||
WordpressPublishModal,
|
||||
BloggerPublishModal,
|
||||
BloggerPagePublishModal,
|
||||
@ -99,6 +117,9 @@ export default {
|
||||
CouchdbCredentialsModal,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'isSponsor',
|
||||
]),
|
||||
...mapGetters('modal', [
|
||||
'config',
|
||||
]),
|
||||
@ -118,6 +139,19 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async sponsor() {
|
||||
try {
|
||||
if (!store.getters['workspace/sponsorToken']) {
|
||||
// User has to sign in
|
||||
await store.dispatch('modal/open', 'signInForSponsorship');
|
||||
await googleHelper.signin();
|
||||
syncSvc.requestSync();
|
||||
}
|
||||
if (!store.getters.isSponsor) {
|
||||
await store.dispatch('modal/open', 'sponsor');
|
||||
}
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
onEscape() {
|
||||
this.config.reject();
|
||||
editorSvc.clEditor.focus();
|
||||
@ -135,25 +169,17 @@ export default {
|
||||
}
|
||||
},
|
||||
onFocusInOut(evt) {
|
||||
const isFocusIn = evt.type === 'focusin';
|
||||
if (evt.target.parentNode && evt.target.parentNode.parentNode) {
|
||||
const { parentNode } = evt.target;
|
||||
if (parentNode && parentNode.parentNode) {
|
||||
// Focus effect
|
||||
if (evt.target.parentNode.classList.contains('form-entry__field')
|
||||
&& evt.target.parentNode.parentNode.classList.contains('form-entry')) {
|
||||
evt.target.parentNode.parentNode.classList.toggle('form-entry--focused', isFocusIn);
|
||||
if (parentNode.classList.contains('form-entry__field')
|
||||
&& parentNode.parentNode.classList.contains('form-entry')) {
|
||||
parentNode.parentNode.classList.toggle(
|
||||
'form-entry--focused',
|
||||
evt.type === 'focusin',
|
||||
);
|
||||
}
|
||||
}
|
||||
if (isFocusIn && this.config) {
|
||||
const modalInner = this.$el.querySelector('.modal__inner-2');
|
||||
let { target } = evt;
|
||||
while (target) {
|
||||
if (target === modalInner) {
|
||||
return;
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
this.config.reject();
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
@ -188,6 +214,18 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.modal__sponsor-banner {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
color: darken($error-color, 10%);
|
||||
background-color: transparentize(lighten($error-color, 33%), 0.1);
|
||||
font-size: 0.9em;
|
||||
line-height: 1.33;
|
||||
text-align: center;
|
||||
padding: 0.25em 1em;
|
||||
}
|
||||
|
||||
.modal__inner-1 {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
@ -291,7 +329,7 @@ export default {
|
||||
.form-entry__label {
|
||||
display: block;
|
||||
font-size: 0.9rem;
|
||||
color: #a0a0a0;
|
||||
color: #808080;
|
||||
|
||||
.form-entry--focused & {
|
||||
color: darken($link-color, 10%);
|
||||
@ -307,17 +345,19 @@ export default {
|
||||
}
|
||||
|
||||
.form-entry__field {
|
||||
border: 1px solid #d8d8d8;
|
||||
border: 1px solid #b0b0b0;
|
||||
border-radius: $border-radius-base;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.form-entry--focused & {
|
||||
border-color: $link-color;
|
||||
box-shadow: 0 0 0 2.5px transparentize($link-color, 0.67);
|
||||
}
|
||||
|
||||
.form-entry--error & {
|
||||
border-color: $error-color;
|
||||
box-shadow: 0 0 0 2.5px transparentize($error-color, 0.67);
|
||||
}
|
||||
}
|
||||
|
||||
@ -353,7 +393,7 @@ export default {
|
||||
|
||||
.form-entry__info {
|
||||
font-size: 0.75em;
|
||||
opacity: 0.5;
|
||||
opacity: 0.67;
|
||||
line-height: 1.4;
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
|
@ -119,11 +119,11 @@ export default {
|
||||
}));
|
||||
},
|
||||
isSyncPossible() {
|
||||
return this.$store.getters['workspace/syncToken'] ||
|
||||
this.$store.getters['syncLocation/current'].length;
|
||||
return store.getters['workspace/syncToken'] ||
|
||||
store.getters['syncLocation/current'].length;
|
||||
},
|
||||
showSpinner() {
|
||||
return !this.$store.state.queue.isEmpty;
|
||||
return !store.state.queue.isEmpty;
|
||||
},
|
||||
titleWidth() {
|
||||
if (!this.mounted) {
|
||||
@ -152,7 +152,7 @@ export default {
|
||||
return result;
|
||||
},
|
||||
editCancelTrigger() {
|
||||
const current = this.$store.getters['file/current'];
|
||||
const current = store.getters['file/current'];
|
||||
return utils.serializeObject([
|
||||
current.id,
|
||||
current.name,
|
||||
@ -187,7 +187,7 @@ export default {
|
||||
}
|
||||
},
|
||||
pagedownClick(name) {
|
||||
if (this.$store.getters['content/isCurrentEditable']) {
|
||||
if (store.getters['content/isCurrentEditable']) {
|
||||
editorSvc.pagedownEditor.uiManager.doClick(name);
|
||||
}
|
||||
},
|
||||
@ -197,11 +197,11 @@ export default {
|
||||
this.titleInputElt.setSelectionRange(0, this.titleInputElt.value.length);
|
||||
} else {
|
||||
const title = this.title.trim();
|
||||
this.title = this.$store.getters['file/current'].name;
|
||||
this.title = store.getters['file/current'].name;
|
||||
if (title) {
|
||||
try {
|
||||
await workspaceSvc.storeItem({
|
||||
...this.$store.getters['file/current'],
|
||||
...store.getters['file/current'],
|
||||
name: title,
|
||||
});
|
||||
} catch (e) {
|
||||
|
@ -21,6 +21,7 @@
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import CommentList from './gutters/CommentList';
|
||||
import PreviewNewDiscussionButton from './gutters/PreviewNewDiscussionButton';
|
||||
import store from '../store';
|
||||
|
||||
const appUri = `${window.location.protocol}//${window.location.host}`;
|
||||
|
||||
@ -84,11 +85,11 @@ export default {
|
||||
previewElt.addEventListener('mouseover', onDiscussionEvt(classToggler(true)));
|
||||
previewElt.addEventListener('mouseout', onDiscussionEvt(classToggler(false)));
|
||||
previewElt.addEventListener('click', onDiscussionEvt((discussionId) => {
|
||||
this.$store.commit('discussion/setCurrentDiscussionId', discussionId);
|
||||
store.commit('discussion/setCurrentDiscussionId', discussionId);
|
||||
}));
|
||||
|
||||
this.$watch(
|
||||
() => this.$store.state.discussion.currentDiscussionId,
|
||||
() => store.state.discussion.currentDiscussionId,
|
||||
(discussionId, oldDiscussionId) => {
|
||||
if (oldDiscussionId) {
|
||||
previewElt.querySelectorAll(`.discussion-preview-highlighting--${oldDiscussionId}`)
|
||||
|
@ -44,6 +44,7 @@ import ImportMenu from './menus/ImportMenu';
|
||||
import MoreMenu from './menus/MoreMenu';
|
||||
import markdownSample from '../data/markdownSample.md';
|
||||
import markdownConversionSvc from '../services/markdownConversionSvc';
|
||||
import store from '../store';
|
||||
|
||||
const panelNames = {
|
||||
menu: 'Menu',
|
||||
@ -75,10 +76,10 @@ export default {
|
||||
}),
|
||||
computed: {
|
||||
panel() {
|
||||
if (this.$store.state.light) {
|
||||
if (store.state.light) {
|
||||
return null; // No menu in light mode
|
||||
}
|
||||
const result = this.$store.getters['data/layoutSettings'].sideBarPanel;
|
||||
const result = store.getters['data/layoutSettings'].sideBarPanel;
|
||||
return panelNames[result] ? result : 'menu';
|
||||
},
|
||||
panelName() {
|
||||
@ -173,7 +174,7 @@ export default {
|
||||
padding: 10px;
|
||||
margin: -10px -10px 10px;
|
||||
background-color: $info-bg;
|
||||
font-size: 0.9em;
|
||||
font-size: 0.95em;
|
||||
|
||||
p {
|
||||
margin: 10px;
|
||||
|
@ -51,6 +51,7 @@
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
import store from '../store';
|
||||
|
||||
const steps = [
|
||||
'welcome',
|
||||
@ -106,7 +107,7 @@ export default {
|
||||
});
|
||||
},
|
||||
finish() {
|
||||
this.$store.dispatch('data/patchLayoutSettings', {
|
||||
store.dispatch('data/patchLayoutSettings', {
|
||||
welcomeTourFinished: true,
|
||||
});
|
||||
},
|
||||
@ -116,7 +117,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.$watch(
|
||||
() => this.$store.getters['layout/styles'],
|
||||
() => store.getters['layout/styles'],
|
||||
() => this.updatePositions(),
|
||||
{ immediate: true },
|
||||
);
|
||||
|
@ -5,16 +5,22 @@
|
||||
|
||||
<script>
|
||||
import userSvc from '../services/userSvc';
|
||||
import store from '../store';
|
||||
|
||||
export default {
|
||||
props: ['userId'],
|
||||
computed: {
|
||||
url() {
|
||||
userSvc.getInfo(this.userId);
|
||||
const userInfo = this.$store.state.userInfo.itemsById[this.userId];
|
||||
const userInfo = store.state.userInfo.itemsById[this.userId];
|
||||
return userInfo && userInfo.imageUrl && `url('${userInfo.imageUrl}')`;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
userId: {
|
||||
handler: userId => userSvc.getInfo(userId),
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -4,15 +4,21 @@
|
||||
|
||||
<script>
|
||||
import userSvc from '../services/userSvc';
|
||||
import store from '../store';
|
||||
|
||||
export default {
|
||||
props: ['userId'],
|
||||
computed: {
|
||||
name() {
|
||||
userSvc.getInfo(this.userId);
|
||||
const userInfo = this.$store.state.userInfo.itemsById[this.userId];
|
||||
const userInfo = store.state.userInfo.itemsById[this.userId];
|
||||
return userInfo ? userInfo.name : 'Someone';
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
userId: {
|
||||
handler: userId => userSvc.getInfo(userId),
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -27,6 +27,7 @@ import UserImage from '../UserImage';
|
||||
import UserName from '../UserName';
|
||||
import editorSvc from '../../services/editorSvc';
|
||||
import htmlSanitizer from '../../libs/htmlSanitizer';
|
||||
import store from '../../store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -36,8 +37,8 @@ export default {
|
||||
props: ['comment'],
|
||||
computed: {
|
||||
showReply() {
|
||||
return this.comment === this.$store.getters['discussion/currentDiscussionLastComment'] &&
|
||||
!this.$store.state.discussion.isCommenting;
|
||||
return this.comment === store.getters['discussion/currentDiscussionLastComment'] &&
|
||||
!store.state.discussion.isCommenting;
|
||||
},
|
||||
text() {
|
||||
return htmlSanitizer.sanitizeHtml(editorSvc.converter.render(this.comment.text));
|
||||
@ -49,8 +50,8 @@ export default {
|
||||
]),
|
||||
async removeComment() {
|
||||
try {
|
||||
await this.$store.dispatch('modal/open', 'commentDeletion');
|
||||
this.$store.dispatch('discussion/cleanCurrentFile', { filterComment: this.comment });
|
||||
await store.dispatch('modal/open', 'commentDeletion');
|
||||
store.dispatch('discussion/cleanCurrentFile', { filterComment: this.comment });
|
||||
} catch (e) {
|
||||
// Cancel
|
||||
}
|
||||
@ -59,7 +60,7 @@ export default {
|
||||
mounted() {
|
||||
const isSticky = this.$el.parentNode.classList.contains('sticky-comment');
|
||||
if (isSticky) {
|
||||
const commentId = this.$store.getters['discussion/currentDiscussionLastCommentId'];
|
||||
const commentId = store.getters['discussion/currentDiscussionLastCommentId'];
|
||||
const scrollerElt = this.$el.querySelector('.comment__text-inner');
|
||||
|
||||
let scrollerMirrorElt;
|
||||
|
@ -13,6 +13,7 @@ import { mapState, mapGetters, mapMutations } from 'vuex';
|
||||
import Comment from './Comment';
|
||||
import NewComment from './NewComment';
|
||||
import editorSvc from '../../services/editorSvc';
|
||||
import store from '../../store';
|
||||
import utils from '../../services/utils';
|
||||
|
||||
export default {
|
||||
@ -63,7 +64,7 @@ export default {
|
||||
'setCurrentDiscussionId',
|
||||
]),
|
||||
updateTops() {
|
||||
const layoutSettings = this.$store.getters['data/layoutSettings'];
|
||||
const layoutSettings = store.getters['data/layoutSettings'];
|
||||
const minTop = -2;
|
||||
let minCommentTop = minTop;
|
||||
const getTop = (discussion, commentElt1, commentElt2, isCurrent) => {
|
||||
@ -126,15 +127,15 @@ export default {
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const layoutSettings = this.$store.getters['data/layoutSettings'];
|
||||
const layoutSettings = store.getters['data/layoutSettings'];
|
||||
this.scrollerElt = layoutSettings.showEditor
|
||||
? editorSvc.editorElt.parentNode
|
||||
: editorSvc.previewElt.parentNode;
|
||||
|
||||
this.updateSticky = () => {
|
||||
const commitIfDifferent = (value) => {
|
||||
if (this.$store.state.discussion.stickyComment !== value) {
|
||||
this.$store.commit('discussion/setStickyComment', value);
|
||||
if (store.state.discussion.stickyComment !== value) {
|
||||
store.commit('discussion/setStickyComment', value);
|
||||
}
|
||||
};
|
||||
let height = 0;
|
||||
|
@ -33,6 +33,7 @@ import editorSvc from '../../services/editorSvc';
|
||||
import animationSvc from '../../services/animationSvc';
|
||||
import markdownConversionSvc from '../../services/markdownConversionSvc';
|
||||
import StickyComment from './StickyComment';
|
||||
import store from '../../store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -72,7 +73,7 @@ export default {
|
||||
]),
|
||||
goToDiscussion(discussionId = this.currentDiscussionId) {
|
||||
this.setCurrentDiscussionId(discussionId);
|
||||
const layoutSettings = this.$store.getters['data/layoutSettings'];
|
||||
const layoutSettings = store.getters['data/layoutSettings'];
|
||||
const discussion = this.currentFileDiscussions[discussionId];
|
||||
const coordinates = layoutSettings.showEditor
|
||||
? editorSvc.clEditor.selectionMgr.getCoordinates(discussion.end)
|
||||
@ -98,8 +99,8 @@ export default {
|
||||
},
|
||||
async removeDiscussion() {
|
||||
try {
|
||||
await this.$store.dispatch('modal/open', 'discussionDeletion');
|
||||
this.$store.dispatch('discussion/cleanCurrentFile', {
|
||||
await store.dispatch('modal/open', 'discussionDeletion');
|
||||
store.dispatch('discussion/cleanCurrentFile', {
|
||||
filterDiscussion: this.currentDiscussion,
|
||||
});
|
||||
} catch (e) {
|
||||
|
@ -7,6 +7,7 @@
|
||||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import editorSvc from '../../services/editorSvc';
|
||||
import store from '../../store';
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
@ -23,7 +24,7 @@ export default {
|
||||
let offset;
|
||||
// Show the button if content is not a revision and has the focus
|
||||
if (
|
||||
!this.$store.state.content.revisionContent &&
|
||||
!store.state.content.revisionContent &&
|
||||
editorSvc.clEditor.selectionMgr.hasFocus()
|
||||
) {
|
||||
this.selection = editorSvc.getTrimmedSelection();
|
||||
|
@ -28,6 +28,8 @@ import cledit from '../../services/editor/cledit';
|
||||
import editorSvc from '../../services/editorSvc';
|
||||
import markdownConversionSvc from '../../services/markdownConversionSvc';
|
||||
import utils from '../../services/utils';
|
||||
import userSvc from '../../services/userSvc';
|
||||
import store from '../../store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -36,8 +38,10 @@ export default {
|
||||
computed: {
|
||||
...mapGetters('workspace', [
|
||||
'loginToken',
|
||||
'userId',
|
||||
]),
|
||||
userId() {
|
||||
return userSvc.getCurrentUserId();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('discussion', [
|
||||
@ -47,13 +51,13 @@ export default {
|
||||
'cancelNewComment',
|
||||
]),
|
||||
addComment() {
|
||||
const text = this.$store.state.discussion.newCommentText.trim();
|
||||
const text = store.state.discussion.newCommentText.trim();
|
||||
if (text.length) {
|
||||
if (text.length > 2000) {
|
||||
this.$store.dispatch('notification/error', 'Comment is too long.');
|
||||
store.dispatch('notification/error', 'Comment is too long.');
|
||||
} else {
|
||||
// Create comment
|
||||
const discussionId = this.$store.state.discussion.currentDiscussionId;
|
||||
const discussionId = store.state.discussion.currentDiscussionId;
|
||||
const comment = {
|
||||
discussionId,
|
||||
sub: this.userId,
|
||||
@ -62,20 +66,20 @@ export default {
|
||||
};
|
||||
const patch = {
|
||||
comments: {
|
||||
...this.$store.getters['content/current'].comments,
|
||||
...store.getters['content/current'].comments,
|
||||
[utils.uid()]: comment,
|
||||
},
|
||||
};
|
||||
// Create discussion
|
||||
if (discussionId === this.$store.state.discussion.newDiscussionId) {
|
||||
if (discussionId === store.state.discussion.newDiscussionId) {
|
||||
patch.discussions = {
|
||||
...this.$store.getters['content/current'].discussions,
|
||||
[discussionId]: this.$store.getters['discussion/newDiscussion'],
|
||||
...store.getters['content/current'].discussions,
|
||||
[discussionId]: store.getters['discussion/newDiscussion'],
|
||||
};
|
||||
}
|
||||
this.$store.dispatch('content/patchCurrent', patch);
|
||||
this.$store.commit('discussion/setNewCommentText');
|
||||
this.$store.commit('discussion/setIsCommenting');
|
||||
store.dispatch('content/patchCurrent', patch);
|
||||
store.commit('discussion/setNewCommentText');
|
||||
store.commit('discussion/setIsCommenting');
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -91,28 +95,28 @@ export default {
|
||||
),
|
||||
sectionParser: text => markdownConversionSvc
|
||||
.parseSections(editorSvc.converter, text).sections,
|
||||
content: this.$store.state.discussion.newCommentText,
|
||||
selectionStart: this.$store.state.discussion.newCommentSelection.start,
|
||||
selectionEnd: this.$store.state.discussion.newCommentSelection.end,
|
||||
content: store.state.discussion.newCommentText,
|
||||
selectionStart: store.state.discussion.newCommentSelection.start,
|
||||
selectionEnd: store.state.discussion.newCommentSelection.end,
|
||||
getCursorFocusRatio: () => 0.2,
|
||||
});
|
||||
clEditor.on('focus', () => this.setNewCommentFocus(true));
|
||||
|
||||
// Save typed content and selection
|
||||
clEditor.on('contentChanged', value =>
|
||||
this.$store.commit('discussion/setNewCommentText', value));
|
||||
store.commit('discussion/setNewCommentText', value));
|
||||
clEditor.selectionMgr.on('selectionChanged', (start, end) =>
|
||||
this.$store.commit('discussion/setNewCommentSelection', {
|
||||
store.commit('discussion/setNewCommentSelection', {
|
||||
start, end,
|
||||
}));
|
||||
|
||||
const isSticky = this.$el.parentNode.classList.contains('sticky-comment');
|
||||
const isVisible = () => isSticky || this.$store.state.discussion.stickyComment === null;
|
||||
const isVisible = () => isSticky || store.state.discussion.stickyComment === null;
|
||||
|
||||
this.$watch(
|
||||
() => this.$store.state.discussion.currentDiscussionId,
|
||||
() => store.state.discussion.currentDiscussionId,
|
||||
() => this.$nextTick(() => {
|
||||
if (isVisible() && this.$store.state.discussion.newCommentFocus) {
|
||||
if (isVisible() && store.state.discussion.newCommentFocus) {
|
||||
clEditor.focus();
|
||||
}
|
||||
}),
|
||||
@ -139,11 +143,11 @@ export default {
|
||||
(visible) => {
|
||||
clEditor.toggleEditable(visible);
|
||||
if (visible) {
|
||||
const text = this.$store.state.discussion.newCommentText;
|
||||
const text = store.state.discussion.newCommentText;
|
||||
clEditor.setContent(text);
|
||||
const selection = this.$store.state.discussion.newCommentSelection;
|
||||
const selection = store.state.discussion.newCommentSelection;
|
||||
clEditor.selectionMgr.setSelectionStartEnd(selection.start, selection.end);
|
||||
if (this.$store.state.discussion.newCommentFocus) {
|
||||
if (store.state.discussion.newCommentFocus) {
|
||||
clEditor.focus();
|
||||
}
|
||||
}
|
||||
@ -151,7 +155,7 @@ export default {
|
||||
{ immediate: true },
|
||||
);
|
||||
this.$watch(
|
||||
() => this.$store.state.discussion.newCommentText,
|
||||
() => store.state.discussion.newCommentText,
|
||||
newCommentText => clEditor.setContent(newCommentText),
|
||||
);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import editorSvc from '../../services/editorSvc';
|
||||
import store from '../../store';
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
@ -23,7 +24,7 @@ export default {
|
||||
let offset;
|
||||
// Show the button if content is not a revision and preview selection is not empty
|
||||
if (
|
||||
!this.$store.state.content.revisionContent &&
|
||||
!store.state.content.revisionContent &&
|
||||
editorSvc.previewSelectionRange
|
||||
) {
|
||||
this.selection = editorSvc.getTrimmedSelection();
|
||||
@ -45,7 +46,7 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
editorSvc.$on('previewSelectionRange', () => this.checkSelection());
|
||||
this.$watch(
|
||||
() => this.$store.getters['layout/styles'].previewWidth,
|
||||
() => store.getters['layout/styles'].previewWidth,
|
||||
() => this.checkSelection(),
|
||||
);
|
||||
this.checkSelection();
|
||||
|
@ -27,6 +27,7 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import MenuEntry from './common/MenuEntry';
|
||||
import exportSvc from '../../services/exportSvc';
|
||||
import store from '../../store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -35,20 +36,20 @@ export default {
|
||||
computed: mapGetters(['isSponsor']),
|
||||
methods: {
|
||||
exportMarkdown() {
|
||||
const currentFile = this.$store.getters['file/current'];
|
||||
const currentFile = store.getters['file/current'];
|
||||
return exportSvc.exportToDisk(currentFile.id, 'md')
|
||||
.catch(() => { /* Cancel */ });
|
||||
},
|
||||
exportHtml() {
|
||||
return this.$store.dispatch('modal/open', 'htmlExport')
|
||||
return store.dispatch('modal/open', 'htmlExport')
|
||||
.catch(() => { /* Cancel */ });
|
||||
},
|
||||
exportPdf() {
|
||||
return this.$store.dispatch('modal/open', 'pdfExport')
|
||||
return store.dispatch('modal/open', 'pdfExport')
|
||||
.catch(() => { /* Cancel */ });
|
||||
},
|
||||
exportPandoc() {
|
||||
return this.$store.dispatch('modal/open', 'pandocExport')
|
||||
return store.dispatch('modal/open', 'pandocExport')
|
||||
.catch(() => { /* Cancel */ });
|
||||
},
|
||||
},
|
||||
|
@ -55,6 +55,7 @@ import PreviewClassApplier from '../common/PreviewClassApplier';
|
||||
import utils from '../../services/utils';
|
||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
import syncSvc from '../../services/syncSvc';
|
||||
import store from '../../store';
|
||||
|
||||
let editorClassAppliers = [];
|
||||
let previewClassAppliers = [];
|
||||
@ -102,14 +103,14 @@ export default {
|
||||
return providerRegistry.providersById[this.syncLocation.providerId].name;
|
||||
},
|
||||
currentFileName() {
|
||||
return this.$store.getters['file/current'].name;
|
||||
return store.getters['file/current'].name;
|
||||
},
|
||||
historyContext() {
|
||||
const { syncLocation } = this;
|
||||
if (syncLocation) {
|
||||
const provider = providerRegistry.providersById[syncLocation.providerId];
|
||||
const token = provider.getToken(syncLocation);
|
||||
const fileId = this.$store.getters['file/current'].id;
|
||||
const fileId = store.getters['file/current'].id;
|
||||
const contentId = `${fileId}/content`;
|
||||
const historyContext = {
|
||||
token,
|
||||
@ -171,7 +172,7 @@ export default {
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$store.dispatch('data/setSideBarPanel', 'menu');
|
||||
store.dispatch('data/setSideBarPanel', 'menu');
|
||||
},
|
||||
showMore() {
|
||||
this.showCount += pageSize;
|
||||
@ -182,7 +183,7 @@ export default {
|
||||
const historyContext = utils.deepCopy(this.historyContext);
|
||||
if (historyContext) {
|
||||
const provider = providerRegistry.providersById[this.syncLocation.providerId];
|
||||
revisionContentPromise = new Promise((resolve, reject) => this.$store.dispatch(
|
||||
revisionContentPromise = new Promise((resolve, reject) => store.dispatch(
|
||||
'queue/enqueue',
|
||||
() => provider.getFileRevisionContent({
|
||||
...historyContext,
|
||||
@ -192,14 +193,14 @@ export default {
|
||||
));
|
||||
revisionContentPromises[revision.id] = revisionContentPromise;
|
||||
revisionContentPromise.catch((err) => {
|
||||
this.$store.dispatch('notification/error', err);
|
||||
store.dispatch('notification/error', err);
|
||||
revisionContentPromises[revision.id] = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (revisionContentPromise) {
|
||||
revisionContentPromise.then(revisionContent =>
|
||||
this.$store.dispatch('content/setRevisionContent', revisionContent));
|
||||
store.dispatch('content/setRevisionContent', revisionContent));
|
||||
}
|
||||
},
|
||||
refreshHighlighters() {
|
||||
@ -256,14 +257,14 @@ export default {
|
||||
cachedHistoryContextHash = this.historyContextHash;
|
||||
revisionContentPromises = {};
|
||||
const provider = providerRegistry.providersById[this.syncLocation.providerId];
|
||||
revisionsPromise = new Promise((resolve, reject) => this.$store.dispatch(
|
||||
revisionsPromise = new Promise((resolve, reject) => store.dispatch(
|
||||
'queue/enqueue',
|
||||
() => provider
|
||||
.listFileRevisions(historyContext)
|
||||
.then(resolve, reject),
|
||||
))
|
||||
.catch((err) => {
|
||||
this.$store.dispatch('notification/error', err);
|
||||
store.dispatch('notification/error', err);
|
||||
cachedHistoryContextHash = null;
|
||||
return [];
|
||||
});
|
||||
@ -282,7 +283,7 @@ export default {
|
||||
revisions(revisions) {
|
||||
const { historyContext } = this;
|
||||
if (historyContext) {
|
||||
this.$store.dispatch(
|
||||
store.dispatch(
|
||||
'queue/enqueue',
|
||||
() => utils.awaitSequence(revisions, async (revision) => {
|
||||
// Make sure revisions and historyContext haven't changed
|
||||
|
@ -39,7 +39,7 @@ const readFile = file => new Promise((resolve) => {
|
||||
reader.onload = (e) => {
|
||||
const content = e.target.result;
|
||||
if (content.match(/\uFFFD/)) {
|
||||
this.$store.dispatch('notification/error', 'File is not readable.');
|
||||
store.dispatch('notification/error', 'File is not readable.');
|
||||
} else {
|
||||
resolve(content);
|
||||
}
|
||||
@ -60,7 +60,7 @@ export default {
|
||||
...Provider.parseContent(content),
|
||||
name: file.name,
|
||||
});
|
||||
this.$store.commit('file/setCurrentId', item.id);
|
||||
store.commit('file/setCurrentId', item.id);
|
||||
},
|
||||
async onImportHtml(evt) {
|
||||
const file = evt.target.files[0];
|
||||
@ -71,7 +71,7 @@ export default {
|
||||
...Provider.parseContent(turndownService.turndown(sanitizedContent)),
|
||||
name: file.name,
|
||||
});
|
||||
this.$store.commit('file/setCurrentId', item.id);
|
||||
store.commit('file/setCurrentId', item.id);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -23,6 +23,9 @@
|
||||
<span v-else-if="currentWorkspace.providerId === 'githubWorkspace'">
|
||||
<b>{{currentWorkspace.name}}</b> synced with a <a :href="workspaceLocationUrl" target="_blank">GitHub repo</a>.
|
||||
</span>
|
||||
<span v-else-if="currentWorkspace.providerId === 'gitlabWorkspace'">
|
||||
<b>{{currentWorkspace.name}}</b> synced with a <a :href="workspaceLocationUrl" target="_blank">GitLab project</a>.
|
||||
</span>
|
||||
</div>
|
||||
<div class="menu-entry menu-entry--info flex flex--row flex--align-center" v-else>
|
||||
<div class="menu-entry__icon menu-entry__icon--disabled">
|
||||
@ -38,18 +41,18 @@
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="setPanel('workspaces')">
|
||||
<icon-database slot="icon"></icon-database>
|
||||
<div><div class="menu-entry__label menu-entry__label--warning">new</div> Workspaces</div>
|
||||
<div><div class="menu-entry__label menu-entry__label--count" v-if="workspaceCount">{{workspaceCount}}</div> Workspaces</div>
|
||||
<span>Switch to another workspace.</span>
|
||||
</menu-entry>
|
||||
<hr>
|
||||
<menu-entry @click.native="setPanel('sync')">
|
||||
<icon-sync slot="icon"></icon-sync>
|
||||
<div>Synchronize</div>
|
||||
<div><div class="menu-entry__label menu-entry__label--count" v-if="syncLocationCount">{{syncLocationCount}}</div> Synchronize</div>
|
||||
<span>Sync your files in the Cloud.</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="setPanel('publish')">
|
||||
<icon-upload slot="icon"></icon-upload>
|
||||
<div>Publish</div>
|
||||
<div><div class="menu-entry__label menu-entry__label--count" v-if="publishLocationCount">{{publishLocationCount}}</div>Publish</div>
|
||||
<span>Export your files to the web.</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="setPanel('history')">
|
||||
@ -98,6 +101,8 @@ import providerRegistry from '../../services/providers/common/providerRegistry';
|
||||
import UserImage from '../UserImage';
|
||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
import syncSvc from '../../services/syncSvc';
|
||||
import userSvc from '../../services/userSvc';
|
||||
import store from '../../store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -109,12 +114,23 @@ export default {
|
||||
'currentWorkspace',
|
||||
'syncToken',
|
||||
'loginToken',
|
||||
'userId',
|
||||
]),
|
||||
userId() {
|
||||
return userSvc.getCurrentUserId();
|
||||
},
|
||||
workspaceLocationUrl() {
|
||||
const provider = providerRegistry.providersById[this.currentWorkspace.providerId];
|
||||
return provider.getWorkspaceLocationUrl(this.currentWorkspace);
|
||||
},
|
||||
workspaceCount() {
|
||||
return Object.keys(store.getters['workspace/workspacesById']).length;
|
||||
},
|
||||
syncLocationCount() {
|
||||
return Object.keys(store.getters['syncLocation/currentWithWorkspaceSyncLocation']).length;
|
||||
},
|
||||
publishLocationCount() {
|
||||
return Object.keys(store.getters['publishLocation/current']).length;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('data', {
|
||||
@ -130,7 +146,7 @@ export default {
|
||||
},
|
||||
async fileProperties() {
|
||||
try {
|
||||
await this.$store.dispatch('modal/open', 'fileProperties');
|
||||
await store.dispatch('modal/open', 'fileProperties');
|
||||
} catch (e) {
|
||||
// Cancel
|
||||
}
|
||||
|
@ -45,6 +45,7 @@
|
||||
import MenuEntry from './common/MenuEntry';
|
||||
import backupSvc from '../../services/backupSvc';
|
||||
import utils from '../../services/utils';
|
||||
import store from '../../store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -52,7 +53,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
templateCount() {
|
||||
return Object.keys(this.$store.getters['data/allTemplatesById']).length;
|
||||
return Object.keys(store.getters['data/allTemplatesById']).length;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@ -63,7 +64,7 @@ export default {
|
||||
reader.onload = (e) => {
|
||||
const text = e.target.result;
|
||||
if (text.match(/\uFFFD/)) {
|
||||
this.$store.dispatch('notification/error', 'File is not readable.');
|
||||
store.dispatch('notification/error', 'File is not readable.');
|
||||
} else {
|
||||
backupSvc.importBackup(text);
|
||||
}
|
||||
@ -82,23 +83,23 @@ export default {
|
||||
},
|
||||
async settings() {
|
||||
try {
|
||||
const settings = await this.$store.dispatch('modal/open', 'settings');
|
||||
this.$store.dispatch('data/setSettings', settings);
|
||||
const settings = await store.dispatch('modal/open', 'settings');
|
||||
store.dispatch('data/setSettings', settings);
|
||||
} catch (e) {
|
||||
// Cancel
|
||||
}
|
||||
},
|
||||
async templates() {
|
||||
try {
|
||||
const { templates } = await this.$store.dispatch('modal/open', 'templates');
|
||||
this.$store.dispatch('data/setTemplatesById', templates);
|
||||
const { templates } = await store.dispatch('modal/open', 'templates');
|
||||
store.dispatch('data/setTemplatesById', templates);
|
||||
} catch (e) {
|
||||
// Cancel
|
||||
}
|
||||
},
|
||||
async reset() {
|
||||
try {
|
||||
await this.$store.dispatch('modal/open', 'reset');
|
||||
await store.dispatch('modal/open', 'reset');
|
||||
window.location.href = '#reset=true';
|
||||
window.location.reload();
|
||||
} catch (e) {
|
||||
@ -106,7 +107,7 @@ export default {
|
||||
}
|
||||
},
|
||||
about() {
|
||||
this.$store.dispatch('modal/open', 'about');
|
||||
store.dispatch('modal/open', 'about');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -21,39 +21,6 @@
|
||||
</menu-entry>
|
||||
</div>
|
||||
<hr>
|
||||
<div v-for="token in googleDriveTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishGoogleDrive(token)">
|
||||
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
|
||||
<div>Publish to Google Drive</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in dropboxTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishDropbox(token)">
|
||||
<icon-provider slot="icon" provider-id="dropbox"></icon-provider>
|
||||
<div>Publish to Dropbox</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in githubTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishGithub(token)">
|
||||
<icon-provider slot="icon" provider-id="github"></icon-provider>
|
||||
<div>Publish to GitHub</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="publishGist(token)">
|
||||
<icon-provider slot="icon" provider-id="gist"></icon-provider>
|
||||
<div>Publish to Gist</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in wordpressTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishWordpress(token)">
|
||||
<icon-provider slot="icon" provider-id="wordpress"></icon-provider>
|
||||
<div>Publish to WordPress</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in bloggerTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishBlogger(token)">
|
||||
<icon-provider slot="icon" provider-id="blogger"></icon-provider>
|
||||
@ -66,6 +33,46 @@
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in dropboxTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishDropbox(token)">
|
||||
<icon-provider slot="icon" provider-id="dropbox"></icon-provider>
|
||||
<div>Publish to Dropbox</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in githubTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishGist(token)">
|
||||
<icon-provider slot="icon" provider-id="gist"></icon-provider>
|
||||
<div>Publish to Gist</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="publishGithub(token)">
|
||||
<icon-provider slot="icon" provider-id="github"></icon-provider>
|
||||
<div>Publish to GitHub</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in gitlabTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishGitlab(token)">
|
||||
<icon-provider slot="icon" provider-id="gitlab"></icon-provider>
|
||||
<div>Publish to GitLab</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in googleDriveTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishGoogleDrive(token)">
|
||||
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
|
||||
<div>Publish to Google Drive</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in wordpressTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishWordpress(token)">
|
||||
<icon-provider slot="icon" provider-id="wordpress"></icon-provider>
|
||||
<div>Publish to WordPress</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in zendeskTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishZendesk(token)">
|
||||
<icon-provider slot="icon" provider-id="zendesk"></icon-provider>
|
||||
@ -74,9 +81,9 @@
|
||||
</menu-entry>
|
||||
</div>
|
||||
<hr>
|
||||
<menu-entry @click.native="addGoogleDriveAccount">
|
||||
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
|
||||
<span>Add Google Drive account</span>
|
||||
<menu-entry @click.native="addBloggerAccount">
|
||||
<icon-provider slot="icon" provider-id="blogger"></icon-provider>
|
||||
<span>Add Blogger account</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addDropboxAccount">
|
||||
<icon-provider slot="icon" provider-id="dropbox"></icon-provider>
|
||||
@ -86,14 +93,18 @@
|
||||
<icon-provider slot="icon" provider-id="github"></icon-provider>
|
||||
<span>Add GitHub account</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addGitlabAccount">
|
||||
<icon-provider slot="icon" provider-id="gitlab"></icon-provider>
|
||||
<span>Add GitLab account</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addGoogleDriveAccount">
|
||||
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
|
||||
<span>Add Google Drive account</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addWordpressAccount">
|
||||
<icon-provider slot="icon" provider-id="wordpress"></icon-provider>
|
||||
<span>Add WordPress account</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addBloggerAccount">
|
||||
<icon-provider slot="icon" provider-id="blogger"></icon-provider>
|
||||
<span>Add Blogger account</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addZendeskAccount">
|
||||
<icon-provider slot="icon" provider-id="zendesk"></icon-provider>
|
||||
<span>Add Zendesk account</span>
|
||||
@ -108,6 +119,7 @@ import MenuEntry from './common/MenuEntry';
|
||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
import dropboxHelper from '../../services/providers/helpers/dropboxHelper';
|
||||
import githubHelper from '../../services/providers/helpers/githubHelper';
|
||||
import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
|
||||
import wordpressHelper from '../../services/providers/helpers/wordpressHelper';
|
||||
import zendeskHelper from '../../services/providers/helpers/zendeskHelper';
|
||||
import publishSvc from '../../services/publishSvc';
|
||||
@ -145,33 +157,32 @@ export default {
|
||||
return Object.keys(this.publishLocations).length;
|
||||
},
|
||||
currentFileName() {
|
||||
return this.$store.getters['file/current'].name;
|
||||
},
|
||||
googleDriveTokens() {
|
||||
return tokensToArray(this.$store.getters['data/googleTokensBySub'], token => token.isDrive);
|
||||
},
|
||||
dropboxTokens() {
|
||||
return tokensToArray(this.$store.getters['data/dropboxTokensBySub']);
|
||||
},
|
||||
githubTokens() {
|
||||
return tokensToArray(this.$store.getters['data/githubTokensBySub']);
|
||||
},
|
||||
wordpressTokens() {
|
||||
return tokensToArray(this.$store.getters['data/wordpressTokensBySub']);
|
||||
return store.getters['file/current'].name;
|
||||
},
|
||||
bloggerTokens() {
|
||||
return tokensToArray(this.$store.getters['data/googleTokensBySub'], token => token.isBlogger);
|
||||
return tokensToArray(store.getters['data/googleTokensBySub'], token => token.isBlogger);
|
||||
},
|
||||
dropboxTokens() {
|
||||
return tokensToArray(store.getters['data/dropboxTokensBySub']);
|
||||
},
|
||||
githubTokens() {
|
||||
return tokensToArray(store.getters['data/githubTokensBySub']);
|
||||
},
|
||||
gitlabTokens() {
|
||||
return tokensToArray(store.getters['data/gitlabTokensBySub']);
|
||||
},
|
||||
googleDriveTokens() {
|
||||
return tokensToArray(store.getters['data/googleTokensBySub'], token => token.isDrive);
|
||||
},
|
||||
wordpressTokens() {
|
||||
return tokensToArray(store.getters['data/wordpressTokensBySub']);
|
||||
},
|
||||
zendeskTokens() {
|
||||
return tokensToArray(this.$store.getters['data/zendeskTokensBySub']);
|
||||
return tokensToArray(store.getters['data/zendeskTokensBySub']);
|
||||
},
|
||||
noToken() {
|
||||
return !this.googleDriveTokens.length
|
||||
&& !this.dropboxTokens.length
|
||||
&& !this.githubTokens.length
|
||||
&& !this.wordpressTokens.length
|
||||
&& !this.bloggerTokens.length
|
||||
&& !this.zendeskTokens.length;
|
||||
return Object.values(store.getters['data/tokensByType'])
|
||||
.every(tokens => !Object.keys(tokens).length);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@ -182,30 +193,7 @@ export default {
|
||||
},
|
||||
async managePublish() {
|
||||
try {
|
||||
await this.$store.dispatch('modal/open', 'publishManagement');
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addGoogleDriveAccount() {
|
||||
try {
|
||||
await this.$store.dispatch('modal/open', { type: 'googleDriveAccount' });
|
||||
await googleHelper.addDriveAccount(!store.getters['data/localSettings'].googleDriveRestrictedAccess);
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addDropboxAccount() {
|
||||
try {
|
||||
await this.$store.dispatch('modal/open', { type: 'dropboxAccount' });
|
||||
await dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess);
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addGithubAccount() {
|
||||
try {
|
||||
await this.$store.dispatch('modal/open', { type: 'githubAccount' });
|
||||
await githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess);
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addWordpressAccount() {
|
||||
try {
|
||||
await wordpressHelper.addAccount();
|
||||
await store.dispatch('modal/open', 'publishManagement');
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addBloggerAccount() {
|
||||
@ -213,19 +201,49 @@ export default {
|
||||
await googleHelper.addBloggerAccount();
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addDropboxAccount() {
|
||||
try {
|
||||
await store.dispatch('modal/open', { type: 'dropboxAccount' });
|
||||
await dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess);
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addGithubAccount() {
|
||||
try {
|
||||
await store.dispatch('modal/open', { type: 'githubAccount' });
|
||||
await githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess);
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addGitlabAccount() {
|
||||
try {
|
||||
const { serverUrl, applicationId } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
|
||||
await gitlabHelper.addAccount(serverUrl, applicationId);
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addGoogleDriveAccount() {
|
||||
try {
|
||||
await store.dispatch('modal/open', { type: 'googleDriveAccount' });
|
||||
await googleHelper.addDriveAccount(!store.getters['data/localSettings'].googleDriveRestrictedAccess);
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addWordpressAccount() {
|
||||
try {
|
||||
await wordpressHelper.addAccount();
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addZendeskAccount() {
|
||||
try {
|
||||
const { subdomain, clientId } = await this.$store.dispatch('modal/open', { type: 'zendeskAccount' });
|
||||
const { subdomain, clientId } = await store.dispatch('modal/open', { type: 'zendeskAccount' });
|
||||
await zendeskHelper.addAccount(subdomain, clientId);
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
publishGoogleDrive: publishModalOpener('googleDrivePublish'),
|
||||
publishBlogger: publishModalOpener('bloggerPublish'),
|
||||
publishBloggerPage: publishModalOpener('bloggerPagePublish'),
|
||||
publishDropbox: publishModalOpener('dropboxPublish'),
|
||||
publishGithub: publishModalOpener('githubPublish'),
|
||||
publishGist: publishModalOpener('gistPublish'),
|
||||
publishGitlab: publishModalOpener('gitlabPublish'),
|
||||
publishGoogleDrive: publishModalOpener('googleDrivePublish'),
|
||||
publishWordpress: publishModalOpener('wordpressPublish'),
|
||||
publishBlogger: publishModalOpener('bloggerPublish'),
|
||||
publishBloggerPage: publishModalOpener('bloggerPagePublish'),
|
||||
publishZendesk: publishModalOpener('zendeskPublish'),
|
||||
},
|
||||
};
|
||||
|
@ -21,18 +21,6 @@
|
||||
</menu-entry>
|
||||
</div>
|
||||
<hr>
|
||||
<div v-for="token in googleDriveTokens" :key="token.sub">
|
||||
<menu-entry @click.native="openGoogleDrive(token)">
|
||||
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
|
||||
<div>Open from Google Drive</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="saveGoogleDrive(token)">
|
||||
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
|
||||
<div>Save on Google Drive</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in dropboxTokens" :key="token.sub">
|
||||
<menu-entry @click.native="openDropbox(token)">
|
||||
<icon-provider slot="icon" provider-id="dropbox"></icon-provider>
|
||||
@ -62,11 +50,31 @@
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in gitlabTokens" :key="token.sub">
|
||||
<menu-entry @click.native="openGitlab(token)">
|
||||
<icon-provider slot="icon" provider-id="gitlab"></icon-provider>
|
||||
<div>Open from GitLab</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="saveGitlab(token)">
|
||||
<icon-provider slot="icon" provider-id="gitlab"></icon-provider>
|
||||
<div>Save on GitLab</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<div v-for="token in googleDriveTokens" :key="token.sub">
|
||||
<menu-entry @click.native="openGoogleDrive(token)">
|
||||
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
|
||||
<div>Open from Google Drive</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="saveGoogleDrive(token)">
|
||||
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
|
||||
<div>Save on Google Drive</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
<hr>
|
||||
<menu-entry @click.native="addGoogleDriveAccount">
|
||||
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
|
||||
<span>Add Google Drive account</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addDropboxAccount">
|
||||
<icon-provider slot="icon" provider-id="dropbox"></icon-provider>
|
||||
<span>Add Dropbox account</span>
|
||||
@ -75,6 +83,14 @@
|
||||
<icon-provider slot="icon" provider-id="github"></icon-provider>
|
||||
<span>Add GitHub account</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addGitlabAccount">
|
||||
<icon-provider slot="icon" provider-id="gitlab"></icon-provider>
|
||||
<span>Add GitLab account</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addGoogleDriveAccount">
|
||||
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
|
||||
<span>Add Google Drive account</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -85,9 +101,11 @@ import MenuEntry from './common/MenuEntry';
|
||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
import dropboxHelper from '../../services/providers/helpers/dropboxHelper';
|
||||
import githubHelper from '../../services/providers/helpers/githubHelper';
|
||||
import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
|
||||
import googleDriveProvider from '../../services/providers/googleDriveProvider';
|
||||
import dropboxProvider from '../../services/providers/dropboxProvider';
|
||||
import githubProvider from '../../services/providers/githubProvider';
|
||||
import gitlabProvider from '../../services/providers/gitlabProvider';
|
||||
import syncSvc from '../../services/syncSvc';
|
||||
import store from '../../store';
|
||||
|
||||
@ -121,16 +139,19 @@ export default {
|
||||
return Object.keys(this.syncLocations).length;
|
||||
},
|
||||
currentFileName() {
|
||||
return this.$store.getters['file/current'].name;
|
||||
},
|
||||
googleDriveTokens() {
|
||||
return tokensToArray(this.$store.getters['data/googleTokensBySub'], token => token.isDrive);
|
||||
return store.getters['file/current'].name;
|
||||
},
|
||||
dropboxTokens() {
|
||||
return tokensToArray(this.$store.getters['data/dropboxTokensBySub']);
|
||||
return tokensToArray(store.getters['data/dropboxTokensBySub']);
|
||||
},
|
||||
githubTokens() {
|
||||
return tokensToArray(this.$store.getters['data/githubTokensBySub']);
|
||||
return tokensToArray(store.getters['data/githubTokensBySub']);
|
||||
},
|
||||
gitlabTokens() {
|
||||
return tokensToArray(store.getters['data/gitlabTokensBySub']);
|
||||
},
|
||||
googleDriveTokens() {
|
||||
return tokensToArray(store.getters['data/googleTokensBySub'], token => token.isDrive);
|
||||
},
|
||||
noToken() {
|
||||
return !this.googleDriveTokens.length
|
||||
@ -146,37 +167,43 @@ export default {
|
||||
},
|
||||
async manageSync() {
|
||||
try {
|
||||
await this.$store.dispatch('modal/open', 'syncManagement');
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addGoogleDriveAccount() {
|
||||
try {
|
||||
await this.$store.dispatch('modal/open', { type: 'googleDriveAccount' });
|
||||
await googleHelper.addDriveAccount(!store.getters['data/localSettings'].googleDriveRestrictedAccess);
|
||||
await store.dispatch('modal/open', 'syncManagement');
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addDropboxAccount() {
|
||||
try {
|
||||
await this.$store.dispatch('modal/open', { type: 'dropboxAccount' });
|
||||
await store.dispatch('modal/open', { type: 'dropboxAccount' });
|
||||
await dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess);
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addGithubAccount() {
|
||||
try {
|
||||
await this.$store.dispatch('modal/open', { type: 'githubAccount' });
|
||||
await store.dispatch('modal/open', { type: 'githubAccount' });
|
||||
await githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess);
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addGitlabAccount() {
|
||||
try {
|
||||
const { serverUrl, applicationId } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
|
||||
await gitlabHelper.addAccount(serverUrl, applicationId);
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async addGoogleDriveAccount() {
|
||||
try {
|
||||
await store.dispatch('modal/open', { type: 'googleDriveAccount' });
|
||||
await googleHelper.addDriveAccount(!store.getters['data/localSettings'].googleDriveRestrictedAccess);
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async openGoogleDrive(token) {
|
||||
const files = await googleHelper.openPicker(token, 'doc');
|
||||
this.$store.dispatch(
|
||||
store.dispatch(
|
||||
'queue/enqueue',
|
||||
() => googleDriveProvider.openFiles(token, files),
|
||||
);
|
||||
},
|
||||
async openDropbox(token) {
|
||||
const paths = await dropboxHelper.openChooser(token);
|
||||
this.$store.dispatch(
|
||||
store.dispatch(
|
||||
'queue/enqueue',
|
||||
() => dropboxProvider.openFiles(token, paths),
|
||||
);
|
||||
@ -197,7 +224,7 @@ export default {
|
||||
type: 'githubOpen',
|
||||
token,
|
||||
});
|
||||
this.$store.dispatch(
|
||||
store.dispatch(
|
||||
'queue/enqueue',
|
||||
() => githubProvider.openFile(token, syncLocation),
|
||||
);
|
||||
@ -213,6 +240,23 @@ export default {
|
||||
await openSyncModal(token, 'gistSync');
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async openGitlab(token) {
|
||||
try {
|
||||
const syncLocation = await store.dispatch('modal/open', {
|
||||
type: 'gitlabOpen',
|
||||
token,
|
||||
});
|
||||
store.dispatch(
|
||||
'queue/enqueue',
|
||||
() => gitlabProvider.openFile(token, syncLocation),
|
||||
);
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async saveGitlab(token) {
|
||||
try {
|
||||
await openSyncModal(token, 'gitlabSave');
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -17,6 +17,11 @@
|
||||
<div>GitHub workspace</div>
|
||||
<span>Add a workspace synced with a GitHub repository.</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addGitlabWorkspace">
|
||||
<icon-provider slot="icon" provider-id="gitlabWorkspace"></icon-provider>
|
||||
<div>GitLab workspace</div>
|
||||
<span>Add a workspace synced with a GitLab project.</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="addGoogleDriveWorkspace">
|
||||
<icon-provider slot="icon" provider-id="googleDriveWorkspace"></icon-provider>
|
||||
<div>Google Drive workspace</div>
|
||||
@ -33,6 +38,8 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import MenuEntry from './common/MenuEntry';
|
||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
|
||||
import store from '../../store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -50,7 +57,7 @@ export default {
|
||||
methods: {
|
||||
async addCouchdbWorkspace() {
|
||||
try {
|
||||
this.$store.dispatch('modal/open', {
|
||||
store.dispatch('modal/open', {
|
||||
type: 'couchdbWorkspace',
|
||||
});
|
||||
} catch (e) {
|
||||
@ -59,17 +66,29 @@ export default {
|
||||
},
|
||||
async addGithubWorkspace() {
|
||||
try {
|
||||
this.$store.dispatch('modal/open', {
|
||||
store.dispatch('modal/open', {
|
||||
type: 'githubWorkspace',
|
||||
});
|
||||
} catch (e) {
|
||||
// Cancel
|
||||
}
|
||||
},
|
||||
async addGitlabWorkspace() {
|
||||
try {
|
||||
const { serverUrl, applicationId } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
|
||||
const token = await gitlabHelper.addAccount(serverUrl, applicationId);
|
||||
store.dispatch('modal/open', {
|
||||
type: 'gitlabWorkspace',
|
||||
token,
|
||||
});
|
||||
} catch (e) {
|
||||
// Cancel
|
||||
}
|
||||
},
|
||||
async addGoogleDriveWorkspace() {
|
||||
try {
|
||||
const token = await googleHelper.addDriveAccount(true);
|
||||
this.$store.dispatch('modal/open', {
|
||||
store.dispatch('modal/open', {
|
||||
type: 'googleDriveWorkspace',
|
||||
token,
|
||||
});
|
||||
@ -78,7 +97,7 @@ export default {
|
||||
}
|
||||
},
|
||||
manageWorkspaces() {
|
||||
this.$store.dispatch('modal/open', 'workspaceManagement');
|
||||
store.dispatch('modal/open', 'workspaceManagement');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -92,6 +92,7 @@ import FormEntry from './common/FormEntry';
|
||||
import CodeEditor from '../CodeEditor';
|
||||
import utils from '../../services/utils';
|
||||
import presets from '../../data/presets';
|
||||
import store from '../../store';
|
||||
|
||||
const simpleProperties = {
|
||||
title: '',
|
||||
@ -125,17 +126,17 @@ export default {
|
||||
presets: () => Object.keys(presets).sort(),
|
||||
tab: {
|
||||
get() {
|
||||
return this.$store.getters['data/localSettings'].filePropertiesTab;
|
||||
return store.getters['data/localSettings'].filePropertiesTab;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.dispatch('data/patchLocalSettings', {
|
||||
store.dispatch('data/patchLocalSettings', {
|
||||
filePropertiesTab: value,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
const content = this.$store.getters['content/current'];
|
||||
const content = store.getters['content/current'];
|
||||
this.contentId = content.id;
|
||||
this.setYamlProperties(content.properties);
|
||||
if (this.tab !== 'yaml') {
|
||||
@ -214,7 +215,7 @@ export default {
|
||||
if (this.error) {
|
||||
this.setYamlTab();
|
||||
} else {
|
||||
this.$store.commit('content/patchItem', {
|
||||
store.commit('content/patchItem', {
|
||||
id: this.contentId,
|
||||
properties: utils.sanitizeText(this.yamlProperties),
|
||||
});
|
||||
|
@ -25,6 +25,7 @@
|
||||
import { mapActions } from 'vuex';
|
||||
import exportSvc from '../../services/exportSvc';
|
||||
import modalTemplate from './common/modalTemplate';
|
||||
import store from '../../store';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
@ -38,7 +39,7 @@ export default modalTemplate({
|
||||
this.$watch('selectedTemplate', (selectedTemplate) => {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(async () => {
|
||||
const currentFile = this.$store.getters['file/current'];
|
||||
const currentFile = store.getters['file/current'];
|
||||
const html = await exportSvc.applyTemplate(
|
||||
currentFile.id,
|
||||
this.allTemplatesById[selectedTemplate],
|
||||
@ -55,7 +56,7 @@ export default modalTemplate({
|
||||
]),
|
||||
resolve() {
|
||||
const { config } = this;
|
||||
const currentFile = this.$store.getters['file/current'];
|
||||
const currentFile = store.getters['file/current'];
|
||||
config.resolve();
|
||||
exportSvc.exportToDisk(currentFile.id, 'html', this.allTemplatesById[this.selectedTemplate]);
|
||||
},
|
||||
|
@ -26,6 +26,7 @@
|
||||
import modalTemplate from './common/modalTemplate';
|
||||
import MenuEntry from '../menus/common/MenuEntry';
|
||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
import store from '../../store';
|
||||
|
||||
export default modalTemplate({
|
||||
components: {
|
||||
@ -36,7 +37,7 @@ export default modalTemplate({
|
||||
}),
|
||||
computed: {
|
||||
googlePhotosTokens() {
|
||||
const googleTokensBySub = this.$store.getters['data/googleTokensBySub'];
|
||||
const googleTokensBySub = store.getters['data/googleTokensBySub'];
|
||||
return Object.values(googleTokensBySub)
|
||||
.filter(token => token.isPhotos)
|
||||
.sort((token1, token2) => token1.name.localeCompare(token2.name));
|
||||
@ -65,7 +66,7 @@ export default modalTemplate({
|
||||
this.config.reject();
|
||||
const res = await googleHelper.openPicker(token, 'img');
|
||||
if (res[0]) {
|
||||
this.$store.dispatch('modal/open', {
|
||||
store.dispatch('modal/open', {
|
||||
type: 'googlePhoto',
|
||||
url: res[0].url,
|
||||
callback,
|
||||
|
@ -32,6 +32,7 @@ import networkSvc from '../../services/networkSvc';
|
||||
import editorSvc from '../../services/editorSvc';
|
||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
import modalTemplate from './common/modalTemplate';
|
||||
import store from '../../store';
|
||||
|
||||
export default modalTemplate({
|
||||
computedLocalSettings: {
|
||||
@ -40,13 +41,13 @@ export default modalTemplate({
|
||||
methods: {
|
||||
async resolve() {
|
||||
this.config.resolve();
|
||||
const currentFile = this.$store.getters['file/current'];
|
||||
const currentContent = this.$store.getters['content/current'];
|
||||
const currentFile = store.getters['file/current'];
|
||||
const currentContent = store.getters['content/current'];
|
||||
const { selectedFormat } = this;
|
||||
this.$store.dispatch('queue/enqueue', async () => {
|
||||
store.dispatch('queue/enqueue', async () => {
|
||||
const [sponsorToken, token] = await Promise.all([
|
||||
Promise.resolve().then(() => {
|
||||
const tokenToRefresh = this.$store.getters['workspace/sponsorToken'];
|
||||
const tokenToRefresh = store.getters['workspace/sponsorToken'];
|
||||
return tokenToRefresh && googleHelper.refreshToken(tokenToRefresh);
|
||||
}),
|
||||
sponsorSvc.getToken(),
|
||||
@ -60,7 +61,7 @@ export default modalTemplate({
|
||||
token,
|
||||
idToken: sponsorToken && sponsorToken.idToken,
|
||||
format: selectedFormat,
|
||||
options: JSON.stringify(this.$store.getters['data/computedSettings'].pandoc),
|
||||
options: JSON.stringify(store.getters['data/computedSettings'].pandoc),
|
||||
metadata: JSON.stringify(currentContent.properties),
|
||||
},
|
||||
body: JSON.stringify(editorSvc.getPandocAst()),
|
||||
@ -70,10 +71,10 @@ export default modalTemplate({
|
||||
FileSaver.saveAs(body, `${currentFile.name}.${selectedFormat}`);
|
||||
} catch (err) {
|
||||
if (err.status === 401) {
|
||||
this.$store.dispatch('modal/open', 'sponsorOnly');
|
||||
store.dispatch('modal/open', 'sponsorOnly');
|
||||
} else {
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
this.$store.dispatch('notification/error', err);
|
||||
store.dispatch('notification/error', err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -27,6 +27,7 @@ import sponsorSvc from '../../services/sponsorSvc';
|
||||
import networkSvc from '../../services/networkSvc';
|
||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||
import modalTemplate from './common/modalTemplate';
|
||||
import store from '../../store';
|
||||
|
||||
export default modalTemplate({
|
||||
computedLocalSettings: {
|
||||
@ -35,11 +36,11 @@ export default modalTemplate({
|
||||
methods: {
|
||||
async resolve() {
|
||||
this.config.resolve();
|
||||
const currentFile = this.$store.getters['file/current'];
|
||||
this.$store.dispatch('queue/enqueue', async () => {
|
||||
const currentFile = store.getters['file/current'];
|
||||
store.dispatch('queue/enqueue', async () => {
|
||||
const [sponsorToken, token, html] = await Promise.all([
|
||||
Promise.resolve().then(() => {
|
||||
const tokenToRefresh = this.$store.getters['workspace/sponsorToken'];
|
||||
const tokenToRefresh = store.getters['workspace/sponsorToken'];
|
||||
return tokenToRefresh && googleHelper.refreshToken(tokenToRefresh);
|
||||
}),
|
||||
sponsorSvc.getToken(),
|
||||
@ -57,7 +58,7 @@ export default modalTemplate({
|
||||
params: {
|
||||
token,
|
||||
idToken: sponsorToken && sponsorToken.idToken,
|
||||
options: JSON.stringify(this.$store.getters['data/computedSettings'].wkhtmltopdf),
|
||||
options: JSON.stringify(store.getters['data/computedSettings'].wkhtmltopdf),
|
||||
},
|
||||
body: html,
|
||||
blob: true,
|
||||
@ -66,10 +67,10 @@ export default modalTemplate({
|
||||
FileSaver.saveAs(body, `${currentFile.name}.pdf`);
|
||||
} catch (err) {
|
||||
if (err.status === 401) {
|
||||
this.$store.dispatch('modal/open', 'sponsorOnly');
|
||||
store.dispatch('modal/open', 'sponsorOnly');
|
||||
} else {
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
this.$store.dispatch('notification/error', err);
|
||||
store.dispatch('notification/error', err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -49,6 +49,7 @@
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import ModalInner from './common/ModalInner';
|
||||
import store from '../../store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -62,7 +63,7 @@ export default {
|
||||
publishLocations: 'current',
|
||||
}),
|
||||
currentFileName() {
|
||||
return this.$store.getters['file/current'].name;
|
||||
return store.getters['file/current'].name;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@ -70,7 +71,7 @@ export default {
|
||||
'info',
|
||||
]),
|
||||
remove(location) {
|
||||
this.$store.commit('publishLocation/deleteItem', location.id);
|
||||
store.commit('publishLocation/deleteItem', location.id);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -37,6 +37,7 @@ import ModalInner from './common/ModalInner';
|
||||
import Tab from './common/Tab';
|
||||
import CodeEditor from '../CodeEditor';
|
||||
import defaultSettings from '../../data/defaults/defaultSettings.yml';
|
||||
import store from '../../store';
|
||||
|
||||
const emptySettings = `# Add your custom settings here to override the
|
||||
# default settings.
|
||||
@ -63,7 +64,7 @@ export default {
|
||||
},
|
||||
},
|
||||
created() {
|
||||
const settings = this.$store.getters['data/settings'];
|
||||
const settings = store.getters['data/settings'];
|
||||
this.setCustomSettings(settings === '\n' ? emptySettings : settings);
|
||||
},
|
||||
methods: {
|
||||
|
@ -19,13 +19,14 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import ModalInner from './common/ModalInner';
|
||||
import utils from '../../services/utils';
|
||||
import store from '../../store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ModalInner,
|
||||
},
|
||||
data() {
|
||||
const sponsorToken = this.$store.getters['workspace/sponsorToken'];
|
||||
const sponsorToken = store.getters['workspace/sponsorToken'];
|
||||
const makeButton = (id, price, description, offer) => {
|
||||
const params = {
|
||||
cmd: '_s-xclick',
|
||||
|
@ -49,6 +49,7 @@
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import ModalInner from './common/ModalInner';
|
||||
import store from '../../store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -62,7 +63,7 @@ export default {
|
||||
syncLocations: 'currentWithWorkspaceSyncLocation',
|
||||
}),
|
||||
currentFileName() {
|
||||
return this.$store.getters['file/current'].name;
|
||||
return store.getters['file/current'].name;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@ -73,7 +74,7 @@ export default {
|
||||
if (location.id === 'main') {
|
||||
this.info('This location can not be removed.');
|
||||
} else {
|
||||
this.$store.commit('syncLocation/deleteItem', location.id);
|
||||
store.commit('syncLocation/deleteItem', location.id);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -57,6 +57,7 @@ import ModalInner from './common/ModalInner';
|
||||
import CodeEditor from '../CodeEditor';
|
||||
import emptyTemplateValue from '../../data/empties/emptyTemplateValue.html';
|
||||
import emptyTemplateHelpers from '!raw-loader!../../data/empties/emptyTemplateHelpers.js'; // eslint-disable-line
|
||||
import store from '../../store';
|
||||
|
||||
const collator = new Intl.Collator(undefined, { sensitivity: 'base' });
|
||||
|
||||
@ -91,7 +92,7 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.$watch(
|
||||
() => this.$store.getters['data/allTemplatesById'],
|
||||
() => store.getters['data/allTemplatesById'],
|
||||
(allTemplatesById) => {
|
||||
const templates = {};
|
||||
// Sort templates by name
|
||||
|
@ -64,6 +64,7 @@
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import ModalInner from './common/ModalInner';
|
||||
import workspaceSvc from '../../services/workspaceSvc';
|
||||
import store from '../../store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -95,7 +96,7 @@ export default {
|
||||
const workspace = this.workspacesById[this.editedId];
|
||||
if (workspace) {
|
||||
if (!cancel && this.editingName) {
|
||||
this.$store.dispatch('workspace/patchWorkspacesById', {
|
||||
store.dispatch('workspace/patchWorkspacesById', {
|
||||
[this.editedId]: {
|
||||
...workspace,
|
||||
name: this.editingName,
|
||||
@ -114,7 +115,7 @@ export default {
|
||||
this.info('Please close the workspace before removing it.');
|
||||
} else {
|
||||
try {
|
||||
await this.$store.dispatch('modal/open', 'removeWorkspace');
|
||||
await store.dispatch('modal/open', 'removeWorkspace');
|
||||
workspaceSvc.removeWorkspace(id);
|
||||
} catch (e) { /* Cancel */ }
|
||||
}
|
||||
|
@ -4,10 +4,6 @@
|
||||
<button class="modal__close-button button not-tabbable" @click="config.reject()" v-title="'Close modal'">
|
||||
<icon-close></icon-close>
|
||||
</button>
|
||||
<div class="modal__sponsor-button" v-if="showSponsorButton">
|
||||
StackEdit is <a class="not-tabbable" target="_blank" href="https://github.com/benweet/stackedit/">open source</a>, please consider
|
||||
<a class="not-tabbable" href="javascript:void(0)" @click="sponsor">sponsoring</a> for just $5.
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
@ -15,33 +11,12 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import googleHelper from '../../../services/providers/helpers/googleHelper';
|
||||
import syncSvc from '../../../services/syncSvc';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'config',
|
||||
]),
|
||||
showSponsorButton() {
|
||||
const { type } = this.$store.getters['modal/config'];
|
||||
return !this.$store.getters.isSponsor && type !== 'sponsor' && type !== 'signInForSponsorship';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async sponsor() {
|
||||
try {
|
||||
if (!this.$store.getters['workspace/sponsorToken']) {
|
||||
// User has to sign in
|
||||
await this.$store.dispatch('modal/open', 'signInForSponsorship');
|
||||
await googleHelper.signin();
|
||||
syncSvc.requestSync();
|
||||
}
|
||||
if (!this.$store.getters.isSponsor) {
|
||||
await this.$store.dispatch('modal/open', 'sponsor');
|
||||
}
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@ -64,15 +39,4 @@ export default {
|
||||
color: rgba(0, 0, 0, 0.67);
|
||||
}
|
||||
}
|
||||
|
||||
.modal__sponsor-button {
|
||||
display: inline-block;
|
||||
color: darken($error-color, 10%);
|
||||
background-color: transparentize($error-color, 0.85);
|
||||
border-radius: $border-radius-base;
|
||||
font-size: 0.9em;
|
||||
padding: 0.75em 1.5em;
|
||||
margin-bottom: 1.2em;
|
||||
line-height: 1.55;
|
||||
}
|
||||
</style>
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
<script>
|
||||
import modalTemplate from '../common/modalTemplate';
|
||||
import store from '../../../store';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
@ -45,7 +46,7 @@ export default modalTemplate({
|
||||
name: this.name,
|
||||
password: this.password,
|
||||
};
|
||||
this.$store.dispatch('data/addCouchdbToken', token);
|
||||
store.dispatch('data/addCouchdbToken', token);
|
||||
this.config.resolve();
|
||||
}
|
||||
},
|
||||
|
@ -46,27 +46,23 @@ export default modalTemplate({
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
if (!this.repoUrl) {
|
||||
const parsedRepo = utils.parseGithubRepoUrl(this.repoUrl);
|
||||
if (!parsedRepo) {
|
||||
this.setError('repoUrl');
|
||||
}
|
||||
if (!this.path) {
|
||||
this.setError('path');
|
||||
}
|
||||
if (this.repoUrl && this.path) {
|
||||
const parsedRepo = utils.parseGithubRepoUrl(this.repoUrl);
|
||||
if (!parsedRepo) {
|
||||
this.setError('repoUrl');
|
||||
} else {
|
||||
// Return new location
|
||||
const location = githubProvider.makeLocation(
|
||||
this.config.token,
|
||||
parsedRepo.owner,
|
||||
parsedRepo.repo,
|
||||
this.branch || 'master',
|
||||
this.path,
|
||||
);
|
||||
this.config.resolve(location);
|
||||
}
|
||||
if (parsedRepo && this.path) {
|
||||
// Return new location
|
||||
const location = githubProvider.makeLocation(
|
||||
this.config.token,
|
||||
parsedRepo.owner,
|
||||
parsedRepo.repo,
|
||||
this.branch || 'master',
|
||||
this.path,
|
||||
);
|
||||
this.config.resolve(location);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -45,6 +45,7 @@
|
||||
<script>
|
||||
import githubProvider from '../../../services/providers/githubProvider';
|
||||
import modalTemplate from '../common/modalTemplate';
|
||||
import utils from '../../../services/utils';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
@ -60,28 +61,24 @@ export default modalTemplate({
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
if (!this.repoUrl) {
|
||||
const parsedRepo = utils.parseGithubRepoUrl(this.repoUrl);
|
||||
if (!parsedRepo) {
|
||||
this.setError('repoUrl');
|
||||
}
|
||||
if (!this.path) {
|
||||
this.setError('path');
|
||||
}
|
||||
if (this.repoUrl && this.path) {
|
||||
const parsedRepo = this.repoUrl.match(/[/:]?([^/:]+)\/([^/]+?)(?:\.git|\/)?$/);
|
||||
if (!parsedRepo) {
|
||||
this.setError('repoUrl');
|
||||
} else {
|
||||
// Return new location
|
||||
const location = githubProvider.makeLocation(
|
||||
this.config.token,
|
||||
parsedRepo[1],
|
||||
parsedRepo[2],
|
||||
this.branch || 'master',
|
||||
this.path,
|
||||
);
|
||||
location.templateId = this.selectedTemplate;
|
||||
this.config.resolve(location);
|
||||
}
|
||||
if (parsedRepo && this.path) {
|
||||
// Return new location
|
||||
const location = githubProvider.makeLocation(
|
||||
this.config.token,
|
||||
parsedRepo.owner,
|
||||
parsedRepo.repo,
|
||||
this.branch || 'master',
|
||||
this.path,
|
||||
);
|
||||
location.templateId = this.selectedTemplate;
|
||||
this.config.resolve(location);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -11,12 +11,6 @@
|
||||
<b>Example:</b> https://github.com/benweet/stackedit
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Branch" info="optional">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
If not supplied, the <code>master</code> branch will be used.
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="File path" error="path">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
@ -24,6 +18,12 @@
|
||||
If the file exists, it will be overwritten.
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Branch" info="optional">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
If not supplied, the <code>master</code> branch will be used.
|
||||
</div>
|
||||
</form-entry>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
|
@ -11,18 +11,18 @@
|
||||
<b>Example:</b> https://github.com/benweet/stackedit
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Branch" info="optional">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
If not supplied, the <code>master</code> branch will be used.
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Folder path" info="optional">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
If not supplied, the root folder will be used.
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Branch" info="optional">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
If not supplied, the <code>master</code> branch will be used.
|
||||
</div>
|
||||
</form-entry>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
|
65
src/components/modals/providers/GitlabAccountModal.vue
Normal file
65
src/components/modals/providers/GitlabAccountModal.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<modal-inner aria-label="GitLab account">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="gitlab"></icon-provider>
|
||||
</div>
|
||||
<p>Link your <b>GitLab</b> account to <b>StackEdit</b>.</p>
|
||||
<form-entry label="GitLab URL" error="serverUrl">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="serverUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> https://gitlab.example.com/
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Application ID" error="applicationId">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="applicationId" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
You have to configure an OAuth2 Application with redirect URL <b>{{redirectUrl}}</b>
|
||||
</div>
|
||||
<div class="form-entry__actions">
|
||||
<a href="https://docs.gitlab.com/ee/integration/oauth_provider.html" target="_blank">More info</a>
|
||||
</div>
|
||||
</form-entry>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||
</div>
|
||||
</modal-inner>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modalTemplate from '../common/modalTemplate';
|
||||
import constants from '../../../data/constants';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
redirectUrl: constants.oauth2RedirectUri,
|
||||
}),
|
||||
computedLocalSettings: {
|
||||
serverUrl: 'gitlabServerUrl',
|
||||
applicationId: 'gitlabApplicationId',
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
if (!this.serverUrl) {
|
||||
this.setError('serverUrl');
|
||||
}
|
||||
if (!this.applicationId) {
|
||||
this.setError('applicationId');
|
||||
}
|
||||
if (this.serverUrl && this.applicationId) {
|
||||
const parsedUrl = this.serverUrl.match(/^(https:\/\/[^/]+)/);
|
||||
if (!parsedUrl) {
|
||||
this.setError('serverUrl');
|
||||
} else {
|
||||
this.config.resolve({
|
||||
serverUrl: parsedUrl[1],
|
||||
applicationId: this.applicationId,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
69
src/components/modals/providers/GitlabOpenModal.vue
Normal file
69
src/components/modals/providers/GitlabOpenModal.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<modal-inner aria-label="Synchronize with GitLab">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="gitlab"></icon-provider>
|
||||
</div>
|
||||
<p>Open a file from your <b>GitLab</b> project and keep it synced.</p>
|
||||
<form-entry label="Project URL" error="projectUrl">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> {{config.token.serverUrl}}path/to/project
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="File path" error="path">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> path/to/README.md
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Branch" info="optional">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
If not supplied, the <code>master</code> branch will be used.
|
||||
</div>
|
||||
</form-entry>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||
</div>
|
||||
</modal-inner>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gitlabProvider from '../../../services/providers/gitlabProvider';
|
||||
import modalTemplate from '../common/modalTemplate';
|
||||
import utils from '../../../services/utils';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
branch: '',
|
||||
path: '',
|
||||
}),
|
||||
computedLocalSettings: {
|
||||
projectUrl: 'gitlabProjectUrl',
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
const projectPath = utils.parseGitlabProjectPath(this.projectUrl);
|
||||
if (!projectPath) {
|
||||
this.setError('projectUrl');
|
||||
}
|
||||
if (!this.path) {
|
||||
this.setError('path');
|
||||
}
|
||||
if (projectPath && this.path) {
|
||||
// Return new location
|
||||
const location = gitlabProvider.makeLocation(
|
||||
this.config.token,
|
||||
projectPath,
|
||||
this.branch || 'master',
|
||||
this.path,
|
||||
);
|
||||
this.config.resolve(location);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
85
src/components/modals/providers/GitlabPublishModal.vue
Normal file
85
src/components/modals/providers/GitlabPublishModal.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<modal-inner aria-label="Publish to GitLab">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="gitlab"></icon-provider>
|
||||
</div>
|
||||
<p>Publish <b>{{currentFileName}}</b> to your <b>GitLab</b> project.</p>
|
||||
<form-entry label="Project URL" error="projectUrl">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> {{config.token.serverUrl}}/path/to/project
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="File path" error="path">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> path/to/README.md<br>
|
||||
If the file exists, it will be overwritten.
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Branch" info="optional">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
If not supplied, the <code>master</code> branch will be used.
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Template">
|
||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="form-entry__actions">
|
||||
<a href="javascript:void(0)" @click="configureTemplates">Configure templates</a>
|
||||
</div>
|
||||
</form-entry>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||
</div>
|
||||
</modal-inner>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gitlabProvider from '../../../services/providers/gitlabProvider';
|
||||
import modalTemplate from '../common/modalTemplate';
|
||||
import utils from '../../../services/utils';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
branch: '',
|
||||
path: '',
|
||||
}),
|
||||
computedLocalSettings: {
|
||||
projectUrl: 'gitlabProjectUrl',
|
||||
selectedTemplate: 'gitlabPublishTemplate',
|
||||
},
|
||||
created() {
|
||||
this.path = `${this.currentFileName}.md`;
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
const projectPath = utils.parseGitlabProjectPath(this.projectUrl);
|
||||
if (!projectPath) {
|
||||
this.setError('projectUrl');
|
||||
}
|
||||
if (!this.path) {
|
||||
this.setError('path');
|
||||
}
|
||||
if (projectPath && this.path) {
|
||||
// Return new location
|
||||
const location = gitlabProvider.makeLocation(
|
||||
this.config.token,
|
||||
projectPath,
|
||||
this.branch || 'master',
|
||||
this.path,
|
||||
);
|
||||
location.templateId = this.selectedTemplate;
|
||||
this.config.resolve(location);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
72
src/components/modals/providers/GitlabSaveModal.vue
Normal file
72
src/components/modals/providers/GitlabSaveModal.vue
Normal file
@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<modal-inner aria-label="Synchronize with GitLab">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="gitlab"></icon-provider>
|
||||
</div>
|
||||
<p>Save <b>{{currentFileName}}</b> to your <b>GitLab</b> project and keep it synced.</p>
|
||||
<form-entry label="Project URL" error="projectUrl">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> {{config.token.serverUrl}}/path/to/project
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="File path" error="path">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> path/to/README.md<br>
|
||||
If the file exists, it will be overwritten.
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Branch" info="optional">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
If not supplied, the <code>master</code> branch will be used.
|
||||
</div>
|
||||
</form-entry>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||
</div>
|
||||
</modal-inner>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gitlabProvider from '../../../services/providers/gitlabProvider';
|
||||
import modalTemplate from '../common/modalTemplate';
|
||||
import utils from '../../../services/utils';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
branch: '',
|
||||
path: '',
|
||||
}),
|
||||
computedLocalSettings: {
|
||||
projectUrl: 'gitlabProjectUrl',
|
||||
},
|
||||
created() {
|
||||
this.path = `${this.currentFileName}.md`;
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
const projectPath = utils.parseGitlabProjectPath(this.projectUrl);
|
||||
if (!projectPath) {
|
||||
this.setError('projectUrl');
|
||||
}
|
||||
if (!this.path) {
|
||||
this.setError('path');
|
||||
}
|
||||
if (projectPath && this.path) {
|
||||
const location = gitlabProvider.makeLocation(
|
||||
this.config.token,
|
||||
projectPath,
|
||||
this.branch || 'master',
|
||||
this.path,
|
||||
);
|
||||
this.config.resolve(location);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
67
src/components/modals/providers/GitlabWorkspaceModal.vue
Normal file
67
src/components/modals/providers/GitlabWorkspaceModal.vue
Normal file
@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<modal-inner aria-label="Synchronize with GitLab">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="gitlab"></icon-provider>
|
||||
</div>
|
||||
<p>Create a workspace synced with a <b>GitLab</b> project folder.</p>
|
||||
<form-entry label="Project URL" error="projectUrl">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="projectUrl" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
<b>Example:</b> {{config.token.serverUrl}}/path/to/project
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Folder path" info="optional">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
If not supplied, the root folder will be used.
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Branch" info="optional">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
If not supplied, the <code>master</code> branch will be used.
|
||||
</div>
|
||||
</form-entry>
|
||||
</div>
|
||||
<div class="modal__button-bar">
|
||||
<button class="button" @click="config.reject()">Cancel</button>
|
||||
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||
</div>
|
||||
</modal-inner>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import utils from '../../../services/utils';
|
||||
import modalTemplate from '../common/modalTemplate';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
branch: '',
|
||||
path: '',
|
||||
}),
|
||||
computedLocalSettings: {
|
||||
projectUrl: 'gitlabWorkspaceProjectUrl',
|
||||
},
|
||||
methods: {
|
||||
resolve() {
|
||||
const projectPath = utils.parseGitlabProjectPath(this.projectUrl);
|
||||
if (!projectPath) {
|
||||
this.setError('projectUrl');
|
||||
} else {
|
||||
const path = this.path && this.path.replace(/^\//, '');
|
||||
const url = utils.addQueryParams('app', {
|
||||
providerId: 'gitlabWorkspace',
|
||||
serverUrl: this.config.token.serverUrl,
|
||||
projectPath,
|
||||
branch: this.branch || 'master',
|
||||
path: path || undefined,
|
||||
sub: this.config.token.sub,
|
||||
}, true);
|
||||
this.config.resolve();
|
||||
window.open(url);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
@ -57,6 +57,7 @@
|
||||
import googleHelper from '../../../services/providers/helpers/googleHelper';
|
||||
import googleDriveProvider from '../../../services/providers/googleDriveProvider';
|
||||
import modalTemplate from '../common/modalTemplate';
|
||||
import store from '../../../store';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
@ -69,12 +70,12 @@ export default modalTemplate({
|
||||
},
|
||||
methods: {
|
||||
openFolder() {
|
||||
return this.$store.dispatch(
|
||||
return store.dispatch(
|
||||
'modal/hideUntil',
|
||||
googleHelper.openPicker(this.config.token, 'folder')
|
||||
.then((folders) => {
|
||||
if (folders[0]) {
|
||||
this.$store.dispatch('data/patchLocalSettings', {
|
||||
store.dispatch('data/patchLocalSettings', {
|
||||
googleDriveFolderId: folders[0].id,
|
||||
});
|
||||
}
|
||||
|
@ -32,6 +32,7 @@
|
||||
import googleHelper from '../../../services/providers/helpers/googleHelper';
|
||||
import googleDriveProvider from '../../../services/providers/googleDriveProvider';
|
||||
import modalTemplate from '../common/modalTemplate';
|
||||
import store from '../../../store';
|
||||
|
||||
export default modalTemplate({
|
||||
data: () => ({
|
||||
@ -42,12 +43,12 @@ export default modalTemplate({
|
||||
},
|
||||
methods: {
|
||||
openFolder() {
|
||||
return this.$store.dispatch(
|
||||
return store.dispatch(
|
||||
'modal/hideUntil',
|
||||
googleHelper.openPicker(this.config.token, 'folder')
|
||||
.then((folders) => {
|
||||
if (folders[0]) {
|
||||
this.$store.dispatch('data/patchLocalSettings', {
|
||||
store.dispatch('data/patchLocalSettings', {
|
||||
googleDriveFolderId: folders[0].id,
|
||||
});
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
import googleHelper from '../../../services/providers/helpers/googleHelper';
|
||||
import modalTemplate from '../common/modalTemplate';
|
||||
import utils from '../../../services/utils';
|
||||
import store from '../../../store';
|
||||
|
||||
export default modalTemplate({
|
||||
computedLocalSettings: {
|
||||
@ -33,12 +34,12 @@ export default modalTemplate({
|
||||
},
|
||||
methods: {
|
||||
openFolder() {
|
||||
return this.$store.dispatch(
|
||||
return store.dispatch(
|
||||
'modal/hideUntil',
|
||||
googleHelper.openPicker(this.config.token, 'folder')
|
||||
.then((folders) => {
|
||||
if (folders[0]) {
|
||||
this.$store.dispatch('data/patchLocalSettings', {
|
||||
store.dispatch('data/patchLocalSettings', {
|
||||
googleDriveWorkspaceFolderId: folders[0].id,
|
||||
});
|
||||
}
|
||||
|
@ -14,8 +14,10 @@
|
||||
<form-entry label="Client Unique Identifier" error="clientId">
|
||||
<input slot="field" class="textfield" type="text" v-model.trim="clientId" @keydown.enter="resolve()">
|
||||
<div class="form-entry__info">
|
||||
You have to configure an OAuth Client with redirect URL <b>{{redirectUrl}}</b><br>
|
||||
<a href="https://support.zendesk.com/hc/en-us/articles/203663836" target="_blank"><b>More info</b></a>
|
||||
You have to configure an OAuth Client with redirect URL <b>{{redirectUrl}}</b>
|
||||
</div>
|
||||
<div class="form-entry__actions">
|
||||
<a href="https://support.zendesk.com/hc/en-us/articles/203663836" target="_blank">More info</a>
|
||||
</div>
|
||||
</form-entry>
|
||||
</div>
|
||||
|
@ -20,11 +20,6 @@ export default {
|
||||
'layoutSettings',
|
||||
'tokens',
|
||||
],
|
||||
userIdPrefixes: {
|
||||
db: 'dropbox',
|
||||
gh: 'github',
|
||||
go: 'google',
|
||||
},
|
||||
textMaxLength: 250000,
|
||||
defaultName: 'Untitled',
|
||||
};
|
||||
|
@ -19,6 +19,11 @@ export default () => ({
|
||||
githubPublishTemplate: 'jekyllSite',
|
||||
gistIsPublic: false,
|
||||
gistPublishTemplate: 'plainText',
|
||||
gitlabServerUrl: '',
|
||||
gitlabApplicationId: '',
|
||||
gitlabProjectUrl: '',
|
||||
gitlabWorkspaceProjectUrl: '',
|
||||
gitlabPublishTemplate: 'plainText',
|
||||
wordpressDomain: '',
|
||||
wordpressPublishTemplate: 'plainHtml',
|
||||
zendeskSiteUrl: '',
|
||||
|
@ -77,8 +77,8 @@ turndown:
|
||||
linkStyle: inlined
|
||||
linkReferenceStyle: full
|
||||
|
||||
# GitHub commit messages
|
||||
github:
|
||||
# GitHub/GitLab commit messages
|
||||
git:
|
||||
createFileMessage: '{{path}} created from https://stackedit.io/'
|
||||
updateFileMessage: '{{path}} updated from https://stackedit.io/'
|
||||
deleteFileMessage: '{{path}} deleted from https://stackedit.io/'
|
||||
|
@ -1,9 +1,9 @@
|
||||
**Where is my data stored?**
|
||||
|
||||
If your workspace is not synced, your files are only stored inside your browser using the [IndexedDB API](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) and are not stored anywhere else.
|
||||
If your workspace is not synced, your files are stored inside your browser and nowhere else.
|
||||
|
||||
We recommend syncing your workspace to make sure files won't be lost in case your browser data is cleared. Self-hosted CouchDB backend is best suited for privacy.
|
||||
We recommend syncing your workspace to make sure files won't be lost in case your browser data is cleared. Self-hosted CouchDB or GitLab backends are well suited for privacy.
|
||||
|
||||
**Can StackEdit access my data without telling me?**
|
||||
|
||||
StackEdit is a frontend application. The access tokens issued by Google, Dropbox, GitHub... are stored in your browser and are not sent to any backend or 3^rd^ parties so your data won't be accessed by anyone.
|
||||
StackEdit is a browser-based application. The access tokens issued by Google, Dropbox, GitHub... are stored in your browser and are not sent to any kind of backend or 3^rd^ party so your data won't be accessed by anyone.
|
||||
|
@ -20,6 +20,8 @@ export default {
|
||||
return 'github';
|
||||
case 'gist':
|
||||
return 'github';
|
||||
case 'gitlabWorkspace':
|
||||
return 'gitlab';
|
||||
case 'bloggerPage':
|
||||
return 'blogger';
|
||||
case 'couchdbWorkspace':
|
||||
@ -57,6 +59,10 @@ export default {
|
||||
background-image: url(../assets/iconGithub.svg);
|
||||
}
|
||||
|
||||
.icon-provider--gitlab {
|
||||
background-image: url(../assets/iconGitlab.svg);
|
||||
}
|
||||
|
||||
.icon-provider--dropbox {
|
||||
background-image: url(../assets/iconDropbox.svg);
|
||||
}
|
||||
|
235
src/services/gitWorkspaceSvc.js
Normal file
235
src/services/gitWorkspaceSvc.js
Normal file
@ -0,0 +1,235 @@
|
||||
import store from '../store';
|
||||
import utils from '../services/utils';
|
||||
|
||||
const endsWith = (str, suffix) => str.slice(-suffix.length) === suffix;
|
||||
|
||||
export default {
|
||||
shaByPath: Object.create(null),
|
||||
makeChanges(tree) {
|
||||
const workspacePath = store.getters['workspace/currentWorkspace'].path || '';
|
||||
|
||||
// Store all blobs sha
|
||||
this.shaByPath = Object.create(null);
|
||||
// Store interesting paths
|
||||
const treeFolderMap = Object.create(null);
|
||||
const treeFileMap = Object.create(null);
|
||||
const treeDataMap = Object.create(null);
|
||||
const treeSyncLocationMap = Object.create(null);
|
||||
const treePublishLocationMap = Object.create(null);
|
||||
|
||||
tree.filter(({ type, path }) => type === 'blob' && path.indexOf(workspacePath) === 0)
|
||||
.forEach((blobEntry) => {
|
||||
// Make path relative
|
||||
const path = blobEntry.path.slice(workspacePath.length);
|
||||
// Collect blob sha
|
||||
this.shaByPath[path] = blobEntry.sha;
|
||||
if (path.indexOf('.stackedit-data/') === 0) {
|
||||
treeDataMap[path] = true;
|
||||
} else {
|
||||
// Collect parents path
|
||||
let parentPath = '';
|
||||
path.split('/').slice(0, -1).forEach((folderName) => {
|
||||
const folderPath = `${parentPath}${folderName}/`;
|
||||
treeFolderMap[folderPath] = parentPath;
|
||||
parentPath = folderPath;
|
||||
});
|
||||
// Collect file path
|
||||
if (endsWith(path, '.md')) {
|
||||
treeFileMap[path] = parentPath;
|
||||
} else if (endsWith(path, '.sync')) {
|
||||
treeSyncLocationMap[path] = true;
|
||||
} else if (endsWith(path, '.publish')) {
|
||||
treePublishLocationMap[path] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Collect changes
|
||||
const changes = [];
|
||||
const idsByPath = {};
|
||||
const syncDataByPath = store.getters['data/syncDataById'];
|
||||
const { itemIdsByGitPath } = store.getters;
|
||||
const getIdFromPath = (path, isFile) => {
|
||||
let itemId = idsByPath[path];
|
||||
if (!itemId) {
|
||||
const existingItemId = itemIdsByGitPath[path];
|
||||
if (existingItemId
|
||||
// Reuse a file ID only if it has already been synced
|
||||
&& (!isFile || syncDataByPath[path]
|
||||
// Content may have already been synced
|
||||
|| syncDataByPath[`/${path}`])
|
||||
) {
|
||||
itemId = existingItemId;
|
||||
} else {
|
||||
// Otherwise, make a new ID for a new item
|
||||
itemId = utils.uid();
|
||||
}
|
||||
// If it's a file path, add the content path as well
|
||||
if (isFile) {
|
||||
idsByPath[`/${path}`] = `${itemId}/content`;
|
||||
}
|
||||
idsByPath[path] = itemId;
|
||||
}
|
||||
return itemId;
|
||||
};
|
||||
|
||||
// Folder creations/updates
|
||||
// Assume map entries are sorted from top to bottom
|
||||
Object.entries(treeFolderMap).forEach(([path, parentPath]) => {
|
||||
if (path === '.stackedit-trash/') {
|
||||
idsByPath[path] = 'trash';
|
||||
} else {
|
||||
const item = utils.addItemHash({
|
||||
id: getIdFromPath(path),
|
||||
type: 'folder',
|
||||
name: path.slice(parentPath.length, -1),
|
||||
parentId: idsByPath[parentPath] || null,
|
||||
});
|
||||
|
||||
const folderSyncData = syncDataByPath[path];
|
||||
if (!folderSyncData || folderSyncData.hash !== item.hash) {
|
||||
changes.push({
|
||||
syncDataId: path,
|
||||
item,
|
||||
syncData: {
|
||||
id: path,
|
||||
type: item.type,
|
||||
hash: item.hash,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// File/content creations/updates
|
||||
Object.entries(treeFileMap).forEach(([path, parentPath]) => {
|
||||
const fileId = getIdFromPath(path, true);
|
||||
const contentPath = `/${path}`;
|
||||
const contentId = idsByPath[contentPath];
|
||||
|
||||
// File creations/updates
|
||||
const item = utils.addItemHash({
|
||||
id: fileId,
|
||||
type: 'file',
|
||||
name: path.slice(parentPath.length, -'.md'.length),
|
||||
parentId: idsByPath[parentPath] || null,
|
||||
});
|
||||
|
||||
const fileSyncData = syncDataByPath[path];
|
||||
if (!fileSyncData || fileSyncData.hash !== item.hash) {
|
||||
changes.push({
|
||||
syncDataId: path,
|
||||
item,
|
||||
syncData: {
|
||||
id: path,
|
||||
type: item.type,
|
||||
hash: item.hash,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Content creations/updates
|
||||
const contentSyncData = syncDataByPath[contentPath];
|
||||
if (!contentSyncData || contentSyncData.sha !== this.shaByPath[path]) {
|
||||
const type = 'content';
|
||||
// Use `/` as a prefix to get a unique syncData id
|
||||
changes.push({
|
||||
syncDataId: contentPath,
|
||||
item: {
|
||||
id: contentId,
|
||||
type,
|
||||
// Need a truthy value to force downloading the content
|
||||
hash: 1,
|
||||
},
|
||||
syncData: {
|
||||
id: contentPath,
|
||||
type,
|
||||
// Need a truthy value to force downloading the content
|
||||
hash: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Data creations/updates
|
||||
const syncDataByItemId = store.getters['data/syncDataByItemId'];
|
||||
Object.keys(treeDataMap).forEach((path) => {
|
||||
// Only template data are stored
|
||||
const [, id] = path.match(/^\.stackedit-data\/(templates)\.json$/) || [];
|
||||
if (id) {
|
||||
idsByPath[path] = id;
|
||||
const syncData = syncDataByItemId[id];
|
||||
if (!syncData || syncData.sha !== this.shaByPath[path]) {
|
||||
const type = 'data';
|
||||
changes.push({
|
||||
syncDataId: path,
|
||||
item: {
|
||||
id,
|
||||
type,
|
||||
// Need a truthy value to force saving sync data
|
||||
hash: 1,
|
||||
},
|
||||
syncData: {
|
||||
id: path,
|
||||
type,
|
||||
// Need a truthy value to force downloading the content
|
||||
hash: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Location creations/updates
|
||||
[{
|
||||
type: 'syncLocation',
|
||||
map: treeSyncLocationMap,
|
||||
pathMatcher: /^([\s\S]+)\.([\w-]+)\.sync$/,
|
||||
}, {
|
||||
type: 'publishLocation',
|
||||
map: treePublishLocationMap,
|
||||
pathMatcher: /^([\s\S]+)\.([\w-]+)\.publish$/,
|
||||
}]
|
||||
.forEach(({ type, map, pathMatcher }) => Object.keys(map).forEach((path) => {
|
||||
const [, filePath, data] = path.match(pathMatcher) || [];
|
||||
if (filePath) {
|
||||
// If there is a corresponding md file in the tree
|
||||
const fileId = idsByPath[`${filePath}.md`];
|
||||
if (fileId) {
|
||||
// Reuse existing ID or create a new one
|
||||
const id = itemIdsByGitPath[path] || utils.uid();
|
||||
idsByPath[path] = id;
|
||||
|
||||
const item = utils.addItemHash({
|
||||
...JSON.parse(utils.decodeBase64(data)),
|
||||
id,
|
||||
type,
|
||||
fileId,
|
||||
});
|
||||
|
||||
const locationSyncData = syncDataByPath[path];
|
||||
if (!locationSyncData || locationSyncData.hash !== item.hash) {
|
||||
changes.push({
|
||||
syncDataId: path,
|
||||
item,
|
||||
syncData: {
|
||||
id: path,
|
||||
type: item.type,
|
||||
hash: item.hash,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Deletions
|
||||
Object.keys(syncDataByPath).forEach((path) => {
|
||||
if (!idsByPath[path]) {
|
||||
changes.push({ syncDataId: path });
|
||||
}
|
||||
});
|
||||
|
||||
return changes;
|
||||
},
|
||||
};
|
@ -127,7 +127,7 @@ export default new Provider({
|
||||
const entries = await dropboxHelper.listRevisions(token, syncLocation.dropboxFileId);
|
||||
return entries.map(entry => ({
|
||||
id: entry.rev,
|
||||
sub: `db:${(entry.sharing_info || {}).modified_by || token.sub}`,
|
||||
sub: `${dropboxHelper.subPrefix}:${(entry.sharing_info || {}).modified_by || token.sub}`,
|
||||
created: new Date(entry.server_modified).getTime(),
|
||||
}));
|
||||
},
|
||||
|
@ -65,7 +65,7 @@ export default new Provider({
|
||||
});
|
||||
|
||||
return entries.map((entry) => {
|
||||
const sub = `gh:${entry.user.id}`;
|
||||
const sub = `${githubHelper.subPrefix}:${entry.user.id}`;
|
||||
userSvc.addInfo({ id: sub, name: entry.user.login, imageUrl: entry.user.avatar_url });
|
||||
return {
|
||||
sub,
|
||||
|
@ -134,7 +134,7 @@ export default new Provider({
|
||||
} else if (committer && committer.login) {
|
||||
user = committer;
|
||||
}
|
||||
const sub = `gh:${user.id}`;
|
||||
const sub = `${githubHelper.subPrefix}:${user.id}`;
|
||||
userSvc.addInfo({ id: sub, name: user.login, imageUrl: user.avatar_url });
|
||||
const date = (commit.author && commit.author.date)
|
||||
|| (commit.committer && commit.committer.date);
|
||||
|
@ -3,19 +3,11 @@ import githubHelper from './helpers/githubHelper';
|
||||
import Provider from './common/Provider';
|
||||
import utils from '../utils';
|
||||
import userSvc from '../userSvc';
|
||||
import gitWorkspaceSvc from '../gitWorkspaceSvc';
|
||||
|
||||
const getAbsolutePath = ({ id }) =>
|
||||
`${store.getters['workspace/currentWorkspace'].path || ''}${id}`;
|
||||
|
||||
let treeShaMap;
|
||||
let treeFolderMap;
|
||||
let treeFileMap;
|
||||
let treeDataMap;
|
||||
let treeSyncLocationMap;
|
||||
let treePublishLocationMap;
|
||||
|
||||
const endsWith = (str, suffix) => str.slice(-suffix.length) === suffix;
|
||||
|
||||
export default new Provider({
|
||||
id: 'githubWorkspace',
|
||||
name: 'GitHub',
|
||||
@ -90,238 +82,13 @@ export default new Provider({
|
||||
return store.getters['workspace/workspacesById'][workspaceId];
|
||||
},
|
||||
getChanges() {
|
||||
const syncToken = store.getters['workspace/syncToken'];
|
||||
return githubHelper.getTree({
|
||||
...store.getters['workspace/currentWorkspace'],
|
||||
token: syncToken,
|
||||
token: this.getToken(),
|
||||
});
|
||||
},
|
||||
prepareChanges(tree) {
|
||||
const workspacePath = store.getters['workspace/currentWorkspace'].path || '';
|
||||
|
||||
// Store all blobs sha
|
||||
treeShaMap = Object.create(null);
|
||||
// Store interesting paths
|
||||
treeFolderMap = Object.create(null);
|
||||
treeFileMap = Object.create(null);
|
||||
treeDataMap = Object.create(null);
|
||||
treeSyncLocationMap = Object.create(null);
|
||||
treePublishLocationMap = Object.create(null);
|
||||
|
||||
tree.filter(({ type, path }) => type === 'blob' && path.indexOf(workspacePath) === 0)
|
||||
.forEach((blobEntry) => {
|
||||
// Make path relative
|
||||
const path = blobEntry.path.slice(workspacePath.length);
|
||||
// Collect blob sha
|
||||
treeShaMap[path] = blobEntry.sha;
|
||||
if (path.indexOf('.stackedit-data/') === 0) {
|
||||
treeDataMap[path] = true;
|
||||
} else {
|
||||
// Collect parents path
|
||||
let parentPath = '';
|
||||
path.split('/').slice(0, -1).forEach((folderName) => {
|
||||
const folderPath = `${parentPath}${folderName}/`;
|
||||
treeFolderMap[folderPath] = parentPath;
|
||||
parentPath = folderPath;
|
||||
});
|
||||
// Collect file path
|
||||
if (endsWith(path, '.md')) {
|
||||
treeFileMap[path] = parentPath;
|
||||
} else if (endsWith(path, '.sync')) {
|
||||
treeSyncLocationMap[path] = true;
|
||||
} else if (endsWith(path, '.publish')) {
|
||||
treePublishLocationMap[path] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Collect changes
|
||||
const changes = [];
|
||||
const idsByPath = {};
|
||||
const syncDataByPath = store.getters['data/syncDataById'];
|
||||
const { itemIdsByGitPath } = store.getters;
|
||||
const getIdFromPath = (path, isFile) => {
|
||||
let itemId = idsByPath[path];
|
||||
if (!itemId) {
|
||||
const existingItemId = itemIdsByGitPath[path];
|
||||
if (existingItemId
|
||||
// Reuse a file ID only if it has already been synced
|
||||
&& (!isFile || syncDataByPath[path]
|
||||
// Content may have already been synced
|
||||
|| syncDataByPath[`/${path}`])
|
||||
) {
|
||||
itemId = existingItemId;
|
||||
} else {
|
||||
// Otherwise, make a new ID for a new item
|
||||
itemId = utils.uid();
|
||||
}
|
||||
// If it's a file path, add the content path as well
|
||||
if (isFile) {
|
||||
idsByPath[`/${path}`] = `${itemId}/content`;
|
||||
}
|
||||
idsByPath[path] = itemId;
|
||||
}
|
||||
return itemId;
|
||||
};
|
||||
|
||||
// Folder creations/updates
|
||||
// Assume map entries are sorted from top to bottom
|
||||
Object.entries(treeFolderMap).forEach(([path, parentPath]) => {
|
||||
if (path === '.stackedit-trash/') {
|
||||
idsByPath[path] = 'trash';
|
||||
} else {
|
||||
const item = utils.addItemHash({
|
||||
id: getIdFromPath(path),
|
||||
type: 'folder',
|
||||
name: path.slice(parentPath.length, -1),
|
||||
parentId: idsByPath[parentPath] || null,
|
||||
});
|
||||
|
||||
const folderSyncData = syncDataByPath[path];
|
||||
if (!folderSyncData || folderSyncData.hash !== item.hash) {
|
||||
changes.push({
|
||||
syncDataId: path,
|
||||
item,
|
||||
syncData: {
|
||||
id: path,
|
||||
type: item.type,
|
||||
hash: item.hash,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// File/content creations/updates
|
||||
Object.entries(treeFileMap).forEach(([path, parentPath]) => {
|
||||
const fileId = getIdFromPath(path, true);
|
||||
const contentPath = `/${path}`;
|
||||
const contentId = idsByPath[contentPath];
|
||||
|
||||
// File creations/updates
|
||||
const item = utils.addItemHash({
|
||||
id: fileId,
|
||||
type: 'file',
|
||||
name: path.slice(parentPath.length, -'.md'.length),
|
||||
parentId: idsByPath[parentPath] || null,
|
||||
});
|
||||
|
||||
const fileSyncData = syncDataByPath[path];
|
||||
if (!fileSyncData || fileSyncData.hash !== item.hash) {
|
||||
changes.push({
|
||||
syncDataId: path,
|
||||
item,
|
||||
syncData: {
|
||||
id: path,
|
||||
type: item.type,
|
||||
hash: item.hash,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Content creations/updates
|
||||
const contentSyncData = syncDataByPath[contentPath];
|
||||
if (!contentSyncData || contentSyncData.sha !== treeShaMap[path]) {
|
||||
const type = 'content';
|
||||
// Use `/` as a prefix to get a unique syncData id
|
||||
changes.push({
|
||||
syncDataId: contentPath,
|
||||
item: {
|
||||
id: contentId,
|
||||
type,
|
||||
// Need a truthy value to force downloading the content
|
||||
hash: 1,
|
||||
},
|
||||
syncData: {
|
||||
id: contentPath,
|
||||
type,
|
||||
// Need a truthy value to force downloading the content
|
||||
hash: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Data creations/updates
|
||||
const syncDataByItemId = store.getters['data/syncDataByItemId'];
|
||||
Object.keys(treeDataMap).forEach((path) => {
|
||||
// Only template data are stored
|
||||
const [, id] = path.match(/^\.stackedit-data\/(templates)\.json$/) || [];
|
||||
if (id) {
|
||||
idsByPath[path] = id;
|
||||
const syncData = syncDataByItemId[id];
|
||||
if (!syncData || syncData.sha !== treeShaMap[path]) {
|
||||
const type = 'data';
|
||||
changes.push({
|
||||
syncDataId: path,
|
||||
item: {
|
||||
id,
|
||||
type,
|
||||
// Need a truthy value to force saving sync data
|
||||
hash: 1,
|
||||
},
|
||||
syncData: {
|
||||
id: path,
|
||||
type,
|
||||
// Need a truthy value to force downloading the content
|
||||
hash: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Location creations/updates
|
||||
[{
|
||||
type: 'syncLocation',
|
||||
map: treeSyncLocationMap,
|
||||
pathMatcher: /^([\s\S]+)\.([\w-]+)\.sync$/,
|
||||
}, {
|
||||
type: 'publishLocation',
|
||||
map: treePublishLocationMap,
|
||||
pathMatcher: /^([\s\S]+)\.([\w-]+)\.publish$/,
|
||||
}]
|
||||
.forEach(({ type, map, pathMatcher }) => Object.keys(map).forEach((path) => {
|
||||
const [, filePath, data] = path.match(pathMatcher) || [];
|
||||
if (filePath) {
|
||||
// If there is a corresponding md file in the tree
|
||||
const fileId = idsByPath[`${filePath}.md`];
|
||||
if (fileId) {
|
||||
// Reuse existing ID or create a new one
|
||||
const id = itemIdsByGitPath[path] || utils.uid();
|
||||
idsByPath[path] = id;
|
||||
|
||||
const item = utils.addItemHash({
|
||||
...JSON.parse(utils.decodeBase64(data)),
|
||||
id,
|
||||
type,
|
||||
fileId,
|
||||
});
|
||||
|
||||
const locationSyncData = syncDataByPath[path];
|
||||
if (!locationSyncData || locationSyncData.hash !== item.hash) {
|
||||
changes.push({
|
||||
syncDataId: path,
|
||||
item,
|
||||
syncData: {
|
||||
id: path,
|
||||
type: item.type,
|
||||
hash: item.hash,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Deletions
|
||||
Object.keys(syncDataByPath).forEach((path) => {
|
||||
if (!idsByPath[path]) {
|
||||
changes.push({ syncDataId: path });
|
||||
}
|
||||
});
|
||||
|
||||
return changes;
|
||||
return gitWorkspaceSvc.makeChanges(tree);
|
||||
},
|
||||
async saveWorkspaceItem({ item }) {
|
||||
const syncData = {
|
||||
@ -342,20 +109,20 @@ export default new Provider({
|
||||
token: syncToken,
|
||||
path: getAbsolutePath(syncData),
|
||||
content: '',
|
||||
sha: treeShaMap[syncData.id],
|
||||
sha: gitWorkspaceSvc.shaByPath[syncData.id],
|
||||
});
|
||||
|
||||
// Return sync data to save
|
||||
return { syncData };
|
||||
},
|
||||
async removeWorkspaceItem({ syncData }) {
|
||||
if (treeShaMap[syncData.id]) {
|
||||
if (gitWorkspaceSvc.shaByPath[syncData.id]) {
|
||||
const syncToken = store.getters['workspace/syncToken'];
|
||||
await githubHelper.removeFile({
|
||||
...store.getters['workspace/currentWorkspace'],
|
||||
token: syncToken,
|
||||
path: getAbsolutePath(syncData),
|
||||
sha: treeShaMap[syncData.id],
|
||||
sha: gitWorkspaceSvc.shaByPath[syncData.id],
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -370,7 +137,7 @@ export default new Provider({
|
||||
token,
|
||||
path: getAbsolutePath(fileSyncData),
|
||||
});
|
||||
treeShaMap[fileSyncData.id] = sha;
|
||||
gitWorkspaceSvc.shaByPath[fileSyncData.id] = sha;
|
||||
const content = Provider.parseContent(data, contentId);
|
||||
return {
|
||||
content,
|
||||
@ -391,7 +158,7 @@ export default new Provider({
|
||||
token,
|
||||
path: getAbsolutePath(syncData),
|
||||
});
|
||||
treeShaMap[syncData.id] = sha;
|
||||
gitWorkspaceSvc.shaByPath[syncData.id] = sha;
|
||||
const item = JSON.parse(data);
|
||||
return {
|
||||
item,
|
||||
@ -410,7 +177,7 @@ export default new Provider({
|
||||
token,
|
||||
path: absolutePath,
|
||||
content: Provider.serializeContent(content),
|
||||
sha: treeShaMap[path],
|
||||
sha: gitWorkspaceSvc.shaByPath[path],
|
||||
});
|
||||
|
||||
// Return new sync data
|
||||
@ -440,7 +207,7 @@ export default new Provider({
|
||||
token,
|
||||
path: getAbsolutePath(syncData),
|
||||
content: JSON.stringify(item),
|
||||
sha: treeShaMap[path],
|
||||
sha: gitWorkspaceSvc.shaByPath[path],
|
||||
});
|
||||
|
||||
return {
|
||||
@ -472,7 +239,7 @@ export default new Provider({
|
||||
} else if (committer && committer.login) {
|
||||
user = committer;
|
||||
}
|
||||
const sub = `gh:${user.id}`;
|
||||
const sub = `${githubHelper.subPrefix}:${user.id}`;
|
||||
userSvc.addInfo({ id: sub, name: user.login, imageUrl: user.avatar_url });
|
||||
const date = (commit.author && commit.author.date)
|
||||
|| (commit.committer && commit.committer.date);
|
||||
|
178
src/services/providers/gitlabProvider.js
Normal file
178
src/services/providers/gitlabProvider.js
Normal file
@ -0,0 +1,178 @@
|
||||
import store from '../../store';
|
||||
import gitlabHelper from './helpers/gitlabHelper';
|
||||
import Provider from './common/Provider';
|
||||
import utils from '../utils';
|
||||
import workspaceSvc from '../workspaceSvc';
|
||||
import userSvc from '../userSvc';
|
||||
|
||||
const savedSha = {};
|
||||
|
||||
export default new Provider({
|
||||
id: 'gitlab',
|
||||
name: 'GitLab',
|
||||
getToken({ sub }) {
|
||||
return store.getters['data/gitlabTokensBySub'][sub];
|
||||
},
|
||||
getLocationUrl({
|
||||
sub,
|
||||
projectPath,
|
||||
branch,
|
||||
path,
|
||||
}) {
|
||||
const token = this.getToken({ sub });
|
||||
return `${token.serverUrl}/${projectPath}/blob/${encodeURIComponent(branch)}/${utils.encodeUrlPath(path)}`;
|
||||
},
|
||||
getLocationDescription({ path }) {
|
||||
return path;
|
||||
},
|
||||
async downloadContent(token, syncLocation) {
|
||||
const { sha, data } = await gitlabHelper.downloadFile({
|
||||
...syncLocation,
|
||||
token,
|
||||
});
|
||||
savedSha[syncLocation.id] = sha;
|
||||
return Provider.parseContent(data, `${syncLocation.fileId}/content`);
|
||||
},
|
||||
async uploadContent(token, content, syncLocation) {
|
||||
const updatedSyncLocation = {
|
||||
...syncLocation,
|
||||
projectId: await gitlabHelper.getProjectId(token, syncLocation),
|
||||
};
|
||||
if (!savedSha[updatedSyncLocation.id]) {
|
||||
try {
|
||||
// Get the last sha
|
||||
await this.downloadContent(token, updatedSyncLocation);
|
||||
} catch (e) {
|
||||
// Ignore error
|
||||
}
|
||||
}
|
||||
const sha = savedSha[updatedSyncLocation.id];
|
||||
delete savedSha[updatedSyncLocation.id];
|
||||
await gitlabHelper.uploadFile({
|
||||
...updatedSyncLocation,
|
||||
token,
|
||||
content: Provider.serializeContent(content),
|
||||
sha,
|
||||
});
|
||||
return updatedSyncLocation;
|
||||
},
|
||||
async publish(token, html, metadata, publishLocation) {
|
||||
const updatedPublishLocation = {
|
||||
...publishLocation,
|
||||
projectId: await gitlabHelper.getProjectId(token, publishLocation),
|
||||
};
|
||||
try {
|
||||
// Get the last sha
|
||||
await this.downloadContent(token, updatedPublishLocation);
|
||||
} catch (e) {
|
||||
// Ignore error
|
||||
}
|
||||
const sha = savedSha[updatedPublishLocation.id];
|
||||
delete savedSha[updatedPublishLocation.id];
|
||||
await gitlabHelper.uploadFile({
|
||||
...updatedPublishLocation,
|
||||
token,
|
||||
content: html,
|
||||
sha,
|
||||
});
|
||||
return updatedPublishLocation;
|
||||
},
|
||||
async openFile(token, syncLocation) {
|
||||
const updatedSyncLocation = {
|
||||
...syncLocation,
|
||||
projectId: await gitlabHelper.getProjectId(token, syncLocation),
|
||||
};
|
||||
|
||||
// Check if the file exists and open it
|
||||
if (!Provider.openFileWithLocation(updatedSyncLocation)) {
|
||||
// Download content from GitLab
|
||||
let content;
|
||||
try {
|
||||
content = await this.downloadContent(token, updatedSyncLocation);
|
||||
} catch (e) {
|
||||
store.dispatch('notification/error', `Could not open file ${updatedSyncLocation.path}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the file
|
||||
let name = updatedSyncLocation.path;
|
||||
const slashPos = name.lastIndexOf('/');
|
||||
if (slashPos > -1 && slashPos < name.length - 1) {
|
||||
name = name.slice(slashPos + 1);
|
||||
}
|
||||
const dotPos = name.lastIndexOf('.');
|
||||
if (dotPos > 0 && slashPos < name.length) {
|
||||
name = name.slice(0, dotPos);
|
||||
}
|
||||
const item = await workspaceSvc.createFile({
|
||||
name,
|
||||
parentId: store.getters['file/current'].parentId,
|
||||
text: content.text,
|
||||
properties: content.properties,
|
||||
discussions: content.discussions,
|
||||
comments: content.comments,
|
||||
}, true);
|
||||
store.commit('file/setCurrentId', item.id);
|
||||
workspaceSvc.addSyncLocation({
|
||||
...updatedSyncLocation,
|
||||
fileId: item.id,
|
||||
});
|
||||
store.dispatch('notification/info', `${store.getters['file/current'].name} was imported from GitLab.`);
|
||||
}
|
||||
},
|
||||
makeLocation(token, projectPath, branch, path) {
|
||||
return {
|
||||
providerId: this.id,
|
||||
sub: token.sub,
|
||||
projectPath,
|
||||
branch,
|
||||
path,
|
||||
};
|
||||
},
|
||||
async listFileRevisions({ token, syncLocation }) {
|
||||
const entries = await gitlabHelper.getCommits({
|
||||
...syncLocation,
|
||||
token,
|
||||
});
|
||||
|
||||
return entries.map(({
|
||||
author,
|
||||
committer,
|
||||
commit,
|
||||
sha,
|
||||
}) => {
|
||||
let user;
|
||||
if (author && author.login) {
|
||||
user = author;
|
||||
} else if (committer && committer.login) {
|
||||
user = committer;
|
||||
}
|
||||
const sub = `${gitlabHelper.subPrefix}:${user.id}`;
|
||||
userSvc.addInfo({ id: sub, name: user.login, imageUrl: user.avatar_url });
|
||||
const date = (commit.author && commit.author.date)
|
||||
|| (commit.committer && commit.committer.date);
|
||||
return {
|
||||
id: sha,
|
||||
sub,
|
||||
created: date ? new Date(date).getTime() : 1,
|
||||
};
|
||||
});
|
||||
},
|
||||
async loadFileRevision() {
|
||||
// Revision are already loaded
|
||||
return false;
|
||||
},
|
||||
async getFileRevisionContent({
|
||||
token,
|
||||
contentId,
|
||||
syncLocation,
|
||||
revisionId,
|
||||
}) {
|
||||
const { data } = await gitlabHelper.downloadFile({
|
||||
...syncLocation,
|
||||
token,
|
||||
branch: revisionId,
|
||||
});
|
||||
return Provider.parseContent(data, contentId);
|
||||
},
|
||||
});
|
276
src/services/providers/gitlabWorkspaceProvider.js
Normal file
276
src/services/providers/gitlabWorkspaceProvider.js
Normal file
@ -0,0 +1,276 @@
|
||||
import store from '../../store';
|
||||
import gitlabHelper from './helpers/gitlabHelper';
|
||||
import Provider from './common/Provider';
|
||||
import utils from '../utils';
|
||||
import userSvc from '../userSvc';
|
||||
import gitWorkspaceSvc from '../gitWorkspaceSvc';
|
||||
|
||||
const getAbsolutePath = ({ id }) =>
|
||||
`${store.getters['workspace/currentWorkspace'].path || ''}${id}`;
|
||||
|
||||
export default new Provider({
|
||||
id: 'gitlabWorkspace',
|
||||
name: 'GitLab',
|
||||
getToken() {
|
||||
return store.getters['workspace/syncToken'];
|
||||
},
|
||||
getWorkspaceParams({
|
||||
serverUrl,
|
||||
projectPath,
|
||||
branch,
|
||||
path,
|
||||
}) {
|
||||
return {
|
||||
providerId: this.id,
|
||||
serverUrl,
|
||||
projectPath,
|
||||
branch,
|
||||
path,
|
||||
};
|
||||
},
|
||||
getWorkspaceLocationUrl({
|
||||
serverUrl,
|
||||
projectPath,
|
||||
branch,
|
||||
path,
|
||||
}) {
|
||||
return `${serverUrl}/${projectPath}/blob/${encodeURIComponent(branch)}/${utils.encodeUrlPath(path)}`;
|
||||
},
|
||||
getSyncDataUrl({ id }) {
|
||||
const { projectPath, branch } = store.getters['workspace/currentWorkspace'];
|
||||
const { serverUrl } = this.getToken();
|
||||
return `${serverUrl}/${projectPath}/blob/${encodeURIComponent(branch)}/${utils.encodeUrlPath(getAbsolutePath({ id }))}`;
|
||||
},
|
||||
getSyncDataDescription({ id }) {
|
||||
return getAbsolutePath({ id });
|
||||
},
|
||||
async initWorkspace() {
|
||||
const { projectPath, branch } = utils.queryParams;
|
||||
const workspaceParams = this.getWorkspaceParams({ projectPath, branch });
|
||||
const path = (utils.queryParams.path || '')
|
||||
.replace(/^\/*/, '') // Remove leading `/`
|
||||
.replace(/\/*$/, '/'); // Add trailing `/`
|
||||
if (path !== '/') {
|
||||
workspaceParams.path = path;
|
||||
}
|
||||
const workspaceId = utils.makeWorkspaceId(workspaceParams);
|
||||
const workspace = store.getters['workspace/workspacesById'][workspaceId];
|
||||
|
||||
// See if we already have a token
|
||||
let token;
|
||||
if (workspace) {
|
||||
// Token sub is in the workspace
|
||||
token = store.getters['data/gitlabTokensBySub'][workspace.sub];
|
||||
}
|
||||
if (!token) {
|
||||
const { serverUrl, applicationId } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
|
||||
token = await gitlabHelper.addAccount(serverUrl, applicationId);
|
||||
}
|
||||
|
||||
if (!workspace) {
|
||||
const pathEntries = (path || '').split('/');
|
||||
const projectPathEntries = (projectPath || '').split('/');
|
||||
const name = pathEntries[pathEntries.length - 2] // path ends with `/`
|
||||
|| projectPathEntries[projectPathEntries.length - 1];
|
||||
store.dispatch('workspace/patchWorkspacesById', {
|
||||
[workspaceId]: {
|
||||
...workspaceParams,
|
||||
id: workspaceId,
|
||||
sub: token.sub,
|
||||
name,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return store.getters['workspace/workspacesById'][workspaceId];
|
||||
},
|
||||
getChanges() {
|
||||
return gitlabHelper.getTree({
|
||||
...store.getters['workspace/currentWorkspace'],
|
||||
token: this.getToken(),
|
||||
});
|
||||
},
|
||||
prepareChanges(tree) {
|
||||
return gitWorkspaceSvc.makeChanges(tree.map(entry => ({
|
||||
...entry,
|
||||
sha: entry.id,
|
||||
})));
|
||||
},
|
||||
async saveWorkspaceItem({ item }) {
|
||||
const syncData = {
|
||||
id: store.getters.gitPathsByItemId[item.id],
|
||||
type: item.type,
|
||||
hash: item.hash,
|
||||
};
|
||||
|
||||
// Files and folders are not in git, only contents
|
||||
if (item.type === 'file' || item.type === 'folder') {
|
||||
return { syncData };
|
||||
}
|
||||
|
||||
// locations are stored as paths, so we upload an empty file
|
||||
const syncToken = store.getters['workspace/syncToken'];
|
||||
await gitlabHelper.uploadFile({
|
||||
...store.getters['workspace/currentWorkspace'],
|
||||
token: syncToken,
|
||||
path: getAbsolutePath(syncData),
|
||||
content: '',
|
||||
sha: gitWorkspaceSvc.shaByPath[syncData.id],
|
||||
});
|
||||
|
||||
// Return sync data to save
|
||||
return { syncData };
|
||||
},
|
||||
async removeWorkspaceItem({ syncData }) {
|
||||
if (gitWorkspaceSvc.shaByPath[syncData.id]) {
|
||||
const syncToken = store.getters['workspace/syncToken'];
|
||||
await gitlabHelper.removeFile({
|
||||
...store.getters['workspace/currentWorkspace'],
|
||||
token: syncToken,
|
||||
path: getAbsolutePath(syncData),
|
||||
sha: gitWorkspaceSvc.shaByPath[syncData.id],
|
||||
});
|
||||
}
|
||||
},
|
||||
async downloadWorkspaceContent({
|
||||
token,
|
||||
contentId,
|
||||
contentSyncData,
|
||||
fileSyncData,
|
||||
}) {
|
||||
const { sha, data } = await gitlabHelper.downloadFile({
|
||||
...store.getters['workspace/currentWorkspace'],
|
||||
token,
|
||||
path: getAbsolutePath(fileSyncData),
|
||||
});
|
||||
gitWorkspaceSvc.shaByPath[fileSyncData.id] = sha;
|
||||
const content = Provider.parseContent(data, contentId);
|
||||
return {
|
||||
content,
|
||||
contentSyncData: {
|
||||
...contentSyncData,
|
||||
hash: content.hash,
|
||||
sha,
|
||||
},
|
||||
};
|
||||
},
|
||||
async downloadWorkspaceData({ token, syncData }) {
|
||||
if (!syncData) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const { sha, data } = await gitlabHelper.downloadFile({
|
||||
...store.getters['workspace/currentWorkspace'],
|
||||
token,
|
||||
path: getAbsolutePath(syncData),
|
||||
});
|
||||
gitWorkspaceSvc.shaByPath[syncData.id] = sha;
|
||||
const item = JSON.parse(data);
|
||||
return {
|
||||
item,
|
||||
syncData: {
|
||||
...syncData,
|
||||
hash: item.hash,
|
||||
sha,
|
||||
},
|
||||
};
|
||||
},
|
||||
async uploadWorkspaceContent({ token, content, file }) {
|
||||
const path = store.getters.gitPathsByItemId[file.id];
|
||||
const absolutePath = `${store.getters['workspace/currentWorkspace'].path || ''}${path}`;
|
||||
const res = await gitlabHelper.uploadFile({
|
||||
...store.getters['workspace/currentWorkspace'],
|
||||
token,
|
||||
path: absolutePath,
|
||||
content: Provider.serializeContent(content),
|
||||
sha: gitWorkspaceSvc.shaByPath[path],
|
||||
});
|
||||
|
||||
// Return new sync data
|
||||
return {
|
||||
contentSyncData: {
|
||||
id: store.getters.gitPathsByItemId[content.id],
|
||||
type: content.type,
|
||||
hash: content.hash,
|
||||
sha: res.content.sha,
|
||||
},
|
||||
fileSyncData: {
|
||||
id: path,
|
||||
type: 'file',
|
||||
hash: file.hash,
|
||||
},
|
||||
};
|
||||
},
|
||||
async uploadWorkspaceData({ token, item }) {
|
||||
const path = store.getters.gitPathsByItemId[item.id];
|
||||
const syncData = {
|
||||
id: path,
|
||||
type: item.type,
|
||||
hash: item.hash,
|
||||
};
|
||||
const res = await gitlabHelper.uploadFile({
|
||||
...store.getters['workspace/currentWorkspace'],
|
||||
token,
|
||||
path: getAbsolutePath(syncData),
|
||||
content: JSON.stringify(item),
|
||||
sha: gitWorkspaceSvc.shaByPath[path],
|
||||
});
|
||||
|
||||
return {
|
||||
syncData: {
|
||||
...syncData,
|
||||
sha: res.content.sha,
|
||||
},
|
||||
};
|
||||
},
|
||||
async listFileRevisions({ token, fileSyncData }) {
|
||||
const { projectId, branch } = store.getters['workspace/currentWorkspace'];
|
||||
const entries = await gitlabHelper.getCommits({
|
||||
token,
|
||||
projectId,
|
||||
sha: branch,
|
||||
path: getAbsolutePath(fileSyncData),
|
||||
});
|
||||
|
||||
return entries.map(({
|
||||
author,
|
||||
committer,
|
||||
commit,
|
||||
sha,
|
||||
}) => {
|
||||
let user;
|
||||
if (author && author.login) {
|
||||
user = author;
|
||||
} else if (committer && committer.login) {
|
||||
user = committer;
|
||||
}
|
||||
const sub = `${gitlabHelper.subPrefix}:${user.id}`;
|
||||
userSvc.addInfo({ id: sub, name: user.login, imageUrl: user.avatar_url });
|
||||
const date = (commit.author && commit.author.date)
|
||||
|| (commit.committer && commit.committer.date);
|
||||
return {
|
||||
id: sha,
|
||||
sub,
|
||||
created: date ? new Date(date).getTime() : 1,
|
||||
};
|
||||
});
|
||||
},
|
||||
async loadFileRevision() {
|
||||
// Revisions are already loaded
|
||||
return false;
|
||||
},
|
||||
async getFileRevisionContent({
|
||||
token,
|
||||
contentId,
|
||||
fileSyncData,
|
||||
revisionId,
|
||||
}) {
|
||||
const { data } = await gitlabHelper.downloadFile({
|
||||
...store.getters['workspace/currentWorkspace'],
|
||||
token,
|
||||
branch: revisionId,
|
||||
path: getAbsolutePath(fileSyncData),
|
||||
});
|
||||
return Provider.parseContent(data, contentId);
|
||||
},
|
||||
});
|
@ -171,7 +171,7 @@ export default new Provider({
|
||||
const revisions = await googleHelper.getAppDataFileRevisions(token, contentSyncData.id);
|
||||
return revisions.map(revision => ({
|
||||
id: revision.id,
|
||||
sub: `go:${revision.lastModifyingUser.permissionId}`,
|
||||
sub: `${googleHelper.subPrefix}:${revision.lastModifyingUser.permissionId}`,
|
||||
created: new Date(revision.modifiedTime).getTime(),
|
||||
}));
|
||||
},
|
||||
|
@ -195,7 +195,7 @@ export default new Provider({
|
||||
const revisions = await googleHelper.getFileRevisions(token, syncLocation.driveFileId);
|
||||
return revisions.map(revision => ({
|
||||
id: revision.id,
|
||||
sub: `go:${revision.lastModifyingUser.permissionId}`,
|
||||
sub: `${googleHelper.subPrefix}:${revision.lastModifyingUser.permissionId}`,
|
||||
created: new Date(revision.modifiedTime).getTime(),
|
||||
}));
|
||||
},
|
||||
|
@ -512,7 +512,7 @@ export default new Provider({
|
||||
const revisions = await googleHelper.getFileRevisions(token, fileSyncData.id);
|
||||
return revisions.map(revision => ({
|
||||
id: revision.id,
|
||||
sub: `go:${revision.lastModifyingUser.permissionId}`,
|
||||
sub: `${googleHelper.subPrefix}:${revision.lastModifyingUser.permissionId}`,
|
||||
created: new Date(revision.modifiedTime).getTime(),
|
||||
}));
|
||||
},
|
||||
|
@ -1,6 +1,7 @@
|
||||
import networkSvc from '../../networkSvc';
|
||||
import utils from '../../utils';
|
||||
import store from '../../../store';
|
||||
import userSvc from '../../userSvc';
|
||||
|
||||
const request = async (token, options = {}) => {
|
||||
const baseUrl = `${token.dbUrl}/`;
|
||||
@ -117,7 +118,7 @@ export default {
|
||||
method: 'POST',
|
||||
body: { item, time: Date.now() },
|
||||
};
|
||||
const userId = store.getters['workspace/userId'];
|
||||
const userId = userSvc.getCurrentUserId();
|
||||
if (userId) {
|
||||
options.body.sub = userId;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import networkSvc from '../../networkSvc';
|
||||
import userSvc from '../../userSvc';
|
||||
import store from '../../../store';
|
||||
|
||||
const getAppKey = (fullAccess) => {
|
||||
@ -22,10 +23,40 @@ const request = ({ accessToken }, options, args) => networkSvc.request({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* https://www.dropbox.com/developers/documentation/http/documentation#users-get_account
|
||||
*/
|
||||
const subPrefix = 'db';
|
||||
userSvc.setInfoResolver('dropbox', subPrefix, async (sub) => {
|
||||
const dropboxToken = Object.values(store.getters['data/dropboxTokensBySub'])[0];
|
||||
try {
|
||||
const { body } = await request(dropboxToken, {
|
||||
method: 'POST',
|
||||
url: 'https://api.dropboxapi.com/2/users/get_account',
|
||||
body: {
|
||||
account_id: sub,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
id: `${subPrefix}:${body.account_id}`,
|
||||
name: body.name.display_name,
|
||||
imageUrl: body.profile_photo_url || '',
|
||||
};
|
||||
} catch (err) {
|
||||
if (!dropboxToken || err.status !== 404) {
|
||||
throw new Error('RETRY');
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
subPrefix,
|
||||
|
||||
/**
|
||||
* https://www.dropbox.com/developers/documentation/http/documentation#oauth2-authorize
|
||||
* https://www.dropbox.com/developers/documentation/http/documentation#users-get_current_account
|
||||
*/
|
||||
async startOauth2(fullAccess, sub = null, silent = false) {
|
||||
const { accessToken } = await networkSvc.startOauth2(
|
||||
@ -42,6 +73,11 @@ export default {
|
||||
method: 'POST',
|
||||
url: 'https://api.dropboxapi.com/2/users/get_current_account',
|
||||
});
|
||||
userSvc.addInfo({
|
||||
id: `${subPrefix}:${body.account_id}`,
|
||||
name: body.name.display_name,
|
||||
imageUrl: body.profile_photo_url || '',
|
||||
});
|
||||
|
||||
// Check the returned sub consistency
|
||||
if (sub && `${body.account_id}` !== sub) {
|
||||
@ -64,28 +100,6 @@ export default {
|
||||
return this.startOauth2(fullAccess);
|
||||
},
|
||||
|
||||
/**
|
||||
* https://www.dropbox.com/developers/documentation/http/documentation#users-get_account
|
||||
*/
|
||||
async getAccount(token, userId) {
|
||||
const { body } = await request(token, {
|
||||
method: 'POST',
|
||||
url: 'https://api.dropboxapi.com/2/users/get_account',
|
||||
body: {
|
||||
account_id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
// Add user info to the store
|
||||
store.commit('userInfo/addItem', {
|
||||
id: `db:${body.account_id}`,
|
||||
name: body.name.display_name,
|
||||
imageUrl: body.profile_photo_url || '',
|
||||
});
|
||||
|
||||
return body;
|
||||
},
|
||||
|
||||
/**
|
||||
* https://www.dropbox.com/developers/documentation/http/documentation#files-upload
|
||||
*/
|
||||
|
@ -1,6 +1,7 @@
|
||||
import utils from '../../utils';
|
||||
import networkSvc from '../../networkSvc';
|
||||
import store from '../../../store';
|
||||
import userSvc from '../../userSvc';
|
||||
|
||||
const clientId = GITHUB_CLIENT_ID;
|
||||
const getScopes = token => [token.repoFullAccess ? 'repo' : 'public_repo', 'gist'];
|
||||
@ -24,11 +25,39 @@ const repoRequest = (token, owner, repo, options) => request(token, {
|
||||
.then(res => res.body);
|
||||
|
||||
const getCommitMessage = (name, path) => {
|
||||
const message = store.getters['data/computedSettings'].github[name];
|
||||
const message = store.getters['data/computedSettings'].git[name];
|
||||
return message.replace(/{{path}}/g, path);
|
||||
};
|
||||
|
||||
/**
|
||||
* Getting a user from its userId is not feasible with API v3.
|
||||
* Using an undocumented endpoint...
|
||||
*/
|
||||
const subPrefix = 'gh';
|
||||
userSvc.setInfoResolver('github', subPrefix, async (sub) => {
|
||||
try {
|
||||
const user = (await networkSvc.request({
|
||||
url: `https://api.github.com/user/${sub}`,
|
||||
params: {
|
||||
t: Date.now(), // Prevent from caching
|
||||
},
|
||||
})).body;
|
||||
|
||||
return {
|
||||
id: `${subPrefix}:${user.id}`,
|
||||
name: user.login,
|
||||
imageUrl: user.avatar_url || '',
|
||||
};
|
||||
} catch (err) {
|
||||
if (err.status !== 404) {
|
||||
throw new Error('RETRY');
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
subPrefix,
|
||||
|
||||
/**
|
||||
* https://developer.github.com/apps/building-oauth-apps/authorization-options-for-oauth-apps/
|
||||
@ -61,6 +90,11 @@ export default {
|
||||
access_token: accessToken,
|
||||
},
|
||||
})).body;
|
||||
userSvc.addInfo({
|
||||
id: `${subPrefix}:${user.id}`,
|
||||
name: user.login,
|
||||
imageUrl: user.avatar_url || '',
|
||||
});
|
||||
|
||||
// Check the returned sub consistency
|
||||
if (sub && `${user.id}` !== sub) {
|
||||
@ -84,28 +118,6 @@ export default {
|
||||
return this.startOauth2(getScopes({ repoFullAccess }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Getting a user from its userId is not feasible with API v3.
|
||||
* Using an undocumented endpoint...
|
||||
*/
|
||||
async getUser(userId) {
|
||||
const user = (await networkSvc.request({
|
||||
url: `https://api.github.com/user/${userId}`,
|
||||
params: {
|
||||
t: Date.now(), // Prevent from caching
|
||||
},
|
||||
})).body;
|
||||
|
||||
// Add user info to the store
|
||||
store.commit('userInfo/addItem', {
|
||||
id: `gh:${user.id}`,
|
||||
name: user.login,
|
||||
imageUrl: user.avatar_url || '',
|
||||
});
|
||||
|
||||
return user;
|
||||
},
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/commits/#get-a-single-commit
|
||||
* https://developer.github.com/v3/git/trees/#get-a-tree
|
||||
|
207
src/services/providers/helpers/gitlabHelper.js
Normal file
207
src/services/providers/helpers/gitlabHelper.js
Normal file
@ -0,0 +1,207 @@
|
||||
import utils from '../../utils';
|
||||
import networkSvc from '../../networkSvc';
|
||||
import store from '../../../store';
|
||||
import userSvc from '../../userSvc';
|
||||
|
||||
const request = ({ accessToken, serverUrl }, options) => networkSvc.request({
|
||||
...options,
|
||||
url: `${serverUrl}/api/v4/${options.url}`,
|
||||
headers: {
|
||||
...options.headers || {},
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
})
|
||||
.then(res => res.body);
|
||||
|
||||
const getCommitMessage = (name, path) => {
|
||||
const message = store.getters['data/computedSettings'].git[name];
|
||||
return message.replace(/{{path}}/g, path);
|
||||
};
|
||||
|
||||
/**
|
||||
* https://docs.gitlab.com/ee/api/users.html#for-user
|
||||
*/
|
||||
const subPrefix = 'gl';
|
||||
userSvc.setInfoResolver('gitlab', subPrefix, async (sub) => {
|
||||
try {
|
||||
const [, serverUrl, id] = sub.match(/^(.+)\/([^/]+)$/);
|
||||
const user = (await networkSvc.request({
|
||||
url: `${serverUrl}/api/v4/users/${id}`,
|
||||
})).body;
|
||||
const uniqueSub = `${serverUrl}/${user.id}`;
|
||||
|
||||
return {
|
||||
id: `${subPrefix}:${uniqueSub}`,
|
||||
name: user.username,
|
||||
imageUrl: user.avatar_url || '',
|
||||
};
|
||||
} catch (err) {
|
||||
if (err.status !== 404) {
|
||||
throw new Error('RETRY');
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
subPrefix,
|
||||
|
||||
/**
|
||||
* https://docs.gitlab.com/ee/api/oauth2.html
|
||||
*/
|
||||
async startOauth2(serverUrl, applicationId, sub = null, silent = false) {
|
||||
const { accessToken } = await networkSvc.startOauth2(
|
||||
`${serverUrl}/oauth/authorize`,
|
||||
{
|
||||
client_id: applicationId,
|
||||
response_type: 'token',
|
||||
scope: 'api',
|
||||
},
|
||||
silent,
|
||||
);
|
||||
|
||||
// Call the user info endpoint
|
||||
const user = await request({ accessToken, serverUrl }, {
|
||||
url: 'user',
|
||||
});
|
||||
const uniqueSub = `${serverUrl}/${user.id}`;
|
||||
userSvc.addInfo({
|
||||
id: `${subPrefix}:${uniqueSub}`,
|
||||
name: user.username,
|
||||
imageUrl: user.avatar_url || '',
|
||||
});
|
||||
|
||||
// Check the returned sub consistency
|
||||
if (sub && uniqueSub !== sub) {
|
||||
throw new Error('GitLab account ID not expected.');
|
||||
}
|
||||
|
||||
// Build token object including scopes and sub
|
||||
const token = {
|
||||
accessToken,
|
||||
name: user.username,
|
||||
serverUrl,
|
||||
sub: uniqueSub,
|
||||
};
|
||||
|
||||
// Add token to gitlab tokens
|
||||
store.dispatch('data/addGitlabToken', token);
|
||||
return token;
|
||||
},
|
||||
addAccount(serverUrl, applicationId) {
|
||||
return this.startOauth2(serverUrl, applicationId);
|
||||
},
|
||||
|
||||
/**
|
||||
* https://docs.gitlab.com/ee/api/projects.html#get-single-project
|
||||
*/
|
||||
async getProjectId(token, { projectPath, projectId }) {
|
||||
if (projectId) {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
const project = await request(token, {
|
||||
url: `projects/${encodeURIComponent(projectPath)}`,
|
||||
});
|
||||
return project.id;
|
||||
},
|
||||
|
||||
/**
|
||||
* https://docs.gitlab.com/ee/api/repositories.html#list-repository-tree
|
||||
*/
|
||||
async getTree({
|
||||
token,
|
||||
projectId,
|
||||
branch,
|
||||
}) {
|
||||
return request(token, {
|
||||
url: `projects/${encodeURIComponent(projectId)}/repository/tree`,
|
||||
params: {
|
||||
ref: branch,
|
||||
recursive: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* https://docs.gitlab.com/ee/api/commits.html#list-repository-commits
|
||||
*/
|
||||
async getCommits({
|
||||
token,
|
||||
projectId,
|
||||
branch,
|
||||
path,
|
||||
}) {
|
||||
return request(token, {
|
||||
url: `projects/${encodeURIComponent(projectId)}/repository/tree`,
|
||||
params: {
|
||||
ref_name: branch,
|
||||
path,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* https://docs.gitlab.com/ee/api/repository_files.html#create-new-file-in-repository
|
||||
* https://docs.gitlab.com/ee/api/repository_files.html#update-existing-file-in-repository
|
||||
*/
|
||||
async uploadFile({
|
||||
token,
|
||||
projectId,
|
||||
branch,
|
||||
path,
|
||||
content,
|
||||
sha,
|
||||
}) {
|
||||
return request(token, {
|
||||
method: sha ? 'PUT' : 'POST',
|
||||
url: `projects/${encodeURIComponent(projectId)}/repository/files/${encodeURIComponent(path)}`,
|
||||
body: {
|
||||
commit_message: getCommitMessage(sha ? 'updateFileMessage' : 'createFileMessage', path),
|
||||
content,
|
||||
last_commit_id: sha,
|
||||
branch,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* https://docs.gitlab.com/ee/api/repository_files.html#delete-existing-file-in-repository
|
||||
*/
|
||||
async removeFile({
|
||||
token,
|
||||
projectId,
|
||||
branch,
|
||||
path,
|
||||
sha,
|
||||
}) {
|
||||
return request(token, {
|
||||
method: 'DELETE',
|
||||
url: `projects/${encodeURIComponent(projectId)}/repository/files/${encodeURIComponent(path)}`,
|
||||
body: {
|
||||
commit_message: getCommitMessage('deleteFileMessage', path),
|
||||
last_commit_id: sha,
|
||||
branch,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* https://docs.gitlab.com/ee/api/repository_files.html#get-file-from-repository
|
||||
*/
|
||||
async downloadFile({
|
||||
token,
|
||||
projectId,
|
||||
branch,
|
||||
path,
|
||||
}) {
|
||||
const res = await request(token, {
|
||||
url: `projects/${encodeURIComponent(projectId)}/repository/files/${encodeURIComponent(path)}`,
|
||||
params: { ref: branch },
|
||||
});
|
||||
return {
|
||||
sha: res.last_commit_id,
|
||||
data: utils.decodeBase64(res.content),
|
||||
};
|
||||
},
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
import utils from '../../utils';
|
||||
import networkSvc from '../../networkSvc';
|
||||
import store from '../../../store';
|
||||
import userSvc from '../../userSvc';
|
||||
|
||||
const clientId = GOOGLE_CLIENT_ID;
|
||||
const apiKey = 'AIzaSyC_M4RA9pY6XmM9pmFxlT59UPMO7aHr9kk';
|
||||
@ -34,7 +35,45 @@ if (utils.queryParams.providerId === 'googleDrive') {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://developers.google.com/+/web/api/rest/latest/people/get
|
||||
*/
|
||||
const getUser = async (sub, token) => {
|
||||
const { body } = await networkSvc.request(token
|
||||
? {
|
||||
method: 'GET',
|
||||
url: `https://www.googleapis.com/plus/v1/people/${sub}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token.accessToken}`,
|
||||
},
|
||||
}
|
||||
: {
|
||||
method: 'GET',
|
||||
url: `https://www.googleapis.com/plus/v1/people/${sub}?key=${apiKey}`,
|
||||
}, true);
|
||||
return body;
|
||||
};
|
||||
|
||||
const subPrefix = 'go';
|
||||
userSvc.setInfoResolver('google', subPrefix, async (sub) => {
|
||||
try {
|
||||
const googleToken = Object.values(store.getters['data/googleTokensBySub'])[0];
|
||||
const body = await getUser(sub, googleToken);
|
||||
return {
|
||||
id: `${subPrefix}:${body.id}`,
|
||||
name: body.displayName,
|
||||
imageUrl: (body.image.url || '').replace(/\bsz?=\d+$/, 'sz=40'),
|
||||
};
|
||||
} catch (err) {
|
||||
if (err.status !== 404) {
|
||||
throw new Error('RETRY');
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
subPrefix,
|
||||
folderMimeType: 'application/vnd.google-apps.folder',
|
||||
driveState,
|
||||
driveActionFolder: null,
|
||||
@ -119,16 +158,14 @@ export default {
|
||||
driveFullAccess: scopes.indexOf('https://www.googleapis.com/auth/drive') !== -1,
|
||||
};
|
||||
|
||||
try {
|
||||
// Call the user info endpoint
|
||||
token.name = (await this.getUser(token.sub)).displayName;
|
||||
} catch (err) {
|
||||
if (err.status === 404) {
|
||||
store.dispatch('notification/info', 'Please activate Google Plus to change your account name and photo.');
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
// Call the user info endpoint
|
||||
const user = await getUser('me', token);
|
||||
token.name = user.displayName;
|
||||
userSvc.addInfo({
|
||||
id: `${subPrefix}:${user.id}`,
|
||||
name: user.displayName,
|
||||
imageUrl: (user.image.url || '').replace(/\bsz?=\d+$/, 'sz=40'),
|
||||
});
|
||||
|
||||
if (existingToken) {
|
||||
// We probably retrieved a new token with restricted scopes.
|
||||
@ -413,7 +450,7 @@ export default {
|
||||
});
|
||||
revisions.forEach((revision) => {
|
||||
store.commit('userInfo/addItem', {
|
||||
id: `go:${revision.lastModifyingUser.permissionId}`,
|
||||
id: `${subPrefix}:${revision.lastModifyingUser.permissionId}`,
|
||||
name: revision.lastModifyingUser.displayName,
|
||||
imageUrl: revision.lastModifyingUser.photoLink,
|
||||
});
|
||||
@ -454,22 +491,6 @@ export default {
|
||||
return this.$downloadFileRevision(refreshedToken, fileId, revisionId);
|
||||
},
|
||||
|
||||
/**
|
||||
* https://developers.google.com/+/web/api/rest/latest/people/get
|
||||
*/
|
||||
async getUser(userId) {
|
||||
const { body } = await networkSvc.request({
|
||||
method: 'GET',
|
||||
url: `https://www.googleapis.com/plus/v1/people/${userId}?key=${apiKey}`,
|
||||
}, true);
|
||||
store.commit('userInfo/addItem', {
|
||||
id: `go:${body.id}`,
|
||||
name: body.displayName,
|
||||
imageUrl: (body.image.url || '').replace(/\bsz?=\d+$/, 'sz=40'),
|
||||
});
|
||||
return body;
|
||||
},
|
||||
|
||||
/**
|
||||
* https://developers.google.com/drive/v3/reference/changes/list
|
||||
*/
|
||||
|
@ -5,11 +5,11 @@ import Provider from './common/Provider';
|
||||
export default new Provider({
|
||||
id: 'wordpress',
|
||||
name: 'WordPress',
|
||||
getToken(location) {
|
||||
return store.getters['data/wordpressTokensBySub'][location.sub];
|
||||
getToken({ sub }) {
|
||||
return store.getters['data/wordpressTokensBySub'][sub];
|
||||
},
|
||||
getLocationUrl(location) {
|
||||
return `https://wordpress.com/post/${location.siteId}/${location.postId}`;
|
||||
getLocationUrl({ siteId, postId }) {
|
||||
return `https://wordpress.com/post/${siteId}/${postId}`;
|
||||
},
|
||||
getLocationDescription({ postId }) {
|
||||
return postId;
|
||||
|
@ -5,12 +5,12 @@ import Provider from './common/Provider';
|
||||
export default new Provider({
|
||||
id: 'zendesk',
|
||||
name: 'Zendesk',
|
||||
getToken(location) {
|
||||
return store.getters['data/zendeskTokensBySub'][location.sub];
|
||||
getToken({ sub }) {
|
||||
return store.getters['data/zendeskTokensBySub'][sub];
|
||||
},
|
||||
getLocationUrl(location) {
|
||||
const token = this.getToken(location);
|
||||
return `https://${token.subdomain}.zendesk.com/hc/${location.locale}/articles/${location.articleId}`;
|
||||
getLocationUrl({ sub, locale, articleId }) {
|
||||
const token = this.getToken({ sub });
|
||||
return `https://${token.subdomain}.zendesk.com/hc/${locale}/articles/${articleId}`;
|
||||
},
|
||||
getLocationDescription({ articleId }) {
|
||||
return articleId;
|
||||
|
@ -129,7 +129,8 @@ const createPublishLocation = (publishLocation) => {
|
||||
store.dispatch(
|
||||
'queue/enqueue',
|
||||
async () => {
|
||||
workspaceSvc.addPublishLocation(await publish(publishLocation));
|
||||
const publishLocationToStore = await publish(publishLocation);
|
||||
workspaceSvc.addPublishLocation(publishLocationToStore);
|
||||
store.dispatch('notification/info', `A new publication location was added to "${currentFile.name}".`);
|
||||
},
|
||||
);
|
||||
|
@ -7,6 +7,7 @@ import providerRegistry from './providers/common/providerRegistry';
|
||||
import googleDriveAppDataProvider from './providers/googleDriveAppDataProvider';
|
||||
import './providers/couchdbWorkspaceProvider';
|
||||
import './providers/githubWorkspaceProvider';
|
||||
import './providers/gitlabWorkspaceProvider';
|
||||
import './providers/googleDriveWorkspaceProvider';
|
||||
import tempFileSvc from './tempFileSvc';
|
||||
import workspaceSvc from './workspaceSvc';
|
||||
|
@ -1,70 +1,79 @@
|
||||
import googleHelper from './providers/helpers/googleHelper';
|
||||
import githubHelper from './providers/helpers/githubHelper';
|
||||
import store from '../store';
|
||||
import dropboxHelper from './providers/helpers/dropboxHelper';
|
||||
import constants from '../data/constants';
|
||||
|
||||
const promised = {};
|
||||
const infoPromisesByUserId = {};
|
||||
const infoResolversByType = {};
|
||||
const subPrefixesByType = {};
|
||||
const typesBySubPrefix = {};
|
||||
|
||||
const parseUserId = (userId) => {
|
||||
const prefix = userId[2] === ':' && userId.slice(0, 2);
|
||||
const type = prefix && constants.userIdPrefixes[prefix];
|
||||
const type = typesBySubPrefix[prefix];
|
||||
return type ? [type, userId.slice(3)] : ['google', userId];
|
||||
};
|
||||
|
||||
|
||||
export default {
|
||||
addInfo({ id, name, imageUrl }) {
|
||||
promised[id] = true;
|
||||
store.commit('userInfo/addItem', { id, name, imageUrl });
|
||||
setInfoResolver(type, subPrefix, resolver) {
|
||||
infoResolversByType[type] = resolver;
|
||||
subPrefixesByType[type] = subPrefix;
|
||||
typesBySubPrefix[subPrefix] = type;
|
||||
},
|
||||
getCurrentUserId() {
|
||||
const loginToken = store.getters['workspace/loginToken'];
|
||||
if (!loginToken) {
|
||||
return null;
|
||||
}
|
||||
const loginType = store.getters['workspace/loginToken'];
|
||||
const prefix = subPrefixesByType[loginType];
|
||||
return prefix ? `${prefix}:${loginToken.sub}` : loginToken.sub;
|
||||
},
|
||||
addInfo(info) {
|
||||
infoPromisesByUserId[info.id] = Promise.resolve(info);
|
||||
store.commit('userInfo/addItem', info);
|
||||
},
|
||||
async getInfo(userId) {
|
||||
if (userId && !promised[userId]) {
|
||||
const [type, sub] = parseUserId(userId);
|
||||
if (!userId) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Try to find a token with this sub
|
||||
const token = store.getters[`data/${type}TokensBySub`][sub];
|
||||
if (token) {
|
||||
store.commit('userInfo/addItem', {
|
||||
id: userId,
|
||||
name: token.name,
|
||||
});
|
||||
}
|
||||
let infoPromise = infoPromisesByUserId[userId];
|
||||
if (infoPromise) {
|
||||
return infoPromise;
|
||||
}
|
||||
|
||||
// Get user info from provider
|
||||
if (!store.state.offline) {
|
||||
promised[userId] = true;
|
||||
switch (type) {
|
||||
case 'dropbox': {
|
||||
const dropboxToken = Object.values(store.getters['data/dropboxTokensBySub'])[0];
|
||||
try {
|
||||
await dropboxHelper.getAccount(dropboxToken, sub);
|
||||
} catch (err) {
|
||||
if (!token || err.status !== 404) {
|
||||
promised[userId] = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
const [type, sub] = parseUserId(userId);
|
||||
|
||||
// Try to find a token with this sub to resolve name as soon as possible
|
||||
const token = store.getters[`data/${type}TokensBySub`][sub];
|
||||
if (token) {
|
||||
store.commit('userInfo/addItem', {
|
||||
id: userId,
|
||||
name: token.name,
|
||||
});
|
||||
}
|
||||
|
||||
if (store.state.offline) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Get user info from helper
|
||||
infoPromise = new Promise(async (resolve) => {
|
||||
const infoResolver = infoResolversByType[type];
|
||||
if (infoResolver) {
|
||||
try {
|
||||
const userInfo = await infoResolver(sub);
|
||||
this.addInfo(userInfo);
|
||||
resolve(userInfo);
|
||||
} catch (err) {
|
||||
if (err && err.message === 'RETRY') {
|
||||
infoPromisesByUserId[userId] = null;
|
||||
}
|
||||
case 'github':
|
||||
try {
|
||||
await githubHelper.getUser(sub);
|
||||
} catch (err) {
|
||||
if (err.status !== 404) {
|
||||
promised[userId] = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'google':
|
||||
default:
|
||||
try {
|
||||
await googleHelper.getUser(sub);
|
||||
} catch (err) {
|
||||
if (err.status !== 404) {
|
||||
promised[userId] = false;
|
||||
}
|
||||
}
|
||||
resolve({});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
infoPromisesByUserId[userId] = infoPromise;
|
||||
return infoPromise;
|
||||
},
|
||||
};
|
||||
|
@ -287,6 +287,10 @@ export default {
|
||||
repo: parsedRepo[2],
|
||||
};
|
||||
},
|
||||
parseGitlabProjectPath(url) {
|
||||
const parsedProject = url && url.match(/^https:\/\/[^/]+\/(.+?)(?:\.git|\/)?$/);
|
||||
return parsedProject && parsedProject[1];
|
||||
},
|
||||
createHiddenIframe(url) {
|
||||
const iframeElt = document.createElement('iframe');
|
||||
iframeElt.style.position = 'absolute';
|
||||
|
@ -85,7 +85,7 @@ const additionalTemplates = {
|
||||
|
||||
// For tokens
|
||||
const tokenAdder = providerId => ({ getters, dispatch }, token) => {
|
||||
dispatch('patchTokensByProviderId', {
|
||||
dispatch('patchTokensByType', {
|
||||
[providerId]: {
|
||||
...getters[`${providerId}TokensBySub`],
|
||||
[token.sub]: token,
|
||||
@ -188,13 +188,14 @@ export default {
|
||||
return result;
|
||||
},
|
||||
dataSyncDataById: getter('dataSyncData'),
|
||||
tokensByProviderId: getter('tokens'),
|
||||
googleTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.google || {},
|
||||
couchdbTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.couchdb || {},
|
||||
dropboxTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.dropbox || {},
|
||||
githubTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.github || {},
|
||||
wordpressTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.wordpress || {},
|
||||
zendeskTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.zendesk || {},
|
||||
tokensByType: getter('tokens'),
|
||||
googleTokensBySub: (state, { tokensByType }) => tokensByType.google || {},
|
||||
couchdbTokensBySub: (state, { tokensByType }) => tokensByType.couchdb || {},
|
||||
dropboxTokensBySub: (state, { tokensByType }) => tokensByType.dropbox || {},
|
||||
githubTokensBySub: (state, { tokensByType }) => tokensByType.github || {},
|
||||
gitlabTokensBySub: (state, { tokensByType }) => tokensByType.gitlab || {},
|
||||
wordpressTokensBySub: (state, { tokensByType }) => tokensByType.wordpress || {},
|
||||
zendeskTokensBySub: (state, { tokensByType }) => tokensByType.zendesk || {},
|
||||
},
|
||||
actions: {
|
||||
setSettings: setter('settings'),
|
||||
@ -258,11 +259,12 @@ export default {
|
||||
setSyncDataById: setter('syncData'),
|
||||
patchSyncDataById: patcher('syncData'),
|
||||
patchDataSyncDataById: patcher('dataSyncData'),
|
||||
patchTokensByProviderId: patcher('tokens'),
|
||||
patchTokensByType: patcher('tokens'),
|
||||
addGoogleToken: tokenAdder('google'),
|
||||
addCouchdbToken: tokenAdder('couchdb'),
|
||||
addDropboxToken: tokenAdder('dropbox'),
|
||||
addGithubToken: tokenAdder('github'),
|
||||
addGitlabToken: tokenAdder('gitlab'),
|
||||
addWordpressToken: tokenAdder('wordpress'),
|
||||
addZendeskToken: tokenAdder('zendesk'),
|
||||
},
|
||||
|
@ -1,6 +1,5 @@
|
||||
import utils from '../services/utils';
|
||||
import providerRegistry from '../services/providers/common/providerRegistry';
|
||||
import constants from '../data/constants';
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
@ -44,9 +43,11 @@ export default {
|
||||
currentWorkspace: ({ currentWorkspaceId }, { workspacesById, mainWorkspace }) =>
|
||||
workspacesById[currentWorkspaceId] || mainWorkspace,
|
||||
currentWorkspaceIsGit: (state, { currentWorkspace }) =>
|
||||
currentWorkspace.providerId === 'githubWorkspace',
|
||||
currentWorkspace.providerId === 'githubWorkspace'
|
||||
|| currentWorkspace.providerId === 'gitlabWorkspace',
|
||||
currentWorkspaceHasUniquePaths: (state, { currentWorkspace }) =>
|
||||
currentWorkspace.providerId === 'githubWorkspace',
|
||||
currentWorkspace.providerId === 'githubWorkspace'
|
||||
|| currentWorkspace.providerId === 'gitlabWorkspace',
|
||||
lastSyncActivityKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastSyncActivity`,
|
||||
lastFocusKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastWindowFocus`,
|
||||
mainWorkspaceToken: (state, getters, rootState, rootGetters) =>
|
||||
@ -62,33 +63,28 @@ export default {
|
||||
return rootGetters['data/googleTokensBySub'][currentWorkspace.sub];
|
||||
case 'githubWorkspace':
|
||||
return rootGetters['data/githubTokensBySub'][currentWorkspace.sub];
|
||||
case 'gitlabWorkspace':
|
||||
return rootGetters['data/gitlabTokensBySub'][currentWorkspace.sub];
|
||||
case 'couchdbWorkspace':
|
||||
return rootGetters['data/couchdbTokensBySub'][currentWorkspace.id];
|
||||
default:
|
||||
return mainWorkspaceToken;
|
||||
}
|
||||
},
|
||||
loginToken: (state, { currentWorkspace, mainWorkspaceToken }, rootState, rootGetters) => {
|
||||
loginType: (state, { currentWorkspace }) => {
|
||||
switch (currentWorkspace.providerId) {
|
||||
case 'googleDriveWorkspace':
|
||||
return rootGetters['data/googleTokensBySub'][currentWorkspace.sub];
|
||||
case 'githubWorkspace':
|
||||
return rootGetters['data/githubTokensBySub'][currentWorkspace.sub];
|
||||
default:
|
||||
return mainWorkspaceToken;
|
||||
return 'google';
|
||||
case 'githubWorkspace':
|
||||
return 'github';
|
||||
case 'gitlabWorkspace':
|
||||
return 'gitlab';
|
||||
}
|
||||
},
|
||||
userId: (state, { loginToken }, rootState, rootGetters) => {
|
||||
if (!loginToken) {
|
||||
return null;
|
||||
}
|
||||
const prefix = utils.someResult(Object.entries(constants.userIdPrefixes), ([key, value]) => {
|
||||
if (rootGetters[`data/${value}TokensBySub`][loginToken.sub]) {
|
||||
return key;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
return prefix ? `${prefix}:${loginToken.sub}` : loginToken.sub;
|
||||
loginToken: (state, { loginType, currentWorkspace }, rootState, rootGetters) => {
|
||||
const tokensBySub = rootGetters['data/tokensByType'][loginType];
|
||||
return tokensBySub && tokensBySub[currentWorkspace.sub];
|
||||
},
|
||||
sponsorToken: (state, { mainWorkspaceToken }) => mainWorkspaceToken,
|
||||
},
|
||||
|
@ -49,6 +49,10 @@ body {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
outline: #349be8 auto 5px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -153,6 +157,7 @@ textarea {
|
||||
color: #fff;
|
||||
margin: -2px 0 -2px 4px;
|
||||
padding: 10px 20px;
|
||||
font-size: 18px;
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
|
@ -9,10 +9,10 @@ $font-size-monospace: 0.85em;
|
||||
$highlighting-color: #ff0;
|
||||
$selection-highlighting-color: #ff9632;
|
||||
$info-bg: transparentize($selection-highlighting-color, 0.85);
|
||||
$code-border-radius: 2px;
|
||||
$code-border-radius: 3px;
|
||||
$link-color: #0c93e4;
|
||||
$error-color: #f31;
|
||||
$border-radius-base: 2px;
|
||||
$border-radius-base: 3px;
|
||||
$hr-color: rgba(128, 128, 128, 0.2);
|
||||
$navbar-bg: #2c2c2c;
|
||||
$navbar-color: mix($navbar-bg, #fff, 33%);
|
||||
|
Loading…
Reference in New Issue
Block a user