chore(webpack): enable typescript
This commit is contained in:
parent
5b4b57060c
commit
ea6aec8b1a
11 changed files with 548 additions and 123 deletions
|
@ -5,6 +5,7 @@ import 'core-js/stable';
|
|||
import 'regenerator-runtime/runtime';
|
||||
import 'dom4';
|
||||
import 'intersection-observer';
|
||||
import 'whatwg-fetch';
|
||||
|
||||
import './polyfills/insertAdjacentElement';
|
||||
import './polyfills/dataset';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { Suspense, lazy, createElement } from 'react';
|
||||
import React, { Suspense, lazy, createElement, ComponentClass } from 'react';
|
||||
import { render } from 'react-dom';
|
||||
|
||||
// This attribute holds the name of component which should be mounted
|
||||
|
@ -13,12 +13,12 @@ const CLASS_NAME_SELECTOR = `[${CLASS_NAME_ATTR}]`;
|
|||
|
||||
// helper method for the mount and unmount methods to find the
|
||||
// `data-react-class` DOM elements
|
||||
function findDOMNodes(searchSelector) {
|
||||
function findDOMNodes(searchSelector?: string): NodeListOf<HTMLDivElement> {
|
||||
const [selector, parent] = getSelector(searchSelector);
|
||||
return parent.querySelectorAll(selector);
|
||||
return parent.querySelectorAll<HTMLDivElement>(selector);
|
||||
}
|
||||
|
||||
function getSelector(searchSelector) {
|
||||
function getSelector(searchSelector?: string): [string, Document] {
|
||||
switch (typeof searchSelector) {
|
||||
case 'undefined':
|
||||
return [CLASS_NAME_SELECTOR, document];
|
||||
|
@ -39,15 +39,15 @@ function getSelector(searchSelector) {
|
|||
class ReactComponentRegistry {
|
||||
#components;
|
||||
|
||||
constructor(components) {
|
||||
constructor(components: Record<string, ComponentClass>) {
|
||||
this.#components = components;
|
||||
}
|
||||
|
||||
getConstructor(className) {
|
||||
return this.#components[className];
|
||||
getConstructor(className: string | null) {
|
||||
return className ? this.#components[className] : null;
|
||||
}
|
||||
|
||||
mountComponents(searchSelector) {
|
||||
mountComponents(searchSelector?: string) {
|
||||
const nodes = findDOMNodes(searchSelector);
|
||||
|
||||
for (const node of nodes) {
|
||||
|
@ -76,10 +76,10 @@ class ReactComponentRegistry {
|
|||
|
||||
const Loader = () => <div className="spinner left" />;
|
||||
|
||||
export function Loadable(loader) {
|
||||
export function Loadable(loader: () => Promise<{ default: ComponentClass }>) {
|
||||
const LazyComponent = lazy(loader);
|
||||
|
||||
return function PureComponent(props) {
|
||||
return function PureComponent(props: Record<string, unknown>) {
|
||||
return (
|
||||
<Suspense fallback={<Loader />}>
|
||||
<LazyComponent {...props} />
|
||||
|
@ -88,7 +88,9 @@ export function Loadable(loader) {
|
|||
};
|
||||
}
|
||||
|
||||
export function registerReactComponents(components) {
|
||||
export function registerReactComponents(
|
||||
components: Record<string, ComponentClass>
|
||||
) {
|
||||
const registry = new ReactComponentRegistry(components);
|
||||
|
||||
addEventListener('ds:page:update', () => registry.mountComponents());
|
|
@ -4,17 +4,17 @@ import debounce from 'debounce';
|
|||
export { debounce };
|
||||
export const { fire, csrfToken } = Rails;
|
||||
|
||||
export function show(el) {
|
||||
export function show(el: HTMLElement) {
|
||||
el && el.classList.remove('hidden');
|
||||
}
|
||||
|
||||
export function hide(el) {
|
||||
export function hide(el: HTMLElement) {
|
||||
el && el.classList.add('hidden');
|
||||
}
|
||||
|
||||
export function toggle(el, force) {
|
||||
export function toggle(el: HTMLElement, force?: boolean) {
|
||||
if (force == undefined) {
|
||||
el & el.classList.toggle('hidden');
|
||||
el && el.classList.toggle('hidden');
|
||||
} else if (force) {
|
||||
el && el.classList.remove('hidden');
|
||||
} else {
|
||||
|
@ -22,27 +22,31 @@ export function toggle(el, force) {
|
|||
}
|
||||
}
|
||||
|
||||
export function enable(el) {
|
||||
export function enable(el: HTMLInputElement) {
|
||||
el && (el.disabled = false);
|
||||
}
|
||||
|
||||
export function disable(el) {
|
||||
export function disable(el: HTMLInputElement) {
|
||||
el && (el.disabled = true);
|
||||
}
|
||||
|
||||
export function hasClass(el, cssClass) {
|
||||
export function hasClass(el: HTMLElement, cssClass: string) {
|
||||
return el && el.classList.contains(cssClass);
|
||||
}
|
||||
|
||||
export function addClass(el, cssClass) {
|
||||
export function addClass(el: HTMLElement, cssClass: string) {
|
||||
el && el.classList.add(cssClass);
|
||||
}
|
||||
|
||||
export function removeClass(el, cssClass) {
|
||||
export function removeClass(el: HTMLElement, cssClass: string) {
|
||||
el && el.classList.remove(cssClass);
|
||||
}
|
||||
|
||||
export function delegate(eventNames, selector, callback) {
|
||||
export function delegate(
|
||||
eventNames: string,
|
||||
selector: string,
|
||||
callback: () => void
|
||||
) {
|
||||
eventNames
|
||||
.split(' ')
|
||||
.forEach((eventName) =>
|
||||
|
@ -57,15 +61,23 @@ export function delegate(eventNames, selector, callback) {
|
|||
// - rejected with an Error object otherwise.
|
||||
//
|
||||
// See Rails.ajax() code for more details.
|
||||
export function ajax(options) {
|
||||
export function ajax(options: Rails.AjaxOptions) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Object.assign(options, {
|
||||
success: (response, statusText, xhr) => {
|
||||
success: (
|
||||
response: unknown,
|
||||
statusText: string,
|
||||
xhr: { status: number }
|
||||
) => {
|
||||
resolve({ response, statusText, xhr });
|
||||
},
|
||||
error: (response, statusText, xhr) => {
|
||||
error: (
|
||||
response: unknown,
|
||||
statusText: string,
|
||||
xhr: { status: number }
|
||||
) => {
|
||||
// NB: on HTTP/2 connections, statusText is always empty.
|
||||
let error = new Error(
|
||||
const error = new Error(
|
||||
`Erreur ${xhr.status}` + (statusText ? ` : ${statusText}` : '')
|
||||
);
|
||||
Object.assign(error, { response, statusText, xhr });
|
||||
|
@ -76,7 +88,7 @@ export function ajax(options) {
|
|||
});
|
||||
}
|
||||
|
||||
export function getJSON(url, data, method = 'GET') {
|
||||
export function getJSON(url: string, data: unknown, method = 'GET') {
|
||||
const { query, ...options } = fetchOptions(data, method);
|
||||
|
||||
return fetch(`${url}${query}`, options).then((response) => {
|
||||
|
@ -86,32 +98,38 @@ export function getJSON(url, data, method = 'GET') {
|
|||
}
|
||||
return response.json();
|
||||
}
|
||||
const error = new Error(response.statusText || response.status);
|
||||
error.response = response;
|
||||
const error = new Error(String(response.statusText || response.status));
|
||||
(error as any).response = response;
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
export function scrollTo(container, scrollTo) {
|
||||
export function scrollTo(container: HTMLElement, scrollTo: HTMLElement) {
|
||||
container.scrollTop =
|
||||
offset(scrollTo).top - offset(container).top + container.scrollTop;
|
||||
}
|
||||
|
||||
export function scrollToBottom(container) {
|
||||
export function scrollToBottom(container: HTMLElement) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
|
||||
export function on(selector, eventName, fn) {
|
||||
export function on(
|
||||
selector: string,
|
||||
eventName: string,
|
||||
fn: (event: Event, detail: unknown) => void
|
||||
) {
|
||||
[...document.querySelectorAll(selector)].forEach((element) =>
|
||||
element.addEventListener(eventName, (event) => fn(event, event.detail))
|
||||
element.addEventListener(eventName, (event) =>
|
||||
fn(event, (event as CustomEvent).detail)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function isNumeric(n) {
|
||||
return !isNaN(parseFloat(n)) && isFinite(n);
|
||||
export function isNumeric(n: string) {
|
||||
return !isNaN(parseFloat(n)) && isFinite(n as any as number);
|
||||
}
|
||||
|
||||
function offset(element) {
|
||||
function offset(element: HTMLElement) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return {
|
||||
top: rect.top + document.body.scrollTop,
|
||||
|
@ -120,8 +138,11 @@ function offset(element) {
|
|||
}
|
||||
|
||||
// Takes a promise, and return a promise that times out after the given delay.
|
||||
export function timeoutable(promise, timeoutDelay) {
|
||||
let timeoutPromise = new Promise((resolve, reject) => {
|
||||
export function timeoutable<T>(
|
||||
promise: Promise<T>,
|
||||
timeoutDelay: number
|
||||
): Promise<T> {
|
||||
const timeoutPromise = new Promise<T>((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error(`Promise timed out after ${timeoutDelay}ms`));
|
||||
}, timeoutDelay);
|
||||
|
@ -131,13 +152,16 @@ export function timeoutable(promise, timeoutDelay) {
|
|||
|
||||
const FETCH_TIMEOUT = 30 * 1000; // 30 sec
|
||||
|
||||
function fetchOptions(data, method = 'GET') {
|
||||
const options = {
|
||||
function fetchOptions(data: unknown, method = 'GET') {
|
||||
const options: RequestInit & {
|
||||
query: string;
|
||||
headers: Record<string, string>;
|
||||
} = {
|
||||
query: '',
|
||||
method: method.toUpperCase(),
|
||||
headers: {
|
||||
accept: 'application/json',
|
||||
'x-csrf-token': csrfToken(),
|
||||
'x-csrf-token': csrfToken() ?? '',
|
||||
'x-requested-with': 'XMLHttpRequest'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
|
@ -145,7 +169,7 @@ function fetchOptions(data, method = 'GET') {
|
|||
|
||||
if (data) {
|
||||
if (options.method === 'GET') {
|
||||
options.query = objectToQuerystring(data);
|
||||
options.query = objectToQuerystring(data as Record<string, string>);
|
||||
} else {
|
||||
options.headers['content-type'] = 'application/json';
|
||||
options.body = JSON.stringify(data);
|
||||
|
@ -164,7 +188,7 @@ function fetchOptions(data, method = 'GET') {
|
|||
return options;
|
||||
}
|
||||
|
||||
function objectToQuerystring(obj) {
|
||||
function objectToQuerystring(obj: Record<string, string>): string {
|
||||
return Object.keys(obj).reduce(function (query, key, i) {
|
||||
return [
|
||||
query,
|
21
app/javascript/types.d.ts
vendored
Normal file
21
app/javascript/types.d.ts
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
declare module '@tmcw/togeojson' {
|
||||
import { FeatureCollection, GeoJsonProperties, Geometry } from 'geojson';
|
||||
|
||||
export function kml(doc: Document): FeatureCollection;
|
||||
|
||||
export function kml<TProperties extends GeoJsonProperties>(
|
||||
doc: Document
|
||||
): FeatureCollection<Geometry, TProperties>;
|
||||
|
||||
export function gpx(doc: Document): FeatureCollection;
|
||||
export function gpx<TProperties extends GeoJsonProperties>(
|
||||
doc: Document
|
||||
): FeatureCollection<Geometry, TProperties>;
|
||||
|
||||
export function tcx(doc: Document): FeatureCollection;
|
||||
export function tcx<TProperties extends GeoJsonProperties>(
|
||||
doc: Document
|
||||
): FeatureCollection<Geometry, TProperties>;
|
||||
}
|
||||
|
||||
declare module 'react-coordinate-input';
|
Loading…
Add table
Add a link
Reference in a new issue