New file properties modal

This commit is contained in:
benweet 2018-04-08 15:49:10 +01:00
parent fb13a52157
commit ab97d9d9fc
29 changed files with 348 additions and 214 deletions

View File

@ -1,5 +1,5 @@
<template>
<div class="app" :class="themeClasses">
<div class="app" :class="classes">
<splash-screen v-if="!ready"></splash-screen>
<layout v-else></layout>
<modal v-if="showModal"></modal>
@ -81,7 +81,7 @@ export default {
ready: false,
}),
computed: {
themeClasses() {
classes() {
const result = themeClasses[this.$store.getters['data/computedSettings'].colorTheme];
return Array.isArray(result) ? result : themeClasses.light;
},

View File

@ -232,6 +232,19 @@ export default {
}
}
.modal__title {
font-weight: bold;
font-size: 1.5rem;
line-height: 1.4;
margin-top: 2.5rem;
}
.modal__sub-title {
opacity: 0.5;
font-size: 0.75rem;
margin-bottom: 1.5rem;
}
.modal__error {
color: #de2c00;
}
@ -241,6 +254,12 @@ export default {
border-radius: $border-radius-base;
margin: 1.2em 0;
padding: 0.75em 1.25em;
font-size: 0.95em;
line-height: 1.6;
pre {
line-height: 1.5;
}
}
.modal__button-bar {
@ -266,6 +285,10 @@ export default {
}
}
.form-entry__label-info {
font-size: 0.75rem;
}
.form-entry__field {
border: 1px solid #d8d8d8;
border-radius: $border-radius-base;

View File

@ -36,8 +36,8 @@
<div class="navigation-bar__inner navigation-bar__inner--edit-pagedownButtons">
<button class="navigation-bar__button button" @click="undo" v-title="'Undo'" :disabled="!canUndo"><icon-undo></icon-undo></button>
<button class="navigation-bar__button button" @click="redo" v-title="'Redo'" :disabled="!canRedo"><icon-redo></icon-redo></button>
<div v-for="button in pagedownButtons" :key="button.action">
<button class="navigation-bar__button button" v-if="button.action" @click="pagedownClick(button.action)" v-title="button.title">
<div v-for="button in pagedownButtons" :key="button.method">
<button class="navigation-bar__button button" v-if="button.method" @click="pagedownClick(button.method)" v-title="button.titleWithShortcut">
<component :is="button.iconClass"></component>
</button>
<div class="navigation-bar__spacer" v-else></div>
@ -55,6 +55,28 @@ import animationSvc from '../services/animationSvc';
import tempFileSvc from '../services/tempFileSvc';
import utils from '../services/utils';
import pagedownButtons from '../data/pagedownButtons';
import store from '../store';
// According to mousetrap
const mod = /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'Meta' : 'Ctrl';
const getShortcut = (method) => {
let result = '';
Object.entries(store.getters['data/computedSettings'].shortcuts).some(([keys, shortcut]) => {
if (`${shortcut.method || shortcut}` !== method) {
return false;
}
result = keys.split('+').map(key => key.toLowerCase()).map((key) => {
if (key === 'mod') {
return mod;
}
// Capitalize
return key && `${key[0].toUpperCase()}${key.slice(1)}`;
}).join('+');
return true;
});
return result && ` ${result}`;
};
export default {
data: () => ({
@ -90,14 +112,11 @@ export default {
publishLocations: 'current',
}),
pagedownButtons() {
return pagedownButtons.map((button) => {
const title = button.title;
return {
...button,
title,
iconClass: `icon-${button.icon}`,
};
});
return pagedownButtons.map(button => ({
...button,
titleWithShortcut: `${button.title}${getShortcut(button.method)}`,
iconClass: `icon-${button.icon}`,
}));
},
isSyncPossible() {
return this.$store.getters['workspace/syncToken'] ||

View File

@ -44,27 +44,29 @@ export default class EditorClassApplier {
};
editorSvc.clEditor.on('contentChanged', this.restoreClass);
nextTick(() => this.applyClass());
nextTick(() => this.restoreClass());
}
applyClass() {
const offset = this.offsetGetter();
if (offset && offset.start !== offset.end) {
const range = editorSvc.clEditor.selectionMgr.createRange(
Math.min(offset.start, offset.end),
Math.max(offset.start, offset.end),
);
const properties = {
...this.properties,
className: this.classGetter().join(' '),
};
editorSvc.clEditor.watcher.noWatch(() => {
utils.wrapRange(range, properties);
});
if (editorSvc.clEditor.selectionMgr.hasFocus()) {
nextTickRestoreSelection();
if (!this.stopped) {
const offset = this.offsetGetter();
if (offset && offset.start !== offset.end) {
const range = editorSvc.clEditor.selectionMgr.createRange(
Math.min(offset.start, offset.end),
Math.max(offset.start, offset.end),
);
const properties = {
...this.properties,
className: this.classGetter().join(' '),
};
editorSvc.clEditor.watcher.noWatch(() => {
utils.wrapRange(range, properties);
});
if (editorSvc.clEditor.selectionMgr.hasFocus()) {
nextTickRestoreSelection();
}
this.lastEltCount = this.eltCollection.length;
}
this.lastEltCount = this.eltCollection.length;
}
}
@ -80,5 +82,6 @@ export default class EditorClassApplier {
stop() {
editorSvc.clEditor.off('contentChanged', this.restoreClass);
nextTick(() => this.removeClass());
this.stopped = true;
}
}

View File

@ -32,30 +32,32 @@ export default class PreviewClassApplier {
};
editorSvc.$on('previewCtxWithDiffs', this.restoreClass);
nextTick(() => this.applyClass());
nextTick(() => this.restoreClass());
}
applyClass() {
const offset = this.offsetGetter();
if (offset) {
const offsetStart = editorSvc.getPreviewOffset(
offset.start, editorSvc.previewCtx.sectionDescList);
const offsetEnd = editorSvc.getPreviewOffset(
offset.end, editorSvc.previewCtx.sectionDescList);
if (offsetStart != null && offsetEnd != null && offsetStart !== offsetEnd) {
const start = cledit.Utils.findContainer(
editorSvc.previewElt, Math.min(offsetStart, offsetEnd));
const end = cledit.Utils.findContainer(
editorSvc.previewElt, Math.max(offsetStart, offsetEnd));
const range = document.createRange();
range.setStart(start.container, start.offsetInContainer);
range.setEnd(end.container, end.offsetInContainer);
const properties = {
...this.properties,
className: this.classGetter().join(' '),
};
utils.wrapRange(range, properties);
this.lastEltCount = this.eltCollection.length;
if (!this.stopped) {
const offset = this.offsetGetter();
if (offset) {
const offsetStart = editorSvc.getPreviewOffset(
offset.start, editorSvc.previewCtx.sectionDescList);
const offsetEnd = editorSvc.getPreviewOffset(
offset.end, editorSvc.previewCtx.sectionDescList);
if (offsetStart != null && offsetEnd != null && offsetStart !== offsetEnd) {
const start = cledit.Utils.findContainer(
editorSvc.previewElt, Math.min(offsetStart, offsetEnd));
const end = cledit.Utils.findContainer(
editorSvc.previewElt, Math.max(offsetStart, offsetEnd));
const range = document.createRange();
range.setStart(start.container, start.offsetInContainer);
range.setEnd(end.container, end.offsetInContainer);
const properties = {
...this.properties,
className: this.classGetter().join(' '),
};
utils.wrapRange(range, properties);
this.lastEltCount = this.eltCollection.length;
}
}
}
}
@ -67,5 +69,6 @@ export default class PreviewClassApplier {
stop() {
editorSvc.$off('previewCtxWithDiffs', this.restoreClass);
nextTick(() => this.removeClass());
this.stopped = true;
}
}

View File

@ -38,7 +38,6 @@ import UserName from '../UserName';
import EditorClassApplier from '../common/EditorClassApplier';
import PreviewClassApplier from '../common/PreviewClassApplier';
import utils from '../../services/utils';
import editorSvc from '../../services/editorSvc';
import googleHelper from '../../services/providers/helpers/googleHelper';
import syncSvc from '../../services/syncSvc';
@ -137,22 +136,20 @@ export default {
previewClassAppliers.forEach(previewClassApplier => previewClassApplier.stop());
previewClassAppliers = [];
if (revisionContent) {
editorSvc.$once('previewCtxWithDiffs', () => {
let offset = 0;
revisionContent.diffs.forEach(([type, text]) => {
if (type) {
const classes = ['revision-diff', `revision-diff--${type > 0 ? 'insert' : 'delete'}`];
const offsets = {
start: offset,
end: offset + text.length,
};
editorClassAppliers.push(new EditorClassApplier(
[`revision-diff--${utils.uid()}`, ...classes], offsets));
previewClassAppliers.push(new PreviewClassApplier(
[`revision-diff--${utils.uid()}`, ...classes], offsets));
}
offset += text.length;
});
let offset = 0;
revisionContent.diffs.forEach(([type, text]) => {
if (type) {
const classes = ['revision-diff', `revision-diff--${type > 0 ? 'insert' : 'delete'}`];
const offsets = {
start: offset,
end: offset + text.length,
};
editorClassAppliers.push(new EditorClassApplier(
[`revision-diff--${utils.uid()}`, ...classes], offsets));
previewClassAppliers.push(new PreviewClassApplier(
[`revision-diff--${utils.uid()}`, ...classes], offsets));
}
offset += text.length;
});
}
},

View File

@ -2,26 +2,76 @@
<modal-inner class="modal__inner-1--file-properties" aria-label="File properties">
<div class="modal__content">
<div class="tabs flex flex--row">
<tab :active="tab === 'custom'" @click="tab = 'custom'">
Current file properties
<tab :active="tab === 'simple'" @click="setSimpleTab()">
Simple properties
</tab>
<tab :active="tab === 'default'" @click="tab = 'default'">
Default properties
<tab :active="tab === 'yaml'" @click="setYamlTab()">
YAML properties
</tab>
</div>
<div class="form-entry" v-if="tab === 'custom'" role="tabpanel" aria-label="Current file properties">
<label class="form-entry__label">YAML</label>
<div class="form-entry__field">
<code-editor lang="yaml" :value="customProperties" key="custom-properties" @changed="setCustomProperties"></code-editor>
<div v-if="tab === 'simple'">
<div class="modal__title">Extensions</div>
<div class="modal__sub-title">Configure the Markdown engine.</div>
<form-entry label="Preset">
<select slot="field" class="textfield" v-model="preset" @keydown.enter="resolve()">
<option v-for="(preset, id) in presets" :key="id" :value="preset">
{{ preset }}
</option>
</select>
</form-entry>
<div class="modal__title">Metadata</div>
<div class="modal__sub-title">Add info to your publications (Wordpress, Blogger...).</div>
<form-entry label="Title">
<input slot="field" class="textfield" type="text" v-model.trim="title" @keydown.enter="resolve()">
</form-entry>
<form-entry label="Author">
<input slot="field" class="textfield" type="text" v-model.trim="author" @keydown.enter="resolve()">
</form-entry>
<form-entry label="Tags" info="comma-separated">
<input slot="field" class="textfield" type="text" v-model.trim="tags" @keydown.enter="resolve()">
</form-entry>
<form-entry label="Categories" info="comma-separated">
<input slot="field" class="textfield" type="text" v-model.trim="categories" @keydown.enter="resolve()">
</form-entry>
<form-entry label="Excerpt">
<input slot="field" class="textfield" type="text" v-model.trim="excerpt" @keydown.enter="resolve()">
</form-entry>
<form-entry label="Featured image">
<input slot="field" class="textfield" type="text" v-model.trim="featuredImage" @keydown.enter="resolve()">
</form-entry>
<form-entry label="Status">
<input slot="field" class="textfield" type="text" v-model.trim="status" @keydown.enter="resolve()">
</form-entry>
<form-entry label="Date" info="YYYY-MM-DD">
<input slot="field" class="textfield" type="text" v-model.trim="date" @keydown.enter="resolve()">
</form-entry>
</div>
<div v-if="tab === 'yaml'">
<div class="form-entry" role="tabpanel" aria-label="YAML properties">
<label class="form-entry__label">YAML</label>
<div class="form-entry__field">
<code-editor lang="yaml" :value="yamlProperties" key="custom-properties" @changed="setYamlProperties"></code-editor>
</div>
</div>
<div class="modal__error modal__error--file-properties">{{error}}</div>
<div class="modal__info">
<p><strong>ProTip:</strong> You can manually toggle extensions:</p>
<pre class=" language-yaml"><code class="prism language-yaml"><span class="token key atrule">extensions</span><span class="token punctuation">:</span>
<span class="token key atrule">emoji</span><span class="token punctuation">:</span>
<span class="token comment"># Enable emoji shortcuts like :) :-(</span>
<span class="token key atrule">shortcuts</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
</code></pre>
<p>Use preset <code>zero</code> to make your own configuration:</p>
<pre class=" language-yaml"><code class="prism language-yaml"><span class="token key atrule">extensions</span><span class="token punctuation">:</span>
<span class="token key atrule">preset</span><span class="token punctuation">:</span> zero
<span class="token key atrule">markdown</span><span class="token punctuation">:</span>
<span class="token key atrule">table</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
<span class="token key atrule">katex</span><span class="token punctuation">:</span>
<span class="token key atrule">enabled</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
</code></pre>
<p>For the full list of options, see <a href="https://github.com/benweet/stackedit/blob/master/src/data/presets.js" target="_blank">here</a>.</p>
</div>
</div>
<div class="form-entry" v-else-if="tab === 'default'" role="tabpanel" aria-label="Default properties">
<label class="form-entry__label">YAML</label>
<div class="form-entry__field">
<code-editor lang="yaml" :value="defaultProperties" key="default-properties" disabled="true"></code-editor>
</div>
</div>
<div class="modal__error modal__error--file-properties">{{error}}</div>
</div>
<div class="modal__button-bar">
<button class="button" @click="config.reject()">Cancel</button>
@ -35,56 +85,135 @@ import yaml from 'js-yaml';
import { mapGetters } from 'vuex';
import ModalInner from './common/ModalInner';
import Tab from './common/Tab';
import FormEntry from './common/FormEntry';
import CodeEditor from '../CodeEditor';
import utils from '../../services/utils';
import defaultProperties from '../../data/defaultFileProperties.yml';
import presets from '../../data/presets';
const emptyProperties = `# Add custom properties for the current file here
# to override the default properties.
`;
const simpleProperties = {
title: '',
author: '',
tags: '',
categories: '',
excerpt: '',
featuredImage: '',
status: '',
date: '',
};
export default {
components: {
ModalInner,
Tab,
FormEntry,
CodeEditor,
},
data: () => ({
contentId: null,
tab: 'custom',
defaultProperties,
customProperties: null,
yamlProperties: null,
preset: '',
error: null,
...simpleProperties,
}),
computed: {
...mapGetters('modal', [
'config',
]),
strippedCustomProperties() {
return this.customProperties === emptyProperties ? '\n' : this.customProperties.replace(/\t/g, ' ');
presets: () => Object.keys(presets).sort(),
tab: {
get() {
return this.$store.getters['data/localSettings'].filePropertiesTab;
},
set(value) {
this.$store.dispatch('data/patchLocalSettings', {
filePropertiesTab: value,
});
},
},
},
created() {
const content = this.$store.getters['content/current'];
this.contentId = content.id;
const properties = content.properties;
this.setCustomProperties(properties === '\n' ? emptyProperties : properties);
this.setYamlProperties(content.properties);
if (this.tab !== 'yaml') {
this.setSimpleTab();
}
},
methods: {
setCustomProperties(value) {
this.customProperties = value;
yamlToSimple() {
const properties = this.properties || {};
const extensions = properties.extensions || {};
this.preset = extensions.preset;
if (this.presets.indexOf(this.preset) === -1) {
this.preset = 'default';
}
Object.keys(simpleProperties).forEach((name) => {
this[name] = `${properties[name] || ''}`;
});
},
simpleToYaml() {
let hasChanged = false;
const properties = this.properties || {};
const extensions = properties.extensions || {};
if (this.preset !== extensions.preset) {
if (this.preset !== 'default') {
extensions.preset = this.preset;
hasChanged = true;
} else if (extensions.preset) {
delete extensions.preset;
hasChanged = true;
}
}
Object.keys(simpleProperties).forEach((name) => {
if (this[name] !== properties[name]) {
if (this[name]) {
properties[name] = this[name];
hasChanged = true;
} else if (properties[name]) {
delete properties[name];
hasChanged = true;
}
}
});
if (hasChanged) {
if (Object.keys(extensions).length) {
properties.extensions = extensions;
} else {
delete properties.extensions;
}
this.setYamlProperties(Object.keys(properties).length
? yaml.safeDump(properties)
: '\n');
}
},
setSimpleTab() {
this.tab = 'simple';
this.yamlToSimple();
},
setYamlTab() {
this.tab = 'yaml';
this.simpleToYaml();
},
setYamlProperties(value) {
this.yamlProperties = value;
try {
yaml.safeLoad(this.strippedCustomProperties);
this.properties = yaml.safeLoad(value);
this.error = null;
} catch (e) {
this.error = e.message;
}
},
resolve() {
if (!this.error) {
if (this.tab === 'simple') {
// Compute YAML properties
this.simpleToYaml();
}
if (this.error) {
this.setYamlTab();
} else {
this.$store.commit('content/patchItem', {
id: this.contentId,
properties: utils.sanitizeText(this.strippedCustomProperties),
properties: utils.sanitizeText(this.yamlProperties),
});
this.config.resolve();
}
@ -97,7 +226,7 @@ export default {
@import '../common/variables.scss';
.modal__inner-1--file-properties {
max-width: 600px;
max-width: 540px;
}
.modal__error--file-properties {

View File

@ -1,6 +1,6 @@
<template>
<div class="form-entry" :error="error">
<label class="form-entry__label" :for="uid">{{label}}</label>
<label class="form-entry__label" :for="uid">{{label}}<span class="form-entry__label-info" v-if="info"> &mdash; {{info}}</span></label>
<div class="form-entry__field">
<slot name="field"></slot>
</div>
@ -12,7 +12,7 @@
import utils from '../../../services/utils';
export default {
props: ['label', 'error'],
props: ['label', 'info', 'error'],
data: () => ({
uid: utils.uid(),
}),

View File

@ -11,7 +11,7 @@
<b>Example:</b> http://example.blogger.com/
</div>
</form-entry>
<form-entry label="Existing page ID (optional)">
<form-entry label="Existing page ID" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="pageId" @keydown.enter="resolve()">
</form-entry>
<form-entry label="Template">

View File

@ -11,7 +11,7 @@
<b>Example:</b> http://example.blogger.com/
</div>
</form-entry>
<form-entry label="Existing post ID (optional)">
<form-entry label="Existing post ID" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="postId" @keydown.enter="resolve()">
</form-entry>
<form-entry label="Template">

View File

@ -15,7 +15,7 @@
</label>
</div>
</div>
<form-entry label="Existing Gist ID (optional)">
<form-entry label="Existing Gist ID" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()">
<div class="form-entry__info">
If the file exists in the Gist, it will be replaced.

View File

@ -15,7 +15,7 @@
</label>
</div>
</div>
<form-entry label="Existing Gist ID (optional)">
<form-entry label="Existing Gist ID" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()">
<div class="form-entry__info">
If the file exists in the Gist, it will be replaced.

View File

@ -11,7 +11,7 @@
<b>Example:</b> https://github.com/benweet/stackedit
</div>
</form-entry>
<form-entry label="Branch (optional)">
<form-entry label="Branch" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
<div class="form-entry__info">
If not provided, the <code>master</code> branch will be used.

View File

@ -11,7 +11,7 @@
<b>Example:</b> https://github.com/benweet/stackedit
</div>
</form-entry>
<form-entry label="Branch (optional)">
<form-entry label="Branch" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
<div class="form-entry__info">
If not provided, the master branch will be used.

View File

@ -11,7 +11,7 @@
<b>Example:</b> https://github.com/benweet/stackedit
</div>
</form-entry>
<form-entry label="Branch (optional)">
<form-entry label="Branch" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
<div class="form-entry__info">
If not provided, the <code>master</code> branch will be used.

View File

@ -5,7 +5,7 @@
<icon-provider provider-id="googleDrive"></icon-provider>
</div>
<p>This will publish <b>{{currentFileName}}</b> to your <b>Google Drive</b> account.</p>
<form-entry label="Folder ID (optional)">
<form-entry label="Folder ID" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="folderId" @keydown.enter="resolve()">
<div class="form-entry__info">
If no folder ID is supplied, the file will be created in your root folder.
@ -14,7 +14,7 @@
<a href="javascript:void(0)" @click="openFolder">Choose folder</a>
</div>
</form-entry>
<form-entry label="Existing file ID (optional)">
<form-entry label="Existing file ID" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="fileId" @keydown.enter="resolve()">
<div class="form-entry__info">
This will overwrite the file on the server.

View File

@ -5,7 +5,7 @@
<icon-provider provider-id="googleDrive"></icon-provider>
</div>
<p>This will save <b>{{currentFileName}}</b> to your <b>Google Drive</b> account and keep it synchronized.</p>
<form-entry label="Folder ID (optional)">
<form-entry label="Folder ID" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="folderId" @keydown.enter="resolve()">
<div class="form-entry__info">
If no folder ID is supplied, the file will be created in your root folder.
@ -14,7 +14,7 @@
<a href="javascript:void(0)" @click="openFolder">Choose folder</a>
</div>
</form-entry>
<form-entry label="Existing file ID (optional)">
<form-entry label="Existing file ID" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="fileId" @keydown.enter="resolve()">
<div class="form-entry__info">
This will overwrite the file on the server.

View File

@ -5,7 +5,7 @@
<icon-provider provider-id="googleDrive"></icon-provider>
</div>
<p>This will create a workspace synchronized with a <b>Google Drive</b> folder.</p>
<form-entry label="Folder ID (optional)">
<form-entry label="Folder ID" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="folderId" @keydown.enter="resolve()">
<div class="form-entry__info">
If no folder ID is supplied, a new workspace folder will be created in your root folder.

View File

@ -2,10 +2,10 @@
<modal-inner class="modal__inner-1--google-photo" aria-label="Import Google Photo">
<div class="modal__content">
<div class="google-photo__tumbnail" :style="{backgroundImage: thumbnailUrl}"></div>
<form-entry label="Title (optional)">
<form-entry label="Title" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="title" @keydown.enter="resolve()">
</form-entry>
<form-entry label="Size limit (optional)">
<form-entry label="Size limit" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="size" @keydown.enter="resolve()">
</form-entry>
</div>

View File

@ -12,7 +12,7 @@
<b>Jetpack plugin</b> is required for self-hosted sites.
</div>
</form-entry>
<form-entry label="Existing post ID (optional)">
<form-entry label="Existing post ID" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="postId" @keydown.enter="resolve()">
</form-entry>
<form-entry label="Template">

View File

@ -11,10 +11,10 @@
https://example.zendesk.com/hc/en-us/sections/<b>21857469</b>-Section-name
</div>
</form-entry>
<form-entry label="Existing article ID (optional)">
<form-entry label="Existing article ID" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="articleId" @keydown.enter="resolve()">
</form-entry>
<form-entry label="Locale (optional)">
<form-entry label="Locale" info="optional">
<input slot="field" class="textfield" type="text" v-model.trim="locale" @keydown.enter="resolve()">
<div class="form-entry__info">
<b>Default:</b> en-us

View File

@ -1,63 +0,0 @@
## File properties can contain metadata used
## for your publications (Wordpress, Blogger...).
## For example:
#title: My article
#author:
#tags: Tag 1, Tag 2
#categories: Categorie 1, Categorie 2
#excerpt:
#featuredImage:
#status: draft
#date: YYYY-MM-DD HH:MM:SS
## Extensions configuration
## Preset can be `default`, `commonmark`, `gfm` or `zero`.
## Use preset `zero` to manually switch on extensions.
extensions:
preset: default
## Markdown extension
#markdown:
#abbr: true
#breaks: true
#deflist: true
#del: true
#fence: true
#footnote: true
#imgsize: true
#linkify: true
#mark: true
#sub: true
#sup: true
#table: true
#tasklist: true
#typographer: true
## Emoji extension
#emoji:
#enabled: true
## Enable shortcuts like :) :-(
## These are disabled by default.
#shortcuts: false
## Katex extension
## Render LaTeX mathematical expressions by using:
## $...$ for inline formulas
## $$...$$ for displayed formulas.
## See https://math.meta.stackexchange.com/questions/5020
#katex:
#enabled: true
## Mermaid extension
## Convert code blocks starting with ```mermaid
## into diagrams and flowcharts.
## See https://mermaidjs.github.io/
#mermaid:
#enabled: true

View File

@ -1,5 +1,6 @@
export default () => ({
welcomeFileHashes: {},
filePropertiesTab: '',
htmlExportTemplate: 'styledHtml',
pdfExportTemplate: 'styledHtml',
pandocExportFormat: 'pdf',

View File

@ -24,15 +24,18 @@ shortcuts:
mod+alt+f: replace
mod+g: replace
mod+shift+b: bold
mod+shift+i: italic
mod+shift+l: link
mod+shift+q: quote
mod+shift+c: clist
mod+shift+k: code
mod+shift+g: image
mod+shift+o: olist
mod+shift+u: ulist
mod+shift+h: heading
mod+shift+r: hr
mod+shift+g: image
mod+shift+i: italic
mod+shift+l: link
mod+shift+o: olist
mod+shift+q: quote
mod+shift+s: strikethrough
mod+shift+t: table
mod+shift+u: ulist
'= = > space':
method: expand
params:
@ -84,6 +87,5 @@ newFileContent: |
# Default properties for new files
newFileProperties: |
# extensions:
# markdown:
# breaks: false
# preset: gfm

View File

@ -1,49 +1,49 @@
export default [{}, {
action: 'bold',
method: 'bold',
title: 'Bold',
icon: 'format-bold',
}, {
action: 'italic',
method: 'italic',
title: 'Italic',
icon: 'format-italic',
}, {
action: 'heading',
method: 'heading',
title: 'Heading',
icon: 'format-size',
}, {
action: 'strikethrough',
method: 'strikethrough',
title: 'Strikethrough',
icon: 'format-strikethrough',
}, {}, {
action: 'ulist',
method: 'ulist',
title: 'Unordered list',
icon: 'format-list-bulleted',
}, {
action: 'olist',
method: 'olist',
title: 'Ordered list',
icon: 'format-list-numbers',
}, {
action: 'clist',
method: 'clist',
title: 'Check list',
icon: 'format-list-checks',
}, {}, {
action: 'quote',
method: 'quote',
title: 'Blockquote',
icon: 'format-quote-close',
}, {
action: 'code',
method: 'code',
title: 'Code',
icon: 'code-tags',
}, {
action: 'table',
method: 'table',
title: 'Table',
icon: 'table',
}, {
action: 'link',
method: 'link',
title: 'Link',
icon: 'link-variant',
}, {
action: 'image',
method: 'image',
title: 'Image',
icon: 'file-image',
}];

View File

@ -1,4 +1,5 @@
const zero = {
// Markdown extensions
markdown: {
abbr: false,
breaks: false,
@ -15,13 +16,28 @@ const zero = {
tasklist: false,
typographer: false,
},
// Emoji extension
emoji: {
enabled: false,
// Enable shortcuts like :) :-(
shortcuts: false,
},
/*
Katex extension
Render LaTeX mathematical expressions using:
$...$ for inline formulas
$$...$$ for displayed formulas.
See https://math.meta.stackexchange.com/questions/5020
*/
katex: {
enabled: false,
},
/*
Mermaid extension
Convert code blocks starting with ```mermaid
into diagrams and flowcharts.
See https://mermaidjs.github.io/
*/
mermaid: {
enabled: false,
},

View File

@ -29,6 +29,7 @@ const methods = {
image: pagedownHandler('image'),
olist: pagedownHandler('olist'),
ulist: pagedownHandler('ulist'),
clist: pagedownHandler('clist'),
heading: pagedownHandler('heading'),
hr: pagedownHandler('hr'),
sync() {
@ -66,8 +67,7 @@ store.watch(
(computedSettings) => {
Mousetrap.reset();
const shortcuts = computedSettings.shortcuts;
Object.entries(shortcuts).forEach(([key, shortcut]) => {
Object.entries(computedSettings.shortcuts).forEach(([key, shortcut]) => {
if (shortcut) {
const method = `${shortcut.method || shortcut}`;
let params = shortcut.params || [];

View File

@ -1,6 +1,5 @@
import yaml from 'js-yaml';
import '../libs/clunderscore';
import defaultPropertiesYaml from '../data/defaultFileProperties.yml';
import presets from '../data/presets';
const origin = `${location.protocol}//${location.host}`;
@ -56,13 +55,14 @@ const deepCopy = (obj) => {
return JSON.parse(JSON.stringify(obj));
};
// Build presets
// Compute presets
const computedPresets = {};
Object.keys(presets).forEach((key) => {
let preset = deepCopy(presets[key][0]);
if (presets[key][1]) {
preset = deepOverride(preset, presets[key][1]);
}
presets[key] = preset;
computedPresets[key] = preset;
});
export default {
@ -154,13 +154,17 @@ export default {
c => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`).join(''));
},
computeProperties(yamlProperties) {
const customProperties = yaml.safeLoad(yamlProperties);
const defaultProperties = yaml.safeLoad(defaultPropertiesYaml);
const properties = deepOverride(defaultProperties, customProperties);
const preset = deepCopy(presets[properties.extensions.preset] || presets.default);
const extensions = deepOverride(preset, properties.extensions);
extensions.preset = properties.extensions.preset;
properties.extensions = extensions;
let properties = {};
try {
properties = yaml.safeLoad(yamlProperties) || {};
} catch (e) {
// Ignore
}
const extensions = properties.extensions || {};
const computedPreset = deepCopy(computedPresets[extensions.preset] || computedPresets.default);
const computedExtensions = deepOverride(computedPreset, properties.extensions);
computedExtensions.preset = extensions.preset;
properties.extensions = computedExtensions;
return properties;
},
randomize(value) {

View File

@ -3,7 +3,7 @@ import pagedownButtons from '../data/pagedownButtons';
let buttonCount = 0;
let spacerCount = 0;
pagedownButtons.forEach((button) => {
if (button.action) {
if (button.method) {
buttonCount += 1;
} else {
spacerCount += 1;