Added explorer
This commit is contained in:
parent
c07fc7135e
commit
0c27a8337a
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ dist/
|
|||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
.vscode
|
||||||
|
BIN
src/assets/fonts/RobotoMono-Bold.woff
Normal file
BIN
src/assets/fonts/RobotoMono-Bold.woff
Normal file
Binary file not shown.
BIN
src/assets/fonts/RobotoMono-Regular.woff
Normal file
BIN
src/assets/fonts/RobotoMono-Regular.woff
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -20,17 +20,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapActions } from 'vuex';
|
import { mapState, mapMutations } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: mapState('layout', [
|
computed: mapState('layout', [
|
||||||
'showNavigationBar',
|
'showNavigationBar',
|
||||||
'showEditor',
|
|
||||||
'showSidePreview',
|
'showSidePreview',
|
||||||
'showSideBar',
|
|
||||||
'showStatusBar',
|
'showStatusBar',
|
||||||
]),
|
]),
|
||||||
methods: mapActions('layout', [
|
methods: mapMutations('layout', [
|
||||||
'toggleNavigationBar',
|
'toggleNavigationBar',
|
||||||
'toggleEditor',
|
'toggleEditor',
|
||||||
'toggleSidePreview',
|
'toggleSidePreview',
|
||||||
@ -61,7 +59,6 @@ export default {
|
|||||||
width: 26px;
|
width: 26px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
border-radius: 3px;
|
|
||||||
margin: 3px 0;
|
margin: 3px 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="editor">
|
<div class="editor">
|
||||||
<pre class="editor__inner markdown-highlighting" :style="{ 'padding-left': editorPadding + 'px', 'padding-right': editorPadding + 'px' }"></pre>
|
<pre class="editor__inner markdown-highlighting" :style="{'padding-left': styles.editorPadding + 'px', 'padding-right': styles.editorPadding + 'px'}"></pre>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: mapState('layout', [
|
computed: mapGetters('layout', [
|
||||||
'editorPadding',
|
'styles',
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,18 +1,122 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="explorer">
|
<div class="explorer flex flex--column">
|
||||||
<div class="side-title">
|
<div class="side-title">
|
||||||
<div class="side-title__text">
|
<button class="side-title__button side-title__button--right button" @click="toggleExplorer(false)">
|
||||||
Explorer
|
<icon-close></icon-close>
|
||||||
</div>
|
</button>
|
||||||
|
<button class="side-title__button button" @click="newItem()">
|
||||||
|
<icon-file-plus></icon-file-plus>
|
||||||
|
</button>
|
||||||
|
<button class="side-title__button button" @click="newItem(true)">
|
||||||
|
<icon-folder-plus></icon-folder-plus>
|
||||||
|
</button>
|
||||||
|
<button class="side-title__button button" @click="editItem()">
|
||||||
|
<icon-pen></icon-pen>
|
||||||
|
</button>
|
||||||
|
<button class="side-title__button button" @click="deleteItem()">
|
||||||
|
<icon-delete></icon-delete>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="explorer__tree" :class="{'explorer__tree--new-item': !newChildNode.isNil}" tabindex="0">
|
||||||
|
<explorer-node :node="rootNode" :depth="0"></explorer-node>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapState, mapGetters, mapMutations } from 'vuex';
|
||||||
|
import ExplorerNode from './ExplorerNode';
|
||||||
|
import emptyFile from '../data/emptyFile';
|
||||||
|
import emptyFolder from '../data/emptyFolder';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
ExplorerNode,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState('explorer', [
|
||||||
|
'newChildNode',
|
||||||
|
]),
|
||||||
|
...mapGetters('explorer', [
|
||||||
|
'rootNode',
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapMutations('explorer', [
|
||||||
|
'setSelectedId',
|
||||||
|
]),
|
||||||
|
...mapMutations('layout', [
|
||||||
|
'toggleExplorer',
|
||||||
|
]),
|
||||||
|
newItem(isFolder) {
|
||||||
|
const parentId = this.$store.getters['explorer/selectedNodeFolder'].item.id;
|
||||||
|
this.$store.dispatch('explorer/openNode', parentId);
|
||||||
|
this.$store.commit('explorer/setNewItem', {
|
||||||
|
...isFolder ? emptyFolder() : emptyFile(),
|
||||||
|
parentId,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
editItem() {
|
||||||
|
const selectNode = this.$store.getters['explorer/selectedNode'];
|
||||||
|
this.$store.commit('explorer/setEditingId', selectNode.item.id);
|
||||||
|
},
|
||||||
|
deleteItem() {
|
||||||
|
// const selectNode = this.$store.getters['explorer/selectedNode'];
|
||||||
|
// switch (this.node.item.type) {
|
||||||
|
// case 'file':
|
||||||
|
// default:
|
||||||
|
// this.$store.commit('files/setCurrentId', id);
|
||||||
|
// break;
|
||||||
|
// case 'folder':
|
||||||
|
// this.$store.commit('explorer/toggleOpenNode', id);
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$store.watch(
|
||||||
|
() => this.$store.getters['files/current'].id,
|
||||||
|
(currentFileId) => {
|
||||||
|
this.setSelectedId(currentFileId);
|
||||||
|
}, {
|
||||||
|
immediate: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import 'common/variables.scss';
|
.explorer,
|
||||||
|
.explorer__tree {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-title {
|
||||||
|
height: 44px;
|
||||||
|
line-height: 44px;
|
||||||
|
padding: 4px 8px 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-title__button {
|
||||||
|
width: 36px;
|
||||||
|
padding: 6px;
|
||||||
|
display: inline-block;
|
||||||
|
background-color: transparent;
|
||||||
|
opacity: 0.75;
|
||||||
|
|
||||||
|
/* prevent from seeing wrapped buttons */
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
&:active,
|
||||||
|
&:focus,
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-title__button--right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="explorer-item">
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import 'common/variables.scss';
|
|
||||||
</style>
|
|
172
src/components/ExplorerNode.vue
Normal file
172
src/components/ExplorerNode.vue
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div class="explorer-node" :class="{'explorer-node--selected': selected, 'explorer-node--open': open}">
|
||||||
|
<div v-if="editing" class="explorer-node__item-editor" :class="['explorer-node__item-editor--' + node.item.type]" :style="{'padding-left': leftPadding}">
|
||||||
|
<input type="text" class="text-input" v-focus @blur="submitEdit()" @keyup.enter="submitEdit()" @keyup.esc="submitEdit(true)" v-model="editingNodeName">
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!node.isRoot" class="explorer-node__item" :class="['explorer-node__item--' + node.item.type]" :style="{'padding-left': leftPadding}" @click="select(node.item.id)">
|
||||||
|
{{node.item.name}}
|
||||||
|
</div>
|
||||||
|
<div v-if="node.isFolder && open">
|
||||||
|
<explorer-node v-for="node in node.folders" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
|
||||||
|
<div v-if="newChild" class="explorer-node__new-child" :class="['explorer-node__new-child--' + newChild.item.type]" :style="{'padding-left': childLeftPadding}">
|
||||||
|
<input type="text" class="text-input" v-focus @blur="submitNewChild()" @keyup.enter="submitNewChild()" @keyup.esc="submitNewChild(true)" v-model.trim="newChildName">
|
||||||
|
</div>
|
||||||
|
<explorer-node v-for="node in node.files" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import utils from '../services/utils';
|
||||||
|
import defaultContent from '../data/defaultContent.md';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'explorer-node',
|
||||||
|
props: ['node', 'depth'],
|
||||||
|
directives: {
|
||||||
|
focus: {
|
||||||
|
inserted(el) {
|
||||||
|
el.focus();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
editingValue: '',
|
||||||
|
}),
|
||||||
|
computed: {
|
||||||
|
leftPadding() {
|
||||||
|
return `${this.depth * 15}px`;
|
||||||
|
},
|
||||||
|
childLeftPadding() {
|
||||||
|
return `${(this.depth + 1) * 15}px`;
|
||||||
|
},
|
||||||
|
selected() {
|
||||||
|
return this.$store.getters['explorer/selectedNode'] === this.node;
|
||||||
|
},
|
||||||
|
editing() {
|
||||||
|
return this.$store.getters['explorer/editingNode'] === this.node;
|
||||||
|
},
|
||||||
|
open() {
|
||||||
|
return this.$store.state.explorer.openNodes[this.node.item.id] || this.node.isRoot;
|
||||||
|
},
|
||||||
|
newChild() {
|
||||||
|
return this.$store.getters['explorer/newChildNodeParent'] === this.node
|
||||||
|
&& this.$store.state.explorer.newChildNode;
|
||||||
|
},
|
||||||
|
newChildName: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.explorer.newChildNode.item.name;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit('explorer/setNewItemName', value && value.slice(0, 250));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
editingNodeName: {
|
||||||
|
get() {
|
||||||
|
return this.$store.getters['explorer/editingNode'].item.name;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.editingValue = value.trim();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
select(id) {
|
||||||
|
this.$store.commit('explorer/setSelectedId', id);
|
||||||
|
if (this.node.isFolder) {
|
||||||
|
this.$store.commit('explorer/toggleOpenNode', id);
|
||||||
|
} else {
|
||||||
|
this.$store.commit('files/setCurrentId', id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitNewChild(cancel) {
|
||||||
|
const newChildNode = this.$store.state.explorer.newChildNode;
|
||||||
|
if (!cancel && newChildNode.item.name) {
|
||||||
|
const id = utils.uid();
|
||||||
|
if (newChildNode.isFolder) {
|
||||||
|
this.$store.commit('folders/setItem', {
|
||||||
|
...newChildNode.item,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const contentId = utils.uid();
|
||||||
|
this.$store.commit('contents/setItem', {
|
||||||
|
id: contentId,
|
||||||
|
text: defaultContent,
|
||||||
|
});
|
||||||
|
this.$store.commit('files/setItem', {
|
||||||
|
...newChildNode.item,
|
||||||
|
id,
|
||||||
|
contentId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.select(id);
|
||||||
|
}
|
||||||
|
this.$store.commit('explorer/setNewItem', null);
|
||||||
|
},
|
||||||
|
submitEdit(cancel) {
|
||||||
|
const id = this.$store.getters['explorer/editingNode'].item.id;
|
||||||
|
const value = this.editingValue;
|
||||||
|
if (!cancel && id && value) {
|
||||||
|
this.$store.commit('files/patchItem', {
|
||||||
|
id,
|
||||||
|
name: value.slice(0, 250),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.$store.commit('explorer/setEditingId', null);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
$item-font-size: 14px;
|
||||||
|
|
||||||
|
.explorer-node__item {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: $item-font-size;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
.explorer-node--selected > & {
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
|
.explorer__tree:focus & {
|
||||||
|
background-color: #39f;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.explorer__tree--new-item & {
|
||||||
|
opacity: 0.33;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.explorer-node__item--folder,
|
||||||
|
.explorer-node__item-editor--folder,
|
||||||
|
.explorer-node__new-child--folder {
|
||||||
|
&::before {
|
||||||
|
content: '▸';
|
||||||
|
position: absolute;
|
||||||
|
margin-left: -13px;
|
||||||
|
|
||||||
|
.explorer-node--open > & {
|
||||||
|
content: '▾';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$new-child-height: 25px;
|
||||||
|
|
||||||
|
.explorer-node__item-editor,
|
||||||
|
.explorer-node__new-child {
|
||||||
|
padding: 1px 10px;
|
||||||
|
|
||||||
|
.text-input {
|
||||||
|
font-size: $item-font-size;
|
||||||
|
padding: 2px;
|
||||||
|
height: $new-child-height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,29 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout">
|
<div class="layout">
|
||||||
<div class="layout__panel flex flex--row">
|
<div class="layout__panel flex flex--row">
|
||||||
<div class="layout__panel layout__panel--explorer" v-show="showExplorer" :style="{ width: explorerWidth + 'px' }">
|
<div class="layout__panel layout__panel--explorer" v-show="styles.showExplorer" :style="{ width: constants.explorerWidth + 'px' }">
|
||||||
<explorer></explorer>
|
<explorer></explorer>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout__panel flex flex--column" :style="{ width: innerWidth + 'px' }">
|
<div class="layout__panel flex flex--column" :style="{ width: styles.innerWidth + 'px' }">
|
||||||
<div class="layout__panel layout__panel--navigation-bar" v-show="showNavigationBar || !showEditor" :style="{ height: navigationBarHeight + 'px' }">
|
<div class="layout__panel layout__panel--navigation-bar" v-show="styles.showNavigationBar" :style="{ height: constants.navigationBarHeight + 'px' }">
|
||||||
<navigation-bar></navigation-bar>
|
<navigation-bar></navigation-bar>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout__panel flex flex--row" :style="{ height: innerHeight + 'px' }">
|
<div class="layout__panel flex flex--row" :style="{ height: styles.innerHeight + 'px' }">
|
||||||
<div class="layout__panel layout__panel--editor" v-show="showEditor" :style="{ width: editorWidth + 'px', 'font-size': fontSize + 'px' }">
|
<div class="layout__panel layout__panel--editor" v-show="styles.showEditor" :style="{ width: styles.editorWidth + 'px', 'font-size': styles.fontSize + 'px' }">
|
||||||
<editor></editor>
|
<editor></editor>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout__panel layout__panel--button-bar" v-show="showEditor" :style="{ width: buttonBarWidth + 'px' }">
|
<div class="layout__panel layout__panel--button-bar" v-show="styles.showEditor" :style="{ width: constants.buttonBarWidth + 'px' }">
|
||||||
<button-bar></button-bar>
|
<button-bar></button-bar>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout__panel layout__panel--preview" v-show="showSidePreview || !showEditor" :style="{ width: previewWidth + 'px', 'font-size': fontSize + 'px' }">
|
<div class="layout__panel layout__panel--preview" v-show="styles.showPreview" :style="{ width: styles.previewWidth + 'px', 'font-size': styles.fontSize + 'px' }">
|
||||||
<preview></preview>
|
<preview></preview>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout__panel layout__panel--status-bar" v-show="showStatusBar" :style="{ height: statusBarHeight + 'px' }">
|
<div class="layout__panel layout__panel--status-bar" v-show="styles.showStatusBar" :style="{ height: constants.statusBarHeight + 'px' }">
|
||||||
<status-bar></status-bar>
|
<status-bar></status-bar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout__panel layout__panel--side-bar" v-show="showSideBar" :style="{ width: sideBarWidth + 'px' }">
|
<div class="layout__panel layout__panel--side-bar" v-show="styles.showSideBar" :style="{ width: constants.sideBarWidth + 'px' }">
|
||||||
<side-bar></side-bar>
|
<side-bar></side-bar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapActions } from 'vuex';
|
import { mapState, mapGetters, mapMutations } from 'vuex';
|
||||||
import NavigationBar from './NavigationBar';
|
import NavigationBar from './NavigationBar';
|
||||||
import ButtonBar from './ButtonBar';
|
import ButtonBar from './ButtonBar';
|
||||||
import StatusBar from './StatusBar';
|
import StatusBar from './StatusBar';
|
||||||
@ -40,7 +40,6 @@ import SideBar from './SideBar';
|
|||||||
import Editor from './Editor';
|
import Editor from './Editor';
|
||||||
import Preview from './Preview';
|
import Preview from './Preview';
|
||||||
import editorSvc from '../services/editorSvc';
|
import editorSvc from '../services/editorSvc';
|
||||||
import constants from '../services/constants';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -52,33 +51,23 @@ export default {
|
|||||||
Editor,
|
Editor,
|
||||||
Preview,
|
Preview,
|
||||||
},
|
},
|
||||||
computed: mapState('layout', {
|
computed: {
|
||||||
explorerWidth: 'explorerWidth',
|
...mapState('layout', [
|
||||||
sideBarWidth: 'sideBarWidth',
|
'constants',
|
||||||
navigationBarHeight: 'navigationBarHeight',
|
]),
|
||||||
buttonBarWidth: 'buttonBarWidth',
|
...mapGetters('layout', [
|
||||||
statusBarHeight: 'statusBarHeight',
|
'styles',
|
||||||
showEditor: 'showEditor',
|
]),
|
||||||
showSidePreview: 'showSidePreview',
|
},
|
||||||
showNavigationBar: 'showNavigationBar',
|
|
||||||
showStatusBar: 'showStatusBar',
|
|
||||||
showSideBar: 'showSideBar',
|
|
||||||
showExplorer: 'showExplorer',
|
|
||||||
fontSize: 'fontSize',
|
|
||||||
innerWidth: 'innerWidth',
|
|
||||||
innerHeight: 'innerHeight',
|
|
||||||
previewWidth: 'previewWidth',
|
|
||||||
editorWidth: 'editorWidth',
|
|
||||||
}),
|
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions('layout', [
|
...mapMutations('layout', [
|
||||||
'updateStyle',
|
'updateBodySize',
|
||||||
]),
|
]),
|
||||||
saveSelection: () => editorSvc.saveSelection(true),
|
saveSelection: () => editorSvc.saveSelection(true),
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.updateStyle();
|
this.updateBodySize();
|
||||||
window.addEventListener('resize', this.updateStyle);
|
window.addEventListener('resize', this.updateBodySize);
|
||||||
window.addEventListener('keyup', this.saveSelection);
|
window.addEventListener('keyup', this.saveSelection);
|
||||||
window.addEventListener('mouseup', this.saveSelection);
|
window.addEventListener('mouseup', this.saveSelection);
|
||||||
window.addEventListener('contextmenu', this.saveSelection);
|
window.addEventListener('contextmenu', this.saveSelection);
|
||||||
@ -106,10 +95,10 @@ export default {
|
|||||||
/ (sectionDesc.tocDimension.height || 1);
|
/ (sectionDesc.tocDimension.height || 1);
|
||||||
const editorScrollTop = sectionDesc.editorDimension.startOffset
|
const editorScrollTop = sectionDesc.editorDimension.startOffset
|
||||||
+ (sectionDesc.editorDimension.height * posInSection);
|
+ (sectionDesc.editorDimension.height * posInSection);
|
||||||
editorElt.parentNode.scrollTop = editorScrollTop - constants.scrollOffset;
|
editorElt.parentNode.scrollTop = editorScrollTop;
|
||||||
const previewScrollTop = sectionDesc.previewDimension.startOffset
|
const previewScrollTop = sectionDesc.previewDimension.startOffset
|
||||||
+ (sectionDesc.previewDimension.height * posInSection);
|
+ (sectionDesc.previewDimension.height * posInSection);
|
||||||
previewElt.parentNode.scrollTop = previewScrollTop - constants.scrollOffset;
|
previewElt.parentNode.scrollTop = previewScrollTop;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -162,7 +151,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.layout__panel--explorer {
|
.layout__panel--explorer {
|
||||||
background-color: #ddd;
|
background-color: #dadada;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout__panel--button-bar,
|
.layout__panel--button-bar,
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="navigation-bar" v-bind:class="{'navigation-bar--editor': showEditor}">
|
<div class="navigation-bar" v-bind:class="{'navigation-bar--editor': styles.showEditor}">
|
||||||
<div class="navigation-bar__inner navigation-bar__inner--left navigation-bar__inner--button">
|
<div class="navigation-bar__inner navigation-bar__inner--left navigation-bar__inner--button">
|
||||||
<button class="navigation-bar__button button" @click="toggleExplorer()">
|
<button class="navigation-bar__button button" @click="toggleExplorer()">
|
||||||
<icon-menu></icon-menu>
|
<icon-folder-multiple></icon-folder-multiple>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="navigation-bar__inner navigation-bar__inner--right navigation-bar__inner--button">
|
<div class="navigation-bar__inner navigation-bar__inner--right navigation-bar__inner--button">
|
||||||
<button class="navigation-bar__button button" @click="toggleExplorer()">
|
<button class="navigation-bar__button button" @click="toggleExplorer()">
|
||||||
<icon-settings></icon-settings>
|
<icon-menu></icon-menu>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="navigation-bar__inner navigation-bar__inner--right flex flex--row">
|
<div class="navigation-bar__inner navigation-bar__inner--right flex flex--row">
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="navigation-bar__title navigation-bar__title--fake text-input"></div>
|
<div class="navigation-bar__title navigation-bar__title--fake text-input"></div>
|
||||||
<div class="navigation-bar__title navigation-bar__title--text text-input" v-bind:style="{maxWidth: titleMaxWidth + 'px'}">{{title}}</div>
|
<div class="navigation-bar__title navigation-bar__title--text text-input" v-bind:style="{maxWidth: styles.titleMaxWidth + 'px'}">{{title}}</div>
|
||||||
<input class="navigation-bar__title navigation-bar__title--input text-input" v-bind:class="{'navigation-bar__title--focus': titleFocus, 'navigation-bar__title--scrolling': titleScrolling}" v-bind:style="{width: titleWidth + 'px'}" @focus="editTitle(true)" @blur="editTitle(false)" @keyup.enter="submitTitle()" @keyup.esc="submitTitle(true)" v-on:mouseenter="titleHover = true" v-on:mouseleave="titleHover = false" v-model="title">
|
<input class="navigation-bar__title navigation-bar__title--input text-input" v-bind:class="{'navigation-bar__title--focus': titleFocus, 'navigation-bar__title--scrolling': titleScrolling}" v-bind:style="{width: titleWidth + 'px'}" @focus="editTitle(true)" @blur="editTitle(false)" @keyup.enter="submitTitle()" @keyup.esc="submitTitle(true)" v-on:mouseenter="titleHover = true" v-on:mouseleave="titleHover = false" v-model="title">
|
||||||
</div>
|
</div>
|
||||||
<div class="navigation-bar__inner navigation-bar__inner--edit-buttons">
|
<div class="navigation-bar__inner navigation-bar__inner--edit-buttons">
|
||||||
@ -60,7 +60,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapActions } from 'vuex';
|
import { mapGetters, mapMutations } from 'vuex';
|
||||||
import editorSvc from '../services/editorSvc';
|
import editorSvc from '../services/editorSvc';
|
||||||
import animationSvc from '../services/animationSvc';
|
import animationSvc from '../services/animationSvc';
|
||||||
|
|
||||||
@ -72,19 +72,18 @@ export default {
|
|||||||
titleHover: false,
|
titleHover: false,
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
...mapState('layout', {
|
...mapGetters('layout', [
|
||||||
showEditor: 'showEditor',
|
'styles',
|
||||||
titleMaxWidth: 'titleMaxWidth',
|
]),
|
||||||
}),
|
|
||||||
titleWidth() {
|
titleWidth() {
|
||||||
if (!this.mounted) {
|
if (!this.mounted) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
this.titleFakeElt.textContent = this.title;
|
this.titleFakeElt.textContent = this.title;
|
||||||
const width = this.titleFakeElt.getBoundingClientRect().width + 1; // 1px for the caret
|
const width = this.titleFakeElt.getBoundingClientRect().width + 2; // 2px for the caret
|
||||||
return width < this.titleMaxWidth
|
return width < this.styles.titleMaxWidth
|
||||||
? width
|
? width
|
||||||
: this.titleMaxWidth;
|
: this.styles.titleMaxWidth;
|
||||||
},
|
},
|
||||||
titleScrolling() {
|
titleScrolling() {
|
||||||
const result = this.titleHover && !this.titleFocus;
|
const result = this.titleHover && !this.titleFocus;
|
||||||
@ -106,7 +105,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions('layout', [
|
...mapMutations('layout', [
|
||||||
'toggleExplorer',
|
'toggleExplorer',
|
||||||
'toggleSideBar',
|
'toggleSideBar',
|
||||||
]),
|
]),
|
||||||
@ -120,7 +119,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
const title = this.title.trim();
|
const title = this.title.trim();
|
||||||
if (title) {
|
if (title) {
|
||||||
this.$store.dispatch('files/patchCurrent', { name: title });
|
this.$store.dispatch('files/patchCurrent', { name: title.slice(0, 250) });
|
||||||
} else {
|
} else {
|
||||||
this.title = this.$store.getters['files/current'].name;
|
this.title = this.$store.getters['files/current'].name;
|
||||||
}
|
}
|
||||||
@ -161,6 +160,10 @@ export default {
|
|||||||
|
|
||||||
.navigation-bar__inner--left {
|
.navigation-bar__inner--left {
|
||||||
float: left;
|
float: left;
|
||||||
|
|
||||||
|
&.navigation-bar__inner--button {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation-bar__inner--right {
|
.navigation-bar__inner--right {
|
||||||
@ -176,9 +179,16 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.navigation-bar__button {
|
.navigation-bar__button {
|
||||||
display: inline-block;
|
|
||||||
width: 34px;
|
width: 34px;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
|
|
||||||
|
/* prevent from seeing wrapped buttons */
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.navigation-bar__inner--button & {
|
||||||
|
padding: 7px;
|
||||||
|
width: 38px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation-bar__title,
|
.navigation-bar__title,
|
||||||
@ -186,7 +196,7 @@ export default {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: $navbar-color;
|
color: $navbar-color;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
font-weight: 400;
|
font-size: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation-bar__title--input,
|
.navigation-bar__title--input,
|
||||||
@ -235,7 +245,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.navigation-bar__spinner {
|
.navigation-bar__spinner {
|
||||||
margin: 10px 5px 0;
|
margin: 10px 5px 0 15px;
|
||||||
color: rgba(255, 255, 255, 0.33);
|
color: rgba(255, 255, 255, 0.33);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="preview">
|
<div class="preview">
|
||||||
<div class="preview__inner" :style="{ 'padding-left': previewPadding + 'px', 'padding-right': previewPadding + 'px' }">
|
<div class="preview__inner" :style="{ 'padding-left': styles.previewPadding + 'px', 'padding-right': styles.previewPadding + 'px' }">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
const appUri = `${window.location.protocol}//${window.location.host}`;
|
const appUri = `${window.location.protocol}//${window.location.host}`;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: mapState('layout', [
|
computed: mapGetters('layout', [
|
||||||
'previewPadding',
|
'styles',
|
||||||
]),
|
]),
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$el.addEventListener('click', (evt) => {
|
this.$el.addEventListener('click', (evt) => {
|
||||||
@ -45,4 +45,17 @@ export default {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 1035px 360px;
|
padding: 0 1035px 360px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preview__inner > :first-child {
|
||||||
|
& > h1,
|
||||||
|
& > h2,
|
||||||
|
& > h3,
|
||||||
|
& > h4,
|
||||||
|
& > h5,
|
||||||
|
& > h6 {
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="stat-panel panel no-overflow">
|
<div class="stat-panel panel no-overflow">
|
||||||
<div class="stat-panel__block stat-panel__block--left" v-if="showEditor">
|
<div class="stat-panel__block stat-panel__block--left" v-if="styles.showEditor">
|
||||||
<span class="stat-panel__block-name">
|
<span class="stat-panel__block-name">
|
||||||
Text
|
Markdown
|
||||||
<small v-show="textSelection">(selection)</small>
|
<small v-show="textSelection">(selection)</small>
|
||||||
</span>
|
</span>
|
||||||
<span v-for="stat in textStats" :key="stat.id">
|
<span v-for="stat in textStats" :key="stat.id">
|
||||||
@ -23,7 +23,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import editorSvc from '../services/editorSvc';
|
import editorSvc from '../services/editorSvc';
|
||||||
import editorEngineSvc from '../services/editorEngineSvc';
|
import editorEngineSvc from '../services/editorEngineSvc';
|
||||||
import utils from '../services/utils';
|
import utils from '../services/utils';
|
||||||
@ -54,9 +54,9 @@ export default {
|
|||||||
new Stat('paragraphs', '\\S.*'),
|
new Stat('paragraphs', '\\S.*'),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
computed: mapState('layout', {
|
computed: mapGetters('layout', [
|
||||||
showEditor: 'showEditor',
|
'styles',
|
||||||
}),
|
]),
|
||||||
created() {
|
created() {
|
||||||
editorSvc.$on('sectionList', () => this.computeText());
|
editorSvc.$on('sectionList', () => this.computeText());
|
||||||
editorSvc.$on('selectionRange', () => this.computeText());
|
editorSvc.$on('selectionRange', () => this.computeText());
|
||||||
|
@ -38,10 +38,11 @@ textarea {
|
|||||||
|
|
||||||
.text-input {
|
.text-input {
|
||||||
display: block;
|
display: block;
|
||||||
|
font-variant-ligatures: no-common-ligatures;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
padding: 3px 12px;
|
padding: 3px 12px;
|
||||||
font-size: 22px;
|
font-size: inherit;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
@ -57,7 +58,7 @@ textarea {
|
|||||||
height: 36px;
|
height: 36px;
|
||||||
padding: 3px 12px;
|
padding: 3px 12px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-size: 22px;
|
font-size: inherit;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -106,13 +107,3 @@ textarea {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-title {
|
|
||||||
height: 44px;
|
|
||||||
line-height: 44px;
|
|
||||||
padding: 0 10px;
|
|
||||||
background-color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-title__text {
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
@ -27,15 +27,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Inconsolata';
|
font-family: 'Roboto Mono';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: url('../assets/fonts/inconsolata.woff') format('woff');
|
src: url('../assets/fonts/RobotoMono-Regular.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Inconsolata';
|
font-family: 'Roboto Mono';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
src: url('../assets/fonts/inconsolata-bold.woff') format('woff');
|
src: url('../assets/fonts/RobotoMono-Bold.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
$font-family-main: Lato, 'Helvetica Neue', Helvetica, sans-serif;
|
$font-family-main: Lato, 'Helvetica Neue', Helvetica, sans-serif;
|
||||||
$font-family-monospace: Inconsolata, 'Lucida Sans Typewriter', 'Lucida Console', monaco, Courrier, monospace;
|
$font-family-monospace: 'Roboto Mono', 'Lucida Sans Typewriter', 'Lucida Console', monaco, Courrier, monospace;
|
||||||
$line-height-base: 1.67;
|
$line-height-base: 1.67;
|
||||||
$line-height-title: 1.33;
|
$line-height-title: 1.33;
|
||||||
$font-size-monospace: 0.95em;
|
$font-size-monospace: 0.85em;
|
||||||
$code-bg: rgba(0, 0, 0, 0.05);
|
$code-bg: rgba(0, 0, 0, 0.05);
|
||||||
$code-border-radius: 2px;
|
$code-border-radius: 2px;
|
||||||
$link-color: #4a80cf;
|
$link-color: #4a80cf;
|
||||||
@ -10,7 +10,7 @@ $border-radius-base: 2px;
|
|||||||
$hr-color: rgba(128, 128, 128, 0.2);
|
$hr-color: rgba(128, 128, 128, 0.2);
|
||||||
$navbar-color: rgba(255, 255, 255, 0.67);
|
$navbar-color: rgba(255, 255, 255, 0.67);
|
||||||
$navbar-hover-color: #fff;
|
$navbar-hover-color: #fff;
|
||||||
$navbar-hover-background: #484848;
|
$navbar-hover-background: rgba(255, 255, 255, 0.1);
|
||||||
|
|
||||||
$editor-color: rgba(0, 0, 0, 0.8);
|
$editor-color: rgba(0, 0, 0, 0.8);
|
||||||
$editor-color-light: rgba(0, 0, 0, 0.28);
|
$editor-color-light: rgba(0, 0, 0, 0.28);
|
||||||
|
4
src/data/defaultContent.md
Normal file
4
src/data/defaultContent.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
> Written with [StackEdit](https://stackedit.io/).
|
@ -1,4 +1,5 @@
|
|||||||
export default () => ({
|
export default () => ({
|
||||||
|
id: null,
|
||||||
type: 'content',
|
type: 'content',
|
||||||
state: {},
|
state: {},
|
||||||
text: '\n',
|
text: '\n',
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
export default () => ({
|
export default () => ({
|
||||||
|
id: null,
|
||||||
type: 'file',
|
type: 'file',
|
||||||
name: '',
|
name: '',
|
||||||
folderId: null,
|
parentId: null,
|
||||||
contentId: null,
|
contentId: null,
|
||||||
});
|
});
|
||||||
|
6
src/data/emptyFolder.js
Normal file
6
src/data/emptyFolder.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default () => ({
|
||||||
|
id: null,
|
||||||
|
type: 'folder',
|
||||||
|
name: '',
|
||||||
|
parentId: null,
|
||||||
|
});
|
5
src/icons/Close.vue
Normal file
5
src/icons/Close.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" class="icon" width="100%" height="100%" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
|
||||||
|
<path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
5
src/icons/Delete.vue
Normal file
5
src/icons/Delete.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" class="icon" width="100%" height="100%" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
|
||||||
|
<path d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19C6,20.1 6.9,21 8,21H16C17.1,21 18,20.1 18,19V7H6V19Z" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
5
src/icons/FilePlus.vue
Normal file
5
src/icons/FilePlus.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" class="icon" width="100%" height="100%" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
|
||||||
|
<path d="M13,9H18.5L13,3.5V9M6,2H14L20,8V20C20,21.1 19.1,22 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M11,15V12H9V15H6V17H9V20H11V17H14V15H11Z" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
5
src/icons/FolderMultiple.vue
Normal file
5
src/icons/FolderMultiple.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" class="icon" width="100%" height="100%" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
|
||||||
|
<path d="M22,4H14L12,2H6C4.9,2 4,2.9 4,4V16C4,17.1 4.9,18 6,18H22C23.1,18 24,17.1 24,16V6C24,4.9 23.1,4 22,4M2,6H0V11H0V20C0,21.1 0.9,22 2,22H20V20H2V6Z" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
5
src/icons/FolderPlus.vue
Normal file
5
src/icons/FolderPlus.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" class="icon" width="100%" height="100%" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
|
||||||
|
<path d="M10,4L12,6H20C21.1,6 22,6.9 22,8V18C22,19.1 21.1,20 20,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10M15,9V12H12V14H15V17H17V14H20V12H17V9H15Z" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
5
src/icons/Pen.vue
Normal file
5
src/icons/Pen.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" class="icon" width="100%" height="100%" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
|
||||||
|
<path d="M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
@ -17,6 +17,12 @@ import SidePreview from './SidePreview';
|
|||||||
import Eye from './Eye';
|
import Eye from './Eye';
|
||||||
import Menu from './Menu';
|
import Menu from './Menu';
|
||||||
import Settings from './Settings';
|
import Settings from './Settings';
|
||||||
|
import FilePlus from './FilePlus';
|
||||||
|
import FolderPlus from './FolderPlus';
|
||||||
|
import Delete from './Delete';
|
||||||
|
import Close from './Close';
|
||||||
|
import FolderMultiple from './FolderMultiple';
|
||||||
|
import Pen from './Pen';
|
||||||
|
|
||||||
Vue.component('iconFormatBold', FormatBold);
|
Vue.component('iconFormatBold', FormatBold);
|
||||||
Vue.component('iconFormatItalic', FormatItalic);
|
Vue.component('iconFormatItalic', FormatItalic);
|
||||||
@ -36,3 +42,9 @@ Vue.component('iconSidePreview', SidePreview);
|
|||||||
Vue.component('iconEye', Eye);
|
Vue.component('iconEye', Eye);
|
||||||
Vue.component('iconMenu', Menu);
|
Vue.component('iconMenu', Menu);
|
||||||
Vue.component('iconSettings', Settings);
|
Vue.component('iconSettings', Settings);
|
||||||
|
Vue.component('iconFilePlus', FilePlus);
|
||||||
|
Vue.component('iconFolderPlus', FolderPlus);
|
||||||
|
Vue.component('iconDelete', Delete);
|
||||||
|
Vue.component('iconClose', Close);
|
||||||
|
Vue.component('iconFolderMultiple', FolderMultiple);
|
||||||
|
Vue.component('iconPen', Pen);
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
export default {
|
|
||||||
scrollOffset: 0,
|
|
||||||
};
|
|
@ -9,7 +9,6 @@ import markdownConversionSvc from './markdownConversionSvc';
|
|||||||
import markdownGrammarSvc from './markdownGrammarSvc';
|
import markdownGrammarSvc from './markdownGrammarSvc';
|
||||||
import sectionUtils from './sectionUtils';
|
import sectionUtils from './sectionUtils';
|
||||||
import extensionSvc from './extensionSvc';
|
import extensionSvc from './extensionSvc';
|
||||||
import constants from './constants';
|
|
||||||
import animationSvc from './animationSvc';
|
import animationSvc from './animationSvc';
|
||||||
import editorEngineSvc from './editorEngineSvc';
|
import editorEngineSvc from './editorEngineSvc';
|
||||||
import store from '../store';
|
import store from '../store';
|
||||||
@ -63,7 +62,7 @@ const editorSvc = Object.assign(new Vue(), { // Use a vue instance as an event b
|
|||||||
getObjectToScroll() {
|
getObjectToScroll() {
|
||||||
let elt = this.editorElt.parentNode;
|
let elt = this.editorElt.parentNode;
|
||||||
let dimensionKey = 'editorDimension';
|
let dimensionKey = 'editorDimension';
|
||||||
if (!store.state.layout.showEditor) {
|
if (!store.getters['layout/styles'].showEditor) {
|
||||||
elt = this.previewElt.parentNode;
|
elt = this.previewElt.parentNode;
|
||||||
dimensionKey = 'previewDimension';
|
dimensionKey = 'previewDimension';
|
||||||
}
|
}
|
||||||
@ -426,10 +425,10 @@ const editorSvc = Object.assign(new Vue(), { // Use a vue instance as an event b
|
|||||||
let scrollerElt = this.previewElt.parentNode;
|
let scrollerElt = this.previewElt.parentNode;
|
||||||
const sectionDesc = anchorHash[anchor];
|
const sectionDesc = anchorHash[anchor];
|
||||||
if (sectionDesc) {
|
if (sectionDesc) {
|
||||||
if (store.state.layout.showSidePreview || !store.state.layout.showEditor) {
|
if (store.getters['layout/styles'].showPreview) {
|
||||||
scrollTop = sectionDesc.previewDimension.startOffset;
|
scrollTop = sectionDesc.previewDimension.startOffset;
|
||||||
} else {
|
} else {
|
||||||
scrollTop = sectionDesc.editorDimension.startOffset - constants.scrollOffset;
|
scrollTop = sectionDesc.editorDimension.startOffset;
|
||||||
scrollerElt = this.editorElt.parentNode;
|
scrollerElt = this.editorElt.parentNode;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -575,7 +574,7 @@ const editorSvc = Object.assign(new Vue(), { // Use a vue instance as an event b
|
|||||||
|
|
||||||
let newSectionList;
|
let newSectionList;
|
||||||
let newSelectionRange;
|
let newSelectionRange;
|
||||||
const debouncedEditorChanged = debounce(() => {
|
const onEditorChanged = () => {
|
||||||
if (this.sectionList !== newSectionList) {
|
if (this.sectionList !== newSectionList) {
|
||||||
this.sectionList = newSectionList;
|
this.sectionList = newSectionList;
|
||||||
this.$emit('sectionList', this.sectionList);
|
this.$emit('sectionList', this.sectionList);
|
||||||
@ -590,7 +589,8 @@ const editorSvc = Object.assign(new Vue(), { // Use a vue instance as an event b
|
|||||||
this.$emit('selectionRange', this.selectionRange);
|
this.$emit('selectionRange', this.selectionRange);
|
||||||
}
|
}
|
||||||
this.saveContentState();
|
this.saveContentState();
|
||||||
}, 10);
|
};
|
||||||
|
const debouncedEditorChanged = debounce(onEditorChanged, 10);
|
||||||
|
|
||||||
editorEngineSvc.clEditor.selectionMgr.on('selectionChanged', (start, end, selectionRange) => {
|
editorEngineSvc.clEditor.selectionMgr.on('selectionChanged', (start, end, selectionRange) => {
|
||||||
newSelectionRange = selectionRange;
|
newSelectionRange = selectionRange;
|
||||||
@ -685,9 +685,15 @@ const editorSvc = Object.assign(new Vue(), { // Use a vue instance as an event b
|
|||||||
|
|
||||||
editorEngineSvc.clEditor.on('contentChanged', (content, diffs, sectionList) => {
|
editorEngineSvc.clEditor.on('contentChanged', (content, diffs, sectionList) => {
|
||||||
newSectionList = sectionList;
|
newSectionList = sectionList;
|
||||||
debouncedEditorChanged();
|
if (instantPreview) {
|
||||||
|
onEditorChanged();
|
||||||
|
} else {
|
||||||
|
debouncedEditorChanged();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.$emit('inited');
|
||||||
|
|
||||||
// scope.$watch('editorLayoutSvc.isEditorOpen', function (isOpen) {
|
// scope.$watch('editorLayoutSvc.isEditorOpen', function (isOpen) {
|
||||||
// clEditorSvc.cledit.toggleEditable(isOpen)
|
// clEditorSvc.cledit.toggleEditable(isOpen)
|
||||||
// })
|
// })
|
||||||
@ -736,12 +742,10 @@ const editorSvc = Object.assign(new Vue(), { // Use a vue instance as an event b
|
|||||||
immediate: true,
|
immediate: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
store.watch(state => `${state.layout.editorWidth},${state.layout.previewWidth}`,
|
store.watch(() => `${store.getters['layout/styles'].editorWidth},${store.getters['layout/styles'].previewWidth}`,
|
||||||
() => editorSvc.measureSectionDimensions(true));
|
() => editorSvc.measureSectionDimensions(true));
|
||||||
store.watch(state => state.layout.showSidePreview,
|
store.watch(() => store.getters['layout/styles'].showSidePreview,
|
||||||
showSidePreview => showSidePreview && editorSvc.measureSectionDimensions());
|
showSidePreview => showSidePreview && editorSvc.measureSectionDimensions());
|
||||||
|
|
||||||
this.$emit('inited');
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -118,6 +118,7 @@ export default {
|
|||||||
[
|
[
|
||||||
store.state.contents,
|
store.state.contents,
|
||||||
store.state.files,
|
store.state.files,
|
||||||
|
store.state.folders,
|
||||||
].forEach(moduleState => Object.assign(storeItemMap, moduleState.itemMap));
|
].forEach(moduleState => Object.assign(storeItemMap, moduleState.itemMap));
|
||||||
this.connection.createTx((tx) => {
|
this.connection.createTx((tx) => {
|
||||||
this.readAll(storeItemMap, tx, () => {
|
this.readAll(storeItemMap, tx, () => {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import store from '../../store';
|
import store from '../../store';
|
||||||
import constants from '../constants';
|
|
||||||
import animationSvc from '../animationSvc';
|
import animationSvc from '../animationSvc';
|
||||||
import editorSvc from '../editorSvc';
|
import editorSvc from '../editorSvc';
|
||||||
|
|
||||||
@ -34,9 +33,7 @@ function throttle(func, wait) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const doScrollSync = () => {
|
const doScrollSync = () => {
|
||||||
const localSkipAnimation = skipAnimation
|
const localSkipAnimation = skipAnimation || !store.getters['layout/styles'].showSidePreview;
|
||||||
|| !store.state.layout.showSidePreview
|
|
||||||
|| !store.state.layout.showEditor;
|
|
||||||
skipAnimation = false;
|
skipAnimation = false;
|
||||||
if (!store.state.editor.scrollSync || !sectionDescList || sectionDescList.length === 0) {
|
if (!store.state.editor.scrollSync || !sectionDescList || sectionDescList.length === 0) {
|
||||||
return;
|
return;
|
||||||
@ -45,12 +42,11 @@ const doScrollSync = () => {
|
|||||||
if (editorScrollTop < 0) {
|
if (editorScrollTop < 0) {
|
||||||
editorScrollTop = 0;
|
editorScrollTop = 0;
|
||||||
}
|
}
|
||||||
let previewScrollTop = previewScrollerElt.scrollTop;
|
const previewScrollTop = previewScrollerElt.scrollTop;
|
||||||
let scrollTo;
|
let scrollTo;
|
||||||
if (isScrollEditor) {
|
if (isScrollEditor) {
|
||||||
// Scroll the preview
|
// Scroll the preview
|
||||||
isScrollEditor = false;
|
isScrollEditor = false;
|
||||||
editorScrollTop += constants.scrollOffset;
|
|
||||||
sectionDescList.some((sectionDesc) => {
|
sectionDescList.some((sectionDesc) => {
|
||||||
if (editorScrollTop > sectionDesc.editorDimension.endOffset) {
|
if (editorScrollTop > sectionDesc.editorDimension.endOffset) {
|
||||||
return false;
|
return false;
|
||||||
@ -58,7 +54,7 @@ const doScrollSync = () => {
|
|||||||
const posInSection = (editorScrollTop - sectionDesc.editorDimension.startOffset)
|
const posInSection = (editorScrollTop - sectionDesc.editorDimension.startOffset)
|
||||||
/ (sectionDesc.editorDimension.height || 1);
|
/ (sectionDesc.editorDimension.height || 1);
|
||||||
scrollTo = (sectionDesc.previewDimension.startOffset
|
scrollTo = (sectionDesc.previewDimension.startOffset
|
||||||
+ (sectionDesc.previewDimension.height * posInSection)) - constants.scrollOffset;
|
+ (sectionDesc.previewDimension.height * posInSection));
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
scrollTo = Math.min(
|
scrollTo = Math.min(
|
||||||
@ -79,10 +75,9 @@ const doScrollSync = () => {
|
|||||||
isPreviewMoving = true;
|
isPreviewMoving = true;
|
||||||
});
|
});
|
||||||
}, localSkipAnimation ? 500 : 50);
|
}, localSkipAnimation ? 500 : 50);
|
||||||
} else if (!store.state.layout.showEditor || isScrollPreview) {
|
} else if (!store.getters['layout/styles'].showEditor || isScrollPreview) {
|
||||||
// Scroll the editor
|
// Scroll the editor
|
||||||
isScrollPreview = false;
|
isScrollPreview = false;
|
||||||
previewScrollTop += constants.scrollOffset;
|
|
||||||
sectionDescList.some((sectionDesc) => {
|
sectionDescList.some((sectionDesc) => {
|
||||||
if (previewScrollTop > sectionDesc.previewDimension.endOffset) {
|
if (previewScrollTop > sectionDesc.previewDimension.endOffset) {
|
||||||
return false;
|
return false;
|
||||||
@ -90,7 +85,7 @@ const doScrollSync = () => {
|
|||||||
const posInSection = (previewScrollTop - sectionDesc.previewDimension.startOffset)
|
const posInSection = (previewScrollTop - sectionDesc.previewDimension.startOffset)
|
||||||
/ (sectionDesc.previewDimension.height || 1);
|
/ (sectionDesc.previewDimension.height || 1);
|
||||||
scrollTo = (sectionDesc.editorDimension.startOffset
|
scrollTo = (sectionDesc.editorDimension.startOffset
|
||||||
+ (sectionDesc.editorDimension.height * posInSection)) - constants.scrollOffset;
|
+ (sectionDesc.editorDimension.height * posInSection));
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
scrollTo = Math.min(
|
scrollTo = Math.min(
|
||||||
@ -119,7 +114,7 @@ let timeoutId;
|
|||||||
|
|
||||||
const forceScrollSync = () => {
|
const forceScrollSync = () => {
|
||||||
if (!isPreviewRefreshing) {
|
if (!isPreviewRefreshing) {
|
||||||
doScrollSync(!store.state.layout.showSidePreview);
|
doScrollSync();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
store.watch(state => state.editor.scrollSync, forceScrollSync);
|
store.watch(state => state.editor.scrollSync, forceScrollSync);
|
||||||
@ -135,7 +130,7 @@ editorSvc.$on('inited', () => {
|
|||||||
}
|
}
|
||||||
isScrollEditor = true;
|
isScrollEditor = true;
|
||||||
isScrollPreview = false;
|
isScrollPreview = false;
|
||||||
doScrollSync(!store.state.layout.showSidePreview);
|
doScrollSync();
|
||||||
});
|
});
|
||||||
|
|
||||||
previewScrollerElt.addEventListener('scroll', () => {
|
previewScrollerElt.addEventListener('scroll', () => {
|
||||||
@ -144,7 +139,7 @@ editorSvc.$on('inited', () => {
|
|||||||
}
|
}
|
||||||
isScrollPreview = true;
|
isScrollPreview = true;
|
||||||
isScrollEditor = false;
|
isScrollEditor = false;
|
||||||
doScrollSync(!store.state.layout.showSidePreview);
|
doScrollSync();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -163,14 +158,15 @@ editorSvc.$on('previewText', () => {
|
|||||||
// Remove height property once the preview as been refreshed
|
// Remove height property once the preview as been refreshed
|
||||||
previewElt.style.removeProperty('height');
|
previewElt.style.removeProperty('height');
|
||||||
// Assume the user is writing in the editor
|
// Assume the user is writing in the editor
|
||||||
isScrollEditor = store.state.layout.showEditor;
|
isScrollEditor = store.getters['layout/styles'].showEditor;
|
||||||
// A preview scrolling event can occur if height is smaller
|
// A preview scrolling event can occur if height is smaller
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
isPreviewRefreshing = false;
|
isPreviewRefreshing = false;
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
store.watch(state => state.layout.showSidePreview,
|
store.watch(
|
||||||
|
() => store.getters['layout/styles'].showSidePreview,
|
||||||
(showSidePreview) => {
|
(showSidePreview) => {
|
||||||
if (showSidePreview) {
|
if (showSidePreview) {
|
||||||
isScrollEditor = true;
|
isScrollEditor = true;
|
||||||
@ -179,6 +175,12 @@ store.watch(state => state.layout.showSidePreview,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
store.watch(
|
||||||
|
() => store.getters['files/current'].id,
|
||||||
|
() => {
|
||||||
|
skipAnimation = true;
|
||||||
|
});
|
||||||
|
|
||||||
editorSvc.$on('sectionDescMeasuredList', (sectionDescMeasuredList) => {
|
editorSvc.$on('sectionDescMeasuredList', (sectionDescMeasuredList) => {
|
||||||
sectionDescList = sectionDescMeasuredList;
|
sectionDescList = sectionDescMeasuredList;
|
||||||
forceScrollSync();
|
forceScrollSync();
|
||||||
|
@ -10,39 +10,35 @@ const ifNoId = cb => (obj) => {
|
|||||||
return cb();
|
return cb();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Watch file changing
|
// Load the DB on boot
|
||||||
store.watch(
|
localDbSvc.sync()
|
||||||
() => store.getters['files/current'].id,
|
// Watch file changing
|
||||||
() => Promise.resolve(store.getters['files/current'])
|
.then(() => store.watch(
|
||||||
// If current file has no ID, get the most recent file
|
() => store.getters['files/current'].id,
|
||||||
.then(ifNoId(() => store.getters['files/mostRecent']))
|
() => Promise.resolve(store.getters['files/current'])
|
||||||
// If no ID, load the DB (we're booting)
|
// If current file has no ID, get the most recent file
|
||||||
.then(ifNoId(() => localDbSvc.sync()
|
.then(ifNoId(() => store.getters['files/mostRecent']))
|
||||||
// Retry
|
// If still no ID, create a new file
|
||||||
.then(() => store.getters['files/current'])
|
.then(ifNoId(() => {
|
||||||
.then(ifNoId(() => store.getters['files/mostRecent'])),
|
const contentId = utils.uid();
|
||||||
))
|
store.commit('contents/setItem', {
|
||||||
// Finally create a new file
|
id: contentId,
|
||||||
.then(ifNoId(() => {
|
text: welcomeFile,
|
||||||
const contentId = utils.uid();
|
});
|
||||||
store.commit('contents/setItem', {
|
const fileId = utils.uid();
|
||||||
id: contentId,
|
store.commit('files/setItem', {
|
||||||
text: welcomeFile,
|
id: fileId,
|
||||||
});
|
name: 'Welcome file',
|
||||||
const fileId = utils.uid();
|
contentId,
|
||||||
store.commit('files/setItem', {
|
});
|
||||||
id: fileId,
|
return store.state.files.itemMap[fileId];
|
||||||
name: 'Welcome file',
|
}))
|
||||||
contentId,
|
.then((currentFile) => {
|
||||||
});
|
store.commit('files/setCurrentId', currentFile.id);
|
||||||
return store.state.files.itemMap[fileId];
|
store.dispatch('files/patchCurrent', {}); // Update `updated` field to make it the mostRecent
|
||||||
}))
|
}),
|
||||||
.then((currentFile) => {
|
{
|
||||||
store.commit('files/setCurrentId', currentFile.id);
|
immediate: true,
|
||||||
store.dispatch('files/patchCurrent', {}); // Update `updated` field to make it the mostRecent
|
}));
|
||||||
}),
|
|
||||||
{
|
|
||||||
immediate: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
utils.setInterval(() => localDbSvc.sync(), 1200);
|
utils.setInterval(() => localDbSvc.sync(), 1200);
|
||||||
|
@ -3,8 +3,10 @@ import Vue from 'vue';
|
|||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
import contents from './modules/contents';
|
import contents from './modules/contents';
|
||||||
import files from './modules/files';
|
import files from './modules/files';
|
||||||
|
import folders from './modules/folders';
|
||||||
import layout from './modules/layout';
|
import layout from './modules/layout';
|
||||||
import editor from './modules/editor';
|
import editor from './modules/editor';
|
||||||
|
import explorer from './modules/explorer';
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
|
||||||
@ -14,8 +16,10 @@ const store = new Vuex.Store({
|
|||||||
modules: {
|
modules: {
|
||||||
contents,
|
contents,
|
||||||
files,
|
files,
|
||||||
|
folders,
|
||||||
layout,
|
layout,
|
||||||
editor,
|
editor,
|
||||||
|
explorer,
|
||||||
},
|
},
|
||||||
strict: debug,
|
strict: debug,
|
||||||
plugins: debug ? [createLogger()] : [],
|
plugins: debug ? [createLogger()] : [],
|
||||||
|
114
src/store/modules/explorer.js
Normal file
114
src/store/modules/explorer.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import Vue from 'vue';
|
||||||
|
import emptyFile from '../../data/emptyFile';
|
||||||
|
import emptyFolder from '../../data/emptyFolder';
|
||||||
|
|
||||||
|
const setter = propertyName => (state, value) => {
|
||||||
|
state[propertyName] = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const collator = new Intl.Collator(undefined, { sensitivity: 'base' });
|
||||||
|
const compare = (node1, node2) => collator.compare(node1.item.name, node2.item.name);
|
||||||
|
|
||||||
|
class Node {
|
||||||
|
constructor(item, isFolder, isRoot) {
|
||||||
|
this.item = item;
|
||||||
|
this.isFolder = isFolder;
|
||||||
|
this.isRoot = isRoot;
|
||||||
|
if (isFolder) {
|
||||||
|
this.folders = [];
|
||||||
|
this.files = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sortChildren() {
|
||||||
|
if (this.isFolder) {
|
||||||
|
this.folders.sort(compare);
|
||||||
|
this.files.sort(compare);
|
||||||
|
this.folders.forEach(child => child.sortChildren());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nilFileNode = new Node(emptyFile());
|
||||||
|
nilFileNode.isNil = true;
|
||||||
|
|
||||||
|
function getParent(node, getters) {
|
||||||
|
if (node === nilFileNode) {
|
||||||
|
return nilFileNode;
|
||||||
|
}
|
||||||
|
return getters.nodeMap[node.item.parentId] || getters.rootNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state: {
|
||||||
|
selectedId: null,
|
||||||
|
editingId: null,
|
||||||
|
newChildNode: nilFileNode,
|
||||||
|
openNodes: {},
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
nodeStructure: (state, getters, rootState, rootGetters) => {
|
||||||
|
const nodeMap = {};
|
||||||
|
rootGetters['folders/items'].forEach((item) => {
|
||||||
|
nodeMap[item.id] = new Node(item, true);
|
||||||
|
});
|
||||||
|
rootGetters['files/items'].forEach((item) => {
|
||||||
|
nodeMap[item.id] = new Node(item);
|
||||||
|
});
|
||||||
|
const rootNode = new Node(emptyFolder(), true, true);
|
||||||
|
Object.keys(nodeMap).forEach((id) => {
|
||||||
|
const node = nodeMap[id];
|
||||||
|
let parentNode = nodeMap[node.item.parentId];
|
||||||
|
if (!parentNode || !parentNode.isFolder) {
|
||||||
|
parentNode = rootNode;
|
||||||
|
}
|
||||||
|
if (node.isFolder) {
|
||||||
|
parentNode.folders.push(node);
|
||||||
|
} else {
|
||||||
|
parentNode.files.push(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rootNode.sortChildren();
|
||||||
|
return {
|
||||||
|
nodeMap,
|
||||||
|
rootNode,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
nodeMap: (state, getters) => getters.nodeStructure.nodeMap,
|
||||||
|
rootNode: (state, getters) => getters.nodeStructure.rootNode,
|
||||||
|
newChildNodeParent: (state, getters) => getParent(state.newChildNode, getters),
|
||||||
|
selectedNode: (state, getters) => getters.nodeMap[state.selectedId] || nilFileNode,
|
||||||
|
selectedNodeFolder: (state, getters) => {
|
||||||
|
const selectedNode = getters.selectedNode;
|
||||||
|
return selectedNode.item.type === 'folder'
|
||||||
|
? selectedNode
|
||||||
|
: getParent(selectedNode, getters);
|
||||||
|
},
|
||||||
|
editingNode: (state, getters) => getters.nodeMap[state.editingId] || nilFileNode,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setSelectedId: setter('selectedId'),
|
||||||
|
setEditingId: setter('editingId'),
|
||||||
|
setNewItem(state, item) {
|
||||||
|
state.newChildNode = item ? new Node(item) : nilFileNode;
|
||||||
|
},
|
||||||
|
setNewItemName(state, name) {
|
||||||
|
state.newChildNode.item.name = name;
|
||||||
|
},
|
||||||
|
toggleOpenNode(state, id) {
|
||||||
|
Vue.set(state.openNodes, id, !state.openNodes[id]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
openNode({ state, getters, commit, dispatch }, id) {
|
||||||
|
const node = getters.nodeMap[id];
|
||||||
|
if (node) {
|
||||||
|
if (node.isFolder && !state.openNodes[id]) {
|
||||||
|
commit('toggleOpenNode', id);
|
||||||
|
}
|
||||||
|
dispatch('openNode', node.item.parentId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
6
src/store/modules/folders.js
Normal file
6
src/store/modules/folders.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import moduleTemplate from './moduleTemplate';
|
||||||
|
import empty from '../../data/emptyFolder';
|
||||||
|
|
||||||
|
const module = moduleTemplate(empty);
|
||||||
|
|
||||||
|
export default module;
|
@ -9,148 +9,135 @@ const setter = propertyName => (state, value) => {
|
|||||||
state[propertyName] = value;
|
state[propertyName] = value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggler = (propertyName, setterName) => ({ state, commit, dispatch }, show) => {
|
const toggler = propertyName => (state, value) => {
|
||||||
commit(setterName, show === undefined ? !state[propertyName] : show);
|
state[propertyName] = value === undefined ? !state[propertyName] : value;
|
||||||
dispatch('updateStyle');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {
|
state: {
|
||||||
// Constants
|
constants: {
|
||||||
explorerWidth: 280,
|
explorerWidth: 250,
|
||||||
sideBarWidth: 280,
|
sideBarWidth: 280,
|
||||||
navigationBarHeight: 44,
|
navigationBarHeight: 44,
|
||||||
buttonBarWidth: 30,
|
buttonBarWidth: 30,
|
||||||
statusBarHeight: 20,
|
statusBarHeight: 20,
|
||||||
|
},
|
||||||
// Configuration
|
// Configuration
|
||||||
showNavigationBar: true,
|
showNavigationBar: true,
|
||||||
showEditor: true,
|
showEditor: true,
|
||||||
showSidePreview: true,
|
showSidePreview: true,
|
||||||
showStatusBar: true,
|
showStatusBar: true,
|
||||||
showSideBar: false,
|
showSideBar: false,
|
||||||
showExplorer: false,
|
showExplorer: true,
|
||||||
editorWidthFactor: 1,
|
editorWidthFactor: 1,
|
||||||
fontSizeFactor: 1,
|
fontSizeFactor: 1,
|
||||||
// Style
|
// Styles
|
||||||
fontSize: 0,
|
bodyWidth: 0,
|
||||||
innerWidth: 0,
|
bodyHeight: 0,
|
||||||
innerHeight: 0,
|
|
||||||
editorWidth: 0,
|
|
||||||
editorPadding: 0,
|
|
||||||
previewWidth: 0,
|
|
||||||
previewPadding: 0,
|
|
||||||
titleMaxWidth: 0,
|
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
setShowNavigationBar: setter('showNavigationBar'),
|
toggleNavigationBar: toggler('showNavigationBar'),
|
||||||
setShowEditor: setter('showEditor'),
|
toggleEditor: toggler('showEditor'),
|
||||||
setShowSidePreview: setter('showSidePreview'),
|
toggleSidePreview: toggler('showSidePreview'),
|
||||||
setShowStatusBar: setter('showStatusBar'),
|
toggleStatusBar: toggler('showStatusBar'),
|
||||||
setShowSideBar: setter('showSideBar'),
|
toggleSideBar: toggler('showSideBar'),
|
||||||
setShowExplorer: setter('showExplorer'),
|
toggleExplorer: toggler('showExplorer'),
|
||||||
setEditorWidthFactor: setter('editorWidthFactor'),
|
setEditorWidthFactor: setter('editorWidthFactor'),
|
||||||
setFontSizeFactor: setter('fontSizeFactor'),
|
setFontSizeFactor: setter('fontSizeFactor'),
|
||||||
setFontSize: setter('fontSize'),
|
updateBodySize: (state) => {
|
||||||
setInnerWidth: setter('innerWidth'),
|
state.bodyWidth = document.body.clientWidth;
|
||||||
setInnerHeight: setter('innerHeight'),
|
state.bodyHeight = document.body.clientHeight;
|
||||||
setEditorWidth: setter('editorWidth'),
|
},
|
||||||
setEditorPadding: setter('editorPadding'),
|
|
||||||
setPreviewWidth: setter('previewWidth'),
|
|
||||||
setPreviewPadding: setter('previewPadding'),
|
|
||||||
setTitleMaxWidth: setter('titleMaxWidth'),
|
|
||||||
},
|
},
|
||||||
actions: {
|
getters: {
|
||||||
toggleNavigationBar: toggler('showNavigationBar', 'setShowNavigationBar'),
|
styles: (state) => {
|
||||||
toggleEditor: toggler('showEditor', 'setShowEditor'),
|
const styles = {
|
||||||
toggleSidePreview: toggler('showSidePreview', 'setShowSidePreview'),
|
showNavigationBar: !state.showEditor || state.showNavigationBar,
|
||||||
toggleStatusBar: toggler('showStatusBar', 'setShowStatusBar'),
|
showStatusBar: state.showStatusBar,
|
||||||
toggleSideBar: toggler('showSideBar', 'setShowSideBar'),
|
showEditor: state.showEditor,
|
||||||
toggleExplorer: toggler('showExplorer', 'setShowExplorer'),
|
showSidePreview: state.showSidePreview && state.showEditor,
|
||||||
updateStyle({ state, commit, dispatch }) {
|
showPreview: state.showSidePreview || !state.showEditor,
|
||||||
const bodyWidth = document.body.clientWidth;
|
showSideBar: state.showSideBar,
|
||||||
const bodyHeight = document.body.clientHeight;
|
showExplorer: state.showExplorer,
|
||||||
|
};
|
||||||
|
|
||||||
const showNavigationBar = !state.showEditor || state.showNavigationBar;
|
function computeStyles() {
|
||||||
let innerHeight = bodyHeight;
|
styles.innerHeight = state.bodyHeight;
|
||||||
if (showNavigationBar) {
|
if (styles.showNavigationBar) {
|
||||||
innerHeight -= state.navigationBarHeight;
|
styles.innerHeight -= state.constants.navigationBarHeight;
|
||||||
}
|
}
|
||||||
if (state.showStatusBar) {
|
if (styles.showStatusBar) {
|
||||||
innerHeight -= state.statusBarHeight;
|
styles.innerHeight -= state.constants.statusBarHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
let innerWidth = bodyWidth;
|
styles.innerWidth = state.bodyWidth;
|
||||||
if (state.showSideBar) {
|
if (styles.showSideBar) {
|
||||||
innerWidth -= state.sideBarWidth;
|
styles.innerWidth -= state.constants.sideBarWidth;
|
||||||
}
|
}
|
||||||
if (state.showExplorer) {
|
if (styles.showExplorer) {
|
||||||
innerWidth -= state.explorerWidth;
|
styles.innerWidth -= state.constants.explorerWidth;
|
||||||
}
|
}
|
||||||
let doublePanelWidth = innerWidth - state.buttonBarWidth;
|
|
||||||
if (doublePanelWidth < editorMinWidth) {
|
let doublePanelWidth = styles.innerWidth - state.constants.buttonBarWidth;
|
||||||
if (state.showSideBar) {
|
if (doublePanelWidth < editorMinWidth) {
|
||||||
dispatch('toggleSideBar', false);
|
if (styles.showSideBar) {
|
||||||
|
styles.showSideBar = false;
|
||||||
|
computeStyles();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (styles.showExplorer) {
|
||||||
|
styles.showExplorer = false;
|
||||||
|
computeStyles();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
doublePanelWidth = editorMinWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (styles.showSidePreview && doublePanelWidth / 2 < editorMinWidth) {
|
||||||
|
styles.showSidePreview = false;
|
||||||
|
computeStyles();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (state.showExplorer) {
|
|
||||||
dispatch('toggleExplorer', false);
|
styles.fontSize = 18;
|
||||||
return;
|
styles.textWidth = 990;
|
||||||
|
if (doublePanelWidth < 1120) {
|
||||||
|
styles.fontSize -= 1;
|
||||||
|
styles.textWidth = 910;
|
||||||
}
|
}
|
||||||
doublePanelWidth = editorMinWidth;
|
if (doublePanelWidth < 1040) {
|
||||||
}
|
styles.textWidth = 830;
|
||||||
const splitPanel = state.showEditor && state.showSidePreview;
|
}
|
||||||
if (splitPanel && doublePanelWidth / 2 < editorMinWidth) {
|
styles.textWidth *= state.editorWidthFactor;
|
||||||
dispatch('toggleSidePreview', false);
|
if (doublePanelWidth < styles.textWidth) {
|
||||||
return;
|
styles.textWidth = doublePanelWidth;
|
||||||
|
}
|
||||||
|
if (styles.textWidth < 640) {
|
||||||
|
styles.fontSize -= 1;
|
||||||
|
}
|
||||||
|
styles.fontSize *= state.fontSizeFactor;
|
||||||
|
|
||||||
|
const panelWidth = doublePanelWidth / 2;
|
||||||
|
styles.previewWidth = styles.showSidePreview ?
|
||||||
|
panelWidth :
|
||||||
|
styles.innerWidth;
|
||||||
|
styles.previewPadding = Math.max((styles.previewWidth - styles.textWidth) / 2, minPadding);
|
||||||
|
styles.editorWidth = styles.showSidePreview ?
|
||||||
|
panelWidth :
|
||||||
|
doublePanelWidth;
|
||||||
|
styles.editorPadding = Math.max((styles.editorWidth - styles.textWidth) / 2, minPadding);
|
||||||
|
|
||||||
|
styles.titleMaxWidth = styles.innerWidth - navigationBarSpaceWidth;
|
||||||
|
if (styles.showEditor) {
|
||||||
|
styles.titleMaxWidth -= navigationBarLeftWidth;
|
||||||
|
}
|
||||||
|
styles.titleMaxWidth = Math.min(styles.titleMaxWidth, maxTitleMaxWidth);
|
||||||
|
styles.titleMaxWidth = Math.max(styles.titleMaxWidth, minTitleMaxWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
let fontSize = 18;
|
computeStyles();
|
||||||
let textWidth = 990;
|
return styles;
|
||||||
if (doublePanelWidth < 1120) {
|
|
||||||
fontSize -= 1;
|
|
||||||
textWidth = 910;
|
|
||||||
}
|
|
||||||
if (doublePanelWidth < 1040) {
|
|
||||||
textWidth = 830;
|
|
||||||
}
|
|
||||||
if (textWidth < 640) {
|
|
||||||
fontSize -= 1;
|
|
||||||
}
|
|
||||||
textWidth *= state.editorWidthFactor;
|
|
||||||
fontSize *= state.fontSizeFactor;
|
|
||||||
|
|
||||||
const panelWidth = doublePanelWidth / 2;
|
|
||||||
const previewWidth = splitPanel ?
|
|
||||||
panelWidth :
|
|
||||||
innerWidth;
|
|
||||||
let previewPadding = (previewWidth - textWidth) / 2;
|
|
||||||
if (previewPadding < minPadding) {
|
|
||||||
previewPadding = minPadding;
|
|
||||||
}
|
|
||||||
const editorWidth = splitPanel ?
|
|
||||||
panelWidth :
|
|
||||||
doublePanelWidth;
|
|
||||||
let editorPadding = (editorWidth - textWidth) / 2;
|
|
||||||
if (editorPadding < minPadding) {
|
|
||||||
editorPadding = minPadding;
|
|
||||||
}
|
|
||||||
|
|
||||||
let titleMaxWidth = innerWidth - navigationBarSpaceWidth;
|
|
||||||
if (state.showEditor) {
|
|
||||||
titleMaxWidth -= navigationBarLeftWidth;
|
|
||||||
}
|
|
||||||
titleMaxWidth = Math.min(titleMaxWidth, maxTitleMaxWidth);
|
|
||||||
titleMaxWidth = Math.max(titleMaxWidth, minTitleMaxWidth);
|
|
||||||
|
|
||||||
commit('setFontSize', fontSize);
|
|
||||||
commit('setInnerWidth', innerWidth);
|
|
||||||
commit('setInnerHeight', innerHeight);
|
|
||||||
commit('setPreviewWidth', previewWidth);
|
|
||||||
commit('setPreviewPadding', previewPadding);
|
|
||||||
commit('setEditorWidth', editorWidth);
|
|
||||||
commit('setEditorPadding', editorPadding);
|
|
||||||
commit('setTitleMaxWidth', titleMaxWidth);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user