Button toggle.

This commit is contained in:
retoor 2025-06-25 16:59:49 +02:00
parent 4f988959ce
commit 79a4f9832c
2 changed files with 124 additions and 10 deletions

View File

@ -73,11 +73,12 @@
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.compose-wrapper textarea { resize: vertical; min-height: 60px; } .compose-wrapper textarea { resize: vertical; min-height: 60px; }
input,textarea { input,textarea,tag-input {
padding: .5rem; padding: .5rem;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 4px; border-radius: 4px;
width: 100%; width: 100%;
margin-bottom: 5px;
} }
button { button {
padding: .5rem 1rem; padding: .5rem 1rem;
@ -85,6 +86,49 @@
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
} }
/* Context Menu Styling */
.context-menu {
position: absolute;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
padding: 5px;
z-index: 1000;
display: none; /* Initially hidden */
min-width: 150px;
}
.context-menu-item {
padding: 8px 12px;
cursor: pointer;
user-select: none;
}
.context-menu-item:hover {
background-color: #f0f0f0;
}
.context-menu-item span {
display: block;
}
/* Animations */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.context-menu.show {
animation: fadeIn 0.2s ease-out;
display: block;
}
</style> </style>
</head> </head>
<body> <body>
@ -105,8 +149,69 @@
<div id="note-grid"></div> <div id="note-grid"></div>
</main> </main>
</div> </div>
<div id="context-menu" class="context-menu">
<div class="context-menu-item"><span>Edit Tag</span></div>
<div class="context-menu-item"><span>Rename Tag</span></div>
<div class="context-menu-item"><span>Delete Tag</span></div>
</div>
<script>
class ContextMenu extends HTMLElement {
constructor() {
super();
this.menu = document.createElement('div');
this.menu.classList.add('context-menu');
document.body.appendChild(this.menu);
document.body.addEventListener('contextmenu', (event) => {
event.preventDefault(); // Prevent default context menu
this.showContextMenu(event);
});
document.addEventListener('click', () => {
this.hideContextMenu();
});
}
addItem(text) {
const item = document.createElement('div');
item.classList.add('context-menu-item');
item.innerHTML = `<span>${text}</span>`;
this.menu.appendChild(item);
}
showContextMenu(event) {
let clicked = event.target.closest('.right-clickable');
if (!clicked){
clicked = event.target;
if (!clicked.classList.contains('right-clickable'))
return;
}
//Get position
const x = event.clientX;
const y = event.clientY;
// Set the clicked tag's data to a property so it's available for the menu items.
this.currentElement = clicked;
this.menu.style.left = `${x}px`;
this.menu.style.top = `${y}px`;
this.menu.classList.add('show'); // Add show class for animation
}
hideContextMenu() {
this.menu.classList.remove('show');
}
}
customElements.define('context-menu', ContextMenu);
const contextMenu = new ContextMenu();
contextMenu.addItem('Edit Tag');
</script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- <!--
@ -379,7 +484,7 @@ class NoteCard extends HTMLElement {
? `<img src="${a.url}">` ? `<img src="${a.url}">`
: `<a href="${a.url}" target="_blank">${a.url}</a>`).join('')} : `<a href="${a.url}" target="_blank">${a.url}</a>`).join('')}
</div>` : ''} </div>` : ''}
${tags.length ? `<div class="tag-list">${tags.map(t => `<span class="tag">${t}</span>`).join('')}</div>` : ''} ${tags.length ? `<div class="tag-list">${tags.map(t => `<span class="tag right-clickable" data-tag="${t}">${t}</span>`).join('')}</div>` : ''}
</div>`; </div>`;
} }
} }
@ -391,14 +496,15 @@ class NoteCompose extends HTMLElement {
super(); super();
this.noteId = null; // null = new this.noteId = null; // null = new
this.innerHTML = ` this.innerHTML = `
<form class="compose-wrapper"> <button class="compose-button">New note</button>
<form class="compose-wrapper" style="display:none;">
<input name="title" placeholder="Title" required> <input name="title" placeholder="Title" required>
<textarea name="body" placeholder="Take a note…" required></textarea> <textarea name="body" placeholder="Take a note…" required></textarea>
<input name="files" type="file" multiple> <input name="files" type="file" multiple>
<tag-input name="tags" placeholder="Tags (comma-separated)"></tag-input> <tag-input name="tags" placeholder="Tags (comma-separated)"></tag-input>
<div style="display:flex;gap:.5rem;"> <div style="display:flex;gap:.5rem;">
<button type="submit">Save</button> <button type="submit">Save</button>
<button type="button" id="cancel" style="display:none;">Cancel</button> <button type="button" class="cancel">Cancel</button>
</div> </div>
</form>`; </form>`;
} }
@ -408,18 +514,27 @@ class NoteCompose extends HTMLElement {
e.preventDefault(); e.preventDefault();
this.save(); this.save();
}); });
this.composeButton = this.querySelector('.compose-button')
this.composeButton.addEventListener('click', () => this.show());
this.querySelector('#cancel').addEventListener('click', () => this.reset()); this.querySelector('.cancel').addEventListener('click', () => this.reset());
document.addEventListener('edit-note', e => this.load(e.detail)); document.addEventListener('edit-note', e => this.load(e.detail));
} }
show(){
this.form.style.display = 'block';
this.q('title').focus();
this.composeButton.style.display = "none";
}
hide(){
this.form.style.display = 'none';
this.composeButton.style.display = "block";
}
q(s) { return this.querySelector(s); } q(s) { return this.querySelector(s); }
load(n) { /* pre-fill for edit */ load(n) { /* pre-fill for edit */
this.noteId = n.id; this.noteId = n.id;
this.q('title').value = n.title; this.q('title').value = n.title;
this.q('body').value = n.body; this.q('body').value = n.body;
this.q('tags').value = (n.tags || []).join(', '); this.q('tags').value = (n.tags || []).join(', ');
this.q('#cancel').style.display = 'inline-block'; this.show();
this.scrollIntoView({behavior:'smooth'}); this.scrollIntoView({behavior:'smooth'});
} }
async save() { async save() {
@ -446,7 +561,7 @@ class NoteCompose extends HTMLElement {
if (res.ok) { this.reset(); document.dispatchEvent(new CustomEvent('notes-changed')); } if (res.ok) { this.reset(); document.dispatchEvent(new CustomEvent('notes-changed')); }
else alert('Save failed'); else alert('Save failed');
} }
reset() { this.noteId=null; this.form.reset(); this.q('#cancel').style.display='none'; this.q('tags').value=''; } reset() { this.noteId=null; this.form.reset(); this.hide(); this.q('tags').value=''; }
q(sel){ return this.querySelector(sel.includes('#') ? sel : `[name=\"${sel}\"]`); } q(sel){ return this.querySelector(sel.includes('#') ? sel : `[name=\"${sel}\"]`); }
} }
customElements.define('note-compose', NoteCompose); customElements.define('note-compose', NoteCompose);

View File

@ -273,7 +273,6 @@ async def _upsert_note(note_id: Optional[int], payload: Dict[str, Any]):
db['notes'].update({"id": note_id, "title": title, "body": body, "updated_at": now}, ["id"]) db['notes'].update({"id": note_id, "title": title, "body": body, "updated_at": now}, ["id"])
db['attachments'].delete(note_id=note_id) db['attachments'].delete(note_id=note_id)
db['note_tags'].delete(note_id=note_id) db['note_tags'].delete(note_id=note_id)
db.query("DELETE FROM notes_fts WHERE note_id = :nid", nid=note_id)
# (Re)insert into FTS table # (Re)insert into FTS table
for t in tags: for t in tags: