New file properties modal
This commit is contained in:
parent
fb13a52157
commit
ab97d9d9fc
@ -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;
|
||||
},
|
||||
|
@ -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;
|
||||
|
@ -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'] ||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -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 {
|
||||
|
@ -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"> — {{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(),
|
||||
}),
|
||||
|
@ -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">
|
||||
|
@ -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">
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -1,5 +1,6 @@
|
||||
export default () => ({
|
||||
welcomeFileHashes: {},
|
||||
filePropertiesTab: '',
|
||||
htmlExportTemplate: 'styledHtml',
|
||||
pdfExportTemplate: 'styledHtml',
|
||||
pandocExportFormat: 'pdf',
|
||||
|
@ -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
|
||||
|
||||
|
@ -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',
|
||||
}];
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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 || [];
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user