diff --git a/app/assets/javascripts/livestamp.min.js b/app/assets/javascripts/livestamp.min.js deleted file mode 100644 index 88583a8f3..000000000 --- a/app/assets/javascripts/livestamp.min.js +++ /dev/null @@ -1,4 +0,0 @@ -// Livestamp.js / v1.1.2 / (c) 2012 Matt Bradley / MIT License -(function(d,g){var h=1E3,i=!1,e=d([]),j=function(b,a){var c=b.data("livestampdata");"number"==typeof a&&(a*=1E3);b.removeAttr("data-livestamp").removeData("livestamp");a=g(a);g.isMoment(a)&&!isNaN(+a)&&(c=d.extend({},{original:b.contents()},c),c.moment=g(a),b.data("livestampdata",c).empty(),e.push(b[0]))},k=function(){i||(f.update(),setTimeout(k,h))},f={update:function(){d("[data-livestamp]").each(function(){var a=d(this);j(a,a.data("livestamp"))});var b=[];e.each(function(){var a=d(this),c=a.data("livestampdata"); - if(void 0===c)b.push(this);else if(g.isMoment(c.moment)){var e=a.html(),c=c.moment.fromNow();if(e!=c){var f=d.Event("change.livestamp");a.trigger(f,[e,c]);f.isDefaultPrevented()||a.html(c)}}});e=e.not(b)},pause:function(){i=!0},resume:function(){i=!1;k()},interval:function(b){if(void 0===b)return h;h=b}},l={add:function(b,a){"number"==typeof a&&(a*=1E3);a=g(a);g.isMoment(a)&&!isNaN(+a)&&(b.each(function(){j(d(this),a)}),f.update());return b},destroy:function(b){e=e.not(b);b.each(function(){var a= - d(this),c=a.data("livestampdata");if(void 0===c)return b;a.html(c.original?c.original:"").removeData("livestampdata")});return b},isLivestamp:function(b){return void 0!==b.data("livestampdata")}};d.livestamp=f;d(function(){f.resume()});d.fn.livestamp=function(b,a){l[b]||(a=b,b="add");return l[b](this,a)}})(jQuery,moment); diff --git a/app/assets/javascripts/notifications.js b/app/assets/javascripts/notifications.js index 021635266..4799e2c54 100644 --- a/app/assets/javascripts/notifications.js +++ b/app/assets/javascripts/notifications.js @@ -1,28 +1,39 @@ $(() => { /** - * @param {QPixelNotification} notification + * @param {QPixelNotification} notification */ const makeNotification = (notification) => { const template = `
`; return template; }; /** - * @param {number} change + * @param {number} change */ const changeInboxCount = (change) => { const counter = $('.inbox-count'); @@ -51,20 +62,20 @@ $(() => { $('.inbox-toggle').on('click', async (ev) => { ev.preventDefault(); const $inbox = $('.inbox'); - if($inbox.hasClass("is-active")) { + if ($inbox.hasClass('is-active')) { const data = await QPixel.getNotifications(); - const $inboxContainer = $inbox.find(".inbox--container"); + const $inboxContainer = $inbox.find('.inbox--container'); $inboxContainer.html(''); - + const unread = data.filter((n) => !n.is_read); const read = data.filter((n) => n.is_read); - + unread.forEach((notification) => { const item = $(makeNotification(notification)); $inboxContainer.append(item); }); - + $inboxContainer.append(``); read.forEach((notification) => { const item = $(makeNotification(notification)); @@ -76,14 +87,17 @@ $(() => { $('.js-read-all-notifs').on('click', async (ev) => { ev.preventDefault(); - await QPixel.fetchJSON('/notifications/read_all', {}, { - headers: { 'Accept': 'application/json' } - }); + await QPixel.fetchJSON( + '/notifications/read_all', + {}, + { + headers: { Accept: 'application/json' }, + }, + ); $('.inbox-count').remove(); $('.js-notification').removeClass('is-teal').addClass('read'); - $('.js-notif-state').text('read'); $('.js-notification-toggle').html(` mark unread`); }); @@ -91,9 +105,13 @@ $(() => { const $tgt = $(evt.target); const id = $tgt.data('id'); - const resp = await QPixel.fetchJSON(`/notifications/${id}/read`, {}, { - headers: { 'Accept': 'application/json' } - }); + const resp = await QPixel.fetchJSON( + `/notifications/${id}/read`, + {}, + { + headers: { Accept: 'application/json' }, + }, + ); const data = await resp.json(); $tgt.parents('.js-notification')[0].outerHTML = makeNotification(data.notification); @@ -106,9 +124,13 @@ $(() => { const $tgt = $(ev.target).is('a') ? $(ev.target) : $(ev.target).parents('a'); const id = $tgt.attr('data-notif-id'); - const resp = await QPixel.fetchJSON(`/notifications/${id}/read`, {}, { - headers: { 'Accept': 'application/json' } - }); + const resp = await QPixel.fetchJSON( + `/notifications/${id}/read`, + {}, + { + headers: { Accept: 'application/json' }, + }, + ); const data = await resp.json(); if (data.status !== 'success') { diff --git a/app/assets/javascripts/posts.js b/app/assets/javascripts/posts.js index 44aad0f44..e0098d5e7 100644 --- a/app/assets/javascripts/posts.js +++ b/app/assets/javascripts/posts.js @@ -50,15 +50,19 @@ $(() => { const files = /** @type {HTMLInputElement} */ ($fileInput[0]).files; const form = /** @type {HTMLFormElement} */ ($tgt[0]); - // TODO: MaxUploadSize is a site setting and can be changed - if (files.length > 0 && files[0].size >= 2000000) { + const maxUploadSize = QPixel.MAX_UPLOAD_SIZE ?? 2 * 1024 * 1024; + + if (files.length > 0 && files[0].size >= maxUploadSize) { const isUploadModalOpened = $('#markdown-image-upload').hasClass('is-active'); const postField = $('.js-post-field'); postField.val(postField.val()?.toString().replace(placeholder, '')); if (!isUploadModalOpened) { - QPixel.createNotification('danger', `Can't upload files with size more than 2MB`); + QPixel.createNotification( + 'danger', + `Can't upload files with size more than ${QPixel.numberToHumanSize(maxUploadSize)}`, + ); } else { $tgt.find('.js-max-size').addClass('has-color-red-700 error-shake'); diff --git a/app/assets/javascripts/profile.js b/app/assets/javascripts/profile.js new file mode 100644 index 000000000..96341813d --- /dev/null +++ b/app/assets/javascripts/profile.js @@ -0,0 +1,32 @@ +document.addEventListener('DOMContentLoaded', () => { + document.querySelector('.js-submit-profile-edit')?.addEventListener('click', (ev) => { + const { target } = ev; + + if (!QPixel.DOM?.isHTMLElement(target)) { + return; + } + + const profileForm = target.closest('form'); + + profileForm?.querySelectorAll('input[type=file]').forEach((el) => { + const files = /** @type {HTMLInputElement} */ (el).files; + + const maxUploadSize = QPixel.MAX_UPLOAD_SIZE ?? 2 * 1024 * 1024; + + if (files.length > 0 && files[0].size >= maxUploadSize) { + if (!ev.defaultPrevented) { + ev.preventDefault(); + } + + const maxSizeCaption = profileForm?.querySelector(`.js-max-size[for='${el.id}']`); + + if (!maxSizeCaption) { + return; + } + + maxSizeCaption.classList.add('has-color-red-700', 'error-shake'); + setTimeout(() => maxSizeCaption?.classList.remove('error-shake'), 1000); + } + }); + }); +}); diff --git a/app/assets/javascripts/qpixel_api.js b/app/assets/javascripts/qpixel_api.js index acbf02447..b66ffb351 100644 --- a/app/assets/javascripts/qpixel_api.js +++ b/app/assets/javascripts/qpixel_api.js @@ -52,6 +52,36 @@ window.QPixel = { popped_modals_ct += 1; }, + supportedNumberLocales: () => { + try { + return Intl.NumberFormat.supportedLocalesOf( + Intl.getCanonicalLocales(QPixel.LOCALE ?? 'en') + ); + } catch { + return ['en'] + } + }, + + numberToHumanSize: (value) => { + /** @type {[number, string][]} */ + const unitMap = [ + [1024 ** 4, 'terabyte'], + [1024 ** 3, 'gigabyte'], + [1024 ** 2, 'megabyte'], + [1024 ** 1, 'kilobyte'], + [0, 'byte'] + ]; + + const [size, unit] = unitMap.find(([size]) => value >= size) ?? [0, 'byte']; + + return new Intl.NumberFormat(QPixel.supportedNumberLocales(), { + notation: 'compact', + style: 'unit', + unit, + unitDisplay: 'narrow', + }).format(size ? value / size : value).toUpperCase(); + }, + offset: function (el) { const topLeft = $(el).offset(); return { @@ -99,7 +129,7 @@ window.QPixel = { }, /** - * @type {QPixelFilter[]|null} + * @type {RecordYou can create a new comment thread. Please check first whether an existing comment thread covers already what you need.
+ <% if post.comment_threads.any? %> +<%= t('comments.captions.new_thread_on_post') %>
<% else %> -Be the first to create a new comment thread.
+<%= t('comments.captions.first_thread_on_post') %>
<% end %> <%= form_tag create_comment_thread_path do %> <%= hidden_field_tag :post_id, post.id %> - <%= label_tag :body, 'Your comment', class: 'form-element' %> + <%= label_tag :body, t('comments.labels.comment_body_input'), class: 'form-element' %>
Extra fields -- your web site, GitHub profile, social-media usernames, whatever you want.
+ Only values that begin with "http" are rendered as links.- Only values that begin with "http" are rendered as links.
usernameorusername#1234. <%= f.text_field :discord, class: 'form-element', autocomplete: 'off', placeholder: 'username#1234' %>A quiet enigma. We don't know anything about <%= rtl_safe_username(@user) %> yet.
- <% elsif !user_signed_in? && !@user.community_user.privilege?('unrestricted') %> - <%= sanitize(effective_profile, attributes: %w()) %> - <% else %> - <%= effective_profile %> - <% end %> + <% effective_profile = raw(sanitize(@user.profile&.strip || '', scrubber: scrubber)) %> + <% if effective_profile.blank? %> +A quiet enigma. We don't know anything about <%= rtl_safe_username(@user) %> yet.
+ <% elsif !user_signed_in? && !@user.community_user.privilege?('unrestricted') %> + <%= sanitize(effective_profile, attributes: %w()) %> + <% else %> + <%= effective_profile %> + <% end %>Extra fields
-Extra fields
+(up minus down) +
+ (up minus down)