feat(layout): Board-Position per Lock-Button fixieren
Neuer Pin-Button (custom SVG, kein Emoji) im Board-Header sperrt die Position eines Boards. Bei gesperrtem Board (.board.locked): - der Drag-Handle wird per CSS ausgeblendet (Flos Wunsch: Handle weg statt nur inaktiv), - ein zweiter Guard in drag.js onDown verweigert zusaetzlich jeden Drag. Schuetzt vor versehentlichem Verschieben (ergaenzt den 3px-Bewegungs-Schwellwert). locked wird wie blurred persistiert, im Export/Import durchgereicht und mit ins Trash-Board geklont. i18n DE/EN ergaenzt.
This commit is contained in:
+25
-3
@@ -46,6 +46,13 @@ function createPlusSvg() {
|
||||
]);
|
||||
}
|
||||
|
||||
/** Erzeugt das Pin-/Reisszwecke-Icon SVG (Position fixieren) — bewusst KEIN Emoji (custom SVG). */
|
||||
function createPinSvg() {
|
||||
return svgEl('svg', { width: '11', height: '12', viewBox: '0 0 24 24', fill: 'currentColor' }, [
|
||||
svgEl('path', { d: 'M16 9V4l1 0c.55 0 1-.45 1-1s-.45-1-1-1H7c-.55 0-1 .45-1 1s.45 1 1 1l1 0v5c0 1.66-1.34 3-3 3v2h5.97v7l1 1 1-1v-7H19v-2c-1.66 0-3-1.34-3-3z' }),
|
||||
]);
|
||||
}
|
||||
|
||||
// ---- POS-MIGRATION ----
|
||||
// Boards ohne pos (Altbestand vor v2.3) aus einem Auto-Raster befuellen,
|
||||
// damit sie sich nicht alle auf (0,0) stapeln. Raster orientiert sich am
|
||||
@@ -113,7 +120,7 @@ function renderBoards() {
|
||||
|
||||
function createBoardEl(board) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'board' + (board.blurred ? ' blurred' : '');
|
||||
div.className = 'board' + (board.blurred ? ' blurred' : '') + (board.locked ? ' locked' : '');
|
||||
div.dataset.boardId = board.id;
|
||||
|
||||
// Header
|
||||
@@ -133,6 +140,11 @@ function createBoardEl(board) {
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'board-actions';
|
||||
|
||||
const btnLock = document.createElement('button');
|
||||
btnLock.className = 'board-action-btn btn-lock-board';
|
||||
btnLock.title = board.locked ? t('boards.unlock') : t('boards.lock');
|
||||
btnLock.appendChild(createPinSvg());
|
||||
|
||||
const btnBlur = document.createElement('button');
|
||||
btnBlur.className = 'board-action-btn btn-blur-board';
|
||||
btnBlur.title = board.blurred ? t('boards.unblur') : t('boards.blur');
|
||||
@@ -152,9 +164,9 @@ function createBoardEl(board) {
|
||||
}
|
||||
|
||||
if (btnDelete) {
|
||||
actions.append(btnBlur, btnRename, btnDelete);
|
||||
actions.append(btnLock, btnBlur, btnRename, btnDelete);
|
||||
} else {
|
||||
actions.append(btnBlur, btnRename);
|
||||
actions.append(btnLock, btnBlur, btnRename);
|
||||
}
|
||||
header.append(dragHandle, titleSpanHeader, actions);
|
||||
|
||||
@@ -163,6 +175,16 @@ function createBoardEl(board) {
|
||||
blurOverlay.className = 'board-blur-overlay';
|
||||
div.appendChild(blurOverlay);
|
||||
|
||||
btnLock.addEventListener('click', async e => {
|
||||
e.stopPropagation();
|
||||
// Position fixieren: blendet via .board.locked den Drag-Handle aus (CSS) und der onDown-Guard
|
||||
// in drag.js verweigert zusaetzlich den Drag. Reiner Klassen-Toggle, kein Re-Render noetig.
|
||||
board.locked = !board.locked;
|
||||
div.classList.toggle('locked', board.locked);
|
||||
btnLock.title = board.locked ? t('boards.unlock') : t('boards.lock');
|
||||
await saveBoards();
|
||||
});
|
||||
|
||||
btnBlur.addEventListener('click', async e => {
|
||||
e.stopPropagation();
|
||||
board.blurred = !board.blurred;
|
||||
|
||||
@@ -72,6 +72,7 @@ function initDataButtons() {
|
||||
id: b.id || uid(),
|
||||
title: String(b.title).slice(0, 100),
|
||||
blurred: !!b.blurred,
|
||||
locked: !!b.locked,
|
||||
bookmarks: b.bookmarks
|
||||
.filter(bm => bm && typeof bm.title === 'string' && isSafeUrl(bm.url))
|
||||
.map(bm => ({
|
||||
@@ -109,6 +110,7 @@ function initDataButtons() {
|
||||
id: e.item.id || uid(),
|
||||
title: String(e.item.title || '').slice(0, 100),
|
||||
blurred: !!e.item.blurred,
|
||||
locked: !!e.item.locked,
|
||||
// Position erhalten, damit ein wiederhergestelltes Board an seinem Platz landet.
|
||||
...(safePos(e.item.pos) ? { pos: safePos(e.item.pos) } : {}),
|
||||
bookmarks: Array.isArray(e.item.bookmarks)
|
||||
|
||||
@@ -21,6 +21,9 @@ function initBoardDragDrop() {
|
||||
handle.addEventListener('pointerdown', function onDown(e) {
|
||||
// Auf Mobil ist .board position:static (Stapel) -> kein Free-Move.
|
||||
if (getComputedStyle(boardEl).position !== 'absolute') return;
|
||||
// Gesperrtes Board (Position fixiert, LAYOUT-LOCK) nicht verschieben. Der Drag-Handle ist
|
||||
// bei .locked schon per CSS ausgeblendet; dieser Guard ist die zweite Sicherung.
|
||||
if (boardEl.classList.contains('locked')) return;
|
||||
e.preventDefault();
|
||||
handle.setPointerCapture(e.pointerId);
|
||||
// .board.dragging hebt das Board per CSS nach vorne (z-index) UND ist das Signal fuer den
|
||||
|
||||
@@ -21,6 +21,8 @@ const STRINGS = {
|
||||
'boards.drag_title': 'Board verschieben',
|
||||
'boards.blur': 'Blur (privat)',
|
||||
'boards.unblur': 'Unblur',
|
||||
'boards.lock': 'Position sperren',
|
||||
'boards.unlock': 'Position entsperren',
|
||||
'boards.rename': 'Umbenennen',
|
||||
'boards.delete': 'Löschen',
|
||||
'boards.delete_confirm': 'Board „{title}" wirklich löschen?',
|
||||
@@ -480,6 +482,8 @@ const STRINGS = {
|
||||
'boards.drag_title': 'Move board',
|
||||
'boards.blur': 'Blur (private)',
|
||||
'boards.unblur': 'Unblur',
|
||||
'boards.lock': 'Lock position',
|
||||
'boards.unlock': 'Unlock position',
|
||||
'boards.rename': 'Rename',
|
||||
'boards.delete': 'Delete',
|
||||
'boards.delete_confirm': 'Really delete board "{title}"?',
|
||||
|
||||
@@ -54,6 +54,7 @@ function getDefaultBoards() {
|
||||
{ id: uid(), title: 'Next.js Docs', url: 'https://nextjs.org/docs', desc: '' },
|
||||
],
|
||||
blurred: false,
|
||||
locked: false,
|
||||
pos: { x: 40, y: 110 }
|
||||
}
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user