fix(editor): placeholder titre "New Page" + aides de bloc au focus seul
FlowDeck CI / test (push) Failing after 14s
FlowDeck CI / docker (push) Skipped

- Titre : data-placeholder="New Page" + bascule .empty a l'input (mecanisme des
  blocs). Le placeholder s'affiche en gris pale quand le titre est vide, y compris
  apres effacement. save() stocke un titre vide (plus de "New page" force), la
  sidebar garde son fallback, duplicatePage retombe sur "New Page".
- Blocs : les placeholders ("Press 'space' for AI...", "Heading 1", "List"...)
  passent sous :focus-within -> ne s'affichent que quand le bloc est focalise.
  Idem pour "Type code..." du bloc code.

v5.1.6
This commit is contained in:
2026-09-07 12:25:29 -04:00
parent d96fb4171e
commit 3956f1ffa5
6 changed files with 38 additions and 11 deletions
+24 -1
View File
@@ -1,6 +1,29 @@
# Changelog — FlowDeck
## v5.1.5 (2026-09-07) — Placeholder des blocs : disparaît dès la frappe
## v5.1.6 (2026-09-07) — Placeholder titre "New Page" + placeholder blocs au focus seul
> Conforme au comportement Notion : le titre affiche « New Page » quand il est
> vide ; les aides de blocs (« Press 'space' for AI… », « Heading 1 »…) ne
> s'affichent que lorsque le bloc a le curseur.
### Titre « New Page » (document vide)
- Le placeholder du titre utilisait `:empty` avec le texte figé « New page » et
la couleur très sombre `--text-dim`. `:empty` casse dès qu'un `<br>` est
injecté dans le contenteditable.
- Ajout de `data-placeholder="New Page"` et d'une bascule de classe `.empty`
(même mécanisme que les blocs) : le placeholder s'affiche en gris pâle
(`--text-secondary`, opacité .75) dès que le titre est vide, y compris après
effacement.
- `save()` stocke désormais un titre vide (au lieu de forcer « New page ») : le
placeholder persiste après autosave ; la sidebar garde son fallback d'affichage.
- `duplicatePage()` retombe sur « New Page » si le titre est vide.
### Aides de blocs affichées seulement au focus
- Les placeholders des blocs (« Press 'space' for AI or '/' for commands »,
« Heading 1 », « List »…) s'affichaient en permanence quand le bloc était vide.
- Les règles passent sous `:focus-within` : l'aide n'apparaît que lorsque le bloc
est focalisé (curseur dans le bloc) et disparaît dès qu'on le quitte.
- Même comportement pour l'aide « Type code... » du bloc code.
> Les textes d'aide « Press 'space' for AI or '/' for commands », « Heading 1 »,
> « List », etc. restaient affichés pendant l'édition.
+1 -1
View File
@@ -1 +1 @@
5.1.5
5.1.6
+1 -1
View File
@@ -60,7 +60,7 @@ async def lifespan(_app: FastAPI):
app = FastAPI(
title="FlowDeck",
version="5.1.5",
version="5.1.6",
docs_url="/docs" if settings.log_level == "DEBUG" else None,
redoc_url=None,
lifespan=lifespan,
+1
View File
@@ -396,6 +396,7 @@
class="page-title-input"
contenteditable="true"
id="_titleEl"
data-placeholder="New Page"
@input="dirty=true;save()"
@keydown.enter.prevent="focusBlock()"
@paste.prevent="pastePlain($event)"
+4 -2
View File
@@ -700,6 +700,8 @@
window.E=this;
this.insertTable=function(){this.sync();var nb={id:genId(),type:'table',content:'',rows:_tblNew(3,3)};this.blocks.push(nb);this.dirty=true;this.autoSave();this.render();setTimeout(function(){_tblFocus(nb.id,0,0);},60);};
SM.init();_tblInitDelegate();this.render();
const tEl=document.getElementById('_titleEl');
if(tEl){const syncT=()=>{tEl.classList.toggle('empty',!(tEl.textContent||'').trim());};syncT();tEl.addEventListener('input',syncT);tEl.addEventListener('blur',syncT);}
setTimeout(()=>{this.focusBlock();const ct=document.getElementById('_blocksCt');if(!ct)return;
ct.addEventListener('keydown',e=>this.onKd(e));
ct.addEventListener('paste',e=>{e.preventDefault();document.execCommand('insertText',false,(e.clipboardData||window.clipboardData).getData('text/plain'));this.dirty=true;this.autoSave();});
@@ -977,7 +979,7 @@ applyAIBlocks(text){
doDownload();
}
},
duplicatePage(){this.moreOpen=false;const csrf=document.cookie.match(/csrf_token=([^;]+)/);fetch(`/board/api/pages?title=${encodeURIComponent(this.pageTitle+' copy')}&section=Private&project=${encodeURIComponent('{{ workspace_key }}')}`,{method:'POST',headers:{'X-CSRF-Token':csrf?csrf[1]:''}}).then(r=>r.json()).then(d=>{window.location.href=`/pages/${d.id}`;}).catch(()=>{window.showToast('Duplicate failed','error');});},
duplicatePage(){this.moreOpen=false;const csrf=document.cookie.match(/csrf_token=([^;]+)/);const t=(this.pageTitle||'').trim()||'New Page';fetch(`/board/api/pages?title=${encodeURIComponent(t+' copy')}&section=Private&project=${encodeURIComponent('{{ workspace_key }}')}`,{method:'POST',headers:{'X-CSRF-Token':csrf?csrf[1]:''}}).then(r=>r.json()).then(d=>{window.location.href=`/pages/${d.id}`;}).catch(()=>{window.showToast('Duplicate failed','error');});},
movePage(){
this.moreOpen=false;
this.moveOpen = true;
@@ -1276,7 +1278,7 @@ applyAIBlocks(text){
if(this.fileData)return;
if(this.saving){ if(cb) cb(); return; }
this.sync();
const t=document.getElementById('_titleEl');if(t)this.pageTitle=t.textContent?.trim()||'New page';
const t=document.getElementById('_titleEl');if(t)this.pageTitle=t.textContent?.trim()||'';
this.saving=true;
const csrf=document.cookie.match(/csrf_token=([^;]+)/);
fetch(`/board/api/pages/${this.pid}/blocks`,{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':csrf?csrf[1]:''},body:JSON.stringify({title:this.pageTitle,blocks:this.blocks.map(b=>{const c={id:b.id,type:b.type,content:b.content};['checked','expanded','language','icon','src','alt','style','embed_type','collection_id','dbs','file_name','file_size','file_mime','children','rows','align','has_header','colsW','meeting'].forEach(k=>{if(b[k]!=null)c[k]=b[k];});return c;})})})
+7 -6
View File
@@ -2205,9 +2205,11 @@ button.breadcrumb-current { max-width: 260px; }
border: none;
word-break: break-word;
}
.page-title-input.empty::before,
.page-title-input:empty::before {
content: 'New page';
color: var(--text-dim);
content: attr(data-placeholder);
color: var(--text-secondary);
opacity: .75;
pointer-events: none;
}
@@ -2334,8 +2336,7 @@ button.breadcrumb-current { max-width: 260px; }
min-height: 28px;
word-break: break-word;
}
.block-content [data-bid].empty::before,
.block-content.empty::before {
.block-content:focus-within [data-bid].empty::before {
content: attr(data-placeholder);
color: var(--text-secondary);
opacity: .75;
@@ -2496,9 +2497,9 @@ button.breadcrumb-current { max-width: 260px; }
display: block;
min-height: 28px;
}
.block-code code:empty::before {
.block-code:focus-within code:empty::before {
content: 'Type code...';
color: var(--text-dim);
color: var(--text-secondary);
}
/* Divider */