Skip to content

Commit

Permalink
wip: remove jquery
Browse files Browse the repository at this point in the history
removed jquery from components:
- ForumApplication
- AbstractPost
- Search
  • Loading branch information
YUCLing committed Nov 15, 2024
1 parent e29df8a commit 297a3d6
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 53 deletions.
8 changes: 5 additions & 3 deletions framework/core/js/src/forum/ForumApplication.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,11 @@ export default class ForumApplication extends Application {
});

if (isSafariMobile()) {
$(() => {
$('.App').addClass('mobile-safari');
});
const callback = () => {
document.querySelector('.App')?.classList.add('mobile-safari');
};
document.addEventListener('DOMContentLoaded', callback);
if (document.readyState != 'loading') callback();
}
}

Expand Down
10 changes: 4 additions & 6 deletions framework/core/js/src/forum/components/AbstractPost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ export default abstract class AbstractPost<CustomAttrs extends IAbstractPostAttr
buttonClassName="Button Button--icon Button--flat"
menuClassName="Dropdown-menu--right"
icon="fas fa-ellipsis-h"
onshow={() => this.$('.Post-controls').addClass('open')}
onhide={() => this.$('.Post-controls').removeClass('open')}
accessibleToggleLabel={app.translator.trans('core.forum.post_controls.toggle_dropdown_accessible_label')}
>
{controls}
Expand All @@ -92,10 +90,10 @@ export default abstract class AbstractPost<CustomAttrs extends IAbstractPostAttr
onupdate(vnode: Mithril.VnodeDOM<CustomAttrs, this>) {
super.onupdate(vnode);

const $actions = this.$('.Post-actions');
const $controls = this.$('.Post-controls');

$actions.toggleClass('openWithin', $controls.hasClass('open'));
this.element.querySelector('.Post-actions')?.classList.toggle(
'openWithin',
this.element.querySelector('.Post-controls')?.classList.contains('open')
);
}

elementAttrs(): Record<string, unknown> {
Expand Down
96 changes: 52 additions & 44 deletions framework/core/js/src/forum/components/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,28 +198,29 @@ export default class Search<T extends SearchAttrs = SearchAttrs> extends Compone
// Highlight the item that is currently selected.
this.setIndex(this.getCurrentNumericIndex());

this.$('.Search-results')
.on('mousedown', (e) => e.preventDefault())
.on('click', () => this.$('input').trigger('blur'))

// Whenever the mouse is hovered over a search result, highlight it.
.on('mouseenter', '> li:not(.Dropdown-header)', function () {
search.setIndex(search.selectableItems().index(this));
});

const $input = this.$('input') as JQuery<HTMLInputElement>;
const searchResults = this.element.querySelector('.Search-results')!;
searchResults.addEventListener('mousedown', (e) => e.preventDefault());
searchResults.addEventListener('click', () => this.element.querySelector('input')?.blur());
// Whenever the mouse is hovered over a search result, highlight it.
searchResults.addEventListener('mouseenter', function(e) {
const el = e.target as HTMLElement;
if (el.parentElement != searchResults || el.tagName != 'LI' || el.classList.contains('Dropdown-header')) return;
search.setIndex(search.selectableItems().indexOf(el as HTMLLIElement));
});

const input = this.element.querySelector('input')!;

this.navigator = new KeyboardNavigatable();
this.navigator
.onUp(() => this.setIndex(this.getCurrentNumericIndex() - 1, true))
.onDown(() => this.setIndex(this.getCurrentNumericIndex() + 1, true))
.onSelect(this.selectResult.bind(this), true)
.onCancel(this.clear.bind(this))
.bindTo($input);
.bindTo(input);

// Handle input key events on the search input, triggering results to load.
$input
.on('input focus', function () {
['input', 'focus'].forEach(ev => {
input.addEventListener(ev as 'input' | 'focus', function() {
const query = this.value.toLowerCase();

if (!query) return;
Expand All @@ -244,13 +245,12 @@ export default class Search<T extends SearchAttrs = SearchAttrs> extends Compone
state.cache(query);
m.redraw();
}, 250);
})

.on('focus', function () {
$(this)
.one('mouseup', (e) => e.preventDefault())
.trigger('select');
});
});
input.addEventListener('focus', function() {
this.addEventListener('mouseup', (e) => e.preventDefault(), { once: true });
this.dispatchEvent(new Event('select'));
});

this.updateMaxHeightHandler = this.updateMaxHeight.bind(this);
window.addEventListener('resize', this.updateMaxHeightHandler);
Expand All @@ -272,14 +272,14 @@ export default class Search<T extends SearchAttrs = SearchAttrs> extends Compone

this.loadingSources = 0;

const selectedUrl = this.getItem(this.index).find('a').attr('href');
const selectedUrl = (this.getItem(this.index).querySelector('a') as HTMLLinkElement | null)?.href;
if (this.searchState.getValue() && selectedUrl) {
m.route.set(selectedUrl);
} else {
this.clear();
}

this.$('input').blur();
this.element.querySelector('input')?.blur();
}

/**
Expand All @@ -304,67 +304,75 @@ export default class Search<T extends SearchAttrs = SearchAttrs> extends Compone
/**
* Get all of the search result items that are selectable.
*/
selectableItems(): JQuery {
return this.$('.Search-results > li:not(.Dropdown-header)');
selectableItems(): HTMLLIElement[] {
return Array.from(this.element.querySelectorAll('.Search-results > li:not(.Dropdown-header)'));
}

/**
* Get the position of the currently selected search result item.
* Returns zero if not found.
*/
getCurrentNumericIndex(): number {
return Math.max(0, this.selectableItems().index(this.getItem(this.index)));
return Math.max(0, this.selectableItems().indexOf(this.getItem(this.index)));
}

/**
* Get the <li> in the search results with the given index (numeric or named).
*/
getItem(index: number): JQuery {
const $items = this.selectableItems();
let $item = $items.filter(`[data-index="${index}"]`);
getItem(index: number): HTMLLIElement {
const items = this.selectableItems();
const filtered = items.filter((v) => v.getAttribute('data-index') == index.toString());

if (!$item.length) {
$item = $items.eq(index);
if (!filtered.length) {
return items[index];
}

return $item;
return filtered[0];
}

/**
* Set the currently-selected search result item to the one with the given
* index.
*/
setIndex(index: number, scrollToItem: boolean = false) {
const $items = this.selectableItems();
const $dropdown = $items.parent();
const items = this.selectableItems();

let fixedIndex = index;
if (index < 0) {
fixedIndex = $items.length - 1;
} else if (index >= $items.length) {
fixedIndex = items.length - 1;
} else if (index >= items.length) {
fixedIndex = 0;
}

const $item = $items.removeClass('active').eq(fixedIndex).addClass('active');
items.forEach(el => el.classList.remove('active'));
const item = items[fixedIndex];
const dropdown = item.parentElement!;
item.classList.add('active');

this.index = parseInt($item.attr('data-index') as string) || fixedIndex;
this.index = parseInt(item.getAttribute('data-index') as string) || fixedIndex;

if (scrollToItem) {
const dropdownScroll = $dropdown.scrollTop()!;
const dropdownTop = $dropdown.offset()!.top;
const dropdownBottom = dropdownTop + $dropdown.outerHeight()!;
const itemTop = $item.offset()!.top;
const itemBottom = itemTop + $item.outerHeight()!;
const documentScrollTop = document.documentElement.scrollTop;
const dropdownScroll = dropdown.scrollTop!;
const dropdownRect = dropdown.getBoundingClientRect();
const dropdownTop = dropdownRect.top + documentScrollTop;
const dropdownBottom = dropdownTop + dropdownRect.height;
const itemRect = item.getBoundingClientRect();
const itemTop = itemRect.top + documentScrollTop;
const itemBottom = itemTop + itemRect.height;

let scrollTop;
if (itemTop < dropdownTop) {
scrollTop = dropdownScroll - dropdownTop + itemTop - parseInt($dropdown.css('padding-top'), 10);
scrollTop = dropdownScroll - dropdownTop + itemTop - parseInt(dropdown.style.paddingTop, 10);
} else if (itemBottom > dropdownBottom) {
scrollTop = dropdownScroll - dropdownBottom + itemBottom + parseInt($dropdown.css('padding-bottom'), 10);
scrollTop = dropdownScroll - dropdownBottom + itemBottom + parseInt(dropdown.style.paddingBottom, 10);
}

if (typeof scrollTop !== 'undefined') {
$dropdown.stop(true).animate({ scrollTop }, 100);
dropdown.scrollTo({
top: scrollTop,
behavior: 'smooth'
});
}
}
}
Expand Down

0 comments on commit 297a3d6

Please sign in to comment.