parent
3b55a5a9c7
commit
7038d6a844
@ -1,29 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
|
||||
const LAYOUT_BREAKPOINT = 630;
|
||||
|
||||
export function isMobile(width) {
|
||||
return width <= LAYOUT_BREAKPOINT;
|
||||
}
|
||||
|
||||
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||
|
||||
let userTouching = false;
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||
|
||||
function touchListener() {
|
||||
userTouching = true;
|
||||
window.removeEventListener('touchstart', touchListener, listenerOptions);
|
||||
}
|
||||
|
||||
window.addEventListener('touchstart', touchListener, listenerOptions);
|
||||
|
||||
export function isUserTouching() {
|
||||
return userTouching;
|
||||
}
|
||||
|
||||
export function isIOS() {
|
||||
return iOS;
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
|
||||
/** Breakpoint at which the application is considered "mobile". */
|
||||
const LAYOUT_BREAKPOINT = 630;
|
||||
|
||||
/** Check if the width is small enough to be considered "mobile". */
|
||||
export function isMobile(width: number) {
|
||||
return width <= LAYOUT_BREAKPOINT;
|
||||
}
|
||||
|
||||
/** Whether the device is iOS (best guess). */
|
||||
const iOS: boolean = /iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream;
|
||||
|
||||
let userTouching = false;
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true } as EventListenerOptions : false;
|
||||
|
||||
function touchListener(): void {
|
||||
userTouching = true;
|
||||
window.removeEventListener('touchstart', touchListener, listenerOptions);
|
||||
}
|
||||
|
||||
window.addEventListener('touchstart', touchListener, listenerOptions);
|
||||
|
||||
/** Whether the user has touched the screen since the page loaded. */
|
||||
export function isUserTouching(): boolean {
|
||||
return userTouching;
|
||||
}
|
||||
|
||||
/** Whether the device is iOS (best guess). */
|
||||
export function isIOS(): boolean {
|
||||
return iOS;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { processHtml } from './tiny_post_html_processor';
|
||||
|
||||
export const addGreentext = html => {
|
||||
export const addGreentext = (html: string): string => {
|
||||
// Copied from Pleroma FE
|
||||
// https://git.pleroma.social/pleroma/pleroma-fe/-/blob/19475ba356c3fd6c54ca0306d3ae392358c212d1/src/components/status_content/status_content.js#L132
|
||||
return processHtml(html, (string) => {
|
@ -1,16 +1,20 @@
|
||||
/** Convert HTML to a plaintext representation, preserving whitespace. */
|
||||
// NB: This function can still return unsafe HTML
|
||||
export const unescapeHTML = (html) => {
|
||||
export const unescapeHTML = (html: string): string => {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = html.replace(/<br\s*\/?>/g, '\n').replace(/<\/p><[^>]*>/g, '\n\n').replace(/<[^>]*>/g, '');
|
||||
return wrapper.textContent;
|
||||
return wrapper.textContent || '';
|
||||
};
|
||||
|
||||
export const stripCompatibilityFeatures = html => {
|
||||
/** Remove compatibility markup for features Soapbox supports. */
|
||||
export const stripCompatibilityFeatures = (html: string): string => {
|
||||
const node = document.createElement('div');
|
||||
node.innerHTML = html;
|
||||
|
||||
const selectors = [
|
||||
// Quote posting
|
||||
'.quote-inline',
|
||||
// Explicit mentions
|
||||
'.recipients-inline',
|
||||
];
|
||||
|
@ -1,16 +0,0 @@
|
||||
import React from 'react';
|
||||
import { FormattedNumber } from 'react-intl';
|
||||
|
||||
export const isNumber = number => typeof number === 'number' && !isNaN(number);
|
||||
|
||||
export const shortNumberFormat = number => {
|
||||
if (!isNumber(number)) return '•';
|
||||
|
||||
if (number < 1000) {
|
||||
return <FormattedNumber value={number} />;
|
||||
} else {
|
||||
return <span><FormattedNumber value={number / 1000} maximumFractionDigits={1} />K</span>;
|
||||
}
|
||||
};
|
||||
|
||||
export const isIntegerId = id => new RegExp(/^-?[0-9]+$/g).test(id);
|
@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { FormattedNumber } from 'react-intl';
|
||||
|
||||
/** Check if a value is REALLY a number. */
|
||||
export const isNumber = (number: unknown): boolean => typeof number === 'number' && !isNaN(number);
|
||||
|
||||
/** Display a number nicely for the UI, eg 1000 becomes 1K. */
|
||||
export const shortNumberFormat = (number: any): React.ReactNode => {
|
||||
if (!isNumber(number)) return '•';
|
||||
|
||||
if (number < 1000) {
|
||||
return <FormattedNumber value={number} />;
|
||||
} else {
|
||||
return <span><FormattedNumber value={number / 1000} maximumFractionDigits={1} />K</span>;
|
||||
}
|
||||
};
|
||||
|
||||
/** Check if an entity ID is an integer (eg not a FlakeId). */
|
||||
export const isIntegerId = (id: string): boolean => new RegExp(/^-?[0-9]+$/g).test(id);
|
@ -1,17 +0,0 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { parseVersion, PLEROMA, MITRA } from './features';
|
||||
|
||||
// For solving bugs between API implementations
|
||||
export const getQuirks = createSelector([
|
||||
instance => parseVersion(instance.get('version')),
|
||||
], (v) => {
|
||||
return {
|
||||
invertedPagination: v.software === PLEROMA,
|
||||
noApps: v.software === MITRA,
|
||||
noOAuthForm: v.software === MITRA,
|
||||
};
|
||||
});
|
||||
|
||||
export const getNextLinkName = getState =>
|
||||
getQuirks(getState().get('instance')).invertedPagination ? 'prev' : 'next';
|
@ -0,0 +1,38 @@
|
||||
/* eslint sort-keys: "error" */
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { parseVersion, PLEROMA, MITRA } from './features';
|
||||
|
||||
import type { RootState } from 'soapbox/store';
|
||||
import type { Instance } from 'soapbox/types/entities';
|
||||
|
||||
/** For solving bugs between API implementations. */
|
||||
export const getQuirks = createSelector([
|
||||
(instance: Instance) => parseVersion(instance.version),
|
||||
], (v) => {
|
||||
return {
|
||||
/**
|
||||
* The `next` and `prev` Link headers are backwards for blocks and mutes.
|
||||
* @see GET /api/v1/blocks
|
||||
* @see GET /api/v1/mutes
|
||||
*/
|
||||
invertedPagination: v.software === PLEROMA,
|
||||
|
||||
/**
|
||||
* Apps are not supported by the API, and should not be created during login or registration.
|
||||
* @see POST /api/v1/apps
|
||||
* @see POST /oauth/token
|
||||
*/
|
||||
noApps: v.software === MITRA,
|
||||
|
||||
/**
|
||||
* There is no OAuth form available for login.
|
||||
* @see GET /oauth/authorize
|
||||
*/
|
||||
noOAuthForm: v.software === MITRA,
|
||||
};
|
||||
});
|
||||
|
||||
/** Shortcut for inverted pagination quirk. */
|
||||
export const getNextLinkName = (getState: () => RootState) =>
|
||||
getQuirks(getState().instance).invertedPagination ? 'prev' : 'next';
|
@ -1,15 +1,15 @@
|
||||
// Returns `true` if the node contains only emojis, up to a limit
|
||||
export const onlyEmoji = (node, limit = 1, ignoreMentions = true) => {
|
||||
/** Returns `true` if the node contains only emojis, up to a limit */
|
||||
export const onlyEmoji = (node: HTMLElement, limit = 1, ignoreMentions = true): boolean => {
|
||||
if (!node) return false;
|
||||
|
||||
try {
|
||||
// Remove mentions before checking content
|
||||
if (ignoreMentions) {
|
||||
node = node.cloneNode(true);
|
||||
node.querySelectorAll('a.mention').forEach(m => m.parentNode.removeChild(m));
|
||||
node = node.cloneNode(true) as HTMLElement;
|
||||
node.querySelectorAll('a.mention').forEach(m => m.parentNode?.removeChild(m));
|
||||
}
|
||||
|
||||
if (node.textContent.replace(new RegExp(' ', 'g'), '') !== '') return false;
|
||||
if (node.textContent?.replace(new RegExp(' ', 'g'), '') !== '') return false;
|
||||
const emojis = Array.from(node.querySelectorAll('img.emojione'));
|
||||
if (emojis.length === 0) return false;
|
||||
if (emojis.length > limit) return false;
|
@ -1,42 +0,0 @@
|
||||
/**
|
||||
* State: general Redux state utility functions.
|
||||
* @module soapbox/utils/state
|
||||
*/
|
||||
|
||||
import { getSoapboxConfig } from'soapbox/actions/soapbox';
|
||||
import { BACKEND_URL } from 'soapbox/build_config';
|
||||
import { isPrerendered } from 'soapbox/precheck';
|
||||
import { getBaseURL as getAccountBaseURL } from 'soapbox/utils/accounts';
|
||||
import { isURL } from 'soapbox/utils/auth';
|
||||
|
||||
export const displayFqn = state => {
|
||||
const soapbox = getSoapboxConfig(state);
|
||||
return soapbox.get('displayFqn');
|
||||
};
|
||||
|
||||
export const federationRestrictionsDisclosed = state => {
|
||||
return state.hasIn(['instance', 'pleroma', 'metadata', 'federation', 'mrf_policies']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine whether Soapbox FE is running in standalone mode.
|
||||
* Standalone mode runs separately from any backend and can login anywhere.
|
||||
* @param {object} state
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isStandalone = state => {
|
||||
const instanceFetchFailed = state.getIn(['meta', 'instance_fetch_failed'], false);
|
||||
return isURL(BACKEND_URL) ? false : (!isPrerendered && instanceFetchFailed);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the baseURL of the instance.
|
||||
* @param {object} state
|
||||
* @returns {string} url
|
||||
*/
|
||||
export const getBaseURL = state => {
|
||||
const me = state.get('me');
|
||||
const account = state.getIn(['accounts', me]);
|
||||
|
||||
return isURL(BACKEND_URL) ? BACKEND_URL : getAccountBaseURL(account);
|
||||
};
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* State: general Redux state utility functions.
|
||||
* @module soapbox/utils/state
|
||||
*/
|
||||
|
||||
import { getSoapboxConfig } from'soapbox/actions/soapbox';
|
||||
import * as BuildConfig from 'soapbox/build_config';
|
||||
import { isPrerendered } from 'soapbox/precheck';
|
||||
import { isURL } from 'soapbox/utils/auth';
|
||||
|
||||
import type { RootState } from 'soapbox/store';
|
||||
|
||||
/** Whether to display the fqn instead of the acct. */
|
||||
export const displayFqn = (state: RootState): boolean => {
|
||||
return getSoapboxConfig(state).displayFqn;
|
||||
};
|
||||
|
||||
/** Whether the instance exposes instance blocks through the API. */
|
||||
export const federationRestrictionsDisclosed = (state: RootState): boolean => {
|
||||
return state.instance.pleroma.hasIn(['metadata', 'federation', 'mrf_policies']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine whether Soapbox FE is running in standalone mode.
|
||||
* Standalone mode runs separately from any backend and can login anywhere.
|
||||
*/
|
||||
export const isStandalone = (state: RootState): boolean => {
|
||||
const instanceFetchFailed = state.meta.instance_fetch_failed;
|
||||
return isURL(BuildConfig.BACKEND_URL) ? false : (!isPrerendered && instanceFetchFailed);
|
||||
};
|
||||
|
||||
const getHost = (url: any): string => {
|
||||
try {
|
||||
return new URL(url).origin;
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
/** Get the baseURL of the instance. */
|
||||
export const getBaseURL = (state: RootState): string => {
|
||||
const account = state.accounts.get(state.me);
|
||||
return isURL(BuildConfig.BACKEND_URL) ? BuildConfig.BACKEND_URL : getHost(account?.url);
|
||||
};
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Static: functions related to static files.
|
||||
* @module soapbox/utils/static
|
||||
*/
|
||||
|
||||
import { join } from 'path';
|
||||
|
||||
import { FE_SUBDIRECTORY } from 'soapbox/build_config';
|
||||
|
||||
export const joinPublicPath = (...paths) => {
|
||||
return join(FE_SUBDIRECTORY, ...paths);
|
||||
};
|
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Static: functions related to static files.
|
||||
* @module soapbox/utils/static
|
||||
*/
|
||||
|
||||
import { join } from 'path';
|
||||
|
||||
import * as BuildConfig from 'soapbox/build_config';
|
||||
|
||||
/** Gets the path to a file with build configuration being considered. */
|
||||
export const joinPublicPath = (...paths: string[]): string => {
|
||||
return join(BuildConfig.FE_SUBDIRECTORY, ...paths);
|
||||
};
|
Loading…
Reference in new issue