frontend for bookmark menu

This commit is contained in:
2026-05-30 01:56:25 +03:00
parent 07623b294e
commit 8c87489f70
11 changed files with 256 additions and 11 deletions

View File

@@ -629,9 +629,20 @@ form.full-width {
display: flex;
flex-direction: column;
align-items: start;
&> textarea, &> select, &> input[type="text"], &> input[type="password"] {
gap: var(--small-padding);
&> textarea, &> select, &> input[type="text"], &> input[type="password"], &> .inline-group {
width: 100%;
}
&> .inline-group {
display: flex;
flex-direction: row;
gap: var(--base-padding);
&> label {
width: 100%;
}
}
}
.context-explain {
@@ -678,6 +689,42 @@ details.separated {
justify-content: center;
}
#bookmark-popover {
position: absolute;
min-width: 400px;
max-width: 400px;
max-height: 500px;
margin-block: var(--small-padding);
margin-inline: 0;
padding-inline: var(--medium-padding);
overflow: scroll;
.bookmark-menu-header {
display: flex;
justify-content: space-between;
}
.bookmark-menu-inner .errors.hidden {
display: none;
}
}
.bookmark-menu-item {
padding-block: var(--medium-padding);
padding-inline: var(--base-padding);
&:has(.bookmark-menu-label:hover, input:hover) {
background-color: #0001;
}
.bookmark-menu-label {
display: flex;
flex-direction: column;
}
}
/* babycode tags */
.inline-code {
background-color: var(--code-bg-color);

View File

@@ -14,7 +14,7 @@ export function enhance(_, __, el) {
if (el.disabled) {
el.disabled = false;
if (el.title.search('JavaScript') !== -1) {
el.title = '';
el.removeAttribute('title');
}
}
}

View File

@@ -1,6 +1,9 @@
export const b = {
babycodePreviewEndpoint: '/api/babycode-preview/',
bookmarksCollectionEndpoint: '/hyperapi/bookmarks/dropdown/',
init: 'babycodeEditorCharCountInit localizeTimestamps',
bookmarkState: {},
}
const getThreadId = () => {
@@ -11,6 +14,22 @@ const getThreadId = () => {
return parseInt(scheme[2]);
}
async function getHTML(endpoint, options = {}) {
let query = {};
if (options._query !== undefined) {
query = options._query;
delete options._query;
}
const params = new URLSearchParams(query);
const res = await fetch(`${endpoint}?${params}`, options);
if (!res.ok) {
console.error(res);
}
return { body: await res.text(), status: res.status };
}
export function setTab(_, sender, el) {
if (sender.ariaSelected === 'true') {
return;
@@ -223,3 +242,101 @@ export function localizeTimestamps(_, __, el) {
const d = new Date(el.dateTime);
el.innerText = d.toLocaleString();
}
export async function showBookmarkMenu(ev, sender, el) {
if (b.bookmarkState.state === undefined) {
el.addEventListener('toggle', e => {
if (e.newState === 'closed') {
b.bookmarkState.state = 'closed';
}
});
}
// dismiss if open and last invoker is the same button that opened it
if (b.bookmarkState.state === 'open' && b.bookmarkState.invoker === sender) {
el.hidePopover();
return;
}
b.bookmarkState.invoker = sender;
b.bookmarkState.state = 'open';
b.send({ 'plain': 'Loading…' }, 'fillBookmarkMenu');
el.showPopover();
const bRect = sender.getBoundingClientRect();
const menuRect = el.getBoundingClientRect();
const preferredLeft = bRect.right - menuRect.width;
const enoughSpace = preferredLeft >= 0;
const scrollY = window.scrollY;
if (enoughSpace) {
el.style.left = `${preferredLeft}px`;
} else {
el.style.left = `${bRect.left}px`;
}
el.style.top = `${bRect.bottom + scrollY}px`;
const conceptKind = sender.prop('conceptKind');
const conceptId = sender.prop('conceptId');
const bookmarkCollections = await getHTML(b.bookmarksCollectionEndpoint, {
_query: {
concept_kind: conceptKind,
concept_id: conceptId,
}
});
b.send({ 'html': bookmarkCollections.body }, 'fillBookmarkMenu');
}
export function fillBookmarkMenu(payload, __, el) {
if (payload.plain) {
el.innerText = payload.plain;
return;
}
el.innerHTML = payload.html;
}
export async function bookmarkMenuSubmit(ev, _, el) {
ev.preventDefault();
const url = el.action;
const body = new URLSearchParams(new FormData(el));
const options = { body: body, method: 'POST' };
const status = (await getHTML(url, options)).status;
if (status !== 204) {
b.trigger('bookmarkMenuShowError');
return;
}
b.trigger('bookmarkMenuShowSavedButton');
}
export function bookmarkMenuShowSavedButton(_, __, el) {
el.value = 'Saved!';
}
export function bookmarkMenuResetSavedButton(_, __, el) {
el.value = 'Save';
}
export function bookmarkMenuShowError(_, __, el) {
if (el === undefined) {
return;
}
if (el.classList.contains('hidden')) {
el.classList.remove('hidden');
setTimeout(() => { b.trigger('bookmarkMenuHideError') }, 4000);
}
}
export function bookmarkMenuHideError(_, __, el) {
if (el === undefined) {
return;
}
if (!el.classList.contains('hidden')) {
el.classList.add('hidden');
}
}