Fixed publishing

This commit is contained in:
Benoit Schweblin 2018-08-17 11:13:08 +01:00
parent bdb01f407b
commit d4624eba9d
13 changed files with 121 additions and 69 deletions

View File

@ -8,7 +8,9 @@ ENV V4_VERSION 4.3.22
RUN npm pack stackedit@$V4_VERSION \ RUN npm pack stackedit@$V4_VERSION \
&& tar xzf stackedit-*.tgz --strip 1 \ && tar xzf stackedit-*.tgz --strip 1 \
&& yarn \ && yarn \
&& yarn cache clean && yarn cache clean \
&& rm -rf ~/.cache/bower \
&& rm -rf ~/.local/share/bower
WORKDIR /opt/stackedit WORKDIR /opt/stackedit

View File

@ -53,7 +53,9 @@ export default {
this.ready = true; this.ready = true;
tempFileSvc.setReady(); tempFileSvc.setReady();
} catch (err) { } catch (err) {
if (err && err.message !== 'RELOAD') { if (err && err.message === 'RELOAD') {
window.location.reload();
} else if (err && err.message !== 'RELOAD') {
console.error(err); // eslint-disable-line no-console console.error(err); // eslint-disable-line no-console
this.$store.dispatch('notification/error', err); this.$store.dispatch('notification/error', err);
} }

View File

@ -12,7 +12,7 @@
</div> </div>
<div class="tour-step__inner" v-else-if="step === 'editor'"> <div class="tour-step__inner" v-else-if="step === 'editor'">
<h2>Your Markdown editor</h2> <h2>Your Markdown editor</h2>
<p>StackEdit renders your Markdown into HTML in real-time.</p> <p>StackEdit converts your Markdown to HTML in real-time.</p>
<p>Click <icon-side-preview></icon-side-preview> to toggle the side preview.</p> <p>Click <icon-side-preview></icon-side-preview> to toggle the side preview.</p>
<div class="tour-step__button-bar"> <div class="tour-step__button-bar">
<button class="button" @click="finish">Skip</button> <button class="button" @click="finish">Skip</button>
@ -144,7 +144,7 @@ $tour-step-width: 240px;
.tour-step__inner { .tour-step__inner {
position: absolute; position: absolute;
background-color: $tour-step-background; background-color: $tour-step-background;
padding: 1em; padding: 1.5em;
font-size: 0.9em; font-size: 0.9em;
line-height: 1.33; line-height: 1.33;
width: $tour-step-width; width: $tour-step-width;
@ -213,8 +213,10 @@ $tour-step-width: 240px;
} }
.tour-step__button-bar { .tour-step__button-bar {
margin-top: 1.75em; margin-top: 1.5em;
text-align: right; display: flex;
flex-direction: row;
justify-content: flex-end;
.button { .button {
font-size: 1.1em; font-size: 1.1em;

View File

@ -7,7 +7,7 @@
</div> </div>
<div class="flex flex--column"> <div class="flex flex--column">
<div>Import Markdown</div> <div>Import Markdown</div>
<span>Open a plain text file.</span> <span>Import a plain text file.</span>
</div> </div>
</label> </label>
<input class="hidden-file" id="import-html-file-input" type="file" @change="onImportHtml"> <input class="hidden-file" id="import-html-file-input" type="file" @change="onImportHtml">

View File

@ -77,11 +77,8 @@ export default {
...utils.queryParams, ...utils.queryParams,
exportWorkspace: true, exportWorkspace: true,
}, true); }, true);
const iframeElt = utils.createHiddenIframe(url); window.location.href = url;
document.body.appendChild(iframeElt); window.location.reload(true);
setTimeout(() => {
document.body.removeChild(iframeElt);
}, 60000);
}, },
async settings() { async settings() {
try { try {

View File

@ -24,7 +24,7 @@
span { span {
display: inline-block; display: inline-block;
font-size: 0.75rem; font-size: 0.75rem;
opacity: 0.6; opacity: 0.67;
line-height: 1.3; line-height: 1.3;
.menu-entry__label { .menu-entry__label {

View File

@ -7,7 +7,8 @@
<p v-if="publishLocations.length"><b>{{currentFileName}}</b> is published to the following location(s):</p> <p v-if="publishLocations.length"><b>{{currentFileName}}</b> is published to the following location(s):</p>
<p v-else><b>{{currentFileName}}</b> is not published yet.</p> <p v-else><b>{{currentFileName}}</b> is not published yet.</p>
<div> <div>
<div class="publish-entry flex flex--row flex--align-center" v-for="location in publishLocations" :key="location.id"> <div class="publish-entry flex flex--column" v-for="location in publishLocations" :key="location.id">
<div class="publish-entry__header flex flex--row flex--align-center">
<div class="publish-entry__icon flex flex--column flex--center"> <div class="publish-entry__icon flex flex--column flex--center">
<icon-provider :provider-id="location.providerId"></icon-provider> <icon-provider :provider-id="location.providerId"></icon-provider>
</div> </div>
@ -15,14 +16,25 @@
{{location.description}} {{location.description}}
</div> </div>
<div class="publish-entry__buttons flex flex--row flex--center"> <div class="publish-entry__buttons flex flex--row flex--center">
<a class="publish-entry__button button" :href="location.url" target="_blank" v-title="'Open location'">
<icon-open-in-new></icon-open-in-new>
</a>
<button class="publish-entry__button button" @click="remove(location)" v-title="'Remove location'"> <button class="publish-entry__button button" @click="remove(location)" v-title="'Remove location'">
<icon-delete></icon-delete> <icon-delete></icon-delete>
</button> </button>
</div> </div>
</div> </div>
<div class="publish-entry__row flex flex--row flex--align-center">
<div class="publish-entry__url">
{{location.url}}
</div>
<div class="publish-entry__buttons flex flex--row flex--center" v-if="location.url">
<button class="publish-entry__button button" v-clipboard="location.url" @click="info('Location URL copied to clipboard!')" v-title="'Copy URL'">
<icon-content-copy></icon-content-copy>
</button>
<a class="publish-entry__button button" v-if="location.url" :href="location.url" target="_blank" v-title="'Open location'">
<icon-open-in-new></icon-open-in-new>
</a>
</div>
</div>
</div>
</div> </div>
<div class="modal__info" v-if="publishLocations.length"> <div class="modal__info" v-if="publishLocations.length">
<b>Tip:</b> Removing a location won't delete any file. <b>Tip:</b> Removing a location won't delete any file.
@ -35,7 +47,7 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import ModalInner from './common/ModalInner'; import ModalInner from './common/ModalInner';
export default { export default {
@ -54,6 +66,9 @@ export default {
}, },
}, },
methods: { methods: {
...mapActions('notification', [
'info',
]),
remove(location) { remove(location) {
this.$store.commit('publishLocation/deleteItem', location.id); this.$store.commit('publishLocation/deleteItem', location.id);
}, },
@ -65,40 +80,70 @@ export default {
@import '../../styles/variables.scss'; @import '../../styles/variables.scss';
.publish-entry { .publish-entry {
padding: 0.5rem 0.25rem; margin: 1.5em 0;
border-bottom: 1px solid $hr-color; height: auto;
font-size: 17px;
line-height: 1.5;
}
&:last-child { $button-size: 30px;
border-bottom: none; $small-button-size: 22px;
}
.publish-entry__header {
line-height: $button-size;
}
.publish-entry__row {
margin-top: 1px;
padding-top: 1px;
border-top: 1px solid rgba(128, 128, 128, 0.15);
line-height: $small-button-size;
} }
.publish-entry__icon { .publish-entry__icon {
height: 30px; height: 22px;
width: 30px; width: 22px;
margin-right: 0.75rem; margin-right: 0.75rem;
flex: none; flex: none;
} }
.publish-entry__description { .publish-entry__description {
opacity: 0.5;
line-height: 1.4;
font-size: 0.9em;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.publish-entry__url {
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
opacity: 0.5;
font-size: 0.67em;
} }
.publish-entry__buttons { .publish-entry__buttons {
margin-left: 0.75rem; margin-left: 0.75rem;
.publish-entry__row & {
margin-left: 0.5rem;
}
} }
.publish-entry__button { .publish-entry__button {
width: 38px; width: $button-size;
height: 38px; height: $button-size;
padding: 6px; padding: 4px;
background-color: transparent; background-color: transparent;
opacity: 0.75; opacity: 0.75;
.publish-entry__row & {
width: $small-button-size;
height: $small-button-size;
padding: 4px;
}
&:active, &:active,
&:focus, &:focus,
&:hover { &:hover {

View File

@ -32,7 +32,7 @@
</label> </label>
</div> </div>
</div> </div>
<form-entry label="Template"> <form-entry label="Template" v-if="format === 'html'">
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()"> <select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id"> <option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
{{ template.name }} {{ template.name }}
@ -84,7 +84,7 @@ export default modalTemplate({
resolve() { resolve() {
// Return new location // Return new location
const location = googleDriveProvider.makeLocation(this.config.token, this.fileId); const location = googleDriveProvider.makeLocation(this.config.token, this.fileId);
if (this.format) { if (this.format === 'html') {
location.templateId = this.selectedTemplate; location.templateId = this.selectedTemplate;
} }
this.config.resolve(location); this.config.resolve(location);

View File

@ -9,7 +9,7 @@
<input slot="field" class="textfield" type="text" v-model.trim="domain" @keydown.enter="resolve()"> <input slot="field" class="textfield" type="text" v-model.trim="domain" @keydown.enter="resolve()">
<div class="form-entry__info"> <div class="form-entry__info">
<b>Example:</b> example.wordpress.com<br> <b>Example:</b> example.wordpress.com<br>
<b>Jetpack plugin</b> is required for self-hosted sites. <b>Note:</b> Jetpack is required for self-hosted sites.
</div> </div>
</form-entry> </form-entry>
<form-entry label="Existing post ID" info="optional"> <form-entry label="Existing post ID" info="optional">

View File

@ -332,7 +332,6 @@ const localDbSvc = {
// Clean data stored in localStorage // Clean data stored in localStorage
localStorage.removeItem(`data/${id}`); localStorage.removeItem(`data/${id}`);
}); });
window.location.reload();
throw new Error('RELOAD'); throw new Error('RELOAD');
} }
@ -349,7 +348,7 @@ const localDbSvc = {
type: 'text/plain;charset=utf-8', type: 'text/plain;charset=utf-8',
}); });
FileSaver.saveAs(blob, 'StackEdit workspace.json'); FileSaver.saveAs(blob, 'StackEdit workspace.json');
return; throw new Error('RELOAD');
} }
// Watch workspace deletions and persist them as soon as possible // Watch workspace deletions and persist them as soon as possible

View File

@ -87,7 +87,7 @@ export default {
date, date,
}) { }) {
const refreshedToken = await this.refreshToken(token); const refreshedToken = await this.refreshToken(token);
await request(refreshedToken, { return request(refreshedToken, {
method: 'POST', method: 'POST',
url: `https://public-api.wordpress.com/rest/v1.2/sites/${siteId || domain}/posts/${postId || 'new'}`, url: `https://public-api.wordpress.com/rest/v1.2/sites/${siteId || domain}/posts/${postId || 'new'}`,
body: { body: {

View File

@ -122,6 +122,7 @@ export default {
}); });
// Build the tree // Build the tree
const parentsMap = {};
Object.entries(nodeMap).forEach(([, node]) => { Object.entries(nodeMap).forEach(([, node]) => {
let parentNode = nodeMap[node.item.parentId]; let parentNode = nodeMap[node.item.parentId];
if (!parentNode || !parentNode.isFolder) { if (!parentNode || !parentNode.isFolder) {
@ -129,6 +130,18 @@ export default {
return; return;
} }
parentNode = rootNode; parentNode = rootNode;
} else if (node.isFolder) {
// Detect circular reference
const parentParents = parentsMap[node.item.parentId] || {};
if (parentParents[node.item.id]) {
// Node is already a parent of its supposed parent
parentNode = rootNode;
} else {
parentsMap[node.item.id] = {
...parentParents,
[node.item.parentId]: true,
};
}
} }
if (node.isFolder) { if (node.isFolder) {
parentNode.folders.push(node); parentNode.folders.push(node);

View File

@ -83,33 +83,25 @@ const store = new Vuex.Store({
}, },
pathsByItemId: (state, getters) => { pathsByItemId: (state, getters) => {
const result = {}; const result = {};
const foldersById = state.folder.itemsById; const processNode = (node, parentPath = '') => {
const getPath = (item) => { let path = parentPath;
let itemPath = result[item.id]; if (node.item.id) {
if (!itemPath) { path += node.item.name;
if (item.parentId === 'trash') { if (node.isTrash) {
itemPath = `.stackedit-trash/${item.name}`; path = '.stackedit-trash/';
} else { } else if (node.isFolder) {
let { name } = item; path += '/';
if (foldersById[item.id]) {
name += '/';
} }
const parentFolder = foldersById[item.parentId]; result[node.item.id] = path;
if (parentFolder) {
itemPath = getPath(parentFolder) + name;
} else {
itemPath = name;
} }
if (node.isFolder) {
node.folders.forEach(child => processNode(child, path));
node.files.forEach(child => processNode(child, path));
} }
}
result[item.id] = itemPath;
return itemPath;
}; };
[ processNode(getters['explorer/rootNode']);
...getters['folder/items'],
...getters['file/items'],
].forEach(item => getPath(item));
return result; return result;
}, },
itemsByPath: (state, { allItemsById, pathsByItemId }) => { itemsByPath: (state, { allItemsById, pathsByItemId }) => {