From f94a33434df44997fbd7770fe6bf6f3fb5a757d9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 11:09:57 +0000 Subject: [PATCH 1/6] =?UTF-8?q?fix(notes):=20=D7=A4=D7=AA=D7=A7=20=D7=A7?= =?UTF-8?q?=D7=95=D7=91=D7=A5=20=D7=97=D7=93=D7=A9=20=D7=A9=D7=95=D7=91=20?= =?UTF-8?q?=D7=A6=D7=A3=20=D7=A2=D7=9D=20=D7=94=D7=92=D7=9C=D7=99=D7=9C?= =?UTF-8?q?=D7=94,=20=D7=95=D7=90=D7=A8=D7=91=D7=A2=D7=94=20=D7=AA=D7=99?= =?UTF-8?q?=D7=A7=D7=95=D7=A0=D7=99=20=D7=9C=D7=95=D7=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **רגרסיה במסלול הקבצים — הפיצ'ר המרכזי.** פתק חדש על קובץ לא המשיך עם המשתמש בגלילה, ורק ``pin`` ואז ``unpin`` הפכו אותו לצף. שוחזר בדפדפן: לפני: line_start=4 → mode=anchored → position=absolute → זז ‎-900px אחרי: line_start=null → mode=screen → position=fixed → זז 0px ``createNote`` הציב ``line_start`` לשורת המקור הקרובה, וזה הספיק כדי ש-``_resolveMode`` יחזיר ``anchored``. ההערה שהייתה שם כבר הצהירה "ללא עיגון כברירת מחדל" — בוטל רק העיגון לכותרות, ועיגון השורה נשאר. הסנטינל ``__floating__`` המפורש הכרחי: בלעדיו המיגרציה הרכה ב- ``_renderNote`` הייתה מעגנת את הפתק מחדש בטעינה הבאה. **ורוד = מוצמד, בשני המשטחים.** בלוח הכפתור נדלק על ``screen`` ובקובץ על ``surface`` — שני סימנים שונים לאותה משמעות באותה מערכת. **פתק צף נחסם גם הוא.** ``_clampToViewport`` חדש: פתק ``fixed`` נמדד מול אזור התצוגה, פתק ``surface`` מול המשטח. קודם רק השני נחסם, והראשון היה "נעלם וחוזר בלחיצה" — תיקון שקורה במקרה, לא התנהגות. **הנקודות נגזרות מהערכה ולא ממערכת ההפעלה.** ה-CSS השתמש ב- ``prefers-color-scheme``, בעוד שהאפליקציה בוחרת ערכה דרך ``data-theme``. כלומר הצבע נקבע לפי משהו שאין לו קשר לערכה. נמדדה ניגודיות פיקסלים על שלוש ערכות אמיתיות: בהיר 26 → 55 כהה 23 → 54 כהה-כחול 18 → 48 **השטח המת.** הגובה היה ``100vh - 130px`` — ניחוש קשיח שפספס ב-40px ויצר גלילה מיותרת. עכשיו ``--board-top`` נמדד מהמיקום בפועל, וסרגל הכלים צף מעל הלוח במקום לשבת מעליו: עליון 168 → 116 תחתון ‎-40 → +6 צדדים 12 → 8 הקישור "כל הלוחות" הפך לאייקון SVG בלי טקסט. תוך כדי נתפסו שתי טעויות שלי: רג'קס בבדיקה שקלט את ה-``transparent`` של הגרדיאנט במקום את צבע הנקודה (``color-mix`` היה תקין מלכתחילה), וכלל CSS שהניח מבנה DOM שלא קיים — הסרגל היה אח של העוטף ולא ילד שלו, ולכן ``position: absolute`` נפתר מול העמוד. שניהם התגלו רק במדידה. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019YULCppaQRPYN1RgeY6NBu --- webapp/static/css/note-boards.css | 70 +++++++++++++++++++++------ webapp/static/js/sticky-notes.js | 78 +++++++++++++++++++------------ webapp/templates/note_board.html | 30 ++++++++++-- 3 files changed, 131 insertions(+), 47 deletions(-) diff --git a/webapp/static/css/note-boards.css b/webapp/static/css/note-boards.css index 8b66f8571..579770a06 100644 --- a/webapp/static/css/note-boards.css +++ b/webapp/static/css/note-boards.css @@ -108,9 +108,38 @@ /* עמוד המשטח מנצל את כל הרוחב ורוב הגובה. ברשימת הלוחות עמודה צרה היא הנכונה, אבל משטח עבודה שמוגבל ל-1100px משאיר שוליים מתים משני הצדדים ועוד 4rem ריקים מתחתיו. */ +/* עמוד המשטח מנצל כמעט את כל המסך. נמדד לפני: שוליים 12/12 בצדדים אבל + 168px מתים למעלה, והמשטח חרג מהמסך ב-40px — כי ``100vh - 130px`` היה + ניחוש קשיח שלא תאם את המיקום בפועל. */ .boards-page.is-board-surface { max-width: none; - padding: .75rem .75rem 1rem; + padding: .4rem .5rem .5rem; +} + +/* סרגל הכלים צף **מעל** המשטח במקום לשבת מעליו. + כך הלוח מתחיל מיד מתחת לנאבבר, והשורה שתפסה 40 פיקסלים מלאים + מפסיקה לגזול שטח מהלוח עצמו. */ +.is-board-surface .board-surface-wrap { position: relative; } + +.is-board-surface .board-toolbar { + position: absolute; + inset-block-start: .5rem; + inset-inline-end: .6rem; + inset-inline-start: auto; + z-index: 5; + margin: 0; + gap: .4rem; + width: auto; + padding: .25rem .35rem; + border-radius: 10px; + background: color-mix(in srgb, var(--bg-primary, #fff) 82%, transparent); + backdrop-filter: blur(6px); + border: 1px solid var(--glass-border, rgba(0, 0, 0, .08)); +} +.is-board-surface .board-title-input { + font-size: 1rem; + padding: .1rem .4rem; + max-width: 40vw; } .board-surface-wrap { @@ -119,17 +148,26 @@ .board-surface { position: relative; /* פתק במצב surface הוא absolute יחסית לזה */ - min-height: calc(100vh - 130px); + /* ``--board-top`` נמדד ב-JS מהמיקום בפועל, במקום המספר הקשיח שפספס + ב-40px ויצר גלילה מיותרת. הערך הקשיח נשאר כנפילה חיננית בלבד. */ + min-height: calc(100dvh - var(--board-top, 130px) - .5rem); border-radius: 16px; overflow: auto; border: 1px solid var(--glass-border, rgba(0, 0, 0, .08)); - /* רשת הנקודות. השקיפות נמוכה במכוון — בשיעור כזה זה כבר לא תמונה אלא - מרקם, כמו נייר משבצות מתחת לפתק אמיתי. */ + /* רשת הנקודות. + * + * הצבע נגזר מ-``--text-primary`` של הערכה **הפעילה**, ולא משאילתת מדיה. + * הגרסה הקודמת השתמשה ב-``prefers-color-scheme``, שהיא הגדרת מערכת + * ההפעלה — בעוד שהאפליקציה בוחרת ערכה דרך ``data-theme`` על ````. + * כלומר צבע הנקודות נקבע לפי משהו שאין לו קשר לערכה שנבחרה: בערכה + * בהירה הן נעלמו לגמרי, ובכהות הן השתנו באקראי. + * + * ``color-mix`` כבר בשימוש בריפו (``codemirror-custom.css``). */ background-color: var(--bg-primary, #fff); background-image: radial-gradient( circle, - var(--board-dot, rgba(103, 126, 234, .22)) 1px, + var(--board-dot, color-mix(in srgb, var(--text-primary, #1f2933) 26%, transparent)) 1px, transparent 1px ); background-size: 24px 24px; @@ -184,12 +222,18 @@ .board-msg.is-error { background: rgba(220, 53, 69, .12); color: #b02a37; } .board-msg.is-info { background: rgba(103, 126, 234, .12); } -@media (prefers-color-scheme: dark) { - .board-surface { - background-image: radial-gradient( - circle, - var(--board-dot, rgba(255, 255, 255, .13)) 1px, - transparent 1px - ); - } +/* אין כאן שאילתת מדיה בכוונה: ``--text-primary`` כבר מתהפך עם הערכה, + ולכן הנקודות מתאימות את עצמן לכל ערכה — כולל ערכות שהמשתמש יצר. */ + +/* קישור כאייקון — בלי טקסט. שטח נגיעה מלא למרות שהאייקון קטן. */ +.board-icon-link { + display: inline-flex; align-items: center; justify-content: center; + width: 34px; height: 34px; flex: 0 0 auto; + border-radius: 8px; + border: 1px solid var(--glass-border, rgba(0, 0, 0, .12)); + background: var(--bg-secondary, #f7fafc); + color: var(--text-primary, inherit); + text-decoration: none; } +.board-icon-link:hover { background: var(--glass-hover, rgba(103, 126, 234, .12)); } +.board-icon-link:focus-visible { outline: 2px solid var(--primary, #667eea); outline-offset: 2px; } diff --git a/webapp/static/js/sticky-notes.js b/webapp/static/js/sticky-notes.js index 40b041758..e69df9372 100644 --- a/webapp/static/js/sticky-notes.js +++ b/webapp/static/js/sticky-notes.js @@ -321,21 +321,6 @@ const noteX = isMobile ? 80 : 120; const noteY = scroll.y + (isMobile ? 80 : 120); - // עיגון אוטומטי לאלמנט הקרוב עם [data-source-line] (רלוונטי ל־MD preview). - // הפתק יהפוך ל־anchored (position:absolute, doc-Y) ולכן יזוז יחד עם התוכן - // כאשר ::: details נפתח/נסגר, תמונות נטענות, וכו'. - // בלוח אין שורות מקור, ולכן החיפוש כולו מדולג — ולא "רץ ומחזיר null". - let autoAnchorLine = null; - if (!this.boardId) { - try { - const nearest = this._findNearestSourceLineElement(noteY); - if (nearest) { - const raw = parseInt(nearest.getAttribute('data-source-line'), 10); - if (Number.isFinite(raw) && raw >= 0) autoAnchorLine = raw + 1; - } - } catch(_) {} - } - const payload = this.boardId ? { content: '', position: { x: noteX, y: noteY }, @@ -349,9 +334,20 @@ position: { x: noteX, y: noteY }, size: { width: isMobile ? 200 : 260, height: isMobile ? 160 : 200 }, color: '#FFFFCC', - line_start: autoAnchorLine, - // חזרה להתנהגות הישנה: ללא עיגון לכותרות כברירת מחדל - anchor_id: '', + // **פתק חדש צף עם המשתמש.** זו זרימת העבודה: כותבים פתק תוך עיון + // בקובץ, גוללים למטה, וממשיכים לכתוב — ורק בסוף נועצים אותו במקום + // שמתאים. + // + // עד כאן ``line_start`` נקבע אוטומטית לשורת המקור הקרובה, וזה + // הספיק כדי ש-``_resolveMode`` יחזיר ``anchored`` — כלומר + // ``position: absolute``, שנשאר במקומו במסמך ולא ממשיך עם המסך. + // נמדד: פתק חדש זז ‎-900px‎ בגלילה של 900, ורק ``pin`` ואז ``unpin`` + // הפכו אותו לצף. ההערה שהייתה כאן כבר הצהירה "ללא עיגון כברירת + // מחדל" — בוטל רק העיגון לכותרות, ועיגון השורה נשאר. + line_start: null, + // הסנטינל המפורש הכרחי: בלעדיו המיגרציה הרכה ב-``_renderNote`` + // הייתה מעגנת את הפתק מחדש בטעינה הבאה ומחזירה את הבאג. + anchor_id: FLOATING_SENTINEL, anchor_text: undefined }; const resp = await fetch(this._scopeUrl, { @@ -691,6 +687,27 @@ // בלי זה החלק העליון של הפתק — שהוא גם ידית הגרירה — יכול לעלות מעל // הקצה, ואז אין שום דרך להחזיר אותו: הגרירה היא לחיצה על הכותרת, // והכותרת מחוץ למסך. + // פתק צף (``position: fixed``) נמדד מול אזור התצוגה ולא מול המשטח. + // + // בלי זה אפשר היה לגרור אותו כך שהכותרת — שהיא ידית הגרירה — יוצאת + // מחוץ למסך. הוא אמנם חזר לגבולות בלחיצה הבאה, בזכות + // ``_reflowWithinViewport`` שרץ בפוקוס, אבל "נעלם ואז חוזר" הוא לא + // התנהגות, הוא תיקון שקורה במקרה. + _clampToViewport(el, x, y){ + try { + const vp = window.visualViewport; + const vpW = Math.max(100, (vp ? vp.width : window.innerWidth) || window.innerWidth || 320); + const vpH = Math.max(100, (vp ? vp.height : window.innerHeight) || window.innerHeight || 320); + const r = el.getBoundingClientRect(); + const w = Math.round(r.width) || 260; + const h = Math.round(r.height) || 200; + return { + x: clamp(Math.round(x), 12, Math.max(12, vpW - w - 12)), + y: clamp(Math.round(y), 60, Math.max(60, vpH - h - 20)) + }; + } catch(_) { return { x, y }; } + } + _clampToSurface(el, x, y, note){ try { const parent = this.container; @@ -731,14 +748,9 @@ const active = !!isActive; pinBtn.classList.toggle('is-active', active); pinBtn.setAttribute('aria-pressed', active ? 'true' : 'false'); - if (this.boardId) { - // בלוח הכפתור מחליף בין "יושב על הלוח" (ברירת המחדל, כבוי) לבין - // "צף מול המסך" (דלוק). "נעיצה למסמך" היא מושג של קובץ ואין לה - // מובן כאן — ולכן גם אין סיבה שהכפתור יידלק כבר בפתיחה. - pinBtn.title = active ? 'החזר את הפתק ללוח' : 'הצמד את הפתק למסך'; - } else { - pinBtn.title = active ? 'בטל נעיצה' : 'נעץ פתק למסמך'; - } + // אותה משמעות, אותו צבע, בשני המשטחים + const anchor = this.boardId ? 'ללוח' : 'למסמך'; + pinBtn.title = active ? `הפתק מוצמד ${anchor} — לחצו כדי לשחרר` : `הצמידו את הפתק ${anchor}`; } // ----- צ'קבוקסים ----- @@ -1041,9 +1053,10 @@ this._reflowWithinViewport(el); } } - // בלוח הכפתור מציין "צף מול המסך", ולא "מוצמד". בלי ההיפוך הזה - // הוא היה נדלק כבר בפתיחת הפתק — כי surface הוא ברירת המחדל. - this._updatePinButtonState(el, this.boardId ? (resolved === 'screen') : isPinned); + // **ורוד = מוצמד למשטח שמתחתיו**, בקובץ ובלוח כאחד. בקובץ זה + // המסמך, בלוח זה הלוח. שני סימנים שונים לאותה משמעות באותה + // מערכת הם באג בפני עצמו. + this._updatePinButtonState(el, isPinned); } catch(e) { console.warn('sticky note: applyPositionMode failed', e); } @@ -1199,7 +1212,12 @@ // חסימה כבר בזמן הגרירה, ולא רק בשמירה: אחרת אפשר לגרור את // הכותרת אל מעל קצה המשטח, ומשם אין דרך חזרה — הכותרת *היא* // ידית הגרירה. - if (this.boardId && el.classList && el.classList.contains('is-pinned')) { + // שני המצבים נחסמים — כל אחד מול מסגרת הייחוס שלו. + const floating = el.classList && el.classList.contains('is-floating'); + if (floating) { + const c = this._clampToViewport(el, nx, ny); + nx = c.x; ny = c.y; + } else if (this.boardId && el.classList && el.classList.contains('is-pinned')) { const c = this._clampToSurface(el, nx, ny, null); nx = c.x; ny = c.y; } diff --git a/webapp/templates/note_board.html b/webapp/templates/note_board.html index 7b49f531b..3b4a8c66a 100644 --- a/webapp/templates/note_board.html +++ b/webapp/templates/note_board.html @@ -9,14 +9,22 @@ {% block content %}
-
+