Merge pull request #124 from mwmbl/tidy-beta

Tidy beta
This commit is contained in:
Daoud Clarke 2023-11-09 09:53:33 +00:00 committed by GitHub
commit 5f47f45ebc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 434 additions and 736 deletions

1
.gitignore vendored
View File

@ -17,6 +17,7 @@ __pycache__/
build/
develop-eggs/
dist/
front-end/dist/
downloads/
eggs/
.eggs/

Binary file not shown.

View File

@ -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;
}

View File

@ -103,4 +103,20 @@ Phosphor Web Font
.ph-info-bold::before {
content: "\f88f";
}
}
.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";
}

View File

@ -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";
}

View File

@ -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=',

View File

@ -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",

View File

@ -11,5 +11,8 @@
"@vitejs/plugin-legacy": "^2.3.1",
"terser": "^5.16.1",
"vite": "^3.2.3"
},
"dependencies": {
"chart.js": "^4.4.0"
}
}

View File

@ -1,69 +0,0 @@
import define from '../utils/define.js';
import config from "../../config.js";
const template = () => /*html*/`
<form>
<h5>Login</h5>
<div>
<label for="login-email-or-username">Email or Username</label>
<div>
<input class="form-control" type="text" id="login-email-or-username" autocomplete="email" required="" minlength="3">
</div>
</div>
<div>
<label for="login-password">Password</label>
<div>
<input type="password" id="login-password" autocomplete="current-password" required="" maxlength="60">
<button type="button" disabled="" title="You will not be able to reset your password without an email.">forgot password</button>
</div>
</div>
<div>
<button class="btn btn-secondary" type="submit">Login</button>
</div>
</form>
`;
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);
}
}
});

View File

@ -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 {

View File

@ -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', {

View File

@ -1,17 +0,0 @@
import define from '../../utils/define.js';
const template = () => /*html*/`
<p>We could not find anything for your search...</p>
`;
export default define('empty-result', class extends HTMLLIElement {
constructor() {
super();
this.classList.add('empty-result');
this.__setup();
}
__setup() {
this.innerHTML = template();
}
}, { extends: 'li' });

View File

@ -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', {

View File

@ -1,36 +0,0 @@
import define from '../../utils/define.js';
import config from '../../../config.js';
const template = ({ data }) => /*html*/`
<p class="footer-text">Find more on</p>
<ul class="footer-list">
${ data.links.map(link => /*html*/`
<li class="footer-item">
<a href="${link.href}" class="footer-link" target="__blank">
<i class="${link.icon}"></i>
<span>${link.name}</span>
</a>
</li>
`).join('') }
</ul>
`;
export default define('footer', class extends HTMLElement {
constructor() {
super();
this.__setup();
}
__setup() {
this.innerHTML = template({
data: {
links: config.footerLinks
}
});
this.__events();
}
__events() {
}
}, { extends: 'footer' });

View File

@ -1,22 +0,0 @@
import define from '../../utils/define.js';
const template = () => /*html*/`
<h1>
Welcome to mwmbl, the free, open-source and non-profit search engine.
</h1>
<p>
You can start searching by using the search bar above!
</p>
`;
export default define('home', class extends HTMLLIElement {
constructor() {
super();
this.classList.add('home');
this.__setup();
}
__setup() {
this.innerHTML = template();
}
}, { extends: 'li' });

View File

@ -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();

View File

@ -8,11 +8,11 @@ const CURATION_URL = config.publicApiURL + "curation/";
const template = () => /*html*/`
<span>🖫</span>
<span></span>
`;
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' });

View File

@ -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*/`
<form class="search-bar">
<i class="ph-magnifying-glass-bold"></i>
<input
type='search'
class='search-bar-input'
placeholder='Search on mwmbl...'
title='Use "CTRL+K" or "/" to focus.'
autocomplete='off'
>
</form>
`;
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();
}
});

View File

@ -1,84 +0,0 @@
import define from '../utils/define.js';
import config from "../../config.js";
const template = () => /*html*/`
<form>
<h5>Register</h5>
<div>
<label for="register-email">Email</label>
<div>
<input class="form-control" type="text" id="register-email" autocomplete="email" required="" minlength="3">
</div>
<label for="register-username">Username</label>
<div>
<input class="form-control" type="text" id="register-username" autocomplete="username" required="" minlength="3">
</div>
</div>
<div>
<label for="register-password">Password</label>
<div>
<input type="password" id="register-password" autocomplete="password" required="" maxlength="60">
</div>
</div>
<div>
<label for="register-password">Confirm password</label>
<div>
<input type="password" id="register-password-verify" autocomplete="confirm password" required="" maxlength="60">
</div>
</div>
<div>
<button class="btn btn-secondary" type="submit">Register</button>
</div>
</form>
`;
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);
}
}
});

View File

@ -1,110 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Metas -->
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Page title -->
<title>MWMBL - Search</title>
<meta name="description" content="The free, open-source and non-profit search engine.">
<!-- Favicons -->
<link rel="icon" href="/images/favicon.svg" type="image/svg+xml">
<!-- Fonts import -->
<link rel="preload" href="/fonts/inter/inter.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript>
<link rel="stylesheet" href="/fonts/inter/inter.css">
</noscript>
<!-- CSS Stylesheets (this is critical CSS) -->
<link rel="stylesheet" type="text/css" href="/css/reset.css">
<link rel="stylesheet" type="text/css" href="/css/theme.css">
<link rel="stylesheet" type="text/css" href="/css/global.css">
<!-- Phosphor Icons (https://github.com/phosphor-icons/phosphor-home) -->
<link rel="preload" href="/fonts/phosphor/icons.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript>
<link rel="stylesheet" href="/fonts/phosphor/icons.css">
</noscript>
<!-- Custom Element Polyfill for Safari -->
<script src="https://unpkg.com/@ungap/custom-elements" type="module"></script>
<!-- OpenSearch -->
<link rel="search" type="application/opensearchdescription+xml" href="../assets/opensearch.xml" title="MWMBL Search">
<!-- POC temporary use of jQueryUI! -->
<link rel="stylesheet" href="//code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.js"></script>
</head>
<body>
<!-- <mwmbl-login></mwmbl-login>-->
<!-- <mwmbl-register></mwmbl-register>-->
<mwmbl-app></mwmbl-app>
<noscript>
<!-- https://stackoverflow.com/a/431554 -->
<style> .jsonly { display: none } </style>
<main class="noscript">
<img class="brand-icon" src="/static/images/logo.svg" width="40" height="40" alt="mwmbl logo">
<h1>
Welcome to mwmbl, the free, open-source and non-profit search engine.
</h1>
<p>This website requires you to support/enable scripts.</p>
<p>
More information on
<a href="https://github.com/mwmbl/mwmbl" target="__blank">
Github
</a>
.
</p>
</main>
</noscript>
<!-- Javasript entrypoint -->
<script src="https://unpkg.com/htmx.org@1.9.6"></script>
<script src="./index.js" type="module"></script>
<main class="jsonly">
<header class="search-menu">
<ul>
<li is="${save}"></li>
</ul>
<div><a href="/accounts/login/">Login</a> <a href="/accounts/signup/">Sign up</a> </div>
<div class="branding">
<img class="brand-icon" src="/static/images/logo.svg" width="40" height="40" alt="mwmbl logo">
<span class="brand-title">MWMBL</span>
</div>
<form class="search-bar">
<i class="ph-magnifying-glass-bold"></i>
<input
type='search'
name='query'
class='search-bar-input'
placeholder='Search on mwmbl...'
title='Use "CTRL+K" or "/" to focus.'
autocomplete='off'
hx-get="/app/search/"
hx-trigger="keyup changed delay:100ms"
hx-target=".results"
>
</form>
</header>
<main>
<mwmbl-results>
<ul class='results'>
<li is='${home}'></li>
</ul>
</mwmbl-results>
</main>
<div is="mwmbl-add-result"></div>
<footer is="mwmbl-footer"></footer>
</main>
</body>
</html>

View File

@ -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");
}
})();

View File

@ -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({

View File

@ -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",
},
]

View File

@ -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

View File

@ -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"])}

View File

@ -1,5 +1,39 @@
{% extends 'base.html' %}
{% block content %}
<h2>Welcome, {{ user.username }}!</h2>
{% endblock %}
{% load result_filters %}
{% load humanize %}
{% include "title.html" %}
{% if query %}
{% if results %}
{% for result in results %}
<li class="result" is="mwmbl-result">
<div class="result-container">
<div class="curation-buttons">
<button class="curation-button curate-delete" is="mwmbl-delete-button"></button>
<button class="curation-button curate-approve" is="mwmbl-validate-button"></button>
<button class="curation-button curate-add" is="mwmbl-add-button"></button>
</div>
<div class="result-link">
<a href="{{result.url}}">
<p class='link'>{{result.url}}</p>
<p class='title'>{{result.title|strengthen}}</p>
<p class='extract'>{{result.extract|strengthen}}</p>
</a>
</div>
</div>
</li>
{% endfor %}
{% else %}
<li class="home">
<h1>
No results found for "{{query}}".
</h1>
</li>
{% endif %}
{% else %}
{% for item in activity %}
<li class="activity">
<h1>
{{ item.user }} made {{ item.num_curations | apnumber }} changes to <a href="{{ item.url }}">{{ item.query }}</a> {{ item.timestamp | naturaltime }}.
</h1>
</li>
{% endfor %}
{% endif %}

106
mwmbl/templates/index.html Normal file
View File

@ -0,0 +1,106 @@
{% load django_vite %}
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Metas -->
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% include "title.html" %}
<meta name="description" content="The free, open-source and non-profit search engine.">
<!-- Favicons -->
<link rel="icon" href="/static/images/favicon.svg" type="image/svg+xml">
<!-- Fonts import -->
<link rel="preload" href="/static/fonts/inter/inter.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript>
<link rel="stylesheet" href="/static/fonts/inter/inter.css">
</noscript>
<!-- CSS Stylesheets (this is critical CSS) -->
<link rel="stylesheet" type="text/css" href="/static/css/reset.css">
<link rel="stylesheet" type="text/css" href="/static/css/theme.css">
<link rel="stylesheet" type="text/css" href="/static/css/global.css">
<!-- Phosphor Icons (https://github.com/phosphor-icons/phosphor-home) -->
<link rel="preload" href="/static/fonts/phosphor/icons.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript>
<link rel="stylesheet" href="/static/fonts/phosphor/icons.css">
</noscript>
<!-- Custom Element Polyfill for Safari -->
<script src="https://unpkg.com/@ungap/custom-elements" type="module"></script>
<!-- OpenSearch -->
<link rel="search" type="application/opensearchdescription+xml" href="/static/assets/opensearch.xml" title="Mwmbl Search">
<!-- POC temporary use of jQueryUI! -->
<link rel="stylesheet" href="//code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.js"></script>
<script src="https://unpkg.com/htmx.org@1.9.6"></script>
{% vite_hmr_client %}
</head>
<body>
<mwmbl-app></mwmbl-app>
<header class="search-menu compact">
<a href="/">
<div class="branding">
<img class="brand-icon" src="/static/images/logo.svg" width="40" height="40" alt="mwmbl logo">
<span class="brand-title">Mwmbl</span>
</div>
</a>
<form class="search-bar">
<i class="ph-magnifying-glass-bold"></i>
<input
type='search'
name='q'
class='search-bar-input'
placeholder='Search on Mwmbl...'
title='Use "CTRL+K" or "/" to focus.'
autocomplete='off'
hx-get="/app/home/"
hx-trigger="keyup changed delay:100ms"
hx-target=".results"
>
</form>
<div is="mwmbl-save"></div>
{% if user.is_authenticated %}
<p class="login-info">Logged in as {{ user.username }}</p>
<a class="button" href="/accounts/logout/">Log out</a>
{% else %}
<a class="button" href="/accounts/login/">Login</a>
<a class="button" href="/accounts/signup/">Sign up</a>
{% endif %}
</header>
<main>
<mwmbl-results>
<ul class='results'>
{% include "home.html" %}
</ul>
</mwmbl-results>
</main>
<div is="mwmbl-add-result"></div>
<div class="footer">
<ul class="footer-list">
{% for link in footer_links %}
<li class="footer-item">
<a href="{{ link.href }}" class="footer-link" target="__blank">
<i class="{{ link.icon }}"></i>
<span>{{ link.name }}</span>
</a>
</li>
{% endfor %}
</ul>
</div>
{% vite_asset 'index.js' %}
{% vite_legacy_polyfills %}
{% vite_legacy_asset 'index-legacy.js' %}
</body>
</html>

View File

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% block title %}Profile Page{% endblock title %}
{% block content %}
<div class="row my-3 p-3">
<h1>This is the profile page for {{user.username}}</h1>
</div>
{% endblock content %}

View File

@ -1,19 +0,0 @@
{% load result_filters %}
{% for result in results %}
<li class="result" is="mwmbl-result">
<div class="result-container">
<div class="curation-buttons">
<button class="curation-button curate-delete" is="mwmbl-delete-button"></button>
<button class="curation-button curate-approve" is="mwmbl-validate-button"></button>
<button class="curation-button curate-add" is="mwmbl-add-button"></button>
</div>
<div class="result-link">
<a href="{{result.url}}">
<p class='link'>{{result.url}}</p>
<p class='title'>{{result.title|strengthen}}</p>
<p class='extract'>{{result.extract|strengthen}}</p>
</a>
</div>
</div>
</li>
{% endfor %}

View File

@ -0,0 +1,6 @@
<!-- Page title -->
{% if query %}
<title>Mwmbl - {{ query }}</title>
{% else %}
<title>Mwmbl - Search</title>
{% endif %}

View File

@ -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")
]

View File

@ -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,
})

View File

@ -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;

138
poetry.lock generated
View File

@ -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"

View File

@ -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 = [