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*/` -
-
Login
-
- -
- -
-
-
- -
- - -
-
-
- -
-
-`; - -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*/` -
-
Register
-
- -
- -
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
-
-`; - -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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
Login Sign up
-
- mwmbl logo - MWMBL -
- -
-
- -
    -
  • -
-
-
-
- -
- - - \ 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 %} + + + + +
    + +
    + mwmbl logo + Mwmbl +
    +
    + +
    + {% if user.is_authenticated %} +

    Logged in as {{ user.username }}

    + Log out + {% else %} + Login + Sign up + {% endif %} +
    +
    + + + +
    +
    + +{% 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 = [