aboutsummaryrefslogtreecommitdiff
path: root/static
diff options
context:
space:
mode:
Diffstat (limited to 'static')
-rw-r--r--static/icons/feather/LICENSE4
-rw-r--r--static/icons/feather/alert-circle.svg37
-rw-r--r--static/icons/feather/archive.svg15
-rw-r--r--static/icons/feather/arrow-down-circle.svg34
-rw-r--r--static/icons/feather/arrow-left.svg29
-rw-r--r--static/icons/feather/arrow-right.svg29
-rw-r--r--static/icons/feather/arrow-up.svg29
-rw-r--r--static/icons/feather/at-sign.svg28
-rw-r--r--static/icons/feather/bookmark.svg14
-rw-r--r--static/icons/feather/calendar.svg46
-rw-r--r--static/icons/feather/circle.svg13
-rw-r--r--static/icons/feather/clock.svg28
-rw-r--r--static/icons/feather/code.svg14
-rw-r--r--static/icons/feather/copy.svg14
-rw-r--r--static/icons/feather/edit.svg26
-rw-r--r--static/icons/feather/external-link.svg32
-rw-r--r--static/icons/feather/eye-off.svg29
-rw-r--r--static/icons/feather/eye.svg18
-rw-r--r--static/icons/feather/file-text.svg41
-rw-r--r--static/icons/feather/git-commit.svg15
-rw-r--r--static/icons/feather/globe.svg34
-rw-r--r--static/icons/feather/heart.svg13
-rw-r--r--static/icons/feather/home.svg14
-rw-r--r--static/icons/feather/link.svg26
-rw-r--r--static/icons/feather/map-pin.svg28
-rw-r--r--static/icons/feather/refresh-cw.svg18
-rw-r--r--static/icons/feather/rss.svg15
-rw-r--r--static/icons/feather/search.svg31
-rw-r--r--static/icons/feather/tag.svg14
-rw-r--r--static/icons/feather/trash-2.svg38
-rw-r--r--static/icons/feather/user.svg14
-rw-r--r--static/icons/feather/users.svg16
-rw-r--r--static/icons/tabler/LICENSE2
-rw-r--r--static/icons/tabler/archive.svg22
-rw-r--r--static/icons/tabler/book-2.svg30
-rw-r--r--static/icons/tabler/circle.svg16
-rw-r--r--static/icons/tabler/clock.svg26
-rw-r--r--static/icons/tabler/code.svg22
-rw-r--r--static/icons/tabler/git-fork.svg28
-rw-r--r--static/icons/tabler/home.svg22
-rw-r--r--static/icons/tabler/notes.svg25
-rw-r--r--static/icons/tabler/pinned.svg30
-rw-r--r--static/icons/tabler/robot.svg51
-rw-r--r--static/icons/tabler/rss.svg22
-rw-r--r--static/icons/tabler/settings.svg19
-rw-r--r--static/icons/tabler/square-letter-m.svg19
-rw-r--r--static/icons/tabler/tag.svg19
-rw-r--r--static/icons/tabler/users.svg25
-rw-r--r--static/js/autoplay.ts42
-rw-r--r--static/js/codecopy.ts7
-rw-r--r--static/js/contextmenu.ts10
-rw-r--r--static/js/domfilter.ts145
-rw-r--r--static/js/fixedsearch.ts192
-rw-r--r--static/js/forms.ts130
-rw-r--r--static/js/fuzzysort.js1101
-rw-r--r--static/js/hoverfix.ts64
-rw-r--r--static/js/index-bundle.ts (renamed from static/js/bundle.ts)9
-rw-r--r--static/js/index.ts7
-rw-r--r--static/js/infinitescroll.ts78
-rw-r--r--static/js/instantpage.ts330
-rw-r--r--static/js/pager.ts107
-rw-r--r--static/js/plumber.ts6
-rw-r--r--static/js/timeago.ts113
-rw-r--r--static/js/update.ts84
64 files changed, 2101 insertions, 1458 deletions
diff --git a/static/icons/feather/LICENSE b/static/icons/feather/LICENSE
index b869713..1f4f433 100644
--- a/static/icons/feather/LICENSE
+++ b/static/icons/feather/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2013-2017 Cole Bemis
+Copyright (c) 2013-2023 Cole Bemis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE. \ No newline at end of file
+SOFTWARE.
diff --git a/static/icons/feather/alert-circle.svg b/static/icons/feather/alert-circle.svg
index 57fbe36..3672e20 100644
--- a/static/icons/feather/alert-circle.svg
+++ b/static/icons/feather/alert-circle.svg
@@ -1,15 +1,26 @@
<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"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><circle
+ cx="12"
+ cy="12"
+ r="10"
+ /><line
+ x1="12"
+ x2="12"
+ y1="8"
+ y2="12"
+ /><line
+ x1="12"
+ x2="12.01"
+ y1="16"
+ y2="16"
+ /></svg
>
- <circle cx="12" cy="12" r="10" />
- <line x1="12" y1="8" x2="12" y2="12" />
- <line x1="12" y1="16" x2="12.01" y2="16" />
-</svg>
diff --git a/static/icons/feather/archive.svg b/static/icons/feather/archive.svg
deleted file mode 100644
index 978eb76..0000000
--- a/static/icons/feather/archive.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-<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"
->
- <polyline points="21 8 21 21 3 21 3 8" />
- <rect x="1" y="3" width="22" height="5" />
- <line x1="10" y1="12" x2="14" y2="12" />
-</svg>
diff --git a/static/icons/feather/arrow-down-circle.svg b/static/icons/feather/arrow-down-circle.svg
index eb9f1a0..816acdd 100644
--- a/static/icons/feather/arrow-down-circle.svg
+++ b/static/icons/feather/arrow-down-circle.svg
@@ -1,15 +1,23 @@
<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"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><circle
+ cx="12"
+ cy="12"
+ r="10"
+ /><polyline
+ points="8 12 12 16 16 12"
+ /><line
+ x1="12"
+ x2="12"
+ y1="8"
+ y2="16"
+ /></svg
>
- <circle cx="12" cy="12" r="10" />
- <polyline points="8 12 12 16 16 12" />
- <line x1="12" y1="8" x2="12" y2="16" />
-</svg>
diff --git a/static/icons/feather/arrow-left.svg b/static/icons/feather/arrow-left.svg
index ce06f88..aebfb9e 100644
--- a/static/icons/feather/arrow-left.svg
+++ b/static/icons/feather/arrow-left.svg
@@ -1,14 +1,19 @@
<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"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><line
+ x1="19"
+ x2="5"
+ y1="12"
+ y2="12"
+ /><polyline
+ points="12 19 5 12 12 5"
+ /></svg
>
- <line x1="19" y1="12" x2="5" y2="12" />
- <polyline points="12 19 5 12 12 5" />
-</svg>
diff --git a/static/icons/feather/arrow-right.svg b/static/icons/feather/arrow-right.svg
index 5c5494f..96fbe50 100644
--- a/static/icons/feather/arrow-right.svg
+++ b/static/icons/feather/arrow-right.svg
@@ -1,14 +1,19 @@
<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"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><line
+ x1="5"
+ x2="19"
+ y1="12"
+ y2="12"
+ /><polyline
+ points="12 5 19 12 12 19"
+ /></svg
>
- <line x1="5" y1="12" x2="19" y2="12" />
- <polyline points="12 5 19 12 12 19" />
-</svg>
diff --git a/static/icons/feather/arrow-up.svg b/static/icons/feather/arrow-up.svg
index f1ff621..8da7d1e 100644
--- a/static/icons/feather/arrow-up.svg
+++ b/static/icons/feather/arrow-up.svg
@@ -1,14 +1,19 @@
<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"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><line
+ x1="12"
+ x2="12"
+ y1="19"
+ y2="5"
+ /><polyline
+ points="5 12 12 5 19 12"
+ /></svg
>
- <line x1="12" y1="19" x2="12" y2="5" />
- <polyline points="5 12 12 5 19 12" />
-</svg>
diff --git a/static/icons/feather/at-sign.svg b/static/icons/feather/at-sign.svg
index 0b7fdfb..28534c6 100644
--- a/static/icons/feather/at-sign.svg
+++ b/static/icons/feather/at-sign.svg
@@ -1,14 +1,18 @@
<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"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><circle
+ cx="12"
+ cy="12"
+ r="4"
+ /><path
+ d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94"
+ /></svg
>
- <circle cx="12" cy="12" r="4" />
- <path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94" />
-</svg>
diff --git a/static/icons/feather/bookmark.svg b/static/icons/feather/bookmark.svg
new file mode 100644
index 0000000..c553459
--- /dev/null
+++ b/static/icons/feather/bookmark.svg
@@ -0,0 +1,14 @@
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><path
+ d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"
+ /></svg
+>
diff --git a/static/icons/feather/calendar.svg b/static/icons/feather/calendar.svg
index c002b85..9c3693e 100644
--- a/static/icons/feather/calendar.svg
+++ b/static/icons/feather/calendar.svg
@@ -1,16 +1,34 @@
<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"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><rect
+ height="18"
+ rx="2"
+ ry="2"
+ width="18"
+ x="3"
+ y="4"
+ /><line
+ x1="16"
+ x2="16"
+ y1="2"
+ y2="6"
+ /><line
+ x1="8"
+ x2="8"
+ y1="2"
+ y2="6"
+ /><line
+ x1="3"
+ x2="21"
+ y1="10"
+ y2="10"
+ /></svg
>
- <rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
- <line x1="16" y1="2" x2="16" y2="6" />
- <line x1="8" y1="2" x2="8" y2="6" />
- <line x1="3" y1="10" x2="21" y2="10" />
-</svg>
diff --git a/static/icons/feather/circle.svg b/static/icons/feather/circle.svg
deleted file mode 100644
index 1717bb4..0000000
--- a/static/icons/feather/circle.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-<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"
->
- <circle cx="12" cy="12" r="10" />
-</svg>
diff --git a/static/icons/feather/clock.svg b/static/icons/feather/clock.svg
index 8ce25b2..9c69907 100644
--- a/static/icons/feather/clock.svg
+++ b/static/icons/feather/clock.svg
@@ -1,14 +1,18 @@
<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"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><circle
+ cx="12"
+ cy="12"
+ r="10"
+ /><polyline
+ points="12 6 12 12 16 14"
+ /></svg
>
- <circle cx="12" cy="12" r="10" />
- <polyline points="12 6 12 12 16 14" />
-</svg>
diff --git a/static/icons/feather/code.svg b/static/icons/feather/code.svg
deleted file mode 100644
index da0f522..0000000
--- a/static/icons/feather/code.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-<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"
->
- <polyline points="16 18 22 12 16 6" />
- <polyline points="8 6 2 12 8 18" />
-</svg>
diff --git a/static/icons/feather/copy.svg b/static/icons/feather/copy.svg
deleted file mode 100644
index c8d4956..0000000
--- a/static/icons/feather/copy.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-<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"
->
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
- <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
-</svg>
diff --git a/static/icons/feather/edit.svg b/static/icons/feather/edit.svg
index 9294758..a6d88b8 100644
--- a/static/icons/feather/edit.svg
+++ b/static/icons/feather/edit.svg
@@ -1,14 +1,16 @@
<svg
- width="24"
- height="24"
- viewBox="0 0 24 24"
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- stroke="currentColor"
- stroke-width="2"
- stroke-linecap="round"
- stroke-linejoin="round"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><path
+ d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
+ /><path
+ d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
+ /></svg
>
- <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
- <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
-</svg>
diff --git a/static/icons/feather/external-link.svg b/static/icons/feather/external-link.svg
index 537b731..a17c25b 100644
--- a/static/icons/feather/external-link.svg
+++ b/static/icons/feather/external-link.svg
@@ -1,15 +1,21 @@
<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"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><path
+ d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"
+ /><polyline
+ points="15 3 21 3 21 9"
+ /><line
+ x1="10"
+ x2="21"
+ y1="14"
+ y2="3"
+ /></svg
>
- <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
- <polyline points="15 3 21 3 21 9" />
- <line x1="10" y1="14" x2="21" y2="3" />
-</svg>
diff --git a/static/icons/feather/eye-off.svg b/static/icons/feather/eye-off.svg
index 98c45f6..0b8c1ee 100644
--- a/static/icons/feather/eye-off.svg
+++ b/static/icons/feather/eye-off.svg
@@ -1,14 +1,19 @@
<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"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><path
+ d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"
+ /><line
+ x1="1"
+ x2="23"
+ y1="1"
+ y2="23"
+ /></svg
>
- <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" />
- <line x1="1" y1="1" x2="23" y2="23" />
-</svg>
diff --git a/static/icons/feather/eye.svg b/static/icons/feather/eye.svg
new file mode 100644
index 0000000..de178f2
--- /dev/null
+++ b/static/icons/feather/eye.svg
@@ -0,0 +1,18 @@
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><path
+ d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"
+ /><circle
+ cx="12"
+ cy="12"
+ r="3"
+ /></svg
+>
diff --git a/static/icons/feather/file-text.svg b/static/icons/feather/file-text.svg
index 6cba58c..6c06a4e 100644
--- a/static/icons/feather/file-text.svg
+++ b/static/icons/feather/file-text.svg
@@ -1,17 +1,28 @@
<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"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><path
+ d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"
+ /><polyline
+ points="14 2 14 8 20 8"
+ /><line
+ x1="16"
+ x2="8"
+ y1="13"
+ y2="13"
+ /><line
+ x1="16"
+ x2="8"
+ y1="17"
+ y2="17"
+ /><polyline
+ points="10 9 9 9 8 9"
+ /></svg
>
- <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
- <polyline points="14 2 14 8 20 8" />
- <line x1="16" y1="13" x2="8" y2="13" />
- <line x1="16" y1="17" x2="8" y2="17" />
- <polyline points="10 9 9 9 8 9" />
-</svg>
diff --git a/static/icons/feather/git-commit.svg b/static/icons/feather/git-commit.svg
deleted file mode 100644
index 1574fbd..0000000
--- a/static/icons/feather/git-commit.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-<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"
->
- <circle cx="12" cy="12" r="4" />
- <line x1="1.05" y1="12" x2="7" y2="12" />
- <line x1="17.01" y1="12" x2="22.96" y2="12" />
-</svg>
diff --git a/static/icons/feather/globe.svg b/static/icons/feather/globe.svg
index a9c820f..00f792d 100644
--- a/static/icons/feather/globe.svg
+++ b/static/icons/feather/globe.svg
@@ -1,15 +1,23 @@
<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"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><circle
+ cx="12"
+ cy="12"
+ r="10"
+ /><line
+ x1="2"
+ x2="22"
+ y1="12"
+ y2="12"
+ /><path
+ d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"
+ /></svg
>
- <circle cx="12" cy="12" r="10" />
- <line x1="2" y1="12" x2="22" y2="12" />
- <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
-</svg>
diff --git a/static/icons/feather/heart.svg b/static/icons/feather/heart.svg
deleted file mode 100644
index 8e0b98d..0000000
--- a/static/icons/feather/heart.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-<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 d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
-</svg>
diff --git a/static/icons/feather/home.svg b/static/icons/feather/home.svg
deleted file mode 100644
index 9cd8f76..0000000
--- a/static/icons/feather/home.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-<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 d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
- <polyline points="9 22 9 12 15 12 15 22" />
-</svg>
diff --git a/static/icons/feather/link.svg b/static/icons/feather/link.svg
index 645e746..51f6aac 100644
--- a/static/icons/feather/link.svg
+++ b/static/icons/feather/link.svg
@@ -1,14 +1,16 @@
<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"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><path
+ d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"
+ /><path
+ d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"
+ /></svg
>
- <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
- <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
-</svg>
diff --git a/static/icons/feather/map-pin.svg b/static/icons/feather/map-pin.svg
index 8f5f320..7802b5a 100644
--- a/static/icons/feather/map-pin.svg
+++ b/static/icons/feather/map-pin.svg
@@ -1,14 +1,18 @@
<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"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><path
+ d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"
+ /><circle
+ cx="12"
+ cy="10"
+ r="3"
+ /></svg
>
- <path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" />
- <circle cx="12" cy="10" r="3" />
-</svg>
diff --git a/static/icons/feather/refresh-cw.svg b/static/icons/feather/refresh-cw.svg
new file mode 100644
index 0000000..f48fd7e
--- /dev/null
+++ b/static/icons/feather/refresh-cw.svg
@@ -0,0 +1,18 @@
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><polyline
+ points="23 4 23 10 17 10"
+ /><polyline
+ points="1 20 1 14 7 14"
+ /><path
+ d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"
+ /></svg
+>
diff --git a/static/icons/feather/rss.svg b/static/icons/feather/rss.svg
deleted file mode 100644
index 3b87036..0000000
--- a/static/icons/feather/rss.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-<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 d="M4 11a9 9 0 0 1 9 9" />
- <path d="M4 4a16 16 0 0 1 16 16" />
- <circle cx="5" cy="19" r="1" />
-</svg>
diff --git a/static/icons/feather/search.svg b/static/icons/feather/search.svg
index 89a8636..8eaab65 100644
--- a/static/icons/feather/search.svg
+++ b/static/icons/feather/search.svg
@@ -1,14 +1,21 @@
<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"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><circle
+ cx="11"
+ cy="11"
+ r="8"
+ /><line
+ x1="21"
+ x2="16.65"
+ y1="21"
+ y2="16.65"
+ /></svg
>
- <circle cx="11" cy="11" r="8" />
- <line x1="21" y1="21" x2="16.65" y2="16.65" />
-</svg>
diff --git a/static/icons/feather/tag.svg b/static/icons/feather/tag.svg
deleted file mode 100644
index e8500cd..0000000
--- a/static/icons/feather/tag.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-<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 d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" />
- <line x1="7" y1="7" x2="7.01" y2="7" />
-</svg>
diff --git a/static/icons/feather/trash-2.svg b/static/icons/feather/trash-2.svg
index f3bd2bd..462a3f7 100644
--- a/static/icons/feather/trash-2.svg
+++ b/static/icons/feather/trash-2.svg
@@ -1,16 +1,26 @@
<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"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ ><polyline
+ points="3 6 5 6 21 6"
+ /><path
+ d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
+ /><line
+ x1="10"
+ x2="10"
+ y1="11"
+ y2="17"
+ /><line
+ x1="14"
+ x2="14"
+ y1="11"
+ y2="17"
+ /></svg
>
- <polyline points="3 6 5 6 21 6" />
- <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
- <line x1="10" y1="11" x2="10" y2="17" />
- <line x1="14" y1="11" x2="14" y2="17" />
-</svg>
diff --git a/static/icons/feather/user.svg b/static/icons/feather/user.svg
deleted file mode 100644
index f075a65..0000000
--- a/static/icons/feather/user.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-<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 d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
- <circle cx="12" cy="7" r="4" />
-</svg>
diff --git a/static/icons/feather/users.svg b/static/icons/feather/users.svg
deleted file mode 100644
index eb87d02..0000000
--- a/static/icons/feather/users.svg
+++ /dev/null
@@ -1,16 +0,0 @@
-<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 d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
- <circle cx="9" cy="7" r="4" />
- <path d="M23 21v-2a4 4 0 0 0-3-3.87" />
- <path d="M16 3.13a4 4 0 0 1 0 7.75" />
-</svg>
diff --git a/static/icons/tabler/LICENSE b/static/icons/tabler/LICENSE
index 1f192ee..974db1a 100644
--- a/static/icons/tabler/LICENSE
+++ b/static/icons/tabler/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020-2022 Paweł Kuna
+Copyright (c) 2020-2024 Paweł Kuna
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/static/icons/tabler/archive.svg b/static/icons/tabler/archive.svg
new file mode 100644
index 0000000..d55999b
--- /dev/null
+++ b/static/icons/tabler/archive.svg
@@ -0,0 +1,22 @@
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ >
+ <path
+ d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z"
+ />
+ <path
+ d="M5 8v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-10"
+ />
+ <path
+ d="M10 12l4 0"
+ />
+</svg
+>
diff --git a/static/icons/tabler/book-2.svg b/static/icons/tabler/book-2.svg
index e136c71..0800060 100644
--- a/static/icons/tabler/book-2.svg
+++ b/static/icons/tabler/book-2.svg
@@ -1,8 +1,22 @@
-<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-book-2" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
- <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
- <path d="M19 4v16h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12z" />
- <path d="M19 16h-12a2 2 0 0 0 -2 2" />
- <path d="M9 8h6" />
-</svg>
-
-
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ >
+ <path
+ d="M19 4v16h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12z"
+ />
+ <path
+ d="M19 16h-12a2 2 0 0 0 -2 2"
+ />
+ <path
+ d="M9 8h6"
+ />
+</svg
+>
diff --git a/static/icons/tabler/circle.svg b/static/icons/tabler/circle.svg
new file mode 100644
index 0000000..5660b32
--- /dev/null
+++ b/static/icons/tabler/circle.svg
@@ -0,0 +1,16 @@
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ >
+ <path
+ d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"
+ />
+</svg
+>
diff --git a/static/icons/tabler/clock.svg b/static/icons/tabler/clock.svg
index fd98984..7ad7d57 100644
--- a/static/icons/tabler/clock.svg
+++ b/static/icons/tabler/clock.svg
@@ -1,7 +1,19 @@
-<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-clock" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
- <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
- <circle cx="12" cy="12" r="9" />
- <polyline points="12 7 12 12 15 15" />
-</svg>
-
-
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ >
+ <path
+ d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0"
+ />
+ <path
+ d="M12 7v5l3 3"
+ />
+</svg
+>
diff --git a/static/icons/tabler/code.svg b/static/icons/tabler/code.svg
new file mode 100644
index 0000000..f9f1ba3
--- /dev/null
+++ b/static/icons/tabler/code.svg
@@ -0,0 +1,22 @@
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ >
+ <path
+ d="M7 8l-4 4l4 4"
+ />
+ <path
+ d="M17 8l4 4l-4 4"
+ />
+ <path
+ d="M14 4l-4 16"
+ />
+</svg
+>
diff --git a/static/icons/tabler/git-fork.svg b/static/icons/tabler/git-fork.svg
new file mode 100644
index 0000000..a27d387
--- /dev/null
+++ b/static/icons/tabler/git-fork.svg
@@ -0,0 +1,28 @@
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ >
+ <path
+ d="M12 18m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"
+ />
+ <path
+ d="M7 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"
+ />
+ <path
+ d="M17 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"
+ />
+ <path
+ d="M7 8v2a2 2 0 0 0 2 2h6a2 2 0 0 0 2 -2v-2"
+ />
+ <path
+ d="M12 12l0 4"
+ />
+</svg
+>
diff --git a/static/icons/tabler/home.svg b/static/icons/tabler/home.svg
new file mode 100644
index 0000000..ebe632d
--- /dev/null
+++ b/static/icons/tabler/home.svg
@@ -0,0 +1,22 @@
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ >
+ <path
+ d="M5 12l-2 0l9 -9l9 9l-2 0"
+ />
+ <path
+ d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7"
+ />
+ <path
+ d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6"
+ />
+</svg
+>
diff --git a/static/icons/tabler/notes.svg b/static/icons/tabler/notes.svg
new file mode 100644
index 0000000..a102d6e
--- /dev/null
+++ b/static/icons/tabler/notes.svg
@@ -0,0 +1,25 @@
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ >
+ <path
+ d="M5 3m0 2a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2z"
+ />
+ <path
+ d="M9 7l6 0"
+ />
+ <path
+ d="M9 11l6 0"
+ />
+ <path
+ d="M9 15l4 0"
+ />
+</svg
+>
diff --git a/static/icons/tabler/pinned.svg b/static/icons/tabler/pinned.svg
index fac8252..74002cb 100644
--- a/static/icons/tabler/pinned.svg
+++ b/static/icons/tabler/pinned.svg
@@ -1,8 +1,22 @@
-<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-pinned" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
- <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
- <path d="M9 4v6l-2 4v2h10v-2l-2 -4v-6" />
- <line x1="12" y1="16" x2="12" y2="21" />
- <line x1="8" y1="4" x2="16" y2="4" />
-</svg>
-
-
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ >
+ <path
+ d="M9 4v6l-2 4v2h10v-2l-2 -4v-6"
+ />
+ <path
+ d="M12 16l0 5"
+ />
+ <path
+ d="M8 4l8 0"
+ />
+</svg
+>
diff --git a/static/icons/tabler/robot.svg b/static/icons/tabler/robot.svg
index 0b94b49..8db2809 100644
--- a/static/icons/tabler/robot.svg
+++ b/static/icons/tabler/robot.svg
@@ -1,11 +1,40 @@
-<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-robot" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
- <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
- <path d="M7 7h10a2 2 0 0 1 2 2v1l1 1v3l-1 1v3a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-3l-1 -1v-3l1 -1v-1a2 2 0 0 1 2 -2z" />
- <path d="M10 16h4" />
- <circle cx="8.5" cy="11.5" r=".5" fill="currentColor" />
- <circle cx="15.5" cy="11.5" r=".5" fill="currentColor" />
- <path d="M9 7l-1 -4" />
- <path d="M15 7l1 -4" />
-</svg>
-
-
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ >
+ <path
+ d="M6 4m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v4a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"
+ />
+ <path
+ d="M12 2v2"
+ />
+ <path
+ d="M9 12v9"
+ />
+ <path
+ d="M15 12v9"
+ />
+ <path
+ d="M5 16l4 -2"
+ />
+ <path
+ d="M15 14l4 2"
+ />
+ <path
+ d="M9 18h6"
+ />
+ <path
+ d="M10 8v.01"
+ />
+ <path
+ d="M14 8v.01"
+ />
+</svg
+>
diff --git a/static/icons/tabler/rss.svg b/static/icons/tabler/rss.svg
new file mode 100644
index 0000000..884e1a6
--- /dev/null
+++ b/static/icons/tabler/rss.svg
@@ -0,0 +1,22 @@
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ >
+ <path
+ d="M5 19m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"
+ />
+ <path
+ d="M4 4a16 16 0 0 1 16 16"
+ />
+ <path
+ d="M4 11a9 9 0 0 1 9 9"
+ />
+</svg
+>
diff --git a/static/icons/tabler/settings.svg b/static/icons/tabler/settings.svg
new file mode 100644
index 0000000..02bea1e
--- /dev/null
+++ b/static/icons/tabler/settings.svg
@@ -0,0 +1,19 @@
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ >
+ <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.065z"
+ />
+ <path
+ d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0"
+ />
+</svg
+>
diff --git a/static/icons/tabler/square-letter-m.svg b/static/icons/tabler/square-letter-m.svg
new file mode 100644
index 0000000..721e549
--- /dev/null
+++ b/static/icons/tabler/square-letter-m.svg
@@ -0,0 +1,19 @@
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ >
+ <path
+ d="M3 3m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z"
+ />
+ <path
+ d="M9 16v-8l3 5l3 -5v8"
+ />
+</svg
+>
diff --git a/static/icons/tabler/tag.svg b/static/icons/tabler/tag.svg
new file mode 100644
index 0000000..abbb8c6
--- /dev/null
+++ b/static/icons/tabler/tag.svg
@@ -0,0 +1,19 @@
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ >
+ <path
+ d="M7.5 7.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"
+ />
+ <path
+ d="M3 6v5.172a2 2 0 0 0 .586 1.414l7.71 7.71a2.41 2.41 0 0 0 3.408 0l5.592 -5.592a2.41 2.41 0 0 0 0 -3.408l-7.71 -7.71a2 2 0 0 0 -1.414 -.586h-5.172a3 3 0 0 0 -3 3z"
+ />
+</svg
+>
diff --git a/static/icons/tabler/users.svg b/static/icons/tabler/users.svg
new file mode 100644
index 0000000..6fa6286
--- /dev/null
+++ b/static/icons/tabler/users.svg
@@ -0,0 +1,25 @@
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ height="24"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ viewBox="0 0 24 24"
+ width="24"
+ >
+ <path
+ d="M9 7m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0"
+ />
+ <path
+ d="M3 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"
+ />
+ <path
+ d="M16 3.13a4 4 0 0 1 0 7.75"
+ />
+ <path
+ d="M21 21v-2a4 4 0 0 0 -3 -3.85"
+ />
+</svg
+>
diff --git a/static/js/autoplay.ts b/static/js/autoplay.ts
new file mode 100644
index 0000000..5987576
--- /dev/null
+++ b/static/js/autoplay.ts
@@ -0,0 +1,42 @@
+(function () {
+ function viewport(element) {
+ const options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {
+ offset: { top: -50, left: 0, bottom: -50, right: 0 }
+ };
+ const view = element.getBoundingClientRect();
+ return view.top >= -options.offset.top
+ && view.left >= -options.offset.left
+ && view.bottom <= (self.innerHeight || document.documentElement.clientHeight) + options.offset.bottom
+ && view.right <= (self.innerWidth || document.documentElement.clientWidth) + options.offset.right;
+ };
+
+ ["scroll", "DOMContentLoaded"].forEach(function (event) {
+ self.addEventListener(event, function () {
+ let first = true;
+ let videos = document.querySelectorAll("video");
+
+ for (i = 0; i < videos.length; i++) {
+ videos[i].autoplay = true;
+ videos[i].controls = true;
+ videos[i].loop = true;
+ videos[i].muted = true;
+ videos[i].playsinline = true;
+
+ videos[i].setAttribute("autoplay", true);
+ videos[i].setAttribute("controls", true);
+ videos[i].setAttribute("loop", true);
+ videos[i].setAttribute("muted", true);
+ videos[i].setAttribute("playsinline", true);
+
+ const onscreen = viewport(videos[i]);
+
+ if (first && onscreen) {
+ videos[i].play();
+ first = false;
+ } else {
+ videos[i].pause();
+ }
+ }
+ });
+ });
+})();
diff --git a/static/js/codecopy.ts b/static/js/codecopy.ts
index 7c47c65..9885ad8 100644
--- a/static/js/codecopy.ts
+++ b/static/js/codecopy.ts
@@ -7,15 +7,12 @@
setTimeout(
async () => {
await navigator.clipboard.writeText(text);
- console.log("Info: Code block text copied succesfully.");
+ console.log("INFO: Code text copied successfully");
},
3000,
);
} catch (error) {
- console.error(
- "Error: navigator.clipboard.writeText() failed.",
- error,
- );
+ console.error("ERROR: Code text copy failed", error);
}
},
);
diff --git a/static/js/contextmenu.ts b/static/js/contextmenu.ts
index dea9bec..5add288 100644
--- a/static/js/contextmenu.ts
+++ b/static/js/contextmenu.ts
@@ -1,11 +1,11 @@
(function () {
- const hide = (triggers) => {
+ const hide = function (triggers) {
for (let i = 0; i < triggers.length; i++) {
triggers[i].checked = false;
}
};
- const hideIfClickedOutside = (menus, triggers, event) => {
+ const hideIfClickedOutside = function (menus, triggers, event) {
for (let i = 0; i < menus.length; i++) {
const active = triggers[i].checked === true;
const outside = !menus[i].contains(event.target);
@@ -14,14 +14,14 @@
};
self.addEventListener("scroll", function () {
- const triggers = document.querySelectorAll("micro-metadata-menu input");
+ const triggers = document.querySelectorAll("menu input");
hide(triggers);
});
["click", "touchstart"].forEach(function (event) {
self.addEventListener(event, function (event) {
- const menus = document.querySelectorAll("micro-metadata-menu");
- const triggers = document.querySelectorAll("micro-metadata-menu input");
+ const menus = document.querySelectorAll("menu");
+ const triggers = document.querySelectorAll("menu input");
hideIfClickedOutside(menus, triggers, event);
});
});
diff --git a/static/js/domfilter.ts b/static/js/domfilter.ts
new file mode 100644
index 0000000..8900e2f
--- /dev/null
+++ b/static/js/domfilter.ts
@@ -0,0 +1,145 @@
+/*
+ * DOM Filter Copyright (C) 2024 Thedro Neely
+ * Licence: AGPL | https://www.gnu.org/licenses/agpl-3.0.txt
+*/
+
+(function () {
+ type millisecond = number;
+ const timeout: millisecond = 300;
+
+ const state = "on";
+ const key = "config.navigation.fast";
+
+ function fetch(url, method, callback) {
+ const http = new XMLHttpRequest();
+ http.onreadystatechange = function () {
+ if (callback && http.readyState === 4) {
+ if (http.status === 200) callback(http);
+ else {
+ console.error("ERROR: Unable to navigate", http);
+ self.location.href = url;
+ }
+ }
+ };
+ http.open(method, url);
+ http.timeout = timeout;
+ http.send();
+ return http;
+ }
+
+ function styles(node) {
+ return (node.nodeName === "LINK") && node.rel && node.rel.includes("stylesheet");
+ }
+
+ function scripts(node) {
+ return (node.nodeName === "SCRIPT") && node.hasAttribute("src");
+ }
+
+ function filter(url, http) {
+ let live = document;
+ let node = live.head.childNodes.length;
+ let next = (new DOMParser()).parseFromString(http.responseText, "text/html");
+
+ if (next.doctype === null || !http.getResponseHeader("content-type").includes("text/html")) return false;
+
+ while (node--) {
+ if (styles(live.head.childNodes[node]) || scripts(live.head.childNodes[node])) {
+ // console.log("INFO: Persist:", live.head.childNodes[node]);
+ } else {
+ live.head.removeChild(live.head.childNodes[node]);
+ }
+ }
+
+ while (next.head.firstChild) {
+ if (styles(next.head.firstChild) || scripts(next.head.firstChild)) {
+ next.head.removeChild(next.head.firstChild);
+ } else {
+ live.head.appendChild(next.head.firstChild);
+ }
+ }
+
+ while (live.documentElement.attributes.length > 0) {
+ live.documentElement.removeAttribute(
+ live.documentElement.attributes[live.documentElement.attributes.length - 1].name,
+ );
+ }
+
+ while (next.documentElement.attributes.length > 0) {
+ live.documentElement.setAttribute(
+ next.documentElement.attributes[next.documentElement.attributes.length - 1].name,
+ next.documentElement.attributes[next.documentElement.attributes.length - 1].value,
+ );
+ next.documentElement.removeAttribute(
+ next.documentElement.attributes[next.documentElement.attributes.length - 1].name,
+ );
+ }
+
+ live.body.parentElement.replaceChild(next.body, live.body);
+ }
+
+ function persist() {
+ let persist = document.createElement("link");
+ persist.rel = "location";
+ persist.target = JSON.stringify(self.location);
+ document.head.appendChild(persist);
+ }
+
+ self.addEventListener("DOMContentLoaded", function () {
+ if (localStorage[key] !== state) return;
+ persist();
+ });
+
+ self.addEventListener("popstate", function (event) {
+ if (localStorage[key] !== state) return;
+ const link = JSON.parse(document.querySelector('link[rel="location"]').target);
+ const url = event.target.location;
+ const hashed = link.pathname === url.pathname;
+ if (hashed) return;
+ fetch(url, "GET", function (http) {
+ if (filter(url, http) === false) return self.location.href = url;
+ persist();
+ self.document.dispatchEvent(new CustomEvent("URLChangedCustomEvent", { bubbles: true }));
+ });
+ });
+
+ self.addEventListener("click", function (event) {
+ if (localStorage[key] !== state) return;
+ const links = document.querySelectorAll("a");
+ for (let i = 0; i < links.length; i++) {
+ const active = links[i].contains(event.target);
+ const change = self.location.href !== links[i].href;
+ const view = links[i].attributes.hasOwnProperty("download") === false;
+ const local = self.location.origin === links[i].origin && links[i].target !== "_self";
+ const hashed = self.location.pathname === links[i].pathname && links[i].href.includes("#");
+ if (active && local && change && view && (hashed === false)) {
+ event.preventDefault();
+ const url = links[i].href;
+ links[i].style.cursor = "wait";
+ fetch(url, "GET", function (http) {
+ links[i].removeAttribute("style")
+ if (filter(url, http) === false) return self.location.href = url;
+ history.pushState({}, "", links[i].href);
+ persist();
+ self.document.dispatchEvent(new CustomEvent("URLChangedCustomEvent", { bubbles: true }));
+ });
+ }
+ }
+ });
+})();
+
+/*
+ * Copyright (C) 2024 Thedro Neely
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
diff --git a/static/js/fixedsearch.ts b/static/js/fixedsearch.ts
index 938e1f5..04d318b 100644
--- a/static/js/fixedsearch.ts
+++ b/static/js/fixedsearch.ts
@@ -1,19 +1,24 @@
/*
- Modified Version of Fixed Search: https://gist.github.com/cmod/5410eae147e4318164258742dd053993
- MIT License: https://gist.github.com/Arty2/8b0c43581013753438a3d35c15091a9f#file-license-md
+ * Fixed Search Copyright (C) 2020 Heracles Papatheodorou
+ * Copyright (C) Craig Mod, Eddie Webb, and Matthew Daly
+ * https://gist.github.com/Arty2/8b0c43581013753438a3d35c15091a9f#file-license-md
+ * Licence: MIT | https://mit-license.org/
*/
(function () {
- self.addEventListener("DOMContentLoaded", function () {
- const form = document.getElementById("search-form"); // search form
- const query = document.getElementById("search-input"); // input box for search
- const submit = document.getElementById("search-submit"); // form submit button
- const dropdown = document.getElementById("search-results"); // targets the <ul>
- const container = document.getElementById("search-frame"); // targets the upper parent container
+ ["DOMContentLoaded", "URLChangedCustomEvent"].forEach(function (event) {
+ self.addEventListener(event, function () {
+ const container = document.querySelector("search-box");
+ const dropdown = document.querySelector("search-box ul");
+ const form = document.querySelector("search-box form");
+ const query = document.querySelector("search-box input");
+ const submit = document.querySelector("search-box button");
function first(element) {
+ if (element.firstChild.nextElementSibling) {
return element.firstChild.nextElementSibling.firstChild
.nextElementSibling;
+ }
}
function last(element) {
@@ -35,9 +40,28 @@
document.dispatchEvent(new KeyboardEvent("keydown", { "key": keyname }));
}
+ let selected;
+
+ if (submit === null) return;
+
submit.addEventListener("click", function (event) {
+ if (selected) {
+ event.preventDefault();
+ selected.focus();
+ return selected.click();
+ }
first(dropdown).focus();
press("ArrowDown");
+ document.activeElement.click();
+ });
+
+ ["keyup", "click"].forEach(function (event) {
+ form.addEventListener(event, function () {
+ if (document.activeElement.nodeName === "A") {
+ return selected = document.activeElement;
+ }
+ selected = undefined;
+ });
});
form.addEventListener("focusin", function () {
@@ -51,11 +75,8 @@
});
form.addEventListener("keydown", function (event) {
- const head = first(dropdown);
- const tail = last(dropdown);
-
// ESC (27)
- if (query.contains(event.target)) {
+ if (form.contains(event.target)) {
if (event.keyCode == 27) {
document.activeElement.blur();
dropdown.setAttribute("hidden", "");
@@ -63,6 +84,17 @@
}
}
+ // BACKSPACE (8)
+ if (event.keyCode == 8) {
+ if (document.activeElement != query) {
+ event.preventDefault();
+ query.focus();
+ }
+ }
+
+ const head = first(dropdown);
+ const tail = last(dropdown);
+
// DOWN (40)
if (event.keyCode == 40) {
event.preventDefault();
@@ -79,20 +111,12 @@
else previous(document);
}
- // BACKSPACE (8)
- if (event.keyCode == 8) {
- if (document.activeElement != query) {
- event.preventDefault();
- query.focus();
- }
- }
-
// ENTER (13)
if (event.keyCode == 13) {
if (dropdown && document.activeElement == query) {
event.preventDefault();
head.focus();
- self.window.location = document.activeElement.href;
+ head.click();
}
}
});
@@ -109,46 +133,31 @@
scrolls++;
});
- document.addEventListener("click", function (event) {
- if (!form.contains(event.target)) {
+ self.addEventListener("click", function (event) {
+ if (
+ !form.contains(event.target) &&
+ !(document.activeElement === query) &&
+ !(document.activeElement === submit)
+ ) {
dropdown.setAttribute("hidden", "");
container.removeAttribute("data-focus");
}
});
- let data = {};
-
- function isEmpty(obj) {
- return Object.keys(obj).length === 0;
- }
-
- function appendItemsTo(local, remote) {
- const paginated = Object.keys(remote).includes("next_url");
- if (isEmpty(local)) {
- local = remote;
- } else {
- local.items = local.items.concat(remote.items);
- }
- if (paginated) {
- fetchJson(remote.next_url, local);
- }
- data = local;
- }
-
- function fetchJson(url, store) {
- const httpRequest = new XMLHttpRequest();
- httpRequest.onreadystatechange = function () {
- if (httpRequest.readyState === 4 && httpRequest.status === 200) {
- appendItemsTo(store, JSON.parse(httpRequest.responseText));
+ function fetch(url, callback) {
+ const http = new XMLHttpRequest();
+ http.onreadystatechange = function () {
+ if (http.readyState === 4 && http.status === 200 && callback) {
+ callback(http);
}
};
- httpRequest.open("GET", url);
- httpRequest.send();
+ http.open("GET", url);
+ http.send();
}
/* Load script based on https://stackoverflow.com/a/55451823 */
- function loadScript(url) {
+ function script(url) {
return new Promise(function (resolve, reject) {
const script = document.createElement("script");
script.onerror = reject;
@@ -158,35 +167,54 @@
script,
document.currentScript,
);
- } else {
- document.head.appendChild(script);
- }
+ } else document.head.appendChild(script);
script.src = url;
});
}
- let firstRun = true;
+ let data = {};
+ let boot = true;
+
+ const options = { key: ["title"] };
- function initialize() {
- if (firstRun) {
- loadScript(window.location.origin + "/js/fuzzysort.js")
- .then(() => {
- firstRun = false;
- fetchJson("/index.json", {});
+ function isEmpty(obj) { return Object.keys(obj).length === 0; }
- const options = { key: ["title"] };
+ function appendItemsTo(local, remote) {
+ const paginated = Object.keys(remote).includes("next_url");
+ if (isEmpty(local)) {
+ local = remote;
+ } else {
+ local.items = local.items.concat(remote.items);
+ }
+ if (paginated) {
+ fetch(remote.next_url, function (request) {
+ appendItemsTo(local, JSON.parse(request.responseText));
+ });
+ } else search(query.value, data.items, options);
+ data = local;
+ }
- query.addEventListener("keyup", function () {
- search(query.value, data.items, options);
+ function initialize() {
+ if (boot) {
+ script(window.location.origin + "/js/fuzzysort.js")
+ .then(function () {
+
+ fetch("/index.json", function (request) {
+ appendItemsTo({}, JSON.parse(request.responseText));
+ search("", data.items, options);
+ boot = false;
});
- query.addEventListener("focusin", function () {
- search(query.value, data.items, options);
+
+ ["keyup", "focusin"].forEach(function (event) {
+ query.addEventListener(event, function () {
+ if (data.items) search(query.value, data.items, options);
+ else { boot = true; initialize(); }
+ });
});
- search(query.value, data.items, options);
- }).catch((error) => {
- console.log("Error failed to load fuzzy sort: " + error);
+ }).catch(function (error) {
+ console.error("ERROR: Failed to load fuzzy search", error);
});
}
}
@@ -204,18 +232,15 @@
if (results.length === 0 && term.length >= 0) {
let separator = "—";
if (term.length === 0) separator = "";
- items = `
- <li>
- <a href="javascript: void(0)" tabindex="0">${
- escape(term)
- } ${separator} No Results Found</a>
- </li>
- `;
+ items = '<li><a tabindex="0">'.concat(
+ escape(term),
+ " ",
+ ).concat(separator, " No Results Found</a></li>");
dropdown.removeAttribute("hidden");
container.setAttribute("data-focus", "");
} else {
dropdown.removeAttribute("hidden");
- for (const string in results.slice(0, 10)) {
+ for (var string in results.slice(0, 10)) {
const title = results[string].obj.title;
let highlight = fuzzysort.highlight(
fuzzysort.single(escape(term), escape(title)),
@@ -223,20 +248,17 @@
"</span>",
);
- if (highlight === null) {
- highlight = title;
- }
+ if (highlight === null) highlight = title;
items = items +
- `
- <li>
- <a href="${results[string].obj.url}" tabindex="0">${highlight}</a>
- </li>
- `;
+ '\n<li>\n<a href="'.concat(
+ results[string].obj.url,
+ '" tabindex="0">',
+ ).concat(highlight, "</a>\n</li>\n");
}
}
-
dropdown.innerHTML = items;
}
});
+ });
})();
diff --git a/static/js/forms.ts b/static/js/forms.ts
new file mode 100644
index 0000000..263fda1
--- /dev/null
+++ b/static/js/forms.ts
@@ -0,0 +1,130 @@
+(function () {
+
+ const cookiesDisabled = !navigator.cookieEnabled;
+
+ if (cookiesDisabled) {
+ document.cookie = "disabled";
+ document.cookie.indexOf("disabled");
+ return console.warn("WARNING: Cannot persist form state due to cookie restrictions");
+ }
+
+ const storage = document.createEvent("Event");
+ storage.initEvent("storage", true, true);
+
+ ["pageshow", "URLChangedCustomEvent", "DOMContentLoaded"].forEach(function (event) {
+ self.addEventListener(event, function (event) {
+ const input = Array.prototype.slice.call(document.querySelectorAll("form input"));
+ const select = Array.prototype.slice.call(document.querySelectorAll("form select"));
+ const textarea = Array.prototype.slice.call(document.querySelectorAll("form textarea"));
+ const summary = Array.prototype.slice.call(document.querySelectorAll("details summary"));
+
+ const states = input.concat(select).concat(textarea);
+
+ for (var i = 0; i < states.length; i++) {
+ const state = states[i];
+ const sync = i === states.length - 1;
+
+ if (localStorage[state.id]) {
+ if (state.type === "radio" || state.type === "checkbox") {
+ if (localStorage[state.id] === "on") state.checked = true;
+ } else state.value = localStorage[state.id];
+ }
+
+ if (sync) self.dispatchEvent(storage);
+
+ state.addEventListener("change", function (event) {
+
+ // console.log("INFO: STATE:", event.target);
+ // console.log("INFO: ID:", event.target.id);
+ // console.log("INFO: NAME:", event.target.name);
+ // console.log("INFO: TYPE:", event.target.type);
+ // console.log("INFO: VALUE:", event.target.value);
+ // console.log("INFO: CHECKED:", event.target.checked);
+
+ localStorage[event.target.id] = event.target.value;
+
+ const group = document.querySelectorAll("input[name='".concat(event.target.name, "']"));
+
+ for (var j = 0; j < group.length; j++) {
+ const member = group[j];
+ if ((member.type === "radio" || member.type === "checkbox") && !member.checked) {
+ localStorage[member.id] = "off";
+ }
+ }
+ self.dispatchEvent(new Event("storage"));
+ });
+ }
+
+ for (var k = 0; k < summary.length; k++) {
+ let child = summary[k];
+ let details = child.parentElement;
+
+ if (details.id && details.nodeName === "DETAILS") {
+ sessionStorage[details.id] === "false" && details.removeAttribute("open");
+ sessionStorage[details.id] === "true" && details.setAttribute("open", true);
+
+ child.addEventListener("click", function (event) {
+ let child = (event.target.nodeName === "SUMMARY" && event.target)
+ || event.target.parentElement;
+ let details = child.parentElement;
+ if (details.id && details.nodeName === "DETAILS") {
+ sessionStorage[details.id] = !details.open;
+ }
+ });
+ }
+ }
+ });
+ });
+
+ ["storage"].forEach(function (event) {
+ self.addEventListener(event, function () {
+ let stylesheet;
+
+ stylesheet = document.querySelector('link[href$="default-simple.css"]')
+
+ if (localStorage["config.layout.simple"] === "on") stylesheet.rel = "stylesheet"
+ if (localStorage["config.layout.default"] === "on") stylesheet.rel = "alternate stylesheet"
+
+ stylesheet = document.querySelector('link[href$="default-fast.css"]')
+
+ if (localStorage["config.navigation.fast"] === "on") stylesheet.rel = "stylesheet"
+ if (localStorage["config.navigation.slow"] === "on") stylesheet.rel = "alternate stylesheet"
+
+ for (var i = 0; i < document.styleSheets.length; i++) {
+ let stylesheet = document.styleSheets[i];
+ for (var k = 0; k < stylesheet.rules.length; k++) {
+ let media = stylesheet.rules[k].media;
+ if (media && media.mediaText.includes("prefers-color-scheme")) {
+ if (localStorage["config.theme.light"] === "on") {
+ media.mediaText = "(prefers-color-scheme: dark)";
+ if (getComputedStyle(document.body).getPropertyValue("color-scheme") === "dark") { media.mediaText = "(prefers-color-scheme: light)"; }
+ }
+
+ if (localStorage["config.theme.auto"] === "on") {
+ media.mediaText = "(prefers-color-scheme: dark)";
+ }
+
+ if (localStorage["config.theme.dark"] === "on") {
+ media.mediaText = "(prefers-color-scheme: light)";
+ if (getComputedStyle(document.body).getPropertyValue("color-scheme") === "light") { media.mediaText = "(prefers-color-scheme: dark)"; }
+ }
+ }
+ }
+ }
+ });
+ });
+
+ const early = setInterval(persistence, 4);
+
+ function persistence() {
+ if (document.styleSheets.length > 0) {
+ self.dispatchEvent(storage);
+ clearInterval(early);
+ }
+ }
+
+ self.addEventListener("DOMContentLoaded", function () {
+ self.dispatchEvent(storage);
+ clearInterval(early);
+ });
+})();
diff --git a/static/js/fuzzysort.js b/static/js/fuzzysort.js
index c6797d8..71c2fcb 100644
--- a/static/js/fuzzysort.js
+++ b/static/js/fuzzysort.js
@@ -1,636 +1,549 @@
/*
- fuzzysort.js https://github.com/farzher/fuzzysort
- SublimeText-like Fuzzy Search
+ * Fuzzy Sort Copyright (C) 2018 Stephen Kamenar
+ * https://github.com/farzher/fuzzysort/blob/master/LICENSE
+ * Licence: MIT | https://mit-license.org/
+*/
- fuzzysort.single('fs', 'Fuzzy Search') // {score: -16}
- fuzzysort.single('test', 'test') // {score: 0}
- fuzzysort.single('doesnt exist', 'target') // null
+;((root, UMD) => {
+ if(typeof define === 'function' && define.amd) define([], UMD)
+ else if(typeof module === 'object' && module.exports) module.exports = UMD()
+ else root['fuzzysort'] = UMD()
+})(this, _ => {
+ 'use strict'
- fuzzysort.go('mr', [{file:'Monitor.cpp'}, {file:'MeshRenderer.cpp'}], {key:'file'})
- // [{score:-18, obj:{file:'MeshRenderer.cpp'}}, {score:-6009, obj:{file:'Monitor.cpp'}}]
+ var single = (search, target) => { if(search=='farzher')return{target:"farzher was here (^-^*)/",score:0,_indexes:[0]}
+ if(!search || !target) return NULL
- fuzzysort.go('mr', ['Monitor.cpp', 'MeshRenderer.cpp'])
- // [{score: -18, target: "MeshRenderer.cpp"}, {score: -6009, target: "Monitor.cpp"}]
+ var preparedSearch = getPreparedSearch(search)
+ if(!isObj(target)) target = getPrepared(target)
- fuzzysort.highlight(fuzzysort.single('fs', 'Fuzzy Search'), '<b>', '</b>')
- // <b>F</b>uzzy <b>S</b>earch
-*/
+ var searchBitflags = preparedSearch.bitflags
+ if((searchBitflags & target._bitflags) !== searchBitflags) return NULL
-// UMD (Universal Module Definition) for fuzzysort
-(function(root, UMD) {
- if(typeof define === 'function' && define.amd) {define([], UMD);}
- else if(typeof module === 'object' && module.exports) {module.exports = UMD();}
- else {root.fuzzysort = UMD();}
-})(this, function UMD() { function fuzzysortNew(instanceOptions) {
-
- var fuzzysort = {
-
- single: function(search, target, options) { if(search=='farzher'){return{target:"farzher was here (^-^*)/",score:0,indexes:[0,1,2,3,4,5,6]};}
- if(!search) {return null;}
- if(!isObj(search)) {search = fuzzysort.getPreparedSearch(search);}
-
- if(!target) {return null;}
- if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
-
- var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
- : instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
- : true;
- var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo;
- return algorithm(search, target, search[0]);
- },
-
- go: function(search, targets, options) { if(search=='farzher'){return[{target:"farzher was here (^-^*)/",score:0,indexes:[0,1,2,3,4,5,6],obj:targets?targets[0]:null}];}
- if(!search) {return noResults;}
- search = fuzzysort.prepareSearch(search);
- var searchLowerCode = search[0];
-
- var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991;
- var limit = options && options.limit || instanceOptions && instanceOptions.limit || 9007199254740991;
- var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
- : instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
- : true;
- var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo;
- var resultsLen = 0; var limitedCount = 0;
- var targetsLen = targets.length;
-
- // This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys]
-
- // options.keys
- if(options && options.keys) {
- var scoreFn = options.scoreFn || defaultScoreFn;
- var keys = options.keys;
- var keysLen = keys.length;
- for(var i = targetsLen - 1; i >= 0; --i) { var obj = targets[i];
- var objResults = new Array(keysLen);
- for (var keyI = keysLen - 1; keyI >= 0; --keyI) {
- var key = keys[keyI];
- var target = getValue(obj, key);
- if(!target) { objResults[keyI] = null; continue; }
- if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
-
- objResults[keyI] = algorithm(search, target, searchLowerCode);
- }
- objResults.obj = obj; // before scoreFn so scoreFn can use it
- var score = scoreFn(objResults);
- if(score === null) {continue;}
- if(score < threshold) {continue;}
- objResults.score = score;
- if(resultsLen < limit) { q.add(objResults); ++resultsLen; }
- else {
- ++limitedCount;
- if(score > q.peek().score) {q.replaceTop(objResults);}
- }
- }
+ return algorithm(preparedSearch, target)
+ }
- // options.key
- } else if(options && options.key) {
- var key = options.key;
- for(var i = targetsLen - 1; i >= 0; --i) { var obj = targets[i];
- var target = getValue(obj, key);
- if(!target) {continue;}
- if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
-
- var result = algorithm(search, target, searchLowerCode);
- if(result === null) {continue;}
- if(result.score < threshold) {continue;}
-
- // have to clone result so duplicate targets from different obj can each reference the correct obj
- result = {target:result.target, _targetLowerCodes:null, _nextBeginningIndexes:null, score:result.score, indexes:result.indexes, obj:obj}; // hidden
-
- if(resultsLen < limit) { q.add(result); ++resultsLen; }
- else {
- ++limitedCount;
- if(result.score > q.peek().score) {q.replaceTop(result);}
- }
- }
- // no keys
- } else {
- for(var i = targetsLen - 1; i >= 0; --i) { var target = targets[i];
- if(!target) {continue;}
- if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
-
- var result = algorithm(search, target, searchLowerCode);
- if(result === null) {continue;}
- if(result.score < threshold) {continue;}
- if(resultsLen < limit) { q.add(result); ++resultsLen; }
- else {
- ++limitedCount;
- if(result.score > q.peek().score) {q.replaceTop(result);}
- }
+ var go = (search, targets, options) => { if(search=='farzher')return[{target:"farzher was here (^-^*)/",score:0,_indexes:[0],obj:targets?targets[0]:NULL}]
+ if(!search) return options&&options.all ? all(search, targets, options) : noResults
+
+ var preparedSearch = getPreparedSearch(search)
+ var searchBitflags = preparedSearch.bitflags
+ var containsSpace = preparedSearch.containsSpace
+
+ var threshold = options&&options.threshold || INT_MIN
+ var limit = options&&options['limit'] || INT_MAX // for some reason only limit breaks when minified
+
+ var resultsLen = 0; var limitedCount = 0
+ var targetsLen = targets.length
+
+ // This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys]
+
+ // options.key
+ if(options && options.key) {
+ var key = options.key
+ for(var i = 0; i < targetsLen; ++i) { var obj = targets[i]
+ var target = getValue(obj, key)
+ if(!target) continue
+ if(!isObj(target)) target = getPrepared(target)
+
+ if((searchBitflags & target._bitflags) !== searchBitflags) continue
+ var result = algorithm(preparedSearch, target)
+ if(result === NULL) continue
+ if(result.score < threshold) continue
+
+ // have to clone result so duplicate targets from different obj can each reference the correct obj
+ result = {target:result.target, _targetLower:'', _targetLowerCodes:NULL, _nextBeginningIndexes:NULL, _bitflags:0, score:result.score, _indexes:result._indexes, obj:obj} // hidden
+
+ if(resultsLen < limit) { q.add(result); ++resultsLen }
+ else {
+ ++limitedCount
+ if(result.score > q.peek().score) q.replaceTop(result)
}
}
- if(resultsLen === 0) {return noResults;}
- var results = new Array(resultsLen);
- for(var i = resultsLen - 1; i >= 0; --i) {results[i] = q.poll();}
- results.total = resultsLen + limitedCount;
- return results;
- },
-
- goAsync: function(search, targets, options) {
- var canceled = false;
- var p = new Promise(function(resolve, reject) { if(search=='farzher'){return resolve([{target:"farzher was here (^-^*)/",score:0,indexes:[0,1,2,3,4,5,6],obj:targets?targets[0]:null}]);}
- if(!search) {return resolve(noResults);}
- search = fuzzysort.prepareSearch(search);
- var searchLowerCode = search[0];
-
- var q = fastpriorityqueue();
- var iCurrent = targets.length - 1;
- var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991;
- var limit = options && options.limit || instanceOptions && instanceOptions.limit || 9007199254740991;
- var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
- : instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
- : true;
- var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo;
- var resultsLen = 0; var limitedCount = 0;
- function step() {
- if(canceled) {return reject('canceled');}
-
- var startMs = Date.now();
-
- // This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys]
-
- // options.keys
- if(options && options.keys) {
- var scoreFn = options.scoreFn || defaultScoreFn;
- var keys = options.keys;
- var keysLen = keys.length;
- for(; iCurrent >= 0; --iCurrent) {
- if(iCurrent%1000/*itemsPerCheck*/ === 0) {
- if(Date.now() - startMs >= 10/*asyncInterval*/) {
- isNode?setImmediate(step):setTimeout(step);
- return;
- }
- }
-
- var obj = targets[iCurrent];
- var objResults = new Array(keysLen);
- for (var keyI = keysLen - 1; keyI >= 0; --keyI) {
- var key = keys[keyI];
- var target = getValue(obj, key);
- if(!target) { objResults[keyI] = null; continue; }
- if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
-
- objResults[keyI] = algorithm(search, target, searchLowerCode);
- }
- objResults.obj = obj; // before scoreFn so scoreFn can use it
- var score = scoreFn(objResults);
- if(score === null) {continue;}
- if(score < threshold) {continue;}
- objResults.score = score;
- if(resultsLen < limit) { q.add(objResults); ++resultsLen; }
- else {
- ++limitedCount;
- if(score > q.peek().score) {q.replaceTop(objResults);}
- }
- }
-
- // options.key
- } else if(options && options.key) {
- var key = options.key;
- for(; iCurrent >= 0; --iCurrent) {
- if(iCurrent%1000/*itemsPerCheck*/ === 0) {
- if(Date.now() - startMs >= 10/*asyncInterval*/) {
- isNode?setImmediate(step):setTimeout(step);
- return;
- }
- }
-
- var obj = targets[iCurrent];
- var target = getValue(obj, key);
- if(!target) {continue;}
- if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
-
- var result = algorithm(search, target, searchLowerCode);
- if(result === null) {continue;}
- if(result.score < threshold) {continue;}
-
- // have to clone result so duplicate targets from different obj can each reference the correct obj
- result = {target:result.target, _targetLowerCodes:null, _nextBeginningIndexes:null, score:result.score, indexes:result.indexes, obj:obj}; // hidden
-
- if(resultsLen < limit) { q.add(result); ++resultsLen; }
- else {
- ++limitedCount;
- if(result.score > q.peek().score) {q.replaceTop(result);}
- }
- }
-
- // no keys
- } else {
- for(; iCurrent >= 0; --iCurrent) {
- if(iCurrent%1000/*itemsPerCheck*/ === 0) {
- if(Date.now() - startMs >= 10/*asyncInterval*/) {
- isNode?setImmediate(step):setTimeout(step);
- return;
- }
- }
-
- var target = targets[iCurrent];
- if(!target) {continue;}
- if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
-
- var result = algorithm(search, target, searchLowerCode);
- if(result === null) {continue;}
- if(result.score < threshold) {continue;}
- if(resultsLen < limit) { q.add(result); ++resultsLen; }
- else {
- ++limitedCount;
- if(result.score > q.peek().score) {q.replaceTop(result);}
- }
- }
- }
-
- if(resultsLen === 0) {return resolve(noResults);}
- var results = new Array(resultsLen);
- for(var i = resultsLen - 1; i >= 0; --i) {results[i] = q.poll();}
- results.total = resultsLen + limitedCount;
- resolve(results);
+ // options.keys
+ } else if(options && options.keys) {
+ var scoreFn = options['scoreFn'] || defaultScoreFn
+ var keys = options.keys
+ var keysLen = keys.length
+ for(var i = 0; i < targetsLen; ++i) { var obj = targets[i]
+ var objResults = new Array(keysLen)
+ for (var keyI = 0; keyI < keysLen; ++keyI) {
+ var key = keys[keyI]
+ var target = getValue(obj, key)
+ if(!target) { objResults[keyI] = NULL; continue }
+ if(!isObj(target)) target = getPrepared(target)
+
+ if((searchBitflags & target._bitflags) !== searchBitflags) objResults[keyI] = NULL
+ else objResults[keyI] = algorithm(preparedSearch, target)
}
-
- isNode?setImmediate(step):step(); //setTimeout here is too slow
- });
- p.cancel = function() { canceled = true; };
- return p;
- },
-
- highlight: function(result, hOpen, hClose) {
- if(typeof hOpen == 'function') {return fuzzysort.highlightCallback(result, hOpen);}
- if(result === null) {return null;}
- if(hOpen === undefined) {hOpen = '<b>';}
- if(hClose === undefined) {hClose = '</b>';}
- var highlighted = '';
- var matchesIndex = 0;
- var opened = false;
- var target = result.target;
- var targetLen = target.length;
- var matchesBest = result.indexes;
- for(var i = 0; i < targetLen; ++i) { var char = target[i];
- if(matchesBest[matchesIndex] === i) {
- ++matchesIndex;
- if(!opened) { opened = true;
- highlighted += hOpen;
- }
-
- if(matchesIndex === matchesBest.length) {
- highlighted += char + hClose + target.substr(i+1);
- break;
- }
- } else {
- if(opened) { opened = false;
- highlighted += hClose;
- }
+ objResults.obj = obj // before scoreFn so scoreFn can use it
+ var score = scoreFn(objResults)
+ if(score === NULL) continue
+ if(score < threshold) continue
+ objResults.score = score
+ if(resultsLen < limit) { q.add(objResults); ++resultsLen }
+ else {
+ ++limitedCount
+ if(score > q.peek().score) q.replaceTop(objResults)
}
- highlighted += char;
}
- return highlighted;
- },
- highlightCallback: function(result, cb) {
- if(result === null) {return null;}
- var target = result.target;
- var targetLen = target.length;
- var indexes = result.indexes;
- var highlighted = '';
- var matchI = 0;
- var indexesI = 0;
- var opened = false;
- var result = [];
- for(var i = 0; i < targetLen; ++i) { var char = target[i];
- if(indexes[indexesI] === i) {
- ++indexesI;
- if(!opened) { opened = true;
- result.push(highlighted); highlighted = '';
- }
-
- if(indexesI === indexes.length) {
- highlighted += char;
- result.push(cb(highlighted, matchI++)); highlighted = '';
- result.push(target.substr(i+1));
- break;
- }
- } else {
- if(opened) { opened = false;
- result.push(cb(highlighted, matchI++)); highlighted = '';
- }
+ // no keys
+ } else {
+ for(var i = 0; i < targetsLen; ++i) { var target = targets[i]
+ if(!target) continue
+ if(!isObj(target)) target = getPrepared(target)
+
+ if((searchBitflags & target._bitflags) !== searchBitflags) continue
+ var result = algorithm(preparedSearch, target)
+ if(result === NULL) continue
+ if(result.score < threshold) continue
+ if(resultsLen < limit) { q.add(result); ++resultsLen }
+ else {
+ ++limitedCount
+ if(result.score > q.peek().score) q.replaceTop(result)
}
- highlighted += char;
}
- return result;
- },
-
- prepare: function(target) {
- if(!target) {return {target: '', _targetLowerCodes: [0/*this 0 doesn't make sense. here because an empty array causes the algorithm to deoptimize and run 50% slower!*/], _nextBeginningIndexes: null, score: null, indexes: null, obj: null};} // hidden
- return {target:target, _targetLowerCodes:fuzzysort.prepareLowerCodes(target), _nextBeginningIndexes:null, score:null, indexes:null, obj:null}; // hidden
- },
- prepareSlow: function(target) {
- if(!target) {return {target: '', _targetLowerCodes: [0/*this 0 doesn't make sense. here because an empty array causes the algorithm to deoptimize and run 50% slower!*/], _nextBeginningIndexes: null, score: null, indexes: null, obj: null};} // hidden
- return {target:target, _targetLowerCodes:fuzzysort.prepareLowerCodes(target), _nextBeginningIndexes:fuzzysort.prepareNextBeginningIndexes(target), score:null, indexes:null, obj:null}; // hidden
- },
- prepareSearch: function(search) {
- if(!search) {search = '';}
- return fuzzysort.prepareLowerCodes(search);
- },
-
-
-
- // Below this point is only internal code
- // Below this point is only internal code
- // Below this point is only internal code
- // Below this point is only internal code
-
-
-
- getPrepared: function(target) {
- if(target.length > 999) {return fuzzysort.prepare(target);} // don't cache huge targets
- var targetPrepared = preparedCache.get(target);
- if(targetPrepared !== undefined) {return targetPrepared;}
- targetPrepared = fuzzysort.prepare(target);
- preparedCache.set(target, targetPrepared);
- return targetPrepared;
- },
- getPreparedSearch: function(search) {
- if(search.length > 999) {return fuzzysort.prepareSearch(search);} // don't cache huge searches
- var searchPrepared = preparedSearchCache.get(search);
- if(searchPrepared !== undefined) {return searchPrepared;}
- searchPrepared = fuzzysort.prepareSearch(search);
- preparedSearchCache.set(search, searchPrepared);
- return searchPrepared;
- },
-
- algorithm: function(searchLowerCodes, prepared, searchLowerCode) {
- var targetLowerCodes = prepared._targetLowerCodes;
- var searchLen = searchLowerCodes.length;
- var targetLen = targetLowerCodes.length;
- var searchI = 0; // where we at
- var targetI = 0; // where you at
- var typoSimpleI = 0;
- var matchesSimpleLen = 0;
-
- // very basic fuzzy match; to remove non-matching targets ASAP!
- // walk through target. find sequential matches.
- // if all chars aren't found then exit
- for(;;) {
- var isMatch = searchLowerCode === targetLowerCodes[targetI];
- if(isMatch) {
- matchesSimple[matchesSimpleLen++] = targetI;
- ++searchI; if(searchI === searchLen) {break;}
- searchLowerCode = searchLowerCodes[typoSimpleI===0?searchI : (typoSimpleI===searchI?searchI+1 : (typoSimpleI===searchI-1?searchI-1 : searchI))];
+ }
+
+ if(resultsLen === 0) return noResults
+ var results = new Array(resultsLen)
+ for(var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll()
+ results.total = resultsLen + limitedCount
+ return results
+ }
+
+
+ var highlight = (result, hOpen, hClose) => {
+ if(typeof hOpen === 'function') return highlightCallback(result, hOpen)
+ if(result === NULL) return NULL
+ if(hOpen === undefined) hOpen = '<b>'
+ if(hClose === undefined) hClose = '</b>'
+ var highlighted = ''
+ var matchesIndex = 0
+ var opened = false
+ var target = result.target
+ var targetLen = target.length
+ var indexes = result._indexes
+ indexes = indexes.slice(0, indexes.len).sort((a,b)=>a-b)
+ for(var i = 0; i < targetLen; ++i) { var char = target[i]
+ if(indexes[matchesIndex] === i) {
+ ++matchesIndex
+ if(!opened) { opened = true
+ highlighted += hOpen
}
- ++targetI; if(targetI >= targetLen) { // Failed to find searchI
- // Check for typo or exit
- // we go as far as possible before trying to transpose
- // then we transpose backwards until we reach the beginning
- for(;;) {
- if(searchI <= 1) {return null;} // not allowed to transpose first char
- if(typoSimpleI === 0) { // we haven't tried to transpose yet
- --searchI;
- var searchLowerCodeNew = searchLowerCodes[searchI];
- if(searchLowerCode === searchLowerCodeNew) {continue;} // doesn't make sense to transpose a repeat char
- typoSimpleI = searchI;
- } else {
- if(typoSimpleI === 1) {return null;} // reached the end of the line for transposing
- --typoSimpleI;
- searchI = typoSimpleI;
- searchLowerCode = searchLowerCodes[searchI + 1];
- var searchLowerCodeNew = searchLowerCodes[searchI];
- if(searchLowerCode === searchLowerCodeNew) {continue;} // doesn't make sense to transpose a repeat char
- }
- matchesSimpleLen = searchI;
- targetI = matchesSimple[matchesSimpleLen - 1] + 1;
- break;
- }
+ if(matchesIndex === indexes.length) {
+ highlighted += char + hClose + target.substr(i+1)
+ break
+ }
+ } else {
+ if(opened) { opened = false
+ highlighted += hClose
}
}
+ highlighted += char
+ }
- var searchI = 0;
- var typoStrictI = 0;
- var successStrict = false;
- var matchesStrictLen = 0;
-
- var nextBeginningIndexes = prepared._nextBeginningIndexes;
- if(nextBeginningIndexes === null) {nextBeginningIndexes = prepared._nextBeginningIndexes = fuzzysort.prepareNextBeginningIndexes(prepared.target);}
- var firstPossibleI = targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1];
-
- // Our target string successfully matched all characters in sequence!
- // Let's try a more advanced and strict test to improve the score
- // only count it as a match if it's consecutive or a beginning character!
- if(targetI !== targetLen) {for(;;) {
- if(targetI >= targetLen) {
- // We failed to find a good spot for this search char, go back to the previous search char and force it forward
- if(searchI <= 0) { // We failed to push chars forward for a better match
- // transpose, starting from the beginning
- ++typoStrictI; if(typoStrictI > searchLen-2) {break;}
- if(searchLowerCodes[typoStrictI] === searchLowerCodes[typoStrictI+1]) {continue;} // doesn't make sense to transpose a repeat char
- targetI = firstPossibleI;
- continue;
- }
-
- --searchI;
- var lastMatch = matchesStrict[--matchesStrictLen];
- targetI = nextBeginningIndexes[lastMatch];
-
- } else {
- var isMatch = searchLowerCodes[typoStrictI===0?searchI : (typoStrictI===searchI?searchI+1 : (typoStrictI===searchI-1?searchI-1 : searchI))] === targetLowerCodes[targetI];
- if(isMatch) {
- matchesStrict[matchesStrictLen++] = targetI;
- ++searchI; if(searchI === searchLen) { successStrict = true; break; }
- ++targetI;
- } else {
- targetI = nextBeginningIndexes[targetI];
- }
+ return highlighted
+ }
+ var highlightCallback = (result, cb) => {
+ if(result === NULL) return NULL
+ var target = result.target
+ var targetLen = target.length
+ var indexes = result._indexes
+ indexes = indexes.slice(0, indexes.len).sort((a,b)=>a-b)
+ var highlighted = ''
+ var matchI = 0
+ var indexesI = 0
+ var opened = false
+ var result = []
+ for(var i = 0; i < targetLen; ++i) { var char = target[i]
+ if(indexes[indexesI] === i) {
+ ++indexesI
+ if(!opened) { opened = true
+ result.push(highlighted); highlighted = ''
}
- }}
-
- { // tally up the score & keep track of matches for highlighting later
- if(successStrict) { var matchesBest = matchesStrict; var matchesBestLen = matchesStrictLen; }
- else { var matchesBest = matchesSimple; var matchesBestLen = matchesSimpleLen; }
- var score = 0;
- var lastTargetI = -1;
- for(var i = 0; i < searchLen; ++i) { var targetI = matchesBest[i];
- // score only goes down if they're not consecutive
- if(lastTargetI !== targetI - 1) {score -= targetI;}
- lastTargetI = targetI;
+
+ if(indexesI === indexes.length) {
+ highlighted += char
+ result.push(cb(highlighted, matchI++)); highlighted = ''
+ result.push(target.substr(i+1))
+ break
}
- if(!successStrict) {
- score *= 1000;
- if(typoSimpleI !== 0) {score += -20;}/*typoPenalty*/
- } else {
- if(typoStrictI !== 0) {score += -20;}/*typoPenalty*/
+ } else {
+ if(opened) { opened = false
+ result.push(cb(highlighted, matchI++)); highlighted = ''
}
- score -= targetLen - searchLen;
- prepared.score = score;
- prepared.indexes = new Array(matchesBestLen); for(var i = matchesBestLen - 1; i >= 0; --i) {prepared.indexes[i] = matchesBest[i];}
+ }
+ highlighted += char
+ }
+ return result
+ }
+
+
+ var indexes = result => result._indexes.slice(0, result._indexes.len).sort((a,b)=>a-b)
+
- return prepared;
+ var prepare = (target) => {
+ if(typeof target !== 'string') target = ''
+ var info = prepareLowerInfo(target)
+ return {'target':target, _targetLower:info._lower, _targetLowerCodes:info.lowerCodes, _nextBeginningIndexes:NULL, _bitflags:info.bitflags, 'score':NULL, _indexes:[0], 'obj':NULL} // hidden
+ }
+
+
+ // Below this point is only internal code
+ // Below this point is only internal code
+ // Below this point is only internal code
+ // Below this point is only internal code
+
+
+ var prepareSearch = (search) => {
+ if(typeof search !== 'string') search = ''
+ search = search.trim()
+ var info = prepareLowerInfo(search)
+
+ var spaceSearches = []
+ if(info.containsSpace) {
+ var searches = search.split(/\s+/)
+ searches = [...new Set(searches)] // distinct
+ for(var i=0; i<searches.length; i++) {
+ if(searches[i] === '') continue
+ var _info = prepareLowerInfo(searches[i])
+ spaceSearches.push({lowerCodes:_info.lowerCodes, _lower:searches[i].toLowerCase(), containsSpace:false})
}
- },
-
- algorithmNoTypo: function(searchLowerCodes, prepared, searchLowerCode) {
- var targetLowerCodes = prepared._targetLowerCodes;
- var searchLen = searchLowerCodes.length;
- var targetLen = targetLowerCodes.length;
- var searchI = 0; // where we at
- var targetI = 0; // where you at
- var matchesSimpleLen = 0;
-
- // very basic fuzzy match; to remove non-matching targets ASAP!
- // walk through target. find sequential matches.
- // if all chars aren't found then exit
- for(;;) {
- var isMatch = searchLowerCode === targetLowerCodes[targetI];
- if(isMatch) {
- matchesSimple[matchesSimpleLen++] = targetI;
- ++searchI; if(searchI === searchLen) {break;}
- searchLowerCode = searchLowerCodes[searchI];
+ }
+
+ return {lowerCodes: info.lowerCodes, bitflags: info.bitflags, containsSpace: info.containsSpace, _lower: info._lower, spaceSearches: spaceSearches}
+ }
+
+
+
+ var getPrepared = (target) => {
+ if(target.length > 999) return prepare(target) // don't cache huge targets
+ var targetPrepared = preparedCache.get(target)
+ if(targetPrepared !== undefined) return targetPrepared
+ targetPrepared = prepare(target)
+ preparedCache.set(target, targetPrepared)
+ return targetPrepared
+ }
+ var getPreparedSearch = (search) => {
+ if(search.length > 999) return prepareSearch(search) // don't cache huge searches
+ var searchPrepared = preparedSearchCache.get(search)
+ if(searchPrepared !== undefined) return searchPrepared
+ searchPrepared = prepareSearch(search)
+ preparedSearchCache.set(search, searchPrepared)
+ return searchPrepared
+ }
+
+
+ var all = (search, targets, options) => {
+ var results = []; results.total = targets.length
+
+ var limit = options && options.limit || INT_MAX
+
+ if(options && options.key) {
+ for(var i=0;i<targets.length;i++) { var obj = targets[i]
+ var target = getValue(obj, options.key)
+ if(!target) continue
+ if(!isObj(target)) target = getPrepared(target)
+ target.score = INT_MIN
+ target._indexes.len = 0
+ var result = target
+ result = {target:result.target, _targetLower:'', _targetLowerCodes:NULL, _nextBeginningIndexes:NULL, _bitflags:0, score:target.score, _indexes:NULL, obj:obj} // hidden
+ results.push(result); if(results.length >= limit) return results
+ }
+ } else if(options && options.keys) {
+ for(var i=0;i<targets.length;i++) { var obj = targets[i]
+ var objResults = new Array(options.keys.length)
+ for (var keyI = options.keys.length - 1; keyI >= 0; --keyI) {
+ var target = getValue(obj, options.keys[keyI])
+ if(!target) { objResults[keyI] = NULL; continue }
+ if(!isObj(target)) target = getPrepared(target)
+ target.score = INT_MIN
+ target._indexes.len = 0
+ objResults[keyI] = target
}
- ++targetI; if(targetI >= targetLen) {return null;} // Failed to find searchI
+ objResults.obj = obj
+ objResults.score = INT_MIN
+ results.push(objResults); if(results.length >= limit) return results
+ }
+ } else {
+ for(var i=0;i<targets.length;i++) { var target = targets[i]
+ if(!target) continue
+ if(!isObj(target)) target = getPrepared(target)
+ target.score = INT_MIN
+ target._indexes.len = 0
+ results.push(target); if(results.length >= limit) return results
}
+ }
- var searchI = 0;
- var successStrict = false;
- var matchesStrictLen = 0;
+ return results
+ }
- var nextBeginningIndexes = prepared._nextBeginningIndexes;
- if(nextBeginningIndexes === null) {nextBeginningIndexes = prepared._nextBeginningIndexes = fuzzysort.prepareNextBeginningIndexes(prepared.target);}
- var firstPossibleI = targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1];
- // Our target string successfully matched all characters in sequence!
- // Let's try a more advanced and strict test to improve the score
- // only count it as a match if it's consecutive or a beginning character!
- if(targetI !== targetLen) {for(;;) {
- if(targetI >= targetLen) {
- // We failed to find a good spot for this search char, go back to the previous search char and force it forward
- if(searchI <= 0) {break;} // We failed to push chars forward for a better match
+ var algorithm = (preparedSearch, prepared, allowSpaces=false) => {
+ if(allowSpaces===false && preparedSearch.containsSpace) return algorithmSpaces(preparedSearch, prepared)
+
+ var searchLower = preparedSearch._lower
+ var searchLowerCodes = preparedSearch.lowerCodes
+ var searchLowerCode = searchLowerCodes[0]
+ var targetLowerCodes = prepared._targetLowerCodes
+ var searchLen = searchLowerCodes.length
+ var targetLen = targetLowerCodes.length
+ var searchI = 0 // where we at
+ var targetI = 0 // where you at
+ var matchesSimpleLen = 0
+
+ // very basic fuzzy match; to remove non-matching targets ASAP!
+ // walk through target. find sequential matches.
+ // if all chars aren't found then exit
+ for(;;) {
+ var isMatch = searchLowerCode === targetLowerCodes[targetI]
+ if(isMatch) {
+ matchesSimple[matchesSimpleLen++] = targetI
+ ++searchI; if(searchI === searchLen) break
+ searchLowerCode = searchLowerCodes[searchI]
+ }
+ ++targetI; if(targetI >= targetLen) return NULL // Failed to find searchI
+ }
+
+ var searchI = 0
+ var successStrict = false
+ var matchesStrictLen = 0
+
+ var nextBeginningIndexes = prepared._nextBeginningIndexes
+ if(nextBeginningIndexes === NULL) nextBeginningIndexes = prepared._nextBeginningIndexes = prepareNextBeginningIndexes(prepared.target)
+ var firstPossibleI = targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1]
+
+ // Our target string successfully matched all characters in sequence!
+ // Let's try a more advanced and strict test to improve the score
+ // only count it as a match if it's consecutive or a beginning character!
+ var backtrackCount = 0
+ if(targetI !== targetLen) for(;;) {
+ if(targetI >= targetLen) {
+ // We failed to find a good spot for this search char, go back to the previous search char and force it forward
+ if(searchI <= 0) break // We failed to push chars forward for a better match
- --searchI;
- var lastMatch = matchesStrict[--matchesStrictLen];
- targetI = nextBeginningIndexes[lastMatch];
+ ++backtrackCount; if(backtrackCount > 200) break // exponential backtracking is taking too long, just give up and return a bad match
+ --searchI
+ var lastMatch = matchesStrict[--matchesStrictLen]
+ targetI = nextBeginningIndexes[lastMatch]
+
+ } else {
+ var isMatch = searchLowerCodes[searchI] === targetLowerCodes[targetI]
+ if(isMatch) {
+ matchesStrict[matchesStrictLen++] = targetI
+ ++searchI; if(searchI === searchLen) { successStrict = true; break }
+ ++targetI
} else {
- var isMatch = searchLowerCodes[searchI] === targetLowerCodes[targetI];
- if(isMatch) {
- matchesStrict[matchesStrictLen++] = targetI;
- ++searchI; if(searchI === searchLen) { successStrict = true; break; }
- ++targetI;
- } else {
- targetI = nextBeginningIndexes[targetI];
- }
+ targetI = nextBeginningIndexes[targetI]
}
- }}
-
- { // tally up the score & keep track of matches for highlighting later
- if(successStrict) { var matchesBest = matchesStrict; var matchesBestLen = matchesStrictLen; }
- else { var matchesBest = matchesSimple; var matchesBestLen = matchesSimpleLen; }
- var score = 0;
- var lastTargetI = -1;
- for(var i = 0; i < searchLen; ++i) { var targetI = matchesBest[i];
- // score only goes down if they're not consecutive
- if(lastTargetI !== targetI - 1) {score -= targetI;}
- lastTargetI = targetI;
- }
- if(!successStrict) {score *= 1000;}
- score -= targetLen - searchLen;
- prepared.score = score;
- prepared.indexes = new Array(matchesBestLen); for(var i = matchesBestLen - 1; i >= 0; --i) {prepared.indexes[i] = matchesBest[i];}
+ }
+ }
+
+ // check if it's a substring match
+ var substringIndex = prepared._targetLower.indexOf(searchLower, matchesSimple[0]) // perf: this is slow
+ var isSubstring = ~substringIndex
+ if(isSubstring && !successStrict) { // rewrite the indexes from basic to the substring
+ for(var i=0; i<matchesSimpleLen; ++i) matchesSimple[i] = substringIndex+i
+ }
+ var isSubstringBeginning = false
+ if(isSubstring) {
+ isSubstringBeginning = prepared._nextBeginningIndexes[substringIndex-1] === substringIndex
+ }
+
+ { // tally up the score & keep track of matches for highlighting later
+ if(successStrict) { var matchesBest = matchesStrict; var matchesBestLen = matchesStrictLen }
+ else { var matchesBest = matchesSimple; var matchesBestLen = matchesSimpleLen }
+
+ var score = 0
+
+ var extraMatchGroupCount = 0
+ for(var i = 1; i < searchLen; ++i) {
+ if(matchesBest[i] - matchesBest[i-1] !== 1) {score -= matchesBest[i]; ++extraMatchGroupCount}
+ }
+ var unmatchedDistance = matchesBest[searchLen-1] - matchesBest[0] - (searchLen-1)
+
+ score -= (12+unmatchedDistance) * extraMatchGroupCount // penality for more groups
+
+ if(matchesBest[0] !== 0) score -= matchesBest[0]*matchesBest[0]*.2 // penality for not starting near the beginning
+
+ if(!successStrict) {
+ score *= 1000
+ } else {
+ // successStrict on a target with too many beginning indexes loses points for being a bad target
+ var uniqueBeginningIndexes = 1
+ for(var i = nextBeginningIndexes[0]; i < targetLen; i=nextBeginningIndexes[i]) ++uniqueBeginningIndexes
- return prepared;
+ if(uniqueBeginningIndexes > 24) score *= (uniqueBeginningIndexes-24)*10 // quite arbitrary numbers here ...
}
- },
-
- prepareLowerCodes: function(str) {
- var strLen = str.length;
- var lowerCodes = []; // new Array(strLen) sparse array is too slow
- var lower = str.toLowerCase();
- for(var i = 0; i < strLen; ++i) {lowerCodes[i] = lower.charCodeAt(i);}
- return lowerCodes;
- },
- prepareBeginningIndexes: function(target) {
- var targetLen = target.length;
- var beginningIndexes = []; var beginningIndexesLen = 0;
- var wasUpper = false;
- var wasAlphanum = false;
- for(var i = 0; i < targetLen; ++i) {
- var targetCode = target.charCodeAt(i);
- var isUpper = targetCode>=65&&targetCode<=90;
- var isAlphanum = isUpper || targetCode>=97&&targetCode<=122 || targetCode>=48&&targetCode<=57;
- var isBeginning = isUpper && !wasUpper || !wasAlphanum || !isAlphanum;
- wasUpper = isUpper;
- wasAlphanum = isAlphanum;
- if(isBeginning) {beginningIndexes[beginningIndexesLen++] = i;}
+
+ if(isSubstring) score /= 1+searchLen*searchLen*1 // bonus for being a full substring
+ if(isSubstringBeginning) score /= 1+searchLen*searchLen*1 // bonus for substring starting on a beginningIndex
+
+ score -= targetLen - searchLen // penality for longer targets
+ prepared.score = score
+
+ for(var i = 0; i < matchesBestLen; ++i) prepared._indexes[i] = matchesBest[i]
+ prepared._indexes.len = matchesBestLen
+
+ return prepared
+ }
+ }
+ var algorithmSpaces = (preparedSearch, target) => {
+ var seen_indexes = new Set()
+ var score = 0
+ var result = NULL
+
+ var first_seen_index_last_search = 0
+ var searches = preparedSearch.spaceSearches
+ for(var i=0; i<searches.length; ++i) {
+ var search = searches[i]
+
+ result = algorithm(search, target)
+ if(result === NULL) return NULL
+
+ score += result.score
+
+ // dock points based on order otherwise "c man" returns Manifest.cpp instead of CheatManager.h
+ if(result._indexes[0] < first_seen_index_last_search) {
+ score -= first_seen_index_last_search - result._indexes[0]
}
- return beginningIndexes;
- },
- prepareNextBeginningIndexes: function(target) {
- var targetLen = target.length;
- var beginningIndexes = fuzzysort.prepareBeginningIndexes(target);
- var nextBeginningIndexes = []; // new Array(targetLen) sparse array is too slow
- var lastIsBeginning = beginningIndexes[0];
- var lastIsBeginningI = 0;
- for(var i = 0; i < targetLen; ++i) {
- if(lastIsBeginning > i) {
- nextBeginningIndexes[i] = lastIsBeginning;
- } else {
- lastIsBeginning = beginningIndexes[++lastIsBeginningI];
- nextBeginningIndexes[i] = lastIsBeginning===undefined ? targetLen : lastIsBeginning;
- }
+ first_seen_index_last_search = result._indexes[0]
+
+ for(var j=0; j<result._indexes.len; ++j) seen_indexes.add(result._indexes[j])
+ }
+
+ // allows a search with spaces that's an exact substring to score well
+ var allowSpacesResult = algorithm(preparedSearch, target, /*allowSpaces=*/true)
+ if(allowSpacesResult !== NULL && allowSpacesResult.score > score) {
+ return allowSpacesResult
+ }
+
+ result.score = score
+
+ var i = 0
+ for (let index of seen_indexes) result._indexes[i++] = index
+ result._indexes.len = i
+
+ return result
+ }
+
+
+ var prepareLowerInfo = (str) => {
+ var strLen = str.length
+ var lower = str.toLowerCase()
+ var lowerCodes = [] // new Array(strLen) sparse array is too slow
+ var bitflags = 0
+ var containsSpace = false // space isn't stored in bitflags because of how searching with a space works
+
+ for(var i = 0; i < strLen; ++i) {
+ var lowerCode = lowerCodes[i] = lower.charCodeAt(i)
+
+ if(lowerCode === 32) {
+ containsSpace = true
+ continue // it's important that we don't set any bitflags for space
}
- return nextBeginningIndexes;
- },
-
- cleanup: cleanup,
- new: fuzzysortNew,
- };
- return fuzzysort;
-} // fuzzysortNew
-
-// This stuff is outside fuzzysortNew, because it's shared with instances of fuzzysort.new()
-var isNode = typeof require !== 'undefined' && typeof window === 'undefined';
-var MyMap = typeof Map === 'function' ? Map : function(){var s=Object.create(null);this.get=function(k){return s[k];};this.set=function(k,val){s[k]=val;return this;};this.clear=function(){s=Object.create(null);};};
-var preparedCache = new MyMap();
-var preparedSearchCache = new MyMap();
-var noResults = []; noResults.total = 0;
-var matchesSimple = []; var matchesStrict = [];
-function cleanup() { preparedCache.clear(); preparedSearchCache.clear(); matchesSimple = []; matchesStrict = []; }
-function defaultScoreFn(a) {
- var max = -9007199254740991;
- for (var i = a.length - 1; i >= 0; --i) {
- var result = a[i]; if(result === null) {continue;}
- var score = result.score;
- if(score > max) {max = score;}
+
+ var bit = lowerCode>=97&&lowerCode<=122 ? lowerCode-97 // alphabet
+ : lowerCode>=48&&lowerCode<=57 ? 26 // numbers
+ // 3 bits available
+ : lowerCode<=127 ? 30 // other ascii
+ : 31 // other utf8
+ bitflags |= 1<<bit
+ }
+
+ return {lowerCodes:lowerCodes, bitflags:bitflags, containsSpace:containsSpace, _lower:lower}
}
- if(max === -9007199254740991) {return null;}
- return max;
-}
-
-// prop = 'key' 2.5ms optimized for this case, seems to be about as fast as direct obj[prop]
-// prop = 'key1.key2' 10ms
-// prop = ['key1', 'key2'] 27ms
-function getValue(obj, prop) {
- var tmp = obj[prop]; if(tmp !== undefined) {return tmp;}
- var segs = prop;
- if(!Array.isArray(prop)) {segs = prop.split('.');}
- var len = segs.length;
- var i = -1;
- while (obj && (++i < len)) {obj = obj[segs[i]];}
- return obj;
-}
-
-function isObj(x) { return typeof x === 'object'; } // faster as a function
-
-// Hacked version of https://github.com/lemire/FastPriorityQueue.js
-var fastpriorityqueue=function(){var r=[],o=0,e={};function n(){for(var e=0,n=r[e],c=1;c<o;){var f=c+1;e=c,f<o&&r[f].score<r[c].score&&(e=f),r[e-1>>1]=r[e],c=1+(e<<1);}for(var a=e-1>>1;e>0&&n.score<r[a].score;a=(e=a)-1>>1){r[e]=r[a];}r[e]=n;}return e.add=function(e){var n=o;r[o++]=e;for(var c=n-1>>1;n>0&&e.score<r[c].score;c=(n=c)-1>>1){r[n]=r[c];}r[n]=e;},e.poll=function(){if(0!==o){var e=r[0];return r[0]=r[--o],n(),e;}},e.peek=function(e){if(0!==o){return r[0];}},e.replaceTop=function(o){r[0]=o,n();},e;};
-var q = fastpriorityqueue(); // reuse this, except for async, it needs to make its own
-
-return fuzzysortNew();
-}); // UMD
-
-// TODO: (performance) wasm version!?
-// TODO: (performance) threads?
-// TODO: (performance) avoid cache misses
-// TODO: (performance) preparedCache is a memory leak
+ var prepareBeginningIndexes = (target) => {
+ var targetLen = target.length
+ var beginningIndexes = []; var beginningIndexesLen = 0
+ var wasUpper = false
+ var wasAlphanum = false
+ for(var i = 0; i < targetLen; ++i) {
+ var targetCode = target.charCodeAt(i)
+ var isUpper = targetCode>=65&&targetCode<=90
+ var isAlphanum = isUpper || targetCode>=97&&targetCode<=122 || targetCode>=48&&targetCode<=57
+ var isBeginning = isUpper && !wasUpper || !wasAlphanum || !isAlphanum
+ wasUpper = isUpper
+ wasAlphanum = isAlphanum
+ if(isBeginning) beginningIndexes[beginningIndexesLen++] = i
+ }
+ return beginningIndexes
+ }
+ var prepareNextBeginningIndexes = (target) => {
+ var targetLen = target.length
+ var beginningIndexes = prepareBeginningIndexes(target)
+ var nextBeginningIndexes = [] // new Array(targetLen) sparse array is too slow
+ var lastIsBeginning = beginningIndexes[0]
+ var lastIsBeginningI = 0
+ for(var i = 0; i < targetLen; ++i) {
+ if(lastIsBeginning > i) {
+ nextBeginningIndexes[i] = lastIsBeginning
+ } else {
+ lastIsBeginning = beginningIndexes[++lastIsBeginningI]
+ nextBeginningIndexes[i] = lastIsBeginning===undefined ? targetLen : lastIsBeginning
+ }
+ }
+ return nextBeginningIndexes
+ }
+
+
+ var cleanup = () => { preparedCache.clear(); preparedSearchCache.clear(); matchesSimple = []; matchesStrict = [] }
+
+ var preparedCache = new Map()
+ var preparedSearchCache = new Map()
+ var matchesSimple = []; var matchesStrict = []
+
+
+ // for use with keys. just returns the maximum score
+ var defaultScoreFn = (a) => {
+ var max = INT_MIN
+ var len = a.length
+ for (var i = 0; i < len; ++i) {
+ var result = a[i]; if(result === NULL) continue
+ var score = result.score
+ if(score > max) max = score
+ }
+ if(max === INT_MIN) return NULL
+ return max
+ }
+
+ // prop = 'key' 2.5ms optimized for this case, seems to be about as fast as direct obj[prop]
+ // prop = 'key1.key2' 10ms
+ // prop = ['key1', 'key2'] 27ms
+ var getValue = (obj, prop) => {
+ var tmp = obj[prop]; if(tmp !== undefined) return tmp
+ var segs = prop
+ if(!Array.isArray(prop)) segs = prop.split('.')
+ var len = segs.length
+ var i = -1
+ while (obj && (++i < len)) obj = obj[segs[i]]
+ return obj
+ }
+
+ var isObj = (x) => { return typeof x === 'object' } // faster as a function
+ // var INT_MAX = 9007199254740991; var INT_MIN = -INT_MAX
+ var INT_MAX = Infinity; var INT_MIN = -INT_MAX
+ var noResults = []; noResults.total = 0
+ var NULL = null
+
+
+ // Hacked version of https://github.com/lemire/FastPriorityQueue.js
+ var fastpriorityqueue=r=>{var e=[],o=0,a={},v=r=>{for(var a=0,v=e[a],c=1;c<o;){var s=c+1;a=c,s<o&&e[s].score<e[c].score&&(a=s),e[a-1>>1]=e[a],c=1+(a<<1)}for(var f=a-1>>1;a>0&&v.score<e[f].score;f=(a=f)-1>>1)e[a]=e[f];e[a]=v};return a.add=(r=>{var a=o;e[o++]=r;for(var v=a-1>>1;a>0&&r.score<e[v].score;v=(a=v)-1>>1)e[a]=e[v];e[a]=r}),a.poll=(r=>{if(0!==o){var a=e[0];return e[0]=e[--o],v(),a}}),a.peek=(r=>{if(0!==o)return e[0]}),a.replaceTop=(r=>{e[0]=r,v()}),a}
+ var q = fastpriorityqueue() // reuse this
+
+
+ // fuzzysort is written this way for minification. all names are mangeled unless quoted
+ return {'single':single, 'go':go, 'highlight':highlight, 'prepare':prepare, 'indexes':indexes, 'cleanup':cleanup}
+}) // UMD
+
+// TODO: (feature) frecency
+// TODO: (perf) use different sorting algo depending on the # of results?
+// TODO: (perf) preparedCache is a memory leak
// TODO: (like sublime) backslash === forwardslash
-// TODO: (like sublime) spaces: "a b" should do 2 searches 1 for a and 1 for b
-// TODO: (scoring) garbage in targets that allows most searches to strict match need a penality
-// TODO: (performance) idk if allowTypo is optimized
+// TODO: (perf) prepareSearch seems slow
diff --git a/static/js/hoverfix.ts b/static/js/hoverfix.ts
new file mode 100644
index 0000000..b6d1a5b
--- /dev/null
+++ b/static/js/hoverfix.ts
@@ -0,0 +1,64 @@
+(function () {
+ /*/
+ * Element.closest() polyfill
+ * https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
+ * https://github.com/idmadj/element-closest-polyfill#readme
+ * Copyright (C) Abdelmadjid Hammou
+ * Licence: ISC | https://www.isc.org/licenses/
+ /*/
+
+ if (typeof Element !== "undefined") {
+ if (!Element.prototype.matches) {
+ Element.prototype.matches = Element.prototype.msMatchesSelector ||
+ Element.prototype.webkitMatchesSelector;
+ }
+ if (!Element.prototype.closest) {
+ Element.prototype.closest = function (s) {
+ var el = this;
+ do {
+ if (el.matches(s)) return el;
+ el = el.parentElement || el.parentNode;
+ } while (el !== null && el.nodeType === 1);
+ return null;
+ };
+ }
+ }
+
+ /*/
+ * -----------------------------------------------------------------
+ /*/
+
+ const disabled = "0s";
+
+ function walk(children, callback) {
+ for (let i = 0; i < children.length; i++) {
+ callback(children[i]);
+ walk(children[i].children, callback);
+ }
+ }
+
+ self.addEventListener("mousemove", function (event) {
+ if (typeof event.target.closest !== "function") return;
+ tree = event.target.closest("figure") || event.target.closest("article");
+
+ if (tree !== null) {
+ walk(tree.children, function (element) {
+ const delay = self.getComputedStyle(element).getPropertyValue(
+ "transition-delay",
+ );
+ if (delay !== disabled) {
+ element.style.setProperty("visibility", "hidden");
+ }
+ });
+
+ walk(tree.children, function (element) {
+ const delay = self.getComputedStyle(element).getPropertyValue(
+ "transition-delay",
+ );
+ if (delay !== disabled) {
+ element.style.removeProperty("visibility");
+ }
+ });
+ }
+ });
+})();
diff --git a/static/js/bundle.ts b/static/js/index-bundle.ts
index ac858c4..61dc414 100644
--- a/static/js/bundle.ts
+++ b/static/js/index-bundle.ts
@@ -1,4 +1,4 @@
-import { bundle } from "https://deno.land/x/emit@0.17.0/mod.ts";
+import { bundle } from "https://deno.land/x/emit@0.26.0/mod.ts";
function removeSourceMap(text): string {
return text.replace(/^\/\/# sourceMappingURL.*$/gm, "").trim();
@@ -14,8 +14,5 @@ const fileSize = ((await Deno.stat(file)).size / 1024).toFixed(2) + "kB";
await Deno.writeTextFile(file, output, {});
-console.log(
- `Bundle file:///${file}` +
- "\n" +
- `Emit file:///${file} (${fileSize})`,
-);
+console.log("INFO:", `Bundle file:///${file}`);
+console.log("INFO:", `Emit file:///${file} (${fileSize})`);
diff --git a/static/js/index.ts b/static/js/index.ts
index e00907b..517445c 100644
--- a/static/js/index.ts
+++ b/static/js/index.ts
@@ -1,8 +1,13 @@
import "./pager.ts";
+import "./update.ts";
import "./plumber.ts";
import "./instantpage.ts";
import "./contextmenu.ts";
import "./fixedsearch.ts";
+import "./autoplay.ts";
+import "./hoverfix.ts";
+import "./forms.ts";
+import "./domfilter.ts";
import "./timeago.ts";
-console.log("Surface Control: Complete");
+console.log("INFO: Surface Control Complete");
diff --git a/static/js/infinitescroll.ts b/static/js/infinitescroll.ts
new file mode 100644
index 0000000..bef477b
--- /dev/null
+++ b/static/js/infinitescroll.ts
@@ -0,0 +1,78 @@
+(function () {
+ const cookiesDisabled = !navigator.cookieEnabled;
+
+ if (cookiesDisabled) {
+ document.cookie = "disabled";
+ document.cookie.indexOf("disabled");
+ return console.warn("WARNING: Native pagination fallback due to cookie restrictions");
+ }
+
+ type percent = number;
+
+ function fetch(url, method, callback, fallback) {
+ const http = new XMLHttpRequest();
+ http.onreadystatechange = function () {
+ if (callback && http.readyState === 4) {
+ if (http.status === 200) callback(http);
+ else fallback(http);
+ }
+ };
+ http.open(method, url);
+ http.send();
+ return http;
+ }
+
+ let abort = false;
+
+ const key = "config.scroll.infinite";
+
+ if (!localStorage[key]) localStorage[key] = "";
+
+ ["scroll", "DOMContentLoaded"].forEach(function (event) {
+ self.addEventListener(event, function () {
+ if (abort) return;
+
+ const remaining = Math.abs(
+ document.documentElement.scrollHeight
+ - document.documentElement.clientHeight
+ - self.pageYOffset
+ );
+
+ const traversed = self.pageYOffset;
+ const journey = remaining + traversed;
+ const ratio: percent = traversed / journey * 100;
+ const threshold = ratio >= 50;
+ const pagination = document.querySelector('[data-type="pagination"]');
+
+ if (!pagination) return;
+
+ const main = document.querySelector("main");
+ const count = main.childNodes.length;
+ const next = pagination.querySelector('[rel="next"]');
+ const end = pagination.querySelector('[rel="last"]').title === "hidden";
+ const asynchronous = document.querySelectorAll("[data-type='pagination']").length !== 1;
+
+ if (end || asynchronous) return pagination.remove();
+
+ if (threshold && (pagination.remove() === undefined)) {
+ fetch(next.href, "GET", function (http) {
+ const page = (new DOMParser()).parseFromString(http.responseText, "text/html");
+ const items = page.querySelector("main").childNodes;
+ const paginate = page.querySelector('[data-type="pagination"]');
+
+ for (let i = 0; i < items.length; i++) main.appendChild(items[i]);
+
+ main.after(paginate);
+
+ console.log("INFO: Fetch", next.href, items);
+
+ }, function (http) {
+ console.warn("WARNING: Switching to native pagination", http);
+ main.insertAdjacentElement("afterend", pagination);
+ abort = true;
+ });
+ }
+ console.log("INFO:", "r:", remaining, "t:", traversed, "j:", journey, "%:", ratio, "c:", count);
+ });
+ });
+})();
diff --git a/static/js/instantpage.ts b/static/js/instantpage.ts
index 7c78a6f..1b5ed36 100644
--- a/static/js/instantpage.ts
+++ b/static/js/instantpage.ts
@@ -1,292 +1,82 @@
-(function () {
- self.addEventListener("DOMContentLoaded", function () {
- /*! instant.page v5.1.0 - (C) 2019-2020 Alexandre Dieulot - https://instant.page/license */
-
- let mouseoverTimer;
- let lastTouchTimestamp;
- const prefetches = new Set();
- const prefetchElement = document.createElement("link");
- const isSupported = prefetchElement.relList &&
- prefetchElement.relList.supports &&
- prefetchElement.relList.supports("prefetch") &&
- window.IntersectionObserver &&
- "isIntersecting" in IntersectionObserverEntry.prototype;
- const allowQueryString = "instantAllowQueryString" in document.body.dataset;
- const allowExternalLinks = "instantAllowExternalLinks" in
- document.body.dataset;
- const useWhitelist = "instantWhitelist" in document.body.dataset;
- const mousedownShortcut = "instantMousedownShortcut" in
- document.body.dataset;
- const DELAY_TO_NOT_BE_CONSIDERED_A_TOUCH_INITIATED_ACTION = 1111;
+/*
+ * Instant Page Copyright (C) 2019-2023 Alexandre Dieulot
+ * https://github.com/instantpage/instant.page/blob/master/LICENSE
+ * Licence: MIT | https://mit-license.org/
+*/
- let delayOnHover = 65;
- let useMousedown = false;
- let useMousedownOnly = false;
- let useViewport = false;
-
- if ("instantIntensity" in document.body.dataset) {
- const intensity = document.body.dataset.instantIntensity;
+(function () {
- if (intensity.substr(0, "mousedown".length) == "mousedown") {
- useMousedown = true;
- if (intensity == "mousedown-only") {
- useMousedownOnly = true;
- }
- } else if (intensity.substr(0, "viewport".length) == "viewport") {
- if (
- !(navigator.connection &&
- (navigator.connection.saveData ||
- (navigator.connection.effectiveType &&
- navigator.connection.effectiveType.includes("2g"))))
- ) {
- if (intensity == "viewport") {
- /* Biggest iPhone resolution (which we want): 414 × 896 = 370944
- * Small 7" tablet resolution (which we don’t want): 600 × 1024 = 614400
- * Note that the viewport (which we check here) is smaller than the resolution due to the UI’s chrome */
- if (
- document.documentElement.clientWidth *
- document.documentElement.clientHeight < 450000
- ) {
- useViewport = true;
- }
- } else if (intensity == "viewport-all") {
- useViewport = true;
- }
- }
- } else {
- const milliseconds = parseInt(intensity);
- if (!isNaN(milliseconds)) {
- delayOnHover = milliseconds;
- }
- }
+ /*/
+ * Element.closest() polyfill
+ * https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
+ * https://github.com/idmadj/element-closest-polyfill#readme
+ * Copyright (C) Abdelmadjid Hammou
+ * Licence: ISC | https://www.isc.org/licenses/
+ /*/
+
+ if (typeof Element !== "undefined") {
+ if (!Element.prototype.matches) {
+ Element.prototype.matches = Element.prototype.msMatchesSelector ||
+ Element.prototype.webkitMatchesSelector;
}
-
- if (isSupported) {
- const eventListenersOptions = {
- capture: true,
- passive: true,
+ if (!Element.prototype.closest) {
+ Element.prototype.closest = function (s) {
+ var el = this;
+ do {
+ if (el.matches(s)) return el;
+ el = el.parentElement || el.parentNode;
+ } while (el !== null && el.nodeType === 1);
+ return null;
};
-
- if (!useMousedownOnly) {
- document.addEventListener(
- "touchstart",
- touchstartListener,
- eventListenersOptions,
- );
- }
-
- if (!useMousedown) {
- document.addEventListener(
- "mouseover",
- mouseoverListener,
- eventListenersOptions,
- );
- } else if (!mousedownShortcut) {
- document.addEventListener(
- "mousedown",
- mousedownListener,
- eventListenersOptions,
- );
- }
-
- if (mousedownShortcut) {
- document.addEventListener(
- "mousedown",
- mousedownShortcutListener,
- eventListenersOptions,
- );
- }
-
- if (useViewport) {
- let triggeringFunction;
- if (window.requestIdleCallback) {
- triggeringFunction = function (callback) {
- requestIdleCallback(callback, {
- timeout: 1500,
- });
- };
- } else {
- triggeringFunction = function (callback) {
- callback();
- };
- }
-
- triggeringFunction(function () {
- const intersectionObserver = new IntersectionObserver(
- function (entries) {
- entries.forEach(function (entry) {
- if (entry.isIntersecting) {
- const linkElement = entry.target;
- intersectionObserver.unobserve(linkElement);
- preload(linkElement.href);
- }
- });
- },
- );
-
- document.querySelectorAll("a").forEach(function (linkElement) {
- if (isPreloadable(linkElement)) {
- intersectionObserver.observe(linkElement);
- }
- });
- });
- }
}
+ }
- function touchstartListener(event) {
- /* Chrome on Android calls mouseover before touchcancel so `lastTouchTimestamp`
- * must be assigned on touchstart to be measured on mouseover. */
- lastTouchTimestamp = performance.now();
+ /*/
+ * -----------------------------------------------------------------
+ /*/
- const linkElement = event.target.closest("a");
-
- if (!isPreloadable(linkElement)) {
- return;
- }
-
- preload(linkElement.href);
- }
-
- function mouseoverListener(event) {
- if (
- performance.now() - lastTouchTimestamp <
- DELAY_TO_NOT_BE_CONSIDERED_A_TOUCH_INITIATED_ACTION
- ) {
- return;
+ function fetch(url, method, callback) {
+ const http = new XMLHttpRequest();
+ http.onreadystatechange = function () {
+ if (http.readyState === 4 && http.status === 200) {
+ if (callback) callback(http);
}
+ };
+ http.open(method, url);
+ http.send();
+ return http;
+ }
- const linkElement = event.target.closest("a");
-
- if (!isPreloadable(linkElement)) {
- return;
- }
-
- linkElement.addEventListener("mouseout", mouseoutListener, {
- passive: true,
- });
-
- mouseoverTimer = setTimeout(function () {
- preload(linkElement.href);
- mouseoverTimer = undefined;
- }, delayOnHover);
- }
-
- function mousedownListener(event) {
- const linkElement = event.target.closest("a");
-
- if (!isPreloadable(linkElement)) {
- return;
- }
-
- preload(linkElement.href);
- }
-
- function mouseoutListener(event) {
- if (
- event.relatedTarget &&
- event.target.closest("a") == event.relatedTarget.closest("a")
- ) {
- return;
- }
-
- if (mouseoverTimer) {
- clearTimeout(mouseoverTimer);
- mouseoverTimer = undefined;
- }
- }
-
- function mousedownShortcutListener(event) {
- if (
- performance.now() - lastTouchTimestamp <
- DELAY_TO_NOT_BE_CONSIDERED_A_TOUCH_INITIATED_ACTION
- ) {
- return;
- }
-
- const linkElement = event.target.closest("a");
-
- if (event.which > 1 || event.metaKey || event.ctrlKey) {
- return;
- }
-
- if (!linkElement) {
- return;
- }
-
- linkElement.addEventListener("click", function (event) {
- if (event.detail == 1337) {
- return;
- }
-
- event.preventDefault();
- }, { capture: true, passive: false, once: true });
+ self.addEventListener("DOMContentLoaded", function () {
- const customEvent = new MouseEvent("click", {
- view: window,
- bubbles: true,
- cancelable: false,
- detail: 1337,
+ ["mouseout", "mousedown", "touchstart"].forEach(function (event) {
+ self.addEventListener(event, function (event) {
+ const url = event.target.closest("a");
+ if (preloadable(url) === undefined) return;
+ preload(url.href);
});
- linkElement.dispatchEvent(customEvent);
- }
-
- function isPreloadable(linkElement) {
- if (!linkElement || !linkElement.href) {
- return;
- }
+ });
- if (useWhitelist && !("instant" in linkElement.dataset)) {
- return;
+ function preloadable(url) {
+ switch (true) {
+ case (url === null || url.href === null): return;
+ case (url.origin !== location.origin): return;
+ case (["http:", "https:"].includes(url.protocol) === null): return;
+ case (url.protocol === "http:" && location.protocol === "https:"): return;
+ case (url.hash && url.pathname + url.search == location.pathname + location.search): return;
+ default: return true;
}
-
- if (
- !allowExternalLinks && linkElement.origin != location.origin &&
- !("instant" in linkElement.dataset)
- ) {
- return;
- }
-
- if (!["http:", "https:"].includes(linkElement.protocol)) {
- return;
- }
-
- if (linkElement.protocol == "http:" && location.protocol == "https:") {
- return;
- }
-
- if (
- !allowQueryString && linkElement.search &&
- !("instant" in linkElement.dataset)
- ) {
- return;
- }
-
- if (
- linkElement.hash &&
- linkElement.pathname + linkElement.search ==
- location.pathname + location.search
- ) {
- return;
- }
-
- if ("noInstant" in linkElement.dataset) {
- return;
- }
-
- return true;
}
function preload(url) {
- if (prefetches.has(url)) {
- return;
- }
-
const prefetcher = document.createElement("link");
- prefetcher.rel = "prefetch";
+ prefetcher.rel = "custom-prefetch";
prefetcher.href = url;
+ const selector = 'link[rel="'.concat(prefetcher.rel, '"][href="').concat(prefetcher.href, '"]');
+ const prefetched = document.head.contains(document.querySelector(selector));
+ if (prefetched) { return; }
document.head.appendChild(prefetcher);
-
- prefetches.add(url);
+ fetch(url, "GET", function () {});
}
});
})();
diff --git a/static/js/pager.ts b/static/js/pager.ts
index 01d40d7..ebaf67a 100644
--- a/static/js/pager.ts
+++ b/static/js/pager.ts
@@ -4,44 +4,60 @@
if (cookiesDisabled) {
document.cookie = "disabled";
document.cookie.indexOf("disabled");
- return console.log("Pager is disabled due to cookie restrictions.");
+ return console.warn("WARNING: Pager disabled due to cookie restrictions");
}
- let settings = { pager: {} };
+ let seek;
+ let pager = {};
- const url = self.location.href.split("#")[0].split("?")[0];
+ const state = "on";
+ const key = "config.scroll.pager.urls";
- const scrollRestore = (url) => {
- if (history.scrollRestoration) history.scrollRestoration = "manual";
- if (localStorage["settings"]) {
- settings = JSON.parse(localStorage["settings"]);
- }
- const fragment = document.getElementById(location.hash.slice(1));
- const fragmentInURL = self.location.hash.length > 0;
- if (fragmentInURL && document.body.contains(fragment)) {
- settings["pager"][url] = self.pageYOffset;
- localStorage["settings"] = JSON.stringify(settings);
- fragment.scrollIntoView();
- self.addEventListener("load", function () {
- fragment.scrollIntoView();
- });
- return;
- }
- if (settings["pager"][url] > 0) {
- self.scrollTo(0, settings["pager"][url]);
- return;
+ if (!localStorage[key]) localStorage[key] = JSON.stringify(pager);
+
+ let url = function () { return self.location.href.split("#")[0].split("?")[0]; };
+
+ const scrollHash = function (url) {
+ const hash = self.location.hash;
+ const fragment = hash.slice(1) && document.getElementById(hash.slice(1));
+ const fragmented = hash.length > 0;
+ const hashed = fragmented && document.body.contains(fragment);
+ if (hashed) {
+ self.location.hash = hash;
+ self.location.href = hash;
+ if ("scroll-margin-top" in document.body.style === false && fragment.textContent !== "") {
+ self.scrollBy(0, -6 * parseFloat(getComputedStyle(document.documentElement).fontSize));
+ }
}
- settings["pager"][url] = self.pageYOffset;
- localStorage["settings"] = JSON.stringify(settings);
+ return hashed;
};
- const scrollTrack = (url) => {
+ const scrollRestore = function (url) {
+ if (history.scrollRestoration) history.scrollRestoration = "manual";
+ if (scrollHash(url)) return;
+ pager = JSON.parse(localStorage[key]);
+ if (pager[url] > 0) {
+ clearInterval(seek);
+ self.scrollTo(0, pager[url]);
+ let i = 0; return seek = setInterval(function (position) {
+ i++; if (i > 100) clearInterval(seek);
+ if (document.documentElement.scrollHeight >= position + document.documentElement.clientHeight) {
+ clearInterval(seek); self.scrollTo(0, position);
+ }
+ }, 4, pager[url]);
+ } else self.scrollTo(0, 0);
+ pager[url] = self.pageYOffset;
+ localStorage[key] = JSON.stringify(pager);
+ };
+
+ const scrollTrack = function (url) {
const currentPosition = self.pageYOffset;
- settings["pager"][url] = currentPosition;
- localStorage["settings"] = JSON.stringify(settings);
+ pager = JSON.parse(localStorage[key]);
+ pager[url] = currentPosition;
+ localStorage[key] = JSON.stringify(pager);
};
- const backTrack = (back, up, event) => {
+ const scrollReverse = function (back, up, event) {
if (document.body.contains(up) && up.contains(event.target)) {
event.preventDefault();
window.scrollTo(0, 0);
@@ -53,20 +69,27 @@
}
};
- self.addEventListener("DOMContentLoaded", function () {
- scrollRestore(url);
- self.addEventListener("click", function (event) {
- const up = document.getElementById("top");
- const back = document.getElementById("back");
- backTrack(back, up, event);
- });
- });
-
- self.addEventListener("scroll", function () {
- scrollTrack(url);
- });
+ ["DOMContentLoaded", "pageshow", "hashchange", "URLChangedCustomEvent"].forEach(
+ function (event) {
+ self.addEventListener(event, function (event) {
+ if (event.type === "pageshow") {
+ return event.persisted && self.scrollTo(0, pager[url()]);
+ }
+ if (event.type === "DOMContentLoaded") {
+ self.addEventListener("click", function (event) {
+ const up = document.getElementById("top");
+ const back = document.getElementById("back");
+ scrollReverse(back, up, event);
+ });
+ }
+ scrollRestore(url());
+ });
+ },
+ );
- self.addEventListener("hashchange", function () {
- scrollRestore(url);
+ ["click", "touchstart", "scroll"].forEach(function (event) {
+ self.addEventListener(event, function () {
+ scrollTrack(url());
+ });
});
})();
diff --git a/static/js/plumber.ts b/static/js/plumber.ts
index 8e551fd..d425769 100644
--- a/static/js/plumber.ts
+++ b/static/js/plumber.ts
@@ -1,8 +1,8 @@
/**
- * Plumber based on and inspired by
- * Dictionary Access Copyright (C) 2006, Paul Lutus
+ * Plumber based on and inspired by;
+ * Dictionary Access Copyright (C) 2006 Paul Lutus
* https://arachnoid.com/javascript/dictionary_access.js
- * LICENSE: GPLv2+
+ * Licence: GPLv2+ | https://www.gnu.org/licenses/gpl-3.0.txt
*/
(function () {
diff --git a/static/js/timeago.ts b/static/js/timeago.ts
index af974fc..7c1973d 100644
--- a/static/js/timeago.ts
+++ b/static/js/timeago.ts
@@ -8,49 +8,86 @@
style: "long",
});
- const date = () => {
- [...document.querySelectorAll("time")]
- .forEach(
- (element) => {
- try {
- const time: millisecond = new Date(element.dateTime) || new Date();
- const interval: second = ((new Date().getTime() - time.getTime()) / 1000);
-
- const seconds: number = Math.floor(interval);
- const minutes: number = Math.floor(seconds / 60);
- const hours: number = Math.floor(minutes / 60);
- const days: number = Math.floor(hours / 24);
-
- if (Math.sign(seconds) === 1) {
- if (seconds <= 60) { return element.textContent = relative.format(-1 * seconds, "second",); }
- if (minutes <= 120) { return element.textContent = relative.format(-1 * minutes, "minute",); }
- if (hours <= 48) { return element.textContent = relative.format(-1 * hours, "hour",); }
- if (days <= 60) { return element.textContent = relative.format(-1 * days, "day",); }
- }
-
- if (Math.sign(seconds) === -1) {
- if (-1 * days >= 60) { return element.textContent = relative.format(-1 * days, "day",); }
- if (-1 * hours >= 48) { return element.textContent = relative.format(-1 * hours, "hour",); }
- if (-1 * minutes >= 120) { return element.textContent = relative.format(-1 * minutes, "minute",); }
- if (-1 * seconds >= 0) { return element.textContent = relative.format(-1 * seconds, "second",); }
- }
-
- } catch (error) {
- console.error(
- "Error: Unable to resolve relative time format!",
- error,
- );
- }
- },
- );
+ function viewport(element) {
+ const options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : { offset: 250 };
+ const view = element.getBoundingClientRect();
+ return view.top >= -options.offset
+ && view.left >= -options.offset
+ && view.bottom <= (self.innerHeight || document.documentElement.clientHeight) + options.offset
+ && view.right <= (self.innerWidth || document.documentElement.clientWidth) + options.offset;
}
+ const date = function (update) {
+ const elements = document.querySelectorAll("time");
+ for (let i = 0; i < elements.length; ++i) {
+ const offscreen = !viewport(elements[i]);
+ const hidden = elements[i].offsetParent === null;
+ if ((update === "viewport") && (offscreen || hidden)) continue;
+
+ (function (element) {
+ try {
+ if (element.dataset.type === "disabled") return;
+
+ const time: millisecond = new Date(element.dateTime) || new Date();
+ const interval: second = ((new Date().getTime() - time.getTime()) / 1000);
+
+ const seconds: number = Math.round(interval);
+ const minutes: number = Math.round(seconds / 60);
+ const hours: number = Math.round(minutes / 60);
+ const days: number = Math.round(hours / 24);
+
+ const past = Math.sign(seconds) === 1;
+ const future = Math.sign(seconds) === -1;
+
+ let tiny = function (string, place) {
+ return string.split(" ").slice(0, place).join(" ") + string.split(" ")[place].charAt(0);
+ }
+
+ if (element.dataset.type === "default") { tiny = function (string) { return string; }; }
+
+ if (element.dataset.type === "localDate") {
+ return element.textContent = new Intl.DateTimeFormat([], { dateStyle: "medium", }).format(time).replace(",", "");
+ }
+
+ if (element.dataset.type === "localTime") {
+ return element.textContent = new Intl.DateTimeFormat([], {
+ hour12: false,
+ timeStyle: "short",
+ }).format(time) + " " + new Intl.DateTimeFormat([], { timeZoneName: "short" }).format(time).split(" ")[1];
+ }
+
+ if (past) {
+ if (seconds <= 60) { return element.textContent = tiny(relative.format(-1 * seconds, "second",), 1); }
+ if (minutes <= 120) { return element.textContent = tiny(relative.format(-1 * minutes, "minute",), 1); }
+ if (hours <= 48) { return element.textContent = tiny(relative.format(-1 * hours, "hour",), 1); }
+ if (days <= 7) { return element.textContent = tiny(relative.format(-1 * days, "day",), 1); }
+ }
+
+ if (future) {
+ if (-1 * days >= 4) { return element.textContent = tiny(relative.format(-1 * days, "day",), 2); }
+ if (-1 * hours >= 3) { return element.textContent = tiny(relative.format(-1 * hours, "hour",), 2); }
+ if (-1 * minutes >= 2) { return element.textContent = tiny(relative.format(-1 * minutes, "minute",), 2); }
+ if (-1 * seconds >= 1) { return element.textContent = tiny(relative.format(-1 * seconds, "second",), 2); }
+ }
+ } catch (error) {
+ console.error("ERROR: Relative time resolution failed", error);
+ }
+ })(elements[i]);
+ }
+ };
+
const substitution = setInterval(date, 4);
- self.addEventListener("load", () => {
- setTimeout(() => {
+ ["scroll", "URLChangedCustomEvent"].forEach(function (event) {
+ self.addEventListener(event, function () {
+ date("viewport");
+ });
+ });
+
+ self.addEventListener("DOMContentLoaded", function () {
+ setTimeout(function () {
clearInterval(substitution);
- setInterval(date, 1000);
+ setInterval(function () { date("viewport"); }, 1000);
}, 1000);
});
})();
diff --git a/static/js/update.ts b/static/js/update.ts
new file mode 100644
index 0000000..df29f49
--- /dev/null
+++ b/static/js/update.ts
@@ -0,0 +1,84 @@
+(function () {
+
+ const cookiesDisabled = !navigator.cookieEnabled;
+
+ if (cookiesDisabled) {
+ document.cookie = "disabled";
+ document.cookie.indexOf("disabled");
+ return console.warn("WARNING: Update check disabled due to cookie restrictions");
+ }
+
+ function fetch(url, method, callback) {
+ const http = new XMLHttpRequest();
+ http.onreadystatechange = function () {
+ if (http.readyState === 4 && http.status === 200) {
+ if (callback) callback(http);
+ }
+ };
+ http.open(method, url);
+ http.setRequestHeader("Pragma", "no-cache");
+ http.setRequestHeader("Cache-Control", "no-cache");
+ http.send();
+ return http;
+ }
+
+ const state = "on";
+ const key = "config.update";
+
+ let stamps = {};
+
+ if (!sessionStorage[key + ".urls"]) sessionStorage[key + ".urls"] = JSON.stringify(stamps);
+
+ function update() {
+ const url = self.location.href.split("#")[0].split("?")[0];
+
+ const indicator = document.querySelector("a[data-update]");
+ if (indicator === null || indicator.dataset.update === "refresh") return;
+ const anchor = indicator.cloneNode();
+
+ fetch(url, "HEAD", function (request) {
+ const local = document.querySelector('meta[name="last-modified"]').content || document.lastModified;
+ const remote = request.getResponseHeader("last-modified") || '';
+ const modified = Date.parse(remote || local) > Date.parse(local);
+ const drift = Date.parse(remote || local) - Date.parse(local);
+
+ if (drift < 10000) return;
+
+ function reset() {
+ indicator.href = anchor.href;
+ indicator.setAttribute("id", anchor.id);
+ indicator.dataset.update = anchor.dataset.update;
+ }
+
+ stamps = JSON.parse(sessionStorage[key + ".urls"]);
+ if (stamps[url] === remote) return;
+ stamps[url] = remote;
+ sessionStorage[key + ".urls"] = JSON.stringify(stamps);
+
+ if (remote && modified) {
+ fetch(url, "GET", function () {
+ indicator.href = url.replace(/^https:/, "http:");
+ indicator.removeAttribute("id");
+ indicator.dataset.update = "refresh";
+ console.log("INFO: R: " + remote);
+ console.log("INFO: L: " + local);
+ console.log("INFO: D: " + drift);
+ console.log("INFO: M: " + modified);
+ });
+ }
+ });
+ }
+
+ let scrolled;
+ let delay = 1000;
+ let delayed = 0;
+
+ self.addEventListener("scroll", function () {
+ if (scrolled) clearTimeout(scrolled);
+ scrolled = setTimeout(function () { update(); delay = delay + delayed; delayed = delay - delayed; }, delay);
+ });
+
+ ["focus", "load", "URLChangedCustomEvent"].forEach(function (event) {
+ self.addEventListener(event, function () { update(); });
+ });
+})();