Skip to content

Show page titles in page selection dropdown #2433

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions backend/btrixcloud/colls.py
Original file line number Diff line number Diff line change
@@ -341,9 +341,11 @@ async def get_collection_out(
result = await self.get_collection_raw(coll_id, public_or_unlisted_only)

if resources:
result["resources"], crawl_ids, pages_optimized = (
await self.get_collection_crawl_resources(coll_id)
)
(
result["resources"],
crawl_ids,
pages_optimized,
) = await self.get_collection_crawl_resources(coll_id)

initial_pages, _ = await self.page_ops.list_pages(
crawl_ids=crawl_ids,
@@ -965,9 +967,11 @@ async def get_collection_all(org: Organization = Depends(org_viewer_dep)):
try:
all_collections, _ = await colls.list_collections(org, page_size=10_000)
for collection in all_collections:
results[collection.name], _, _ = (
await colls.get_collection_crawl_resources(collection.id)
)
(
results[collection.name],
_,
_,
) = await colls.get_collection_crawl_resources(collection.id)
except Exception as exc:
# pylint: disable=raise-missing-from
raise HTTPException(
1 change: 1 addition & 0 deletions backend/btrixcloud/models.py
Original file line number Diff line number Diff line change
@@ -1376,6 +1376,7 @@ class PageUrlCount(BaseModel):
"""Model for counting pages by URL"""

url: AnyHttpUrl
title: Optional[str] = None
count: int = 0
snapshots: List[PageIdTimestamp] = []

26 changes: 21 additions & 5 deletions frontend/src/components/ui/combobox.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { consume } from "@lit/context";
import type { SlMenu, SlMenuItem, SlPopup } from "@shoelace-style/shoelace";
import { css, html, LitElement, type PropertyValues } from "lit";
import { css, html, type PropertyValues } from "lit";
import {
customElement,
property,
@@ -8,6 +9,8 @@ import {
state,
} from "lit/decorators.js";

import { TailwindElement } from "@/classes/TailwindElement";
import { popupBoundary } from "@/context/popup-boundary";
import { dropdown } from "@/utils/css";

/**
@@ -20,7 +23,7 @@ import { dropdown } from "@/utils/css";
* @event request-close
*/
@customElement("btrix-combobox")
export class Combobox extends LitElement {
export class Combobox extends TailwindElement {
static styles = [
dropdown,
css`
@@ -34,6 +37,13 @@ export class Combobox extends LitElement {
@property({ type: Boolean })
open = false;

@property({ type: Boolean })
loading = false;

@consume({ context: popupBoundary })
@state()
autoSizeBoundary?: Element | Element[] | undefined;

@state()
isActive = true;

@@ -69,22 +79,25 @@ export class Combobox extends LitElement {
}

render() {
console.log(this.autoSizeBoundary);
return html`
<sl-popup
placement="bottom-start"
shift
strategy="fixed"
autoSize="both"
.autoSizeBoundary=${this.autoSizeBoundary}
?active=${this.isActive}
@keydown=${this.onKeydown}
@keyup=${this.onKeyup}
@focusout=${this.onFocusout}
>
<div slot="anchor">
<div slot="anchor" class="relative z-20">
<slot></slot>
</div>
<div
id="dropdown"
class="dropdown hidden"
class="dropdown z-10 -mt-2 hidden"
@animationend=${(e: AnimationEvent) => {
const el = e.target as HTMLDivElement;
if (e.animationName === "dropdownShow") {
@@ -97,7 +110,10 @@ export class Combobox extends LitElement {
}
}}
>
<sl-menu role="listbox">
<sl-menu role="listbox" class="border-t-0 pt-4">
<!-- <div class="fixed inset-0 bg-neutral-50 opacity-25">
<sl-spinner></sl-spinner>
</div> -->
<slot name="menu-item"></slot>
</sl-menu>
</div>
1 change: 1 addition & 0 deletions frontend/src/components/ui/index.ts
Original file line number Diff line number Diff line change
@@ -39,3 +39,4 @@ import("./tag-input");
import("./tag");
import("./time-input");
import("./user-language-select");
import("./menu-item-without-focus");
22 changes: 22 additions & 0 deletions frontend/src/components/ui/menu-item-without-focus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/** A version of <sl-menu-item> that doesn't steal focus on mouseover */

import { SlMenuItem } from "@shoelace-style/shoelace";
import { customElement } from "lit/decorators.js";

@customElement("btrix-menu-item")
// @ts-expect-error this shouldn't be allowed, but idk of an easier way without
// forking the whole component
export class BtrixMenuItem extends SlMenuItem {
private readonly handleMouseOver = (event: MouseEvent) => {
// NOT doing this.focus();
event.stopPropagation();
};

connectedCallback() {
super.connectedCallback();
}

disconnectedCallback() {
super.disconnectedCallback();
}
}
8 changes: 8 additions & 0 deletions frontend/src/context/popup-boundary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createContext } from "@lit/context";

/**
* Boundary for custom <sl-popup> instances to use, e.g. when inside a dialog
*/
export const popupBoundary = createContext<Element | Element[] | undefined>(
"popup-boundary",
);
6 changes: 6 additions & 0 deletions frontend/src/features/collections/collection-edit-dialog.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { provide } from "@lit/context";
import { localized, msg, str } from "@lit/localize";
import { Task, TaskStatus } from "@lit/task";
import { type SlRequestCloseEvent } from "@shoelace-style/shoelace";
@@ -22,6 +23,7 @@ import { type SelectCollectionPage } from "./select-collection-page";
import { BtrixElement } from "@/classes/BtrixElement";
import type { Dialog } from "@/components/ui/dialog";
import { type TabGroupPanel } from "@/components/ui/tab-group/tab-panel";
import { popupBoundary } from "@/context/popup-boundary";
import {
type Collection,
type CollectionThumbnailSource,
@@ -118,6 +120,9 @@ export class CollectionEdit extends BtrixElement {
@query("btrix-collection-snapshot-preview")
public readonly thumbnailPreview?: CollectionSnapshotPreview | null;

@provide({ context: popupBoundary })
private popupBoundary: Element | Element[] | undefined;

protected willUpdate(changedProperties: PropertyValues<this>): void {
if (changedProperties.has("collectionId") && this.collectionId) {
void this.fetchCollection(this.collectionId);
@@ -135,6 +140,7 @@ export class CollectionEdit extends BtrixElement {
null;
this.selectedSnapshot = this.collection?.thumbnailSource ?? null;
}
this.popupBoundary = this.dialog;
}

readonly checkChanged = checkChanged.bind(this);
Original file line number Diff line number Diff line change
@@ -100,7 +100,7 @@ export default function renderPresentation(this: CollectionEdit) {
</sl-tooltip>`
: this.thumbnailPreview?.blobTask.status === TaskStatus.PENDING &&
!this.blobIsLoaded
? html`<sl-spinner slot="prefix"></sl-spinner>`
? html`<sl-spinner class="size-4" slot="prefix"></sl-spinner>`
: nothing}
</btrix-select-collection-page>
<sl-checkbox
46 changes: 32 additions & 14 deletions frontend/src/features/collections/select-collection-page.ts
Original file line number Diff line number Diff line change
@@ -22,6 +22,8 @@ import type { APIPaginationQuery } from "@/types/api";
import type { Collection } from "@/types/collection";
import type { UnderlyingFunction } from "@/types/utils";
import { tw } from "@/utils/tailwind";
import { timeoutCache } from "@/utils/timeoutCache";
import { cached } from "@/utils/weakCache";

type Snapshot = {
pageId: string;
@@ -31,6 +33,7 @@ type Snapshot = {

type Page = {
url: string;
title?: string;
count: number;
snapshots: Snapshot[];
};
@@ -179,17 +182,20 @@ export class SelectCollectionPage extends BtrixElement {
}

private readonly searchResults = new Task(this, {
task: async ([searchValue], { signal }) => {
const pageUrls = await this.getPageUrls(
{
id: this.collectionId!,
urlPrefix: searchValue,
},
signal,
);
task: cached(
async ([searchValue], { signal }) => {
const pageUrls = await this.getPageUrls(
{
id: this.collectionId!,
urlPrefix: searchValue,
},
signal,
);

return pageUrls;
},
return pageUrls;
},
{ cacheConstructor: timeoutCache(300) },
),
args: () => [this.searchQuery] as const,
});

@@ -365,19 +371,21 @@ export class SelectCollectionPage extends BtrixElement {
this.renderItems(
// Render previous value so that dropdown doesn't shift while typing
this.searchResults.value,
true,
),
complete: this.renderItems,
});
}

private readonly renderItems = (
results: SelectCollectionPage["searchResults"]["value"],
loading = false,
) => {
if (!results) return;

const { items } = results;

if (!items.length) {
if (!loading && !items.length) {
return html`
<sl-menu-item slot="menu-item" disabled>
${msg("No matching page found.")}
@@ -388,7 +396,7 @@ export class SelectCollectionPage extends BtrixElement {
return html`
${items.map((item: Page) => {
return html`
<sl-menu-item
<btrix-menu-item
slot="menu-item"
@click=${async () => {
if (this.input) {
@@ -401,8 +409,18 @@ export class SelectCollectionPage extends BtrixElement {

this.selectedSnapshot = this.selectedPage.snapshots[0];
}}
>${item.url}
</sl-menu-item>
>${item.title
? html`<div class="text-sm font-semibold">${item.title}</div>
<div class="text-xs leading-4 text-blue-600">
${item.url}
</div>`
: html`<div class="text-sm font-semibold opacity-50">
${msg("No page title")}
</div>
<div class="text-xs leading-4 text-blue-600">
${item.url}
</div>`}
</btrix-menu-item>
`;
})}
`;
13 changes: 6 additions & 7 deletions frontend/src/utils/css.ts
Original file line number Diff line number Diff line change
@@ -102,7 +102,6 @@ export const animatePulse = css`
export const dropdown = css`
.dropdown {
contain: content;
transform-origin: top left;
box-shadow: var(--sl-shadow-medium);
}

@@ -111,34 +110,34 @@ export const dropdown = css`
}

.animateShow {
animation: dropdownShow 100ms ease forwards;
animation: dropdownShow 150ms cubic-bezier(0, 0, 0.2, 1) forwards;
}

.animateHide {
animation: dropdownHide 100ms ease forwards;
animation: dropdownHide 150ms cubic-bezier(0.4, 0, 1, 1) forwards;
}

@keyframes dropdownShow {
from {
opacity: 0;
transform: scale(0.9);
transform: translateY(-8px);
}

to {
opacity: 1;
transform: scale(1);
transform: translateY(0);
}
}

@keyframes dropdownHide {
from {
opacity: 1;
transform: scale(1);
transform: translateY(0);
}

to {
opacity: 0;
transform: scale(0.9);
transform: translateY(-8px);
}
}
`;