Implement tag auto-completion
This commit is contained in:
parent
9ff8356a4d
commit
70b66122c8
3
.gitignore
vendored
3
.gitignore
vendored
@ -222,7 +222,8 @@ typings/
|
||||
.next
|
||||
|
||||
### Custom
|
||||
|
||||
# Rollup compilation output
|
||||
/build
|
||||
# Collected static files for deployment
|
||||
/static
|
||||
# Build output, etc.
|
||||
|
@ -102,6 +102,10 @@ Create a user for the frontend:
|
||||
```
|
||||
python3 manage.py createsuperuser --username=joe --email=joe@example.com
|
||||
```
|
||||
Start the Node.js development server (used for compiling JavaScript components like tag auto-completion) with:
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
Start the Django development server with:
|
||||
```
|
||||
python3 manage.py runserver
|
||||
|
155
bookmarks/components/TagAutocomplete.svelte
Normal file
155
bookmarks/components/TagAutocomplete.svelte
Normal file
@ -0,0 +1,155 @@
|
||||
<script>
|
||||
export let id;
|
||||
export let name;
|
||||
export let value;
|
||||
export let tags;
|
||||
|
||||
let isFocus = false;
|
||||
let isOpen = false;
|
||||
let input = null;
|
||||
|
||||
let suggestions = [];
|
||||
let selectedIndex = 0;
|
||||
|
||||
function handleFocus() {
|
||||
isFocus = true;
|
||||
}
|
||||
|
||||
function handleBlur() {
|
||||
isFocus = false;
|
||||
close();
|
||||
}
|
||||
|
||||
function handleInput(e) {
|
||||
input = e.target;
|
||||
const word = getCurrentWord(e.target);
|
||||
|
||||
if (!word) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
open(word);
|
||||
}
|
||||
|
||||
function handleKeyDown(e) {
|
||||
if (isOpen && (e.keyCode === 13 || e.keyCode === 9)) {
|
||||
const suggestion = suggestions[selectedIndex];
|
||||
complete(suggestion);
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.keyCode === 27) {
|
||||
close();
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.keyCode === 38) {
|
||||
updateSelection(-1);
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.keyCode === 40) {
|
||||
updateSelection(1);
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function open(word) {
|
||||
isOpen = true;
|
||||
updateSuggestions(word);
|
||||
selectedIndex = 0;
|
||||
}
|
||||
|
||||
function close() {
|
||||
isOpen = false;
|
||||
suggestions = [];
|
||||
selectedIndex = 0;
|
||||
}
|
||||
|
||||
function complete(suggestion) {
|
||||
const bounds = getCurrentWordBounds(input);
|
||||
const value = input.value;
|
||||
input.value = value.substring(0, bounds.start) + suggestion + value.substring(bounds.end);
|
||||
|
||||
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 updateSuggestions(word) {
|
||||
suggestions = tags.filter(tag => tag.indexOf(word) === 0);
|
||||
}
|
||||
|
||||
function updateSelection(dir) {
|
||||
|
||||
const length = suggestions.length;
|
||||
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">
|
||||
<!-- autocomplete input container -->
|
||||
<div class="form-autocomplete-input form-input" class:is-focused={isFocus}>
|
||||
<!-- autocomplete real input box -->
|
||||
<input id="{id}" name="{name}" value="{value ||''}"
|
||||
class="form-input" type="text" autocomplete="off"
|
||||
on:input={handleInput} on:keydown={handleKeyDown}
|
||||
on:focus={handleFocus} on:blur={handleBlur}>
|
||||
</div>
|
||||
|
||||
<!-- autocomplete suggestion list -->
|
||||
<ul class="menu" class:open={isOpen && suggestions.length > 0}>
|
||||
<!-- menu list items -->
|
||||
{#each suggestions as tag,i}
|
||||
<li class="menu-item" class:selected={selectedIndex === i}>
|
||||
<a href="#" on:mousedown|preventDefault={() => complete(tag)}>
|
||||
<div class="tile tile-centered">
|
||||
<div class="tile-content">
|
||||
{tag}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.menu {
|
||||
display: none;
|
||||
max-height: 200px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.menu.open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* TODO: Should be read from theme */
|
||||
.menu-item.selected > a {
|
||||
background: #f1f1fc;
|
||||
color: #5755d9;
|
||||
}
|
||||
</style>
|
6
bookmarks/components/index.js
Normal file
6
bookmarks/components/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
import TagAutoComplete from './TagAutocomplete.svelte'
|
||||
|
||||
export default {
|
||||
TagAutoComplete
|
||||
}
|
||||
|
@ -78,6 +78,10 @@ def query_tags(user: User, query_string: str):
|
||||
return query_set.distinct()
|
||||
|
||||
|
||||
def get_user_tags(user: User):
|
||||
return Tag.objects.filter(owner=user).all()
|
||||
|
||||
|
||||
def _parse_query_string(query_string):
|
||||
# Sanitize query params
|
||||
if not query_string:
|
||||
|
@ -10,6 +10,7 @@ $alternative-color-dark: darken($alternative-color, 5%);
|
||||
|
||||
// Import Spectre CSS lib
|
||||
@import "../../node_modules/spectre.css/src/spectre";
|
||||
@import "../../node_modules/spectre.css/src/autocomplete";
|
||||
// Import Spectre icons
|
||||
@import "../../node_modules/spectre.css/src/icons/icons-core";
|
||||
@import "../../node_modules/spectre.css/src/icons/icons-navigation";
|
||||
|
@ -8,7 +8,7 @@
|
||||
<h2>Edit bookmark</h2>
|
||||
</div>
|
||||
<form action="{% url 'bookmarks:edit' bookmark_id %}" method="post" class="col-6 col-md-12" novalidate>
|
||||
{% bookmark_form form %}
|
||||
{% bookmark_form form all_tags %}
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% load widget_tweaks %}
|
||||
{% load static %}
|
||||
|
||||
<div class="bookmarks-form">
|
||||
{% csrf_token %}
|
||||
@ -13,7 +14,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="{{ form.title.id_for_label }}" class="form-label">Tags</label>
|
||||
<label for="{{ form.tag_string.id_for_label }}" class="form-label">Tags</label>
|
||||
{{ form.tag_string|add_class:"form-input" }}
|
||||
<div class="form-input-hint">
|
||||
Enter any number of tags separated by space and <strong>without</strong> the hash (#). If a tag does not
|
||||
@ -54,6 +55,26 @@
|
||||
<a href="{% url 'bookmarks:index' %}" class="btn">Nevermind</a>
|
||||
</div>
|
||||
|
||||
{# Replace tag input with auto-complete component #}
|
||||
<script src="{% static "bundle.js" %}"></script>
|
||||
<script type="application/javascript">
|
||||
const wrapper = document.createElement('div');
|
||||
const tagInput = document.getElementById('{{ form.tag_string.id_for_label }}');
|
||||
const allTagsString = '{{ all_tags }}';
|
||||
const allTags = allTagsString.split(' ');
|
||||
|
||||
new linkding.TagAutoComplete({
|
||||
target: wrapper,
|
||||
props: {
|
||||
id: '{{ form.tag_string.id_for_label }}',
|
||||
name: '{{ form.tag_string.name }}',
|
||||
value: tagInput.value,
|
||||
tags: allTags
|
||||
}
|
||||
});
|
||||
|
||||
tagInput.parentElement.replaceChild(wrapper, tagInput);
|
||||
</script>
|
||||
<script type="application/javascript">
|
||||
/**
|
||||
* Pre-fill title and description placeholders with metadata from website as soon as URL changes
|
||||
|
@ -8,7 +8,7 @@
|
||||
<h2>New bookmark</h2>
|
||||
</div>
|
||||
<form action="{% url 'bookmarks:new' %}" method="post" class="col-6 col-md-12" novalidate>
|
||||
{% bookmark_form form auto_close %}
|
||||
{% bookmark_form form all_tags auto_close %}
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
@ -3,16 +3,21 @@ from typing import List
|
||||
from django import template
|
||||
from django.core.paginator import Page
|
||||
|
||||
from bookmarks.models import BookmarkForm, Tag
|
||||
from bookmarks.models import BookmarkForm, Tag, build_tag_string
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.inclusion_tag('bookmarks/form.html', name='bookmark_form')
|
||||
def bookmark_form(form: BookmarkForm, auto_close: bool = False):
|
||||
def bookmark_form(form: BookmarkForm, all_tags: List[Tag], auto_close: bool = False):
|
||||
|
||||
all_tag_names = [tag.name for tag in all_tags]
|
||||
all_tags_string = build_tag_string(all_tag_names, ' ')
|
||||
|
||||
return {
|
||||
'form': form,
|
||||
'auto_close': auto_close
|
||||
'auto_close': auto_close,
|
||||
'all_tags': all_tags_string
|
||||
}
|
||||
|
||||
|
||||
|
@ -7,6 +7,7 @@ from django.urls import reverse
|
||||
from bookmarks import queries
|
||||
from bookmarks.models import Bookmark, BookmarkForm, build_tag_string
|
||||
from bookmarks.services.bookmarks import create_bookmark, update_bookmark
|
||||
from bookmarks.queries import get_user_tags
|
||||
|
||||
_default_page_size = 30
|
||||
|
||||
@ -56,7 +57,10 @@ def new(request):
|
||||
if initial_auto_close:
|
||||
form.initial['auto_close'] = 'true'
|
||||
|
||||
return render(request, 'bookmarks/new.html', {'form': form, 'auto_close': initial_auto_close})
|
||||
all_tags = get_user_tags(request.user)
|
||||
context = {'form': form, 'auto_close': initial_auto_close, 'all_tags': all_tags}
|
||||
|
||||
return render(request, 'bookmarks/new.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@ -71,7 +75,11 @@ def edit(request, bookmark_id: int):
|
||||
form = BookmarkForm(instance=bookmark)
|
||||
|
||||
form.initial['tag_string'] = build_tag_string(bookmark.tag_names, ' ')
|
||||
return render(request, 'bookmarks/edit.html', {'form': form, 'bookmark_id': bookmark_id})
|
||||
|
||||
all_tags = get_user_tags(request.user)
|
||||
context = {'form': form, 'bookmark_id': bookmark_id, 'all_tags': all_tags}
|
||||
|
||||
return render(request, 'bookmarks/edit.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
rm -rf static
|
||||
npm run build
|
||||
python manage.py compilescss
|
||||
python manage.py collectstatic --ignore=*.scss
|
||||
python manage.py compilescss --delete-files
|
||||
|
294
package-lock.json
generated
294
package-lock.json
generated
@ -4,10 +4,304 @@
|
||||
"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==",
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz",
|
||||
"integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==",
|
||||
"requires": {
|
||||
"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==",
|
||||
"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/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==",
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.0.0",
|
||||
"@types/resolve": "0.0.8",
|
||||
"builtin-modules": "^3.1.0",
|
||||
"is-module": "^1.0.0",
|
||||
"resolve": "^1.11.1"
|
||||
}
|
||||
},
|
||||
"@rollup/pluginutils": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.1.tgz",
|
||||
"integrity": "sha512-PmNurkecagFimv7ZdKCVOfQuqKDPkrcpLFxRBcQ00LYr4HAjJwhCFxBiY2Xoletll2htTIiXBg6g0Yg21h2M3w==",
|
||||
"requires": {
|
||||
"estree-walker": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"@types/estree": {
|
||||
"version": "0.0.39",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
|
||||
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "13.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.1.1.tgz",
|
||||
"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==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"acorn": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
|
||||
"integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ=="
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"esutils": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||
},
|
||||
"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==",
|
||||
"requires": {
|
||||
"@types/estree": "0.0.39"
|
||||
}
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "24.9.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz",
|
||||
"integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==",
|
||||
"requires": {
|
||||
"merge-stream": "^2.0.0",
|
||||
"supports-color": "^6.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"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==",
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"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==",
|
||||
"requires": {
|
||||
"sourcemap-codec": "^1.4.4"
|
||||
}
|
||||
},
|
||||
"merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"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==",
|
||||
"requires": {
|
||||
"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==",
|
||||
"requires": {
|
||||
"@types/estree": "*",
|
||||
"@types/node": "*",
|
||||
"acorn": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"requires": {
|
||||
"require-relative": "^0.8.7",
|
||||
"rollup-pluginutils": "^2.3.3",
|
||||
"sourcemap-codec": "^1.4.4"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"rollup-pluginutils": {
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
|
||||
"integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
|
||||
"requires": {
|
||||
"estree-walker": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"serialize-javascript": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
|
||||
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ=="
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||
},
|
||||
"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==",
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"sourcemap-codec": {
|
||||
"version": "1.4.6",
|
||||
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz",
|
||||
"integrity": "sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg=="
|
||||
},
|
||||
"spectre.css": {
|
||||
"version": "0.5.8",
|
||||
"resolved": "https://registry.npmjs.org/spectre.css/-/spectre.css-0.5.8.tgz",
|
||||
"integrity": "sha512-3N4WocWY+Dl6b3e5v3nsZYyp+VSDcBfGDzyyHw/H78ie9BoAhHkxmrhLxo9y8RadxYzVrPjfPdlev3hXEUzR2w=="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"svelte": {
|
||||
"version": "3.16.7",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.16.7.tgz",
|
||||
"integrity": "sha512-egrva1UklB1n7KAv179IhDpQzMGAvubJUlOQ9PitmmZmAfrCUEgrQnx2vPxn2s+mGV3aYegXvJ/yQ35N2SfnYQ=="
|
||||
},
|
||||
"terser": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.4.3.tgz",
|
||||
"integrity": "sha512-0ikKraVtRDKGzHrzkCv5rUNDzqlhmhowOBqC0XqUHFpW+vJ45+20/IFBcebwKfiS2Z9fJin6Eo+F1zLZsxi8RA==",
|
||||
"requires": {
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.6.1",
|
||||
"source-map-support": "~0.5.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
package.json
11
package.json
@ -4,7 +4,8 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"build": "rollup -c",
|
||||
"dev": "rollup -c -w"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -18,6 +19,12 @@
|
||||
},
|
||||
"homepage": "https://github.com/sissbruecker/linkdings#readme",
|
||||
"dependencies": {
|
||||
"spectre.css": "^0.5.8"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
40
rollup.config.js
Normal file
40
rollup.config.js
Normal file
@ -0,0 +1,40 @@
|
||||
import svelte from 'rollup-plugin-svelte';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH;
|
||||
|
||||
export default {
|
||||
input: 'bookmarks/components/index.js',
|
||||
output: {
|
||||
sourcemap: true,
|
||||
format: 'iife',
|
||||
name: 'linkding',
|
||||
file: 'build/bundle.js'
|
||||
},
|
||||
plugins: [
|
||||
svelte({
|
||||
// enable run-time checks when not in production
|
||||
dev: !production,
|
||||
}),
|
||||
|
||||
// If you have external dependencies installed from
|
||||
// npm, you'll most likely need these plugins. In
|
||||
// some cases you'll need additional configuration —
|
||||
// consult the documentation for details:
|
||||
// https://github.com/rollup/rollup-plugin-commonjs
|
||||
resolve({
|
||||
browser: true,
|
||||
dedupe: importee => importee === 'svelte' || importee.startsWith('svelte/')
|
||||
}),
|
||||
commonjs(),
|
||||
|
||||
// If we're building for production (npm run build
|
||||
// instead of npm run dev), minify
|
||||
production && terser()
|
||||
],
|
||||
watch: {
|
||||
clearScreen: false
|
||||
}
|
||||
};
|
@ -139,6 +139,7 @@ STATICFILES_FINDERS = [
|
||||
|
||||
# Include SASS styles into static path, otherwise they can not be found by the SASS preprocessor
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, 'build'),
|
||||
os.path.join(BASE_DIR, 'bookmarks', 'styles'),
|
||||
]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user