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 networkSvc from '../services/networkSvc';
|
||||||
import sponsorSvc from '../services/sponsorSvc';
|
import sponsorSvc from '../services/sponsorSvc';
|
||||||
import tempFileSvc from '../services/tempFileSvc';
|
import tempFileSvc from '../services/tempFileSvc';
|
||||||
|
import store from '../store';
|
||||||
import './common/vueGlobals';
|
import './common/vueGlobals';
|
||||||
|
|
||||||
const themeClasses = {
|
const themeClasses = {
|
||||||
@ -41,7 +42,7 @@ export default {
|
|||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
classes() {
|
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;
|
return Array.isArray(result) ? result : themeClasses.light;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -57,7 +58,7 @@ export default {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
} else if (err && err.message !== 'RELOAD') {
|
} else if (err && err.message !== 'RELOAD') {
|
||||||
console.error(err); // eslint-disable-line no-console
|
console.error(err); // eslint-disable-line no-console
|
||||||
this.$store.dispatch('notification/error', err);
|
store.dispatch('notification/error', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex';
|
import { mapState } from 'vuex';
|
||||||
|
import store from '../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
@ -24,7 +25,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
close(item = null) {
|
close(item = null) {
|
||||||
this.resolve(item);
|
this.resolve(item);
|
||||||
this.$store.dispatch('contextMenu/close');
|
store.dispatch('contextMenu/close');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import CommentList from './gutters/CommentList';
|
import CommentList from './gutters/CommentList';
|
||||||
import EditorNewDiscussionButton from './gutters/EditorNewDiscussionButton';
|
import EditorNewDiscussionButton from './gutters/EditorNewDiscussionButton';
|
||||||
|
import store from '../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -52,11 +53,11 @@ export default {
|
|||||||
editorElt.addEventListener('mouseover', onDiscussionEvt(classToggler(true)));
|
editorElt.addEventListener('mouseover', onDiscussionEvt(classToggler(true)));
|
||||||
editorElt.addEventListener('mouseout', onDiscussionEvt(classToggler(false)));
|
editorElt.addEventListener('mouseout', onDiscussionEvt(classToggler(false)));
|
||||||
editorElt.addEventListener('click', onDiscussionEvt((discussionId) => {
|
editorElt.addEventListener('click', onDiscussionEvt((discussionId) => {
|
||||||
this.$store.commit('discussion/setCurrentDiscussionId', discussionId);
|
store.commit('discussion/setCurrentDiscussionId', discussionId);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.$store.state.discussion.currentDiscussionId,
|
() => store.state.discussion.currentDiscussionId,
|
||||||
(discussionId, oldDiscussionId) => {
|
(discussionId, oldDiscussionId) => {
|
||||||
if (oldDiscussionId) {
|
if (oldDiscussionId) {
|
||||||
editorElt.querySelectorAll(`.discussion-editor-highlighting--${oldDiscussionId}`)
|
editorElt.querySelectorAll(`.discussion-editor-highlighting--${oldDiscussionId}`)
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
import { mapState, mapGetters, mapActions } from 'vuex';
|
import { mapState, mapGetters, mapActions } from 'vuex';
|
||||||
import ExplorerNode from './ExplorerNode';
|
import ExplorerNode from './ExplorerNode';
|
||||||
import explorerSvc from '../services/explorerSvc';
|
import explorerSvc from '../services/explorerSvc';
|
||||||
|
import store from '../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -55,16 +56,16 @@ export default {
|
|||||||
editItem() {
|
editItem() {
|
||||||
const node = this.selectedNode;
|
const node = this.selectedNode;
|
||||||
if (!node.isTrash && !node.isTemp) {
|
if (!node.isTrash && !node.isTemp) {
|
||||||
this.$store.commit('explorer/setEditingId', node.item.id);
|
store.commit('explorer/setEditingId', node.item.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.$store.getters['file/current'].id,
|
() => store.getters['file/current'].id,
|
||||||
(currentFileId) => {
|
(currentFileId) => {
|
||||||
this.$store.commit('explorer/setSelectedId', currentFileId);
|
store.commit('explorer/setSelectedId', currentFileId);
|
||||||
this.$store.dispatch('explorer/openNode', currentFileId);
|
store.dispatch('explorer/openNode', currentFileId);
|
||||||
}, {
|
}, {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
},
|
},
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
import { mapMutations, mapActions } from 'vuex';
|
import { mapMutations, mapActions } from 'vuex';
|
||||||
import workspaceSvc from '../services/workspaceSvc';
|
import workspaceSvc from '../services/workspaceSvc';
|
||||||
import explorerSvc from '../services/explorerSvc';
|
import explorerSvc from '../services/explorerSvc';
|
||||||
|
import store from '../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'explorer-node', // Required for recursivity
|
name: 'explorer-node', // Required for recursivity
|
||||||
@ -36,35 +37,35 @@ export default {
|
|||||||
return `${(this.depth + 1) * 15}px`;
|
return `${(this.depth + 1) * 15}px`;
|
||||||
},
|
},
|
||||||
isSelected() {
|
isSelected() {
|
||||||
return this.$store.getters['explorer/selectedNode'] === this.node;
|
return store.getters['explorer/selectedNode'] === this.node;
|
||||||
},
|
},
|
||||||
isEditing() {
|
isEditing() {
|
||||||
return this.$store.getters['explorer/editingNode'] === this.node;
|
return store.getters['explorer/editingNode'] === this.node;
|
||||||
},
|
},
|
||||||
isDragTarget() {
|
isDragTarget() {
|
||||||
return this.$store.getters['explorer/dragTargetNode'] === this.node;
|
return store.getters['explorer/dragTargetNode'] === this.node;
|
||||||
},
|
},
|
||||||
isDragTargetFolder() {
|
isDragTargetFolder() {
|
||||||
return this.$store.getters['explorer/dragTargetNodeFolder'] === this.node;
|
return store.getters['explorer/dragTargetNodeFolder'] === this.node;
|
||||||
},
|
},
|
||||||
isOpen() {
|
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() {
|
newChild() {
|
||||||
return this.$store.getters['explorer/newChildNodeParent'] === this.node
|
return store.getters['explorer/newChildNodeParent'] === this.node
|
||||||
&& this.$store.state.explorer.newChildNode;
|
&& store.state.explorer.newChildNode;
|
||||||
},
|
},
|
||||||
newChildName: {
|
newChildName: {
|
||||||
get() {
|
get() {
|
||||||
return this.$store.state.explorer.newChildNode.item.name;
|
return store.state.explorer.newChildNode.item.name;
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
this.$store.commit('explorer/setNewItemName', value);
|
store.commit('explorer/setNewItemName', value);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
editingNodeName: {
|
editingNodeName: {
|
||||||
get() {
|
get() {
|
||||||
return this.$store.getters['explorer/editingNode'].item.name;
|
return store.getters['explorer/editingNode'].item.name;
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
this.editingValue = value.trim();
|
this.editingValue = value.trim();
|
||||||
@ -79,25 +80,25 @@ export default {
|
|||||||
'setDragTarget',
|
'setDragTarget',
|
||||||
]),
|
]),
|
||||||
select(id = this.node.item.id, doOpen = true) {
|
select(id = this.node.item.id, doOpen = true) {
|
||||||
const node = this.$store.getters['explorer/nodeMap'][id];
|
const node = store.getters['explorer/nodeMap'][id];
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.$store.commit('explorer/setSelectedId', id);
|
store.commit('explorer/setSelectedId', id);
|
||||||
if (doOpen) {
|
if (doOpen) {
|
||||||
// Prevent from freezing the UI while loading the file
|
// Prevent from freezing the UI while loading the file
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (node.isFolder) {
|
if (node.isFolder) {
|
||||||
this.$store.commit('explorer/toggleOpenNode', id);
|
store.commit('explorer/toggleOpenNode', id);
|
||||||
} else {
|
} else {
|
||||||
this.$store.commit('file/setCurrentId', id);
|
store.commit('file/setCurrentId', id);
|
||||||
}
|
}
|
||||||
}, 10);
|
}, 10);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
async submitNewChild(cancel) {
|
async submitNewChild(cancel) {
|
||||||
const { newChildNode } = this.$store.state.explorer;
|
const { newChildNode } = store.state.explorer;
|
||||||
if (!cancel && !newChildNode.isNil && newChildNode.item.name) {
|
if (!cancel && !newChildNode.isNil && newChildNode.item.name) {
|
||||||
try {
|
try {
|
||||||
if (newChildNode.isFolder) {
|
if (newChildNode.isFolder) {
|
||||||
@ -111,10 +112,10 @@ export default {
|
|||||||
// Cancel
|
// Cancel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.$store.commit('explorer/setNewItem', null);
|
store.commit('explorer/setNewItem', null);
|
||||||
},
|
},
|
||||||
async submitEdit(cancel) {
|
async submitEdit(cancel) {
|
||||||
const { item } = this.$store.getters['explorer/editingNode'];
|
const { item } = store.getters['explorer/editingNode'];
|
||||||
const value = this.editingValue;
|
const value = this.editingValue;
|
||||||
this.setEditingId(null);
|
this.setEditingId(null);
|
||||||
if (!cancel && item.id && value) {
|
if (!cancel && item.id && value) {
|
||||||
@ -133,14 +134,14 @@ export default {
|
|||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.$store.commit('explorer/setDragSourceId', this.node.item.id);
|
store.commit('explorer/setDragSourceId', this.node.item.id);
|
||||||
// Fix for Firefox
|
// Fix for Firefox
|
||||||
// See https://stackoverflow.com/a/3977637/1333165
|
// See https://stackoverflow.com/a/3977637/1333165
|
||||||
evt.dataTransfer.setData('Text', '');
|
evt.dataTransfer.setData('Text', '');
|
||||||
},
|
},
|
||||||
onDrop() {
|
onDrop() {
|
||||||
const sourceNode = this.$store.getters['explorer/dragSourceNode'];
|
const sourceNode = store.getters['explorer/dragSourceNode'];
|
||||||
const targetNode = this.$store.getters['explorer/dragTargetNodeFolder'];
|
const targetNode = store.getters['explorer/dragTargetNodeFolder'];
|
||||||
this.setDragTarget();
|
this.setDragTarget();
|
||||||
if (!sourceNode.isNil
|
if (!sourceNode.isNil
|
||||||
&& !targetNode.isNil
|
&& !targetNode.isNil
|
||||||
@ -156,7 +157,7 @@ export default {
|
|||||||
if (this.select(undefined, false)) {
|
if (this.select(undefined, false)) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
const item = await this.$store.dispatch('contextMenu/open', {
|
const item = await store.dispatch('contextMenu/open', {
|
||||||
coordinates: {
|
coordinates: {
|
||||||
left: evt.clientX,
|
left: evt.clientX,
|
||||||
top: evt.clientY,
|
top: evt.clientY,
|
||||||
|
@ -223,7 +223,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
this.$store.commit('findReplace/setType');
|
store.commit('findReplace/setType');
|
||||||
},
|
},
|
||||||
onEscape() {
|
onEscape() {
|
||||||
editorSvc.clEditor.focus();
|
editorSvc.clEditor.focus();
|
||||||
@ -260,7 +260,7 @@ export default {
|
|||||||
this.onKeyup = (evt) => {
|
this.onKeyup = (evt) => {
|
||||||
if (evt.which === 27) {
|
if (evt.which === 27) {
|
||||||
// Esc key
|
// Esc key
|
||||||
this.$store.commit('findReplace/setType');
|
store.commit('findReplace/setType');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener('keyup', this.onKeyup);
|
window.addEventListener('keyup', this.onKeyup);
|
||||||
|
@ -63,6 +63,7 @@ import CurrentDiscussion from './gutters/CurrentDiscussion';
|
|||||||
import FindReplace from './FindReplace';
|
import FindReplace from './FindReplace';
|
||||||
import editorSvc from '../services/editorSvc';
|
import editorSvc from '../services/editorSvc';
|
||||||
import markdownConversionSvc from '../services/markdownConversionSvc';
|
import markdownConversionSvc from '../services/markdownConversionSvc';
|
||||||
|
import store from '../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -96,7 +97,7 @@ export default {
|
|||||||
'layoutSettings',
|
'layoutSettings',
|
||||||
]),
|
]),
|
||||||
showFindReplace() {
|
showFindReplace() {
|
||||||
return !!this.$store.state.findReplace.type;
|
return !!store.state.findReplace.type;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="modal" v-if="config" @keydown.esc="onEscape" @keydown.tab="onTab" @focusin="onFocusInOut" @focusout="onFocusInOut">
|
<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>
|
<component v-if="currentModalComponent" :is="currentModalComponent"></component>
|
||||||
<modal-inner v-else aria-label="Dialog">
|
<modal-inner v-else aria-label="Dialog">
|
||||||
<div class="modal__content" v-html="simpleModal.contentHtml(config)"></div>
|
<div class="modal__content" v-html="simpleModal.contentHtml(config)"></div>
|
||||||
@ -15,6 +19,10 @@
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import simpleModals from '../data/simpleModals';
|
import simpleModals from '../data/simpleModals';
|
||||||
import editorSvc from '../services/editorSvc';
|
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 ModalInner from './modals/common/ModalInner';
|
||||||
import FilePropertiesModal from './modals/FilePropertiesModal';
|
import FilePropertiesModal from './modals/FilePropertiesModal';
|
||||||
import SettingsModal from './modals/SettingsModal';
|
import SettingsModal from './modals/SettingsModal';
|
||||||
@ -46,6 +54,11 @@ import GithubWorkspaceModal from './modals/providers/GithubWorkspaceModal';
|
|||||||
import GithubPublishModal from './modals/providers/GithubPublishModal';
|
import GithubPublishModal from './modals/providers/GithubPublishModal';
|
||||||
import GistSyncModal from './modals/providers/GistSyncModal';
|
import GistSyncModal from './modals/providers/GistSyncModal';
|
||||||
import GistPublishModal from './modals/providers/GistPublishModal';
|
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 WordpressPublishModal from './modals/providers/WordpressPublishModal';
|
||||||
import BloggerPublishModal from './modals/providers/BloggerPublishModal';
|
import BloggerPublishModal from './modals/providers/BloggerPublishModal';
|
||||||
import BloggerPagePublishModal from './modals/providers/BloggerPagePublishModal';
|
import BloggerPagePublishModal from './modals/providers/BloggerPagePublishModal';
|
||||||
@ -54,7 +67,7 @@ import ZendeskPublishModal from './modals/providers/ZendeskPublishModal';
|
|||||||
import CouchdbWorkspaceModal from './modals/providers/CouchdbWorkspaceModal';
|
import CouchdbWorkspaceModal from './modals/providers/CouchdbWorkspaceModal';
|
||||||
import CouchdbCredentialsModal from './modals/providers/CouchdbCredentialsModal';
|
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
|
// Filter enabled and visible element
|
||||||
.cl_filter(el => !el.disabled && el.offsetParent !== null && !el.classList.contains('not-tabbable'));
|
.cl_filter(el => !el.disabled && el.offsetParent !== null && !el.classList.contains('not-tabbable'));
|
||||||
|
|
||||||
@ -90,6 +103,11 @@ export default {
|
|||||||
GithubPublishModal,
|
GithubPublishModal,
|
||||||
GistSyncModal,
|
GistSyncModal,
|
||||||
GistPublishModal,
|
GistPublishModal,
|
||||||
|
GitlabAccountModal,
|
||||||
|
GitlabOpenModal,
|
||||||
|
GitlabPublishModal,
|
||||||
|
GitlabSaveModal,
|
||||||
|
GitlabWorkspaceModal,
|
||||||
WordpressPublishModal,
|
WordpressPublishModal,
|
||||||
BloggerPublishModal,
|
BloggerPublishModal,
|
||||||
BloggerPagePublishModal,
|
BloggerPagePublishModal,
|
||||||
@ -99,6 +117,9 @@ export default {
|
|||||||
CouchdbCredentialsModal,
|
CouchdbCredentialsModal,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'isSponsor',
|
||||||
|
]),
|
||||||
...mapGetters('modal', [
|
...mapGetters('modal', [
|
||||||
'config',
|
'config',
|
||||||
]),
|
]),
|
||||||
@ -118,6 +139,19 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
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() {
|
onEscape() {
|
||||||
this.config.reject();
|
this.config.reject();
|
||||||
editorSvc.clEditor.focus();
|
editorSvc.clEditor.focus();
|
||||||
@ -135,25 +169,17 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onFocusInOut(evt) {
|
onFocusInOut(evt) {
|
||||||
const isFocusIn = evt.type === 'focusin';
|
const { parentNode } = evt.target;
|
||||||
if (evt.target.parentNode && evt.target.parentNode.parentNode) {
|
if (parentNode && parentNode.parentNode) {
|
||||||
// Focus effect
|
// Focus effect
|
||||||
if (evt.target.parentNode.classList.contains('form-entry__field')
|
if (parentNode.classList.contains('form-entry__field')
|
||||||
&& evt.target.parentNode.parentNode.classList.contains('form-entry')) {
|
&& parentNode.parentNode.classList.contains('form-entry')) {
|
||||||
evt.target.parentNode.parentNode.classList.toggle('form-entry--focused', isFocusIn);
|
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() {
|
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 {
|
.modal__inner-1 {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -291,7 +329,7 @@ export default {
|
|||||||
.form-entry__label {
|
.form-entry__label {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: #a0a0a0;
|
color: #808080;
|
||||||
|
|
||||||
.form-entry--focused & {
|
.form-entry--focused & {
|
||||||
color: darken($link-color, 10%);
|
color: darken($link-color, 10%);
|
||||||
@ -307,17 +345,19 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-entry__field {
|
.form-entry__field {
|
||||||
border: 1px solid #d8d8d8;
|
border: 1px solid #b0b0b0;
|
||||||
border-radius: $border-radius-base;
|
border-radius: $border-radius-base;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.form-entry--focused & {
|
.form-entry--focused & {
|
||||||
border-color: $link-color;
|
border-color: $link-color;
|
||||||
|
box-shadow: 0 0 0 2.5px transparentize($link-color, 0.67);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-entry--error & {
|
.form-entry--error & {
|
||||||
border-color: $error-color;
|
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 {
|
.form-entry__info {
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
opacity: 0.5;
|
opacity: 0.67;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
margin: 0.25em 0;
|
margin: 0.25em 0;
|
||||||
}
|
}
|
||||||
|
@ -119,11 +119,11 @@ export default {
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
isSyncPossible() {
|
isSyncPossible() {
|
||||||
return this.$store.getters['workspace/syncToken'] ||
|
return store.getters['workspace/syncToken'] ||
|
||||||
this.$store.getters['syncLocation/current'].length;
|
store.getters['syncLocation/current'].length;
|
||||||
},
|
},
|
||||||
showSpinner() {
|
showSpinner() {
|
||||||
return !this.$store.state.queue.isEmpty;
|
return !store.state.queue.isEmpty;
|
||||||
},
|
},
|
||||||
titleWidth() {
|
titleWidth() {
|
||||||
if (!this.mounted) {
|
if (!this.mounted) {
|
||||||
@ -152,7 +152,7 @@ export default {
|
|||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
editCancelTrigger() {
|
editCancelTrigger() {
|
||||||
const current = this.$store.getters['file/current'];
|
const current = store.getters['file/current'];
|
||||||
return utils.serializeObject([
|
return utils.serializeObject([
|
||||||
current.id,
|
current.id,
|
||||||
current.name,
|
current.name,
|
||||||
@ -187,7 +187,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
pagedownClick(name) {
|
pagedownClick(name) {
|
||||||
if (this.$store.getters['content/isCurrentEditable']) {
|
if (store.getters['content/isCurrentEditable']) {
|
||||||
editorSvc.pagedownEditor.uiManager.doClick(name);
|
editorSvc.pagedownEditor.uiManager.doClick(name);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -197,11 +197,11 @@ export default {
|
|||||||
this.titleInputElt.setSelectionRange(0, this.titleInputElt.value.length);
|
this.titleInputElt.setSelectionRange(0, this.titleInputElt.value.length);
|
||||||
} else {
|
} else {
|
||||||
const title = this.title.trim();
|
const title = this.title.trim();
|
||||||
this.title = this.$store.getters['file/current'].name;
|
this.title = store.getters['file/current'].name;
|
||||||
if (title) {
|
if (title) {
|
||||||
try {
|
try {
|
||||||
await workspaceSvc.storeItem({
|
await workspaceSvc.storeItem({
|
||||||
...this.$store.getters['file/current'],
|
...store.getters['file/current'],
|
||||||
name: title,
|
name: title,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
import { mapGetters, mapActions } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
import CommentList from './gutters/CommentList';
|
import CommentList from './gutters/CommentList';
|
||||||
import PreviewNewDiscussionButton from './gutters/PreviewNewDiscussionButton';
|
import PreviewNewDiscussionButton from './gutters/PreviewNewDiscussionButton';
|
||||||
|
import store from '../store';
|
||||||
|
|
||||||
const appUri = `${window.location.protocol}//${window.location.host}`;
|
const appUri = `${window.location.protocol}//${window.location.host}`;
|
||||||
|
|
||||||
@ -84,11 +85,11 @@ export default {
|
|||||||
previewElt.addEventListener('mouseover', onDiscussionEvt(classToggler(true)));
|
previewElt.addEventListener('mouseover', onDiscussionEvt(classToggler(true)));
|
||||||
previewElt.addEventListener('mouseout', onDiscussionEvt(classToggler(false)));
|
previewElt.addEventListener('mouseout', onDiscussionEvt(classToggler(false)));
|
||||||
previewElt.addEventListener('click', onDiscussionEvt((discussionId) => {
|
previewElt.addEventListener('click', onDiscussionEvt((discussionId) => {
|
||||||
this.$store.commit('discussion/setCurrentDiscussionId', discussionId);
|
store.commit('discussion/setCurrentDiscussionId', discussionId);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.$store.state.discussion.currentDiscussionId,
|
() => store.state.discussion.currentDiscussionId,
|
||||||
(discussionId, oldDiscussionId) => {
|
(discussionId, oldDiscussionId) => {
|
||||||
if (oldDiscussionId) {
|
if (oldDiscussionId) {
|
||||||
previewElt.querySelectorAll(`.discussion-preview-highlighting--${oldDiscussionId}`)
|
previewElt.querySelectorAll(`.discussion-preview-highlighting--${oldDiscussionId}`)
|
||||||
|
@ -44,6 +44,7 @@ import ImportMenu from './menus/ImportMenu';
|
|||||||
import MoreMenu from './menus/MoreMenu';
|
import MoreMenu from './menus/MoreMenu';
|
||||||
import markdownSample from '../data/markdownSample.md';
|
import markdownSample from '../data/markdownSample.md';
|
||||||
import markdownConversionSvc from '../services/markdownConversionSvc';
|
import markdownConversionSvc from '../services/markdownConversionSvc';
|
||||||
|
import store from '../store';
|
||||||
|
|
||||||
const panelNames = {
|
const panelNames = {
|
||||||
menu: 'Menu',
|
menu: 'Menu',
|
||||||
@ -75,10 +76,10 @@ export default {
|
|||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
panel() {
|
panel() {
|
||||||
if (this.$store.state.light) {
|
if (store.state.light) {
|
||||||
return null; // No menu in light mode
|
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';
|
return panelNames[result] ? result : 'menu';
|
||||||
},
|
},
|
||||||
panelName() {
|
panelName() {
|
||||||
@ -173,7 +174,7 @@ export default {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: -10px -10px 10px;
|
margin: -10px -10px 10px;
|
||||||
background-color: $info-bg;
|
background-color: $info-bg;
|
||||||
font-size: 0.9em;
|
font-size: 0.95em;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import store from '../store';
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
'welcome',
|
'welcome',
|
||||||
@ -106,7 +107,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
finish() {
|
finish() {
|
||||||
this.$store.dispatch('data/patchLayoutSettings', {
|
store.dispatch('data/patchLayoutSettings', {
|
||||||
welcomeTourFinished: true,
|
welcomeTourFinished: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -116,7 +117,7 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.$store.getters['layout/styles'],
|
() => store.getters['layout/styles'],
|
||||||
() => this.updatePositions(),
|
() => this.updatePositions(),
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
@ -5,16 +5,22 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import userSvc from '../services/userSvc';
|
import userSvc from '../services/userSvc';
|
||||||
|
import store from '../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['userId'],
|
props: ['userId'],
|
||||||
computed: {
|
computed: {
|
||||||
url() {
|
url() {
|
||||||
userSvc.getInfo(this.userId);
|
const userInfo = store.state.userInfo.itemsById[this.userId];
|
||||||
const userInfo = this.$store.state.userInfo.itemsById[this.userId];
|
|
||||||
return userInfo && userInfo.imageUrl && `url('${userInfo.imageUrl}')`;
|
return userInfo && userInfo.imageUrl && `url('${userInfo.imageUrl}')`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
userId: {
|
||||||
|
handler: userId => userSvc.getInfo(userId),
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -4,15 +4,21 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import userSvc from '../services/userSvc';
|
import userSvc from '../services/userSvc';
|
||||||
|
import store from '../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['userId'],
|
props: ['userId'],
|
||||||
computed: {
|
computed: {
|
||||||
name() {
|
name() {
|
||||||
userSvc.getInfo(this.userId);
|
const userInfo = store.state.userInfo.itemsById[this.userId];
|
||||||
const userInfo = this.$store.state.userInfo.itemsById[this.userId];
|
|
||||||
return userInfo ? userInfo.name : 'Someone';
|
return userInfo ? userInfo.name : 'Someone';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
userId: {
|
||||||
|
handler: userId => userSvc.getInfo(userId),
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -27,6 +27,7 @@ import UserImage from '../UserImage';
|
|||||||
import UserName from '../UserName';
|
import UserName from '../UserName';
|
||||||
import editorSvc from '../../services/editorSvc';
|
import editorSvc from '../../services/editorSvc';
|
||||||
import htmlSanitizer from '../../libs/htmlSanitizer';
|
import htmlSanitizer from '../../libs/htmlSanitizer';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -36,8 +37,8 @@ export default {
|
|||||||
props: ['comment'],
|
props: ['comment'],
|
||||||
computed: {
|
computed: {
|
||||||
showReply() {
|
showReply() {
|
||||||
return this.comment === this.$store.getters['discussion/currentDiscussionLastComment'] &&
|
return this.comment === store.getters['discussion/currentDiscussionLastComment'] &&
|
||||||
!this.$store.state.discussion.isCommenting;
|
!store.state.discussion.isCommenting;
|
||||||
},
|
},
|
||||||
text() {
|
text() {
|
||||||
return htmlSanitizer.sanitizeHtml(editorSvc.converter.render(this.comment.text));
|
return htmlSanitizer.sanitizeHtml(editorSvc.converter.render(this.comment.text));
|
||||||
@ -49,8 +50,8 @@ export default {
|
|||||||
]),
|
]),
|
||||||
async removeComment() {
|
async removeComment() {
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('modal/open', 'commentDeletion');
|
await store.dispatch('modal/open', 'commentDeletion');
|
||||||
this.$store.dispatch('discussion/cleanCurrentFile', { filterComment: this.comment });
|
store.dispatch('discussion/cleanCurrentFile', { filterComment: this.comment });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Cancel
|
// Cancel
|
||||||
}
|
}
|
||||||
@ -59,7 +60,7 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
const isSticky = this.$el.parentNode.classList.contains('sticky-comment');
|
const isSticky = this.$el.parentNode.classList.contains('sticky-comment');
|
||||||
if (isSticky) {
|
if (isSticky) {
|
||||||
const commentId = this.$store.getters['discussion/currentDiscussionLastCommentId'];
|
const commentId = store.getters['discussion/currentDiscussionLastCommentId'];
|
||||||
const scrollerElt = this.$el.querySelector('.comment__text-inner');
|
const scrollerElt = this.$el.querySelector('.comment__text-inner');
|
||||||
|
|
||||||
let scrollerMirrorElt;
|
let scrollerMirrorElt;
|
||||||
|
@ -13,6 +13,7 @@ import { mapState, mapGetters, mapMutations } from 'vuex';
|
|||||||
import Comment from './Comment';
|
import Comment from './Comment';
|
||||||
import NewComment from './NewComment';
|
import NewComment from './NewComment';
|
||||||
import editorSvc from '../../services/editorSvc';
|
import editorSvc from '../../services/editorSvc';
|
||||||
|
import store from '../../store';
|
||||||
import utils from '../../services/utils';
|
import utils from '../../services/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -63,7 +64,7 @@ export default {
|
|||||||
'setCurrentDiscussionId',
|
'setCurrentDiscussionId',
|
||||||
]),
|
]),
|
||||||
updateTops() {
|
updateTops() {
|
||||||
const layoutSettings = this.$store.getters['data/layoutSettings'];
|
const layoutSettings = store.getters['data/layoutSettings'];
|
||||||
const minTop = -2;
|
const minTop = -2;
|
||||||
let minCommentTop = minTop;
|
let minCommentTop = minTop;
|
||||||
const getTop = (discussion, commentElt1, commentElt2, isCurrent) => {
|
const getTop = (discussion, commentElt1, commentElt2, isCurrent) => {
|
||||||
@ -126,15 +127,15 @@ export default {
|
|||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const layoutSettings = this.$store.getters['data/layoutSettings'];
|
const layoutSettings = store.getters['data/layoutSettings'];
|
||||||
this.scrollerElt = layoutSettings.showEditor
|
this.scrollerElt = layoutSettings.showEditor
|
||||||
? editorSvc.editorElt.parentNode
|
? editorSvc.editorElt.parentNode
|
||||||
: editorSvc.previewElt.parentNode;
|
: editorSvc.previewElt.parentNode;
|
||||||
|
|
||||||
this.updateSticky = () => {
|
this.updateSticky = () => {
|
||||||
const commitIfDifferent = (value) => {
|
const commitIfDifferent = (value) => {
|
||||||
if (this.$store.state.discussion.stickyComment !== value) {
|
if (store.state.discussion.stickyComment !== value) {
|
||||||
this.$store.commit('discussion/setStickyComment', value);
|
store.commit('discussion/setStickyComment', value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let height = 0;
|
let height = 0;
|
||||||
|
@ -33,6 +33,7 @@ import editorSvc from '../../services/editorSvc';
|
|||||||
import animationSvc from '../../services/animationSvc';
|
import animationSvc from '../../services/animationSvc';
|
||||||
import markdownConversionSvc from '../../services/markdownConversionSvc';
|
import markdownConversionSvc from '../../services/markdownConversionSvc';
|
||||||
import StickyComment from './StickyComment';
|
import StickyComment from './StickyComment';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -72,7 +73,7 @@ export default {
|
|||||||
]),
|
]),
|
||||||
goToDiscussion(discussionId = this.currentDiscussionId) {
|
goToDiscussion(discussionId = this.currentDiscussionId) {
|
||||||
this.setCurrentDiscussionId(discussionId);
|
this.setCurrentDiscussionId(discussionId);
|
||||||
const layoutSettings = this.$store.getters['data/layoutSettings'];
|
const layoutSettings = store.getters['data/layoutSettings'];
|
||||||
const discussion = this.currentFileDiscussions[discussionId];
|
const discussion = this.currentFileDiscussions[discussionId];
|
||||||
const coordinates = layoutSettings.showEditor
|
const coordinates = layoutSettings.showEditor
|
||||||
? editorSvc.clEditor.selectionMgr.getCoordinates(discussion.end)
|
? editorSvc.clEditor.selectionMgr.getCoordinates(discussion.end)
|
||||||
@ -98,8 +99,8 @@ export default {
|
|||||||
},
|
},
|
||||||
async removeDiscussion() {
|
async removeDiscussion() {
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('modal/open', 'discussionDeletion');
|
await store.dispatch('modal/open', 'discussionDeletion');
|
||||||
this.$store.dispatch('discussion/cleanCurrentFile', {
|
store.dispatch('discussion/cleanCurrentFile', {
|
||||||
filterDiscussion: this.currentDiscussion,
|
filterDiscussion: this.currentDiscussion,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapActions } from 'vuex';
|
import { mapActions } from 'vuex';
|
||||||
import editorSvc from '../../services/editorSvc';
|
import editorSvc from '../../services/editorSvc';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: () => ({
|
data: () => ({
|
||||||
@ -23,7 +24,7 @@ export default {
|
|||||||
let offset;
|
let offset;
|
||||||
// Show the button if content is not a revision and has the focus
|
// Show the button if content is not a revision and has the focus
|
||||||
if (
|
if (
|
||||||
!this.$store.state.content.revisionContent &&
|
!store.state.content.revisionContent &&
|
||||||
editorSvc.clEditor.selectionMgr.hasFocus()
|
editorSvc.clEditor.selectionMgr.hasFocus()
|
||||||
) {
|
) {
|
||||||
this.selection = editorSvc.getTrimmedSelection();
|
this.selection = editorSvc.getTrimmedSelection();
|
||||||
|
@ -28,6 +28,8 @@ import cledit from '../../services/editor/cledit';
|
|||||||
import editorSvc from '../../services/editorSvc';
|
import editorSvc from '../../services/editorSvc';
|
||||||
import markdownConversionSvc from '../../services/markdownConversionSvc';
|
import markdownConversionSvc from '../../services/markdownConversionSvc';
|
||||||
import utils from '../../services/utils';
|
import utils from '../../services/utils';
|
||||||
|
import userSvc from '../../services/userSvc';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -36,8 +38,10 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapGetters('workspace', [
|
...mapGetters('workspace', [
|
||||||
'loginToken',
|
'loginToken',
|
||||||
'userId',
|
|
||||||
]),
|
]),
|
||||||
|
userId() {
|
||||||
|
return userSvc.getCurrentUserId();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations('discussion', [
|
...mapMutations('discussion', [
|
||||||
@ -47,13 +51,13 @@ export default {
|
|||||||
'cancelNewComment',
|
'cancelNewComment',
|
||||||
]),
|
]),
|
||||||
addComment() {
|
addComment() {
|
||||||
const text = this.$store.state.discussion.newCommentText.trim();
|
const text = store.state.discussion.newCommentText.trim();
|
||||||
if (text.length) {
|
if (text.length) {
|
||||||
if (text.length > 2000) {
|
if (text.length > 2000) {
|
||||||
this.$store.dispatch('notification/error', 'Comment is too long.');
|
store.dispatch('notification/error', 'Comment is too long.');
|
||||||
} else {
|
} else {
|
||||||
// Create comment
|
// Create comment
|
||||||
const discussionId = this.$store.state.discussion.currentDiscussionId;
|
const discussionId = store.state.discussion.currentDiscussionId;
|
||||||
const comment = {
|
const comment = {
|
||||||
discussionId,
|
discussionId,
|
||||||
sub: this.userId,
|
sub: this.userId,
|
||||||
@ -62,20 +66,20 @@ export default {
|
|||||||
};
|
};
|
||||||
const patch = {
|
const patch = {
|
||||||
comments: {
|
comments: {
|
||||||
...this.$store.getters['content/current'].comments,
|
...store.getters['content/current'].comments,
|
||||||
[utils.uid()]: comment,
|
[utils.uid()]: comment,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// Create discussion
|
// Create discussion
|
||||||
if (discussionId === this.$store.state.discussion.newDiscussionId) {
|
if (discussionId === store.state.discussion.newDiscussionId) {
|
||||||
patch.discussions = {
|
patch.discussions = {
|
||||||
...this.$store.getters['content/current'].discussions,
|
...store.getters['content/current'].discussions,
|
||||||
[discussionId]: this.$store.getters['discussion/newDiscussion'],
|
[discussionId]: store.getters['discussion/newDiscussion'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
this.$store.dispatch('content/patchCurrent', patch);
|
store.dispatch('content/patchCurrent', patch);
|
||||||
this.$store.commit('discussion/setNewCommentText');
|
store.commit('discussion/setNewCommentText');
|
||||||
this.$store.commit('discussion/setIsCommenting');
|
store.commit('discussion/setIsCommenting');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -91,28 +95,28 @@ export default {
|
|||||||
),
|
),
|
||||||
sectionParser: text => markdownConversionSvc
|
sectionParser: text => markdownConversionSvc
|
||||||
.parseSections(editorSvc.converter, text).sections,
|
.parseSections(editorSvc.converter, text).sections,
|
||||||
content: this.$store.state.discussion.newCommentText,
|
content: store.state.discussion.newCommentText,
|
||||||
selectionStart: this.$store.state.discussion.newCommentSelection.start,
|
selectionStart: store.state.discussion.newCommentSelection.start,
|
||||||
selectionEnd: this.$store.state.discussion.newCommentSelection.end,
|
selectionEnd: store.state.discussion.newCommentSelection.end,
|
||||||
getCursorFocusRatio: () => 0.2,
|
getCursorFocusRatio: () => 0.2,
|
||||||
});
|
});
|
||||||
clEditor.on('focus', () => this.setNewCommentFocus(true));
|
clEditor.on('focus', () => this.setNewCommentFocus(true));
|
||||||
|
|
||||||
// Save typed content and selection
|
// Save typed content and selection
|
||||||
clEditor.on('contentChanged', value =>
|
clEditor.on('contentChanged', value =>
|
||||||
this.$store.commit('discussion/setNewCommentText', value));
|
store.commit('discussion/setNewCommentText', value));
|
||||||
clEditor.selectionMgr.on('selectionChanged', (start, end) =>
|
clEditor.selectionMgr.on('selectionChanged', (start, end) =>
|
||||||
this.$store.commit('discussion/setNewCommentSelection', {
|
store.commit('discussion/setNewCommentSelection', {
|
||||||
start, end,
|
start, end,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const isSticky = this.$el.parentNode.classList.contains('sticky-comment');
|
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.$watch(
|
||||||
() => this.$store.state.discussion.currentDiscussionId,
|
() => store.state.discussion.currentDiscussionId,
|
||||||
() => this.$nextTick(() => {
|
() => this.$nextTick(() => {
|
||||||
if (isVisible() && this.$store.state.discussion.newCommentFocus) {
|
if (isVisible() && store.state.discussion.newCommentFocus) {
|
||||||
clEditor.focus();
|
clEditor.focus();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@ -139,11 +143,11 @@ export default {
|
|||||||
(visible) => {
|
(visible) => {
|
||||||
clEditor.toggleEditable(visible);
|
clEditor.toggleEditable(visible);
|
||||||
if (visible) {
|
if (visible) {
|
||||||
const text = this.$store.state.discussion.newCommentText;
|
const text = store.state.discussion.newCommentText;
|
||||||
clEditor.setContent(text);
|
clEditor.setContent(text);
|
||||||
const selection = this.$store.state.discussion.newCommentSelection;
|
const selection = store.state.discussion.newCommentSelection;
|
||||||
clEditor.selectionMgr.setSelectionStartEnd(selection.start, selection.end);
|
clEditor.selectionMgr.setSelectionStartEnd(selection.start, selection.end);
|
||||||
if (this.$store.state.discussion.newCommentFocus) {
|
if (store.state.discussion.newCommentFocus) {
|
||||||
clEditor.focus();
|
clEditor.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,7 +155,7 @@ export default {
|
|||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.$store.state.discussion.newCommentText,
|
() => store.state.discussion.newCommentText,
|
||||||
newCommentText => clEditor.setContent(newCommentText),
|
newCommentText => clEditor.setContent(newCommentText),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapActions } from 'vuex';
|
import { mapActions } from 'vuex';
|
||||||
import editorSvc from '../../services/editorSvc';
|
import editorSvc from '../../services/editorSvc';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: () => ({
|
data: () => ({
|
||||||
@ -23,7 +24,7 @@ export default {
|
|||||||
let offset;
|
let offset;
|
||||||
// Show the button if content is not a revision and preview selection is not empty
|
// Show the button if content is not a revision and preview selection is not empty
|
||||||
if (
|
if (
|
||||||
!this.$store.state.content.revisionContent &&
|
!store.state.content.revisionContent &&
|
||||||
editorSvc.previewSelectionRange
|
editorSvc.previewSelectionRange
|
||||||
) {
|
) {
|
||||||
this.selection = editorSvc.getTrimmedSelection();
|
this.selection = editorSvc.getTrimmedSelection();
|
||||||
@ -45,7 +46,7 @@ export default {
|
|||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
editorSvc.$on('previewSelectionRange', () => this.checkSelection());
|
editorSvc.$on('previewSelectionRange', () => this.checkSelection());
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.$store.getters['layout/styles'].previewWidth,
|
() => store.getters['layout/styles'].previewWidth,
|
||||||
() => this.checkSelection(),
|
() => this.checkSelection(),
|
||||||
);
|
);
|
||||||
this.checkSelection();
|
this.checkSelection();
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import MenuEntry from './common/MenuEntry';
|
import MenuEntry from './common/MenuEntry';
|
||||||
import exportSvc from '../../services/exportSvc';
|
import exportSvc from '../../services/exportSvc';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -35,20 +36,20 @@ export default {
|
|||||||
computed: mapGetters(['isSponsor']),
|
computed: mapGetters(['isSponsor']),
|
||||||
methods: {
|
methods: {
|
||||||
exportMarkdown() {
|
exportMarkdown() {
|
||||||
const currentFile = this.$store.getters['file/current'];
|
const currentFile = store.getters['file/current'];
|
||||||
return exportSvc.exportToDisk(currentFile.id, 'md')
|
return exportSvc.exportToDisk(currentFile.id, 'md')
|
||||||
.catch(() => { /* Cancel */ });
|
.catch(() => { /* Cancel */ });
|
||||||
},
|
},
|
||||||
exportHtml() {
|
exportHtml() {
|
||||||
return this.$store.dispatch('modal/open', 'htmlExport')
|
return store.dispatch('modal/open', 'htmlExport')
|
||||||
.catch(() => { /* Cancel */ });
|
.catch(() => { /* Cancel */ });
|
||||||
},
|
},
|
||||||
exportPdf() {
|
exportPdf() {
|
||||||
return this.$store.dispatch('modal/open', 'pdfExport')
|
return store.dispatch('modal/open', 'pdfExport')
|
||||||
.catch(() => { /* Cancel */ });
|
.catch(() => { /* Cancel */ });
|
||||||
},
|
},
|
||||||
exportPandoc() {
|
exportPandoc() {
|
||||||
return this.$store.dispatch('modal/open', 'pandocExport')
|
return store.dispatch('modal/open', 'pandocExport')
|
||||||
.catch(() => { /* Cancel */ });
|
.catch(() => { /* Cancel */ });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -55,6 +55,7 @@ import PreviewClassApplier from '../common/PreviewClassApplier';
|
|||||||
import utils from '../../services/utils';
|
import utils from '../../services/utils';
|
||||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||||
import syncSvc from '../../services/syncSvc';
|
import syncSvc from '../../services/syncSvc';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
let editorClassAppliers = [];
|
let editorClassAppliers = [];
|
||||||
let previewClassAppliers = [];
|
let previewClassAppliers = [];
|
||||||
@ -102,14 +103,14 @@ export default {
|
|||||||
return providerRegistry.providersById[this.syncLocation.providerId].name;
|
return providerRegistry.providersById[this.syncLocation.providerId].name;
|
||||||
},
|
},
|
||||||
currentFileName() {
|
currentFileName() {
|
||||||
return this.$store.getters['file/current'].name;
|
return store.getters['file/current'].name;
|
||||||
},
|
},
|
||||||
historyContext() {
|
historyContext() {
|
||||||
const { syncLocation } = this;
|
const { syncLocation } = this;
|
||||||
if (syncLocation) {
|
if (syncLocation) {
|
||||||
const provider = providerRegistry.providersById[syncLocation.providerId];
|
const provider = providerRegistry.providersById[syncLocation.providerId];
|
||||||
const token = provider.getToken(syncLocation);
|
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 contentId = `${fileId}/content`;
|
||||||
const historyContext = {
|
const historyContext = {
|
||||||
token,
|
token,
|
||||||
@ -171,7 +172,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
this.$store.dispatch('data/setSideBarPanel', 'menu');
|
store.dispatch('data/setSideBarPanel', 'menu');
|
||||||
},
|
},
|
||||||
showMore() {
|
showMore() {
|
||||||
this.showCount += pageSize;
|
this.showCount += pageSize;
|
||||||
@ -182,7 +183,7 @@ export default {
|
|||||||
const historyContext = utils.deepCopy(this.historyContext);
|
const historyContext = utils.deepCopy(this.historyContext);
|
||||||
if (historyContext) {
|
if (historyContext) {
|
||||||
const provider = providerRegistry.providersById[this.syncLocation.providerId];
|
const provider = providerRegistry.providersById[this.syncLocation.providerId];
|
||||||
revisionContentPromise = new Promise((resolve, reject) => this.$store.dispatch(
|
revisionContentPromise = new Promise((resolve, reject) => store.dispatch(
|
||||||
'queue/enqueue',
|
'queue/enqueue',
|
||||||
() => provider.getFileRevisionContent({
|
() => provider.getFileRevisionContent({
|
||||||
...historyContext,
|
...historyContext,
|
||||||
@ -192,14 +193,14 @@ export default {
|
|||||||
));
|
));
|
||||||
revisionContentPromises[revision.id] = revisionContentPromise;
|
revisionContentPromises[revision.id] = revisionContentPromise;
|
||||||
revisionContentPromise.catch((err) => {
|
revisionContentPromise.catch((err) => {
|
||||||
this.$store.dispatch('notification/error', err);
|
store.dispatch('notification/error', err);
|
||||||
revisionContentPromises[revision.id] = null;
|
revisionContentPromises[revision.id] = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (revisionContentPromise) {
|
if (revisionContentPromise) {
|
||||||
revisionContentPromise.then(revisionContent =>
|
revisionContentPromise.then(revisionContent =>
|
||||||
this.$store.dispatch('content/setRevisionContent', revisionContent));
|
store.dispatch('content/setRevisionContent', revisionContent));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
refreshHighlighters() {
|
refreshHighlighters() {
|
||||||
@ -256,14 +257,14 @@ export default {
|
|||||||
cachedHistoryContextHash = this.historyContextHash;
|
cachedHistoryContextHash = this.historyContextHash;
|
||||||
revisionContentPromises = {};
|
revisionContentPromises = {};
|
||||||
const provider = providerRegistry.providersById[this.syncLocation.providerId];
|
const provider = providerRegistry.providersById[this.syncLocation.providerId];
|
||||||
revisionsPromise = new Promise((resolve, reject) => this.$store.dispatch(
|
revisionsPromise = new Promise((resolve, reject) => store.dispatch(
|
||||||
'queue/enqueue',
|
'queue/enqueue',
|
||||||
() => provider
|
() => provider
|
||||||
.listFileRevisions(historyContext)
|
.listFileRevisions(historyContext)
|
||||||
.then(resolve, reject),
|
.then(resolve, reject),
|
||||||
))
|
))
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.$store.dispatch('notification/error', err);
|
store.dispatch('notification/error', err);
|
||||||
cachedHistoryContextHash = null;
|
cachedHistoryContextHash = null;
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
@ -282,7 +283,7 @@ export default {
|
|||||||
revisions(revisions) {
|
revisions(revisions) {
|
||||||
const { historyContext } = this;
|
const { historyContext } = this;
|
||||||
if (historyContext) {
|
if (historyContext) {
|
||||||
this.$store.dispatch(
|
store.dispatch(
|
||||||
'queue/enqueue',
|
'queue/enqueue',
|
||||||
() => utils.awaitSequence(revisions, async (revision) => {
|
() => utils.awaitSequence(revisions, async (revision) => {
|
||||||
// Make sure revisions and historyContext haven't changed
|
// Make sure revisions and historyContext haven't changed
|
||||||
|
@ -39,7 +39,7 @@ const readFile = file => new Promise((resolve) => {
|
|||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
const content = e.target.result;
|
const content = e.target.result;
|
||||||
if (content.match(/\uFFFD/)) {
|
if (content.match(/\uFFFD/)) {
|
||||||
this.$store.dispatch('notification/error', 'File is not readable.');
|
store.dispatch('notification/error', 'File is not readable.');
|
||||||
} else {
|
} else {
|
||||||
resolve(content);
|
resolve(content);
|
||||||
}
|
}
|
||||||
@ -60,7 +60,7 @@ export default {
|
|||||||
...Provider.parseContent(content),
|
...Provider.parseContent(content),
|
||||||
name: file.name,
|
name: file.name,
|
||||||
});
|
});
|
||||||
this.$store.commit('file/setCurrentId', item.id);
|
store.commit('file/setCurrentId', item.id);
|
||||||
},
|
},
|
||||||
async onImportHtml(evt) {
|
async onImportHtml(evt) {
|
||||||
const file = evt.target.files[0];
|
const file = evt.target.files[0];
|
||||||
@ -71,7 +71,7 @@ export default {
|
|||||||
...Provider.parseContent(turndownService.turndown(sanitizedContent)),
|
...Provider.parseContent(turndownService.turndown(sanitizedContent)),
|
||||||
name: file.name,
|
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'">
|
<span v-else-if="currentWorkspace.providerId === 'githubWorkspace'">
|
||||||
<b>{{currentWorkspace.name}}</b> synced with a <a :href="workspaceLocationUrl" target="_blank">GitHub repo</a>.
|
<b>{{currentWorkspace.name}}</b> synced with a <a :href="workspaceLocationUrl" target="_blank">GitHub repo</a>.
|
||||||
</span>
|
</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>
|
||||||
<div class="menu-entry menu-entry--info flex flex--row flex--align-center" v-else>
|
<div class="menu-entry menu-entry--info flex flex--row flex--align-center" v-else>
|
||||||
<div class="menu-entry__icon menu-entry__icon--disabled">
|
<div class="menu-entry__icon menu-entry__icon--disabled">
|
||||||
@ -38,18 +41,18 @@
|
|||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="setPanel('workspaces')">
|
<menu-entry @click.native="setPanel('workspaces')">
|
||||||
<icon-database slot="icon"></icon-database>
|
<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>
|
<span>Switch to another workspace.</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<hr>
|
<hr>
|
||||||
<menu-entry @click.native="setPanel('sync')">
|
<menu-entry @click.native="setPanel('sync')">
|
||||||
<icon-sync slot="icon"></icon-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>
|
<span>Sync your files in the Cloud.</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="setPanel('publish')">
|
<menu-entry @click.native="setPanel('publish')">
|
||||||
<icon-upload slot="icon"></icon-upload>
|
<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>
|
<span>Export your files to the web.</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="setPanel('history')">
|
<menu-entry @click.native="setPanel('history')">
|
||||||
@ -98,6 +101,8 @@ import providerRegistry from '../../services/providers/common/providerRegistry';
|
|||||||
import UserImage from '../UserImage';
|
import UserImage from '../UserImage';
|
||||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||||
import syncSvc from '../../services/syncSvc';
|
import syncSvc from '../../services/syncSvc';
|
||||||
|
import userSvc from '../../services/userSvc';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -109,12 +114,23 @@ export default {
|
|||||||
'currentWorkspace',
|
'currentWorkspace',
|
||||||
'syncToken',
|
'syncToken',
|
||||||
'loginToken',
|
'loginToken',
|
||||||
'userId',
|
|
||||||
]),
|
]),
|
||||||
|
userId() {
|
||||||
|
return userSvc.getCurrentUserId();
|
||||||
|
},
|
||||||
workspaceLocationUrl() {
|
workspaceLocationUrl() {
|
||||||
const provider = providerRegistry.providersById[this.currentWorkspace.providerId];
|
const provider = providerRegistry.providersById[this.currentWorkspace.providerId];
|
||||||
return provider.getWorkspaceLocationUrl(this.currentWorkspace);
|
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: {
|
methods: {
|
||||||
...mapActions('data', {
|
...mapActions('data', {
|
||||||
@ -130,7 +146,7 @@ export default {
|
|||||||
},
|
},
|
||||||
async fileProperties() {
|
async fileProperties() {
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('modal/open', 'fileProperties');
|
await store.dispatch('modal/open', 'fileProperties');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Cancel
|
// Cancel
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
import MenuEntry from './common/MenuEntry';
|
import MenuEntry from './common/MenuEntry';
|
||||||
import backupSvc from '../../services/backupSvc';
|
import backupSvc from '../../services/backupSvc';
|
||||||
import utils from '../../services/utils';
|
import utils from '../../services/utils';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -52,7 +53,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
templateCount() {
|
templateCount() {
|
||||||
return Object.keys(this.$store.getters['data/allTemplatesById']).length;
|
return Object.keys(store.getters['data/allTemplatesById']).length;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -63,7 +64,7 @@ export default {
|
|||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
const text = e.target.result;
|
const text = e.target.result;
|
||||||
if (text.match(/\uFFFD/)) {
|
if (text.match(/\uFFFD/)) {
|
||||||
this.$store.dispatch('notification/error', 'File is not readable.');
|
store.dispatch('notification/error', 'File is not readable.');
|
||||||
} else {
|
} else {
|
||||||
backupSvc.importBackup(text);
|
backupSvc.importBackup(text);
|
||||||
}
|
}
|
||||||
@ -82,23 +83,23 @@ export default {
|
|||||||
},
|
},
|
||||||
async settings() {
|
async settings() {
|
||||||
try {
|
try {
|
||||||
const settings = await this.$store.dispatch('modal/open', 'settings');
|
const settings = await store.dispatch('modal/open', 'settings');
|
||||||
this.$store.dispatch('data/setSettings', settings);
|
store.dispatch('data/setSettings', settings);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Cancel
|
// Cancel
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async templates() {
|
async templates() {
|
||||||
try {
|
try {
|
||||||
const { templates } = await this.$store.dispatch('modal/open', 'templates');
|
const { templates } = await store.dispatch('modal/open', 'templates');
|
||||||
this.$store.dispatch('data/setTemplatesById', templates);
|
store.dispatch('data/setTemplatesById', templates);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Cancel
|
// Cancel
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async reset() {
|
async reset() {
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('modal/open', 'reset');
|
await store.dispatch('modal/open', 'reset');
|
||||||
window.location.href = '#reset=true';
|
window.location.href = '#reset=true';
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -106,7 +107,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
about() {
|
about() {
|
||||||
this.$store.dispatch('modal/open', 'about');
|
store.dispatch('modal/open', 'about');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -21,39 +21,6 @@
|
|||||||
</menu-entry>
|
</menu-entry>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<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">
|
<div v-for="token in bloggerTokens" :key="token.sub">
|
||||||
<menu-entry @click.native="publishBlogger(token)">
|
<menu-entry @click.native="publishBlogger(token)">
|
||||||
<icon-provider slot="icon" provider-id="blogger"></icon-provider>
|
<icon-provider slot="icon" provider-id="blogger"></icon-provider>
|
||||||
@ -66,6 +33,46 @@
|
|||||||
<span>{{token.name}}</span>
|
<span>{{token.name}}</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
</div>
|
</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">
|
<div v-for="token in zendeskTokens" :key="token.sub">
|
||||||
<menu-entry @click.native="publishZendesk(token)">
|
<menu-entry @click.native="publishZendesk(token)">
|
||||||
<icon-provider slot="icon" provider-id="zendesk"></icon-provider>
|
<icon-provider slot="icon" provider-id="zendesk"></icon-provider>
|
||||||
@ -74,9 +81,9 @@
|
|||||||
</menu-entry>
|
</menu-entry>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<menu-entry @click.native="addGoogleDriveAccount">
|
<menu-entry @click.native="addBloggerAccount">
|
||||||
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
|
<icon-provider slot="icon" provider-id="blogger"></icon-provider>
|
||||||
<span>Add Google Drive account</span>
|
<span>Add Blogger account</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="addDropboxAccount">
|
<menu-entry @click.native="addDropboxAccount">
|
||||||
<icon-provider slot="icon" provider-id="dropbox"></icon-provider>
|
<icon-provider slot="icon" provider-id="dropbox"></icon-provider>
|
||||||
@ -86,14 +93,18 @@
|
|||||||
<icon-provider slot="icon" provider-id="github"></icon-provider>
|
<icon-provider slot="icon" provider-id="github"></icon-provider>
|
||||||
<span>Add GitHub account</span>
|
<span>Add GitHub account</span>
|
||||||
</menu-entry>
|
</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">
|
<menu-entry @click.native="addWordpressAccount">
|
||||||
<icon-provider slot="icon" provider-id="wordpress"></icon-provider>
|
<icon-provider slot="icon" provider-id="wordpress"></icon-provider>
|
||||||
<span>Add WordPress account</span>
|
<span>Add WordPress account</span>
|
||||||
</menu-entry>
|
</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">
|
<menu-entry @click.native="addZendeskAccount">
|
||||||
<icon-provider slot="icon" provider-id="zendesk"></icon-provider>
|
<icon-provider slot="icon" provider-id="zendesk"></icon-provider>
|
||||||
<span>Add Zendesk account</span>
|
<span>Add Zendesk account</span>
|
||||||
@ -108,6 +119,7 @@ import MenuEntry from './common/MenuEntry';
|
|||||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||||
import dropboxHelper from '../../services/providers/helpers/dropboxHelper';
|
import dropboxHelper from '../../services/providers/helpers/dropboxHelper';
|
||||||
import githubHelper from '../../services/providers/helpers/githubHelper';
|
import githubHelper from '../../services/providers/helpers/githubHelper';
|
||||||
|
import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
|
||||||
import wordpressHelper from '../../services/providers/helpers/wordpressHelper';
|
import wordpressHelper from '../../services/providers/helpers/wordpressHelper';
|
||||||
import zendeskHelper from '../../services/providers/helpers/zendeskHelper';
|
import zendeskHelper from '../../services/providers/helpers/zendeskHelper';
|
||||||
import publishSvc from '../../services/publishSvc';
|
import publishSvc from '../../services/publishSvc';
|
||||||
@ -145,33 +157,32 @@ export default {
|
|||||||
return Object.keys(this.publishLocations).length;
|
return Object.keys(this.publishLocations).length;
|
||||||
},
|
},
|
||||||
currentFileName() {
|
currentFileName() {
|
||||||
return this.$store.getters['file/current'].name;
|
return 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']);
|
|
||||||
},
|
},
|
||||||
bloggerTokens() {
|
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() {
|
zendeskTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/zendeskTokensBySub']);
|
return tokensToArray(store.getters['data/zendeskTokensBySub']);
|
||||||
},
|
},
|
||||||
noToken() {
|
noToken() {
|
||||||
return !this.googleDriveTokens.length
|
return Object.values(store.getters['data/tokensByType'])
|
||||||
&& !this.dropboxTokens.length
|
.every(tokens => !Object.keys(tokens).length);
|
||||||
&& !this.githubTokens.length
|
|
||||||
&& !this.wordpressTokens.length
|
|
||||||
&& !this.bloggerTokens.length
|
|
||||||
&& !this.zendeskTokens.length;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -182,30 +193,7 @@ export default {
|
|||||||
},
|
},
|
||||||
async managePublish() {
|
async managePublish() {
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('modal/open', 'publishManagement');
|
await 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();
|
|
||||||
} catch (e) { /* cancel */ }
|
} catch (e) { /* cancel */ }
|
||||||
},
|
},
|
||||||
async addBloggerAccount() {
|
async addBloggerAccount() {
|
||||||
@ -213,19 +201,49 @@ export default {
|
|||||||
await googleHelper.addBloggerAccount();
|
await googleHelper.addBloggerAccount();
|
||||||
} catch (e) { /* cancel */ }
|
} 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() {
|
async addZendeskAccount() {
|
||||||
try {
|
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);
|
await zendeskHelper.addAccount(subdomain, clientId);
|
||||||
} catch (e) { /* cancel */ }
|
} catch (e) { /* cancel */ }
|
||||||
},
|
},
|
||||||
publishGoogleDrive: publishModalOpener('googleDrivePublish'),
|
publishBlogger: publishModalOpener('bloggerPublish'),
|
||||||
|
publishBloggerPage: publishModalOpener('bloggerPagePublish'),
|
||||||
publishDropbox: publishModalOpener('dropboxPublish'),
|
publishDropbox: publishModalOpener('dropboxPublish'),
|
||||||
publishGithub: publishModalOpener('githubPublish'),
|
publishGithub: publishModalOpener('githubPublish'),
|
||||||
publishGist: publishModalOpener('gistPublish'),
|
publishGist: publishModalOpener('gistPublish'),
|
||||||
|
publishGitlab: publishModalOpener('gitlabPublish'),
|
||||||
|
publishGoogleDrive: publishModalOpener('googleDrivePublish'),
|
||||||
publishWordpress: publishModalOpener('wordpressPublish'),
|
publishWordpress: publishModalOpener('wordpressPublish'),
|
||||||
publishBlogger: publishModalOpener('bloggerPublish'),
|
|
||||||
publishBloggerPage: publishModalOpener('bloggerPagePublish'),
|
|
||||||
publishZendesk: publishModalOpener('zendeskPublish'),
|
publishZendesk: publishModalOpener('zendeskPublish'),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -21,18 +21,6 @@
|
|||||||
</menu-entry>
|
</menu-entry>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<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">
|
<div v-for="token in dropboxTokens" :key="token.sub">
|
||||||
<menu-entry @click.native="openDropbox(token)">
|
<menu-entry @click.native="openDropbox(token)">
|
||||||
<icon-provider slot="icon" provider-id="dropbox"></icon-provider>
|
<icon-provider slot="icon" provider-id="dropbox"></icon-provider>
|
||||||
@ -62,11 +50,31 @@
|
|||||||
<span>{{token.name}}</span>
|
<span>{{token.name}}</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<div v-for="token in gitlabTokens" :key="token.sub">
|
||||||
<menu-entry @click.native="addGoogleDriveAccount">
|
<menu-entry @click.native="openGitlab(token)">
|
||||||
<icon-provider slot="icon" provider-id="googleDrive"></icon-provider>
|
<icon-provider slot="icon" provider-id="gitlab"></icon-provider>
|
||||||
<span>Add Google Drive account</span>
|
<div>Open from GitLab</div>
|
||||||
|
<span>{{token.name}}</span>
|
||||||
</menu-entry>
|
</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="addDropboxAccount">
|
<menu-entry @click.native="addDropboxAccount">
|
||||||
<icon-provider slot="icon" provider-id="dropbox"></icon-provider>
|
<icon-provider slot="icon" provider-id="dropbox"></icon-provider>
|
||||||
<span>Add Dropbox account</span>
|
<span>Add Dropbox account</span>
|
||||||
@ -75,6 +83,14 @@
|
|||||||
<icon-provider slot="icon" provider-id="github"></icon-provider>
|
<icon-provider slot="icon" provider-id="github"></icon-provider>
|
||||||
<span>Add GitHub account</span>
|
<span>Add GitHub account</span>
|
||||||
</menu-entry>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -85,9 +101,11 @@ import MenuEntry from './common/MenuEntry';
|
|||||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||||
import dropboxHelper from '../../services/providers/helpers/dropboxHelper';
|
import dropboxHelper from '../../services/providers/helpers/dropboxHelper';
|
||||||
import githubHelper from '../../services/providers/helpers/githubHelper';
|
import githubHelper from '../../services/providers/helpers/githubHelper';
|
||||||
|
import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
|
||||||
import googleDriveProvider from '../../services/providers/googleDriveProvider';
|
import googleDriveProvider from '../../services/providers/googleDriveProvider';
|
||||||
import dropboxProvider from '../../services/providers/dropboxProvider';
|
import dropboxProvider from '../../services/providers/dropboxProvider';
|
||||||
import githubProvider from '../../services/providers/githubProvider';
|
import githubProvider from '../../services/providers/githubProvider';
|
||||||
|
import gitlabProvider from '../../services/providers/gitlabProvider';
|
||||||
import syncSvc from '../../services/syncSvc';
|
import syncSvc from '../../services/syncSvc';
|
||||||
import store from '../../store';
|
import store from '../../store';
|
||||||
|
|
||||||
@ -121,16 +139,19 @@ export default {
|
|||||||
return Object.keys(this.syncLocations).length;
|
return Object.keys(this.syncLocations).length;
|
||||||
},
|
},
|
||||||
currentFileName() {
|
currentFileName() {
|
||||||
return this.$store.getters['file/current'].name;
|
return store.getters['file/current'].name;
|
||||||
},
|
|
||||||
googleDriveTokens() {
|
|
||||||
return tokensToArray(this.$store.getters['data/googleTokensBySub'], token => token.isDrive);
|
|
||||||
},
|
},
|
||||||
dropboxTokens() {
|
dropboxTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/dropboxTokensBySub']);
|
return tokensToArray(store.getters['data/dropboxTokensBySub']);
|
||||||
},
|
},
|
||||||
githubTokens() {
|
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() {
|
noToken() {
|
||||||
return !this.googleDriveTokens.length
|
return !this.googleDriveTokens.length
|
||||||
@ -146,37 +167,43 @@ export default {
|
|||||||
},
|
},
|
||||||
async manageSync() {
|
async manageSync() {
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('modal/open', 'syncManagement');
|
await 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);
|
|
||||||
} catch (e) { /* cancel */ }
|
} catch (e) { /* cancel */ }
|
||||||
},
|
},
|
||||||
async addDropboxAccount() {
|
async addDropboxAccount() {
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('modal/open', { type: 'dropboxAccount' });
|
await store.dispatch('modal/open', { type: 'dropboxAccount' });
|
||||||
await dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess);
|
await dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess);
|
||||||
} catch (e) { /* cancel */ }
|
} catch (e) { /* cancel */ }
|
||||||
},
|
},
|
||||||
async addGithubAccount() {
|
async addGithubAccount() {
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('modal/open', { type: 'githubAccount' });
|
await store.dispatch('modal/open', { type: 'githubAccount' });
|
||||||
await githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess);
|
await githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess);
|
||||||
} catch (e) { /* cancel */ }
|
} 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) {
|
async openGoogleDrive(token) {
|
||||||
const files = await googleHelper.openPicker(token, 'doc');
|
const files = await googleHelper.openPicker(token, 'doc');
|
||||||
this.$store.dispatch(
|
store.dispatch(
|
||||||
'queue/enqueue',
|
'queue/enqueue',
|
||||||
() => googleDriveProvider.openFiles(token, files),
|
() => googleDriveProvider.openFiles(token, files),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
async openDropbox(token) {
|
async openDropbox(token) {
|
||||||
const paths = await dropboxHelper.openChooser(token);
|
const paths = await dropboxHelper.openChooser(token);
|
||||||
this.$store.dispatch(
|
store.dispatch(
|
||||||
'queue/enqueue',
|
'queue/enqueue',
|
||||||
() => dropboxProvider.openFiles(token, paths),
|
() => dropboxProvider.openFiles(token, paths),
|
||||||
);
|
);
|
||||||
@ -197,7 +224,7 @@ export default {
|
|||||||
type: 'githubOpen',
|
type: 'githubOpen',
|
||||||
token,
|
token,
|
||||||
});
|
});
|
||||||
this.$store.dispatch(
|
store.dispatch(
|
||||||
'queue/enqueue',
|
'queue/enqueue',
|
||||||
() => githubProvider.openFile(token, syncLocation),
|
() => githubProvider.openFile(token, syncLocation),
|
||||||
);
|
);
|
||||||
@ -213,6 +240,23 @@ export default {
|
|||||||
await openSyncModal(token, 'gistSync');
|
await openSyncModal(token, 'gistSync');
|
||||||
} catch (e) { /* cancel */ }
|
} 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>
|
</script>
|
||||||
|
@ -17,6 +17,11 @@
|
|||||||
<div>GitHub workspace</div>
|
<div>GitHub workspace</div>
|
||||||
<span>Add a workspace synced with a GitHub repository.</span>
|
<span>Add a workspace synced with a GitHub repository.</span>
|
||||||
</menu-entry>
|
</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">
|
<menu-entry @click.native="addGoogleDriveWorkspace">
|
||||||
<icon-provider slot="icon" provider-id="googleDriveWorkspace"></icon-provider>
|
<icon-provider slot="icon" provider-id="googleDriveWorkspace"></icon-provider>
|
||||||
<div>Google Drive workspace</div>
|
<div>Google Drive workspace</div>
|
||||||
@ -33,6 +38,8 @@
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import MenuEntry from './common/MenuEntry';
|
import MenuEntry from './common/MenuEntry';
|
||||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||||
|
import gitlabHelper from '../../services/providers/helpers/gitlabHelper';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -50,7 +57,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
async addCouchdbWorkspace() {
|
async addCouchdbWorkspace() {
|
||||||
try {
|
try {
|
||||||
this.$store.dispatch('modal/open', {
|
store.dispatch('modal/open', {
|
||||||
type: 'couchdbWorkspace',
|
type: 'couchdbWorkspace',
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -59,17 +66,29 @@ export default {
|
|||||||
},
|
},
|
||||||
async addGithubWorkspace() {
|
async addGithubWorkspace() {
|
||||||
try {
|
try {
|
||||||
this.$store.dispatch('modal/open', {
|
store.dispatch('modal/open', {
|
||||||
type: 'githubWorkspace',
|
type: 'githubWorkspace',
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Cancel
|
// 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() {
|
async addGoogleDriveWorkspace() {
|
||||||
try {
|
try {
|
||||||
const token = await googleHelper.addDriveAccount(true);
|
const token = await googleHelper.addDriveAccount(true);
|
||||||
this.$store.dispatch('modal/open', {
|
store.dispatch('modal/open', {
|
||||||
type: 'googleDriveWorkspace',
|
type: 'googleDriveWorkspace',
|
||||||
token,
|
token,
|
||||||
});
|
});
|
||||||
@ -78,7 +97,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
manageWorkspaces() {
|
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 CodeEditor from '../CodeEditor';
|
||||||
import utils from '../../services/utils';
|
import utils from '../../services/utils';
|
||||||
import presets from '../../data/presets';
|
import presets from '../../data/presets';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
const simpleProperties = {
|
const simpleProperties = {
|
||||||
title: '',
|
title: '',
|
||||||
@ -125,17 +126,17 @@ export default {
|
|||||||
presets: () => Object.keys(presets).sort(),
|
presets: () => Object.keys(presets).sort(),
|
||||||
tab: {
|
tab: {
|
||||||
get() {
|
get() {
|
||||||
return this.$store.getters['data/localSettings'].filePropertiesTab;
|
return store.getters['data/localSettings'].filePropertiesTab;
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
this.$store.dispatch('data/patchLocalSettings', {
|
store.dispatch('data/patchLocalSettings', {
|
||||||
filePropertiesTab: value,
|
filePropertiesTab: value,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
const content = this.$store.getters['content/current'];
|
const content = store.getters['content/current'];
|
||||||
this.contentId = content.id;
|
this.contentId = content.id;
|
||||||
this.setYamlProperties(content.properties);
|
this.setYamlProperties(content.properties);
|
||||||
if (this.tab !== 'yaml') {
|
if (this.tab !== 'yaml') {
|
||||||
@ -214,7 +215,7 @@ export default {
|
|||||||
if (this.error) {
|
if (this.error) {
|
||||||
this.setYamlTab();
|
this.setYamlTab();
|
||||||
} else {
|
} else {
|
||||||
this.$store.commit('content/patchItem', {
|
store.commit('content/patchItem', {
|
||||||
id: this.contentId,
|
id: this.contentId,
|
||||||
properties: utils.sanitizeText(this.yamlProperties),
|
properties: utils.sanitizeText(this.yamlProperties),
|
||||||
});
|
});
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
import { mapActions } from 'vuex';
|
import { mapActions } from 'vuex';
|
||||||
import exportSvc from '../../services/exportSvc';
|
import exportSvc from '../../services/exportSvc';
|
||||||
import modalTemplate from './common/modalTemplate';
|
import modalTemplate from './common/modalTemplate';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default modalTemplate({
|
export default modalTemplate({
|
||||||
data: () => ({
|
data: () => ({
|
||||||
@ -38,7 +39,7 @@ export default modalTemplate({
|
|||||||
this.$watch('selectedTemplate', (selectedTemplate) => {
|
this.$watch('selectedTemplate', (selectedTemplate) => {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
timeoutId = setTimeout(async () => {
|
timeoutId = setTimeout(async () => {
|
||||||
const currentFile = this.$store.getters['file/current'];
|
const currentFile = store.getters['file/current'];
|
||||||
const html = await exportSvc.applyTemplate(
|
const html = await exportSvc.applyTemplate(
|
||||||
currentFile.id,
|
currentFile.id,
|
||||||
this.allTemplatesById[selectedTemplate],
|
this.allTemplatesById[selectedTemplate],
|
||||||
@ -55,7 +56,7 @@ export default modalTemplate({
|
|||||||
]),
|
]),
|
||||||
resolve() {
|
resolve() {
|
||||||
const { config } = this;
|
const { config } = this;
|
||||||
const currentFile = this.$store.getters['file/current'];
|
const currentFile = store.getters['file/current'];
|
||||||
config.resolve();
|
config.resolve();
|
||||||
exportSvc.exportToDisk(currentFile.id, 'html', this.allTemplatesById[this.selectedTemplate]);
|
exportSvc.exportToDisk(currentFile.id, 'html', this.allTemplatesById[this.selectedTemplate]);
|
||||||
},
|
},
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
import modalTemplate from './common/modalTemplate';
|
import modalTemplate from './common/modalTemplate';
|
||||||
import MenuEntry from '../menus/common/MenuEntry';
|
import MenuEntry from '../menus/common/MenuEntry';
|
||||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default modalTemplate({
|
export default modalTemplate({
|
||||||
components: {
|
components: {
|
||||||
@ -36,7 +37,7 @@ export default modalTemplate({
|
|||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
googlePhotosTokens() {
|
googlePhotosTokens() {
|
||||||
const googleTokensBySub = this.$store.getters['data/googleTokensBySub'];
|
const googleTokensBySub = store.getters['data/googleTokensBySub'];
|
||||||
return Object.values(googleTokensBySub)
|
return Object.values(googleTokensBySub)
|
||||||
.filter(token => token.isPhotos)
|
.filter(token => token.isPhotos)
|
||||||
.sort((token1, token2) => token1.name.localeCompare(token2.name));
|
.sort((token1, token2) => token1.name.localeCompare(token2.name));
|
||||||
@ -65,7 +66,7 @@ export default modalTemplate({
|
|||||||
this.config.reject();
|
this.config.reject();
|
||||||
const res = await googleHelper.openPicker(token, 'img');
|
const res = await googleHelper.openPicker(token, 'img');
|
||||||
if (res[0]) {
|
if (res[0]) {
|
||||||
this.$store.dispatch('modal/open', {
|
store.dispatch('modal/open', {
|
||||||
type: 'googlePhoto',
|
type: 'googlePhoto',
|
||||||
url: res[0].url,
|
url: res[0].url,
|
||||||
callback,
|
callback,
|
||||||
|
@ -32,6 +32,7 @@ import networkSvc from '../../services/networkSvc';
|
|||||||
import editorSvc from '../../services/editorSvc';
|
import editorSvc from '../../services/editorSvc';
|
||||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||||
import modalTemplate from './common/modalTemplate';
|
import modalTemplate from './common/modalTemplate';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default modalTemplate({
|
export default modalTemplate({
|
||||||
computedLocalSettings: {
|
computedLocalSettings: {
|
||||||
@ -40,13 +41,13 @@ export default modalTemplate({
|
|||||||
methods: {
|
methods: {
|
||||||
async resolve() {
|
async resolve() {
|
||||||
this.config.resolve();
|
this.config.resolve();
|
||||||
const currentFile = this.$store.getters['file/current'];
|
const currentFile = store.getters['file/current'];
|
||||||
const currentContent = this.$store.getters['content/current'];
|
const currentContent = store.getters['content/current'];
|
||||||
const { selectedFormat } = this;
|
const { selectedFormat } = this;
|
||||||
this.$store.dispatch('queue/enqueue', async () => {
|
store.dispatch('queue/enqueue', async () => {
|
||||||
const [sponsorToken, token] = await Promise.all([
|
const [sponsorToken, token] = await Promise.all([
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
const tokenToRefresh = this.$store.getters['workspace/sponsorToken'];
|
const tokenToRefresh = store.getters['workspace/sponsorToken'];
|
||||||
return tokenToRefresh && googleHelper.refreshToken(tokenToRefresh);
|
return tokenToRefresh && googleHelper.refreshToken(tokenToRefresh);
|
||||||
}),
|
}),
|
||||||
sponsorSvc.getToken(),
|
sponsorSvc.getToken(),
|
||||||
@ -60,7 +61,7 @@ export default modalTemplate({
|
|||||||
token,
|
token,
|
||||||
idToken: sponsorToken && sponsorToken.idToken,
|
idToken: sponsorToken && sponsorToken.idToken,
|
||||||
format: selectedFormat,
|
format: selectedFormat,
|
||||||
options: JSON.stringify(this.$store.getters['data/computedSettings'].pandoc),
|
options: JSON.stringify(store.getters['data/computedSettings'].pandoc),
|
||||||
metadata: JSON.stringify(currentContent.properties),
|
metadata: JSON.stringify(currentContent.properties),
|
||||||
},
|
},
|
||||||
body: JSON.stringify(editorSvc.getPandocAst()),
|
body: JSON.stringify(editorSvc.getPandocAst()),
|
||||||
@ -70,10 +71,10 @@ export default modalTemplate({
|
|||||||
FileSaver.saveAs(body, `${currentFile.name}.${selectedFormat}`);
|
FileSaver.saveAs(body, `${currentFile.name}.${selectedFormat}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.status === 401) {
|
if (err.status === 401) {
|
||||||
this.$store.dispatch('modal/open', 'sponsorOnly');
|
store.dispatch('modal/open', 'sponsorOnly');
|
||||||
} else {
|
} else {
|
||||||
console.error(err); // eslint-disable-line no-console
|
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 networkSvc from '../../services/networkSvc';
|
||||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||||
import modalTemplate from './common/modalTemplate';
|
import modalTemplate from './common/modalTemplate';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default modalTemplate({
|
export default modalTemplate({
|
||||||
computedLocalSettings: {
|
computedLocalSettings: {
|
||||||
@ -35,11 +36,11 @@ export default modalTemplate({
|
|||||||
methods: {
|
methods: {
|
||||||
async resolve() {
|
async resolve() {
|
||||||
this.config.resolve();
|
this.config.resolve();
|
||||||
const currentFile = this.$store.getters['file/current'];
|
const currentFile = store.getters['file/current'];
|
||||||
this.$store.dispatch('queue/enqueue', async () => {
|
store.dispatch('queue/enqueue', async () => {
|
||||||
const [sponsorToken, token, html] = await Promise.all([
|
const [sponsorToken, token, html] = await Promise.all([
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
const tokenToRefresh = this.$store.getters['workspace/sponsorToken'];
|
const tokenToRefresh = store.getters['workspace/sponsorToken'];
|
||||||
return tokenToRefresh && googleHelper.refreshToken(tokenToRefresh);
|
return tokenToRefresh && googleHelper.refreshToken(tokenToRefresh);
|
||||||
}),
|
}),
|
||||||
sponsorSvc.getToken(),
|
sponsorSvc.getToken(),
|
||||||
@ -57,7 +58,7 @@ export default modalTemplate({
|
|||||||
params: {
|
params: {
|
||||||
token,
|
token,
|
||||||
idToken: sponsorToken && sponsorToken.idToken,
|
idToken: sponsorToken && sponsorToken.idToken,
|
||||||
options: JSON.stringify(this.$store.getters['data/computedSettings'].wkhtmltopdf),
|
options: JSON.stringify(store.getters['data/computedSettings'].wkhtmltopdf),
|
||||||
},
|
},
|
||||||
body: html,
|
body: html,
|
||||||
blob: true,
|
blob: true,
|
||||||
@ -66,10 +67,10 @@ export default modalTemplate({
|
|||||||
FileSaver.saveAs(body, `${currentFile.name}.pdf`);
|
FileSaver.saveAs(body, `${currentFile.name}.pdf`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.status === 401) {
|
if (err.status === 401) {
|
||||||
this.$store.dispatch('modal/open', 'sponsorOnly');
|
store.dispatch('modal/open', 'sponsorOnly');
|
||||||
} else {
|
} else {
|
||||||
console.error(err); // eslint-disable-line no-console
|
console.error(err); // eslint-disable-line no-console
|
||||||
this.$store.dispatch('notification/error', err);
|
store.dispatch('notification/error', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters, mapActions } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
import ModalInner from './common/ModalInner';
|
import ModalInner from './common/ModalInner';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -62,7 +63,7 @@ export default {
|
|||||||
publishLocations: 'current',
|
publishLocations: 'current',
|
||||||
}),
|
}),
|
||||||
currentFileName() {
|
currentFileName() {
|
||||||
return this.$store.getters['file/current'].name;
|
return store.getters['file/current'].name;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -70,7 +71,7 @@ export default {
|
|||||||
'info',
|
'info',
|
||||||
]),
|
]),
|
||||||
remove(location) {
|
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 Tab from './common/Tab';
|
||||||
import CodeEditor from '../CodeEditor';
|
import CodeEditor from '../CodeEditor';
|
||||||
import defaultSettings from '../../data/defaults/defaultSettings.yml';
|
import defaultSettings from '../../data/defaults/defaultSettings.yml';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
const emptySettings = `# Add your custom settings here to override the
|
const emptySettings = `# Add your custom settings here to override the
|
||||||
# default settings.
|
# default settings.
|
||||||
@ -63,7 +64,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
const settings = this.$store.getters['data/settings'];
|
const settings = store.getters['data/settings'];
|
||||||
this.setCustomSettings(settings === '\n' ? emptySettings : settings);
|
this.setCustomSettings(settings === '\n' ? emptySettings : settings);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -19,13 +19,14 @@
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import ModalInner from './common/ModalInner';
|
import ModalInner from './common/ModalInner';
|
||||||
import utils from '../../services/utils';
|
import utils from '../../services/utils';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ModalInner,
|
ModalInner,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
const sponsorToken = this.$store.getters['workspace/sponsorToken'];
|
const sponsorToken = store.getters['workspace/sponsorToken'];
|
||||||
const makeButton = (id, price, description, offer) => {
|
const makeButton = (id, price, description, offer) => {
|
||||||
const params = {
|
const params = {
|
||||||
cmd: '_s-xclick',
|
cmd: '_s-xclick',
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters, mapActions } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
import ModalInner from './common/ModalInner';
|
import ModalInner from './common/ModalInner';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -62,7 +63,7 @@ export default {
|
|||||||
syncLocations: 'currentWithWorkspaceSyncLocation',
|
syncLocations: 'currentWithWorkspaceSyncLocation',
|
||||||
}),
|
}),
|
||||||
currentFileName() {
|
currentFileName() {
|
||||||
return this.$store.getters['file/current'].name;
|
return store.getters['file/current'].name;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -73,7 +74,7 @@ export default {
|
|||||||
if (location.id === 'main') {
|
if (location.id === 'main') {
|
||||||
this.info('This location can not be removed.');
|
this.info('This location can not be removed.');
|
||||||
} else {
|
} 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 CodeEditor from '../CodeEditor';
|
||||||
import emptyTemplateValue from '../../data/empties/emptyTemplateValue.html';
|
import emptyTemplateValue from '../../data/empties/emptyTemplateValue.html';
|
||||||
import emptyTemplateHelpers from '!raw-loader!../../data/empties/emptyTemplateHelpers.js'; // eslint-disable-line
|
import emptyTemplateHelpers from '!raw-loader!../../data/empties/emptyTemplateHelpers.js'; // eslint-disable-line
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
const collator = new Intl.Collator(undefined, { sensitivity: 'base' });
|
const collator = new Intl.Collator(undefined, { sensitivity: 'base' });
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ export default {
|
|||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.$store.getters['data/allTemplatesById'],
|
() => store.getters['data/allTemplatesById'],
|
||||||
(allTemplatesById) => {
|
(allTemplatesById) => {
|
||||||
const templates = {};
|
const templates = {};
|
||||||
// Sort templates by name
|
// Sort templates by name
|
||||||
|
@ -64,6 +64,7 @@
|
|||||||
import { mapGetters, mapActions } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
import ModalInner from './common/ModalInner';
|
import ModalInner from './common/ModalInner';
|
||||||
import workspaceSvc from '../../services/workspaceSvc';
|
import workspaceSvc from '../../services/workspaceSvc';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -95,7 +96,7 @@ export default {
|
|||||||
const workspace = this.workspacesById[this.editedId];
|
const workspace = this.workspacesById[this.editedId];
|
||||||
if (workspace) {
|
if (workspace) {
|
||||||
if (!cancel && this.editingName) {
|
if (!cancel && this.editingName) {
|
||||||
this.$store.dispatch('workspace/patchWorkspacesById', {
|
store.dispatch('workspace/patchWorkspacesById', {
|
||||||
[this.editedId]: {
|
[this.editedId]: {
|
||||||
...workspace,
|
...workspace,
|
||||||
name: this.editingName,
|
name: this.editingName,
|
||||||
@ -114,7 +115,7 @@ export default {
|
|||||||
this.info('Please close the workspace before removing it.');
|
this.info('Please close the workspace before removing it.');
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('modal/open', 'removeWorkspace');
|
await store.dispatch('modal/open', 'removeWorkspace');
|
||||||
workspaceSvc.removeWorkspace(id);
|
workspaceSvc.removeWorkspace(id);
|
||||||
} catch (e) { /* Cancel */ }
|
} catch (e) { /* Cancel */ }
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,6 @@
|
|||||||
<button class="modal__close-button button not-tabbable" @click="config.reject()" v-title="'Close modal'">
|
<button class="modal__close-button button not-tabbable" @click="config.reject()" v-title="'Close modal'">
|
||||||
<icon-close></icon-close>
|
<icon-close></icon-close>
|
||||||
</button>
|
</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>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -15,33 +11,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import googleHelper from '../../../services/providers/helpers/googleHelper';
|
|
||||||
import syncSvc from '../../../services/syncSvc';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('modal', [
|
...mapGetters('modal', [
|
||||||
'config',
|
'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>
|
</script>
|
||||||
@ -64,15 +39,4 @@ export default {
|
|||||||
color: rgba(0, 0, 0, 0.67);
|
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>
|
</style>
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import modalTemplate from '../common/modalTemplate';
|
import modalTemplate from '../common/modalTemplate';
|
||||||
|
import store from '../../../store';
|
||||||
|
|
||||||
export default modalTemplate({
|
export default modalTemplate({
|
||||||
data: () => ({
|
data: () => ({
|
||||||
@ -45,7 +46,7 @@ export default modalTemplate({
|
|||||||
name: this.name,
|
name: this.name,
|
||||||
password: this.password,
|
password: this.password,
|
||||||
};
|
};
|
||||||
this.$store.dispatch('data/addCouchdbToken', token);
|
store.dispatch('data/addCouchdbToken', token);
|
||||||
this.config.resolve();
|
this.config.resolve();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -46,17 +46,14 @@ export default modalTemplate({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
resolve() {
|
resolve() {
|
||||||
if (!this.repoUrl) {
|
const parsedRepo = utils.parseGithubRepoUrl(this.repoUrl);
|
||||||
|
if (!parsedRepo) {
|
||||||
this.setError('repoUrl');
|
this.setError('repoUrl');
|
||||||
}
|
}
|
||||||
if (!this.path) {
|
if (!this.path) {
|
||||||
this.setError('path');
|
this.setError('path');
|
||||||
}
|
}
|
||||||
if (this.repoUrl && this.path) {
|
if (parsedRepo && this.path) {
|
||||||
const parsedRepo = utils.parseGithubRepoUrl(this.repoUrl);
|
|
||||||
if (!parsedRepo) {
|
|
||||||
this.setError('repoUrl');
|
|
||||||
} else {
|
|
||||||
// Return new location
|
// Return new location
|
||||||
const location = githubProvider.makeLocation(
|
const location = githubProvider.makeLocation(
|
||||||
this.config.token,
|
this.config.token,
|
||||||
@ -67,7 +64,6 @@ export default modalTemplate({
|
|||||||
);
|
);
|
||||||
this.config.resolve(location);
|
this.config.resolve(location);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import githubProvider from '../../../services/providers/githubProvider';
|
import githubProvider from '../../../services/providers/githubProvider';
|
||||||
import modalTemplate from '../common/modalTemplate';
|
import modalTemplate from '../common/modalTemplate';
|
||||||
|
import utils from '../../../services/utils';
|
||||||
|
|
||||||
export default modalTemplate({
|
export default modalTemplate({
|
||||||
data: () => ({
|
data: () => ({
|
||||||
@ -60,29 +61,25 @@ export default modalTemplate({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
resolve() {
|
resolve() {
|
||||||
if (!this.repoUrl) {
|
const parsedRepo = utils.parseGithubRepoUrl(this.repoUrl);
|
||||||
|
if (!parsedRepo) {
|
||||||
this.setError('repoUrl');
|
this.setError('repoUrl');
|
||||||
}
|
}
|
||||||
if (!this.path) {
|
if (!this.path) {
|
||||||
this.setError('path');
|
this.setError('path');
|
||||||
}
|
}
|
||||||
if (this.repoUrl && this.path) {
|
if (parsedRepo && this.path) {
|
||||||
const parsedRepo = this.repoUrl.match(/[/:]?([^/:]+)\/([^/]+?)(?:\.git|\/)?$/);
|
|
||||||
if (!parsedRepo) {
|
|
||||||
this.setError('repoUrl');
|
|
||||||
} else {
|
|
||||||
// Return new location
|
// Return new location
|
||||||
const location = githubProvider.makeLocation(
|
const location = githubProvider.makeLocation(
|
||||||
this.config.token,
|
this.config.token,
|
||||||
parsedRepo[1],
|
parsedRepo.owner,
|
||||||
parsedRepo[2],
|
parsedRepo.repo,
|
||||||
this.branch || 'master',
|
this.branch || 'master',
|
||||||
this.path,
|
this.path,
|
||||||
);
|
);
|
||||||
location.templateId = this.selectedTemplate;
|
location.templateId = this.selectedTemplate;
|
||||||
this.config.resolve(location);
|
this.config.resolve(location);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -11,12 +11,6 @@
|
|||||||
<b>Example:</b> https://github.com/benweet/stackedit
|
<b>Example:</b> https://github.com/benweet/stackedit
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</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">
|
<form-entry label="File path" error="path">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
@ -24,6 +18,12 @@
|
|||||||
If the file exists, it will be overwritten.
|
If the file exists, it will be overwritten.
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</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>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
|
@ -11,18 +11,18 @@
|
|||||||
<b>Example:</b> https://github.com/benweet/stackedit
|
<b>Example:</b> https://github.com/benweet/stackedit
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</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">
|
<form-entry label="Folder path" info="optional">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
If not supplied, the root folder will be used.
|
If not supplied, the root folder will be used.
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</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>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<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 googleHelper from '../../../services/providers/helpers/googleHelper';
|
||||||
import googleDriveProvider from '../../../services/providers/googleDriveProvider';
|
import googleDriveProvider from '../../../services/providers/googleDriveProvider';
|
||||||
import modalTemplate from '../common/modalTemplate';
|
import modalTemplate from '../common/modalTemplate';
|
||||||
|
import store from '../../../store';
|
||||||
|
|
||||||
export default modalTemplate({
|
export default modalTemplate({
|
||||||
data: () => ({
|
data: () => ({
|
||||||
@ -69,12 +70,12 @@ export default modalTemplate({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openFolder() {
|
openFolder() {
|
||||||
return this.$store.dispatch(
|
return store.dispatch(
|
||||||
'modal/hideUntil',
|
'modal/hideUntil',
|
||||||
googleHelper.openPicker(this.config.token, 'folder')
|
googleHelper.openPicker(this.config.token, 'folder')
|
||||||
.then((folders) => {
|
.then((folders) => {
|
||||||
if (folders[0]) {
|
if (folders[0]) {
|
||||||
this.$store.dispatch('data/patchLocalSettings', {
|
store.dispatch('data/patchLocalSettings', {
|
||||||
googleDriveFolderId: folders[0].id,
|
googleDriveFolderId: folders[0].id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
import googleHelper from '../../../services/providers/helpers/googleHelper';
|
import googleHelper from '../../../services/providers/helpers/googleHelper';
|
||||||
import googleDriveProvider from '../../../services/providers/googleDriveProvider';
|
import googleDriveProvider from '../../../services/providers/googleDriveProvider';
|
||||||
import modalTemplate from '../common/modalTemplate';
|
import modalTemplate from '../common/modalTemplate';
|
||||||
|
import store from '../../../store';
|
||||||
|
|
||||||
export default modalTemplate({
|
export default modalTemplate({
|
||||||
data: () => ({
|
data: () => ({
|
||||||
@ -42,12 +43,12 @@ export default modalTemplate({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openFolder() {
|
openFolder() {
|
||||||
return this.$store.dispatch(
|
return store.dispatch(
|
||||||
'modal/hideUntil',
|
'modal/hideUntil',
|
||||||
googleHelper.openPicker(this.config.token, 'folder')
|
googleHelper.openPicker(this.config.token, 'folder')
|
||||||
.then((folders) => {
|
.then((folders) => {
|
||||||
if (folders[0]) {
|
if (folders[0]) {
|
||||||
this.$store.dispatch('data/patchLocalSettings', {
|
store.dispatch('data/patchLocalSettings', {
|
||||||
googleDriveFolderId: folders[0].id,
|
googleDriveFolderId: folders[0].id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
import googleHelper from '../../../services/providers/helpers/googleHelper';
|
import googleHelper from '../../../services/providers/helpers/googleHelper';
|
||||||
import modalTemplate from '../common/modalTemplate';
|
import modalTemplate from '../common/modalTemplate';
|
||||||
import utils from '../../../services/utils';
|
import utils from '../../../services/utils';
|
||||||
|
import store from '../../../store';
|
||||||
|
|
||||||
export default modalTemplate({
|
export default modalTemplate({
|
||||||
computedLocalSettings: {
|
computedLocalSettings: {
|
||||||
@ -33,12 +34,12 @@ export default modalTemplate({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openFolder() {
|
openFolder() {
|
||||||
return this.$store.dispatch(
|
return store.dispatch(
|
||||||
'modal/hideUntil',
|
'modal/hideUntil',
|
||||||
googleHelper.openPicker(this.config.token, 'folder')
|
googleHelper.openPicker(this.config.token, 'folder')
|
||||||
.then((folders) => {
|
.then((folders) => {
|
||||||
if (folders[0]) {
|
if (folders[0]) {
|
||||||
this.$store.dispatch('data/patchLocalSettings', {
|
store.dispatch('data/patchLocalSettings', {
|
||||||
googleDriveWorkspaceFolderId: folders[0].id,
|
googleDriveWorkspaceFolderId: folders[0].id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,10 @@
|
|||||||
<form-entry label="Client Unique Identifier" error="clientId">
|
<form-entry label="Client Unique Identifier" error="clientId">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="clientId" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="clientId" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
You have to configure an OAuth Client with redirect URL <b>{{redirectUrl}}</b><br>
|
You have to configure an OAuth Client with redirect URL <b>{{redirectUrl}}</b>
|
||||||
<a href="https://support.zendesk.com/hc/en-us/articles/203663836" target="_blank"><b>More info</b></a>
|
</div>
|
||||||
|
<div class="form-entry__actions">
|
||||||
|
<a href="https://support.zendesk.com/hc/en-us/articles/203663836" target="_blank">More info</a>
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</form-entry>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,11 +20,6 @@ export default {
|
|||||||
'layoutSettings',
|
'layoutSettings',
|
||||||
'tokens',
|
'tokens',
|
||||||
],
|
],
|
||||||
userIdPrefixes: {
|
|
||||||
db: 'dropbox',
|
|
||||||
gh: 'github',
|
|
||||||
go: 'google',
|
|
||||||
},
|
|
||||||
textMaxLength: 250000,
|
textMaxLength: 250000,
|
||||||
defaultName: 'Untitled',
|
defaultName: 'Untitled',
|
||||||
};
|
};
|
||||||
|
@ -19,6 +19,11 @@ export default () => ({
|
|||||||
githubPublishTemplate: 'jekyllSite',
|
githubPublishTemplate: 'jekyllSite',
|
||||||
gistIsPublic: false,
|
gistIsPublic: false,
|
||||||
gistPublishTemplate: 'plainText',
|
gistPublishTemplate: 'plainText',
|
||||||
|
gitlabServerUrl: '',
|
||||||
|
gitlabApplicationId: '',
|
||||||
|
gitlabProjectUrl: '',
|
||||||
|
gitlabWorkspaceProjectUrl: '',
|
||||||
|
gitlabPublishTemplate: 'plainText',
|
||||||
wordpressDomain: '',
|
wordpressDomain: '',
|
||||||
wordpressPublishTemplate: 'plainHtml',
|
wordpressPublishTemplate: 'plainHtml',
|
||||||
zendeskSiteUrl: '',
|
zendeskSiteUrl: '',
|
||||||
|
@ -77,8 +77,8 @@ turndown:
|
|||||||
linkStyle: inlined
|
linkStyle: inlined
|
||||||
linkReferenceStyle: full
|
linkReferenceStyle: full
|
||||||
|
|
||||||
# GitHub commit messages
|
# GitHub/GitLab commit messages
|
||||||
github:
|
git:
|
||||||
createFileMessage: '{{path}} created from https://stackedit.io/'
|
createFileMessage: '{{path}} created from https://stackedit.io/'
|
||||||
updateFileMessage: '{{path}} updated from https://stackedit.io/'
|
updateFileMessage: '{{path}} updated from https://stackedit.io/'
|
||||||
deleteFileMessage: '{{path}} deleted from https://stackedit.io/'
|
deleteFileMessage: '{{path}} deleted from https://stackedit.io/'
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
**Where is my data stored?**
|
**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?**
|
**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';
|
return 'github';
|
||||||
case 'gist':
|
case 'gist':
|
||||||
return 'github';
|
return 'github';
|
||||||
|
case 'gitlabWorkspace':
|
||||||
|
return 'gitlab';
|
||||||
case 'bloggerPage':
|
case 'bloggerPage':
|
||||||
return 'blogger';
|
return 'blogger';
|
||||||
case 'couchdbWorkspace':
|
case 'couchdbWorkspace':
|
||||||
@ -57,6 +59,10 @@ export default {
|
|||||||
background-image: url(../assets/iconGithub.svg);
|
background-image: url(../assets/iconGithub.svg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-provider--gitlab {
|
||||||
|
background-image: url(../assets/iconGitlab.svg);
|
||||||
|
}
|
||||||
|
|
||||||
.icon-provider--dropbox {
|
.icon-provider--dropbox {
|
||||||
background-image: url(../assets/iconDropbox.svg);
|
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);
|
const entries = await dropboxHelper.listRevisions(token, syncLocation.dropboxFileId);
|
||||||
return entries.map(entry => ({
|
return entries.map(entry => ({
|
||||||
id: entry.rev,
|
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(),
|
created: new Date(entry.server_modified).getTime(),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
@ -65,7 +65,7 @@ export default new Provider({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return entries.map((entry) => {
|
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 });
|
userSvc.addInfo({ id: sub, name: entry.user.login, imageUrl: entry.user.avatar_url });
|
||||||
return {
|
return {
|
||||||
sub,
|
sub,
|
||||||
|
@ -134,7 +134,7 @@ export default new Provider({
|
|||||||
} else if (committer && committer.login) {
|
} else if (committer && committer.login) {
|
||||||
user = committer;
|
user = committer;
|
||||||
}
|
}
|
||||||
const sub = `gh:${user.id}`;
|
const sub = `${githubHelper.subPrefix}:${user.id}`;
|
||||||
userSvc.addInfo({ id: sub, name: user.login, imageUrl: user.avatar_url });
|
userSvc.addInfo({ id: sub, name: user.login, imageUrl: user.avatar_url });
|
||||||
const date = (commit.author && commit.author.date)
|
const date = (commit.author && commit.author.date)
|
||||||
|| (commit.committer && commit.committer.date);
|
|| (commit.committer && commit.committer.date);
|
||||||
|
@ -3,19 +3,11 @@ import githubHelper from './helpers/githubHelper';
|
|||||||
import Provider from './common/Provider';
|
import Provider from './common/Provider';
|
||||||
import utils from '../utils';
|
import utils from '../utils';
|
||||||
import userSvc from '../userSvc';
|
import userSvc from '../userSvc';
|
||||||
|
import gitWorkspaceSvc from '../gitWorkspaceSvc';
|
||||||
|
|
||||||
const getAbsolutePath = ({ id }) =>
|
const getAbsolutePath = ({ id }) =>
|
||||||
`${store.getters['workspace/currentWorkspace'].path || ''}${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({
|
export default new Provider({
|
||||||
id: 'githubWorkspace',
|
id: 'githubWorkspace',
|
||||||
name: 'GitHub',
|
name: 'GitHub',
|
||||||
@ -90,238 +82,13 @@ export default new Provider({
|
|||||||
return store.getters['workspace/workspacesById'][workspaceId];
|
return store.getters['workspace/workspacesById'][workspaceId];
|
||||||
},
|
},
|
||||||
getChanges() {
|
getChanges() {
|
||||||
const syncToken = store.getters['workspace/syncToken'];
|
|
||||||
return githubHelper.getTree({
|
return githubHelper.getTree({
|
||||||
...store.getters['workspace/currentWorkspace'],
|
...store.getters['workspace/currentWorkspace'],
|
||||||
token: syncToken,
|
token: this.getToken(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
prepareChanges(tree) {
|
prepareChanges(tree) {
|
||||||
const workspacePath = store.getters['workspace/currentWorkspace'].path || '';
|
return gitWorkspaceSvc.makeChanges(tree);
|
||||||
|
|
||||||
// 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;
|
|
||||||
},
|
},
|
||||||
async saveWorkspaceItem({ item }) {
|
async saveWorkspaceItem({ item }) {
|
||||||
const syncData = {
|
const syncData = {
|
||||||
@ -342,20 +109,20 @@ export default new Provider({
|
|||||||
token: syncToken,
|
token: syncToken,
|
||||||
path: getAbsolutePath(syncData),
|
path: getAbsolutePath(syncData),
|
||||||
content: '',
|
content: '',
|
||||||
sha: treeShaMap[syncData.id],
|
sha: gitWorkspaceSvc.shaByPath[syncData.id],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Return sync data to save
|
// Return sync data to save
|
||||||
return { syncData };
|
return { syncData };
|
||||||
},
|
},
|
||||||
async removeWorkspaceItem({ syncData }) {
|
async removeWorkspaceItem({ syncData }) {
|
||||||
if (treeShaMap[syncData.id]) {
|
if (gitWorkspaceSvc.shaByPath[syncData.id]) {
|
||||||
const syncToken = store.getters['workspace/syncToken'];
|
const syncToken = store.getters['workspace/syncToken'];
|
||||||
await githubHelper.removeFile({
|
await githubHelper.removeFile({
|
||||||
...store.getters['workspace/currentWorkspace'],
|
...store.getters['workspace/currentWorkspace'],
|
||||||
token: syncToken,
|
token: syncToken,
|
||||||
path: getAbsolutePath(syncData),
|
path: getAbsolutePath(syncData),
|
||||||
sha: treeShaMap[syncData.id],
|
sha: gitWorkspaceSvc.shaByPath[syncData.id],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -370,7 +137,7 @@ export default new Provider({
|
|||||||
token,
|
token,
|
||||||
path: getAbsolutePath(fileSyncData),
|
path: getAbsolutePath(fileSyncData),
|
||||||
});
|
});
|
||||||
treeShaMap[fileSyncData.id] = sha;
|
gitWorkspaceSvc.shaByPath[fileSyncData.id] = sha;
|
||||||
const content = Provider.parseContent(data, contentId);
|
const content = Provider.parseContent(data, contentId);
|
||||||
return {
|
return {
|
||||||
content,
|
content,
|
||||||
@ -391,7 +158,7 @@ export default new Provider({
|
|||||||
token,
|
token,
|
||||||
path: getAbsolutePath(syncData),
|
path: getAbsolutePath(syncData),
|
||||||
});
|
});
|
||||||
treeShaMap[syncData.id] = sha;
|
gitWorkspaceSvc.shaByPath[syncData.id] = sha;
|
||||||
const item = JSON.parse(data);
|
const item = JSON.parse(data);
|
||||||
return {
|
return {
|
||||||
item,
|
item,
|
||||||
@ -410,7 +177,7 @@ export default new Provider({
|
|||||||
token,
|
token,
|
||||||
path: absolutePath,
|
path: absolutePath,
|
||||||
content: Provider.serializeContent(content),
|
content: Provider.serializeContent(content),
|
||||||
sha: treeShaMap[path],
|
sha: gitWorkspaceSvc.shaByPath[path],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Return new sync data
|
// Return new sync data
|
||||||
@ -440,7 +207,7 @@ export default new Provider({
|
|||||||
token,
|
token,
|
||||||
path: getAbsolutePath(syncData),
|
path: getAbsolutePath(syncData),
|
||||||
content: JSON.stringify(item),
|
content: JSON.stringify(item),
|
||||||
sha: treeShaMap[path],
|
sha: gitWorkspaceSvc.shaByPath[path],
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -472,7 +239,7 @@ export default new Provider({
|
|||||||
} else if (committer && committer.login) {
|
} else if (committer && committer.login) {
|
||||||
user = committer;
|
user = committer;
|
||||||
}
|
}
|
||||||
const sub = `gh:${user.id}`;
|
const sub = `${githubHelper.subPrefix}:${user.id}`;
|
||||||
userSvc.addInfo({ id: sub, name: user.login, imageUrl: user.avatar_url });
|
userSvc.addInfo({ id: sub, name: user.login, imageUrl: user.avatar_url });
|
||||||
const date = (commit.author && commit.author.date)
|
const date = (commit.author && commit.author.date)
|
||||||
|| (commit.committer && commit.committer.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);
|
const revisions = await googleHelper.getAppDataFileRevisions(token, contentSyncData.id);
|
||||||
return revisions.map(revision => ({
|
return revisions.map(revision => ({
|
||||||
id: revision.id,
|
id: revision.id,
|
||||||
sub: `go:${revision.lastModifyingUser.permissionId}`,
|
sub: `${googleHelper.subPrefix}:${revision.lastModifyingUser.permissionId}`,
|
||||||
created: new Date(revision.modifiedTime).getTime(),
|
created: new Date(revision.modifiedTime).getTime(),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
@ -195,7 +195,7 @@ export default new Provider({
|
|||||||
const revisions = await googleHelper.getFileRevisions(token, syncLocation.driveFileId);
|
const revisions = await googleHelper.getFileRevisions(token, syncLocation.driveFileId);
|
||||||
return revisions.map(revision => ({
|
return revisions.map(revision => ({
|
||||||
id: revision.id,
|
id: revision.id,
|
||||||
sub: `go:${revision.lastModifyingUser.permissionId}`,
|
sub: `${googleHelper.subPrefix}:${revision.lastModifyingUser.permissionId}`,
|
||||||
created: new Date(revision.modifiedTime).getTime(),
|
created: new Date(revision.modifiedTime).getTime(),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
@ -512,7 +512,7 @@ export default new Provider({
|
|||||||
const revisions = await googleHelper.getFileRevisions(token, fileSyncData.id);
|
const revisions = await googleHelper.getFileRevisions(token, fileSyncData.id);
|
||||||
return revisions.map(revision => ({
|
return revisions.map(revision => ({
|
||||||
id: revision.id,
|
id: revision.id,
|
||||||
sub: `go:${revision.lastModifyingUser.permissionId}`,
|
sub: `${googleHelper.subPrefix}:${revision.lastModifyingUser.permissionId}`,
|
||||||
created: new Date(revision.modifiedTime).getTime(),
|
created: new Date(revision.modifiedTime).getTime(),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import networkSvc from '../../networkSvc';
|
import networkSvc from '../../networkSvc';
|
||||||
import utils from '../../utils';
|
import utils from '../../utils';
|
||||||
import store from '../../../store';
|
import store from '../../../store';
|
||||||
|
import userSvc from '../../userSvc';
|
||||||
|
|
||||||
const request = async (token, options = {}) => {
|
const request = async (token, options = {}) => {
|
||||||
const baseUrl = `${token.dbUrl}/`;
|
const baseUrl = `${token.dbUrl}/`;
|
||||||
@ -117,7 +118,7 @@ export default {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { item, time: Date.now() },
|
body: { item, time: Date.now() },
|
||||||
};
|
};
|
||||||
const userId = store.getters['workspace/userId'];
|
const userId = userSvc.getCurrentUserId();
|
||||||
if (userId) {
|
if (userId) {
|
||||||
options.body.sub = userId;
|
options.body.sub = userId;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import networkSvc from '../../networkSvc';
|
import networkSvc from '../../networkSvc';
|
||||||
|
import userSvc from '../../userSvc';
|
||||||
import store from '../../../store';
|
import store from '../../../store';
|
||||||
|
|
||||||
const getAppKey = (fullAccess) => {
|
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 {
|
export default {
|
||||||
|
subPrefix,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.dropbox.com/developers/documentation/http/documentation#oauth2-authorize
|
* 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) {
|
async startOauth2(fullAccess, sub = null, silent = false) {
|
||||||
const { accessToken } = await networkSvc.startOauth2(
|
const { accessToken } = await networkSvc.startOauth2(
|
||||||
@ -42,6 +73,11 @@ export default {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'https://api.dropboxapi.com/2/users/get_current_account',
|
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
|
// Check the returned sub consistency
|
||||||
if (sub && `${body.account_id}` !== sub) {
|
if (sub && `${body.account_id}` !== sub) {
|
||||||
@ -64,28 +100,6 @@ export default {
|
|||||||
return this.startOauth2(fullAccess);
|
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
|
* https://www.dropbox.com/developers/documentation/http/documentation#files-upload
|
||||||
*/
|
*/
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import utils from '../../utils';
|
import utils from '../../utils';
|
||||||
import networkSvc from '../../networkSvc';
|
import networkSvc from '../../networkSvc';
|
||||||
import store from '../../../store';
|
import store from '../../../store';
|
||||||
|
import userSvc from '../../userSvc';
|
||||||
|
|
||||||
const clientId = GITHUB_CLIENT_ID;
|
const clientId = GITHUB_CLIENT_ID;
|
||||||
const getScopes = token => [token.repoFullAccess ? 'repo' : 'public_repo', 'gist'];
|
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);
|
.then(res => res.body);
|
||||||
|
|
||||||
const getCommitMessage = (name, path) => {
|
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);
|
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 {
|
export default {
|
||||||
|
subPrefix,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://developer.github.com/apps/building-oauth-apps/authorization-options-for-oauth-apps/
|
* https://developer.github.com/apps/building-oauth-apps/authorization-options-for-oauth-apps/
|
||||||
@ -61,6 +90,11 @@ export default {
|
|||||||
access_token: accessToken,
|
access_token: accessToken,
|
||||||
},
|
},
|
||||||
})).body;
|
})).body;
|
||||||
|
userSvc.addInfo({
|
||||||
|
id: `${subPrefix}:${user.id}`,
|
||||||
|
name: user.login,
|
||||||
|
imageUrl: user.avatar_url || '',
|
||||||
|
});
|
||||||
|
|
||||||
// Check the returned sub consistency
|
// Check the returned sub consistency
|
||||||
if (sub && `${user.id}` !== sub) {
|
if (sub && `${user.id}` !== sub) {
|
||||||
@ -84,28 +118,6 @@ export default {
|
|||||||
return this.startOauth2(getScopes({ repoFullAccess }));
|
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/repos/commits/#get-a-single-commit
|
||||||
* https://developer.github.com/v3/git/trees/#get-a-tree
|
* 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 utils from '../../utils';
|
||||||
import networkSvc from '../../networkSvc';
|
import networkSvc from '../../networkSvc';
|
||||||
import store from '../../../store';
|
import store from '../../../store';
|
||||||
|
import userSvc from '../../userSvc';
|
||||||
|
|
||||||
const clientId = GOOGLE_CLIENT_ID;
|
const clientId = GOOGLE_CLIENT_ID;
|
||||||
const apiKey = 'AIzaSyC_M4RA9pY6XmM9pmFxlT59UPMO7aHr9kk';
|
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 {
|
export default {
|
||||||
|
subPrefix,
|
||||||
folderMimeType: 'application/vnd.google-apps.folder',
|
folderMimeType: 'application/vnd.google-apps.folder',
|
||||||
driveState,
|
driveState,
|
||||||
driveActionFolder: null,
|
driveActionFolder: null,
|
||||||
@ -119,16 +158,14 @@ export default {
|
|||||||
driveFullAccess: scopes.indexOf('https://www.googleapis.com/auth/drive') !== -1,
|
driveFullAccess: scopes.indexOf('https://www.googleapis.com/auth/drive') !== -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
|
||||||
// Call the user info endpoint
|
// Call the user info endpoint
|
||||||
token.name = (await this.getUser(token.sub)).displayName;
|
const user = await getUser('me', token);
|
||||||
} catch (err) {
|
token.name = user.displayName;
|
||||||
if (err.status === 404) {
|
userSvc.addInfo({
|
||||||
store.dispatch('notification/info', 'Please activate Google Plus to change your account name and photo.');
|
id: `${subPrefix}:${user.id}`,
|
||||||
} else {
|
name: user.displayName,
|
||||||
throw err;
|
imageUrl: (user.image.url || '').replace(/\bsz?=\d+$/, 'sz=40'),
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (existingToken) {
|
if (existingToken) {
|
||||||
// We probably retrieved a new token with restricted scopes.
|
// We probably retrieved a new token with restricted scopes.
|
||||||
@ -413,7 +450,7 @@ export default {
|
|||||||
});
|
});
|
||||||
revisions.forEach((revision) => {
|
revisions.forEach((revision) => {
|
||||||
store.commit('userInfo/addItem', {
|
store.commit('userInfo/addItem', {
|
||||||
id: `go:${revision.lastModifyingUser.permissionId}`,
|
id: `${subPrefix}:${revision.lastModifyingUser.permissionId}`,
|
||||||
name: revision.lastModifyingUser.displayName,
|
name: revision.lastModifyingUser.displayName,
|
||||||
imageUrl: revision.lastModifyingUser.photoLink,
|
imageUrl: revision.lastModifyingUser.photoLink,
|
||||||
});
|
});
|
||||||
@ -454,22 +491,6 @@ export default {
|
|||||||
return this.$downloadFileRevision(refreshedToken, fileId, revisionId);
|
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
|
* https://developers.google.com/drive/v3/reference/changes/list
|
||||||
*/
|
*/
|
||||||
|
@ -5,11 +5,11 @@ import Provider from './common/Provider';
|
|||||||
export default new Provider({
|
export default new Provider({
|
||||||
id: 'wordpress',
|
id: 'wordpress',
|
||||||
name: 'WordPress',
|
name: 'WordPress',
|
||||||
getToken(location) {
|
getToken({ sub }) {
|
||||||
return store.getters['data/wordpressTokensBySub'][location.sub];
|
return store.getters['data/wordpressTokensBySub'][sub];
|
||||||
},
|
},
|
||||||
getLocationUrl(location) {
|
getLocationUrl({ siteId, postId }) {
|
||||||
return `https://wordpress.com/post/${location.siteId}/${location.postId}`;
|
return `https://wordpress.com/post/${siteId}/${postId}`;
|
||||||
},
|
},
|
||||||
getLocationDescription({ postId }) {
|
getLocationDescription({ postId }) {
|
||||||
return postId;
|
return postId;
|
||||||
|
@ -5,12 +5,12 @@ import Provider from './common/Provider';
|
|||||||
export default new Provider({
|
export default new Provider({
|
||||||
id: 'zendesk',
|
id: 'zendesk',
|
||||||
name: 'Zendesk',
|
name: 'Zendesk',
|
||||||
getToken(location) {
|
getToken({ sub }) {
|
||||||
return store.getters['data/zendeskTokensBySub'][location.sub];
|
return store.getters['data/zendeskTokensBySub'][sub];
|
||||||
},
|
},
|
||||||
getLocationUrl(location) {
|
getLocationUrl({ sub, locale, articleId }) {
|
||||||
const token = this.getToken(location);
|
const token = this.getToken({ sub });
|
||||||
return `https://${token.subdomain}.zendesk.com/hc/${location.locale}/articles/${location.articleId}`;
|
return `https://${token.subdomain}.zendesk.com/hc/${locale}/articles/${articleId}`;
|
||||||
},
|
},
|
||||||
getLocationDescription({ articleId }) {
|
getLocationDescription({ articleId }) {
|
||||||
return articleId;
|
return articleId;
|
||||||
|
@ -129,7 +129,8 @@ const createPublishLocation = (publishLocation) => {
|
|||||||
store.dispatch(
|
store.dispatch(
|
||||||
'queue/enqueue',
|
'queue/enqueue',
|
||||||
async () => {
|
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}".`);
|
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 googleDriveAppDataProvider from './providers/googleDriveAppDataProvider';
|
||||||
import './providers/couchdbWorkspaceProvider';
|
import './providers/couchdbWorkspaceProvider';
|
||||||
import './providers/githubWorkspaceProvider';
|
import './providers/githubWorkspaceProvider';
|
||||||
|
import './providers/gitlabWorkspaceProvider';
|
||||||
import './providers/googleDriveWorkspaceProvider';
|
import './providers/googleDriveWorkspaceProvider';
|
||||||
import tempFileSvc from './tempFileSvc';
|
import tempFileSvc from './tempFileSvc';
|
||||||
import workspaceSvc from './workspaceSvc';
|
import workspaceSvc from './workspaceSvc';
|
||||||
|
@ -1,27 +1,49 @@
|
|||||||
import googleHelper from './providers/helpers/googleHelper';
|
|
||||||
import githubHelper from './providers/helpers/githubHelper';
|
|
||||||
import store from '../store';
|
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 parseUserId = (userId) => {
|
||||||
const prefix = userId[2] === ':' && userId.slice(0, 2);
|
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];
|
return type ? [type, userId.slice(3)] : ['google', userId];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
addInfo({ id, name, imageUrl }) {
|
setInfoResolver(type, subPrefix, resolver) {
|
||||||
promised[id] = true;
|
infoResolversByType[type] = resolver;
|
||||||
store.commit('userInfo/addItem', { id, name, imageUrl });
|
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) {
|
async getInfo(userId) {
|
||||||
if (userId && !promised[userId]) {
|
if (!userId) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let infoPromise = infoPromisesByUserId[userId];
|
||||||
|
if (infoPromise) {
|
||||||
|
return infoPromise;
|
||||||
|
}
|
||||||
|
|
||||||
const [type, sub] = parseUserId(userId);
|
const [type, sub] = parseUserId(userId);
|
||||||
|
|
||||||
// Try to find a token with this sub
|
// Try to find a token with this sub to resolve name as soon as possible
|
||||||
const token = store.getters[`data/${type}TokensBySub`][sub];
|
const token = store.getters[`data/${type}TokensBySub`][sub];
|
||||||
if (token) {
|
if (token) {
|
||||||
store.commit('userInfo/addItem', {
|
store.commit('userInfo/addItem', {
|
||||||
@ -30,41 +52,28 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user info from provider
|
if (store.state.offline) {
|
||||||
if (!store.state.offline) {
|
return {};
|
||||||
promised[userId] = true;
|
}
|
||||||
switch (type) {
|
|
||||||
case 'dropbox': {
|
// Get user info from helper
|
||||||
const dropboxToken = Object.values(store.getters['data/dropboxTokensBySub'])[0];
|
infoPromise = new Promise(async (resolve) => {
|
||||||
|
const infoResolver = infoResolversByType[type];
|
||||||
|
if (infoResolver) {
|
||||||
try {
|
try {
|
||||||
await dropboxHelper.getAccount(dropboxToken, sub);
|
const userInfo = await infoResolver(sub);
|
||||||
|
this.addInfo(userInfo);
|
||||||
|
resolve(userInfo);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!token || err.status !== 404) {
|
if (err && err.message === 'RETRY') {
|
||||||
promised[userId] = false;
|
infoPromisesByUserId[userId] = null;
|
||||||
}
|
}
|
||||||
}
|
resolve({});
|
||||||
break;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
infoPromisesByUserId[userId] = infoPromise;
|
||||||
|
return infoPromise;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -287,6 +287,10 @@ export default {
|
|||||||
repo: parsedRepo[2],
|
repo: parsedRepo[2],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
parseGitlabProjectPath(url) {
|
||||||
|
const parsedProject = url && url.match(/^https:\/\/[^/]+\/(.+?)(?:\.git|\/)?$/);
|
||||||
|
return parsedProject && parsedProject[1];
|
||||||
|
},
|
||||||
createHiddenIframe(url) {
|
createHiddenIframe(url) {
|
||||||
const iframeElt = document.createElement('iframe');
|
const iframeElt = document.createElement('iframe');
|
||||||
iframeElt.style.position = 'absolute';
|
iframeElt.style.position = 'absolute';
|
||||||
|
@ -85,7 +85,7 @@ const additionalTemplates = {
|
|||||||
|
|
||||||
// For tokens
|
// For tokens
|
||||||
const tokenAdder = providerId => ({ getters, dispatch }, token) => {
|
const tokenAdder = providerId => ({ getters, dispatch }, token) => {
|
||||||
dispatch('patchTokensByProviderId', {
|
dispatch('patchTokensByType', {
|
||||||
[providerId]: {
|
[providerId]: {
|
||||||
...getters[`${providerId}TokensBySub`],
|
...getters[`${providerId}TokensBySub`],
|
||||||
[token.sub]: token,
|
[token.sub]: token,
|
||||||
@ -188,13 +188,14 @@ export default {
|
|||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
dataSyncDataById: getter('dataSyncData'),
|
dataSyncDataById: getter('dataSyncData'),
|
||||||
tokensByProviderId: getter('tokens'),
|
tokensByType: getter('tokens'),
|
||||||
googleTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.google || {},
|
googleTokensBySub: (state, { tokensByType }) => tokensByType.google || {},
|
||||||
couchdbTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.couchdb || {},
|
couchdbTokensBySub: (state, { tokensByType }) => tokensByType.couchdb || {},
|
||||||
dropboxTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.dropbox || {},
|
dropboxTokensBySub: (state, { tokensByType }) => tokensByType.dropbox || {},
|
||||||
githubTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.github || {},
|
githubTokensBySub: (state, { tokensByType }) => tokensByType.github || {},
|
||||||
wordpressTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.wordpress || {},
|
gitlabTokensBySub: (state, { tokensByType }) => tokensByType.gitlab || {},
|
||||||
zendeskTokensBySub: (state, { tokensByProviderId }) => tokensByProviderId.zendesk || {},
|
wordpressTokensBySub: (state, { tokensByType }) => tokensByType.wordpress || {},
|
||||||
|
zendeskTokensBySub: (state, { tokensByType }) => tokensByType.zendesk || {},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setSettings: setter('settings'),
|
setSettings: setter('settings'),
|
||||||
@ -258,11 +259,12 @@ export default {
|
|||||||
setSyncDataById: setter('syncData'),
|
setSyncDataById: setter('syncData'),
|
||||||
patchSyncDataById: patcher('syncData'),
|
patchSyncDataById: patcher('syncData'),
|
||||||
patchDataSyncDataById: patcher('dataSyncData'),
|
patchDataSyncDataById: patcher('dataSyncData'),
|
||||||
patchTokensByProviderId: patcher('tokens'),
|
patchTokensByType: patcher('tokens'),
|
||||||
addGoogleToken: tokenAdder('google'),
|
addGoogleToken: tokenAdder('google'),
|
||||||
addCouchdbToken: tokenAdder('couchdb'),
|
addCouchdbToken: tokenAdder('couchdb'),
|
||||||
addDropboxToken: tokenAdder('dropbox'),
|
addDropboxToken: tokenAdder('dropbox'),
|
||||||
addGithubToken: tokenAdder('github'),
|
addGithubToken: tokenAdder('github'),
|
||||||
|
addGitlabToken: tokenAdder('gitlab'),
|
||||||
addWordpressToken: tokenAdder('wordpress'),
|
addWordpressToken: tokenAdder('wordpress'),
|
||||||
addZendeskToken: tokenAdder('zendesk'),
|
addZendeskToken: tokenAdder('zendesk'),
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import utils from '../services/utils';
|
import utils from '../services/utils';
|
||||||
import providerRegistry from '../services/providers/common/providerRegistry';
|
import providerRegistry from '../services/providers/common/providerRegistry';
|
||||||
import constants from '../data/constants';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
@ -44,9 +43,11 @@ export default {
|
|||||||
currentWorkspace: ({ currentWorkspaceId }, { workspacesById, mainWorkspace }) =>
|
currentWorkspace: ({ currentWorkspaceId }, { workspacesById, mainWorkspace }) =>
|
||||||
workspacesById[currentWorkspaceId] || mainWorkspace,
|
workspacesById[currentWorkspaceId] || mainWorkspace,
|
||||||
currentWorkspaceIsGit: (state, { currentWorkspace }) =>
|
currentWorkspaceIsGit: (state, { currentWorkspace }) =>
|
||||||
currentWorkspace.providerId === 'githubWorkspace',
|
currentWorkspace.providerId === 'githubWorkspace'
|
||||||
|
|| currentWorkspace.providerId === 'gitlabWorkspace',
|
||||||
currentWorkspaceHasUniquePaths: (state, { currentWorkspace }) =>
|
currentWorkspaceHasUniquePaths: (state, { currentWorkspace }) =>
|
||||||
currentWorkspace.providerId === 'githubWorkspace',
|
currentWorkspace.providerId === 'githubWorkspace'
|
||||||
|
|| currentWorkspace.providerId === 'gitlabWorkspace',
|
||||||
lastSyncActivityKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastSyncActivity`,
|
lastSyncActivityKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastSyncActivity`,
|
||||||
lastFocusKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastWindowFocus`,
|
lastFocusKey: (state, { currentWorkspace }) => `${currentWorkspace.id}/lastWindowFocus`,
|
||||||
mainWorkspaceToken: (state, getters, rootState, rootGetters) =>
|
mainWorkspaceToken: (state, getters, rootState, rootGetters) =>
|
||||||
@ -62,33 +63,28 @@ export default {
|
|||||||
return rootGetters['data/googleTokensBySub'][currentWorkspace.sub];
|
return rootGetters['data/googleTokensBySub'][currentWorkspace.sub];
|
||||||
case 'githubWorkspace':
|
case 'githubWorkspace':
|
||||||
return rootGetters['data/githubTokensBySub'][currentWorkspace.sub];
|
return rootGetters['data/githubTokensBySub'][currentWorkspace.sub];
|
||||||
|
case 'gitlabWorkspace':
|
||||||
|
return rootGetters['data/gitlabTokensBySub'][currentWorkspace.sub];
|
||||||
case 'couchdbWorkspace':
|
case 'couchdbWorkspace':
|
||||||
return rootGetters['data/couchdbTokensBySub'][currentWorkspace.id];
|
return rootGetters['data/couchdbTokensBySub'][currentWorkspace.id];
|
||||||
default:
|
default:
|
||||||
return mainWorkspaceToken;
|
return mainWorkspaceToken;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
loginToken: (state, { currentWorkspace, mainWorkspaceToken }, rootState, rootGetters) => {
|
loginType: (state, { currentWorkspace }) => {
|
||||||
switch (currentWorkspace.providerId) {
|
switch (currentWorkspace.providerId) {
|
||||||
case 'googleDriveWorkspace':
|
case 'googleDriveWorkspace':
|
||||||
return rootGetters['data/googleTokensBySub'][currentWorkspace.sub];
|
|
||||||
case 'githubWorkspace':
|
|
||||||
return rootGetters['data/githubTokensBySub'][currentWorkspace.sub];
|
|
||||||
default:
|
default:
|
||||||
return mainWorkspaceToken;
|
return 'google';
|
||||||
|
case 'githubWorkspace':
|
||||||
|
return 'github';
|
||||||
|
case 'gitlabWorkspace':
|
||||||
|
return 'gitlab';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
userId: (state, { loginToken }, rootState, rootGetters) => {
|
loginToken: (state, { loginType, currentWorkspace }, rootState, rootGetters) => {
|
||||||
if (!loginToken) {
|
const tokensBySub = rootGetters['data/tokensByType'][loginType];
|
||||||
return null;
|
return tokensBySub && tokensBySub[currentWorkspace.sub];
|
||||||
}
|
|
||||||
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;
|
|
||||||
},
|
},
|
||||||
sponsorToken: (state, { mainWorkspaceToken }) => mainWorkspaceToken,
|
sponsorToken: (state, { mainWorkspaceToken }) => mainWorkspaceToken,
|
||||||
},
|
},
|
||||||
|
@ -49,6 +49,10 @@ body {
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type=checkbox] {
|
||||||
|
outline: #349be8 auto 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -153,6 +157,7 @@ textarea {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
margin: -2px 0 -2px 4px;
|
margin: -2px 0 -2px 4px;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
|
font-size: 18px;
|
||||||
|
|
||||||
&:active,
|
&:active,
|
||||||
&:focus,
|
&:focus,
|
||||||
|
@ -9,10 +9,10 @@ $font-size-monospace: 0.85em;
|
|||||||
$highlighting-color: #ff0;
|
$highlighting-color: #ff0;
|
||||||
$selection-highlighting-color: #ff9632;
|
$selection-highlighting-color: #ff9632;
|
||||||
$info-bg: transparentize($selection-highlighting-color, 0.85);
|
$info-bg: transparentize($selection-highlighting-color, 0.85);
|
||||||
$code-border-radius: 2px;
|
$code-border-radius: 3px;
|
||||||
$link-color: #0c93e4;
|
$link-color: #0c93e4;
|
||||||
$error-color: #f31;
|
$error-color: #f31;
|
||||||
$border-radius-base: 2px;
|
$border-radius-base: 3px;
|
||||||
$hr-color: rgba(128, 128, 128, 0.2);
|
$hr-color: rgba(128, 128, 128, 0.2);
|
||||||
$navbar-bg: #2c2c2c;
|
$navbar-bg: #2c2c2c;
|
||||||
$navbar-color: mix($navbar-bg, #fff, 33%);
|
$navbar-color: mix($navbar-bg, #fff, 33%);
|
||||||
|
Loading…
Reference in New Issue
Block a user