Skip to content

Commit

Permalink
convert util.js to util.ts, remove jquery
Browse files Browse the repository at this point in the history
  • Loading branch information
freyavs committed Jul 30, 2023
1 parent 0764401 commit dd1a5a4
Show file tree
Hide file tree
Showing 2 changed files with 273 additions and 4 deletions.
7 changes: 3 additions & 4 deletions app/assets/javascripts/copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import { ready, tooltip } from "util.js";
export async function initClipboard(): Promise<void> {
await ready;
const selector = ".btn";
const delay = 1000;
const clip = new ClipboardJS(selector);
const targetOf = (e): any => $($(e.trigger).data("clipboard-target"));
clip.on("success", e => tooltip(targetOf(e), I18n.t("js.copy-success"), delay));
clip.on("error", e => tooltip(targetOf(e), I18n.t("js.copy-fail"), delay));
const targetOf = (e): Element => document.querySelector(e.trigger.dataset["clipboard-target"]);
clip.on("success", e => tooltip(targetOf(e), I18n.t("js.copy-success")));
clip.on("error", e => tooltip(targetOf(e), I18n.t("js.copy-fail")));
}

/**
Expand Down
270 changes: 270 additions & 0 deletions app/assets/javascripts/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
import { isInIframe } from "iframe";
import { Dutch } from "flatpickr/dist/l10n/nl";
import { CustomLocale } from "flatpickr/dist/types/locale";
import flatpickr from "flatpickr";

/**
* Create a function that will delay all subsequent calls on the same timer.
* You don't necessarily have to call the delayer with the same function.
*
* In the first example, the typical usage is illustrated. The second example
* illustrates what happens with multiple delayers, each with their own timer.
*
* There is also a pre-made delayer available with a global timer, see `delay`.
* @example
* const delay = createDelayer();
* delay(() => console.log(1), 100);
* delay(() => console.log(2), 100);
* // prints 2, since the first invocation is cancelled
*
* @example
* const delay1 = createDelayer();
* const delay2 = createDelayer();
* delay1(() => console.log(1), 100);
* delay2(() => console.log(2), 100);
* // prints 1 and then 2, since both have their own timer.
*
* @return {function(TimerHandler, number): void}
*/
function createDelayer(): (callback: () => void, ms?: number) => void {
const timer = 0;
return (callback, ms) => {
clearTimeout(timer);
setTimeout(callback, ms);

Check warning on line 33 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L32-L33

Added lines #L32 - L33 were not covered by tests
};
}

/*
* Function to delay some other function until it isn't
* called for "ms" ms. This runs on a global timer, meaning
* the actual function doesn't matter. If you want a delay
* specifically for one function, you need to first create
* your own "delayer" with `createDelayer`.
*/
const delay = createDelayer();

function updateURLParameter(_url: string, param: string, paramVal: string): string {
const url = new URL(_url, window.location.origin);
if (paramVal) {
url.searchParams.set(param, paramVal);
} else {
url.searchParams.delete(param);

Check warning on line 51 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L46-L51

Added lines #L46 - L51 were not covered by tests
}
return url.toString();

Check warning on line 53 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L53

Added line #L53 was not covered by tests
}

function updateArrayURLParameter(_url: string, param: string, _paramVals: string[]): string {
const paramVals = new Set(_paramVals); // remove duplicate items

Check warning on line 57 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L56-L57

Added lines #L56 - L57 were not covered by tests
// convert "%5B%5D" back to "[]"
const url = new URL(_url.replace(/%5B%5D/g, "[]"), window.location.origin);
url.searchParams.delete(`${param}[]`);
paramVals.forEach(paramVal => {
url.searchParams.append(`${param}[]`, paramVal);

Check warning on line 62 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L59-L62

Added lines #L59 - L62 were not covered by tests
});
return url.toString();

Check warning on line 64 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L64

Added line #L64 was not covered by tests
}

function getURLParameter(name: string, _url: string): string {
const url = new URL(_url ?? window.location.href, window.location.origin);
return url.searchParams.get(name);

Check warning on line 69 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L67-L69

Added lines #L67 - L69 were not covered by tests
}

function getArrayURLParameter(name: string, _url: string): string[] {
const url = new URL(_url ?? window.location.href, window.location.origin);
return url.searchParams.getAll(`${name}[]`);

Check warning on line 74 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L72-L74

Added lines #L72 - L74 were not covered by tests
}

function checkTimeZone(offset: number): void {
if (offset !== new Date().getTimezoneOffset()) {
document.querySelector("#time-zone-warning").classList.remove("hidden");

Check warning on line 79 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L77-L79

Added lines #L77 - L79 were not covered by tests
}
}

function checkIframe(): void {
if (isInIframe()) {
document.querySelector("#iframe-warning").classList.remove("hidden");

Check warning on line 85 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L83-L85

Added lines #L83 - L85 were not covered by tests
}
}

// TODO: remove?
// add CSRF token to each ajax-request
async function initCSRF(): Promise<void> {
await ready;
$.ajaxSetup({

Check warning on line 93 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L91-L93

Added lines #L91 - L93 were not covered by tests
"headers": {
"X-CSRF-Token": $("meta[name='csrf-token']").attr("content"),
},
});
}

/**
* @param {Document | Element} root
*/
function initTooltips(root = document): void {

Check warning on line 103 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L103

Added line #L103 was not covered by tests
// First remove dead tooltips
const tooltips = root.querySelectorAll(".tooltip");
for (const tooltip of tooltips) {
tooltip.remove();

Check warning on line 107 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L105-L107

Added lines #L105 - L107 were not covered by tests
}

// Then reinitialize tooltips
const elements = root.querySelectorAll("[data-bs-toggle=\"tooltip\"]") as NodeListOf<HTMLElement>;
for (const element of elements) {
const tooltip = window.bootstrap.Tooltip.getOrCreateInstance(element);
if (element.title) {
tooltip.setContent({ ".tooltip-inner": element.title });
element.removeAttribute("title");

Check warning on line 116 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L111-L116

Added lines #L111 - L116 were not covered by tests
}
}
}

function tooltip(target: Element | null, message: string, disappearAfter = 3000): void {
if (target) {
const originalTitle = target.getAttribute("data-original-title");
target.setAttribute("data-original-title", message);
target.setAttribute("title", message);
setTimeout(() => {
if (originalTitle) {
target.setAttribute("title", originalTitle);
target.setAttribute("data-original-title", originalTitle);
} else {
target.removeAttribute("title");
target.removeAttribute("data-original-title");

Check warning on line 132 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L121-L132

Added lines #L121 - L132 were not covered by tests
}
}, disappearAfter);
}
}

function fetch(url: URL | RequestInfo, options: RequestInit = {}): Promise<Response> {
const headers = options.headers || {};
headers["x-csrf-token"] = headers["x-csrf-token"] || (document.querySelector("meta[name=\"csrf-token\"]") as HTMLMetaElement).content;
headers["x-requested-with"] = headers["x-requested-with"] || "XMLHttpRequest";
options["headers"] = headers;
return window.fetch(url, options);

Check warning on line 143 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L138-L143

Added lines #L138 - L143 were not covered by tests
}

/**
* Make an element invisible by applying "visibility: hidden".
*
* @param {HTMLElement} element The element to hide.
*/
function makeInvisible(element: HTMLElement): void {
element.style.visibility = "hidden";

Check warning on line 152 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L151-L152

Added lines #L151 - L152 were not covered by tests
}

/**
* Make an element visible by applying "visibility: visible".
*
* @param {HTMLElement} element The element to show.
*/
function makeVisible(element: HTMLElement): void {
element.style.visibility = "visible";

Check warning on line 161 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L160-L161

Added lines #L160 - L161 were not covered by tests
}

/**
* Set the title of the webpage.
*
* @param {string} title The new title.
*/
function setDocumentTitle(title: string): void {
document.title = title;

Check warning on line 170 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L169-L170

Added lines #L169 - L170 were not covered by tests
}

type DatePickerOptions = {
wrap?: boolean,
enableTime?: boolean,
dateFormat?: string,
altInput?: boolean,
altFormat?: string,
locale?: CustomLocale
};

/**
* Initiates a datepicker using flatpicker
* @param {string} selector - The selector of div containing the input field and buttons
* @param {object} options - optional, Options object as should be provided to the flatpicker creation method
* @return {flatpickr} the created flatpicker
*/
function initDatePicker(selector: string, options: DatePickerOptions = {}): object {
function init(): object {
if (I18n.locale === "nl") {
options.locale = Dutch;

Check warning on line 191 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L188-L191

Added lines #L188 - L191 were not covered by tests
}
return flatpickr(selector, options);

Check warning on line 193 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L193

Added line #L193 was not covered by tests
}

return init();

Check warning on line 196 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L196

Added line #L196 was not covered by tests
}

/**
* This promise will resolve when the dom content is fully loaded
* This could mean immediately if the dom is already loaded
*/
const ready = new Promise<void>(resolve => {
if (document.readyState !== "loading") {
resolve();

Check warning on line 205 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L205

Added line #L205 was not covered by tests
} else {
document.addEventListener("DOMContentLoaded", () => resolve());
}
});

// source https://github.com/janl/mustache.js/blob/master/mustache.js#L73
const entityMap = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
"\"": "&quot;",
"'": "&#39;",
"/": "&#x2F;",
"`": "&#x60;",
"=": "&#x3D;"
};

function htmlEncode(str: string): string {
return String(str).replace(/[&<>"'`=/]/g, function (s) {
return entityMap[s];

Check warning on line 225 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L223-L225

Added lines #L223 - L225 were not covered by tests
});
}

/**
* Returns the first parent of an element that has at least all of the given classes.
* Returns null if no such parent exists.
* @param {Element} element - Iterate over the parents of this element
* @param {string} classNames - The class names to search for, separated by white space
* @return {?Element} The parent containing the classes
*/
function getParentByClassName(element: Element, classNames: string): Element {
let parent = element.parentElement;
while (parent) {
if (classNames.split(/\s+/).every(className => parent.classList.contains(className))) {
return parent;

Check warning on line 240 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L236-L240

Added lines #L236 - L240 were not covered by tests
}
parent = parent.parentElement;

Check warning on line 242 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L242

Added line #L242 was not covered by tests
}
return null;

Check warning on line 244 in app/assets/javascripts/util.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/util.ts#L244

Added line #L244 was not covered by tests
}

// insert `cached` function here after move to typescript
// the function is currently in `app/assets/javascripts/mark.ts`

export {
createDelayer,
delay,
fetch,
updateURLParameter,
updateArrayURLParameter,
getURLParameter,
getArrayURLParameter,
checkTimeZone,
checkIframe,
initCSRF,
tooltip,
initTooltips,
makeInvisible,
makeVisible,
setDocumentTitle,
initDatePicker,
ready,
htmlEncode,
getParentByClassName,
};

0 comments on commit dd1a5a4

Please sign in to comment.