diff --git a/.gitignore b/.gitignore
index 451cbea..ead122e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,7 @@ __pycache__/
build/
develop-eggs/
dist/
+front-end/dist/
downloads/
eggs/
.eggs/
diff --git a/devdata/index-v2.tinysearch b/devdata/index-v2.tinysearch
index 1af5824..a2dc808 100644
Binary files a/devdata/index-v2.tinysearch and b/devdata/index-v2.tinysearch differ
diff --git a/front-end/assets/css/global.css b/front-end/assets/css/global.css
index b6f04dd..9d817a2 100644
--- a/front-end/assets/css/global.css
+++ b/front-end/assets/css/global.css
@@ -62,12 +62,9 @@ body {
height: 2rem;
}
-mwmbl-search-bar {
- width: 100%;
-}
-
.search-bar {
position: relative;
+ width: 100%;
}
.search-bar-input {
@@ -162,7 +159,7 @@ mwmbl-results, footer {
font-weight: var(--bold-font-weight);
}
-footer {
+.footer {
position: sticky;
top: 100vh;
margin-bottom: 25px;
@@ -323,4 +320,28 @@ a {
color: black;
text-decoration: none;
cursor: pointer;
+}
+
+.button {
+ background-color: var(--primary-color);
+ border: none;
+ color: white;
+ padding: 10px 20px;
+ margin: 10px;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: var(--default-font-size);
+ border-radius: 50px;
+ cursor: pointer;
+ flex-shrink: 0;
+ transition: background-color 200ms ease-in-out;
+}
+
+.button:hover {
+ background-color: var(--dark-color);
+}
+
+.login-info {
+ padding: 10px;
}
\ No newline at end of file
diff --git a/front-end/assets/fonts/phosphor/icons.css b/front-end/assets/fonts/phosphor/icons.css
index b9c4e82..42398b9 100644
--- a/front-end/assets/fonts/phosphor/icons.css
+++ b/front-end/assets/fonts/phosphor/icons.css
@@ -103,4 +103,20 @@ Phosphor Web Font
.ph-info-bold::before {
content: "\f88f";
-}
\ No newline at end of file
+}
+
+.ph-book-bold::before {
+ content: "\f6fb";
+}
+
+.ph-browser-bold::before {
+ content: "\f70d";
+}
+
+.ph-youtube-logo-bold::before {
+ content: "\fa5d";
+}
+
+.ph-chat-circle-text-bold::before {
+ content: "\f74c";
+}
diff --git a/front-end/assets/fonts/phosphor/unused.css b/front-end/assets/fonts/phosphor/unused.css
index 16da7b4..024afd1 100644
--- a/front-end/assets/fonts/phosphor/unused.css
+++ b/front-end/assets/fonts/phosphor/unused.css
@@ -13290,10 +13290,6 @@
content: "\f6fa";
}
-.ph-book-bold::before {
- content: "\f6fb";
-}
-
.ph-book-bookmark-bold::before {
content: "\f6fc";
}
@@ -13362,10 +13358,6 @@
content: "\f70c";
}
-.ph-browser-bold::before {
- content: "\f70d";
-}
-
.ph-browsers-bold::before {
content: "\f70e";
}
@@ -13614,10 +13606,6 @@
content: "\f74b";
}
-.ph-chat-circle-text-bold::before {
- content: "\f74c";
-}
-
.ph-chat-dots-bold::before {
content: "\f74d";
}
@@ -16750,10 +16738,6 @@
content: "\fa5c";
}
-.ph-youtube-logo-bold::before {
- content: "\fa5d";
-}
-
.ph-activity-fill::before {
content: "\fa5e";
}
diff --git a/front-end/config.js b/front-end/config.js
index e7abff4..0769191 100644
--- a/front-end/config.js
+++ b/front-end/config.js
@@ -11,18 +11,6 @@ export default {
publicApiURL: '/api/v1/',
// publicApiURL: 'http://localhost:5000/',
searchQueryParam: 'q',
- footerLinks: [
- {
- name: 'Github',
- icon: 'ph-github-logo-bold',
- href: 'https://github.com/mwmbl/mwmbl'
- },
- {
- name: 'Wiki',
- icon: 'ph-info-bold',
- href: 'https://github.com/mwmbl/mwmbl/wiki'
- }
- ],
commands: {
'go: ': 'https://',
'search: google.com ': 'https://www.google.com/search?q=',
diff --git a/front-end/package-lock.json b/front-end/package-lock.json
index 277dfb4..af0e754 100644
--- a/front-end/package-lock.json
+++ b/front-end/package-lock.json
@@ -5,6 +5,9 @@
"packages": {
"": {
"name": "front-end",
+ "dependencies": {
+ "chart.js": "^4.4.0"
+ },
"devDependencies": {
"@vitejs/plugin-legacy": "^2.3.1",
"terser": "^5.16.1",
@@ -110,6 +113,11 @@
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
+ "node_modules/@kurkle/color": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
+ "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
+ },
"node_modules/@vitejs/plugin-legacy": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-2.3.1.tgz",
@@ -148,6 +156,17 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
+ "node_modules/chart.js": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz",
+ "integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=7"
+ }
+ },
"node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
@@ -855,6 +874,11 @@
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
+ "@kurkle/color": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
+ "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
+ },
"@vitejs/plugin-legacy": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-2.3.1.tgz",
@@ -880,6 +904,14 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
+ "chart.js": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz",
+ "integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==",
+ "requires": {
+ "@kurkle/color": "^0.3.0"
+ }
+ },
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
diff --git a/front-end/package.json b/front-end/package.json
index 76e8d53..53a57e0 100644
--- a/front-end/package.json
+++ b/front-end/package.json
@@ -11,5 +11,8 @@
"@vitejs/plugin-legacy": "^2.3.1",
"terser": "^5.16.1",
"vite": "^3.2.3"
+ },
+ "dependencies": {
+ "chart.js": "^4.4.0"
}
}
diff --git a/front-end/src/components/login.js b/front-end/src/components/login.js
deleted file mode 100644
index 9546695..0000000
--- a/front-end/src/components/login.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import define from '../utils/define.js';
-import config from "../../config.js";
-
-const template = () => /*html*/`
-
-`;
-
-export default define('login', class extends HTMLElement {
- constructor() {
- super();
- this.loginForm = null;
- this.emailOrUsernameInput = null;
- this.passwordInput = null;
- this.__setup();
- this.__events();
- }
-
- __setup() {
- this.innerHTML = template();
- this.loginForm = this.querySelector('form');
- this.emailOrUsernameInput = this.querySelector('#login-email-or-username');
- this.passwordInput = this.querySelector('#login-password');
- }
-
- __events() {
- this.loginForm.addEventListener('submit', (e) => {
- e.preventDefault();
- this.__handleLogin(e);
- });
- }
-
- __handleLogin = async () => {
- const response = await fetch(`${config.publicApiURL}user/login`, {
- method: 'POST',
- cache: 'no-cache',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({
- "username_or_email": this.emailOrUsernameInput.value,
- "password": this.passwordInput.value,
- })
- });
- if (response.status === 200) {
- const loginData = await response.json();
- console.log("Login data", loginData);
- document.cookie = `jwt=${loginData["jwt"]}; SameSite=Strict`;
- console.log("Login success");
- } else {
- console.log("Login error", response);
- }
- }
-});
\ No newline at end of file
diff --git a/front-end/src/components/molecules/add-button.js b/front-end/src/components/molecules/add-button.js
index d59884a..39f3595 100644
--- a/front-end/src/components/molecules/add-button.js
+++ b/front-end/src/components/molecules/add-button.js
@@ -1,7 +1,4 @@
import define from "../../utils/define.js";
-import {globalBus} from "../../utils/events.js";
-import addResult from "./add-result.js";
-import emptyResult from "./empty-result.js";
export default define('add-button', class extends HTMLButtonElement {
diff --git a/front-end/src/components/molecules/delete-button.js b/front-end/src/components/molecules/delete-button.js
index 1914684..f0097f8 100644
--- a/front-end/src/components/molecules/delete-button.js
+++ b/front-end/src/components/molecules/delete-button.js
@@ -19,7 +19,7 @@ export default define('delete-button', class extends HTMLButtonElement {
const result = this.closest('.result');
const parent = result.parentNode;
- const index = Array.prototype.indexOf.call(parent.children, result);
+ const index = Array.prototype.indexOf.call(parent.getElementsByClassName('result'), result);
console.log("Delete index", index);
const beginCuratingEvent = new CustomEvent('curate-delete-result', {
diff --git a/front-end/src/components/molecules/empty-result.js b/front-end/src/components/molecules/empty-result.js
deleted file mode 100644
index 565c52a..0000000
--- a/front-end/src/components/molecules/empty-result.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import define from '../../utils/define.js';
-
-const template = () => /*html*/`
- We could not find anything for your search...
-`;
-
-export default define('empty-result', class extends HTMLLIElement {
- constructor() {
- super();
- this.classList.add('empty-result');
- this.__setup();
- }
-
- __setup() {
- this.innerHTML = template();
- }
-}, { extends: 'li' });
\ No newline at end of file
diff --git a/front-end/src/components/molecules/validate-button.js b/front-end/src/components/molecules/validate-button.js
index 65e3b10..997af12 100644
--- a/front-end/src/components/molecules/validate-button.js
+++ b/front-end/src/components/molecules/validate-button.js
@@ -21,7 +21,7 @@ export default define('validate-button', class extends HTMLButtonElement {
const result = this.closest('.result');
const parent = result.parentNode;
- const index = Array.prototype.indexOf.call(parent.children, result);
+ const index = Array.prototype.indexOf.call(parent.getElementsByClassName('result'), result);
console.log("Validate index", index);
const curationValidateEvent = new CustomEvent('curate-validate-result', {
diff --git a/front-end/src/components/organisms/footer.js b/front-end/src/components/organisms/footer.js
deleted file mode 100644
index 4b8d62f..0000000
--- a/front-end/src/components/organisms/footer.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import define from '../../utils/define.js';
-import config from '../../../config.js';
-
-const template = ({ data }) => /*html*/`
-
-
-`;
-
-export default define('footer', class extends HTMLElement {
- constructor() {
- super();
- this.__setup();
- }
-
- __setup() {
- this.innerHTML = template({
- data: {
- links: config.footerLinks
- }
- });
- this.__events();
- }
-
- __events() {
-
- }
-}, { extends: 'footer' });
\ No newline at end of file
diff --git a/front-end/src/components/organisms/home.js b/front-end/src/components/organisms/home.js
deleted file mode 100644
index d8939f7..0000000
--- a/front-end/src/components/organisms/home.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import define from '../../utils/define.js';
-
-const template = () => /*html*/`
-
- Welcome to mwmbl, the free, open-source and non-profit search engine.
-
-
- You can start searching by using the search bar above!
-
-`;
-
-export default define('home', class extends HTMLLIElement {
- constructor() {
- super();
- this.classList.add('home');
- this.__setup();
- }
-
- __setup() {
- this.innerHTML = template();
- }
-}, { extends: 'li' });
\ No newline at end of file
diff --git a/front-end/src/components/organisms/results.js b/front-end/src/components/organisms/results.js
index 7a0e6b4..0cbd481 100644
--- a/front-end/src/components/organisms/results.js
+++ b/front-end/src/components/organisms/results.js
@@ -1,11 +1,5 @@
import {globalBus} from '../../utils/events.js';
-
-document.body.addEventListener('htmx:load', function(evt) {
-
-});
-
-
class ResultsHandler {
constructor() {
this.results = null;
@@ -16,19 +10,12 @@ class ResultsHandler {
__setup() {
this.__events();
+ this.__initializeResults();
}
__events() {
document.body.addEventListener('htmx:load', e => {
- this.results = document.querySelector('.results');
-
- // Allow the user to re-order search results
- $(".results").sortable({
- "activate": this.__sortableActivate.bind(this),
- "deactivate": this.__sortableDeactivate.bind(this),
- });
-
- this.curating = false;
+ this.__initializeResults();
});
// Focus first element when coming from the search bar
@@ -119,6 +106,18 @@ class ResultsHandler {
});
}
+ __initializeResults() {
+ this.results = document.querySelector('.results');
+
+ // Allow the user to re-order search results
+ $(".results").sortable({
+ "activate": this.__sortableActivate.bind(this),
+ "deactivate": this.__sortableDeactivate.bind(this),
+ });
+
+ this.curating = false;
+ }
+
__sortableActivate(event, ui) {
console.log("Sortable activate", ui);
this.__beginCurating();
diff --git a/front-end/src/components/organisms/save.js b/front-end/src/components/organisms/save.js
index 38f2dbe..73a23d8 100644
--- a/front-end/src/components/organisms/save.js
+++ b/front-end/src/components/organisms/save.js
@@ -8,11 +8,11 @@ const CURATION_URL = config.publicApiURL + "curation/";
const template = () => /*html*/`
- 🖫
+
`;
-export default define('save', class extends HTMLLIElement {
+export default define('save', class extends HTMLDivElement {
constructor() {
super();
this.currentCurationId = null;
@@ -75,8 +75,8 @@ export default define('save', class extends HTMLLIElement {
return;
}
- if (localStorage.length > 0) {
- const key = this.__getOldestCurationKey();
+ const key = this.__getOldestCurationKey();
+ if (key !== null) {
const value = JSON.parse(localStorage.getItem(key));
console.log("Value", value);
const url = CURATION_URL + value['type'];
@@ -108,5 +108,5 @@ export default define('save', class extends HTMLLIElement {
}
this.sending = false;
}
-}, { extends: 'li' });
+}, { extends: 'div' });
diff --git a/front-end/src/components/organisms/search-bar.js b/front-end/src/components/organisms/search-bar.js
deleted file mode 100644
index 5adf52b..0000000
--- a/front-end/src/components/organisms/search-bar.js
+++ /dev/null
@@ -1,184 +0,0 @@
-import define from '../../utils/define.js';
-import config from '../../../config.js';
-import { globalBus } from '../../utils/events.js';
-import debounce from '../../utils/debounce.js'
-
-const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion)').matches;
-
-const template = () => /*html*/`
-
-`;
-
-export default define('search-bar', class extends HTMLElement {
- constructor() {
- super();
- this.searchInput = null;
- this.searchForm = null;
- this.abortController = new AbortController();
- this.__setup();
- }
-
- __setup() {
- this.innerHTML = template();
- this.searchInput = this.querySelector('input');
- this.searchForm = this.querySelector('form');
- this.__events();
- }
-
- __dispatchSearch({ results = null, error = null }) {
- const searchEvent = new CustomEvent('search', {
- detail: {
- results,
- error,
- },
- });
- globalBus.dispatch(searchEvent)
- }
-
- /**
- * Updates the overall layout of the page.
- *
- * `home` centers the search bar on the page.
- * `compact` raises it to the top and makes room for displaying results.
- *
- * @param {'compact' | 'home'} mode
- * @return {void}
- */
- __setDisplayMode(mode) {
- switch (mode) {
- case 'compact': {
- document.body.style.paddingTop = '25px';
- document.querySelector('.search-menu').classList.add('compact');
- break;
- }
- case 'home': {
- document.body.style.paddingTop = '30vh';
- document.querySelector('.search-menu').classList.remove('compact');
- break;
- }
- }
- }
-
- async __executeSearch() {
- this.abortController.abort();
- this.abortController = new AbortController();
- // Get response from API
- const response = await fetch(`${config.publicApiURL}search?s=${encodeURIComponent(this.searchInput.value)}`, {
- signal: this.abortController.signal
- });
- // Getting results from API
- const search = await (response).json();
- return search;
- }
-
- __handleSearch = async () => {
- // Update page title
- document.title = `MWMBL - ${this.searchInput.value || "Search"}`;
-
- // Update query params
- const queryParams = new URLSearchParams(document.location.search);
- // Sets query param if search value is not empty
- if (this.searchInput.value) queryParams.set(config.searchQueryParam, this.searchInput.value);
- else queryParams.delete(config.searchQueryParam);
- // New URL with query params
- const newURL =
- document.location.protocol
- + "//"
- + document.location.host
- + document.location.pathname
- + (this.searchInput.value ? '?' : '')
- + queryParams.toString();
- // Replace history state
- window.history.replaceState({ path: newURL }, '', newURL);
-
- if (this.searchInput.value) {
- this.__setDisplayMode('compact')
-
- try {
- const search = await this.__executeSearch()
- // This is a guess at an explanation
- // Check the searcInput.value before setting the results to prevent
- // race condition where the user has cleared the search input after
- // submitting an original search but before the search results have
- // come back from the API
- this.__dispatchSearch({ results: this.searchInput.value ? search : null });
- }
- catch(error) {
- this.__dispatchSearch({ error })
- }
- }
- else {
- this.__setDisplayMode('home')
- this.__dispatchSearch({ results: null });
- }
- }
-
- __events() {
- /**
- * Always add the submit event, it makes things feel faster if
- * someone does not prefer reduced motion and reflexively hits
- * return once they've finished typing.
- */
- this.searchForm.addEventListener('submit', (e) => {
- e.preventDefault();
- this.__handleSearch(e);
- });
-
- /**
- * Only add the "real time" search behavior when the client does
- * not prefer reduced motion; this prevents the page from changing
- * while the user is still typing their query.
- */
- if (!prefersReducedMotion) {
- this.searchInput.addEventListener('input', debounce(this.__handleSearch, 500))
- }
-
- // Focus search bar when pressing `ctrl + k` or `/`
- document.addEventListener('keydown', (e) => {
- if ((e.key === 'k' && e.ctrlKey) || e.key === 'Escape') {
- e.preventDefault();
-
- // Remove the modal if it's visible
- document.querySelector('.modal').style.display = 'none';
-
- this.searchInput.focus();
- }
- });
-
- // Focus first result when pressing down arrow
- this.addEventListener('keydown', (e) => {
- if (e.key === 'ArrowDown' && this.searchInput.value) {
- e.preventDefault();
- const focusResultEvent = new CustomEvent('focus-result');
- globalBus.dispatch(focusResultEvent);
- }
- });
-
- globalBus.on('focus-search', (e) => {
- this.searchInput.focus();
- });
- }
-
- connectedCallback() {
- // Focus search input when component is connected
- this.searchInput.focus();
-
- const searchQuery = new URLSearchParams(document.location.search).get(config.searchQueryParam);
- this.searchInput.value = searchQuery;
- /**
- * Trigger search handling to coordinate the value pulled from the query string
- * across the rest of the UI and to actually retrieve the results if the search
- * value is now non-empty.
- */
- this.__handleSearch();
- }
-});
diff --git a/front-end/src/components/register.js b/front-end/src/components/register.js
deleted file mode 100644
index ff5bfc0..0000000
--- a/front-end/src/components/register.js
+++ /dev/null
@@ -1,84 +0,0 @@
-import define from '../utils/define.js';
-import config from "../../config.js";
-
-const template = () => /*html*/`
-
-`;
-
-export default define('register', class extends HTMLElement {
- constructor() {
- super();
- this.registerForm = null;
- this.emailInput = null;
- this.usernameInput = null;
- this.passwordInput = null;
- this.passwordVerifyInput = null;
- this.__setup();
- this.__events();
- }
-
- __setup() {
- this.innerHTML = template();
- this.registerForm = this.querySelector('form');
- this.emailInput = this.querySelector('#register-email');
- this.usernameInput = this.querySelector('#register-username');
- this.passwordInput = this.querySelector('#register-password');
- this.passwordVerifyInput = this.querySelector('#register-password-verify');
- }
-
- __events() {
- this.registerForm.addEventListener('submit', (e) => {
- e.preventDefault();
- this.__handleRegister(e);
- });
- }
-
- __handleRegister = async () => {
- const response = await fetch(`${config.publicApiURL}user/register`, {
- method: 'POST',
- cache: 'no-cache',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({
- "username": this.usernameInput.value,
- "email": this.emailInput.value,
- "password": this.passwordInput.value,
- "password_verify": this.passwordVerifyInput.value,
- })
- });
- if (response.status === 200) {
- const registerData = await response.json();
- console.log("Register data", registerData);
- document.cookie = `jwt=${registerData["jwt"]}; SameSite=Strict`;
- console.log("Register success");
- } else {
- console.log("Register error", response);
- }
- }
-});
\ No newline at end of file
diff --git a/front-end/src/index.html b/front-end/src/index.html
deleted file mode 100644
index a08e557..0000000
--- a/front-end/src/index.html
+++ /dev/null
@@ -1,110 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- MWMBL - Search
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/front-end/src/index.js b/front-end/src/index.js
index 34cd8cb..027f34c 100644
--- a/front-end/src/index.js
+++ b/front-end/src/index.js
@@ -5,6 +5,7 @@
* Please do not pollute this file if you can make
* util or component files instead.
*/
+import 'vite/modulepreload-polyfill';
// Waiting for top-level await to be better supported.
(async () => {
@@ -14,11 +15,12 @@
if (!redirected) {
// Load components only after redirects are checked.
- import('./components/login.js');
- import('./components/register.js');
- import("./components/organisms/search-bar.js");
import("./components/organisms/results.js");
- import("./components/organisms/footer.js");
import("./components/organisms/save.js");
+ import("./components/molecules/add-button.js");
+ import("./components/molecules/add-result.js");
+ import("./components/molecules/delete-button.js");
+ import("./components/molecules/result.js");
+ import("./components/molecules/validate-button.js");
}
})();
diff --git a/front-end/vite.config.js b/front-end/vite.config.js
index 6855f71..5914005 100644
--- a/front-end/vite.config.js
+++ b/front-end/vite.config.js
@@ -1,11 +1,20 @@
import legacy from '@vitejs/plugin-legacy'
+import { resolve } from 'path'
export default {
root: './src',
base: '/static',
publicDir: '../assets',
build: {
- outDir: '../dist'
+ outDir: '../dist',
+ manifest: true,
+ rollupOptions: {
+ input: {
+ index: resolve(__dirname, 'src/index.js'),
+ stats: resolve(__dirname, 'src/stats/index.html'),
+ },
+ },
+ minify: false,
},
plugins: [
legacy({
diff --git a/mwmbl/settings_common.py b/mwmbl/settings_common.py
index a71ac6d..c0a7436 100644
--- a/mwmbl/settings_common.py
+++ b/mwmbl/settings_common.py
@@ -29,8 +29,10 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
+ 'django.contrib.humanize',
'mwmbl',
-
+ 'django_htmx',
+ 'django_vite',
'allauth',
'allauth.account',
'allauth.socialaccount',
@@ -45,6 +47,7 @@ MIDDLEWARE = [
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ "django_htmx.middleware.HtmxMiddleware",
"allauth.account.middleware.AccountMiddleware",
]
@@ -105,6 +108,9 @@ USE_TZ = True
STATIC_URL = 'static/'
+DJANGO_VITE_DEV_MODE = False
+
+
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
@@ -126,3 +132,35 @@ ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
DEFAULT_FROM_EMAIL = "admin@mwmbl.org"
+
+LOGIN_REDIRECT_URL = "/"
+
+FOOTER_LINKS = [
+ {
+ "name": "Matrix",
+ "icon": "ph-chat-circle-text-bold",
+ "href": "https://matrix.to/#/#mwmbl:matrix.org",
+ },
+ {
+ "name": "Book",
+ "icon": "ph-book-bold",
+ "href": "https://book.mwmbl.org",
+ },
+ {
+ "name": "Blog",
+ "icon": "ph-browser-bold",
+ "href": "https://blog.mwmbl.org",
+ },
+ {
+ "name": "GitHub",
+ "icon": "ph-github-logo-bold",
+ "href": "https://github.com/mwmbl/mwmbl",
+ },
+ {
+ "name": "YouTube",
+ "icon": "ph-youtube-logo-bold",
+ "href": "https://www.youtube.com/channel/UCFLbqrH63-icAHxQ1eFfAvA",
+ },
+
+
+]
diff --git a/mwmbl/settings_dev.py b/mwmbl/settings_dev.py
index de11849..344e1bd 100644
--- a/mwmbl/settings_dev.py
+++ b/mwmbl/settings_dev.py
@@ -13,7 +13,11 @@ DATABASES = {
}
-STATICFILES_DIRS = [str(Path(__file__).parent.parent / "front-end" / "dist")]
+STATIC_ROOT = ""
+DJANGO_VITE_ASSETS_PATH = Path(__file__).parent.parent / "front-end" / "dist"
+DJANGO_VITE_MANIFEST_PATH = DJANGO_VITE_ASSETS_PATH / "manifest.json"
+
+STATICFILES_DIRS = [str(DJANGO_VITE_ASSETS_PATH)]
DEBUG = True
diff --git a/mwmbl/settings_prod.py b/mwmbl/settings_prod.py
index 837dbb0..7716636 100644
--- a/mwmbl/settings_prod.py
+++ b/mwmbl/settings_prod.py
@@ -9,8 +9,9 @@ SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
STATIC_ROOT = "/app/static/"
-STATICFILES_DIRS = ["/front-end-build/"]
+DJANGO_VITE_ASSETS_PATH = "/front-end-build/"
+STATICFILES_DIRS = [DJANGO_VITE_ASSETS_PATH]
DATABASES = {'default': dj_database_url.config(default=os.environ["DATABASE_URL"])}
diff --git a/mwmbl/templates/home.html b/mwmbl/templates/home.html
index ed59579..b15e047 100644
--- a/mwmbl/templates/home.html
+++ b/mwmbl/templates/home.html
@@ -1,5 +1,39 @@
-{% extends 'base.html' %}
-
-{% block content %}
- Welcome, {{ user.username }}!
-{% endblock %}
+{% load result_filters %}
+{% load humanize %}
+{% include "title.html" %}
+{% if query %}
+ {% if results %}
+ {% for result in results %}
+
+
+
+
+
+
+
+
+
+
+ {% endfor %}
+ {% else %}
+
+
+ No results found for "{{query}}".
+
+
+ {% endif %}
+{% else %}
+ {% for item in activity %}
+
+
+ {{ item.user }} made {{ item.num_curations | apnumber }} changes to {{ item.query }} {{ item.timestamp | naturaltime }}.
+
+
+ {% endfor %}
+{% endif %}
diff --git a/mwmbl/templates/index.html b/mwmbl/templates/index.html
new file mode 100644
index 0000000..b098809
--- /dev/null
+++ b/mwmbl/templates/index.html
@@ -0,0 +1,106 @@
+{% load django_vite %}
+
+
+
+
+
+
+
+
+
+ {% include "title.html" %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% vite_hmr_client %}
+
+
+
+
+
+
+
+
+ {% include "home.html" %}
+
+
+
+
+
+{% vite_asset 'index.js' %}
+{% vite_legacy_polyfills %}
+{% vite_legacy_asset 'index-legacy.js' %}
+
+
+
\ No newline at end of file
diff --git a/mwmbl/templates/profile.html b/mwmbl/templates/profile.html
deleted file mode 100644
index 2923d40..0000000
--- a/mwmbl/templates/profile.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{% extends "base.html" %}
-{% block title %}Profile Page{% endblock title %}
-{% block content %}
-
-
This is the profile page for {{user.username}}
-
-
-{% endblock content %}
diff --git a/mwmbl/templates/results.html b/mwmbl/templates/results.html
deleted file mode 100644
index 11fa224..0000000
--- a/mwmbl/templates/results.html
+++ /dev/null
@@ -1,19 +0,0 @@
-{% load result_filters %}
-{% for result in results %}
-
-
-
-
-
-
-
-
-
-
-{% endfor %}
diff --git a/mwmbl/templates/title.html b/mwmbl/templates/title.html
new file mode 100644
index 0000000..f02b0e5
--- /dev/null
+++ b/mwmbl/templates/title.html
@@ -0,0 +1,6 @@
+
+ {% if query %}
+ Mwmbl - {{ query }}
+ {% else %}
+ Mwmbl - Search
+ {% endif %}
diff --git a/mwmbl/urls.py b/mwmbl/urls.py
index 8ce524a..776361a 100644
--- a/mwmbl/urls.py
+++ b/mwmbl/urls.py
@@ -17,16 +17,15 @@ Including another URLconf
from django.contrib import admin
from django.urls import path, include
-from mwmbl.api import api_original as api, api_v1
-from mwmbl.views import profile, search_results, fetch_url
+from mwmbl.api import api_v1
+from mwmbl.views import home_fragment, fetch_url, index
urlpatterns = [
path('admin/', admin.site.urls),
- path('', api.urls),
path('api/v1/', api_v1.urls),
path('accounts/', include('allauth.urls')),
- path('accounts/profile/', profile, name='profile'),
- path('app/search/', search_results, name="search_results"),
+ path('', index, name="home"),
+ path('app/home/', home_fragment, name="home"),
path('app/fetch/', fetch_url, name="fetch_url")
]
diff --git a/mwmbl/views.py b/mwmbl/views.py
index a3fb215..0b3daba 100644
--- a/mwmbl/views.py
+++ b/mwmbl/views.py
@@ -1,9 +1,16 @@
+from dataclasses import dataclass
+from datetime import datetime
+from itertools import groupby
+from urllib.parse import urlparse, parse_qs
+
import justext
import requests
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
+from django_htmx.http import push_url
from mwmbl.format import format_result
+from mwmbl.models import UserCuration, MwmblUser
from mwmbl.search_setup import ranker
from justext.core import html_to_dom, ParagraphMaker, classify_paragraphs, revise_paragraph_classification, \
@@ -12,6 +19,7 @@ from justext.core import html_to_dom, ParagraphMaker, classify_paragraphs, revis
from mwmbl.settings import NUM_EXTRACT_CHARS
from mwmbl.tinysearchengine.indexer import Document
+from django.conf import settings
def justext_with_dom(html_text, stoplist, length_low=LENGTH_LOW_DEFAULT,
@@ -40,15 +48,68 @@ def justext_with_dom(html_text, stoplist, length_low=LENGTH_LOW_DEFAULT,
return paragraphs, title
-@login_required
-def profile(request):
- return render(request, 'profile.html')
+def index(request):
+ activity, query, results = _get_results_and_activity(request)
+ return render(request, "index.html", {
+ "results": results,
+ "query": query,
+ "user": request.user,
+ "activity": activity,
+ "footer_links": settings.FOOTER_LINKS,
+ })
-def search_results(request):
- query = request.GET["query"]
- results = ranker.search(query)
- return render(request, "results.html", {"results": results})
+def home_fragment(request):
+ activity, query, results = _get_results_and_activity(request)
+ response = render(request, "home.html", {
+ "results": results,
+ "query": query,
+ "activity": activity,
+ })
+ current_url = request.htmx.current_url
+ # Replace query string with new query
+ stripped_url = current_url[:current_url.index("?")] if "?" in current_url else current_url
+ query_string = "?q=" + query if len(query) > 0 else ""
+ new_url = stripped_url + query_string
+ # Set the htmx replace header
+ response["HX-Replace-Url"] = new_url
+ return response
+
+
+@dataclass
+class Activity:
+ user: MwmblUser
+ num_curations: int
+ timestamp: datetime
+ query: str
+ url: str
+
+
+def _get_results_and_activity(request):
+ query = request.GET.get("q")
+ if query:
+ results = ranker.search(query)
+ activity = None
+ else:
+ results = None
+ curations = UserCuration.objects.order_by("-timestamp")[:100]
+ sorted_curations = sorted(curations, key=lambda x: x.user.username)
+ groups = groupby(sorted_curations, key=lambda x: (x.user.username, x.url))
+ activity = []
+ for (user, url), group in groups:
+ parsed_url_query = parse_qs(urlparse(url).query)
+ activity_query = parsed_url_query.get("q", [""])[0]
+ group = list(group)
+ activity.append(Activity(
+ user=user,
+ num_curations=len(group),
+ timestamp=max([i.timestamp for i in group]),
+ query=activity_query,
+ url=url,
+ ))
+
+ print("Activity", activity)
+ return activity, query, results
def fetch_url(request):
@@ -63,4 +124,7 @@ def fetch_url(request):
extract = extract[:NUM_EXTRACT_CHARS - 1] + '…'
result = Document(title=title, url=url, extract=extract, score=0.0)
- return render(request, "results.html", {"results": [format_result(result, query)]})
+ return render(request, "home.html", {
+ "results": [format_result(result, query)],
+ "query": query,
+ })
diff --git a/nginx.conf.sigil b/nginx.conf.sigil
index 89fa102..48a4ca7 100644
--- a/nginx.conf.sigil
+++ b/nginx.conf.sigil
@@ -103,12 +103,7 @@ server {
alias /var/lib/dokku/data/storage/mwmbl-beta/;
}
- ## Root and stats served statically
- location = / {
- root /var/lib/dokku/data/storage/mwmbl-beta;
- try_files /index.html =404;
- }
-
+ ## Stats served statically
location ~ ^\/stats\/?$ {
root /var/lib/dokku/data/storage/mwmbl-beta;
try_files /stats/index.html =404;
diff --git a/poetry.lock b/poetry.lock
index 0808bb5..800e178 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,10 +1,9 @@
-# This file is automatically @generated by Poetry and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
[[package]]
name = "anyio"
version = "3.7.1"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -26,7 +25,6 @@ trio = ["trio (<0.22)"]
name = "asgiref"
version = "3.7.2"
description = "ASGI specs, helper code, and adapters"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -44,7 +42,6 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
name = "async-timeout"
version = "4.0.3"
description = "Timeout context manager for asyncio programs"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -56,7 +53,6 @@ files = [
name = "attrs"
version = "23.1.0"
description = "Classes Without Boilerplate"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -75,7 +71,6 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte
name = "beautifulsoup4"
version = "4.10.0"
description = "Screen-scraping library"
-category = "main"
optional = true
python-versions = ">3.0.0"
files = [
@@ -94,7 +89,6 @@ lxml = ["lxml"]
name = "blis"
version = "0.7.11"
description = "The Blis BLAS-like linear algebra library, as a self-contained C-extension."
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -141,7 +135,6 @@ numpy = {version = ">=1.19.0", markers = "python_version >= \"3.9\""}
name = "boto3"
version = "1.28.62"
description = "The AWS SDK for Python"
-category = "main"
optional = false
python-versions = ">= 3.7"
files = [
@@ -161,7 +154,6 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
name = "botocore"
version = "1.31.62"
description = "Low-level, data-driven core of boto 3."
-category = "main"
optional = false
python-versions = ">= 3.7"
files = [
@@ -181,7 +173,6 @@ crt = ["awscrt (==0.16.26)"]
name = "catalogue"
version = "2.0.10"
description = "Super lightweight function registries for your library"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -193,7 +184,6 @@ files = [
name = "cattrs"
version = "23.1.2"
description = "Composable complex class support for attrs and dataclasses."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -219,7 +209,6 @@ ujson = ["ujson (>=5.4.0,<6.0.0)"]
name = "certifi"
version = "2023.7.22"
description = "Python package for providing Mozilla's CA Bundle."
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -231,7 +220,6 @@ files = [
name = "cffi"
version = "1.16.0"
description = "Foreign Function Interface for Python calling C code."
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -296,7 +284,6 @@ pycparser = "*"
name = "charset-normalizer"
version = "3.3.0"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
-category = "main"
optional = false
python-versions = ">=3.7.0"
files = [
@@ -396,7 +383,6 @@ files = [
name = "click"
version = "8.1.7"
description = "Composable command line interface toolkit"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -411,7 +397,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
-category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
@@ -423,7 +408,6 @@ files = [
name = "cryptography"
version = "41.0.4"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -469,7 +453,6 @@ test-randomorder = ["pytest-randomly"]
name = "cymem"
version = "2.0.8"
description = "Manage calls to calloc/free through Cython"
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -512,7 +495,6 @@ files = [
name = "defusedxml"
version = "0.7.1"
description = "XML bomb protection for Python stdlib modules"
-category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [
@@ -524,7 +506,6 @@ files = [
name = "dj-database-url"
version = "2.1.0"
description = "Use Database URLs in your Django Application."
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -540,7 +521,6 @@ typing-extensions = ">=3.10.0.0"
name = "django"
version = "4.2.6"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -561,7 +541,6 @@ bcrypt = ["bcrypt"]
name = "django-allauth"
version = "0.57.0"
description = "Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -579,11 +558,24 @@ requests-oauthlib = ">=0.3.0"
mfa = ["qrcode (>=7.0.0)"]
saml = ["python3-saml (>=1.15.0,<2.0.0)"]
+[[package]]
+name = "django-htmx"
+version = "1.17.0"
+description = "Extensions for using Django with htmx."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "django_htmx-1.17.0-py3-none-any.whl", hash = "sha256:070a37092b88a42cd7af26c1b65f63c4529bae276710fd16137dc934938b44f2"},
+ {file = "django_htmx-1.17.0.tar.gz", hash = "sha256:2ef0d19db41c6152881e782673cd2cd1755a7fd6784f8b4f2279fb18dc03d2c2"},
+]
+
+[package.dependencies]
+Django = ">=3.2"
+
[[package]]
name = "django-ninja"
version = "0.22.2"
description = "Django Ninja - Fast Django REST framework"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -600,11 +592,27 @@ dev = ["pre-commit"]
doc = ["markdown-include", "mkdocs", "mkdocs-material", "mkdocstrings"]
test = ["black", "django-stubs", "flake8", "isort", "mypy (==0.931)", "psycopg2-binary", "pytest", "pytest-asyncio", "pytest-cov", "pytest-django"]
+[[package]]
+name = "django-vite"
+version = "2.1.3"
+description = "Integration of ViteJS in a Django project."
+optional = false
+python-versions = "*"
+files = [
+ {file = "django-vite-2.1.3.tar.gz", hash = "sha256:c59b3bbd85501bc1faf63c500df66542abed2951cfa10dfbf8be8ecf229f7652"},
+ {file = "django_vite-2.1.3-py3-none-any.whl", hash = "sha256:97984ac495910b7b71039228ccddff52d132231fa6612d3d31c6c228c95b0217"},
+]
+
+[package.dependencies]
+Django = ">=1.11"
+
+[package.extras]
+dev = ["black", "flake8"]
+
[[package]]
name = "exceptiongroup"
version = "1.1.3"
description = "Backport of PEP 654 (exception groups)"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -619,7 +627,6 @@ test = ["pytest (>=6)"]
name = "fastapi"
version = "0.70.1"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
-category = "main"
optional = false
python-versions = ">=3.6.1"
files = [
@@ -641,7 +648,6 @@ test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==21.9b0)", "databases[sqlite] (
name = "h11"
version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -653,7 +659,6 @@ files = [
name = "hiredis"
version = "2.2.3"
description = "Python wrapper for hiredis"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -752,7 +757,6 @@ files = [
name = "idna"
version = "3.3"
description = "Internationalized Domain Names in Applications (IDNA)"
-category = "main"
optional = false
python-versions = ">=3.5"
files = [
@@ -764,7 +768,6 @@ files = [
name = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -776,7 +779,6 @@ files = [
name = "jinja2"
version = "3.1.2"
description = "A very fast and expressive template engine."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -794,7 +796,6 @@ i18n = ["Babel (>=2.7)"]
name = "jmespath"
version = "1.0.1"
description = "JSON Matching Expressions"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -806,7 +807,6 @@ files = [
name = "joblib"
version = "1.3.2"
description = "Lightweight pipelining with Python functions"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -818,7 +818,6 @@ files = [
name = "justext"
version = "3.0.0"
description = "Heuristic based boilerplate removal tool"
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -833,7 +832,6 @@ lxml = ">=4.4.2"
name = "langcodes"
version = "3.3.0"
description = "Tools for labeling human languages with IETF language tags"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -848,7 +846,6 @@ data = ["language-data (>=1.1,<2.0)"]
name = "langdetect"
version = "1.0.9"
description = "Language detection library ported from Google's language-detection."
-category = "main"
optional = true
python-versions = "*"
files = [
@@ -863,7 +860,6 @@ six = "*"
name = "levenshtein"
version = "0.16.0"
description = "Python extension for computing string edit distances and similarities."
-category = "main"
optional = true
python-versions = ">=3.5"
files = [
@@ -928,7 +924,6 @@ rapidfuzz = ">=1.8.2,<1.9"
name = "lxml"
version = "4.6.4"
description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
-category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
files = [
@@ -1004,7 +999,6 @@ source = ["Cython (>=0.29.7)"]
name = "markupsafe"
version = "2.1.3"
description = "Safely add untrusted strings to HTML/XML markup."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -1074,7 +1068,6 @@ files = [
name = "mmh3"
version = "3.1.0"
description = "Python wrapper for MurmurHash (MurmurHash3), a set of fast and robust hash functions."
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -1119,7 +1112,6 @@ files = [
name = "murmurhash"
version = "1.0.10"
description = "Cython bindings for MurmurHash"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -1162,7 +1154,6 @@ files = [
name = "numpy"
version = "1.26.0"
description = "Fundamental package for array computing in Python"
-category = "main"
optional = false
python-versions = "<3.13,>=3.9"
files = [
@@ -1204,7 +1195,6 @@ files = [
name = "oauthlib"
version = "3.2.2"
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -1221,7 +1211,6 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
name = "packaging"
version = "23.2"
description = "Core utilities for Python packages"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -1233,7 +1222,6 @@ files = [
name = "pandas"
version = "1.5.3"
description = "Powerful data structures for data analysis, time series, and statistics"
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -1278,7 +1266,6 @@ test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"]
name = "pathy"
version = "0.10.2"
description = "pathlib.Path subclasses for local and cloud bucket storage"
-category = "main"
optional = false
python-versions = ">= 3.6"
files = [
@@ -1301,7 +1288,6 @@ test = ["mock", "pytest", "pytest-coverage", "typer-cli"]
name = "platformdirs"
version = "3.11.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -1317,7 +1303,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co
name = "pluggy"
version = "1.3.0"
description = "plugin and hook calling mechanisms for python"
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -1333,7 +1318,6 @@ testing = ["pytest", "pytest-benchmark"]
name = "preshed"
version = "3.0.9"
description = "Cython hash table that trusts the keys are pre-hashed"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -1380,7 +1364,6 @@ murmurhash = ">=0.28.0,<1.1.0"
name = "psycopg2-binary"
version = "2.9.9"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -1410,6 +1393,7 @@ files = [
{file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"},
+ {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"},
@@ -1418,6 +1402,8 @@ files = [
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"},
+ {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"},
+ {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"},
@@ -1459,7 +1445,6 @@ files = [
name = "py4j"
version = "0.10.9.2"
description = "Enables Python programs to dynamically access arbitrary Java objects"
-category = "main"
optional = true
python-versions = "*"
files = [
@@ -1471,7 +1456,6 @@ files = [
name = "pyarrow"
version = "6.0.0"
description = "Python library for Apache Arrow"
-category = "main"
optional = true
python-versions = ">=3.6"
files = [
@@ -1520,7 +1504,6 @@ numpy = ">=1.16.6"
name = "pycparser"
version = "2.21"
description = "C parser in Python"
-category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
@@ -1532,7 +1515,6 @@ files = [
name = "pydantic"
version = "1.8.2"
description = "Data validation and settings management using python 3.6 type hinting"
-category = "main"
optional = false
python-versions = ">=3.6.1"
files = [
@@ -1571,7 +1553,6 @@ email = ["email-validator (>=1.0.3)"]
name = "pyjwt"
version = "2.8.0"
description = "JSON Web Token implementation in Python"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -1592,7 +1573,6 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
name = "pyspark"
version = "3.2.0"
description = "Apache Spark Python API"
-category = "main"
optional = true
python-versions = ">=3.6"
files = [
@@ -1612,7 +1592,6 @@ sql = ["pandas (>=0.23.2)", "pyarrow (>=1.0.0)"]
name = "pytest"
version = "7.4.2"
description = "pytest: simple powerful testing with Python"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -1635,7 +1614,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no
name = "pytest-mock"
version = "3.11.1"
description = "Thin-wrapper around the mock package for easier use with pytest"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -1653,7 +1631,6 @@ dev = ["pre-commit", "pytest-asyncio", "tox"]
name = "python-dateutil"
version = "2.8.2"
description = "Extensions to the standard Python datetime module"
-category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
files = [
@@ -1668,7 +1645,6 @@ six = ">=1.5"
name = "python3-openid"
version = "3.2.0"
description = "OpenID support for modern servers and consumers."
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -1687,7 +1663,6 @@ postgresql = ["psycopg2"]
name = "pytz"
version = "2023.3.post1"
description = "World timezone definitions, modern and historical"
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -1699,7 +1674,6 @@ files = [
name = "pyyaml"
version = "6.0"
description = "YAML parser and emitter for Python"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -1749,7 +1723,6 @@ files = [
name = "rapidfuzz"
version = "1.8.3"
description = "rapid fuzzy string matching"
-category = "main"
optional = true
python-versions = ">=2.7"
files = [
@@ -1816,7 +1789,6 @@ full = ["numpy"]
name = "redis"
version = "5.0.1"
description = "Python client for Redis database and key-value store"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -1836,7 +1808,6 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"
name = "requests"
version = "2.31.0"
description = "Python HTTP for Humans."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -1858,7 +1829,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
name = "requests-cache"
version = "1.1.0"
description = "A persistent cache for python requests"
-category = "main"
optional = false
python-versions = ">=3.7,<4.0"
files = [
@@ -1889,7 +1859,6 @@ yaml = ["pyyaml (>=5.4)"]
name = "requests-oauthlib"
version = "1.3.1"
description = "OAuthlib authentication support for Requests."
-category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
@@ -1908,7 +1877,6 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
name = "s3transfer"
version = "0.7.0"
description = "An Amazon S3 Transfer Manager"
-category = "main"
optional = false
python-versions = ">= 3.7"
files = [
@@ -1926,7 +1894,6 @@ crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"]
name = "scikit-learn"
version = "1.3.1"
description = "A set of python modules for machine learning and data mining"
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -1941,6 +1908,11 @@ files = [
{file = "scikit_learn-1.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f66eddfda9d45dd6cadcd706b65669ce1df84b8549875691b1f403730bdef217"},
{file = "scikit_learn-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6448c37741145b241eeac617028ba6ec2119e1339b1385c9720dae31367f2be"},
{file = "scikit_learn-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c413c2c850241998168bbb3bd1bb59ff03b1195a53864f0b80ab092071af6028"},
+ {file = "scikit_learn-1.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ef540e09873e31569bc8b02c8a9f745ee04d8e1263255a15c9969f6f5caa627f"},
+ {file = "scikit_learn-1.3.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9147a3a4df4d401e618713880be023e36109c85d8569b3bf5377e6cd3fecdeac"},
+ {file = "scikit_learn-1.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2cd3634695ad192bf71645702b3df498bd1e246fc2d529effdb45a06ab028b4"},
+ {file = "scikit_learn-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c275a06c5190c5ce00af0acbb61c06374087949f643ef32d355ece12c4db043"},
+ {file = "scikit_learn-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:0e1aa8f206d0de814b81b41d60c1ce31f7f2c7354597af38fae46d9c47c45122"},
{file = "scikit_learn-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:52b77cc08bd555969ec5150788ed50276f5ef83abb72e6f469c5b91a0009bbca"},
{file = "scikit_learn-1.3.1-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a683394bc3f80b7c312c27f9b14ebea7766b1f0a34faf1a2e9158d80e860ec26"},
{file = "scikit_learn-1.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15d964d9eb181c79c190d3dbc2fff7338786bf017e9039571418a1d53dab236"},
@@ -1969,7 +1941,6 @@ tests = ["black (>=23.3.0)", "matplotlib (>=3.1.3)", "mypy (>=1.3)", "numpydoc (
name = "scipy"
version = "1.11.3"
description = "Fundamental algorithms for scientific computing in Python"
-category = "main"
optional = false
python-versions = "<3.13,>=3.9"
files = [
@@ -2012,7 +1983,6 @@ test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeo
name = "setuptools"
version = "68.2.2"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -2029,7 +1999,6 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
-category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
@@ -2041,7 +2010,6 @@ files = [
name = "smart-open"
version = "6.4.0"
description = "Utils for streaming large files (S3, HDFS, GCS, Azure Blob Storage, gzip, bz2...)"
-category = "main"
optional = false
python-versions = ">=3.6,<4.0"
files = [
@@ -2063,7 +2031,6 @@ webhdfs = ["requests"]
name = "sniffio"
version = "1.3.0"
description = "Sniff out which async library your code is running under"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -2075,7 +2042,6 @@ files = [
name = "soupsieve"
version = "2.5"
description = "A modern CSS selector implementation for Beautiful Soup."
-category = "main"
optional = true
python-versions = ">=3.8"
files = [
@@ -2087,7 +2053,6 @@ files = [
name = "spacy"
version = "3.2.1"
description = "Industrial-strength Natural Language Processing (NLP) in Python"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -2157,7 +2122,6 @@ transformers = ["spacy-transformers (>=1.1.2,<1.2.0)"]
name = "spacy-legacy"
version = "3.0.12"
description = "Legacy registered functions for spaCy backwards compatibility"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -2169,7 +2133,6 @@ files = [
name = "spacy-loggers"
version = "1.0.5"
description = "Logging utilities for SpaCy"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -2181,7 +2144,6 @@ files = [
name = "sqlparse"
version = "0.4.4"
description = "A non-validating SQL parser."
-category = "main"
optional = false
python-versions = ">=3.5"
files = [
@@ -2198,7 +2160,6 @@ test = ["pytest", "pytest-cov"]
name = "srsly"
version = "2.4.8"
description = "Modern high-performance serialization utilities for Python"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -2245,7 +2206,6 @@ catalogue = ">=2.0.3,<2.1.0"
name = "starlette"
version = "0.16.0"
description = "The little ASGI library that shines."
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -2263,7 +2223,6 @@ full = ["graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "req
name = "thinc"
version = "8.0.17"
description = "A refreshing functional take on deep learning, compatible with your favorite libraries"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -2329,7 +2288,6 @@ torch = ["torch (>=1.6.0)"]
name = "threadpoolctl"
version = "3.2.0"
description = "threadpoolctl"
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -2341,7 +2299,6 @@ files = [
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -2353,7 +2310,6 @@ files = [
name = "tqdm"
version = "4.66.1"
description = "Fast, Extensible Progress Meter"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -2374,7 +2330,6 @@ telegram = ["requests"]
name = "typer"
version = "0.4.2"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -2395,7 +2350,6 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=5.2,<6.0)", "isort (>=5.0.6,<6.
name = "typing-extensions"
version = "4.8.0"
description = "Backported and Experimental Type Hints for Python 3.8+"
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -2407,7 +2361,6 @@ files = [
name = "tzdata"
version = "2023.3"
description = "Provider of IANA time zone data"
-category = "main"
optional = false
python-versions = ">=2"
files = [
@@ -2419,7 +2372,6 @@ files = [
name = "ujson"
version = "4.3.0"
description = "Ultra fast JSON encoder and decoder for Python"
-category = "main"
optional = true
python-versions = ">=3.6"
files = [
@@ -2473,7 +2425,6 @@ files = [
name = "url-normalize"
version = "1.4.3"
description = "URL normalization for Python"
-category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
files = [
@@ -2488,7 +2439,6 @@ six = "*"
name = "urllib3"
version = "2.0.6"
description = "HTTP library with thread-safe connection pooling, file post, and more."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -2506,7 +2456,6 @@ zstd = ["zstandard (>=0.18.0)"]
name = "uvicorn"
version = "0.16.0"
description = "The lightning-fast ASGI server."
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -2526,7 +2475,6 @@ standard = ["PyYAML (>=5.1)", "colorama (>=0.4)", "httptools (>=0.2.0,<0.4.0)",
name = "warcio"
version = "1.7.4"
description = "Streaming WARC (and ARC) IO library"
-category = "main"
optional = true
python-versions = "*"
files = [
@@ -2541,7 +2489,6 @@ six = "*"
name = "wasabi"
version = "0.10.1"
description = "A lightweight console printing and formatting toolkit"
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -2553,7 +2500,6 @@ files = [
name = "zstandard"
version = "0.16.0"
description = "Zstandard bindings for Python"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -2610,9 +2556,9 @@ cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\
cffi = ["cffi (>=1.11)"]
[extras]
-indexer = ["ujson", "warcio", "idna", "beautifulsoup4", "lxml", "langdetect", "pyarrow", "pyspark", "Levenshtein"]
+indexer = ["Levenshtein", "beautifulsoup4", "idna", "langdetect", "lxml", "pyarrow", "pyspark", "ujson", "warcio"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<3.11"
-content-hash = "37c79d582b976c81d731ea9bac38911f8cf578ae72fe715e23ab7d1236712f81"
+content-hash = "4e4233221e9f3bd317c0693584612898b7b736f45983b7f3f5bad4d43e567353"
diff --git a/pyproject.toml b/pyproject.toml
index 548dacf..ba9c755 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -39,6 +39,8 @@ requests-cache = "^1.1.0"
redis = {extras = ["hiredis"], version = "^5.0.1"}
django-allauth = "^0.57.0"
dj-database-url = "^2.1.0"
+django-htmx = "^1.17.0"
+django-vite = "^2.1.3"
[tool.poetry.extras]
indexer = [