mirror of
https://github.com/mediacms-io/mediacms.git
synced 2026-03-22 04:33:09 -04:00
refactor(frontend): replace legacy utils JS files with typed TS equivalents
This commit is contained in:
@@ -1,2 +0,0 @@
|
|||||||
export { default as months } from './months';
|
|
||||||
export { default as weekdays } from './weekdays';
|
|
||||||
2
frontend/src/static/js/utils/constants/index.ts
Normal file
2
frontend/src/static/js/utils/constants/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './months';
|
||||||
|
export * from './weekdays';
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
export default [
|
|
||||||
'January',
|
|
||||||
'February',
|
|
||||||
'March',
|
|
||||||
'April',
|
|
||||||
'May',
|
|
||||||
'June',
|
|
||||||
'July',
|
|
||||||
'August',
|
|
||||||
'September',
|
|
||||||
'October',
|
|
||||||
'November',
|
|
||||||
'December',
|
|
||||||
];
|
|
||||||
14
frontend/src/static/js/utils/constants/months.ts
Executable file
14
frontend/src/static/js/utils/constants/months.ts
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
export const months = [
|
||||||
|
'January',
|
||||||
|
'February',
|
||||||
|
'March',
|
||||||
|
'April',
|
||||||
|
'May',
|
||||||
|
'June',
|
||||||
|
'July',
|
||||||
|
'August',
|
||||||
|
'September',
|
||||||
|
'October',
|
||||||
|
'November',
|
||||||
|
'December',
|
||||||
|
] as const;
|
||||||
@@ -1 +0,0 @@
|
|||||||
export default ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
||||||
1
frontend/src/static/js/utils/constants/weekdays.ts
Executable file
1
frontend/src/static/js/utils/constants/weekdays.ts
Executable file
@@ -0,0 +1 @@
|
|||||||
|
export const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] as const;
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
const Dispatcher = require('flux').Dispatcher;
|
|
||||||
module.exports = new Dispatcher();
|
|
||||||
3
frontend/src/static/js/utils/dispatcher.ts
Executable file
3
frontend/src/static/js/utils/dispatcher.ts
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
import { Dispatcher } from 'flux';
|
||||||
|
|
||||||
|
export const dispatcher = new Dispatcher();
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
export function csrfToken() {
|
|
||||||
var i,
|
|
||||||
cookies,
|
|
||||||
cookie,
|
|
||||||
cookieVal = null;
|
|
||||||
if (document.cookie && '' !== document.cookie) {
|
|
||||||
cookies = document.cookie.split(';');
|
|
||||||
i = 0;
|
|
||||||
while (i < cookies.length) {
|
|
||||||
cookie = cookies[i].trim();
|
|
||||||
if ('csrftoken=' === cookie.substring(0, 10)) {
|
|
||||||
cookieVal = decodeURIComponent(cookie.substring(10));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cookieVal;
|
|
||||||
}
|
|
||||||
18
frontend/src/static/js/utils/helpers/csrfToken.ts
Executable file
18
frontend/src/static/js/utils/helpers/csrfToken.ts
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
export function csrfToken() {
|
||||||
|
let cookieVal = null;
|
||||||
|
|
||||||
|
if (document.cookie && '' !== document.cookie) {
|
||||||
|
const cookies = document.cookie.split(';');
|
||||||
|
let i = 0;
|
||||||
|
while (i < cookies.length) {
|
||||||
|
const cookie = cookies[i].trim();
|
||||||
|
if ('csrftoken=' === cookie.substring(0, 10)) {
|
||||||
|
cookieVal = decodeURIComponent(cookie.substring(10));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookieVal;
|
||||||
|
}
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
export function supportsSvgAsImg() {
|
|
||||||
// @link: https://github.com/Modernizr/Modernizr/blob/master/feature-detects/svg/asimg.js
|
|
||||||
return document.implementation.hasFeature('http://www.w3.org/TR/SVG11/feature#Image', '1.1');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeClassname(el, cls) {
|
|
||||||
if (el.classList) {
|
|
||||||
el.classList.remove(cls);
|
|
||||||
} else {
|
|
||||||
el.className = el.className.replace(new RegExp('(^|\\b)' + cls.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addClassname(el, cls) {
|
|
||||||
if (el.classList) {
|
|
||||||
el.classList.add(cls);
|
|
||||||
} else {
|
|
||||||
el.className += ' ' + cls;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasClassname(el, cls) {
|
|
||||||
return el.className && new RegExp('(\\s|^)' + cls + '(\\s|$)').test(el.className);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;
|
|
||||||
|
|
||||||
export const requestAnimationFrame =
|
|
||||||
window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
|
|
||||||
|
|
||||||
export function BrowserEvents() {
|
|
||||||
const callbacks = {
|
|
||||||
document: {
|
|
||||||
visibility: [],
|
|
||||||
},
|
|
||||||
window: {
|
|
||||||
resize: [],
|
|
||||||
scroll: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function onDocumentVisibilityChange() {
|
|
||||||
callbacks.document.visibility.map((fn) => fn());
|
|
||||||
}
|
|
||||||
|
|
||||||
function onWindowResize() {
|
|
||||||
callbacks.window.resize.map((fn) => fn());
|
|
||||||
}
|
|
||||||
|
|
||||||
function onWindowScroll() {
|
|
||||||
callbacks.window.scroll.map((fn) => fn());
|
|
||||||
}
|
|
||||||
|
|
||||||
function windowEvents(resizeCallback, scrollCallback) {
|
|
||||||
if ('function' === typeof resizeCallback) {
|
|
||||||
callbacks.window.resize.push(resizeCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('function' === typeof scrollCallback) {
|
|
||||||
callbacks.window.scroll.push(scrollCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function documentEvents(visibilityChangeCallback) {
|
|
||||||
if ('function' === typeof visibilityChangeCallback) {
|
|
||||||
callbacks.document.visibility.push(visibilityChangeCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('visibilitychange', onDocumentVisibilityChange);
|
|
||||||
|
|
||||||
window.addEventListener('resize', onWindowResize);
|
|
||||||
window.addEventListener('scroll', onWindowScroll);
|
|
||||||
|
|
||||||
return {
|
|
||||||
doc: documentEvents,
|
|
||||||
win: windowEvents,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
95
frontend/src/static/js/utils/helpers/dom.ts
Executable file
95
frontend/src/static/js/utils/helpers/dom.ts
Executable file
@@ -0,0 +1,95 @@
|
|||||||
|
export function supportsSvgAsImg() {
|
||||||
|
// @link: https://github.com/Modernizr/Modernizr/blob/master/feature-detects/svg/asimg.js
|
||||||
|
return document.implementation.hasFeature('http://www.w3.org/TR/SVG11/feature#Image', '1.1');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeClassname(el: HTMLElement, cls: string) {
|
||||||
|
if (el.classList) {
|
||||||
|
el.classList.remove(cls);
|
||||||
|
} else {
|
||||||
|
el.className = el.className.replace(new RegExp('(^|\\b)' + cls.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addClassname(el: HTMLElement, cls: string) {
|
||||||
|
if (el.classList) {
|
||||||
|
el.classList.add(cls);
|
||||||
|
} else {
|
||||||
|
el.className += ' ' + cls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasClassname(el: HTMLElement, cls: string) {
|
||||||
|
return el.className && new RegExp('(\\s|^)' + cls + '(\\s|$)').test(el.className);
|
||||||
|
}
|
||||||
|
|
||||||
|
type LegacyWindow = Window & {
|
||||||
|
mozCancelAnimationFrame?: Window['cancelAnimationFrame'];
|
||||||
|
mozRequestAnimationFrame?: Window['requestAnimationFrame'];
|
||||||
|
msRequestAnimationFrame?: Window['requestAnimationFrame'];
|
||||||
|
webkitRequestAnimationFrame?: Window['requestAnimationFrame'];
|
||||||
|
};
|
||||||
|
|
||||||
|
const legacyWindow = window as LegacyWindow;
|
||||||
|
|
||||||
|
export const cancelAnimationFrame: Window['cancelAnimationFrame'] =
|
||||||
|
legacyWindow.cancelAnimationFrame ||
|
||||||
|
legacyWindow.mozCancelAnimationFrame ||
|
||||||
|
((id: number) => window.clearTimeout(id));
|
||||||
|
|
||||||
|
export const requestAnimationFrame: Window['requestAnimationFrame'] =
|
||||||
|
legacyWindow.requestAnimationFrame ||
|
||||||
|
legacyWindow.mozRequestAnimationFrame ||
|
||||||
|
legacyWindow.webkitRequestAnimationFrame ||
|
||||||
|
legacyWindow.msRequestAnimationFrame ||
|
||||||
|
((callback: FrameRequestCallback) => window.setTimeout(() => callback(performance.now()), 16));
|
||||||
|
|
||||||
|
export function BrowserEvents() {
|
||||||
|
const callbacks = {
|
||||||
|
document: {
|
||||||
|
visibility: [] as Function[],
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
resize: [] as Function[],
|
||||||
|
scroll: [] as Function[],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function onDocumentVisibilityChange() {
|
||||||
|
callbacks.document.visibility.map((fn) => fn());
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWindowResize() {
|
||||||
|
callbacks.window.resize.map((fn) => fn());
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWindowScroll() {
|
||||||
|
callbacks.window.scroll.map((fn) => fn());
|
||||||
|
}
|
||||||
|
|
||||||
|
function windowEvents(resizeCallback?: Function, scrollCallback?: Function) {
|
||||||
|
if ('function' === typeof resizeCallback) {
|
||||||
|
callbacks.window.resize.push(resizeCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('function' === typeof scrollCallback) {
|
||||||
|
callbacks.window.scroll.push(scrollCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function documentEvents(visibilityChangeCallback?: Function) {
|
||||||
|
if ('function' === typeof visibilityChangeCallback) {
|
||||||
|
callbacks.document.visibility.push(visibilityChangeCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', onDocumentVisibilityChange);
|
||||||
|
|
||||||
|
window.addEventListener('resize', onWindowResize);
|
||||||
|
window.addEventListener('scroll', onWindowScroll);
|
||||||
|
|
||||||
|
return {
|
||||||
|
doc: documentEvents,
|
||||||
|
win: windowEvents,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ export function inEmbeddedApp() {
|
|||||||
sessionStorage.setItem('media_cms_embed_mode', 'true');
|
sessionStorage.setItem('media_cms_embed_mode', 'true');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === 'standard') {
|
if (mode === 'standard') {
|
||||||
sessionStorage.removeItem('media_cms_embed_mode');
|
sessionStorage.removeItem('media_cms_embed_mode');
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
// TODO: Improve or (even better) remove this file code.
|
|
||||||
|
|
||||||
import { error as logErrFn, warn as logWarnFn } from './log';
|
|
||||||
|
|
||||||
function logAndReturnError(logFn, msgArr, ErrorConstructor) {
|
|
||||||
let err;
|
|
||||||
switch (ErrorConstructor) {
|
|
||||||
case TypeError:
|
|
||||||
case RangeError:
|
|
||||||
case SyntaxError:
|
|
||||||
case ReferenceError:
|
|
||||||
err = new ErrorConstructor(msgArr[0]);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
err = new Error(msgArr[0]);
|
|
||||||
}
|
|
||||||
logFn(err.message, ...msgArr.slice(1));
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function logErrorAndReturnError(msgArr, ErrorConstructor) {
|
|
||||||
return logAndReturnError(logErrFn, msgArr, ErrorConstructor);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function logWarningAndReturnError(msgArr, ErrorConstructor) {
|
|
||||||
return logAndReturnError(logWarnFn, msgArr, ErrorConstructor);
|
|
||||||
}
|
|
||||||
15
frontend/src/static/js/utils/helpers/errors.ts
Executable file
15
frontend/src/static/js/utils/helpers/errors.ts
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
// @todo: Improve or (even better) remove this file.
|
||||||
|
|
||||||
|
import { error, warn } from './log';
|
||||||
|
|
||||||
|
export function logErrorAndReturnError(msgArr: string[]) {
|
||||||
|
const err = new Error(msgArr[0]);
|
||||||
|
error(...msgArr);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logWarningAndReturnError(msgArr: string[]) {
|
||||||
|
const err = new Error(msgArr[0]);
|
||||||
|
warn(...msgArr);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import * as dispatcher from '../dispatcher.js';
|
|
||||||
export default function (store, handler) {
|
|
||||||
dispatcher.register(store[handler].bind(store));
|
|
||||||
return store;
|
|
||||||
}
|
|
||||||
28
frontend/src/static/js/utils/helpers/exportStore.ts
Executable file
28
frontend/src/static/js/utils/helpers/exportStore.ts
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
import EventEmitter from 'events';
|
||||||
|
import { dispatcher } from '../dispatcher';
|
||||||
|
|
||||||
|
// type ClassProperties<C> = {
|
||||||
|
// [Key in keyof C as C[Key] extends Function ? never : Key]: C[Key];
|
||||||
|
// };
|
||||||
|
|
||||||
|
type ClassMethods<C> = {
|
||||||
|
[Key in keyof C as C[Key] extends Function ? Key : never]: C[Key];
|
||||||
|
};
|
||||||
|
|
||||||
|
// @todo: Check this again
|
||||||
|
export function exportStore<TStore extends EventEmitter, THandler extends keyof ClassMethods<TStore>>(
|
||||||
|
store: TStore,
|
||||||
|
handler: THandler
|
||||||
|
) {
|
||||||
|
const method = store[handler] as Function;
|
||||||
|
const callback: (payload: unknown) => void = method.bind(store);
|
||||||
|
dispatcher.register(callback);
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo: Remove older vesion.
|
||||||
|
// export function exportStore_OLD(store, handler) {
|
||||||
|
// const callback = store[handler].bind(store);
|
||||||
|
// dispatcher.register(callback);
|
||||||
|
// return store;
|
||||||
|
// }
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import urlParse from 'url-parse';
|
|
||||||
|
|
||||||
export function formatInnerLink(url, baseUrl) {
|
|
||||||
let link = urlParse(url, {});
|
|
||||||
|
|
||||||
if ('' === link.origin || 'null' === link.origin || !link.origin) {
|
|
||||||
link = urlParse(baseUrl + '/' + url.replace(/^\//g, ''), {});
|
|
||||||
}
|
|
||||||
|
|
||||||
return link.toString();
|
|
||||||
}
|
|
||||||
11
frontend/src/static/js/utils/helpers/formatInnerLink.ts
Executable file
11
frontend/src/static/js/utils/helpers/formatInnerLink.ts
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
import urlParse from 'url-parse';
|
||||||
|
|
||||||
|
export function formatInnerLink(url: string, baseUrl: string) {
|
||||||
|
let link = urlParse(url, {});
|
||||||
|
|
||||||
|
if ('' === link.origin || 'null' === link.origin || !link.origin) {
|
||||||
|
link = urlParse(baseUrl + '/' + url.replace(/^\//g, ''), {});
|
||||||
|
}
|
||||||
|
|
||||||
|
return link.toString();
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { months as monthList } from '../constants/';
|
|
||||||
|
|
||||||
export function formatManagementTableDate(date) {
|
|
||||||
const day = date.getDate();
|
|
||||||
const month = monthList[date.getMonth()].substring(0, 3);
|
|
||||||
const year = date.getFullYear();
|
|
||||||
const hours = date.getHours();
|
|
||||||
const minutes = date.getMinutes();
|
|
||||||
const seconds = date.getSeconds();
|
|
||||||
let ret = month + ' ' + day + ', ' + year;
|
|
||||||
ret += ' ' + (hours < 10 ? '0' : '') + hours;
|
|
||||||
ret += ':' + (minutes < 10 ? '0' : '') + minutes;
|
|
||||||
ret += ':' + (seconds < 10 ? '0' : '') + seconds;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
15
frontend/src/static/js/utils/helpers/formatManagementTableDate.ts
Executable file
15
frontend/src/static/js/utils/helpers/formatManagementTableDate.ts
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
import { months as monthList } from '../constants';
|
||||||
|
|
||||||
|
export function formatManagementTableDate(date: Date) {
|
||||||
|
const day = date.getDate();
|
||||||
|
const month = monthList[date.getMonth()].substring(0, 3);
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const hours = date.getHours();
|
||||||
|
const minutes = date.getMinutes();
|
||||||
|
const seconds = date.getSeconds();
|
||||||
|
let ret = month + ' ' + day + ', ' + year;
|
||||||
|
ret += ' ' + (hours < 10 ? '0' : '') + hours;
|
||||||
|
ret += ':' + (minutes < 10 ? '0' : '') + minutes;
|
||||||
|
ret += ':' + (seconds < 10 ? '0' : '') + seconds;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
export default function (views_number, fullNumber) {
|
|
||||||
function formattedValue(val, lim, unit) {
|
|
||||||
return Number(parseFloat(val / lim).toFixed(val < 10 * lim ? 1 : 0)) + unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
function format(i, views, mult, compare, limit, units) {
|
|
||||||
while (views >= compare) {
|
|
||||||
limit *= mult;
|
|
||||||
compare *= mult;
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
return i < units.length
|
|
||||||
? formattedValue(views, limit, units[i])
|
|
||||||
: formattedValue(views * (mult * (i - (units.length - 1))), limit, units[units.length - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fullNumber ? views_number.toLocaleString() : format(0, views_number, 1000, 1000, 1, ['', 'K', 'M', 'B', 'T']);
|
|
||||||
}
|
|
||||||
17
frontend/src/static/js/utils/helpers/formatViewsNumber.ts
Executable file
17
frontend/src/static/js/utils/helpers/formatViewsNumber.ts
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
const formattedValue = (val: number, lim: number, unit: string) =>
|
||||||
|
Number((val / lim).toFixed(val < 10 * lim ? 1 : 0)) + unit;
|
||||||
|
|
||||||
|
function format(cntr: number, views: number, mult: number, compare: number, limit: number, units: string[]) {
|
||||||
|
let i = cntr;
|
||||||
|
while (views >= compare) {
|
||||||
|
limit *= mult;
|
||||||
|
compare *= mult;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
return i < units.length
|
||||||
|
? formattedValue(views, limit, units[i])
|
||||||
|
: formattedValue(views * (mult * (i - (units.length - 1))), limit, units[units.length - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatViewsNumber = (views_number: number, fullNumber?: boolean) =>
|
||||||
|
fullNumber ? views_number.toLocaleString() : format(0, views_number, 1000, 1000, 1, ['', 'K', 'M', 'B', 'T']);
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export const imageExtension = (img) => {
|
|
||||||
if (!img) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const ext = img.split('.');
|
|
||||||
return ext[ext.length - 1];
|
|
||||||
};
|
|
||||||
5
frontend/src/static/js/utils/helpers/imageExtension.ts
Executable file
5
frontend/src/static/js/utils/helpers/imageExtension.ts
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
export const imageExtension = (img: string) => {
|
||||||
|
if (img) {
|
||||||
|
return img.split('.').pop();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
export * from './dom';
|
|
||||||
export * from './errors';
|
|
||||||
export { default as exportStore } from './exportStore';
|
|
||||||
export { formatInnerLink } from './formatInnerLink';
|
|
||||||
export * from './formatManagementTableDate';
|
|
||||||
export { default as formatViewsNumber } from './formatViewsNumber';
|
|
||||||
export * from './csrfToken';
|
|
||||||
export { imageExtension } from './imageExtension';
|
|
||||||
export * from './log';
|
|
||||||
export * from './math';
|
|
||||||
export * from './propTypeFilters';
|
|
||||||
export { default as publishedOnDate } from './publishedOnDate';
|
|
||||||
export * from './quickSort';
|
|
||||||
export * from './requests';
|
|
||||||
export { translateString } from './translate';
|
|
||||||
export { replaceString } from './replacementStrings';
|
|
||||||
export * from './embeddedApp';
|
|
||||||
17
frontend/src/static/js/utils/helpers/index.ts
Normal file
17
frontend/src/static/js/utils/helpers/index.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export * from './csrfToken';
|
||||||
|
export * from './dom';
|
||||||
|
export * from './embeddedApp';
|
||||||
|
export * from './errors';
|
||||||
|
export * from './exportStore';
|
||||||
|
export * from './formatInnerLink';
|
||||||
|
export * from './formatManagementTableDate';
|
||||||
|
export * from './formatViewsNumber';
|
||||||
|
export * from './imageExtension';
|
||||||
|
export * from './log';
|
||||||
|
export * from './math';
|
||||||
|
export * from './propTypeFilters';
|
||||||
|
export * from './publishedOnDate';
|
||||||
|
export * from './quickSort';
|
||||||
|
export * from './requests';
|
||||||
|
export * from './translate';
|
||||||
|
export * from './replacementStrings';
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
const log = (...x) => console[x[0]](...x.slice(1));
|
|
||||||
|
|
||||||
export const warn = (...x) => log('warn', ...x);
|
|
||||||
export const error = (...x) => log('error', ...x);
|
|
||||||
9
frontend/src/static/js/utils/helpers/log.ts
Executable file
9
frontend/src/static/js/utils/helpers/log.ts
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
// @todo: Delete this file
|
||||||
|
|
||||||
|
export const warn = (...x: string[]) => {
|
||||||
|
console.warn(...x);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const error = (...x: string[]) => {
|
||||||
|
console.error(...x);
|
||||||
|
};
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
export const isGt = (x, y) => x > y;
|
|
||||||
export const isZero = (x) => 0 === x;
|
|
||||||
export const isNumber = (x) => !isNaN(x) && x === 0 + x;
|
|
||||||
export const isInteger = (x) => x === Math.trunc(x);
|
|
||||||
export const isPositive = (x) => isGt(x, 0);
|
|
||||||
export const isPositiveNumber = (x) => isNumber(x) && isPositive(x);
|
|
||||||
export const isPositiveInteger = (x) => isInteger(x) && isPositive(x);
|
|
||||||
export const isPositiveIntegerOrZero = (x) => isInteger(x) && (isPositive(x) || isZero(x));
|
|
||||||
|
|
||||||
export const greaterCommonDivision = (a, b) => (!b ? a : greaterCommonDivision(b, a % b));
|
|
||||||
10
frontend/src/static/js/utils/helpers/math.ts
Executable file
10
frontend/src/static/js/utils/helpers/math.ts
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
export const isGt = (x: number, y: number) => x > y;
|
||||||
|
export const isZero = (x: number) => 0 === x;
|
||||||
|
export const isNumber = (x: number) => 'number' === typeof x && !Number.isNaN(x);
|
||||||
|
export const isInteger = (x: number) => x === Math.trunc(x);
|
||||||
|
export const isPositive = (x: number) => isGt(x, 0);
|
||||||
|
export const isPositiveNumber = (x: number) => isNumber(x) && isPositive(x);
|
||||||
|
export const isPositiveInteger = (x: number) => isInteger(x) && isPositive(x);
|
||||||
|
export const isPositiveIntegerOrZero = (x: number) => isInteger(x) && (isPositive(x) || isZero(x));
|
||||||
|
|
||||||
|
export const greaterCommonDivision = (a: number, b: number): number => (!b ? a : greaterCommonDivision(b, a % b));
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { logErrorAndReturnError } from './errors';
|
import { logErrorAndReturnError } from './errors';
|
||||||
|
import { isPositiveInteger, isPositiveIntegerOrZero } from './math';
|
||||||
|
|
||||||
|
// @todo: Check this
|
||||||
export const PositiveIntegerOrZero = (function () {
|
export const PositiveIntegerOrZero = (function () {
|
||||||
const isPositiveIntegerOrZero = (x) => x === Math.trunc(x) && x >= 0;
|
return function (obj: Record<string, number>, key: string, comp: string) {
|
||||||
|
return obj[key] === undefined || isPositiveIntegerOrZero(obj[key])
|
||||||
return function (obj, key, comp) {
|
|
||||||
return void 0 === obj[key] || isPositiveIntegerOrZero(obj[key])
|
|
||||||
? null
|
? null
|
||||||
: logErrorAndReturnError([
|
: logErrorAndReturnError([
|
||||||
'Invalid prop `' +
|
'Invalid prop `' +
|
||||||
@@ -20,11 +20,10 @@ export const PositiveIntegerOrZero = (function () {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// @todo: Check this
|
||||||
export const PositiveInteger = (function () {
|
export const PositiveInteger = (function () {
|
||||||
const isPositiveInteger = (x) => x === Math.trunc(x) && x > 0;
|
return function (obj: Record<string, number>, key: string, comp: string) {
|
||||||
|
return obj[key] === undefined || isPositiveInteger(obj[key])
|
||||||
return function (obj, key, comp) {
|
|
||||||
return void 0 === obj[key] || isPositiveInteger(obj[key])
|
|
||||||
? null
|
? null
|
||||||
: logErrorAndReturnError([
|
: logErrorAndReturnError([
|
||||||
'Invalid prop `' +
|
'Invalid prop `' +
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { months } from '../constants';
|
|
||||||
|
|
||||||
export default function publishedOnDate(date, type) {
|
|
||||||
if (date instanceof Date) {
|
|
||||||
type = 0 + type;
|
|
||||||
type = 0 < type ? type : 1;
|
|
||||||
switch (type) {
|
|
||||||
case 1:
|
|
||||||
return months[date.getMonth()].substring(0, 3) + ' ' + date.getDate() + ', ' + date.getFullYear();
|
|
||||||
case 2:
|
|
||||||
return date.getDate() + ' ' + months[date.getMonth()].substring(0, 3) + ' ' + date.getFullYear();
|
|
||||||
case 3:
|
|
||||||
return date.getDate() + ' ' + months[date.getMonth()] + ' ' + date.getFullYear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
17
frontend/src/static/js/utils/helpers/publishedOnDate.ts
Executable file
17
frontend/src/static/js/utils/helpers/publishedOnDate.ts
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
import { months } from '../constants';
|
||||||
|
|
||||||
|
export function publishedOnDate(date: Date, type: 1 | 2 | 3 = 1) {
|
||||||
|
if (!(date instanceof Date)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 2) {
|
||||||
|
return date.getDate() + ' ' + months[date.getMonth()].substring(0, 3) + ' ' + date.getFullYear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 3) {
|
||||||
|
return date.getDate() + ' ' + months[date.getMonth()] + ' ' + date.getFullYear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return months[date.getMonth()].substring(0, 3) + ' ' + date.getDate() + ', ' + date.getFullYear();
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
function swap(arr, i, j) {
|
|
||||||
var temp = arr[i];
|
|
||||||
arr[i] = arr[j];
|
|
||||||
arr[j] = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
function partition(arr, pivot, left, right) {
|
|
||||||
var pivotValue = arr[pivot],
|
|
||||||
partitionIndex = left;
|
|
||||||
|
|
||||||
for (var i = left; i < right; i++) {
|
|
||||||
if (arr[i] < pivotValue) {
|
|
||||||
swap(arr, i, partitionIndex);
|
|
||||||
partitionIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
swap(arr, right, partitionIndex);
|
|
||||||
return partitionIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function quickSort(arr, left, right) {
|
|
||||||
var len = arr.length,
|
|
||||||
pivot,
|
|
||||||
partitionIndex;
|
|
||||||
|
|
||||||
if (left < right) {
|
|
||||||
pivot = right;
|
|
||||||
partitionIndex = partition(arr, pivot, left, right);
|
|
||||||
|
|
||||||
//sort left and right
|
|
||||||
quickSort(arr, left, partitionIndex - 1);
|
|
||||||
quickSort(arr, partitionIndex + 1, right);
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
29
frontend/src/static/js/utils/helpers/quickSort.ts
Executable file
29
frontend/src/static/js/utils/helpers/quickSort.ts
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
function swap(arr: unknown[], i: number, j: number) {
|
||||||
|
const temp = arr[i];
|
||||||
|
arr[i] = arr[j];
|
||||||
|
arr[j] = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function partition(arr: number[], pivot: number, left: number, right: number) {
|
||||||
|
const pivotValue = arr[pivot];
|
||||||
|
let partitionIndex = left;
|
||||||
|
for (let i = left; i < right; i++) {
|
||||||
|
if (arr[i] < pivotValue) {
|
||||||
|
swap(arr, i, partitionIndex);
|
||||||
|
partitionIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
swap(arr, right, partitionIndex);
|
||||||
|
return partitionIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function quickSort(arr: number[], left: number, right: number) {
|
||||||
|
if (left < right) {
|
||||||
|
const pivot = right;
|
||||||
|
const partitionIndex = partition(arr, pivot, left, right);
|
||||||
|
//sort left and right
|
||||||
|
quickSort(arr, left, partitionIndex - 1);
|
||||||
|
quickSort(arr, partitionIndex + 1, right);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// check templates/config/installation/translations.html for more
|
|
||||||
|
|
||||||
export function replaceString(word) {
|
|
||||||
if (!window.REPLACEMENTS) {
|
|
||||||
return word;
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = word;
|
|
||||||
|
|
||||||
for (const [search, replacement] of Object.entries(window.REPLACEMENTS)) {
|
|
||||||
result = result.split(search).join(replacement);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
47
frontend/src/static/js/utils/helpers/replacementStrings.ts
Normal file
47
frontend/src/static/js/utils/helpers/replacementStrings.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// check templates/config/installation/translations.html for more
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
REPLACEMENTS?: Record<string, string>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function replaceString(word: string) {
|
||||||
|
if (!window.REPLACEMENTS) {
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = word;
|
||||||
|
|
||||||
|
for (const [search, replacement] of Object.entries(window.REPLACEMENTS)) {
|
||||||
|
result = result.split(search).join(replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo: Check this alterative.
|
||||||
|
/*function replaceStringRegExp(word: string) {
|
||||||
|
if (!window.REPLACEMENTS) {
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
|
||||||
|
let result = word;
|
||||||
|
|
||||||
|
for (const [search, replacement] of Object.entries(window.REPLACEMENTS)) {
|
||||||
|
const regex = new RegExp(escapeRegExp(search), 'g');
|
||||||
|
result = result.replace(regex, replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// @todo: Remove older vesion.
|
||||||
|
/*export function replaceString_OLD(string: string) {
|
||||||
|
for (const key in window.REPLACEMENTS) {
|
||||||
|
string = string.replace(key, window.REPLACEMENTS[key]);
|
||||||
|
}
|
||||||
|
return string;
|
||||||
|
}*/
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
export async function getRequest(url, sync, callback, errorCallback) {
|
|
||||||
const requestConfig = {
|
|
||||||
timeout: null,
|
|
||||||
maxContentLength: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
function responseHandler(result) {
|
|
||||||
if (callback instanceof Function || typeof callback === 'function') {
|
|
||||||
callback(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function errorHandler(error) {
|
|
||||||
if (errorCallback instanceof Function || typeof errorCallback === 'function') {
|
|
||||||
let err = error;
|
|
||||||
if (void 0 === error.response) {
|
|
||||||
err = {
|
|
||||||
type: 'network',
|
|
||||||
error: error,
|
|
||||||
};
|
|
||||||
} else if (void 0 !== error.response.status) {
|
|
||||||
// TODO: Improve this, it's valid only in case of media requests.
|
|
||||||
switch (error.response.status) {
|
|
||||||
case 401:
|
|
||||||
err = {
|
|
||||||
type: 'private',
|
|
||||||
error: error,
|
|
||||||
message: 'Media is private',
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 400:
|
|
||||||
err = {
|
|
||||||
type: 'unavailable',
|
|
||||||
error: error,
|
|
||||||
message: 'Media is unavailable',
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
errorCallback(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sync) {
|
|
||||||
await axios.get(url, requestConfig)
|
|
||||||
.then(responseHandler)
|
|
||||||
.catch(errorHandler || null);
|
|
||||||
} else {
|
|
||||||
axios.get(url, requestConfig)
|
|
||||||
.then(responseHandler)
|
|
||||||
.catch(errorHandler || null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function postRequest(url, postData, configData, sync, callback, errorCallback) {
|
|
||||||
postData = postData || {};
|
|
||||||
|
|
||||||
function responseHandler(result) {
|
|
||||||
if (callback instanceof Function || typeof callback === 'function') {
|
|
||||||
callback(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function errorHandler(error) {
|
|
||||||
if (errorCallback instanceof Function || typeof errorCallback === 'function') {
|
|
||||||
errorCallback(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sync) {
|
|
||||||
await axios.post(url, postData, configData || null)
|
|
||||||
.then(responseHandler)
|
|
||||||
.catch(errorHandler || null);
|
|
||||||
} else {
|
|
||||||
axios.post(url, postData, configData || null)
|
|
||||||
.then(responseHandler)
|
|
||||||
.catch(errorHandler || null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function putRequest(url, putData, configData, sync, callback, errorCallback) {
|
|
||||||
putData = putData || {};
|
|
||||||
|
|
||||||
function responseHandler(result) {
|
|
||||||
if (callback instanceof Function || typeof callback === 'function') {
|
|
||||||
callback(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function errorHandler(error) {
|
|
||||||
if (errorCallback instanceof Function || typeof errorCallback === 'function') {
|
|
||||||
errorCallback(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sync) {
|
|
||||||
await axios.put(url, putData, configData || null)
|
|
||||||
.then(responseHandler)
|
|
||||||
.catch(errorHandler || null);
|
|
||||||
} else {
|
|
||||||
axios.put(url, putData, configData || null)
|
|
||||||
.then(responseHandler)
|
|
||||||
.catch(errorHandler || null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteRequest(url, configData, sync, callback, errorCallback) {
|
|
||||||
configData = configData || {};
|
|
||||||
|
|
||||||
function responseHandler(result) {
|
|
||||||
if (callback instanceof Function || typeof callback === 'function') {
|
|
||||||
callback(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function errorHandler(error) {
|
|
||||||
if (errorCallback instanceof Function || typeof errorCallback === 'function') {
|
|
||||||
errorCallback(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sync) {
|
|
||||||
await axios
|
|
||||||
.delete(url, configData || null)
|
|
||||||
.then(responseHandler)
|
|
||||||
.catch(errorHandler || null);
|
|
||||||
} else {
|
|
||||||
axios
|
|
||||||
.delete(url, configData || null)
|
|
||||||
.then(responseHandler)
|
|
||||||
.catch(errorHandler || null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
169
frontend/src/static/js/utils/helpers/requests.ts
Normal file
169
frontend/src/static/js/utils/helpers/requests.ts
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||||
|
|
||||||
|
export async function getRequest(
|
||||||
|
url: string,
|
||||||
|
sync: boolean = false,
|
||||||
|
callback?: (response: AxiosResponse<any, any, {}>) => void,
|
||||||
|
errorCallback?: (err: any) => void
|
||||||
|
) {
|
||||||
|
const requestConfig = {
|
||||||
|
timeout: undefined,
|
||||||
|
maxContentLength: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
function responseHandler(result: AxiosResponse<any, any, {}>) {
|
||||||
|
if (callback) {
|
||||||
|
callback(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorHandler(reason: any) {
|
||||||
|
if (!errorCallback) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let err = reason;
|
||||||
|
if (reason.response === undefined) {
|
||||||
|
err = {
|
||||||
|
type: 'network',
|
||||||
|
error: reason,
|
||||||
|
};
|
||||||
|
} else if (reason.response.status !== undefined) {
|
||||||
|
// @todo: Improve this, it's valid only in case of media requests.
|
||||||
|
switch (reason.response.status) {
|
||||||
|
case 401:
|
||||||
|
err = {
|
||||||
|
type: 'private',
|
||||||
|
error: reason,
|
||||||
|
message: 'Media is private',
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 400:
|
||||||
|
err = {
|
||||||
|
type: 'unavailable',
|
||||||
|
error: reason,
|
||||||
|
message: 'Media is unavailable',
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorCallback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sync) {
|
||||||
|
await axios
|
||||||
|
.get(url, requestConfig)
|
||||||
|
.then(responseHandler)
|
||||||
|
.catch(errorHandler || null);
|
||||||
|
} else {
|
||||||
|
axios
|
||||||
|
.get(url, requestConfig)
|
||||||
|
.then(responseHandler)
|
||||||
|
.catch(errorHandler || null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postRequest(
|
||||||
|
url: string,
|
||||||
|
postData: any,
|
||||||
|
configData?: AxiosRequestConfig<any>,
|
||||||
|
sync: boolean = false,
|
||||||
|
callback?: (response: AxiosResponse<any, any, {}>) => void,
|
||||||
|
errorCallback?: (error: any) => void
|
||||||
|
) {
|
||||||
|
postData = postData || {};
|
||||||
|
|
||||||
|
function responseHandler(result: AxiosResponse<any, any, {}>) {
|
||||||
|
if (callback) {
|
||||||
|
callback(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorHandler(error: any) {
|
||||||
|
if (errorCallback) {
|
||||||
|
errorCallback(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sync) {
|
||||||
|
await axios
|
||||||
|
.post(url, postData, configData)
|
||||||
|
.then(responseHandler)
|
||||||
|
.catch(errorHandler || null);
|
||||||
|
} else {
|
||||||
|
axios
|
||||||
|
.post(url, postData, configData)
|
||||||
|
.then(responseHandler)
|
||||||
|
.catch(errorHandler || null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function putRequest(
|
||||||
|
url: string,
|
||||||
|
putData: any,
|
||||||
|
configData?: AxiosRequestConfig<any>,
|
||||||
|
sync: boolean = false,
|
||||||
|
callback?: (response: AxiosResponse<any, any, {}>) => void,
|
||||||
|
errorCallback?: (error: any) => void
|
||||||
|
) {
|
||||||
|
putData = putData || {};
|
||||||
|
|
||||||
|
function responseHandler(result: AxiosResponse<any, any, {}>) {
|
||||||
|
if (callback) {
|
||||||
|
callback(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorHandler(error: any) {
|
||||||
|
if (errorCallback) {
|
||||||
|
errorCallback(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sync) {
|
||||||
|
await axios
|
||||||
|
.put(url, putData, configData)
|
||||||
|
.then(responseHandler)
|
||||||
|
.catch(errorHandler || null);
|
||||||
|
} else {
|
||||||
|
axios
|
||||||
|
.put(url, putData, configData)
|
||||||
|
.then(responseHandler)
|
||||||
|
.catch(errorHandler || null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteRequest(
|
||||||
|
url: string,
|
||||||
|
configData?: AxiosRequestConfig<any>,
|
||||||
|
sync: boolean = false,
|
||||||
|
callback?: (response: AxiosResponse<any, any, {}>) => void,
|
||||||
|
errorCallback?: (error: any) => void
|
||||||
|
) {
|
||||||
|
configData = configData || {};
|
||||||
|
|
||||||
|
function responseHandler(result: AxiosResponse<any, any, {}>) {
|
||||||
|
if (callback) {
|
||||||
|
callback(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorHandler(error: any) {
|
||||||
|
if (errorCallback) {
|
||||||
|
errorCallback(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sync) {
|
||||||
|
await axios
|
||||||
|
.delete(url, configData)
|
||||||
|
.then(responseHandler)
|
||||||
|
.catch(errorHandler || null);
|
||||||
|
} else {
|
||||||
|
axios
|
||||||
|
.delete(url, configData || null)
|
||||||
|
.then(responseHandler)
|
||||||
|
.catch(errorHandler || null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// check templates/config/installation/translations.html for more
|
|
||||||
|
|
||||||
export function translateString(str) {
|
|
||||||
return window.TRANSLATION?.[str] ?? str;
|
|
||||||
}
|
|
||||||
11
frontend/src/static/js/utils/helpers/translate.ts
Normal file
11
frontend/src/static/js/utils/helpers/translate.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// check templates/config/installation/translations.html for more
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
TRANSLATION?: Record<string, string>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function translateString(word: string) {
|
||||||
|
return window.TRANSLATION?.[word] ?? word;
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
BrowserEvents,
|
BrowserEvents,
|
||||||
} from '../../../src/static/js/utils/helpers/dom';
|
} from '../../../src/static/js/utils/helpers/dom';
|
||||||
|
|
||||||
|
const domModulePath = '../../../src/static/js/utils/helpers/dom';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
mozRequestAnimationFrame?: Window['requestAnimationFrame'];
|
mozRequestAnimationFrame?: Window['requestAnimationFrame'];
|
||||||
@@ -15,7 +17,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('js/utils/helpers', () => {
|
describe('utils/helpers', () => {
|
||||||
describe('dom', () => {
|
describe('dom', () => {
|
||||||
describe('supportsSvgAsImg', () => {
|
describe('supportsSvgAsImg', () => {
|
||||||
test('Delegates to document.implementation.hasFeature', () => {
|
test('Delegates to document.implementation.hasFeature', () => {
|
||||||
@@ -78,7 +80,7 @@ describe('js/utils/helpers', () => {
|
|||||||
test('Does not register non-function callbacks', () => {
|
test('Does not register non-function callbacks', () => {
|
||||||
const be = BrowserEvents();
|
const be = BrowserEvents();
|
||||||
|
|
||||||
be.win('not-a-fn', null);
|
be.win('not-a-fn' as unknown as Function, null as unknown as Function);
|
||||||
be.doc(undefined);
|
be.doc(undefined);
|
||||||
|
|
||||||
// Should still have registered the listeners on construction
|
// Should still have registered the listeners on construction
|
||||||
@@ -152,8 +154,8 @@ describe('js/utils/helpers', () => {
|
|||||||
test('Ignores non-function values without throwing and still registers listeners once', () => {
|
test('Ignores non-function values without throwing and still registers listeners once', () => {
|
||||||
const be = BrowserEvents();
|
const be = BrowserEvents();
|
||||||
|
|
||||||
be.doc('noop');
|
be.doc('noop' as unknown as Function);
|
||||||
be.win(null, undefined);
|
be.win(null as unknown as Function, undefined);
|
||||||
|
|
||||||
const docCount = (document.addEventListener as jest.Mock).mock.calls.filter(
|
const docCount = (document.addEventListener as jest.Mock).mock.calls.filter(
|
||||||
(c) => c[0] === 'visibilitychange'
|
(c) => c[0] === 'visibilitychange'
|
||||||
@@ -216,5 +218,64 @@ describe('js/utils/helpers', () => {
|
|||||||
expect(hasClassname(el, 'two-three')).toBe(true);
|
expect(hasClassname(el, 'two-three')).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Animation frame helpers', () => {
|
||||||
|
const requestAnimationFrameDescriptor = Object.getOwnPropertyDescriptor(window, 'requestAnimationFrame');
|
||||||
|
const cancelAnimationFrameDescriptor = Object.getOwnPropertyDescriptor(window, 'cancelAnimationFrame');
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (requestAnimationFrameDescriptor) {
|
||||||
|
Object.defineProperty(window, 'requestAnimationFrame', requestAnimationFrameDescriptor);
|
||||||
|
} else {
|
||||||
|
delete (window as Partial<Window>).requestAnimationFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancelAnimationFrameDescriptor) {
|
||||||
|
Object.defineProperty(window, 'cancelAnimationFrame', cancelAnimationFrameDescriptor);
|
||||||
|
} else {
|
||||||
|
delete (window as Partial<Window>).cancelAnimationFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.resetModules();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('requestAnimationFrame export is directly callable and delegates to window API', () => {
|
||||||
|
const requestAnimationFrameMock = jest.fn((callback: FrameRequestCallback) => {
|
||||||
|
callback(123);
|
||||||
|
return 7;
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(window, 'requestAnimationFrame', {
|
||||||
|
configurable: true,
|
||||||
|
value: requestAnimationFrameMock,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.resetModules();
|
||||||
|
const domHelpers = require(domModulePath) as typeof import('../../../src/static/js/utils/helpers/dom');
|
||||||
|
const callback = jest.fn();
|
||||||
|
const frameId = domHelpers.requestAnimationFrame(callback);
|
||||||
|
|
||||||
|
expect(frameId).toBe(7);
|
||||||
|
expect(requestAnimationFrameMock).toHaveBeenCalledWith(callback);
|
||||||
|
expect(callback).toHaveBeenCalledWith(123);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cancelAnimationFrame export is directly callable and delegates to window API', () => {
|
||||||
|
const cancelAnimationFrameMock = jest.fn();
|
||||||
|
|
||||||
|
Object.defineProperty(window, 'cancelAnimationFrame', {
|
||||||
|
configurable: true,
|
||||||
|
value: cancelAnimationFrameMock,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.resetModules();
|
||||||
|
const domHelpers = require(domModulePath) as typeof import('../../../src/static/js/utils/helpers/dom');
|
||||||
|
|
||||||
|
domHelpers.cancelAnimationFrame(42);
|
||||||
|
expect(cancelAnimationFrameMock).toHaveBeenCalledWith(42);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,47 +1,49 @@
|
|||||||
// Mock the './log' module used by errors.ts to capture calls without console side effects
|
import { logErrorAndReturnError, logWarningAndReturnError } from '../../../src/static/js/utils/helpers';
|
||||||
jest.mock('../../../src/static/js/utils/helpers/log', () => ({ error: jest.fn(), warn: jest.fn() }));
|
|
||||||
|
// Mock the './log' module used by errors.ts to capture calls without console side effects
|
||||||
|
jest.mock('../../../src/static/js/utils/helpers/log', () => ({
|
||||||
|
error: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
import { logErrorAndReturnError, logWarningAndReturnError } from '../../../src/static/js/utils/helpers/errors';
|
|
||||||
import { error as mockedError, warn as mockedWarn } from '../../../src/static/js/utils/helpers/log';
|
import { error as mockedError, warn as mockedWarn } from '../../../src/static/js/utils/helpers/log';
|
||||||
|
|
||||||
describe('js/utils/helpers', () => {
|
describe('utils/helpers', () => {
|
||||||
describe('errors', () => {
|
describe('errors', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('logErrorAndReturnError returns Error with first message and logs with error', () => {
|
test('logErrorAndReturnError returns Error with first message and logs with error', () => {
|
||||||
const messages = ['Primary msg', 'details', 'more'];
|
const messages = ['Primary error', 'details', 'more'];
|
||||||
const err = logErrorAndReturnError(messages);
|
const err = logErrorAndReturnError(messages);
|
||||||
expect(err).toBeInstanceOf(Error);
|
expect(err).toBeInstanceOf(Error);
|
||||||
expect(err.message).toBe('Primary msg');
|
expect(err.message).toBe('Primary error');
|
||||||
expect(mockedError).toHaveBeenCalledTimes(1);
|
expect(mockedError).toHaveBeenCalledTimes(1);
|
||||||
expect(mockedError).toHaveBeenCalledWith(...messages);
|
expect(mockedError).toHaveBeenCalledWith(...messages);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('logWarningAndReturnError returns Error with first message and logs with warn', () => {
|
test('logWarningAndReturnError returns Error with first message and logs with warn', () => {
|
||||||
const messages = ['Primary msg', 'details', 'more'];
|
const messages = ['Warn msg', 'context'];
|
||||||
const err = logWarningAndReturnError(messages);
|
const err = logWarningAndReturnError(messages);
|
||||||
expect(err).toBeInstanceOf(Error);
|
expect(err).toBeInstanceOf(Error);
|
||||||
expect(err.message).toBe('Primary msg');
|
expect(err.message).toBe('Warn msg');
|
||||||
expect(mockedWarn).toHaveBeenCalledTimes(1);
|
expect(mockedWarn).toHaveBeenCalledTimes(1);
|
||||||
expect(mockedWarn).toHaveBeenCalledWith(...messages);
|
expect(mockedWarn).toHaveBeenCalledWith(...messages);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Handles empty array creating an Error with undefined message and logs called with no args', () => {
|
test('handles empty array creating an Error with undefined message and logs called with no args', () => {
|
||||||
const messages: string[] = [];
|
const messages: string[] = [];
|
||||||
|
|
||||||
const err1 = logErrorAndReturnError(messages);
|
const err1 = logErrorAndReturnError(messages);
|
||||||
expect(err1).toBeInstanceOf(Error);
|
expect(err1).toBeInstanceOf(Error);
|
||||||
expect(err1.message).toBe('');
|
expect(err1.message).toBe('');
|
||||||
expect(mockedError).toHaveBeenCalledWith('');
|
expect(mockedError).toHaveBeenCalledWith(...messages);
|
||||||
|
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
const err2 = logWarningAndReturnError(messages);
|
const err2 = logWarningAndReturnError(messages);
|
||||||
expect(err2).toBeInstanceOf(Error);
|
expect(err2).toBeInstanceOf(Error);
|
||||||
expect(err2.message).toBe('');
|
expect(err2.message).toBe('');
|
||||||
expect(mockedWarn).toHaveBeenCalledWith('');
|
expect(mockedWarn).toHaveBeenCalledWith(...messages);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,44 +1,96 @@
|
|||||||
// Mock the dispatcher module used by exportStore
|
import EventEmitter from 'events';
|
||||||
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ register: jest.fn() }));
|
import { exportStore } from '../../../src/static/js/utils/helpers';
|
||||||
|
|
||||||
import exportStore from '../../../src/static/js/utils/helpers/exportStore';
|
// The dispatcher is an external module dependency used by exportStore; mock it to observe registrations
|
||||||
|
jest.mock('../../../src/static/js/utils/dispatcher', () => ({
|
||||||
|
dispatcher: {
|
||||||
|
register: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
// Re-import the mocked dispatcher for assertions
|
import { dispatcher } from '../../../src/static/js/utils/dispatcher';
|
||||||
import * as dispatcher from '../../../src/static/js/utils/dispatcher';
|
|
||||||
|
|
||||||
describe('js/utils/helpers', () => {
|
/**
|
||||||
|
* Behaviors covered:
|
||||||
|
* 1. Binds the provided handler method to store instance context.
|
||||||
|
* 2. Registers the bound callback exactly once with the dispatcher.
|
||||||
|
* 3. Returns the same store instance that was provided.
|
||||||
|
* 4. Invoking the registered callback forwards payload to the handler with correct this.
|
||||||
|
* 5. Type-safety assumption: only function keys are accepted as handler (runtime sanity via mock class).
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe('utils/helpers', () => {
|
||||||
describe('exportStore', () => {
|
describe('exportStore', () => {
|
||||||
|
class TestStore extends (EventEmitter as { new (): EventEmitter }) {
|
||||||
|
public calls: unknown[] = [];
|
||||||
|
public handler(payload: unknown) {
|
||||||
|
// Assert `this` is the store instance when called via bound function
|
||||||
|
this.calls.push({ self: this, payload });
|
||||||
|
}
|
||||||
|
public otherHandler(payload: unknown) {
|
||||||
|
this.calls.push({ self: this, payload, type: 'other' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Registers store handler with dispatcher and binds context', () => {
|
test('Returns the same store instance and registers exactly once', () => {
|
||||||
const ctx: { value: number; inc?: () => void } = { value: 0 };
|
const store = new TestStore();
|
||||||
const handlerName = 'inc';
|
const returned = exportStore(store as unknown as EventEmitter, 'handler' as any);
|
||||||
const handler = function (this: typeof ctx) {
|
expect(returned).toBe(store);
|
||||||
this.value += 1;
|
expect(dispatcher.register).toHaveBeenCalledTimes(1);
|
||||||
};
|
expect(typeof (dispatcher.register as jest.Mock).mock.calls[0][0]).toBe('function');
|
||||||
ctx[handlerName] = handler as any;
|
|
||||||
|
|
||||||
const result = exportStore(ctx, handlerName);
|
|
||||||
|
|
||||||
// returns the same store instance
|
|
||||||
expect(result).toBe(ctx);
|
|
||||||
|
|
||||||
// Ensure dispatcher.register was called once with a bound function
|
|
||||||
expect((dispatcher as any).register).toHaveBeenCalledTimes(1);
|
|
||||||
const registeredFn = (dispatcher as any).register.mock.calls[0][0] as Function;
|
|
||||||
expect(typeof registeredFn).toBe('function');
|
|
||||||
|
|
||||||
// Verify the registered function is bound to the store context
|
|
||||||
registeredFn();
|
|
||||||
expect(ctx.value).toBe(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Throws if handler name does not exist on store', () => {
|
test('Binds the handler to the store instance context', () => {
|
||||||
const store: any = {};
|
const store = new TestStore();
|
||||||
// Accessing store[handler] would be undefined; calling .bind on undefined would throw
|
exportStore(store as unknown as EventEmitter, 'handler' as any);
|
||||||
expect(() => exportStore(store, 'missing')).toThrow();
|
|
||||||
|
const registered = (dispatcher.register as jest.Mock).mock.calls[0][0] as (p: unknown) => void;
|
||||||
|
const payload = { a: 1 };
|
||||||
|
registered(payload);
|
||||||
|
|
||||||
|
expect(store.calls).toHaveLength(1);
|
||||||
|
const { self, payload: received } = store.calls[0] as any;
|
||||||
|
expect(self).toBe(store);
|
||||||
|
expect(received).toBe(payload);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Forwards any payload through the registered callback to the handler', () => {
|
||||||
|
const store = new TestStore();
|
||||||
|
exportStore(store as unknown as EventEmitter, 'otherHandler' as any);
|
||||||
|
|
||||||
|
const registered = (dispatcher.register as jest.Mock).mock.calls[0][0] as (p: unknown) => void;
|
||||||
|
registered(null);
|
||||||
|
registered(42);
|
||||||
|
registered({ x: 'y' });
|
||||||
|
|
||||||
|
expect(store.calls.map((c: any) => c.payload)).toEqual([null, 42, { x: 'y' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Supports different handler keys of the same store', () => {
|
||||||
|
const store = new TestStore();
|
||||||
|
exportStore(store as unknown as EventEmitter, 'handler' as any);
|
||||||
|
exportStore(store as unknown as EventEmitter, 'otherHandler' as any);
|
||||||
|
|
||||||
|
expect(dispatcher.register).toHaveBeenCalledTimes(2);
|
||||||
|
const cb1 = (dispatcher.register as jest.Mock).mock.calls[0][0] as (p: unknown) => void;
|
||||||
|
const cb2 = (dispatcher.register as jest.Mock).mock.calls[1][0] as (p: unknown) => void;
|
||||||
|
|
||||||
|
cb1('a');
|
||||||
|
cb2('b');
|
||||||
|
|
||||||
|
expect(store.calls).toHaveLength(2);
|
||||||
|
expect(store.calls[0]).toMatchObject({ payload: 'a' });
|
||||||
|
expect(store.calls[1]).toMatchObject({ payload: 'b', type: 'other' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Runtime guard scenario: non-existing handler results in TypeError on bind access', () => {
|
||||||
|
const store = new TestStore();
|
||||||
|
// @ts-expect-error intentionally passing wrong key to simulate runtime misuse
|
||||||
|
expect(() => exportStore(store as unknown as EventEmitter, 'notAHandler')).toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import formatViewsNumber from '../../../src/static/js/utils/helpers/formatViewsNumber';
|
import { formatViewsNumber } from '../../../src/static/js/utils/helpers';
|
||||||
|
|
||||||
describe('js/utils/helpers', () => {
|
describe('js/utils/helpers', () => {
|
||||||
describe('formatViewsNumber', () => {
|
describe('formatViewsNumber', () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import publishedOnDate from '../../../src/static/js/utils/helpers/publishedOnDate';
|
import { publishedOnDate } from '../../../src/static/js/utils/helpers';
|
||||||
|
|
||||||
// Helper to create Date in UTC to avoid timezone issues in CI environments
|
// Helper to create Date in UTC to avoid timezone issues in CI environments
|
||||||
const makeDate = (y: number, mZeroBased: number, d: number) => new Date(Date.UTC(y, mZeroBased, d));
|
const makeDate = (y: number, mZeroBased: number, d: number) => new Date(Date.UTC(y, mZeroBased, d));
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ jest.mock('axios');
|
|||||||
|
|
||||||
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
||||||
|
|
||||||
describe('js/utils/helpers', () => {
|
describe('utils/helpers', () => {
|
||||||
describe('requests', () => {
|
describe('requests', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
@@ -21,8 +21,8 @@ describe('js/utils/helpers', () => {
|
|||||||
getRequest(url, false, cb, undefined);
|
getRequest(url, false, cb, undefined);
|
||||||
|
|
||||||
expect(mockedAxios.get).toHaveBeenCalledWith(url, {
|
expect(mockedAxios.get).toHaveBeenCalledWith(url, {
|
||||||
timeout: null,
|
timeout: undefined,
|
||||||
maxContentLength: null,
|
maxContentLength: undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ describe('js/utils/helpers', () => {
|
|||||||
|
|
||||||
await postRequest(url, undefined as any, undefined as any, true, cb, undefined);
|
await postRequest(url, undefined as any, undefined as any, true, cb, undefined);
|
||||||
|
|
||||||
expect(mockedAxios.post).toHaveBeenCalledWith(url, {}, null);
|
expect(mockedAxios.post).toHaveBeenCalledWith(url, {}, undefined);
|
||||||
expect(cb).toHaveBeenCalled();
|
expect(cb).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ describe('js/utils/helpers', () => {
|
|||||||
|
|
||||||
await putRequest(url, undefined as any, undefined as any, true, undefined, undefined);
|
await putRequest(url, undefined as any, undefined as any, true, undefined, undefined);
|
||||||
|
|
||||||
expect(mockedAxios.put).toHaveBeenCalledWith(url, {}, null);
|
expect(mockedAxios.put).toHaveBeenCalledWith(url, {}, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Invokes errorCallback on error', async () => {
|
test('Invokes errorCallback on error', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user