mirror of
https://github.com/sissbruecker/linkding.git
synced 2026-03-06 01:43:12 +08:00
Compare commits
4 Commits
v1.45.0
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a37967f4a | ||
|
|
d52caefe2c | ||
|
|
c998dd35b7 | ||
|
|
397eb6d316 |
53
CHANGELOG.md
53
CHANGELOG.md
@@ -1,5 +1,58 @@
|
||||
# Changelog
|
||||
|
||||
## v1.45.0 (06/01/2026)
|
||||
|
||||
### What's Changed
|
||||
* API token management by @sissbruecker in https://github.com/sissbruecker/linkding/pull/1248
|
||||
* Add option to disable login form by @sissbruecker in https://github.com/sissbruecker/linkding/pull/1269
|
||||
* Turn scheme-less URLs into HTTPS instead of HTTP links by @Maaxxs in https://github.com/sissbruecker/linkding/pull/1225
|
||||
* Disable bulk execute button when no bookmarks selected by @emanuelebeffa in https://github.com/sissbruecker/linkding/pull/1241
|
||||
* Add option to run supervisor as main process by @sissbruecker in https://github.com/sissbruecker/linkding/pull/1270
|
||||
* Allow setting date_added and date_modified for new bookmarks through REST API by @jmason in https://github.com/sissbruecker/linkding/pull/1063
|
||||
* Download PDF instead of creating HTML snapshot if URL points at PDF by @sissbruecker in https://github.com/sissbruecker/linkding/pull/1271
|
||||
* Allow sandboxed scripts when viewing assets by @sissbruecker in https://github.com/sissbruecker/linkding/pull/1252
|
||||
* Allow viewing video assets by @sissbruecker in https://github.com/sissbruecker/linkding/pull/1259
|
||||
* Remove absolute URIs from settings page by @packrat386 in https://github.com/sissbruecker/linkding/pull/1261
|
||||
* Move tag management forms into dialogs by @sissbruecker in https://github.com/sissbruecker/linkding/pull/1253
|
||||
* Move bulk edit checkboxes into bookmark list container by @sissbruecker in https://github.com/sissbruecker/linkding/pull/1257
|
||||
* Remove registration switch by @sissbruecker in https://github.com/sissbruecker/linkding/pull/1268
|
||||
* Add linkdinger to community projects by @lmmendes in https://github.com/sissbruecker/linkding/pull/1266
|
||||
|
||||
### New Contributors
|
||||
* @packrat386 made their first contribution in https://github.com/sissbruecker/linkding/pull/1261
|
||||
* @lmmendes made their first contribution in https://github.com/sissbruecker/linkding/pull/1266
|
||||
* @Maaxxs made their first contribution in https://github.com/sissbruecker/linkding/pull/1225
|
||||
* @emanuelebeffa made their first contribution in https://github.com/sissbruecker/linkding/pull/1241
|
||||
* @jmason made their first contribution in https://github.com/sissbruecker/linkding/pull/1063
|
||||
|
||||
**Full Changelog**: https://github.com/sissbruecker/linkding/compare/v1.44.2...v1.45.0
|
||||
|
||||
---
|
||||
|
||||
## v1.44.2 (13/12/2025)
|
||||
|
||||
### What's Changed
|
||||
|
||||
> [!WARNING]
|
||||
> *This resolves a [security vulnerability](https://github.com/sissbruecker/linkding/security/advisories/GHSA-3pf9-5cjv-2w7q) in linkding. Everyone is encouraged to upgrade to the latest version as soon as possible.*
|
||||
|
||||
* Use sandbox CSP for viewing assets by @sissbruecker in https://github.com/sissbruecker/linkding/pull/1245
|
||||
* Fix devcontainer by @m3eno in https://github.com/sissbruecker/linkding/pull/1208
|
||||
* Fix tag cloud highlighting first char when tags are not grouped by @sissbruecker in https://github.com/sissbruecker/linkding/pull/1209
|
||||
* Bump supervisor to 4.3.0 to fix warning by @simonhammes in https://github.com/sissbruecker/linkding/pull/1216
|
||||
* Added Javascript client and library for Linkding REST API by @vbsampath in https://github.com/sissbruecker/linkding/pull/1195
|
||||
* Add Komrade project to community resources by @dev-inside in https://github.com/sissbruecker/linkding/pull/1236
|
||||
|
||||
### New Contributors
|
||||
* @m3eno made their first contribution in https://github.com/sissbruecker/linkding/pull/1208
|
||||
* @vbsampath made their first contribution in https://github.com/sissbruecker/linkding/pull/1195
|
||||
* @dev-inside made their first contribution in https://github.com/sissbruecker/linkding/pull/1236
|
||||
* @simonhammes made their first contribution in https://github.com/sissbruecker/linkding/pull/1216
|
||||
|
||||
**Full Changelog**: https://github.com/sissbruecker/linkding/compare/v1.44.1...v1.44.2
|
||||
|
||||
---
|
||||
|
||||
## v1.44.1 (11/10/2025)
|
||||
|
||||
### What's Changed
|
||||
|
||||
257
bookmarks/frontend/components/dev-tool.js
Normal file
257
bookmarks/frontend/components/dev-tool.js
Normal file
@@ -0,0 +1,257 @@
|
||||
import { LitElement, html, css } from "lit";
|
||||
|
||||
class DevTool extends LitElement {
|
||||
static properties = {
|
||||
profile: { type: Object, state: true },
|
||||
formAction: { type: String, attribute: "data-form-action" },
|
||||
csrfToken: { type: String, attribute: "data-csrf-token" },
|
||||
isOpen: { type: Boolean, state: true },
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
position: fixed;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.button {
|
||||
background: var(--btn-primary-bg-color);
|
||||
color: var(--btn-primary-text-color);
|
||||
border: none;
|
||||
padding: var(--unit-2);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--btn-box-shadow);
|
||||
cursor: pointer;
|
||||
height: auto;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
right: 0;
|
||||
background: var(--body-color);
|
||||
color: var(--text-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
padding: var(--unit-2);
|
||||
margin-bottom: var(--unit-2);
|
||||
min-width: 220px;
|
||||
box-shadow: var(--box-shadow-lg);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
:host([open]) .overlay {
|
||||
display: block;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0 0 var(--unit-2) 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--unit-1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
label:has(select) {
|
||||
margin-bottom: var(--unit-1);
|
||||
}
|
||||
|
||||
label:has(select) span {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: var(--unit-2) 0;
|
||||
border: none;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
`;
|
||||
|
||||
static fields = [
|
||||
{
|
||||
type: "select",
|
||||
key: "theme",
|
||||
label: "Theme",
|
||||
options: [
|
||||
{ value: "auto", label: "Auto" },
|
||||
{ value: "light", label: "Light" },
|
||||
{ value: "dark", label: "Dark" },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
key: "bookmark_date_display",
|
||||
label: "Date",
|
||||
options: [
|
||||
{ value: "relative", label: "Relative" },
|
||||
{ value: "absolute", label: "Absolute" },
|
||||
{ value: "hidden", label: "Hidden" },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
key: "bookmark_description_display",
|
||||
label: "Description",
|
||||
options: [
|
||||
{ value: "inline", label: "Inline" },
|
||||
{ value: "separate", label: "Separate" },
|
||||
],
|
||||
},
|
||||
{ type: "checkbox", key: "enable_favicons", label: "Favicons" },
|
||||
{ type: "checkbox", key: "enable_preview_images", label: "Preview images" },
|
||||
{ type: "checkbox", key: "display_url", label: "Display URL" },
|
||||
{ type: "checkbox", key: "permanent_notes", label: "Permanent notes" },
|
||||
{ type: "checkbox", key: "collapse_side_panel", label: "Collapse sidebar" },
|
||||
{ type: "checkbox", key: "sticky_pagination", label: "Sticky pagination" },
|
||||
{ type: "checkbox", key: "hide_bundles", label: "Hide bundles" },
|
||||
];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.isOpen = false;
|
||||
this.profile = {};
|
||||
this._onOutsideClick = this._onOutsideClick.bind(this);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
const profileData = document.getElementById("json_profile");
|
||||
this.profile = JSON.parse(profileData.textContent || "{}");
|
||||
document.addEventListener("click", this._onOutsideClick);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
document.removeEventListener("click", this._onOutsideClick);
|
||||
}
|
||||
|
||||
_onOutsideClick(e) {
|
||||
if (!this.contains(e.target) && this.isOpen) {
|
||||
this.isOpen = false;
|
||||
this.removeAttribute("open");
|
||||
}
|
||||
}
|
||||
|
||||
_toggle() {
|
||||
this.isOpen = !this.isOpen;
|
||||
if (this.isOpen) {
|
||||
this.setAttribute("open", "");
|
||||
} else {
|
||||
this.removeAttribute("open");
|
||||
}
|
||||
}
|
||||
|
||||
_handleChange(key, value) {
|
||||
this.profile = { ...this.profile, [key]: value };
|
||||
if (key === "theme") {
|
||||
const themeLinks = document.head.querySelectorAll('link[href*="theme"]');
|
||||
themeLinks.forEach((link) => link.remove());
|
||||
}
|
||||
this._submitForm();
|
||||
}
|
||||
|
||||
_renderField(field) {
|
||||
switch (field.type) {
|
||||
case "checkbox":
|
||||
return html`
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${this.profile[field.key] || false}
|
||||
@change=${(e) => this._handleChange(field.key, e.target.checked)}
|
||||
/>
|
||||
${field.label}
|
||||
</label>
|
||||
`;
|
||||
case "select":
|
||||
return html`
|
||||
<label>
|
||||
<span>${field.label}:</span>
|
||||
<select
|
||||
@change=${(e) => this._handleChange(field.key, e.target.value)}
|
||||
>
|
||||
${field.options.map(
|
||||
(opt) => html`
|
||||
<option
|
||||
value=${opt.value}
|
||||
?selected=${this.profile[field.key] === opt.value}
|
||||
>
|
||||
${opt.label}
|
||||
</option>
|
||||
`,
|
||||
)}
|
||||
</select>
|
||||
</label>
|
||||
`;
|
||||
case "divider":
|
||||
return html`<hr />`;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async _submitForm() {
|
||||
const formData = new FormData();
|
||||
formData.append("csrfmiddlewaretoken", this.csrfToken);
|
||||
|
||||
// Profile fields
|
||||
for (const [key, value] of Object.entries(this.profile)) {
|
||||
if (typeof value === "boolean" && value) {
|
||||
formData.append(key, "on");
|
||||
} else if (typeof value !== "boolean") {
|
||||
formData.append(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Submit button name that settings.update expects
|
||||
formData.append("update_profile", "1");
|
||||
|
||||
await fetch(this.formAction, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set("ts", Date.now().toString());
|
||||
window.history.replaceState({}, "", url);
|
||||
|
||||
Turbo.visit(url.toString());
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<button class="button" @click=${() => this._toggle()}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065"
|
||||
/>
|
||||
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="overlay">
|
||||
<h3>Dev Tools</h3>
|
||||
${DevTool.fields.map((field) => this._renderField(field))}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ld-dev-tool", DevTool);
|
||||
@@ -3,6 +3,7 @@ import "./components/bookmark-page.js";
|
||||
import "./components/clear-button.js";
|
||||
import "./components/confirm-dropdown.js";
|
||||
import "./components/details-modal.js";
|
||||
import "./components/dev-tool.js";
|
||||
import "./components/dropdown.js";
|
||||
import "./components/filter-drawer.js";
|
||||
import "./components/form.js";
|
||||
|
||||
9
bookmarks/templates/shared/dev_tool.html
Normal file
9
bookmarks/templates/shared/dev_tool.html
Normal file
@@ -0,0 +1,9 @@
|
||||
{% load shared %}
|
||||
{% if debug and request.user.is_authenticated %}
|
||||
{{ request.user.profile|model_to_dict|json_script:"json_profile" }}
|
||||
<ld-dev-tool id="dev-tool"
|
||||
data-csrf-token="{{ csrf_token }}"
|
||||
data-form-action="{% url 'linkding:settings.update' %}"
|
||||
data-turbo-permanent>
|
||||
</ld-dev-tool>
|
||||
{% endif %}
|
||||
@@ -46,5 +46,6 @@
|
||||
<div class="modals">
|
||||
{% block overlays %}{% endblock %}
|
||||
</div>
|
||||
{% include 'shared/dev_tool.html' %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,6 +5,7 @@ import markdown
|
||||
from bleach.linkifier import DEFAULT_CALLBACKS, Linker
|
||||
from bleach_allowlist import markdown_attrs, markdown_tags
|
||||
from django import template
|
||||
from django.forms.models import model_to_dict
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from bookmarks import utils
|
||||
@@ -60,6 +61,12 @@ def humanize_relative_date(value):
|
||||
return utils.humanize_relative_date(value)
|
||||
|
||||
|
||||
@register.filter(name="model_to_dict")
|
||||
def model_to_dict_filter(value):
|
||||
result = model_to_dict(value)
|
||||
return result
|
||||
|
||||
|
||||
@register.tag
|
||||
def htmlmin(parser, token):
|
||||
nodelist = parser.parse(("endhtmlmin",))
|
||||
|
||||
6
docs/package-lock.json
generated
6
docs/package-lock.json
generated
@@ -2566,9 +2566,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
|
||||
"integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz",
|
||||
"integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
|
||||
@@ -11,9 +11,7 @@ Linkding can automatically create HTML snapshots whenever a bookmark is added. T
|
||||
|
||||
The snapshots are created using [singlefile-cli](https://github.com/gildas-lormeau/single-file-cli), which effectively runs a headless Chromium instance on the server to convert the web page into a single HTML file. Linkding will also load the [uBlock Origin Lite extension](https://github.com/uBlockOrigin/uBOL-home) into Chromium to attempt to block ads and other unwanted content.
|
||||
|
||||
<!--
|
||||
When bookmarking a URL that points directly to a PDF file, linkding will download the PDF instead of creating an HTML snapshot. This happens automatically based on the content type of the URL, and the downloaded PDF will be stored as an asset alongside the bookmark, just like HTML snapshots.
|
||||
-->
|
||||
|
||||
This method is fairly easy to set up, but also has several downsides:
|
||||
- The Docker image is significantly larger than the base image, as it includes a Chromium installation.
|
||||
|
||||
@@ -3,6 +3,17 @@ import terser from '@rollup/plugin-terser';
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH;
|
||||
|
||||
// Custom plugin to exclude dev-tool.js from production builds
|
||||
const excludeDevTool = {
|
||||
name: 'exclude-dev-tool',
|
||||
load(id) {
|
||||
if (production && id.endsWith('dev-tool.js')) {
|
||||
return '';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
input: 'bookmarks/frontend/index.js',
|
||||
output: {
|
||||
@@ -13,6 +24,7 @@ export default {
|
||||
file: 'bookmarks/static/bundle.js',
|
||||
},
|
||||
plugins: [
|
||||
excludeDevTool,
|
||||
// If you have external dependencies installed from
|
||||
// npm, you'll most likely need these plugins. In
|
||||
// some cases you'll need additional configuration —
|
||||
|
||||
Reference in New Issue
Block a user