Compare commits

..

10 Commits

Author SHA1 Message Date
Sascha Ißbrücker
f98c89e99d Update CHANGELOG.md 2021-01-01 13:25:11 +01:00
Sascha Ißbrücker
6addee1377 Bump version 2021-01-01 13:22:28 +01:00
Sascha Ißbrücker
16ba7f390d Improve README structure 2021-01-01 13:17:47 +01:00
ScientiaSitPotentia
64914fb0d5 Docker compose support (#54)
* added docker-compose files

* updated readme with docker-compose instructions

* updated default docker-compose data folder
2021-01-01 13:11:22 +01:00
Sascha Ißbrücker
ac0f0a7831 Update CHANGELOG.md 2020-12-31 10:02:47 +01:00
Sascha Ißbrücker
f45310fc2b Update CHANGELOG.md 2020-12-31 09:49:26 +01:00
Sascha Ißbrücker
17dccdf0e9 Bump version 2020-12-31 09:48:47 +01:00
Sascha Ißbrücker
c13b27e170 Add search autocomplete (#53)
* Implement search autocomplete for recent searches

* Implement search autocomplete for bookmarks

* Fix URL encoding of query param

* Add tag suggestions to search autocomplete

Co-authored-by: Sascha Ißbrücker <sissbruecker@lyska.io>
2020-12-31 09:47:51 +01:00
Sascha Ißbrücker
816a887d99 Update node dependencies 2020-12-31 07:34:33 +01:00
Sascha Ißbrücker
779de41b65 Implement custom netscape file parser (#51)
* Implement custom Netscape file parser (#50)

* Add environment variable to configure request timeouts (#50)

Co-authored-by: Sascha Ißbrücker <sissbruecker@lyska.io>
2020-12-31 07:02:28 +01:00
27 changed files with 858 additions and 193 deletions

3
.env.sample Normal file
View File

@@ -0,0 +1,3 @@
LD_CONTAINER_NAME=linkding
LD_HOST_PORT=9090
LD_HOST_DATA_DIR=./data

2
API.md
View File

@@ -45,6 +45,8 @@ Example response:
"url": "https://example.com",
"title": "Example title",
"description": "Example description",
"website_title": "Website title",
"website_description": "Website description",
"tag_names": [
"tag1",
"tag2"

View File

@@ -1,5 +1,15 @@
# Changelog
## v1.1.1 (01/01/2021)
- [**enhancement**] Add docker-compose support [#54](https://github.com/sissbruecker/linkding/pull/54)
---
## v1.1.0 (31/12/2020)
- [**enhancement**] Search autocomplete [#52](https://github.com/sissbruecker/linkding/issues/52)
- [**enhancement**] Improve Netscape bookmarks file parsing [#50](https://github.com/sissbruecker/linkding/issues/50)
---
## v1.0.0 (31/12/2020)
- [**bug**] Import does not import bookmark descriptions [#47](https://github.com/sissbruecker/linkding/issues/47)
- [**enhancement**] Enhancement: return to same page we were on after editing a bookmark [#26](https://github.com/sissbruecker/linkding/issues/26)

View File

@@ -28,7 +28,7 @@ docker run --name linkding -p 9090:9090 -d sissbruecker/linkding:latest
By default the application runs on port `9090`, but you can map it to a different host port by modifying the command above.
However for **production use** you also want to mount a data folder on your system, so that the applications database is not stored in the container, but on your hosts file system. This is safer in case something happens to the container and makes it easier to update the container later on, or to run backups. To do so you can use the following extended command, where you replace `{host-data-folder}` with the absolute path to a folder on your system where you want to store the data:
```
```shell
docker run --name linkding -p 9090:9090 -v {host-data-folder}:/etc/linkding/data -d sissbruecker/linkding:latest
```
@@ -40,12 +40,27 @@ If you are using a Linux system you can use the following [shell script](https:/
The script can be configured using using shell variables - for more details have a look at the script itself.
### Docker-compose setup
To install linkding using docker-compose you can use the `docker-compose.yml` file. Copy the `.env.sample` file to `.env` and set your parameters, then run:
```shell
docker-compose up -d
```
### User setup
Finally you need to create a user so that you can access the frontend. Replace the credentials in the following command and run it:
```
**Docker**
```shell
docker exec -it linkding python manage.py createsuperuser --username=joe --email=joe@example.com
```
**Docker-compose**
```shell
docker-compose exec linkding python manage.py createsuperuser --username=joe --email=joe@example.com
```
The command will prompt you for a secure password. After the command has completed you can start using the application by logging into the UI with your credentials.
### Manual setup
@@ -71,6 +86,21 @@ For automatic backups you want to backup the applications database. As described
The application provides a REST API that can be used by 3rd party applications to manage bookmarks. Check the [API docs](API.md) for further information.
## Troubleshooting
**Import fails with `502 Bad Gateway`**
The default timeout for requests is 60 seconds, after which the application server will cancel the request and return the above error.
Depending on the system that the application runs on, and the number of bookmarks that need to be imported, the import may take longer than the default 60 seconds.
To increase the timeout you can provide a custom timeout to the Docker container using the `LD_REQUEST_TIMEOUT` environment variable:
```
docker run --name linkding -p 9090:9090 -e LD_REQUEST_TIMEOUT=180 -d sissbruecker/linkding:latest
```
Note that any proxy servers that you are running in front of linkding may have their own timeout settings, which are not affected by the variable.
## Development
The application is open source, so you are free to modify or contribute. The application is built using the Django web framework. You can get started by checking out the excellent Django docs: https://docs.djangoproject.com/en/3.0/. The `bookmarks` folder contains the actual bookmark application, `siteroot` is the Django root application. Other than that the code should be self-explanatory / standard Django stuff 🙂.

View File

@@ -12,8 +12,23 @@ class TagListField(serializers.ListField):
class BookmarkSerializer(serializers.ModelSerializer):
class Meta:
model = Bookmark
fields = ['id', 'url', 'title', 'description', 'tag_names', 'date_added', 'date_modified']
read_only_fields = ['date_added', 'date_modified']
fields = [
'id',
'url',
'title',
'description',
'website_title',
'website_description',
'tag_names',
'date_added',
'date_modified'
]
read_only_fields = [
'website_title',
'website_description',
'date_added',
'date_modified'
]
# Override readonly tag_names property to allow passing a list of tag names to create/update
tag_names = TagListField()

View File

@@ -0,0 +1,274 @@
<script>
import {SearchHistory} from "./SearchHistory";
import {clampText, debounce, getCurrentWord, getCurrentWordBounds} from "./util";
const searchHistory = new SearchHistory()
export let name;
export let placeholder;
export let value;
export let tags;
export let apiClient;
let isFocus = false;
let isOpen = false;
let suggestions = []
let selectedIndex = undefined;
let input = null;
// Track current search query after loading the page
searchHistory.pushCurrent()
updateSuggestions()
function handleFocus() {
isFocus = true;
}
function handleBlur() {
isFocus = false;
close();
}
function handleInput(e) {
value = e.target.value
debouncedLoadSuggestions()
}
function handleKeyDown(e) {
// Enter
if (isOpen && selectedIndex !== undefined && (e.keyCode === 13 || e.keyCode === 9)) {
const suggestion = suggestions.total[selectedIndex];
if (suggestion) completeSuggestion(suggestion);
e.preventDefault();
}
// Escape
if (e.keyCode === 27) {
close();
e.preventDefault();
}
// Up arrow
if (e.keyCode === 38) {
updateSelection(-1);
e.preventDefault();
}
// Down arrow
if (e.keyCode === 40) {
if (!isOpen) {
loadSuggestions()
} else {
updateSelection(1);
}
e.preventDefault();
}
}
function open() {
isOpen = true;
}
function close() {
isOpen = false;
updateSuggestions()
selectedIndex = undefined
}
function hasSuggestions() {
return suggestions.total.length > 0
}
async function loadSuggestions() {
let suggestionIndex = 0
function nextIndex() {
return suggestionIndex++
}
// Tag suggestions
let tagSuggestions = []
const currentWord = getCurrentWord(input)
if (currentWord && currentWord.length > 1 && currentWord[0] === '#') {
const searchTag = currentWord.substring(1, currentWord.length)
tagSuggestions = (tags || []).filter(tagName => tagName.toLowerCase().indexOf(searchTag.toLowerCase()) === 0)
.slice(0, 5)
.map(tagName => ({
type: 'tag',
index: nextIndex(),
label: `#${tagName}`,
tagName: tagName
}))
}
// Recent search suggestions
const search = searchHistory.getRecentSearches(value, 5).map(value => ({
type: 'search',
index: nextIndex(),
label: value,
value
}))
// Bookmark suggestions
let bookmarks = []
if (value && value.length >= 3) {
const fetchedBookmarks = await apiClient.getBookmarks(value, {limit: 5, offset: 0})
bookmarks = fetchedBookmarks.map(bookmark => {
const fullLabel = bookmark.title || bookmark.website_title || bookmark.url
const label = clampText(fullLabel, 60)
return {
type: 'bookmark',
index: nextIndex(),
label,
bookmark
}
})
}
updateSuggestions(search, bookmarks, tagSuggestions)
if (hasSuggestions()) {
open()
} else {
close()
}
}
const debouncedLoadSuggestions = debounce(loadSuggestions)
function updateSuggestions(search, bookmarks, tagSuggestions) {
search = search || []
bookmarks = bookmarks || []
tagSuggestions = tagSuggestions || []
suggestions = {
search,
bookmarks,
tags: tagSuggestions,
total: [
...tagSuggestions,
...search,
...bookmarks,
]
}
}
function completeSuggestion(suggestion) {
if (suggestion.type === 'search') {
value = suggestion.value
close()
}
if (suggestion.type === 'bookmark') {
window.open(suggestion.bookmark.url, '_blank')
close()
}
if (suggestion.type === 'tag') {
const bounds = getCurrentWordBounds(input);
const inputValue = input.value;
input.value = inputValue.substring(0, bounds.start) + `#${suggestion.tagName} ` + inputValue.substring(bounds.end);
close()
}
}
function updateSelection(dir) {
const length = suggestions.total.length;
if (length === 0) return
if (selectedIndex === undefined) {
selectedIndex = dir > 0 ? 0 : Math.max(length - 1, 0)
return
}
let newIndex = selectedIndex + dir;
if (newIndex < 0) newIndex = Math.max(length - 1, 0);
if (newIndex >= length) newIndex = 0;
selectedIndex = newIndex;
}
</script>
<div class="form-autocomplete">
<div class="form-autocomplete-input" class:is-focused={isFocus}>
<input type="search" name="{name}" placeholder="{placeholder}" autocomplete="off" value="{value}"
bind:this={input}
on:input={handleInput} on:keydown={handleKeyDown} on:focus={handleFocus} on:blur={handleBlur}>
</div>
<ul class="menu" class:open={isOpen}>
{#if suggestions.tags.length > 0}
<li class="menu-item group-item">Tags</li>
{/if}
{#each suggestions.tags as suggestion}
<li class="menu-item" class:selected={selectedIndex === suggestion.index}>
<a href="#" on:mousedown|preventDefault={() => completeSuggestion(suggestion)}>
<div class="tile tile-centered">
<div class="tile-content">
{suggestion.label}
</div>
</div>
</a>
</li>
{/each}
{#if suggestions.search.length > 0}
<li class="menu-item group-item">Recent Searches</li>
{/if}
{#each suggestions.search as suggestion}
<li class="menu-item" class:selected={selectedIndex === suggestion.index}>
<a href="#" on:mousedown|preventDefault={() => completeSuggestion(suggestion)}>
<div class="tile tile-centered">
<div class="tile-content">
{suggestion.label}
</div>
</div>
</a>
</li>
{/each}
{#if suggestions.bookmarks.length > 0}
<li class="menu-item group-item">Bookmarks</li>
{/if}
{#each suggestions.bookmarks as suggestion}
<li class="menu-item" class:selected={selectedIndex === suggestion.index}>
<a href="#" on:mousedown|preventDefault={() => completeSuggestion(suggestion)}>
<div class="tile tile-centered">
<div class="tile-content">
{suggestion.label}
</div>
</div>
</a>
</li>
{/each}
</ul>
</div>
<style>
.menu {
display: none;
max-height: 400px;
overflow: auto;
}
.menu.open {
display: block;
}
.form-autocomplete-input {
padding: 0;
}
/* TODO: Should be read from theme */
.menu-item.selected > a {
background: #f1f1fc;
color: #5755d9;
}
.group-item, .group-item:hover {
color: #999999;
text-transform: uppercase;
background: none;
font-size: 0.6rem;
font-weight: bold;
}
</style>

View File

@@ -0,0 +1,48 @@
const SEARCH_HISTORY_KEY = 'searchHistory'
const MAX_ENTRIES = 30
export class SearchHistory {
getHistory() {
const historyJson = localStorage.getItem(SEARCH_HISTORY_KEY)
return historyJson ? JSON.parse(historyJson) : {
recent: []
}
}
pushCurrent() {
// Skip if browser is not compatible
if (!window.URLSearchParams) return
const urlParams = new URLSearchParams(window.location.search);
const searchParam = urlParams.get('q');
if (!searchParam) return
this.push(searchParam)
}
push(search) {
const history = this.getHistory()
history.recent.unshift(search)
// Remove duplicates and clamp to max entries
history.recent = history.recent.reduce((acc, cur) => {
if (acc.length >= MAX_ENTRIES) return acc
if (acc.indexOf(cur) >= 0) return acc
acc.push(cur)
return acc
}, [])
const newHistoryJson = JSON.stringify(history)
localStorage.setItem(SEARCH_HISTORY_KEY, newHistoryJson)
}
getRecentSearches(query, max) {
const history = this.getHistory()
return history.recent
.filter(search => !query || search.toLowerCase().indexOf(query.toLowerCase()) >= 0)
.slice(0, max)
}
}

View File

@@ -1,4 +1,6 @@
<script>
import {getCurrentWord, getCurrentWordBounds} from "./util";
export let id;
export let name;
export let value;
@@ -23,7 +25,7 @@
function handleInput(e) {
input = e.target;
const word = getCurrentWord();
const word = getCurrentWord(input);
suggestions = word ? tags.filter(tag => tag.indexOf(word) === 0) : [];
@@ -73,27 +75,6 @@
close();
}
function getCurrentWordBounds() {
const text = input.value;
const end = input.selectionStart;
let start = end;
let currentChar = text.charAt(start - 1);
while (currentChar && currentChar !== ' ' && start > 0) {
start--;
currentChar = text.charAt(start - 1);
}
return {start, end};
}
function getCurrentWord() {
const bounds = getCurrentWordBounds(input);
return input.value.substring(bounds.start, bounds.end);
}
function updateSelection(dir) {
const length = suggestions.length;

View File

@@ -0,0 +1,14 @@
export class ApiClient {
constructor(baseUrl) {
this.baseUrl = baseUrl
}
getBookmarks(query, options = {limit: 100, offset: 0}) {
const encodedQuery = encodeURIComponent(query)
const url = `${this.baseUrl}bookmarks?q=${encodedQuery}&limit=${options.limit}&offset=${options.offset}`
return fetch(url)
.then(response => response.json())
.then(data => data.results)
}
}

View File

@@ -1,6 +1,10 @@
import TagAutoComplete from './TagAutocomplete.svelte'
import SearchAutoComplete from './SearchAutoComplete.svelte'
import {ApiClient} from './api'
export default {
TagAutoComplete
ApiClient,
TagAutoComplete,
SearchAutoComplete
}

View File

@@ -0,0 +1,37 @@
export function debounce(callback, delay = 250) {
let timeoutId
return (...args) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => {
timeoutId = null
callback(...args)
}, delay)
}
}
export function clampText(text, maxChars = 30) {
if(!text || text.length <= 30) return text
return text.substr(0, maxChars) + '...'
}
export function getCurrentWordBounds(input) {
const text = input.value;
const end = input.selectionStart;
let start = end;
let currentChar = text.charAt(start - 1);
while (currentChar && currentChar !== ' ' && start > 0) {
start--;
currentChar = text.charAt(start - 1);
}
return {start, end};
}
export function getCurrentWord(input) {
const bounds = getCurrentWordBounds(input);
return input.value.substring(bounds.start, bounds.end);
}

View File

@@ -2,11 +2,10 @@ import logging
from dataclasses import dataclass
from datetime import datetime
import bs4
from bs4 import BeautifulSoup
from django.contrib.auth.models import User
from bookmarks.models import Bookmark, parse_tag_string
from bookmarks.services.parser import parse, NetscapeBookmark
from bookmarks.services.tags import get_or_create_tags
logger = logging.getLogger(__name__)
@@ -23,52 +22,41 @@ def import_netscape_html(html: str, user: User):
result = ImportResult()
try:
soup = BeautifulSoup(html, 'html.parser')
netscape_bookmarks = parse(html)
except:
logging.exception('Could not read bookmarks file.')
raise
bookmark_tags = soup.find_all('dt')
for bookmark_tag in bookmark_tags:
for netscape_bookmark in netscape_bookmarks:
result.total = result.total + 1
try:
_import_bookmark_tag(bookmark_tag, user)
_import_bookmark_tag(netscape_bookmark, user)
result.success = result.success + 1
except:
shortened_bookmark_tag_str = str(bookmark_tag)[:100] + '...'
shortened_bookmark_tag_str = str(netscape_bookmark)[:100] + '...'
logging.exception('Error importing bookmark: ' + shortened_bookmark_tag_str)
result.failed = result.failed + 1
return result
def _import_bookmark_tag(bookmark_tag: bs4.Tag, user: User):
link_tag = bookmark_tag.a
if link_tag is None:
return
def _import_bookmark_tag(netscape_bookmark: NetscapeBookmark, user: User):
# Either modify existing bookmark for the URL or create new one
url = link_tag['href']
description = _extract_description(bookmark_tag)
bookmark = _get_or_create_bookmark(url, user)
bookmark = _get_or_create_bookmark(netscape_bookmark.href, user)
bookmark.url = url
add_date = link_tag.get('add_date', datetime.now().timestamp())
bookmark.date_added = datetime.utcfromtimestamp(int(add_date)).astimezone()
bookmark.url = netscape_bookmark.href
bookmark.date_added = datetime.utcfromtimestamp(int(netscape_bookmark.date_added)).astimezone()
bookmark.date_modified = bookmark.date_added
bookmark.unread = link_tag.get('toread', '0') == '1'
bookmark.title = link_tag.string
if description:
bookmark.description = description
bookmark.unread = False
bookmark.title = netscape_bookmark.title
if netscape_bookmark.description:
bookmark.description = netscape_bookmark.description
bookmark.owner = user
bookmark.save()
# Set tags
tag_string = link_tag.get('tags', '')
tag_names = parse_tag_string(tag_string)
tag_names = parse_tag_string(netscape_bookmark.tag_string)
tags = get_or_create_tags(tag_names, user)
bookmark.tags.set(tags)
@@ -80,27 +68,3 @@ def _get_or_create_bookmark(url: str, user: User):
return Bookmark.objects.get(url=url, owner=user)
except Bookmark.DoesNotExist:
return Bookmark()
def _extract_description(bookmark_tag: bs4.Tag):
"""
Since the Netscape HTML format has no closing tags, all following bookmark tags are part of the description tag
so to extract the description text we have to get creative. For now we combine the text of all text nodes until we
detect a <dt> tag which indicates a new bookmark
:param bookmark_tag:
:return:
"""
description_tag = bookmark_tag.find('dd', recursive=False)
if description_tag is None:
return None
description = ''
for content in description_tag.contents:
if type(content) is bs4.element.Tag and content.name == 'dt':
break
if type(content) is bs4.element.NavigableString:
description += content
return description.strip()

View File

@@ -0,0 +1,73 @@
from dataclasses import dataclass
from datetime import datetime
import pyparsing as pp
@dataclass
class NetscapeBookmark:
href: str
title: str
description: str
date_added: int
tag_string: str
def extract_bookmark_link(tag):
href = tag[0].href
title = tag[0].text
tag_string = tag[0].tags
date_added_string = tag[0].add_date if tag[0].add_date else datetime.now().timestamp()
date_added = int(date_added_string)
return {
'href': href,
'title': title,
'tag_string': tag_string,
'date_added': date_added
}
def extract_bookmark(tag):
link = tag[0].link
description = tag[0].description
description = description[0] if description else ''
return {
'link': link,
'description': description,
}
def extract_description(tag):
return tag[0].strip()
# define grammar
dt_start, _ = pp.makeHTMLTags("DT")
dd_start, _ = pp.makeHTMLTags("DD")
a_start, a_end = pp.makeHTMLTags("A")
bookmark_link_tag = pp.Group(a_start + a_start.tag_body("text") + a_end.suppress())
bookmark_link_tag.addParseAction(extract_bookmark_link)
bookmark_description_tag = dd_start.suppress() + pp.SkipTo(pp.anyOpenTag | pp.anyCloseTag)("description")
bookmark_description_tag.addParseAction(extract_description)
bookmark_tag = pp.Group(dt_start + bookmark_link_tag("link") + pp.ZeroOrMore(bookmark_description_tag)("description"))
bookmark_tag.addParseAction(extract_bookmark)
def parse(html: str) -> [NetscapeBookmark]:
matches = bookmark_tag.searchString(html)
bookmarks = []
for match in matches:
bookmark_match = match[0]
bookmark = NetscapeBookmark(
href=bookmark_match['link']['href'],
title=bookmark_match['link']['title'],
description=bookmark_match['description'],
tag_string=bookmark_match['link']['tag_string'],
date_added=bookmark_match['link']['date_added'],
)
bookmarks.append(bookmark)
return bookmarks

View File

@@ -1,7 +1,12 @@
.bookmarks-page {
.search input[type=search] {
width: 200px;
width: 180px;
height: 1.8rem;
@media (min-width: $control-width-md) {
width: 300px;
}
}
}

View File

@@ -1,4 +1,5 @@
{% extends "bookmarks/layout.html" %}
{% load static %}
{% load shared %}
{% load bookmarks %}
@@ -13,7 +14,10 @@
<div class="search">
<form action="{% url 'bookmarks:index' %}" method="get">
<div class="input-group">
<input type="search" name="q" placeholder="Search for words or #tags" value="{{ query }}">
<span id="search-input-wrap">
<input type="search" name="q" placeholder="Search for words or #tags"
value="{{ query }}">
</span>
<input type="submit" value="Search" class="btn input-group-btn">
</div>
</form>
@@ -36,4 +40,24 @@
</section>
</div>
{# Replace search input with auto-complete component #}
<script src="{% static "bundle.js" %}"></script>
<script type="application/javascript">
const currentTagsString = '{{ tags_string }}';
const currentTags = currentTagsString.split(' ');
const apiClient = new linkding.ApiClient('{% url 'bookmarks:api-root' %}')
const wrapper = document.getElementById('search-input-wrap')
const newWrapper = document.createElement('div')
new linkding.SearchAutoComplete({
target: newWrapper,
props: {
name: 'q',
placeholder: 'Search for words or #tags',
value: '{{ query }}',
tags: currentTags,
apiClient
}
})
wrapper.parentElement.replaceChild(newWrapper, wrapper)
</script>
{% endblock %}

View File

@@ -22,5 +22,5 @@ urlpatterns = [
path('settings/export', views.settings.bookmark_export, name='settings.export'),
# API
path('api/check_url', views.api.check_url, name='api.check_url'),
url(r'^api/', include(router.urls))
path('api/', include(router.urls), name='api')
]

View File

@@ -22,6 +22,8 @@ def index(request):
paginator = Paginator(query_set, _default_page_size)
bookmarks = paginator.get_page(page)
tags = queries.query_tags(request.user, query_string)
tag_names = [tag.name for tag in tags]
tags_string = build_tag_string(tag_names, ' ')
return_url = generate_index_return_url(page, query_string)
if request.GET.get('tag'):
@@ -32,6 +34,7 @@ def index(request):
context = {
'bookmarks': bookmarks,
'tags': tags,
'tags_string': tags_string,
'query': query_string if query_string else '',
'empty': paginator.count == 0,
'return_url': return_url

View File

@@ -35,7 +35,7 @@ def bookmark_import(request):
return HttpResponseRedirect(reverse('bookmarks:settings.index'))
try:
content = import_file.read()
content = import_file.read().decode()
result = import_netscape_html(content, request.user)
success_msg = str(result.success) + ' bookmarks were successfully imported.'
messages.success(request, success_msg, 'bookmark_import_success')

11
docker-compose.yml Normal file
View File

@@ -0,0 +1,11 @@
version: '3'
services:
linkding:
container_name: "${LD_CONTAINER_NAME:-linkding}"
image: sissbruecker/linkding:latest
ports:
- "${LD_HOST_PORT:-9090}:9090"
volumes:
- "${LD_HOST_DATA_DIR:-./data}:/etc/linkding/data"
restart: unless-stopped

View File

@@ -19,6 +19,7 @@
<sourceFolder url="file://$MODULE_DIR$/polls" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/siteroot" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/bookmarks" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

363
package-lock.json generated
View File

@@ -1,57 +1,74 @@
{
"name": "linkdings",
"name": "linkding",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/code-frame": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
"integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
"integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
"requires": {
"@babel/highlight": "^7.0.0"
"@babel/highlight": "^7.10.4"
}
},
"@babel/helper-validator-identifier": {
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="
},
"@babel/highlight": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz",
"integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
"integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
"requires": {
"@babel/helper-validator-identifier": "^7.10.4",
"chalk": "^2.0.0",
"esutils": "^2.0.2",
"js-tokens": "^4.0.0"
}
},
"@rollup/plugin-commonjs": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.0.0.tgz",
"integrity": "sha512-jnm//T5ZWOZ6zmJ61fReSCBOif+Ax8dHVoVggA+d2NA7T4qCWgQ3KYr+zN2faGEYLpe1wa03IzvhR+sqVLxUWg==",
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-17.0.0.tgz",
"integrity": "sha512-/omBIJG1nHQc+bgkYDuLpb/V08QyutP9amOrJRUSlYJZP+b/68gM//D8sxJe3Yry2QnYIr3QjR3x4AlxJEN3GA==",
"requires": {
"@rollup/pluginutils": "^3.0.0",
"estree-walker": "^0.6.1",
"is-reference": "^1.1.2",
"magic-string": "^0.25.2",
"resolve": "^1.11.0"
"@rollup/pluginutils": "^3.1.0",
"commondir": "^1.0.1",
"estree-walker": "^2.0.1",
"glob": "^7.1.6",
"is-reference": "^1.2.1",
"magic-string": "^0.25.7",
"resolve": "^1.17.0"
}
},
"@rollup/plugin-node-resolve": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-6.0.0.tgz",
"integrity": "sha512-GqWz1CfXOsqpeVMcoM315+O7zMxpRsmhWyhJoxLFHVSp9S64/u02i7len/FnbTNbmgYs+sZyilasijH8UiuboQ==",
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.0.1.tgz",
"integrity": "sha512-ltlsj/4Bhwwhb+Nb5xCz/6vieuEj2/BAkkqVIKmZwC7pIdl8srmgmglE4S0jFlZa32K4qvdQ6NHdmpRKD/LwoQ==",
"requires": {
"@rollup/pluginutils": "^3.0.0",
"@types/resolve": "0.0.8",
"@rollup/pluginutils": "^3.1.0",
"@types/resolve": "1.17.1",
"builtin-modules": "^3.1.0",
"deepmerge": "^4.2.2",
"is-module": "^1.0.0",
"resolve": "^1.11.1"
"resolve": "^1.19.0"
}
},
"@rollup/pluginutils": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.1.tgz",
"integrity": "sha512-PmNurkecagFimv7ZdKCVOfQuqKDPkrcpLFxRBcQ00LYr4HAjJwhCFxBiY2Xoletll2htTIiXBg6g0Yg21h2M3w==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
"integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
"requires": {
"estree-walker": "^0.6.1"
"@types/estree": "0.0.39",
"estree-walker": "^1.0.1",
"picomatch": "^2.2.2"
},
"dependencies": {
"estree-walker": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="
}
}
},
"@types/estree": {
@@ -65,18 +82,13 @@
"integrity": "sha512-hx6zWtudh3Arsbl3cXay+JnkvVgCKzCWKv42C9J01N2T2np4h8w5X8u6Tpz5mj38kE3M9FM0Pazx8vKFFMnjLQ=="
},
"@types/resolve": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz",
"integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==",
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
"integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
"requires": {
"@types/node": "*"
}
},
"acorn": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz",
"integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ=="
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
@@ -85,15 +97,29 @@
"color-convert": "^1.9.0"
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
},
"builtin-modules": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz",
"integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw=="
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz",
"integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA=="
},
"chalk": {
"version": "2.4.2",
@@ -123,54 +149,129 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"commondir": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"estree-walker": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"fsevents": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
"integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
"optional": true
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
"function-bind": "^1.1.1"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"is-core-module": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz",
"integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==",
"requires": {
"has": "^1.0.3"
}
},
"is-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
"integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE="
},
"is-reference": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz",
"integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
"integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
"requires": {
"@types/estree": "0.0.39"
"@types/estree": "*"
}
},
"jest-worker": {
"version": "24.9.0",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz",
"integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==",
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
"integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==",
"requires": {
"@types/node": "*",
"merge-stream": "^2.0.0",
"supports-color": "^6.1.0"
"supports-color": "^7.0.0"
},
"dependencies": {
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"requires": {
"has-flag": "^3.0.0"
"has-flag": "^4.0.0"
}
}
}
@@ -181,9 +282,9 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"magic-string": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.4.tgz",
"integrity": "sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw==",
"version": "0.25.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
"integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
"requires": {
"sourcemap-codec": "^1.4.4"
}
@@ -193,54 +294,85 @@
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "^1.1.7"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
},
"picomatch": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg=="
},
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"requires": {
"safe-buffer": "^5.1.0"
}
},
"require-relative": {
"version": "0.8.7",
"resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
"integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4="
},
"resolve": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.1.tgz",
"integrity": "sha512-fn5Wobh4cxbLzuHaE+nphztHy43/b++4M6SsGFC2gB8uYwf0C8LcarfCz1un7UTW8OFQg9iNjZ4xpcFVGebDPg==",
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz",
"integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==",
"requires": {
"is-core-module": "^2.1.0",
"path-parse": "^1.0.6"
}
},
"rollup": {
"version": "1.27.14",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-1.27.14.tgz",
"integrity": "sha512-DuDjEyn8Y79ALYXMt+nH/EI58L5pEw5HU9K38xXdRnxQhvzUTI/nxAawhkAHUQeudANQ//8iyrhVRHJBuR6DSQ==",
"version": "2.35.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.35.1.tgz",
"integrity": "sha512-q5KxEyWpprAIcainhVy6HfRttD9kutQpHbeqDTWnqAFNJotiojetK6uqmcydNMymBEtC4I8bCYR+J3mTMqeaUA==",
"requires": {
"@types/estree": "*",
"@types/node": "*",
"acorn": "^7.1.0"
"fsevents": "~2.1.2"
}
},
"rollup-plugin-svelte": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/rollup-plugin-svelte/-/rollup-plugin-svelte-5.1.1.tgz",
"integrity": "sha512-wP3CnKHjR4fZUgNm5Iey7eItnxwnH/nAw568WJ8dpMSchBxxZ/DmKSx8e6h8k/B6SwG1wfGvWehadFJHcuFFSw==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-svelte/-/rollup-plugin-svelte-7.0.0.tgz",
"integrity": "sha512-cw4yv/5v1NQV3nPbpOJtikgkB+9mfSJaqKUdq7x5fVQJnwLtcdc2JOszBs5pBY+SemTs5pmJbdEMseEavbUtjQ==",
"requires": {
"require-relative": "^0.8.7",
"rollup-pluginutils": "^2.3.3",
"sourcemap-codec": "^1.4.4"
"rollup-pluginutils": "^2.8.2"
}
},
"rollup-plugin-terser": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.1.3.tgz",
"integrity": "sha512-FuFuXE5QUJ7snyxHLPp/0LFXJhdomKlIx/aK7Tg88Yubsx/UU/lmInoJafXJ4jwVVNcORJ1wRUC5T9cy5yk0wA==",
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz",
"integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==",
"requires": {
"@babel/code-frame": "^7.0.0",
"jest-worker": "^24.6.0",
"rollup-pluginutils": "^2.8.1",
"serialize-javascript": "^2.1.2",
"terser": "^4.1.0"
"@babel/code-frame": "^7.10.4",
"jest-worker": "^26.2.1",
"serialize-javascript": "^4.0.0",
"terser": "^5.0.0"
}
},
"rollup-pluginutils": {
@@ -249,31 +381,53 @@
"integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
"requires": {
"estree-walker": "^0.6.1"
},
"dependencies": {
"estree-walker": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="
}
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"serialize-javascript": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ=="
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
"requires": {
"randombytes": "^2.1.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
},
"source-map-support": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
"integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
"sourcemap-codec": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz",
"integrity": "sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg=="
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
},
"spectre.css": {
"version": "0.5.8",
@@ -289,19 +443,24 @@
}
},
"svelte": {
"version": "3.16.7",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.16.7.tgz",
"integrity": "sha512-egrva1UklB1n7KAv179IhDpQzMGAvubJUlOQ9PitmmZmAfrCUEgrQnx2vPxn2s+mGV3aYegXvJ/yQ35N2SfnYQ=="
"version": "3.31.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.31.0.tgz",
"integrity": "sha512-r+n8UJkDqoQm1b+3tA3Lh6mHXKpcfOSOuEuIo5gE2W9wQYi64RYX/qE6CZBDDsP/H4M+N426JwY7XGH4xASvGQ=="
},
"terser": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.4.3.tgz",
"integrity": "sha512-0ikKraVtRDKGzHrzkCv5rUNDzqlhmhowOBqC0XqUHFpW+vJ45+20/IFBcebwKfiS2Z9fJin6Eo+F1zLZsxi8RA==",
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.5.1.tgz",
"integrity": "sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ==",
"requires": {
"commander": "^2.20.0",
"source-map": "~0.6.1",
"source-map-support": "~0.5.12"
"source-map": "~0.7.2",
"source-map-support": "~0.5.19"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "linkding",
"version": "1.0.0",
"version": "1.1.1",
"description": "",
"main": "index.js",
"scripts": {
@@ -20,11 +20,11 @@
"homepage": "https://github.com/sissbruecker/linkding#readme",
"dependencies": {
"spectre.css": "^0.5.8",
"@rollup/plugin-commonjs": "^11.0.0",
"@rollup/plugin-node-resolve": "^6.0.0",
"rollup": "^1.20.0",
"rollup-plugin-svelte": "^5.0.3",
"rollup-plugin-terser": "^5.1.2",
"svelte": "^3.0.0"
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.0.1",
"rollup": "^2.35.1",
"rollup-plugin-svelte": "^7.0.0",
"rollup-plugin-terser": "^7.0.2",
"svelte": "^3.31.0"
}
}

View File

@@ -11,6 +11,7 @@ django-sass-processor==0.7.3
django-widget-tweaks==1.4.5
djangorestframework==3.11.1
idna==2.8
pyparsing==2.4.7
pytz==2019.1
rcssmin==1.0.6
requests==2.22.0

View File

@@ -13,6 +13,7 @@ django-widget-tweaks==1.4.5
djangorestframework==3.11.1
idna==2.8
libsass==0.19.2
pyparsing==2.4.7
pytz==2019.1
rcssmin==1.0.6
requests==2.22.0

View File

@@ -15,8 +15,7 @@ export default {
},
plugins: [
svelte({
// enable run-time checks when not in production
dev: !production,
emitCss: false
}),
// If you have external dependencies installed from

View File

@@ -11,3 +11,9 @@ vacuum=True
stats = 127.0.0.1:9191
uid = www-data
gid = www-data
if-env = LD_REQUEST_TIMEOUT
http-timeout = %(_)
socket-timeout = %(_)
harakiri = %(_)
endif =

View File

@@ -1 +1 @@
1.0.0
1.1.1