fix(editor-v2): eliminate first-paint flicker
Three changes that together stop the page from briefly painting the
raw textareas before cm6 takes over:
1. base.html gains a {% block extra_head %}{% endblock %} hook.
2. blueprint_detail.html and overlay_detail.html include
_editor_assets.html via that extra_head block instead of inside
{% block content %}. Editor CSS now loads from <head>, so the
textarea pre-hide rule (added below) applies before first paint;
the defer'd scripts also download in parallel with HTML parse,
which is the better default anyway.
3. editor.css adds
textarea[data-editor-language] { display: none; }
so opt-in textareas are hidden from the very first paint.
editor.js + _editor_assets.html cover the three paths the pre-hide
must not break:
- bundle didn't load: top-of-IIFE bails early and un-hides every
matching textarea via style.display = "revert".
- per-textarea mount throws: init()'s catch un-hides that specific
textarea so the form stays usable.
- JS disabled entirely: _editor_assets.html ships a <noscript>
<style> override that un-hides via display: revert.
Fast suite + e2e suite both still green (676 + 3 pass, 0 fail).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
704e4cdfd1
commit
fd0d96b349
6 changed files with 37 additions and 5 deletions
|
|
@ -3,6 +3,12 @@
|
|||
* in tokens.css; this file scopes the editor container's chrome to
|
||||
* match the rest of the app. */
|
||||
|
||||
/* Pre-hide opt-in textareas to avoid the textarea-visible-then-hidden
|
||||
* flicker on page load. editor.js un-hides them again if the bundle
|
||||
* fails to load or a per-textarea mount throws. The <noscript> rule
|
||||
* in _editor_assets.html un-hides for JS-disabled users. */
|
||||
textarea[data-editor-language] { display: none; }
|
||||
|
||||
.cm-editor {
|
||||
border: var(--line);
|
||||
border-radius: var(--radius-s);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,23 @@
|
|||
// a named alias for the files-editor modal.
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// editor.css pre-hides every textarea[data-editor-language] so the
|
||||
// page never paints the raw textarea before cm6 takes over. Both
|
||||
// failure paths below restore the textarea by clearing the CSS rule
|
||||
// with an inline display:revert.
|
||||
function unhideTextarea(ta) {
|
||||
ta.style.display = "revert";
|
||||
}
|
||||
|
||||
if (!window.__editor || typeof window.__editor.mount !== "function") {
|
||||
return; // bundle didn't load — graceful no-JS fallback
|
||||
// Bundle didn't load — un-hide every editor textarea so the form
|
||||
// is still usable. Mirrors the <noscript> override for the
|
||||
// JS-disabled case.
|
||||
for (const ta of document.querySelectorAll("textarea[data-editor-language]")) {
|
||||
unhideTextarea(ta);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let vocabPromise = null;
|
||||
|
|
@ -72,7 +87,10 @@
|
|||
|
||||
function init() {
|
||||
for (const ta of document.querySelectorAll("textarea[data-editor-language]")) {
|
||||
mountOne(ta).catch(err => console.error("[editor] mount failed", err));
|
||||
mountOne(ta).catch(err => {
|
||||
console.error("[editor] mount failed", err);
|
||||
unhideTextarea(ta); // restore the form-usable raw textarea
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
{# Editor assets — include on any page that mounts a <textarea data-editor-language>. #}
|
||||
{# Editor assets — included from {% block extra_head %} on any page that
|
||||
mounts a <textarea data-editor-language>. Loading from <head> means the
|
||||
CSS pre-hide rule in editor.css applies before first paint (no textarea
|
||||
flicker), and the defer'd scripts download in parallel with HTML parse
|
||||
and execute before DOMContentLoaded. #}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='vendor/editor.bundle.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/editor.css') }}">
|
||||
<noscript><style>textarea[data-editor-language] { display: revert; }</style></noscript>
|
||||
<script src="{{ url_for('static', filename='vendor/editor.bundle.js') }}" nonce="{{ g.csp_nonce }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/editor.js') }}" nonce="{{ g.csp_nonce }}" defer></script>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
<link rel="stylesheet" href="{{ url_for('static', filename='css/layout.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/components.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/logs.css') }}">
|
||||
{% block extra_head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
{% block title %}Blueprint {{ blueprint.name }} | left4me{% endblock %}
|
||||
|
||||
{% block extra_head %}{% include "_editor_assets.html" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="panel">
|
||||
<div class="page-heading">
|
||||
|
|
@ -92,5 +94,4 @@
|
|||
</div>
|
||||
</dialog>
|
||||
<script src="{{ url_for('static', filename='js/blueprint-overlay-picker.js') }}" defer></script>
|
||||
{% include "_editor_assets.html" %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
{% block title %}Overlay {{ overlay.name }} | left4me{% endblock %}
|
||||
|
||||
{% block extra_head %}{% include "_editor_assets.html" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% set can_edit = g.user.admin or (overlay.type in ['workshop', 'script', 'files'] and overlay.user_id == g.user.id) %}
|
||||
{% set is_files_overlay = overlay.type == 'files' %}
|
||||
|
|
@ -282,5 +284,4 @@
|
|||
|
||||
<script src="{{ url_for('static', filename='js/files-overlay.js') }}" defer></script>
|
||||
{% endif %}
|
||||
{% include "_editor_assets.html" %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
Loading…
Reference in a new issue