commit
5f47f45ebc
35 changed files with 434 additions and 736 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -17,6 +17,7 @@ __pycache__/
|
|||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
front-end/dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
|
|
Binary file not shown.
|
@ -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;
|
||||
|
@ -324,3 +321,27 @@ a {
|
|||
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;
|
||||
}
|
|
@ -104,3 +104,19 @@ 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";
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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=',
|
||||
|
|
32
front-end/package-lock.json
generated
32
front-end/package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -11,5 +11,8 @@
|
|||
"@vitejs/plugin-legacy": "^2.3.1",
|
||||
"terser": "^5.16.1",
|
||||
"vite": "^3.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"chart.js": "^4.4.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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 {
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -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' });
|
|
@ -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', {
|
||||
|
|
|
@ -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' });
|
|
@ -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' });
|
|
@ -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();
|
||||
|
|
|
@ -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' });
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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>
|
|
@ -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");
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
|
||||
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"])}
|
||||
|
||||
|
|
|
@ -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
106
mwmbl/templates/index.html
Normal 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>
|
|
@ -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 %}
|
|
@ -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 %}
|
6
mwmbl/templates/title.html
Normal file
6
mwmbl/templates/title.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
<!-- Page title -->
|
||||
{% if query %}
|
||||
<title>Mwmbl - {{ query }}</title>
|
||||
{% else %}
|
||||
<title>Mwmbl - Search</title>
|
||||
{% endif %}
|
|
@ -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")
|
||||
]
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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
138
poetry.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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 = [
|
||||
|
|
Loading…
Reference in a new issue