about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/util
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch/util')
-rw-r--r--app/javascript/flavours/glitch/util/async-components.js16
-rw-r--r--app/javascript/flavours/glitch/util/backend_links.js3
-rw-r--r--app/javascript/flavours/glitch/util/compare_id.js5
-rw-r--r--app/javascript/flavours/glitch/util/emoji/emoji_mart_search_light.js2
-rw-r--r--app/javascript/flavours/glitch/util/emoji/index.js11
-rw-r--r--app/javascript/flavours/glitch/util/idna.js10
-rw-r--r--app/javascript/flavours/glitch/util/initial_state.js5
-rw-r--r--app/javascript/flavours/glitch/util/load_keyboard_extensions.js16
-rw-r--r--app/javascript/flavours/glitch/util/log_out.js34
-rw-r--r--app/javascript/flavours/glitch/util/numbers.js4
-rw-r--r--app/javascript/flavours/glitch/util/resize_image.js8
-rw-r--r--app/javascript/flavours/glitch/util/rtl.js1
-rw-r--r--app/javascript/flavours/glitch/util/scroll.js6
-rw-r--r--app/javascript/flavours/glitch/util/scrollbar.js34
-rw-r--r--app/javascript/flavours/glitch/util/stream.js2
15 files changed, 146 insertions, 11 deletions
diff --git a/app/javascript/flavours/glitch/util/async-components.js b/app/javascript/flavours/glitch/util/async-components.js
index f2aeda834..26255bbb7 100644
--- a/app/javascript/flavours/glitch/util/async-components.js
+++ b/app/javascript/flavours/glitch/util/async-components.js
@@ -122,6 +122,10 @@ export function MuteModal () {
   return import(/* webpackChunkName: "flavours/glitch/async/mute_modal" */'flavours/glitch/features/ui/components/mute_modal');
 }
 
+export function BlockModal () {
+  return import(/* webpackChunkName: "flavours/glitch/async/block_modal" */'flavours/glitch/features/ui/components/block_modal');
+}
+
 export function ReportModal () {
   return import(/* webpackChunkName: "flavours/glitch/async/report_modal" */'flavours/glitch/features/ui/components/report_modal');
 }
@@ -138,6 +142,10 @@ export function Video () {
   return import(/* webpackChunkName: "flavours/glitch/async/video" */'flavours/glitch/features/video');
 }
 
+export function Audio () {
+  return import(/* webpackChunkName: "features/glitch/async/audio" */'flavours/glitch/features/audio');
+}
+
 export function EmbedModal () {
   return import(/* webpackChunkName: "flavours/glitch/async/embed_modal" */'flavours/glitch/features/ui/components/embed_modal');
 }
@@ -153,3 +161,11 @@ export function ListAdder () {
 export function Search () {
   return import(/*webpackChunkName: "features/glitch/async/search" */'flavours/glitch/features/search');
 }
+
+export function Tesseract () {
+  return import(/*webpackChunkName: "tesseract" */'tesseract.js');
+}
+
+export function Directory () {
+  return import(/* webpackChunkName: "features/glitch/async/directory" */'flavours/glitch/features/directory');
+}
diff --git a/app/javascript/flavours/glitch/util/backend_links.js b/app/javascript/flavours/glitch/util/backend_links.js
index 4fc03f919..0fb378cc1 100644
--- a/app/javascript/flavours/glitch/util/backend_links.js
+++ b/app/javascript/flavours/glitch/util/backend_links.js
@@ -4,3 +4,6 @@ export const signOutLink = '/auth/sign_out';
 export const termsLink = '/terms';
 export const accountAdminLink = (id) => `/admin/accounts/${id}`;
 export const statusAdminLink = (account_id, status_id) => `/admin/accounts/${account_id}/statuses/${status_id}`;
+export const filterEditLink = (id) => `/filters/${id}/edit`;
+export const relationshipsLink = '/relationships';
+export const securityLink = '/auth/edit';
diff --git a/app/javascript/flavours/glitch/util/compare_id.js b/app/javascript/flavours/glitch/util/compare_id.js
index aaff66481..66cf51c4b 100644
--- a/app/javascript/flavours/glitch/util/compare_id.js
+++ b/app/javascript/flavours/glitch/util/compare_id.js
@@ -1,10 +1,11 @@
-export default function compareId(id1, id2) {
+export default function compareId (id1, id2) {
   if (id1 === id2) {
     return 0;
   }
+
   if (id1.length === id2.length) {
     return id1 > id2 ? 1 : -1;
   } else {
     return id1.length > id2.length ? 1 : -1;
   }
-}
+};
diff --git a/app/javascript/flavours/glitch/util/emoji/emoji_mart_search_light.js b/app/javascript/flavours/glitch/util/emoji/emoji_mart_search_light.js
index 164fdcc0b..e4519a13e 100644
--- a/app/javascript/flavours/glitch/util/emoji/emoji_mart_search_light.js
+++ b/app/javascript/flavours/glitch/util/emoji/emoji_mart_search_light.js
@@ -74,7 +74,7 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo
       return [emojisList['-1']];
     }
 
-    let values = value.toLowerCase().split(/[\s|,|\-|_]+/),
+    let values = value.toLowerCase().split(/[\s|,\-_]+/),
       allResults = [];
 
     if (values.length > 2) {
diff --git a/app/javascript/flavours/glitch/util/emoji/index.js b/app/javascript/flavours/glitch/util/emoji/index.js
index 82a1ef89c..e1a244127 100644
--- a/app/javascript/flavours/glitch/util/emoji/index.js
+++ b/app/javascript/flavours/glitch/util/emoji/index.js
@@ -1,4 +1,4 @@
-import { autoPlayGif } from 'flavours/glitch/util/initial_state';
+import { autoPlayGif, useSystemEmojiFont } from 'flavours/glitch/util/initial_state';
 import unicodeMapping from './emoji_unicode_mapping_light';
 import Trie from 'substring-trie';
 
@@ -12,7 +12,7 @@ const emojify = (str, customEmojis = {}) => {
   let rtn = '', tagChars = tagCharsWithEmojis, invisible = 0;
   for (;;) {
     let match, i = 0, tag;
-    while (i < str.length && (tag = tagChars.indexOf(str[i])) === -1 && (invisible || !(match = trie.search(str.slice(i))))) {
+    while (i < str.length && (tag = tagChars.indexOf(str[i])) === -1 && (invisible || useSystemEmojiFont || !(match = trie.search(str.slice(i))))) {
       i += str.codePointAt(i) < 65536 ? 1 : 2;
     }
     let rend, replacement = '';
@@ -29,7 +29,7 @@ const emojify = (str, customEmojis = {}) => {
         // if you want additional emoji handler, add statements below which set replacement and return true.
         if (shortname in customEmojis) {
           const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
-          replacement = `<img draggable="false" class="emojione" alt="${shortname}" title="${shortname}" src="${filename}" />`;
+          replacement = `<img draggable="false" class="emojione custom-emoji" alt="${shortname}" title="${shortname}" src="${filename}" data-original="${customEmojis[shortname].url}" data-static="${customEmojis[shortname].static_url}" />`;
           return true;
         }
         return false;
@@ -57,7 +57,7 @@ const emojify = (str, customEmojis = {}) => {
         }
       }
       i = rend;
-    } else { // matched to unicode emoji
+    } else if (!useSystemEmojiFont) { // matched to unicode emoji
       const { filename, shortCode } = unicodeMapping[match];
       const title = shortCode ? `:${shortCode}:` : '';
       replacement = `<img draggable="false" class="emojione" alt="${match}" title="${title}" src="${assetHost}/emoji/${filename}.svg" />`;
@@ -93,8 +93,11 @@ export const buildCustomEmojis = (customEmojis) => {
       keywords: [name],
       imageUrl: url,
       custom: true,
+      customCategory: emoji.get('category'),
     });
   });
 
   return emojis;
 };
+
+export const categoriesFromEmojis = customEmojis => customEmojis.reduce((set, emoji) => set.add(emoji.get('category') ? `custom-${emoji.get('category')}` : 'custom'), new Set(['custom']));
diff --git a/app/javascript/flavours/glitch/util/idna.js b/app/javascript/flavours/glitch/util/idna.js
new file mode 100644
index 000000000..efab5bacf
--- /dev/null
+++ b/app/javascript/flavours/glitch/util/idna.js
@@ -0,0 +1,10 @@
+import punycode from 'punycode';
+
+const IDNA_PREFIX = 'xn--';
+
+export const decode = domain => {
+  return domain
+    .split('.')
+    .map(part => part.indexOf(IDNA_PREFIX) === 0 ? punycode.decode(part.slice(IDNA_PREFIX.length)) : part)
+    .join('.');
+};
diff --git a/app/javascript/flavours/glitch/util/initial_state.js b/app/javascript/flavours/glitch/util/initial_state.js
index f42c06a3a..911468e6f 100644
--- a/app/javascript/flavours/glitch/util/initial_state.js
+++ b/app/javascript/flavours/glitch/util/initial_state.js
@@ -26,8 +26,13 @@ export const pollLimits = (initialState && initialState.poll_limits);
 export const invitesEnabled = getMeta('invites_enabled');
 export const version = getMeta('version');
 export const mascot = getMeta('mascot');
+export const profile_directory = getMeta('profile_directory');
 export const isStaff = getMeta('is_staff');
 export const defaultContentType = getMeta('default_content_type');
 export const forceSingleColumn = getMeta('advanced_layout') === false;
+export const useBlurhash = getMeta('use_blurhash');
+export const usePendingItems = getMeta('use_pending_items');
+export const useSystemEmojiFont = getMeta('system_emoji_font');
+export const showTrends = getMeta('trends');
 
 export default initialState;
diff --git a/app/javascript/flavours/glitch/util/load_keyboard_extensions.js b/app/javascript/flavours/glitch/util/load_keyboard_extensions.js
new file mode 100644
index 000000000..2dd0e45fa
--- /dev/null
+++ b/app/javascript/flavours/glitch/util/load_keyboard_extensions.js
@@ -0,0 +1,16 @@
+// On KaiOS, we may not be able to use a mouse cursor or navigate using Tab-based focus, so we install
+// special left/right focus navigation keyboard listeners, at least on public pages (i.e. so folks
+// can at least log in using KaiOS devices).
+
+function importArrowKeyNavigation() {
+  return import(/* webpackChunkName: "arrow-key-navigation" */ 'arrow-key-navigation');
+}
+
+export default function loadKeyboardExtensions() {
+  if (/KAIOS/.test(navigator.userAgent)) {
+    return importArrowKeyNavigation().then(arrowKeyNav => {
+      arrowKeyNav.register();
+    });
+  }
+  return Promise.resolve();
+}
diff --git a/app/javascript/flavours/glitch/util/log_out.js b/app/javascript/flavours/glitch/util/log_out.js
new file mode 100644
index 000000000..8e1659293
--- /dev/null
+++ b/app/javascript/flavours/glitch/util/log_out.js
@@ -0,0 +1,34 @@
+import Rails from 'rails-ujs';
+import { signOutLink } from 'flavours/glitch/util/backend_links';
+
+export const logOut = () => {
+  const form = document.createElement('form');
+
+  const methodInput = document.createElement('input');
+  methodInput.setAttribute('name', '_method');
+  methodInput.setAttribute('value', 'delete');
+  methodInput.setAttribute('type', 'hidden');
+  form.appendChild(methodInput);
+
+  const csrfToken = Rails.csrfToken();
+  const csrfParam = Rails.csrfParam();
+
+  if (csrfParam && csrfToken) {
+    const csrfInput = document.createElement('input');
+    csrfInput.setAttribute('name', csrfParam);
+    csrfInput.setAttribute('value', csrfToken);
+    csrfInput.setAttribute('type', 'hidden');
+    form.appendChild(csrfInput);
+  }
+
+  const submitButton = document.createElement('input');
+  submitButton.setAttribute('type', 'submit');
+  form.appendChild(submitButton);
+
+  form.method = 'post';
+  form.action = signOutLink;
+  form.style.display = 'none';
+
+  document.body.appendChild(form);
+  submitButton.click();
+};
diff --git a/app/javascript/flavours/glitch/util/numbers.js b/app/javascript/flavours/glitch/util/numbers.js
index fdd8269ae..f7e4ceb93 100644
--- a/app/javascript/flavours/glitch/util/numbers.js
+++ b/app/javascript/flavours/glitch/util/numbers.js
@@ -4,7 +4,9 @@ import { FormattedNumber } from 'react-intl';
 export const shortNumberFormat = number => {
   if (number < 1000) {
     return <FormattedNumber value={number} />;
-  } else {
+  } else if (number < 1000000) {
     return <Fragment><FormattedNumber value={number / 1000} maximumFractionDigits={1} />K</Fragment>;
+  } else {
+    return <Fragment><FormattedNumber value={number / 1000000} maximumFractionDigits={1} />M</Fragment>;
   }
 };
diff --git a/app/javascript/flavours/glitch/util/resize_image.js b/app/javascript/flavours/glitch/util/resize_image.js
index bbdbc865e..d566edb03 100644
--- a/app/javascript/flavours/glitch/util/resize_image.js
+++ b/app/javascript/flavours/glitch/util/resize_image.js
@@ -67,6 +67,14 @@ const processImage = (img, { width, height, orientation, type = 'image/png' }) =
 
   context.drawImage(img, 0, 0, width, height);
 
+  // The Tor Browser and maybe other browsers may prevent reading from canvas
+  // and return an all-white image instead. Assume reading failed if the resized
+  // image is perfectly white.
+  const imageData = context.getImageData(0, 0, width, height);
+  if (imageData.data.every(value => value === 255)) {
+    throw 'Failed to read from canvas';
+  }
+
   canvas.toBlob(resolve, type);
 });
 
diff --git a/app/javascript/flavours/glitch/util/rtl.js b/app/javascript/flavours/glitch/util/rtl.js
index 00870a15d..89bed6de8 100644
--- a/app/javascript/flavours/glitch/util/rtl.js
+++ b/app/javascript/flavours/glitch/util/rtl.js
@@ -20,6 +20,7 @@ export function isRtl(text) {
   text = text.replace(/(?:^|[^\/\w])@([a-z0-9_]+(@[a-z0-9\.\-]+)?)/ig, '');
   text = text.replace(/(?:^|[^\/\w])#([\S]+)/ig, '');
   text = text.replace(/\s+/g, '');
+  text = text.replace(/(\w\S+\.\w{2,}\S*)/g, '');
 
   const matches = text.match(rtlChars);
 
diff --git a/app/javascript/flavours/glitch/util/scroll.js b/app/javascript/flavours/glitch/util/scroll.js
index 2af07e0fb..84fe58269 100644
--- a/app/javascript/flavours/glitch/util/scroll.js
+++ b/app/javascript/flavours/glitch/util/scroll.js
@@ -26,5 +26,7 @@ const scroll = (node, key, target) => {
   };
 };
 
-export const scrollRight = (node, position) => scroll(node, 'scrollLeft', position);
-export const scrollTop = (node) => scroll(node, 'scrollTop', 0);
+const isScrollBehaviorSupported = 'scrollBehavior' in document.documentElement.style;
+
+export const scrollRight = (node, position) => isScrollBehaviorSupported ? node.scrollTo({ left: position, behavior: 'smooth' }) : scroll(node, 'scrollLeft', position);
+export const scrollTop = (node) => isScrollBehaviorSupported ? node.scrollTo({ top: 0, behavior: 'smooth' }) : scroll(node, 'scrollTop', 0);
diff --git a/app/javascript/flavours/glitch/util/scrollbar.js b/app/javascript/flavours/glitch/util/scrollbar.js
new file mode 100644
index 000000000..929b036d6
--- /dev/null
+++ b/app/javascript/flavours/glitch/util/scrollbar.js
@@ -0,0 +1,34 @@
+/** @type {number | null} */
+let cachedScrollbarWidth = null;
+
+/**
+ * @return {number}
+ */
+const getActualScrollbarWidth = () => {
+  const outer = document.createElement('div');
+  outer.style.visibility = 'hidden';
+  outer.style.overflow = 'scroll';
+  document.body.appendChild(outer);
+
+  const inner = document.createElement('div');
+  outer.appendChild(inner);
+
+  const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
+  outer.parentNode.removeChild(outer);
+
+  return scrollbarWidth;
+};
+
+/**
+ * @return {number}
+ */
+export const getScrollbarWidth = () => {
+  if (cachedScrollbarWidth !== null) {
+    return cachedScrollbarWidth;
+  }
+
+  const scrollbarWidth = getActualScrollbarWidth();
+  cachedScrollbarWidth = scrollbarWidth;
+
+  return scrollbarWidth;
+};
diff --git a/app/javascript/flavours/glitch/util/stream.js b/app/javascript/flavours/glitch/util/stream.js
index c4642344f..50f90d44c 100644
--- a/app/javascript/flavours/glitch/util/stream.js
+++ b/app/javascript/flavours/glitch/util/stream.js
@@ -1,4 +1,4 @@
-import WebSocketClient from 'websocket.js';
+import WebSocketClient from '@gamestdio/websocket';
 
 const randomIntUpTo = max => Math.floor(Math.random() * Math.floor(max));