diff --git a/bookmarks/frontend/behaviors/search-autocomplete.js b/bookmarks/frontend/behaviors/search-autocomplete.js index 96d2c53..3259744 100644 --- a/bookmarks/frontend/behaviors/search-autocomplete.js +++ b/bookmarks/frontend/behaviors/search-autocomplete.js @@ -1,6 +1,5 @@ -import { mount } from "svelte"; import { Behavior, registerBehavior } from "./index"; -import SearchAutoCompleteComponent from "../components/SearchAutoComplete.svelte"; +import "../components/SearchAutocomplete.js"; class SearchAutocomplete extends Behavior { constructor(element) { @@ -11,26 +10,20 @@ class SearchAutocomplete extends Behavior { return; } - const container = document.createElement("div"); - - mount(SearchAutoCompleteComponent, { - target: container, - props: { - name: "q", - placeholder: input.getAttribute("placeholder") || "", - value: input.value, - linkTarget: input.dataset.linkTarget, - mode: input.dataset.mode, - search: { - user: input.dataset.user, - shared: input.dataset.shared, - unread: input.dataset.unread, - }, - }, - }); + const autocomplete = document.createElement("ld-search-autocomplete"); + autocomplete.name = "q"; + autocomplete.placeholder = input.getAttribute("placeholder") || ""; + autocomplete.value = input.value; + autocomplete.linkTarget = input.dataset.linkTarget || "_blank"; + autocomplete.mode = input.dataset.mode || ""; + autocomplete.search = { + user: input.dataset.user, + shared: input.dataset.shared, + unread: input.dataset.unread, + }; this.input = input; - this.autocomplete = container.firstElementChild; + this.autocomplete = autocomplete; input.replaceWith(this.autocomplete); } diff --git a/bookmarks/frontend/behaviors/tag-autocomplete.js b/bookmarks/frontend/behaviors/tag-autocomplete.js index 6f2afed..c1f6fe3 100644 --- a/bookmarks/frontend/behaviors/tag-autocomplete.js +++ b/bookmarks/frontend/behaviors/tag-autocomplete.js @@ -1,6 +1,5 @@ import { Behavior, registerBehavior } from "./index"; -import TagAutoCompleteComponent from "../components/TagAutocomplete.svelte"; -import { mount } from "svelte"; +import "../components/TagAutocomplete.js"; class TagAutocomplete extends Behavior { constructor(element) { @@ -11,22 +10,16 @@ class TagAutocomplete extends Behavior { return; } - const container = document.createElement("div"); - - mount(TagAutoCompleteComponent, { - target: container, - props: { - id: input.id, - name: input.name, - value: input.value, - placeholder: input.getAttribute("placeholder") || "", - ariaDescribedBy: input.getAttribute("aria-describedby") || "", - variant: input.getAttribute("variant"), - }, - }); + const autocomplete = document.createElement("ld-tag-autocomplete"); + autocomplete.id = input.id; + autocomplete.name = input.name; + autocomplete.value = input.value; + autocomplete.placeholder = input.getAttribute("placeholder") || ""; + autocomplete.ariaDescribedBy = input.getAttribute("aria-describedby") || ""; + autocomplete.variant = input.getAttribute("variant") || "default"; this.input = input; - this.autocomplete = container.firstElementChild; + this.autocomplete = autocomplete; input.replaceWith(this.autocomplete); } diff --git a/bookmarks/frontend/components/SearchAutoComplete.svelte b/bookmarks/frontend/components/SearchAutoComplete.svelte deleted file mode 100644 index a59a13f..0000000 --- a/bookmarks/frontend/components/SearchAutoComplete.svelte +++ /dev/null @@ -1,264 +0,0 @@ - - -
-
- -
- - -
- - diff --git a/bookmarks/frontend/components/SearchAutocomplete.js b/bookmarks/frontend/components/SearchAutocomplete.js new file mode 100644 index 0000000..4927bb5 --- /dev/null +++ b/bookmarks/frontend/components/SearchAutocomplete.js @@ -0,0 +1,304 @@ +import { LitElement, html } from "lit"; +import { SearchHistory } from "./SearchHistory.js"; +import { api } from "../api.js"; +import { cache } from "../cache.js"; +import { + clampText, + debounce, + getCurrentWord, + getCurrentWordBounds, +} from "../util.js"; + +export class SearchAutocomplete extends LitElement { + static properties = { + name: { type: String }, + placeholder: { type: String }, + value: { type: String }, + mode: { type: String }, + search: { type: Object }, + linkTarget: { type: String }, + isFocus: { state: true }, + isOpen: { state: true }, + suggestions: { state: true }, + selectedIndex: { state: true }, + }; + + constructor() { + super(); + this.name = ""; + this.placeholder = ""; + this.value = ""; + this.mode = ""; + this.search = {}; + this.linkTarget = "_blank"; + this.isFocus = false; + this.isOpen = false; + this.suggestions = { + recentSearches: [], + bookmarks: [], + tags: [], + total: [], + }; + this.selectedIndex = undefined; + this.input = null; + this.searchHistory = new SearchHistory(); + this.debouncedLoadSuggestions = debounce(() => this.loadSuggestions()); + } + + createRenderRoot() { + return this; + } + + firstUpdated() { + this.style.setProperty("--menu-max-height", "400px"); + this.input = this.querySelector("input"); + // Track current search query after loading the page + this.searchHistory.pushCurrent(); + this.updateSuggestions(); + } + + handleFocus() { + this.isFocus = true; + } + + handleBlur() { + this.isFocus = false; + this.close(); + } + + handleInput(e) { + this.value = e.target.value; + this.debouncedLoadSuggestions(); + } + + handleKeyDown(e) { + // Enter + if ( + this.isOpen && + this.selectedIndex !== undefined && + (e.keyCode === 13 || e.keyCode === 9) + ) { + const suggestion = this.suggestions.total[this.selectedIndex]; + if (suggestion) this.completeSuggestion(suggestion); + e.preventDefault(); + } + // Escape + if (e.keyCode === 27) { + this.close(); + e.preventDefault(); + } + // Up arrow + if (e.keyCode === 38) { + this.updateSelection(-1); + e.preventDefault(); + } + // Down arrow + if (e.keyCode === 40) { + if (!this.isOpen) { + this.loadSuggestions(); + } else { + this.updateSelection(1); + } + e.preventDefault(); + } + } + + open() { + this.isOpen = true; + } + + close() { + this.isOpen = false; + this.updateSuggestions(); + this.selectedIndex = undefined; + } + + hasSuggestions() { + return this.suggestions.total.length > 0; + } + + async loadSuggestions() { + let suggestionIndex = 0; + + function nextIndex() { + return suggestionIndex++; + } + + // Tag suggestions + const tags = await cache.getTags(); + let tagSuggestions = []; + const currentWord = getCurrentWord(this.input); + if (currentWord && currentWord.length > 1 && currentWord[0] === "#") { + const searchTag = currentWord.substring(1, currentWord.length); + tagSuggestions = (tags || []) + .filter( + (tag) => + tag.name.toLowerCase().indexOf(searchTag.toLowerCase()) === 0, + ) + .slice(0, 5) + .map((tag) => ({ + type: "tag", + index: nextIndex(), + label: `#${tag.name}`, + tagName: tag.name, + })); + } + + // Recent search suggestions + const recentSearches = this.searchHistory + .getRecentSearches(this.value, 5) + .map((value) => ({ + type: "search", + index: nextIndex(), + label: value, + value, + })); + + // Bookmark suggestions + let bookmarks = []; + + if (this.value && this.value.length >= 3) { + const path = this.mode ? `/${this.mode}` : ""; + const suggestionSearch = { + ...this.search, + q: this.value, + }; + const fetchedBookmarks = await api.listBookmarks(suggestionSearch, { + limit: 5, + offset: 0, + path, + }); + bookmarks = fetchedBookmarks.map((bookmark) => { + const fullLabel = bookmark.title || bookmark.url; + const label = clampText(fullLabel, 60); + return { + type: "bookmark", + index: nextIndex(), + label, + bookmark, + }; + }); + } + + this.updateSuggestions(recentSearches, bookmarks, tagSuggestions); + + if (this.hasSuggestions()) { + this.open(); + } else { + this.close(); + } + } + + updateSuggestions(recentSearches, bookmarks, tagSuggestions) { + recentSearches = recentSearches || []; + bookmarks = bookmarks || []; + tagSuggestions = tagSuggestions || []; + this.suggestions = { + recentSearches, + bookmarks, + tags: tagSuggestions, + total: [...tagSuggestions, ...recentSearches, ...bookmarks], + }; + } + + completeSuggestion(suggestion) { + if (suggestion.type === "search") { + this.value = suggestion.value; + this.close(); + } + if (suggestion.type === "bookmark") { + window.open(suggestion.bookmark.url, this.linkTarget); + this.close(); + } + if (suggestion.type === "tag") { + const bounds = getCurrentWordBounds(this.input); + const inputValue = this.input.value; + this.input.value = + inputValue.substring(0, bounds.start) + + `#${suggestion.tagName} ` + + inputValue.substring(bounds.end); + this.close(); + } + } + + updateSelection(dir) { + const length = this.suggestions.total.length; + + if (length === 0) return; + + if (this.selectedIndex === undefined) { + this.selectedIndex = dir > 0 ? 0 : Math.max(length - 1, 0); + return; + } + + let newIndex = this.selectedIndex + dir; + + if (newIndex < 0) newIndex = Math.max(length - 1, 0); + if (newIndex >= length) newIndex = 0; + + this.selectedIndex = newIndex; + } + + renderSuggestions(suggestions, title) { + if (suggestions.length === 0) return ""; + + return html` + + ${suggestions.map( + (suggestion) => html` + + `, + )} + `; + } + + render() { + return html` +
+
+ +
+ + +
+ `; + } +} + +customElements.define("ld-search-autocomplete", SearchAutocomplete); diff --git a/bookmarks/frontend/components/TagAutocomplete.js b/bookmarks/frontend/components/TagAutocomplete.js new file mode 100644 index 0000000..426b628 --- /dev/null +++ b/bookmarks/frontend/components/TagAutocomplete.js @@ -0,0 +1,194 @@ +import { LitElement, html } from "lit"; +import { cache } from "../cache.js"; +import { getCurrentWord, getCurrentWordBounds } from "../util.js"; + +export class TagAutocomplete extends LitElement { + static properties = { + id: { type: String }, + name: { type: String }, + value: { type: String }, + placeholder: { type: String }, + ariaDescribedBy: { type: String, attribute: "aria-described-by" }, + variant: { type: String }, + isFocus: { state: true }, + isOpen: { state: true }, + suggestions: { state: true }, + selectedIndex: { state: true }, + }; + + constructor() { + super(); + this.id = ""; + this.name = ""; + this.value = ""; + this.placeholder = ""; + this.ariaDescribedBy = ""; + this.variant = "default"; + this.isFocus = false; + this.isOpen = false; + this.suggestions = []; + this.selectedIndex = 0; + this.input = null; + this.suggestionList = null; + } + + createRenderRoot() { + return this; + } + + firstUpdated() { + this.input = this.querySelector("input"); + this.suggestionList = this.querySelector(".menu"); + } + + handleFocus() { + this.isFocus = true; + } + + handleBlur() { + this.isFocus = false; + this.close(); + } + + async handleInput(e) { + this.input = e.target; + + const tags = await cache.getTags(); + const word = getCurrentWord(this.input); + + this.suggestions = word + ? tags.filter( + (tag) => tag.name.toLowerCase().indexOf(word.toLowerCase()) === 0, + ) + : []; + + if (word && this.suggestions.length > 0) { + this.open(); + } else { + this.close(); + } + } + + handleKeyDown(e) { + if (this.isOpen && (e.keyCode === 13 || e.keyCode === 9)) { + const suggestion = this.suggestions[this.selectedIndex]; + this.complete(suggestion); + e.preventDefault(); + } + if (e.keyCode === 27) { + this.close(); + e.preventDefault(); + } + if (e.keyCode === 38) { + this.updateSelection(-1); + e.preventDefault(); + } + if (e.keyCode === 40) { + this.updateSelection(1); + e.preventDefault(); + } + } + + open() { + this.isOpen = true; + this.selectedIndex = 0; + } + + close() { + this.isOpen = false; + this.suggestions = []; + this.selectedIndex = 0; + } + + complete(suggestion) { + const bounds = getCurrentWordBounds(this.input); + const value = this.input.value; + this.input.value = + value.substring(0, bounds.start) + + suggestion.name + + " " + + value.substring(bounds.end); + this.input.dispatchEvent(new CustomEvent("change", { bubbles: true })); + + this.close(); + } + + updateSelection(dir) { + const length = this.suggestions.length; + let newIndex = this.selectedIndex + dir; + + if (newIndex < 0) newIndex = Math.max(length - 1, 0); + if (newIndex >= length) newIndex = 0; + + this.selectedIndex = newIndex; + + // Scroll to selected list item + setTimeout(() => { + if (this.suggestionList) { + const selectedListItem = + this.suggestionList.querySelector("li.selected"); + if (selectedListItem) { + selectedListItem.scrollIntoView({ block: "center" }); + } + } + }, 0); + } + + render() { + return html` +
+ +
+ + +
+ + + +
+ `; + } +} + +customElements.define("ld-tag-autocomplete", TagAutocomplete); diff --git a/bookmarks/frontend/components/TagAutocomplete.svelte b/bookmarks/frontend/components/TagAutocomplete.svelte deleted file mode 100644 index d3239d9..0000000 --- a/bookmarks/frontend/components/TagAutocomplete.svelte +++ /dev/null @@ -1,173 +0,0 @@ - - -
- -
- - -
- - - -
- - diff --git a/bookmarks/frontend/index.js b/bookmarks/frontend/index.js index 1f036bd..dfe13c7 100644 --- a/bookmarks/frontend/index.js +++ b/bookmarks/frontend/index.js @@ -11,7 +11,5 @@ import "./behaviors/global-shortcuts"; import "./behaviors/search-autocomplete"; import "./behaviors/tag-autocomplete"; -export { default as TagAutoComplete } from "./components/TagAutocomplete.svelte"; -export { default as SearchAutoComplete } from "./components/SearchAutoComplete.svelte"; export { api } from "./api"; export { cache } from "./cache"; diff --git a/bookmarks/styles/theme/autocomplete.css b/bookmarks/styles/theme/autocomplete.css index d202df8..7f72504 100644 --- a/bookmarks/styles/theme/autocomplete.css +++ b/bookmarks/styles/theme/autocomplete.css @@ -3,13 +3,14 @@ position: relative; & .form-autocomplete-input { + box-sizing: border-box; align-content: flex-start; display: flex; flex-wrap: wrap; - height: auto; - min-height: var(--unit-8); - padding: var(--unit-h); background: var(--input-bg-color); + height: var(--control-size); + min-height: var(--control-size); + padding: 0; &.is-focused { outline: var(--focus-outline); @@ -22,10 +23,11 @@ box-shadow: none; display: inline-block; flex: 1 0 auto; - height: var(--unit-6); line-height: var(--unit-4); - margin: var(--unit-h); - width: auto; + width: 100%; + height: 100%; + margin: 0; + border: none; &:focus { outline: none; @@ -33,11 +35,30 @@ } } + &.small { + .form-autocomplete-input { + height: var(--control-size-sm); + min-height: var(--control-size-sm); + } + + .form-autocomplete-input input { + padding: 0.05rem 0.3rem; + font-size: var(--font-size-sm); + } + + .menu .menu-item { + font-size: var(--font-size-sm); + } + } + & .menu { + display: none; left: 0; position: absolute; top: 100%; width: 100%; + max-height: var(--menu-max-height, 200px); + overflow: auto; & .menu-item.selected > a, & .menu-item > a:hover { @@ -54,4 +75,8 @@ font-weight: bold; } } + + & .menu.open { + display: block; + } } diff --git a/package-lock.json b/package-lock.json index edcf6cb..c54ea4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,12 +14,11 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/wasm-node": "^4.13.0", "cssnano": "^7.0.6", + "lit": "^3.3.1", "postcss": "^8.4.45", "postcss-cli": "^11.0.0", "postcss-import": "^16.1.0", - "postcss-nesting": "^13.0.0", - "rollup-plugin-svelte": "^7.2.2", - "svelte": "^5.0.0" + "postcss-nesting": "^13.0.0" }, "devDependencies": { "prettier": "^3.3.3" @@ -88,16 +87,6 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -133,6 +122,21 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz", + "integrity": "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.1.tgz", + "integrity": "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.4.0" + } + }, "node_modules/@rollup/plugin-node-resolve": { "version": "16.0.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", @@ -500,15 +504,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", - "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8.9.0" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -521,6 +516,12 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -582,24 +583,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -738,15 +721,6 @@ "node": ">=12" } }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1060,21 +1034,6 @@ "node": ">=6" } }, - "node_modules/esm-env": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "license": "MIT" - }, - "node_modules/esrap": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", - "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", @@ -1258,15 +1217,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6" - } - }, "node_modules/jsonfile": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", @@ -1291,11 +1241,36 @@ "url": "https://github.com/sponsors/antonk52" } }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "license": "MIT" + "node_modules/lit": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.1.tgz", + "integrity": "sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.1.tgz", + "integrity": "sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.4.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.1.tgz", + "integrity": "sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } }, "node_modules/lodash.memoize": { "version": "4.1.2", @@ -1309,15 +1284,6 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "license": "MIT" }, - "node_modules/magic-string": { - "version": "0.30.18", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", - "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, "node_modules/mdn-data": { "version": "2.12.2", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", @@ -2093,20 +2059,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/rollup": { "version": "4.48.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.48.0.tgz", "integrity": "sha512-BXHRqK1vyt9XVSEHZ9y7xdYtuYbwVod2mLwOMFP7t/Eqoc1pHRlG/WdV2qNeNvZHRQdLedaFycljaYYM96RqJQ==", "license": "MIT", + "optional": true, "peer": true, "dependencies": { "@types/estree": "1.0.8" @@ -2142,48 +2100,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/rollup-plugin-svelte": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-svelte/-/rollup-plugin-svelte-7.2.2.tgz", - "integrity": "sha512-hgnIblTRewaBEVQD6N0Q43o+y6q1TmDRhBjaEzQCi50bs8TXqjc+d1zFZyE8tsfgcfNHZQzclh4RxlFUB85H8Q==", - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^4.1.0", - "resolve.exports": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "rollup": ">=2.0.0", - "svelte": ">=3.5.0" - } - }, - "node_modules/rollup-plugin-svelte/node_modules/@rollup/pluginutils": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", - "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", - "license": "MIT", - "dependencies": { - "estree-walker": "^2.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/rollup-plugin-svelte/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2319,31 +2235,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svelte": { - "version": "5.38.2", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.38.2.tgz", - "integrity": "sha512-iAcp/oFAWauVSGILdD67n7DiwgLHXZzWZIdzl7araRxu72jUr7PFAo2Iie7gXt0IbnlYvhxCb9GT3ZJUquO3PA==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "@jridgewell/sourcemap-codec": "^1.5.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/estree": "^1.0.5", - "acorn": "^8.12.1", - "aria-query": "^5.3.1", - "axobject-query": "^4.1.0", - "clsx": "^2.1.1", - "esm-env": "^1.2.1", - "esrap": "^2.1.0", - "is-reference": "^3.0.3", - "locate-character": "^3.0.0", - "magic-string": "^0.30.11", - "zimmerframe": "^1.1.2" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/svgo": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", @@ -2536,12 +2427,6 @@ "engines": { "node": ">=12" } - }, - "node_modules/zimmerframe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", - "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", - "license": "MIT" } } } diff --git a/package.json b/package.json index de8b69a..3361fcb 100644 --- a/package.json +++ b/package.json @@ -27,12 +27,11 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/wasm-node": "^4.13.0", "cssnano": "^7.0.6", + "lit": "^3.3.1", "postcss": "^8.4.45", "postcss-cli": "^11.0.0", "postcss-import": "^16.1.0", - "postcss-nesting": "^13.0.0", - "rollup-plugin-svelte": "^7.2.2", - "svelte": "^5.0.0" + "postcss-nesting": "^13.0.0" }, "devDependencies": { "prettier": "^3.3.3" diff --git a/rollup.config.mjs b/rollup.config.mjs index bc445db..7394394 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,27 +1,18 @@ -import svelte from "rollup-plugin-svelte"; -import resolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; +import resolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; const production = !process.env.ROLLUP_WATCH; export default { - input: "bookmarks/frontend/index.js", + input: 'bookmarks/frontend/index.js', output: { sourcemap: true, - format: "iife", - name: "linkding", + format: 'iife', + name: 'linkding', // Generate bundle in static folder to that it is picked up by Django static files finder - file: "bookmarks/static/bundle.js", + file: 'bookmarks/static/bundle.js', }, plugins: [ - svelte({ - emitCss: false, - compilerOptions: { - // Workaround for rollup-plugin-svelte not setting the compiler option when emitCss is false - css: "injected", - }, - }), - // If you have external dependencies installed from // npm, you'll most likely need these plugins. In // some cases you'll need additional configuration —