Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove jQuery #1431

Draft
wants to merge 14 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,5 @@ dump.rdb
# Ignore IRB files
.irbrc
.irb_history

node_modules
10 changes: 8 additions & 2 deletions app/assets/javascripts/admin.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
$(() => {
/*!
* FILE NOTE
* 2024-10-12
* Not fully converted away from jQuery yet because of rails-ujs' dependency on it.
*/

document.addEventListener('DOMContentLoaded', () => {
$('.js-destroy-user').on('ajax:success', () => {
location.href = '/users';
});
Expand All @@ -8,4 +14,4 @@ $(() => {
}).on('ajax:error', (_ev, xhr) => {
QPixel.createNotification('danger', xhr.responseJSON.message || 'Failed to delete.');
});
});
});
Empty file.
76 changes: 39 additions & 37 deletions app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,65 +15,67 @@
//= require jquery_ujs
//= require_tree .

$(document).on('ready', function() {
$("a.flag-dialog-link").bind("click", (ev) => {
document.addEventListener('DOMContentLoaded', async () => {
QPixel.DOM.addSelectorListener('click', 'a.flag-dialog-link', (ev) => {
ev.preventDefault();
const self = $(ev.target);
self.parents(".post--body").find(".js-flag-box").toggleClass("is-active");
const tgt = /** @type {HTMLElement} */(ev.target);
const flagDialog = tgt.closest('.post--body').querySelector('.js-flag-box');
flagDialog.classList.toggle('is-active');
});

$('.close-dialog-link').on('click', (ev) => {
QPixel.DOM.addSelectorListener('click', '.close-dialog-link', (ev) => {
ev.preventDefault();
const self = $(ev.target);
console.log(self.parents(".post--body").find(".js-close-box").toggleClass("is-active"));
const tgt = /** @type {HTMLElement} */(ev.target);
const dialog = tgt.closest('.post--body').querySelector('.js-close-box');
dialog.classList.toggle('is-active');
});

$("a.show-all-flags-dialog-link").bind("click", (ev) => {
QPixel.DOM.addSelectorListener('click', '.show-all-flags-dialog-link', (ev) => {
ev.preventDefault();
const self = $(ev.target);
self.parents(".post--body").find(".js-flags").toggleClass("is-active");
const tgt = /** @type {HTMLElement} */(ev.target);
const dialog = tgt.closest('.post--body').querySelector('.js-flags');
dialog.classList.toggle('is-active');
});

$("a.flag-resolve").bind("click", function(ev) {
QPixel.DOM.addSelectorListener('click', '.flag-resolve', async (ev) => {
ev.preventDefault();
var self = $(this);
var id = self.data("flag-id");
var data = {
'result': self.data("result"),
'message': self.parent().parent().find(".flag-resolve-comment").val()
const tgt = /** @type {HTMLElement} */(ev.target);
const id = tgt.dataset.flagId;
const data = {
result: tgt.dataset.result,
message: tgt.parentNode.parentNode.querySelector('.flag-resolve-comment').value
};

$.ajax({
'type': 'POST',
'url': '/mod/flags/' + id + '/resolve',
'data': data,
'el': self
})
.done(function(response) {
if(response['status'] !== 'success') {
QPixel.createNotification('danger', "<strong>Failed:</strong> " + response['message']);
const req = await fetch(`/mod/flags/${id}/resolve`, {
method: 'POST',
body: JSON.stringify(data),
credentials: 'include',
headers: { 'X-CSRF-Token': QPixel.csrfToken() }
});
if (req.status === 200) {
const res = await req.json();
if (res.status === 'success') {
const flagContainer = /** @type {HTMLElement} */(tgt.parentNode.parentNode.parentNode);
QPixel.DOM.fadeOut(flagContainer, 200);
}
else {
$(this.el).parent().parent().parent().fadeOut(200, function() {
$(this).remove();
});
QPixel.createNotification('danger', `<strong>Failed:</strong> ${res.message}`);
}
})
.fail(function(jqXHR, textStatus, errorThrown) {
QPixel.createNotification('danger', "<strong>Failed:</strong> " + jqXHR.status);
console.log(jqXHR.responseText);
});
}
else {
QPixel.createNotification('danger', `<strong>Failed:</strong> Unexpected status (${req.status})`);
}
});

if (document.cookie.indexOf('dismiss_fvn') === -1 ) {
$('#fvn-dismiss').on('click', () => {
if (document.cookie.indexOf('dismiss_fvn') === -1) {
QPixel.DOM.addSelectorListener('click', '#fvn-dismiss', (_ev) => {
document.cookie = 'dismiss_fvn=true; path=/; expires=Fri, 31 Dec 9999 23:59:59 GMT';
});
}
});

const cssVar = name => window.getComputedStyle(document.documentElement).getPropertyValue(`--${name}`).trim();
const cssVar = (name) => window.getComputedStyle(document.documentElement).getPropertyValue(`--${name}`).trim();

Chartkick.setDefaultOptions({
colors: Array.from(Array(5).keys()).map(idx => cssVar(`data-${idx}`))
colors: Array.from(Array(5).keys()).map((idx) => cssVar(`data-${idx}`))
});
2 changes: 1 addition & 1 deletion app/assets/javascripts/caret.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
} else if (height === targetHeight) {
style.lineHeight = computed.lineHeight;
} else {
style.lineHeight = 0;
style.lineHeight = '0';
}
} else {
style.lineHeight = computed.height;
Expand Down
36 changes: 18 additions & 18 deletions app/assets/javascripts/categories.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
$(() => {
$('.js-category-tag-set-select').on('change', ev => {
$('.js-category-tag-set-select').on('change', (ev) => {
const $tgt = $(ev.target);
const tagSetId = $tgt.val();
const tagSetId = $tgt.val()?.toString();
const formGroups = $('.js-category-tags-group');
if (tagSetId) {
formGroups.each((i, el) => {
formGroups.each((_i, el) => {
const $el = $(el);
const $caption = $el.find('.js-tags-group-caption');
$caption.find('[data-state="absent"]').hide();
Expand All @@ -14,7 +14,7 @@ $(() => {
});
}
else {
formGroups.each((i, el) => {
formGroups.each((_i, el) => {
const $el = $(el);
const $caption = $el.find('.js-tags-group-caption');
$caption.find('[data-state="absent"]').show();
Expand All @@ -25,50 +25,50 @@ $(() => {
}
});

$('.js-add-required-topic').on('click', ev => {
$('.js-add-required-topic').on('click', (_ev) => {
const $required = $('.js-required-tags');
const $topic = $('.js-topic-tags');
const union = ($required.val() || []).concat($topic.val() || []);

const options = $topic.find('option').toArray();
const optionIds = options.map(x => $(x).attr('value'));
const missing = union.filter(x => !optionIds.includes(x));
const missingOptions = $required.find('option').toArray().filter(x => missing.includes($(x).attr('value')));
const optionIds = options.map((x) => $(x).attr('value'));
const missing = union.filter((x) => !optionIds.includes(x));
const missingOptions = $required.find('option').toArray().filter((x) => missing.includes($(x).attr('value')));

missingOptions.forEach(opt => {
missingOptions.forEach((opt) => {
const $append = $(opt).clone();
$append.removeAttr('data-select2-id');
$topic.append($append);
});
$topic.val(union).trigger('change');
});

$('.js-category-change-select').each((i, el) => {
$('.js-category-change-select').each((_i, el) => {
const $tgt = $(el);
$tgt.select2({
ajax: {
url: '/categories',
headers: { 'Accept': 'application/json' },
delay: 100,
processResults: data => ({results: data.map(c => ({id: c.id, text: c.name}))}),
processResults: (data) => ({results: data.map((c) => ({id: c.id, text: c.name}))}),
}
});
});

$('.js-change-category').on('ajax:success', ev => {
$('.js-change-category').on('ajax:success', (_ev) => {
location.reload();
}).on('ajax:error', (ev, xhr) => {
}).on('ajax:error', (_ev, xhr) => {
const data = xhr.responseJSON;
QPixel.createNotification('danger', `Failed (${xhr.status}): ${data.errors.join(', ')}`);
});

$(document).on('click', '.js-update-cpt', async ev => {
$(document).on('click', '.js-update-cpt', async (ev) => {
const $tgt = $(ev.target);
const $widget = $tgt.parents('.widget');
const categoryId = $tgt.attr('data-category');
const postTypeId = parseInt($widget.find('.js-cpt-post-type').val(), 10) || null;
const upvoteRep = parseInt($widget.find('.js-cpt-upvote-rep').val(), 10) || 0;
const downvoteRep = parseInt($widget.find('.js-cpt-downvote-rep').val(), 10) || 0;
const postTypeId = parseInt($widget.find('.js-cpt-post-type').val()?.toString(), 10) || null;
const upvoteRep = parseInt($widget.find('.js-cpt-upvote-rep').val()?.toString(), 10) || 0;
const downvoteRep = parseInt($widget.find('.js-cpt-downvote-rep').val()?.toString(), 10) || 0;

const resp = await fetch(`/categories/${categoryId}/edit/post-types`, {
method: 'POST',
Expand All @@ -94,7 +94,7 @@ $(() => {
}
});

$(document).on('click', '.js-delete-cpt', async ev => {
$(document).on('click', '.js-delete-cpt', async (ev) => {
const $tgt = $(ev.target);
const categoryId = $tgt.attr('data-category');
const postTypeId = $tgt.attr('data-post-type');
Expand Down
5 changes: 4 additions & 1 deletion app/assets/javascripts/character_count.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ $(() => {

/**
* Sets the icon to show before the counter, if any
* @param {JQuery} el
* @param {CounterIcon} icon name of the icon to show
*/
const setCounterIcon = (el, icon) => {
Expand Down Expand Up @@ -36,6 +37,7 @@ $(() => {

/**
* Sets the input's validation state
* @param {JQuery} el
* @param {InputValidationState} state the state to set
*/
const setInputValidationState = (el, state) => {
Expand All @@ -45,6 +47,7 @@ $(() => {

/**
* Sets the submit button's disabled state
* @param {JQuery} el
* @param {SubmitButtonDisabledState} state the state to set
*/
const setSubmitButtonDisabledState = (el, state) => {
Expand Down Expand Up @@ -89,7 +92,7 @@ $(() => {
$count.text(text);
});

$(document).on('ajax:success', 'form', ev => {
$(document).on('ajax:success', 'form', (ev) => {
const $tgt = $(ev.target);
$tgt.find('[data-character-count]').val('').trigger('change');
});
Expand Down
67 changes: 35 additions & 32 deletions app/assets/javascripts/closure.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,52 @@
$(() => {
$('.js-close-question').on('click', (ev) => {
document.addEventListener('DOMContentLoaded', async () => {
QPixel.DOM.addSelectorListener('click', '.js-close-question', async (ev) => {
ev.preventDefault();

const self = $(ev.target);
const activeRadio = self.parents('.js-close-box').find("input[type='radio'][name='close-reason']:checked");
const otherPostInput = activeRadio.parents('.widget--body').find('.js-close-other-post');
const otherPostRequired = activeRadio.attr('data-rop') === 'true';
const self = /** @type {HTMLElement} */(ev.target);
const activeRadio = self.closest('.js-close-box').querySelector("input[type='radio'][name='close-reason']:checked");

if (!activeRadio) {
QPixel.createNotification('danger', 'You must select a close reason.');
return;
}

const otherPostInput = activeRadio.closest('.widget--body').querySelector('.js-close-other-post');
const otherPostRequired = activeRadio.dataset.rop === 'true';
const data = {
'reason_id': activeRadio.val(),
'other_post': otherPostInput.val()
'reason_id': parseInt(activeRadio.value, 10),
'other_post': otherPostInput?.value
// option will be silently discarded if no input element
};

if (data['other_post']) {
if (data['other_post'].match(/\/[0-9]+$/)) {
data['other_post'] = data['other_post'].replace(/.*\/([0-9]+)$/, "$1");
}
if (data['other_post'] && data['other_post'].match(/\/[0-9]+$/)) {
data['other_post'] = data['other_post'].replace(/.*\/([0-9]+)$/, "$1");
}

if (!activeRadio.val()) {
QPixel.createNotification('danger', 'You must select a close reason.');
return;
}
if (!otherPostInput.val() && otherPostRequired) {
if (!otherPostInput?.value && otherPostRequired) {
QPixel.createNotification('danger', 'You must enter an ID or URL to another post.');
return;
}

$.ajax({
'type': 'POST',
'url': '/posts/' + self.data('post-id') + '/close',
'data': data,
'target': self
})
.done((response) => {
if(response.status !== 'success') {
QPixel.createNotification('danger', '<strong>Failed:</strong> ' + response.message);
const req = await fetch(`/posts/${self.dataset.postId}/close`, {
method: 'POST',
body: JSON.stringify(data),
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': QPixel.csrfToken()
}
else {
});
if (req.status === 200) {
const res = await req.json();
if (res.status === 'success') {
location.reload();
}
})
.fail((jqXHR, textStatus, errorThrown) => {
QPixel.createNotification('danger', '<strong>Failed:</strong> ' + jqXHR.status);
console.log(jqXHR.responseText);
});
else {
QPixel.createNotification('danger', `<strong>Failed:</strong> ${res.message}`);
}
}
else {
QPixel.createNotification('danger', `<strong>Failed:</strong> ${req.status}`);
}
});
});
Loading
Loading