Centralize handling of form elements state handling.
This makes it easier to maintain state changes. Seems to handle all states well now
This commit is contained in:
parent
85014d2789
commit
c6dd37ed9e
3 changed files with 66 additions and 20 deletions
|
@ -59,7 +59,13 @@ function confirmDiscard() {
|
||||||
return popup(instantiate("confirm-discard"));
|
return popup(instantiate("confirm-discard"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasBeenOpen = false;
|
const state = {
|
||||||
|
hasBeenOpen: false,
|
||||||
|
saving: false,
|
||||||
|
editing: function () { return document.querySelector(".container").classList.contains('edit'); },
|
||||||
|
hasCancelUrl: function () { return document.querySelector("a.button-cancel").getAttribute('href') !== ""; }
|
||||||
|
};
|
||||||
|
|
||||||
function openEditor() {
|
function openEditor() {
|
||||||
const bodyElement = document.querySelector("body");
|
const bodyElement = document.querySelector("body");
|
||||||
const container = document.querySelector(".container");
|
const container = document.querySelector(".container");
|
||||||
|
@ -69,6 +75,7 @@ function openEditor() {
|
||||||
const shadow = editor.querySelector('textarea.shadow-control');
|
const shadow = editor.querySelector('textarea.shadow-control');
|
||||||
const form = document.getElementById('article-editor');
|
const form = document.getElementById('article-editor');
|
||||||
const cancel = form.querySelector('.cancel');
|
const cancel = form.querySelector('.cancel');
|
||||||
|
const cancelInteractionGroup = form.querySelector(".cancel-interaction-group");
|
||||||
|
|
||||||
const footer = document.querySelector("footer");
|
const footer = document.querySelector("footer");
|
||||||
const lastUpdated = footer.querySelector(".last-updated");
|
const lastUpdated = footer.querySelector(".last-updated");
|
||||||
|
@ -76,22 +83,40 @@ function openEditor() {
|
||||||
textarea.style.height = rendered.clientHeight + "px";
|
textarea.style.height = rendered.clientHeight + "px";
|
||||||
|
|
||||||
container.classList.add('edit');
|
container.classList.add('edit');
|
||||||
|
updateFormEnabledState();
|
||||||
|
|
||||||
autosizeTextarea(textarea, shadow);
|
autosizeTextarea(textarea, shadow);
|
||||||
|
|
||||||
textarea.setSelectionRange(textarea.value.length, textarea.value.length);
|
textarea.setSelectionRange(textarea.value.length, textarea.value.length);
|
||||||
textarea.focus();
|
textarea.focus();
|
||||||
|
|
||||||
if (hasBeenOpen) return;
|
if (state.hasBeenOpen) return;
|
||||||
hasBeenOpen = true;
|
state.hasBeenOpen = true;
|
||||||
|
|
||||||
textarea.addEventListener('input', () => autosizeTextarea(textarea, shadow));
|
textarea.addEventListener('input', () => autosizeTextarea(textarea, shadow));
|
||||||
window.addEventListener('resize', () => autosizeTextarea(textarea, shadow));
|
window.addEventListener('resize', () => autosizeTextarea(textarea, shadow));
|
||||||
|
|
||||||
|
function updateFormEnabledState() {
|
||||||
|
const baseEnabled = !state.saving && state.editing();
|
||||||
|
const enabled = {
|
||||||
|
cancel: baseEnabled && state.hasCancelUrl(),
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelInteractionGroup.classList.remove(!enabled.cancel ? "interaction-group--root--enabled" : "interaction-group--root--disabled");
|
||||||
|
cancelInteractionGroup.classList.add(enabled.cancel ? "interaction-group--root--enabled" : "interaction-group--root--disabled");
|
||||||
|
|
||||||
|
for (const el of form.elements) {
|
||||||
|
el.disabled = !baseEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: edit-link in footer?
|
||||||
|
}
|
||||||
|
|
||||||
function doSave() {
|
function doSave() {
|
||||||
|
state.saving = true;
|
||||||
|
updateFormEnabledState();
|
||||||
|
|
||||||
const body = queryArgsFromForm(form);
|
const body = queryArgsFromForm(form);
|
||||||
textarea.disabled = true;
|
|
||||||
// TODO Disable other interaction as well: title editor, cancel and OK buttons
|
|
||||||
|
|
||||||
fetch(
|
fetch(
|
||||||
form.getAttribute("action"),
|
form.getAttribute("action"),
|
||||||
|
@ -111,7 +136,8 @@ function openEditor() {
|
||||||
if (probablyLoginRedirect) {
|
if (probablyLoginRedirect) {
|
||||||
return loginDialog(response.url)
|
return loginDialog(response.url)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
textarea.disabled = false;
|
state.saving = false;
|
||||||
|
updateFormEnabledState();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,9 +145,10 @@ function openEditor() {
|
||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
.then(result => {
|
.then(result => {
|
||||||
// Update url-bar, page title and footer
|
// Update url-bar, page title, footer and cancel link
|
||||||
window.history.replaceState(null, result.title, result.slug == "" ? "." : result.slug);
|
const url = result.slug == "" ? "." : result.slug;
|
||||||
// TODO Cancel-link URL should be updated to new slug
|
window.history.replaceState(null, result.title, url);
|
||||||
|
cancel.setAttribute("href", url);
|
||||||
document.querySelector("title").textContent = result.title;
|
document.querySelector("title").textContent = result.title;
|
||||||
lastUpdated.innerHTML = result.last_updated;
|
lastUpdated.innerHTML = result.last_updated;
|
||||||
lastUpdated.classList.remove("missing");
|
lastUpdated.classList.remove("missing");
|
||||||
|
@ -144,9 +171,11 @@ function openEditor() {
|
||||||
|
|
||||||
if (!result.conflict) {
|
if (!result.conflict) {
|
||||||
container.classList.remove('edit');
|
container.classList.remove('edit');
|
||||||
|
document.activeElement && document.activeElement.blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea.disabled = false;
|
state.saving = false;
|
||||||
|
updateFormEnabledState();
|
||||||
autosizeTextarea(textarea, shadow);
|
autosizeTextarea(textarea, shadow);
|
||||||
|
|
||||||
if (result.conflict) {
|
if (result.conflict) {
|
||||||
|
@ -156,7 +185,8 @@ function openEditor() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
textarea.disabled = false;
|
state.saving = false;
|
||||||
|
updateFormEnabledState();
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return alertAsync(err.toString());
|
return alertAsync(err.toString());
|
||||||
});
|
});
|
||||||
|
@ -167,6 +197,8 @@ function openEditor() {
|
||||||
.then(doReset => {
|
.then(doReset => {
|
||||||
if (doReset) {
|
if (doReset) {
|
||||||
container.classList.remove('edit');
|
container.classList.remove('edit');
|
||||||
|
document.activeElement && document.activeElement.blur();
|
||||||
|
updateFormEnabledState();
|
||||||
form.reset();
|
form.reset();
|
||||||
|
|
||||||
let selectedTheme = form.querySelector(`.theme-picker--option[checked]`).value;
|
let selectedTheme = form.querySelector(`.theme-picker--option[checked]`).value;
|
||||||
|
@ -197,8 +229,7 @@ function openEditor() {
|
||||||
document.addEventListener("keypress", function (ev) {
|
document.addEventListener("keypress", function (ev) {
|
||||||
const accel = ev.ctrlKey || ev.metaKey; // Imprecise, but works cross platform
|
const accel = ev.ctrlKey || ev.metaKey; // Imprecise, but works cross platform
|
||||||
if (ev.key === "Enter" && accel) {
|
if (ev.key === "Enter" && accel) {
|
||||||
const isEditing = container.classList.contains('edit');
|
if (!state.editing()) return;
|
||||||
if (!isEditing) return;
|
|
||||||
|
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
|
@ -414,6 +414,14 @@ h1>input {
|
||||||
--button-alt: var(--theme-input);
|
--button-alt: var(--theme-input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.interaction-group--root--enabled .interaction-group--disabled {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-group--root--disabled .interaction-group--enabled {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 960px) {
|
@media (min-width: 960px) {
|
||||||
/* min-width is calculated like this:
|
/* min-width is calculated like this:
|
||||||
|
|
||||||
|
|
|
@ -31,13 +31,20 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="editor-controls">
|
<div class="editor-controls">
|
||||||
{{#cancel_url}}
|
{{#edit?}}
|
||||||
<a class="button button-cancel cancel" href="{{.}}">Cancel</a>
|
<div class="cancel-interaction-group {{#cancel_url}}interaction-group--root--enabled{{/cancel_url}}{{^cancel_url}}interaction-group--root--disabled{{/cancel_url}}">
|
||||||
{{/cancel_url}}
|
<a class="interaction-group--enabled button button-cancel cancel" href="{{#cancel_url}}{{.}}{{/cancel_url}}">Cancel</a>
|
||||||
{{^cancel_url}}
|
<button class="interaction-group--disabled button button-cancel" disabled>Cancel</a>
|
||||||
<button class="button button-cancel cancel" disabled>Cancel</a>
|
</div>
|
||||||
{{/cancel_url}}
|
<button class="button button-default" type=submit {{^edit?}}disabled{{/edit}}>Save</button>
|
||||||
<button class="button button-default" type=submit>Save</button>
|
{{/edit}}
|
||||||
|
{{^edit?}}
|
||||||
|
<div class="cancel-interaction-group interaction-group--root--disabled">
|
||||||
|
<a class="interaction-group--enabled button button-cancel cancel" href="{{#cancel_url}}{{.}}{{/cancel_url}}">Cancel</a>
|
||||||
|
<button class="interaction-group--disabled button button-cancel" disabled>Cancel</a>
|
||||||
|
</div>
|
||||||
|
<button class="button button-default" type=submit disabled>Save</button>
|
||||||
|
{{/edit}}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue