Added explorer

This commit is contained in:
benweet 2017-07-31 10:04:01 +01:00
parent c07fc7135e
commit 0c27a8337a
37 changed files with 719 additions and 290 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.vscode

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -20,17 +20,15 @@
</template>
<script>
import { mapState, mapActions } from 'vuex';
import { mapState, mapMutations } from 'vuex';
export default {
computed: mapState('layout', [
'showNavigationBar',
'showEditor',
'showSidePreview',
'showSideBar',
'showStatusBar',
]),
methods: mapActions('layout', [
methods: mapMutations('layout', [
'toggleNavigationBar',
'toggleEditor',
'toggleSidePreview',
@ -61,7 +59,6 @@ export default {
width: 26px;
height: 26px;
padding: 2px;
border-radius: 3px;
margin: 3px 0;
&:hover {

View File

@ -1,16 +1,16 @@
<template>
<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>
</template>
<script>
import { mapState } from 'vuex';
import { mapGetters } from 'vuex';
export default {
computed: mapState('layout', [
'editorPadding',
computed: mapGetters('layout', [
'styles',
]),
};
</script>

View File

@ -1,18 +1,122 @@
<template>
<div class="explorer">
<div class="explorer flex flex--column">
<div class="side-title">
<div class="side-title__text">
Explorer
</div>
<button class="side-title__button side-title__button--right button" @click="toggleExplorer(false)">
<icon-close></icon-close>
</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>
</template>
<script>
import { mapState, mapGetters, mapMutations } from 'vuex';
import ExplorerNode from './ExplorerNode';
import emptyFile from '../data/emptyFile';
import emptyFolder from '../data/emptyFolder';
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>
<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>

View File

@ -1,13 +0,0 @@
<template>
<div class="explorer-item">
</div>
</template>
<script>
export default {
};
</script>
<style lang="scss">
@import 'common/variables.scss';
</style>

View 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>

View File

@ -1,29 +1,29 @@
<template>
<div class="layout">
<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>
</div>
<div class="layout__panel flex flex--column" :style="{ width: innerWidth + 'px' }">
<div class="layout__panel layout__panel--navigation-bar" v-show="showNavigationBar || !showEditor" :style="{ height: navigationBarHeight + 'px' }">
<div class="layout__panel flex flex--column" :style="{ width: styles.innerWidth + 'px' }">
<div class="layout__panel layout__panel--navigation-bar" v-show="styles.showNavigationBar" :style="{ height: constants.navigationBarHeight + 'px' }">
<navigation-bar></navigation-bar>
</div>
<div class="layout__panel flex flex--row" :style="{ height: innerHeight + 'px' }">
<div class="layout__panel layout__panel--editor" v-show="showEditor" :style="{ width: editorWidth + 'px', 'font-size': fontSize + 'px' }">
<div class="layout__panel flex flex--row" :style="{ height: styles.innerHeight + 'px' }">
<div class="layout__panel layout__panel--editor" v-show="styles.showEditor" :style="{ width: styles.editorWidth + 'px', 'font-size': styles.fontSize + 'px' }">
<editor></editor>
</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>
</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>
</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>
</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>
</div>
</div>
@ -31,7 +31,7 @@
</template>
<script>
import { mapState, mapActions } from 'vuex';
import { mapState, mapGetters, mapMutations } from 'vuex';
import NavigationBar from './NavigationBar';
import ButtonBar from './ButtonBar';
import StatusBar from './StatusBar';
@ -40,7 +40,6 @@ import SideBar from './SideBar';
import Editor from './Editor';
import Preview from './Preview';
import editorSvc from '../services/editorSvc';
import constants from '../services/constants';
export default {
components: {
@ -52,33 +51,23 @@ export default {
Editor,
Preview,
},
computed: mapState('layout', {
explorerWidth: 'explorerWidth',
sideBarWidth: 'sideBarWidth',
navigationBarHeight: 'navigationBarHeight',
buttonBarWidth: 'buttonBarWidth',
statusBarHeight: 'statusBarHeight',
showEditor: 'showEditor',
showSidePreview: 'showSidePreview',
showNavigationBar: 'showNavigationBar',
showStatusBar: 'showStatusBar',
showSideBar: 'showSideBar',
showExplorer: 'showExplorer',
fontSize: 'fontSize',
innerWidth: 'innerWidth',
innerHeight: 'innerHeight',
previewWidth: 'previewWidth',
editorWidth: 'editorWidth',
}),
computed: {
...mapState('layout', [
'constants',
]),
...mapGetters('layout', [
'styles',
]),
},
methods: {
...mapActions('layout', [
'updateStyle',
...mapMutations('layout', [
'updateBodySize',
]),
saveSelection: () => editorSvc.saveSelection(true),
},
created() {
this.updateStyle();
window.addEventListener('resize', this.updateStyle);
this.updateBodySize();
window.addEventListener('resize', this.updateBodySize);
window.addEventListener('keyup', this.saveSelection);
window.addEventListener('mouseup', this.saveSelection);
window.addEventListener('contextmenu', this.saveSelection);
@ -106,10 +95,10 @@ export default {
/ (sectionDesc.tocDimension.height || 1);
const editorScrollTop = sectionDesc.editorDimension.startOffset
+ (sectionDesc.editorDimension.height * posInSection);
editorElt.parentNode.scrollTop = editorScrollTop - constants.scrollOffset;
editorElt.parentNode.scrollTop = editorScrollTop;
const previewScrollTop = sectionDesc.previewDimension.startOffset
+ (sectionDesc.previewDimension.height * posInSection);
previewElt.parentNode.scrollTop = previewScrollTop - constants.scrollOffset;
previewElt.parentNode.scrollTop = previewScrollTop;
return true;
});
}
@ -162,7 +151,7 @@ export default {
}
.layout__panel--explorer {
background-color: #ddd;
background-color: #dadada;
}
.layout__panel--button-bar,

View File

@ -1,13 +1,13 @@
<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">
<button class="navigation-bar__button button" @click="toggleExplorer()">
<icon-menu></icon-menu>
<icon-folder-multiple></icon-folder-multiple>
</button>
</div>
<div class="navigation-bar__inner navigation-bar__inner--right navigation-bar__inner--button">
<button class="navigation-bar__button button" @click="toggleExplorer()">
<icon-settings></icon-settings>
<icon-menu></icon-menu>
</button>
</div>
<div class="navigation-bar__inner navigation-bar__inner--right flex flex--row">
@ -15,7 +15,7 @@
<div class="spinner"></div>
</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">
</div>
<div class="navigation-bar__inner navigation-bar__inner--edit-buttons">
@ -60,7 +60,7 @@
</template>
<script>
import { mapState, mapActions } from 'vuex';
import { mapGetters, mapMutations } from 'vuex';
import editorSvc from '../services/editorSvc';
import animationSvc from '../services/animationSvc';
@ -72,19 +72,18 @@ export default {
titleHover: false,
}),
computed: {
...mapState('layout', {
showEditor: 'showEditor',
titleMaxWidth: 'titleMaxWidth',
}),
...mapGetters('layout', [
'styles',
]),
titleWidth() {
if (!this.mounted) {
return 0;
}
this.titleFakeElt.textContent = this.title;
const width = this.titleFakeElt.getBoundingClientRect().width + 1; // 1px for the caret
return width < this.titleMaxWidth
const width = this.titleFakeElt.getBoundingClientRect().width + 2; // 2px for the caret
return width < this.styles.titleMaxWidth
? width
: this.titleMaxWidth;
: this.styles.titleMaxWidth;
},
titleScrolling() {
const result = this.titleHover && !this.titleFocus;
@ -106,7 +105,7 @@ export default {
},
},
methods: {
...mapActions('layout', [
...mapMutations('layout', [
'toggleExplorer',
'toggleSideBar',
]),
@ -120,7 +119,7 @@ export default {
} else {
const title = this.title.trim();
if (title) {
this.$store.dispatch('files/patchCurrent', { name: title });
this.$store.dispatch('files/patchCurrent', { name: title.slice(0, 250) });
} else {
this.title = this.$store.getters['files/current'].name;
}
@ -161,6 +160,10 @@ export default {
.navigation-bar__inner--left {
float: left;
&.navigation-bar__inner--button {
margin-right: 15px;
}
}
.navigation-bar__inner--right {
@ -176,9 +179,16 @@ export default {
}
.navigation-bar__button {
display: inline-block;
width: 34px;
padding: 6px;
/* prevent from seeing wrapped buttons */
margin-bottom: 20px;
.navigation-bar__inner--button & {
padding: 7px;
width: 38px;
}
}
.navigation-bar__title,
@ -186,7 +196,7 @@ export default {
display: inline-block;
color: $navbar-color;
background-color: transparent;
font-weight: 400;
font-size: 22px;
}
.navigation-bar__title--input,
@ -235,7 +245,7 @@ export default {
}
.navigation-bar__spinner {
margin: 10px 5px 0;
margin: 10px 5px 0 15px;
color: rgba(255, 255, 255, 0.33);
}

View File

@ -1,19 +1,19 @@
<template>
<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>
</template>
<script>
import { mapState } from 'vuex';
import { mapGetters } from 'vuex';
const appUri = `${window.location.protocol}//${window.location.host}`;
export default {
computed: mapState('layout', [
'previewPadding',
computed: mapGetters('layout', [
'styles',
]),
mounted() {
this.$el.addEventListener('click', (evt) => {
@ -45,4 +45,17 @@ export default {
margin: 0;
padding: 0 1035px 360px;
}
.preview__inner > :first-child {
& > h1,
& > h2,
& > h3,
& > h4,
& > h5,
& > h6 {
&:first-child {
margin-top: 0;
}
}
}
</style>

View File

@ -1,8 +1,8 @@
<template>
<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">
Text
Markdown
<small v-show="textSelection">(selection)</small>
</span>
<span v-for="stat in textStats" :key="stat.id">
@ -23,7 +23,7 @@
</template>
<script>
import { mapState } from 'vuex';
import { mapGetters } from 'vuex';
import editorSvc from '../services/editorSvc';
import editorEngineSvc from '../services/editorEngineSvc';
import utils from '../services/utils';
@ -54,9 +54,9 @@ export default {
new Stat('paragraphs', '\\S.*'),
],
}),
computed: mapState('layout', {
showEditor: 'showEditor',
}),
computed: mapGetters('layout', [
'styles',
]),
created() {
editorSvc.$on('sectionList', () => this.computeText());
editorSvc.$on('selectionRange', () => this.computeText());

View File

@ -38,10 +38,11 @@ textarea {
.text-input {
display: block;
font-variant-ligatures: no-common-ligatures;
width: 100%;
height: 36px;
padding: 3px 12px;
font-size: 22px;
font-size: inherit;
line-height: 1.5;
color: inherit;
background-color: #fff;
@ -57,7 +58,7 @@ textarea {
height: 36px;
padding: 3px 12px;
margin-bottom: 0;
font-size: 22px;
font-size: inherit;
font-weight: 400;
line-height: 1.4;
overflow: hidden;
@ -106,13 +107,3 @@ textarea {
flex-direction: column;
}
.side-title {
height: 44px;
line-height: 44px;
padding: 0 10px;
background-color: #ccc;
}
.side-title__text {
text-transform: uppercase;
}

View File

@ -27,15 +27,15 @@
}
@font-face {
font-family: 'Inconsolata';
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: url('../assets/fonts/inconsolata.woff') format('woff');
src: url('../assets/fonts/RobotoMono-Regular.woff') format('woff');
}
@font-face {
font-family: 'Inconsolata';
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 600;
src: url('../assets/fonts/inconsolata-bold.woff') format('woff');
src: url('../assets/fonts/RobotoMono-Bold.woff') format('woff');
}

View File

@ -1,8 +1,8 @@
$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-title: 1.33;
$font-size-monospace: 0.95em;
$font-size-monospace: 0.85em;
$code-bg: rgba(0, 0, 0, 0.05);
$code-border-radius: 2px;
$link-color: #4a80cf;
@ -10,7 +10,7 @@ $border-radius-base: 2px;
$hr-color: rgba(128, 128, 128, 0.2);
$navbar-color: rgba(255, 255, 255, 0.67);
$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-light: rgba(0, 0, 0, 0.28);

View File

@ -0,0 +1,4 @@
> Written with [StackEdit](https://stackedit.io/).

View File

@ -1,4 +1,5 @@
export default () => ({
id: null,
type: 'content',
state: {},
text: '\n',

View File

@ -1,6 +1,7 @@
export default () => ({
id: null,
type: 'file',
name: '',
folderId: null,
parentId: null,
contentId: null,
});

6
src/data/emptyFolder.js Normal file
View File

@ -0,0 +1,6 @@
export default () => ({
id: null,
type: 'folder',
name: '',
parentId: null,
});

5
src/icons/Close.vue Normal file
View 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
View 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
View 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>

View 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
View 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
View 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>

View File

@ -17,6 +17,12 @@ import SidePreview from './SidePreview';
import Eye from './Eye';
import Menu from './Menu';
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('iconFormatItalic', FormatItalic);
@ -36,3 +42,9 @@ Vue.component('iconSidePreview', SidePreview);
Vue.component('iconEye', Eye);
Vue.component('iconMenu', Menu);
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);

View File

@ -1,3 +0,0 @@
export default {
scrollOffset: 0,
};

View File

@ -9,7 +9,6 @@ import markdownConversionSvc from './markdownConversionSvc';
import markdownGrammarSvc from './markdownGrammarSvc';
import sectionUtils from './sectionUtils';
import extensionSvc from './extensionSvc';
import constants from './constants';
import animationSvc from './animationSvc';
import editorEngineSvc from './editorEngineSvc';
import store from '../store';
@ -63,7 +62,7 @@ const editorSvc = Object.assign(new Vue(), { // Use a vue instance as an event b
getObjectToScroll() {
let elt = this.editorElt.parentNode;
let dimensionKey = 'editorDimension';
if (!store.state.layout.showEditor) {
if (!store.getters['layout/styles'].showEditor) {
elt = this.previewElt.parentNode;
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;
const sectionDesc = anchorHash[anchor];
if (sectionDesc) {
if (store.state.layout.showSidePreview || !store.state.layout.showEditor) {
if (store.getters['layout/styles'].showPreview) {
scrollTop = sectionDesc.previewDimension.startOffset;
} else {
scrollTop = sectionDesc.editorDimension.startOffset - constants.scrollOffset;
scrollTop = sectionDesc.editorDimension.startOffset;
scrollerElt = this.editorElt.parentNode;
}
} else {
@ -575,7 +574,7 @@ const editorSvc = Object.assign(new Vue(), { // Use a vue instance as an event b
let newSectionList;
let newSelectionRange;
const debouncedEditorChanged = debounce(() => {
const onEditorChanged = () => {
if (this.sectionList !== newSectionList) {
this.sectionList = newSectionList;
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.saveContentState();
}, 10);
};
const debouncedEditorChanged = debounce(onEditorChanged, 10);
editorEngineSvc.clEditor.selectionMgr.on('selectionChanged', (start, end, 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) => {
newSectionList = sectionList;
debouncedEditorChanged();
if (instantPreview) {
onEditorChanged();
} else {
debouncedEditorChanged();
}
});
this.$emit('inited');
// scope.$watch('editorLayoutSvc.isEditorOpen', function (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,
});
store.watch(state => `${state.layout.editorWidth},${state.layout.previewWidth}`,
store.watch(() => `${store.getters['layout/styles'].editorWidth},${store.getters['layout/styles'].previewWidth}`,
() => editorSvc.measureSectionDimensions(true));
store.watch(state => state.layout.showSidePreview,
store.watch(() => store.getters['layout/styles'].showSidePreview,
showSidePreview => showSidePreview && editorSvc.measureSectionDimensions());
this.$emit('inited');
},
});

View File

@ -118,6 +118,7 @@ export default {
[
store.state.contents,
store.state.files,
store.state.folders,
].forEach(moduleState => Object.assign(storeItemMap, moduleState.itemMap));
this.connection.createTx((tx) => {
this.readAll(storeItemMap, tx, () => {

View File

@ -1,5 +1,4 @@
import store from '../../store';
import constants from '../constants';
import animationSvc from '../animationSvc';
import editorSvc from '../editorSvc';
@ -34,9 +33,7 @@ function throttle(func, wait) {
}
const doScrollSync = () => {
const localSkipAnimation = skipAnimation
|| !store.state.layout.showSidePreview
|| !store.state.layout.showEditor;
const localSkipAnimation = skipAnimation || !store.getters['layout/styles'].showSidePreview;
skipAnimation = false;
if (!store.state.editor.scrollSync || !sectionDescList || sectionDescList.length === 0) {
return;
@ -45,12 +42,11 @@ const doScrollSync = () => {
if (editorScrollTop < 0) {
editorScrollTop = 0;
}
let previewScrollTop = previewScrollerElt.scrollTop;
const previewScrollTop = previewScrollerElt.scrollTop;
let scrollTo;
if (isScrollEditor) {
// Scroll the preview
isScrollEditor = false;
editorScrollTop += constants.scrollOffset;
sectionDescList.some((sectionDesc) => {
if (editorScrollTop > sectionDesc.editorDimension.endOffset) {
return false;
@ -58,7 +54,7 @@ const doScrollSync = () => {
const posInSection = (editorScrollTop - sectionDesc.editorDimension.startOffset)
/ (sectionDesc.editorDimension.height || 1);
scrollTo = (sectionDesc.previewDimension.startOffset
+ (sectionDesc.previewDimension.height * posInSection)) - constants.scrollOffset;
+ (sectionDesc.previewDimension.height * posInSection));
return true;
});
scrollTo = Math.min(
@ -79,10 +75,9 @@ const doScrollSync = () => {
isPreviewMoving = true;
});
}, localSkipAnimation ? 500 : 50);
} else if (!store.state.layout.showEditor || isScrollPreview) {
} else if (!store.getters['layout/styles'].showEditor || isScrollPreview) {
// Scroll the editor
isScrollPreview = false;
previewScrollTop += constants.scrollOffset;
sectionDescList.some((sectionDesc) => {
if (previewScrollTop > sectionDesc.previewDimension.endOffset) {
return false;
@ -90,7 +85,7 @@ const doScrollSync = () => {
const posInSection = (previewScrollTop - sectionDesc.previewDimension.startOffset)
/ (sectionDesc.previewDimension.height || 1);
scrollTo = (sectionDesc.editorDimension.startOffset
+ (sectionDesc.editorDimension.height * posInSection)) - constants.scrollOffset;
+ (sectionDesc.editorDimension.height * posInSection));
return true;
});
scrollTo = Math.min(
@ -119,7 +114,7 @@ let timeoutId;
const forceScrollSync = () => {
if (!isPreviewRefreshing) {
doScrollSync(!store.state.layout.showSidePreview);
doScrollSync();
}
};
store.watch(state => state.editor.scrollSync, forceScrollSync);
@ -135,7 +130,7 @@ editorSvc.$on('inited', () => {
}
isScrollEditor = true;
isScrollPreview = false;
doScrollSync(!store.state.layout.showSidePreview);
doScrollSync();
});
previewScrollerElt.addEventListener('scroll', () => {
@ -144,7 +139,7 @@ editorSvc.$on('inited', () => {
}
isScrollPreview = true;
isScrollEditor = false;
doScrollSync(!store.state.layout.showSidePreview);
doScrollSync();
});
});
@ -163,14 +158,15 @@ editorSvc.$on('previewText', () => {
// Remove height property once the preview as been refreshed
previewElt.style.removeProperty('height');
// 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
timeoutId = setTimeout(() => {
isPreviewRefreshing = false;
}, 100);
});
store.watch(state => state.layout.showSidePreview,
store.watch(
() => store.getters['layout/styles'].showSidePreview,
(showSidePreview) => {
if (showSidePreview) {
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) => {
sectionDescList = sectionDescMeasuredList;
forceScrollSync();

View File

@ -10,39 +10,35 @@ const ifNoId = cb => (obj) => {
return cb();
};
// Watch file changing
store.watch(
() => store.getters['files/current'].id,
() => Promise.resolve(store.getters['files/current'])
// If current file has no ID, get the most recent file
.then(ifNoId(() => store.getters['files/mostRecent']))
// If no ID, load the DB (we're booting)
.then(ifNoId(() => localDbSvc.sync()
// Retry
.then(() => store.getters['files/current'])
.then(ifNoId(() => store.getters['files/mostRecent'])),
))
// Finally create a new file
.then(ifNoId(() => {
const contentId = utils.uid();
store.commit('contents/setItem', {
id: contentId,
text: welcomeFile,
});
const fileId = utils.uid();
store.commit('files/setItem', {
id: fileId,
name: 'Welcome file',
contentId,
});
return store.state.files.itemMap[fileId];
}))
.then((currentFile) => {
store.commit('files/setCurrentId', currentFile.id);
store.dispatch('files/patchCurrent', {}); // Update `updated` field to make it the mostRecent
}),
{
immediate: true,
});
// Load the DB on boot
localDbSvc.sync()
// Watch file changing
.then(() => store.watch(
() => store.getters['files/current'].id,
() => Promise.resolve(store.getters['files/current'])
// If current file has no ID, get the most recent file
.then(ifNoId(() => store.getters['files/mostRecent']))
// If still no ID, create a new file
.then(ifNoId(() => {
const contentId = utils.uid();
store.commit('contents/setItem', {
id: contentId,
text: welcomeFile,
});
const fileId = utils.uid();
store.commit('files/setItem', {
id: fileId,
name: 'Welcome file',
contentId,
});
return store.state.files.itemMap[fileId];
}))
.then((currentFile) => {
store.commit('files/setCurrentId', currentFile.id);
store.dispatch('files/patchCurrent', {}); // Update `updated` field to make it the mostRecent
}),
{
immediate: true,
}));
utils.setInterval(() => localDbSvc.sync(), 1200);

View File

@ -3,8 +3,10 @@ import Vue from 'vue';
import Vuex from 'vuex';
import contents from './modules/contents';
import files from './modules/files';
import folders from './modules/folders';
import layout from './modules/layout';
import editor from './modules/editor';
import explorer from './modules/explorer';
Vue.use(Vuex);
@ -14,8 +16,10 @@ const store = new Vuex.Store({
modules: {
contents,
files,
folders,
layout,
editor,
explorer,
},
strict: debug,
plugins: debug ? [createLogger()] : [],

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

View File

@ -0,0 +1,6 @@
import moduleTemplate from './moduleTemplate';
import empty from '../../data/emptyFolder';
const module = moduleTemplate(empty);
export default module;

View File

@ -9,148 +9,135 @@ const setter = propertyName => (state, value) => {
state[propertyName] = value;
};
const toggler = (propertyName, setterName) => ({ state, commit, dispatch }, show) => {
commit(setterName, show === undefined ? !state[propertyName] : show);
dispatch('updateStyle');
const toggler = propertyName => (state, value) => {
state[propertyName] = value === undefined ? !state[propertyName] : value;
};
export default {
namespaced: true,
state: {
// Constants
explorerWidth: 280,
sideBarWidth: 280,
navigationBarHeight: 44,
buttonBarWidth: 30,
statusBarHeight: 20,
constants: {
explorerWidth: 250,
sideBarWidth: 280,
navigationBarHeight: 44,
buttonBarWidth: 30,
statusBarHeight: 20,
},
// Configuration
showNavigationBar: true,
showEditor: true,
showSidePreview: true,
showStatusBar: true,
showSideBar: false,
showExplorer: false,
showExplorer: true,
editorWidthFactor: 1,
fontSizeFactor: 1,
// Style
fontSize: 0,
innerWidth: 0,
innerHeight: 0,
editorWidth: 0,
editorPadding: 0,
previewWidth: 0,
previewPadding: 0,
titleMaxWidth: 0,
// Styles
bodyWidth: 0,
bodyHeight: 0,
},
mutations: {
setShowNavigationBar: setter('showNavigationBar'),
setShowEditor: setter('showEditor'),
setShowSidePreview: setter('showSidePreview'),
setShowStatusBar: setter('showStatusBar'),
setShowSideBar: setter('showSideBar'),
setShowExplorer: setter('showExplorer'),
toggleNavigationBar: toggler('showNavigationBar'),
toggleEditor: toggler('showEditor'),
toggleSidePreview: toggler('showSidePreview'),
toggleStatusBar: toggler('showStatusBar'),
toggleSideBar: toggler('showSideBar'),
toggleExplorer: toggler('showExplorer'),
setEditorWidthFactor: setter('editorWidthFactor'),
setFontSizeFactor: setter('fontSizeFactor'),
setFontSize: setter('fontSize'),
setInnerWidth: setter('innerWidth'),
setInnerHeight: setter('innerHeight'),
setEditorWidth: setter('editorWidth'),
setEditorPadding: setter('editorPadding'),
setPreviewWidth: setter('previewWidth'),
setPreviewPadding: setter('previewPadding'),
setTitleMaxWidth: setter('titleMaxWidth'),
updateBodySize: (state) => {
state.bodyWidth = document.body.clientWidth;
state.bodyHeight = document.body.clientHeight;
},
},
actions: {
toggleNavigationBar: toggler('showNavigationBar', 'setShowNavigationBar'),
toggleEditor: toggler('showEditor', 'setShowEditor'),
toggleSidePreview: toggler('showSidePreview', 'setShowSidePreview'),
toggleStatusBar: toggler('showStatusBar', 'setShowStatusBar'),
toggleSideBar: toggler('showSideBar', 'setShowSideBar'),
toggleExplorer: toggler('showExplorer', 'setShowExplorer'),
updateStyle({ state, commit, dispatch }) {
const bodyWidth = document.body.clientWidth;
const bodyHeight = document.body.clientHeight;
getters: {
styles: (state) => {
const styles = {
showNavigationBar: !state.showEditor || state.showNavigationBar,
showStatusBar: state.showStatusBar,
showEditor: state.showEditor,
showSidePreview: state.showSidePreview && state.showEditor,
showPreview: state.showSidePreview || !state.showEditor,
showSideBar: state.showSideBar,
showExplorer: state.showExplorer,
};
const showNavigationBar = !state.showEditor || state.showNavigationBar;
let innerHeight = bodyHeight;
if (showNavigationBar) {
innerHeight -= state.navigationBarHeight;
}
if (state.showStatusBar) {
innerHeight -= state.statusBarHeight;
}
function computeStyles() {
styles.innerHeight = state.bodyHeight;
if (styles.showNavigationBar) {
styles.innerHeight -= state.constants.navigationBarHeight;
}
if (styles.showStatusBar) {
styles.innerHeight -= state.constants.statusBarHeight;
}
let innerWidth = bodyWidth;
if (state.showSideBar) {
innerWidth -= state.sideBarWidth;
}
if (state.showExplorer) {
innerWidth -= state.explorerWidth;
}
let doublePanelWidth = innerWidth - state.buttonBarWidth;
if (doublePanelWidth < editorMinWidth) {
if (state.showSideBar) {
dispatch('toggleSideBar', false);
styles.innerWidth = state.bodyWidth;
if (styles.showSideBar) {
styles.innerWidth -= state.constants.sideBarWidth;
}
if (styles.showExplorer) {
styles.innerWidth -= state.constants.explorerWidth;
}
let doublePanelWidth = styles.innerWidth - state.constants.buttonBarWidth;
if (doublePanelWidth < editorMinWidth) {
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;
}
if (state.showExplorer) {
dispatch('toggleExplorer', false);
return;
styles.fontSize = 18;
styles.textWidth = 990;
if (doublePanelWidth < 1120) {
styles.fontSize -= 1;
styles.textWidth = 910;
}
doublePanelWidth = editorMinWidth;
}
const splitPanel = state.showEditor && state.showSidePreview;
if (splitPanel && doublePanelWidth / 2 < editorMinWidth) {
dispatch('toggleSidePreview', false);
return;
if (doublePanelWidth < 1040) {
styles.textWidth = 830;
}
styles.textWidth *= state.editorWidthFactor;
if (doublePanelWidth < styles.textWidth) {
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;
let textWidth = 990;
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);
computeStyles();
return styles;
},
},
};