chore: Move to Typescript (#2783)
This PR moves the entire project to Typescript. Due to the ~1000 ignores this will lead to a messy codebase for a while, but the churn is worth it – all of those ignore comments are places that were never type-safe previously. closes #1282
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
// @flow
|
||||
import retry from "fetch-retry";
|
||||
import invariant from "invariant";
|
||||
import { map, trim } from "lodash";
|
||||
import { getCookie } from "tiny-cookie";
|
||||
import stores from "stores";
|
||||
import stores from "~/stores";
|
||||
import download from "./download";
|
||||
import {
|
||||
AuthorizationError,
|
||||
@@ -17,18 +16,18 @@ import {
|
||||
} from "./errors";
|
||||
|
||||
type Options = {
|
||||
baseUrl?: string,
|
||||
baseUrl?: string;
|
||||
};
|
||||
|
||||
// authorization cookie set by a Cloudflare Access proxy
|
||||
const CF_AUTHORIZATION = getCookie("CF_Authorization");
|
||||
|
||||
// if the cookie is set, we must pass it with all ApiClient requests
|
||||
const CREDENTIALS = CF_AUTHORIZATION ? "same-origin" : "omit";
|
||||
|
||||
const fetchWithRetry = retry(fetch);
|
||||
|
||||
class ApiClient {
|
||||
baseUrl: string;
|
||||
|
||||
userAgent: string;
|
||||
|
||||
constructor(options: Options = {}) {
|
||||
@@ -39,8 +38,8 @@ class ApiClient {
|
||||
fetch = async (
|
||||
path: string,
|
||||
method: string,
|
||||
data: ?Object | FormData | void,
|
||||
options: Object = {}
|
||||
data: (Record<string, any> | undefined) | FormData,
|
||||
options: Record<string, any> = {}
|
||||
) => {
|
||||
let body;
|
||||
let modifiedPath;
|
||||
@@ -74,18 +73,21 @@ class ApiClient {
|
||||
urlToFetch = this.baseUrl + (modifiedPath || path);
|
||||
}
|
||||
|
||||
let headerOptions: any = {
|
||||
const headerOptions: any = {
|
||||
Accept: "application/json",
|
||||
"cache-control": "no-cache",
|
||||
// @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'EDITOR_VERSION'.
|
||||
"x-editor-version": EDITOR_VERSION,
|
||||
pragma: "no-cache",
|
||||
};
|
||||
|
||||
// for multipart forms or other non JSON requests fetch
|
||||
// populates the Content-Type without needing to explicitly
|
||||
// set it.
|
||||
if (isJson) {
|
||||
headerOptions["Content-Type"] = "application/json";
|
||||
}
|
||||
|
||||
const headers = new Headers(headerOptions);
|
||||
|
||||
if (stores.auth.authenticated) {
|
||||
@@ -94,9 +96,11 @@ class ApiClient {
|
||||
}
|
||||
|
||||
let response;
|
||||
|
||||
try {
|
||||
response = await fetchWithRetry(urlToFetch, {
|
||||
method,
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type 'string | Record<string, any> | undefined' is... Remove this comment to see the full error message
|
||||
body,
|
||||
headers,
|
||||
redirect: "follow",
|
||||
@@ -118,7 +122,6 @@ class ApiClient {
|
||||
const fileName = (
|
||||
response.headers.get("content-disposition") || ""
|
||||
).split("filename=")[1];
|
||||
|
||||
download(blob, trim(fileName, '"'));
|
||||
return;
|
||||
} else if (success && response.status === 204) {
|
||||
@@ -135,56 +138,78 @@ class ApiClient {
|
||||
|
||||
// Handle failed responses
|
||||
const error = {};
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'statusCode' does not exist on type '{}'.
|
||||
error.statusCode = response.status;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'response' does not exist on type '{}'.
|
||||
error.response = response;
|
||||
|
||||
try {
|
||||
const parsed = await response.json();
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'message' does not exist on type '{}'.
|
||||
error.message = parsed.message || "";
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'error' does not exist on type '{}'.
|
||||
error.error = parsed.error;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'data' does not exist on type '{}'.
|
||||
error.data = parsed.data;
|
||||
} catch (_err) {
|
||||
// we're trying to parse an error so JSON may not be valid
|
||||
}
|
||||
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'error' does not exist on type '{}'.
|
||||
if (response.status === 400 && error.error === "editor_update_required") {
|
||||
window.location.reload(true);
|
||||
window.location.reload();
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'message' does not exist on type '{}'.
|
||||
throw new UpdateRequiredError(error.message);
|
||||
}
|
||||
|
||||
if (response.status === 400) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'message' does not exist on type '{}'.
|
||||
throw new BadRequestError(error.message);
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'error' does not exist on type '{}'.
|
||||
if (error.error === "user_suspended") {
|
||||
stores.auth.logout();
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'message' does not exist on type '{}'.
|
||||
throw new AuthorizationError(error.message);
|
||||
}
|
||||
|
||||
if (response.status === 404) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'message' does not exist on type '{}'.
|
||||
throw new NotFoundError(error.message);
|
||||
}
|
||||
|
||||
if (response.status === 503) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'message' does not exist on type '{}'.
|
||||
throw new ServiceUnavailableError(error.message);
|
||||
}
|
||||
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'message' does not exist on type '{}'.
|
||||
throw new RequestError(error.message);
|
||||
};
|
||||
|
||||
get = (path: string, data: ?Object, options?: Object) => {
|
||||
get = (
|
||||
path: string,
|
||||
data: Record<string, any> | undefined,
|
||||
options?: Record<string, any>
|
||||
) => {
|
||||
return this.fetch(path, "GET", data, options);
|
||||
};
|
||||
|
||||
post = (path: string, data: ?Object, options?: Object) => {
|
||||
post = (
|
||||
path: string,
|
||||
data?: Record<string, any> | undefined,
|
||||
options?: Record<string, any>
|
||||
) => {
|
||||
return this.fetch(path, "POST", data, options);
|
||||
};
|
||||
|
||||
// Helpers
|
||||
constructQueryString = (data: { [key: string]: string }) => {
|
||||
constructQueryString = (data: Record<string, any>) => {
|
||||
return map(
|
||||
data,
|
||||
(v, k) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`
|
||||
@@ -192,7 +217,6 @@ class ApiClient {
|
||||
};
|
||||
}
|
||||
|
||||
export default ApiClient;
|
||||
export default ApiClient; // In case you don't want to always initiate, just import with `import { client } ...`
|
||||
|
||||
// In case you don't want to always initiate, just import with `import { client } ...`
|
||||
export const client = new ApiClient();
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
client: {
|
||||
post: jest.fn(() => Promise.resolve),
|
||||
},
|
||||
};
|
||||
post: jest.fn(() => Promise.resolve)
|
||||
}
|
||||
};
|
||||
@@ -1,17 +1,15 @@
|
||||
// @flow
|
||||
import Compressor from "compressorjs";
|
||||
|
||||
type Options = { maxWidth?: number, maxHeight?: number };
|
||||
type Options = {
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
};
|
||||
|
||||
export const compressImage = async (
|
||||
file: File | Blob,
|
||||
options?: Options
|
||||
): Promise<Blob> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
new Compressor(file, {
|
||||
...options,
|
||||
success: resolve,
|
||||
error: reject,
|
||||
});
|
||||
new Compressor(file, { ...options, success: resolve, error: reject });
|
||||
});
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import {
|
||||
isToday,
|
||||
isYesterday,
|
||||
@@ -7,13 +6,13 @@ import {
|
||||
differenceInCalendarYears,
|
||||
format as formatDate,
|
||||
} from "date-fns";
|
||||
import { type TFunction } from "react-i18next";
|
||||
import { dateLocale } from "utils/i18n";
|
||||
import { TFunction } from "react-i18next";
|
||||
import { dateLocale } from "~/utils/i18n";
|
||||
|
||||
export function dateToHeading(
|
||||
dateTime: string,
|
||||
t: TFunction,
|
||||
userLocale: ?string
|
||||
userLocale: string | null | undefined
|
||||
) {
|
||||
const date = Date.parse(dateTime);
|
||||
const now = new Date();
|
||||
@@ -31,8 +30,11 @@ export function dateToHeading(
|
||||
// of the week as a string. We use the LocaleTime component here to gain
|
||||
// async bundle loading of languages
|
||||
const weekDiff = differenceInCalendarWeeks(now, date);
|
||||
|
||||
if (weekDiff === 0) {
|
||||
return formatDate(Date.parse(dateTime), "iiii", { locale });
|
||||
return formatDate(Date.parse(dateTime), "iiii", {
|
||||
locale,
|
||||
});
|
||||
}
|
||||
|
||||
if (weekDiff === 1) {
|
||||
@@ -40,6 +42,7 @@ export function dateToHeading(
|
||||
}
|
||||
|
||||
const monthDiff = differenceInCalendarMonths(now, date);
|
||||
|
||||
if (monthDiff === 0) {
|
||||
return t("This month");
|
||||
}
|
||||
@@ -49,10 +52,13 @@ export function dateToHeading(
|
||||
}
|
||||
|
||||
const yearDiff = differenceInCalendarYears(now, date);
|
||||
|
||||
if (yearDiff === 0) {
|
||||
return t("This year");
|
||||
}
|
||||
|
||||
// If older than the current calendar year then just print the year e.g 2020
|
||||
return formatDate(Date.parse(dateTime), "y", { locale });
|
||||
return formatDate(Date.parse(dateTime), "y", {
|
||||
locale,
|
||||
});
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
// @flow
|
||||
|
||||
// A function to delete all IndexedDB databases
|
||||
export async function deleteAllDatabases() {
|
||||
const databases = await window.indexedDB.databases();
|
||||
|
||||
for (const database of databases) {
|
||||
if (database.name) {
|
||||
await window.indexedDB.deleteDatabase(database.name);
|
||||
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
import { parseDomain, stripSubdomain } from "shared/utils/domains";
|
||||
import env from "env";
|
||||
import { parseDomain, stripSubdomain } from "@shared/utils/domains";
|
||||
import env from "~/env";
|
||||
|
||||
export function getCookieDomain(domain: string) {
|
||||
return env.SUBDOMAINS_ENABLED ? stripSubdomain(domain) : domain;
|
||||
@@ -9,7 +8,6 @@ export function getCookieDomain(domain: string) {
|
||||
export function isCustomDomain() {
|
||||
const parsed = parseDomain(window.location.origin);
|
||||
const main = parseDomain(env.URL);
|
||||
|
||||
return (
|
||||
parsed && main && (main.domain !== parsed.domain || main.tld !== parsed.tld)
|
||||
);
|
||||
@@ -1,77 +1,96 @@
|
||||
// @flow
|
||||
|
||||
// download.js v3.0, by dandavis; 2008-2014. [CCBY2] see http://danml.com/download.html for tests/usage
|
||||
// v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime
|
||||
// v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs
|
||||
// v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support
|
||||
|
||||
// data can be a string, Blob, File, or dataURL
|
||||
|
||||
export default function download(
|
||||
data: Blob | string | File,
|
||||
strFileName: string,
|
||||
strMimeType?: string
|
||||
) {
|
||||
var self = window, // this script is only for browsers anyway...
|
||||
u = "application/octet-stream", // this default mime also triggers iframe downloads
|
||||
const self = window,
|
||||
// this script is only for browsers anyway...
|
||||
u = "application/octet-stream",
|
||||
// this default mime also triggers iframe downloads
|
||||
m = strMimeType || u,
|
||||
x = data,
|
||||
D = document,
|
||||
a = D.createElement("a"),
|
||||
z = function (a, o) {
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'a' implicitly has an 'any' type.
|
||||
z = function (a) {
|
||||
return String(a);
|
||||
},
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'MozBlob' does not exist on type 'Window ... Remove this comment to see the full error message
|
||||
B = self.Blob || self.MozBlob || self.WebKitBlob || z,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'WebKitBlobBuilder' does not exist on typ... Remove this comment to see the full error message
|
||||
BB = self.MSBlobBuilder || self.WebKitBlobBuilder || self.BlobBuilder,
|
||||
fn = strFileName || "download",
|
||||
// @ts-expect-error ts-migrate(1155) FIXME: 'const' declarations must be initialized.
|
||||
blob,
|
||||
// @ts-expect-error ts-migrate(1155) FIXME: 'const' declarations must be initialized.
|
||||
b,
|
||||
// @ts-expect-error ts-migrate(1155) FIXME: 'const' declarations must be initialized.
|
||||
fr;
|
||||
|
||||
if (String(this) === "true") {
|
||||
//reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
|
||||
// @ts-expect-error ts-migrate(2588) FIXME: Cannot assign to 'x' because it is a constant.
|
||||
x = [x, m];
|
||||
// @ts-expect-error ts-migrate(2588) FIXME: Cannot assign to 'm' because it is a constant.
|
||||
m = x[0];
|
||||
// @ts-expect-error ts-migrate(2588) FIXME: Cannot assign to 'x' because it is a constant.
|
||||
x = x[1];
|
||||
}
|
||||
|
||||
//go ahead and download dataURLs right away
|
||||
// go ahead and download dataURLs right away
|
||||
if (String(x).match(/^data:[\w+-]+\/[\w+-]+[,;]/)) {
|
||||
// $FlowIssue
|
||||
return navigator.msSaveBlob // IE10 can't do a[download], only Blobs:
|
||||
? // $FlowIssue
|
||||
navigator.msSaveBlob(d2b(x), fn)
|
||||
: saver(x); // everyone else can save dataURLs un-processed
|
||||
} //end if dataURL passed?
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||
return saver(x); // everyone else can save dataURLs un-processed
|
||||
}
|
||||
|
||||
//end if dataURL passed?
|
||||
try {
|
||||
blob = x instanceof B ? x : new B([x], { type: m });
|
||||
// @ts-expect-error ts-migrate(2588) FIXME: Cannot assign to 'blob' because it is a constant.
|
||||
blob =
|
||||
x instanceof B
|
||||
? x
|
||||
: new B([x], {
|
||||
type: m,
|
||||
});
|
||||
} catch (y) {
|
||||
if (BB) {
|
||||
// @ts-expect-error ts-migrate(2588) FIXME: Cannot assign to 'b' because it is a constant.
|
||||
b = new BB();
|
||||
b.append([x]);
|
||||
// @ts-expect-error ts-migrate(2588) FIXME: Cannot assign to 'blob' because it is a constant.
|
||||
blob = b.getBlob(m); // the blob
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'u' implicitly has an 'any' type.
|
||||
function d2b(u) {
|
||||
if (typeof u !== "string") {
|
||||
throw Error("Attempted to pass non-string to d2b");
|
||||
}
|
||||
var p = u.split(/[:;,]/),
|
||||
|
||||
const p = u.split(/[:;,]/),
|
||||
t = p[1],
|
||||
dec = p[2] === "base64" ? atob : decodeURIComponent,
|
||||
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
|
||||
bin = dec(p.pop()),
|
||||
mx = bin.length,
|
||||
i = 0,
|
||||
uia = new Uint8Array(mx);
|
||||
|
||||
// @ts-expect-error ts-migrate(2588) FIXME: Cannot assign to 'i' because it is a constant.
|
||||
for (i; i < mx; ++i) uia[i] = bin.charCodeAt(i);
|
||||
|
||||
return new B([uia], { type: t });
|
||||
return new B([uia], {
|
||||
type: t,
|
||||
});
|
||||
}
|
||||
|
||||
function saver(url, winMode) {
|
||||
function saver(url: string, winMode: boolean) {
|
||||
if (typeof url !== "string") {
|
||||
throw Error("Attempted to pass non-string url to saver");
|
||||
}
|
||||
@@ -84,6 +103,7 @@ export default function download(
|
||||
setTimeout(function () {
|
||||
a.click();
|
||||
D.body && D.body.removeChild(a);
|
||||
|
||||
if (winMode === true) {
|
||||
setTimeout(function () {
|
||||
self.URL.revokeObjectURL(a.href);
|
||||
@@ -94,8 +114,9 @@ export default function download(
|
||||
}
|
||||
|
||||
//do iframe dataURL download (old ch+FF):
|
||||
var f = D.createElement("iframe");
|
||||
const f = D.createElement("iframe");
|
||||
D.body && D.body.appendChild(f);
|
||||
|
||||
if (!winMode) {
|
||||
// force a mime that will download:
|
||||
url = "data:" + url.replace(/^data:([\w/\-+]+)/, u);
|
||||
@@ -105,13 +126,8 @@ export default function download(
|
||||
setTimeout(function () {
|
||||
D.body && D.body.removeChild(f);
|
||||
}, 333);
|
||||
}
|
||||
|
||||
// $FlowIssue
|
||||
if (navigator.msSaveBlob) {
|
||||
// IE10+ : (has Blob, but not a[download] or URL)
|
||||
// $FlowIssue
|
||||
return navigator.msSaveBlob(blob, fn);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (self.URL) {
|
||||
@@ -125,16 +141,21 @@ export default function download(
|
||||
typeof m === "string"
|
||||
) {
|
||||
try {
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||
return saver("data:" + m + ";base64," + self.btoa(blob));
|
||||
} catch (y) {
|
||||
// $FlowIssue
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||
return saver("data:" + m + "," + encodeURIComponent(blob));
|
||||
}
|
||||
}
|
||||
|
||||
// Blob but not URL:
|
||||
// @ts-expect-error ts-migrate(2588) FIXME: Cannot assign to 'fr' because it is a constant.
|
||||
fr = new FileReader();
|
||||
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'e' implicitly has an 'any' type.
|
||||
fr.onload = function (e) {
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||
saver(this.result);
|
||||
};
|
||||
|
||||
@@ -142,5 +163,6 @@ export default function download(
|
||||
fr.readAsDataURL(blob);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export function emojiToUrl(text: string) {
|
||||
return `data:image/svg+xml;data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>${text}</text></svg>`;
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
// @flow
|
||||
import ExtendableError from "es6-error";
|
||||
|
||||
export class AuthorizationError extends ExtendableError {}
|
||||
|
||||
export class BadRequestError extends ExtendableError {}
|
||||
|
||||
export class NetworkError extends ExtendableError {}
|
||||
|
||||
export class NotFoundError extends ExtendableError {}
|
||||
|
||||
export class OfflineError extends ExtendableError {}
|
||||
|
||||
export class ServiceUnavailableError extends ExtendableError {}
|
||||
|
||||
export class RequestError extends ExtendableError {}
|
||||
|
||||
export class UpdateRequiredError extends ExtendableError {}
|
||||
@@ -1,22 +0,0 @@
|
||||
// @flow
|
||||
export default function getDataTransferFiles(event: SyntheticEvent<>): File[] {
|
||||
let dataTransferItemsList = [];
|
||||
|
||||
// $FlowFixMe
|
||||
if (event.dataTransfer) {
|
||||
const dt = event.dataTransfer;
|
||||
if (dt.files && dt.files.length) {
|
||||
dataTransferItemsList = dt.files;
|
||||
} else if (dt.items && dt.items.length) {
|
||||
// During the drag even the dataTransfer.files is null
|
||||
// but Chrome implements some drag store, which is accesible via dataTransfer.items
|
||||
dataTransferItemsList = dt.items;
|
||||
}
|
||||
|
||||
// $FlowFixMe
|
||||
} else if (event.target && event.target.files) {
|
||||
dataTransferItemsList = event.target.files;
|
||||
}
|
||||
// Convert from DataTransferItemsList to the native Array
|
||||
return Array.prototype.slice.call(dataTransferItemsList);
|
||||
}
|
||||
29
app/utils/getDataTransferFiles.ts
Normal file
29
app/utils/getDataTransferFiles.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export default function getDataTransferFiles(
|
||||
event:
|
||||
| Event
|
||||
| React.FormEvent<HTMLInputElement>
|
||||
| React.DragEvent<HTMLElement>
|
||||
): File[] {
|
||||
let dataTransferItemsList = [];
|
||||
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dataTransfer' does not exist on type 'Sy... Remove this comment to see the full error message
|
||||
if (event.dataTransfer) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dataTransfer' does not exist on type 'Sy... Remove this comment to see the full error message
|
||||
const dt = event.dataTransfer;
|
||||
|
||||
if (dt.files && dt.files.length) {
|
||||
dataTransferItemsList = dt.files;
|
||||
} else if (dt.items && dt.items.length) {
|
||||
// During the drag even the dataTransfer.files is null
|
||||
// but Chrome implements some drag store, which is accesible via dataTransfer.items
|
||||
dataTransferItemsList = dt.items;
|
||||
}
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'files' does not exist on type 'EventTarg... Remove this comment to see the full error message
|
||||
} else if (event.target && event.target.files) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'files' does not exist on type 'EventTarg... Remove this comment to see the full error message
|
||||
dataTransferItemsList = event.target.files;
|
||||
}
|
||||
|
||||
// Convert from DataTransferItemsList to the native Array
|
||||
return Array.prototype.slice.call(dataTransferItemsList);
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { createBrowserHistory } from "history";
|
||||
|
||||
const history = createBrowserHistory();
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import {
|
||||
enUS,
|
||||
de,
|
||||
@@ -33,6 +32,6 @@ const locales = {
|
||||
pl_PL: pl,
|
||||
};
|
||||
|
||||
export function dateLocale(userLocale: ?string) {
|
||||
export function dateLocale(userLocale: string | null | undefined) {
|
||||
return userLocale ? locales[userLocale] : undefined;
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
// @flow
|
||||
const inputs = ["input", "select", "button", "textarea"];
|
||||
const inputs = ["input", "select", "button", "textarea"]; // detect if node is a text input element
|
||||
|
||||
// detect if node is a text input element
|
||||
export default function isTextInput(element: HTMLElement): boolean {
|
||||
return (
|
||||
element &&
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
const isMac = window.navigator.platform === "MacIntel";
|
||||
|
||||
export const metaDisplay = isMac ? "⌘" : "Ctrl";
|
||||
@@ -6,7 +5,7 @@ export const metaDisplay = isMac ? "⌘" : "Ctrl";
|
||||
export const meta = isMac ? "cmd" : "ctrl";
|
||||
|
||||
export function isModKey(
|
||||
event: KeyboardEvent | MouseEvent | SyntheticKeyboardEvent<>
|
||||
event: KeyboardEvent | MouseEvent | React.KeyboardEvent
|
||||
) {
|
||||
return isMac ? event.metaKey : event.ctrlKey;
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
// @flow
|
||||
|
||||
export function detectLanguage() {
|
||||
const [ln, r] = navigator.language.split("-");
|
||||
const region = (r || ln).toUpperCase();
|
||||
return `${ln}_${region}`;
|
||||
}
|
||||
|
||||
export function changeLanguage(toLanguageString: ?string, i18n: any) {
|
||||
export function changeLanguage(
|
||||
toLanguageString: string | null | undefined,
|
||||
i18n: any
|
||||
) {
|
||||
if (toLanguageString && i18n.language !== toLanguageString) {
|
||||
// Languages are stored in en_US format in the database, however the
|
||||
// frontend translation framework (i18next) expects en-US
|
||||
@@ -1,3 +1,3 @@
|
||||
// @flow
|
||||
import { domMax } from "framer-motion";
|
||||
|
||||
export default domMax;
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
let hidden = "hidden";
|
||||
let visibilityChange = "visibilitychange";
|
||||
|
||||
@@ -20,6 +19,5 @@ export function getVisibilityListener(): string {
|
||||
}
|
||||
|
||||
export function getPageVisible(): boolean {
|
||||
// $FlowFixMe
|
||||
return !document[hidden];
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
import queryString from "query-string";
|
||||
import Collection from "models/Collection";
|
||||
import Document from "models/Document";
|
||||
import Collection from "~/models/Collection";
|
||||
import Document from "~/models/Document";
|
||||
|
||||
export function homePath(): string {
|
||||
return "/home";
|
||||
@@ -31,7 +30,7 @@ export function groupSettingsPath(): string {
|
||||
return "/settings/groups";
|
||||
}
|
||||
|
||||
export function collectionUrl(url: string, section: ?string): string {
|
||||
export function collectionUrl(url: string, section?: string): string {
|
||||
if (section) return `${url}/${section}`;
|
||||
return url;
|
||||
}
|
||||
@@ -76,21 +75,21 @@ export function updateDocumentUrl(oldUrl: string, document: Document): string {
|
||||
|
||||
export function newDocumentPath(
|
||||
collectionId: string,
|
||||
params?: {
|
||||
parentDocumentId?: string,
|
||||
templateId?: string,
|
||||
template?: boolean,
|
||||
}
|
||||
params: {
|
||||
parentDocumentId?: string;
|
||||
templateId?: string;
|
||||
template?: boolean;
|
||||
} = {}
|
||||
): string {
|
||||
return `/collection/${collectionId}/new?${queryString.stringify(params)}`;
|
||||
}
|
||||
|
||||
export function searchUrl(
|
||||
query?: string,
|
||||
params?: {
|
||||
collectionId?: string,
|
||||
ref?: string,
|
||||
}
|
||||
params: {
|
||||
collectionId?: string;
|
||||
ref?: string;
|
||||
} = {}
|
||||
): string {
|
||||
let search = queryString.stringify(params);
|
||||
let route = "/search";
|
||||
@@ -1,10 +1,9 @@
|
||||
// @flow
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { Integrations } from "@sentry/tracing";
|
||||
import { type RouterHistory } from "react-router-dom";
|
||||
import env from "env";
|
||||
import { History } from "history";
|
||||
import env from "~/env";
|
||||
|
||||
export function initSentry(history: RouterHistory) {
|
||||
export function initSentry(history: History) {
|
||||
Sentry.init({
|
||||
dsn: env.SENTRY_DSN,
|
||||
environment: env.ENVIRONMENT,
|
||||
@@ -1,16 +1,17 @@
|
||||
// @flow
|
||||
import invariant from "invariant";
|
||||
import { client } from "./ApiClient";
|
||||
|
||||
type Options = {
|
||||
name?: string,
|
||||
documentId?: string,
|
||||
public?: boolean,
|
||||
name?: string;
|
||||
documentId?: string;
|
||||
public?: boolean;
|
||||
};
|
||||
|
||||
export const uploadFile = async (
|
||||
file: File | Blob,
|
||||
options?: Options = { name: "" }
|
||||
options: Options = {
|
||||
name: "",
|
||||
}
|
||||
) => {
|
||||
const name = file instanceof File ? file.name : options.name;
|
||||
const response = await client.post("/attachments.create", {
|
||||
@@ -20,9 +21,7 @@ export const uploadFile = async (
|
||||
size: file.size,
|
||||
name,
|
||||
});
|
||||
|
||||
invariant(response, "Response should be available");
|
||||
|
||||
const data = response.data;
|
||||
const attachment = data.attachment;
|
||||
const formData = new FormData();
|
||||
@@ -31,9 +30,9 @@ export const uploadFile = async (
|
||||
formData.append(key, data.form[key]);
|
||||
}
|
||||
|
||||
// $FlowFixMe
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'blob' does not exist on type 'File | Blo... Remove this comment to see the full error message
|
||||
if (file.blob) {
|
||||
// $FlowFixMe
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'file' does not exist on type 'File | Blo... Remove this comment to see the full error message
|
||||
formData.append("file", file.file);
|
||||
} else {
|
||||
formData.append("file", file);
|
||||
@@ -43,18 +42,20 @@ export const uploadFile = async (
|
||||
method: "post",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
invariant(uploadResponse.ok, "Upload failed, try again?");
|
||||
|
||||
return attachment;
|
||||
};
|
||||
|
||||
export const dataUrlToBlob = (dataURL: string) => {
|
||||
var blobBin = atob(dataURL.split(",")[1]);
|
||||
var array = [];
|
||||
for (var i = 0; i < blobBin.length; i++) {
|
||||
const blobBin = atob(dataURL.split(",")[1]);
|
||||
const array = [];
|
||||
|
||||
for (let i = 0; i < blobBin.length; i++) {
|
||||
array.push(blobBin.charCodeAt(i));
|
||||
}
|
||||
const file = new Blob([new Uint8Array(array)], { type: "image/png" });
|
||||
|
||||
const file = new Blob([new Uint8Array(array)], {
|
||||
type: "image/png",
|
||||
});
|
||||
return file;
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { decodeURIComponentSafe } from "./urls";
|
||||
|
||||
describe("decodeURIComponentSafe", () => {
|
||||
@@ -1,9 +1,7 @@
|
||||
// @flow
|
||||
import { parseDomain } from "../../shared/utils/domains";
|
||||
import { parseDomain } from "@shared/utils/domains";
|
||||
|
||||
export function isInternalUrl(href: string) {
|
||||
if (href[0] === "/") return true;
|
||||
|
||||
const outline = parseDomain(window.location.href);
|
||||
const parsed = parseDomain(href);
|
||||
|
||||
Reference in New Issue
Block a user