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 —