Fix broken JS

This commit is contained in:
Daoud Clarke 2023-11-07 18:59:38 +00:00
parent d7ad64b4e0
commit 28b326aedf
14 changed files with 63 additions and 218 deletions

View file

@ -1,7 +1,4 @@
import define from "../../utils/define.js"; 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 { 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 result = this.closest('.result');
const parent = result.parentNode; 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); console.log("Delete index", index);
const beginCuratingEvent = new CustomEvent('curate-delete-result', { 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 result = this.closest('.result');
const parent = result.parentNode; 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); console.log("Validate index", index);
const curationValidateEvent = new CustomEvent('curate-validate-result', { const curationValidateEvent = new CustomEvent('curate-validate-result', {

View file

@ -10,19 +10,12 @@ class ResultsHandler {
__setup() { __setup() {
this.__events(); this.__events();
this.__initializeResults();
} }
__events() { __events() {
document.body.addEventListener('htmx:load', e => { document.body.addEventListener('htmx:load', e => {
this.results = document.querySelector('.results'); this.__initializeResults();
// Allow the user to re-order search results
$(".results").sortable({
"activate": this.__sortableActivate.bind(this),
"deactivate": this.__sortableDeactivate.bind(this),
});
this.curating = false;
}); });
// Focus first element when coming from the search bar // Focus first element when coming from the search bar
@ -113,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) { __sortableActivate(event, ui) {
console.log("Sortable activate", ui); console.log("Sortable activate", ui);
this.__beginCurating(); this.__beginCurating();

View file

@ -1,180 +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 === '/' || e.key === 'Escape') {
e.preventDefault();
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

@ -5,6 +5,7 @@
* Please do not pollute this file if you can make * Please do not pollute this file if you can make
* util or component files instead. * util or component files instead.
*/ */
import 'vite/modulepreload-polyfill';
// Waiting for top-level await to be better supported. // Waiting for top-level await to be better supported.
(async () => { (async () => {
@ -17,5 +18,10 @@
import("./components/organisms/results.js"); import("./components/organisms/results.js");
import("./components/organisms/footer.js"); import("./components/organisms/footer.js");
import("./components/organisms/save.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

@ -7,12 +7,14 @@ export default {
publicDir: '../assets', publicDir: '../assets',
build: { build: {
outDir: '../dist', outDir: '../dist',
manifest: true,
rollupOptions: { rollupOptions: {
input: { input: {
index: resolve(__dirname, 'src/index.js'), index: resolve(__dirname, 'src/index.js'),
stats: resolve(__dirname, 'src/stats/index.html'), stats: resolve(__dirname, 'src/stats/index.html'),
}, },
}, },
minify: false,
}, },
plugins: [ plugins: [
legacy({ legacy({

View file

@ -30,7 +30,8 @@ INSTALLED_APPS = [
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'mwmbl', 'mwmbl',
"django_htmx", 'django_htmx',
'django_vite',
'allauth', 'allauth',
'allauth.account', 'allauth.account',
'allauth.socialaccount', 'allauth.socialaccount',
@ -106,6 +107,9 @@ USE_TZ = True
STATIC_URL = 'static/' STATIC_URL = 'static/'
DJANGO_VITE_DEV_MODE = False
# Default primary key field type # Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

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 DEBUG = True

View file

@ -9,8 +9,9 @@ SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
STATIC_ROOT = "/app/static/" 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"])} DATABASES = {'default': dj_database_url.config(default=os.environ["DATABASE_URL"])}

View file

@ -1,3 +1,4 @@
{% load django_vite %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@ -45,6 +46,9 @@
<link rel="stylesheet" href="//code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css"> <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/jquery-3.6.0.js"></script>
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.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> </head>
<body> <body>
@ -53,9 +57,7 @@
<mwmbl-app></mwmbl-app> <mwmbl-app></mwmbl-app>
<!-- Javasript entrypoint --> <!-- Javasript entrypoint -->
<script src="https://unpkg.com/htmx.org@1.9.6"></script> {#<script src="/static/src/index.js" type="module"></script>#}
<script src="/static/src/index.js" type="module"></script>
<header class="search-menu"> <header class="search-menu">
<!-- <ul> <!-- <ul>
<li is="${save}"></li> <li is="${save}"></li>
@ -89,6 +91,9 @@
</main> </main>
<div is="mwmbl-add-result"></div> <div is="mwmbl-add-result"></div>
<footer is="mwmbl-footer"></footer> <footer is="mwmbl-footer"></footer>
{% vite_asset 'index.js' %}
{% vite_legacy_polyfills %}
{% vite_legacy_asset 'index-legacy.js' %}
</body> </body>
</html> </html>

19
poetry.lock generated
View file

@ -592,6 +592,23 @@ dev = ["pre-commit"]
doc = ["markdown-include", "mkdocs", "mkdocs-material", "mkdocstrings"] 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"] 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]] [[package]]
name = "exceptiongroup" name = "exceptiongroup"
version = "1.1.3" version = "1.1.3"
@ -2544,4 +2561,4 @@ indexer = ["Levenshtein", "beautifulsoup4", "idna", "langdetect", "lxml", "pyarr
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.10,<3.11" python-versions = ">=3.10,<3.11"
content-hash = "88ebe68ab5d0445ed67c07723156e622ebe10dbc50381b4982741f1404c0e8ce" content-hash = "4e4233221e9f3bd317c0693584612898b7b736f45983b7f3f5bad4d43e567353"

View file

@ -40,6 +40,7 @@ redis = {extras = ["hiredis"], version = "^5.0.1"}
django-allauth = "^0.57.0" django-allauth = "^0.57.0"
dj-database-url = "^2.1.0" dj-database-url = "^2.1.0"
django-htmx = "^1.17.0" django-htmx = "^1.17.0"
django-vite = "^2.1.3"
[tool.poetry.extras] [tool.poetry.extras]
indexer = [ indexer = [