Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 18:44:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 18:44:42 +0300
commit4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch)
tree5423a1c7516cffe36384133ade12572cf709398d /app/assets/javascripts/lib/utils
parente570267f2f6b326480d284e0164a6464ba4081bc (diff)
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'app/assets/javascripts/lib/utils')
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js3
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js2
-rw-r--r--app/assets/javascripts/lib/utils/keys.js1
-rw-r--r--app/assets/javascripts/lib/utils/number_utils.js10
-rw-r--r--app/assets/javascripts/lib/utils/recurrence.js154
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js2
-rw-r--r--app/assets/javascripts/lib/utils/uuids.js76
-rw-r--r--app/assets/javascripts/lib/utils/vuex_module_mappers.js91
8 files changed, 335 insertions, 4 deletions
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index fb257228597..8666d325c1b 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -645,9 +645,6 @@ export const convertObjectPropsToCamelCase = (obj = {}, options = {}) =>
export const convertObjectPropsToSnakeCase = (obj = {}, options = {}) =>
convertObjectProps(convertToSnakeCase, obj, options);
-export const imagePath = (imgUrl) =>
- `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/${imgUrl}`;
-
export const addSelectOnFocusBehaviour = (selector = '.js-select-on-focus') => {
// Click a .js-select-on-focus field, select the contents
// Prevent a mouseup event from deselecting the input
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index a509828815a..0a038febb9f 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -4,6 +4,8 @@ import { isString, mapValues, isNumber, reduce } from 'lodash';
import * as timeago from 'timeago.js';
import { languageCode, s__, __, n__ } from '../../locale';
+export const SECONDS_IN_DAY = 86400;
+
const DAYS_IN_WEEK = 7;
window.timeago = timeago;
diff --git a/app/assets/javascripts/lib/utils/keys.js b/app/assets/javascripts/lib/utils/keys.js
index 2a8b1759e54..bd47f10b3ac 100644
--- a/app/assets/javascripts/lib/utils/keys.js
+++ b/app/assets/javascripts/lib/utils/keys.js
@@ -1,2 +1,3 @@
export const ESC_KEY = 'Escape';
export const ENTER_KEY = 'Enter';
+export const BACKSPACE_KEY = 'Backspace';
diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js
index 63feb6f9b1d..e3500d02a79 100644
--- a/app/assets/javascripts/lib/utils/number_utils.js
+++ b/app/assets/javascripts/lib/utils/number_utils.js
@@ -171,3 +171,13 @@ export const formattedChangeInPercent = (firstY, lastY, { nonFiniteResult = '-'
export const isNumeric = (value) => {
return !Number.isNaN(parseInt(value, 10));
};
+
+const numberRegex = /^[0-9]+$/;
+
+/**
+ * Checks whether the value is a positive number or 0, or a string with equivalent value
+ *
+ * @param value
+ * @return {boolean}
+ */
+export const isPositiveInteger = (value) => numberRegex.test(value);
diff --git a/app/assets/javascripts/lib/utils/recurrence.js b/app/assets/javascripts/lib/utils/recurrence.js
new file mode 100644
index 00000000000..8fd26f3e393
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/recurrence.js
@@ -0,0 +1,154 @@
+import { uuids } from './uuids';
+
+/**
+ * @module recurrence
+ */
+
+const instances = {};
+
+/**
+ * Create a new unique {@link module:recurrence~RecurInstance|RecurInstance}
+ * @returns {module:recurrence.RecurInstance} The newly created {@link module:recurrence~RecurInstance|RecurInstance}
+ */
+export function create() {
+ const id = uuids()[0];
+ let handlers = {};
+ let count = 0;
+
+ /**
+ * @namespace RecurInstance
+ * @description A RecurInstance tracks the count of any occurrence as registered by calls to <code>occur</code>.
+ * <br /><br />
+ * It maintains an internal counter and a registry of handlers that can be arbitrarily assigned by a user.
+ * <br /><br />
+ * While a RecurInstance isn't specific to any particular use-case, it may be useful for:
+ * <br />
+ * <ul>
+ * <li>Tracking repeated errors across multiple - but not linked - network requests</li>
+ * <li>Tracking repeated user interactions (e.g. multiple clicks)</li>
+ * </ul>
+ * @summary A closure to track repeated occurrences of any arbitrary event.
+ * */
+ const instance = {
+ /**
+ * @type {module:uuids~UUIDv4}
+ * @description A randomly generated {@link module:uuids~UUIDv4|UUID} for this particular recurrence instance
+ * @memberof module:recurrence~RecurInstance
+ * @readonly
+ * @inner
+ */
+ get id() {
+ return id;
+ },
+ /**
+ * @type {Number}
+ * @description The number of times this particular instance of recurrence has been triggered
+ * @memberof module:recurrence~RecurInstance
+ * @readonly
+ * @inner
+ */
+ get count() {
+ return count;
+ },
+ /**
+ * @type {Object}
+ * @description The handlers assigned to this recurrence tracker
+ * @example
+ * myRecurrence.handle( 4, () => console.log( "four" ) );
+ * console.log( myRecurrence.handlers ); // {"4": () => console.log( "four" )}
+ * @memberof module:recurrence~RecurInstance
+ * @readonly
+ * @inner
+ */
+ get handlers() {
+ return handlers;
+ },
+ /**
+ * @type {Boolean}
+ * @description Delete any internal reference to the instance.
+ * <br />
+ * Keep in mind that this will only attempt to remove the <strong>internal</strong> reference.
+ * <br />
+ * If your code maintains a reference to the instance, the regular garbage collector will not free the memory.
+ * @memberof module:recurrence~RecurInstance
+ * @inner
+ */
+ free() {
+ return delete instances[id];
+ },
+ /**
+ * @description Register a handler to be called when this occurrence is seen <code>onCount</code> number of times.
+ * @param {Number} onCount - The number of times the occurrence has been seen to respond to
+ * @param {Function} behavior - A callback function to run when the occurrence has been seen <code>onCount</code> times
+ * @memberof module:recurrence~RecurInstance
+ * @inner
+ */
+ handle(onCount, behavior) {
+ if (onCount && behavior) {
+ handlers[onCount] = behavior;
+ }
+ },
+ /**
+ * @description Remove the behavior callback handler that would be run when the occurrence is seen <code>onCount</code> times
+ * @param {Number} onCount - The count identifier for which to eject the callback handler
+ * @memberof module:recurrence~RecurInstance
+ * @inner
+ */
+ eject(onCount) {
+ if (onCount) {
+ delete handlers[onCount];
+ }
+ },
+ /**
+ * @description Register that this occurrence has been seen and trigger any appropriate handlers
+ * @memberof module:recurrence~RecurInstance
+ * @inner
+ */
+ occur() {
+ count += 1;
+
+ if (typeof handlers[count] === 'function') {
+ handlers[count](count);
+ }
+ },
+ /**
+ * @description Reset this recurrence instance without destroying it entirely
+ * @param {Object} [options]
+ * @param {Boolean} [options.currentCount = true] - Whether to reset the count
+ * @param {Boolean} [options.handlersList = false] - Whether to reset the list of attached handlers back to an empty state
+ * @memberof module:recurrence~RecurInstance
+ * @inner
+ */
+ reset({ currentCount = true, handlersList = false } = {}) {
+ if (currentCount) {
+ count = 0;
+ }
+
+ if (handlersList) {
+ handlers = {};
+ }
+ },
+ };
+
+ instances[id] = instance;
+
+ return instance;
+}
+
+/**
+ * Retrieve a stored {@link module:recurrence~RecurInstance|RecurInstance} by {@link module:uuids~UUIDv4|UUID}
+ * @param {module:uuids~UUIDv4} id - The {@link module:uuids~UUIDv4|UUID} of a previously created {@link module:recurrence~RecurInstance|RecurInstance}
+ * @returns {(module:recurrence~RecurInstance|undefined)} The {@link module:recurrence~RecurInstance|RecurInstance}, or undefined if the UUID doesn't refer to a known Instance
+ */
+export function recall(id) {
+ return instances[id];
+}
+
+/**
+ * Release the memory space for a given {@link module:recurrence~RecurInstance|RecurInstance} by {@link module:uuids~UUIDv4|UUID}
+ * @param {module:uuids~UUIDv4} id - The {@link module:uuids~UUIDv4|UUID} of a previously created {@link module:recurrence~RecurInstance|RecurInstance}
+ * @returns {Boolean} Whether the reference to the stored {@link module:recurrence~RecurInstance|RecurInstance} was released
+ */
+export function free(id) {
+ return recall(id)?.free() || false;
+}
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 1593a363dd1..6ff2af47dd8 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -232,7 +232,7 @@ export function insertMarkdownText({
.join('\n');
}
} else if (tag.indexOf(textPlaceholder) > -1) {
- textToInsert = tag.replace(textPlaceholder, selected.replace(/\\n/g, '\n'));
+ textToInsert = tag.replace(textPlaceholder, () => selected.replace(/\\n/g, '\n'));
} else {
textToInsert = String(startChar) + tag + selected + (wrap ? tag : '');
}
diff --git a/app/assets/javascripts/lib/utils/uuids.js b/app/assets/javascripts/lib/utils/uuids.js
new file mode 100644
index 00000000000..98fe4bf9664
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/uuids.js
@@ -0,0 +1,76 @@
+/**
+ * @module uuids
+ */
+
+/**
+ * A string or number representing a start state for a random generator
+ * @typedef {(Number|String)} Seed
+ */
+/**
+ * A UUIDv4 string in the format <code>Hex{8}-Hex{4}-4Hex{3}-[89ab]Hex{3}-Hex{12}</code>
+ * @typedef {String} UUIDv4
+ */
+
+import { MersenneTwister } from 'fast-mersenne-twister';
+import { isString } from 'lodash';
+import stringHash from 'string-hash';
+import { v4 } from 'uuid';
+
+function getSeed(seeds) {
+ return seeds.reduce((seedling, seed, i) => {
+ let thisSeed = 0;
+
+ if (Number.isInteger(seed)) {
+ thisSeed = seed;
+ } else if (isString(seed)) {
+ thisSeed = stringHash(seed);
+ }
+
+ return seedling + (seeds.length - i) * thisSeed;
+ }, 0);
+}
+
+function getPseudoRandomNumberGenerator(...seeds) {
+ let seedNumber;
+
+ if (seeds.length) {
+ seedNumber = getSeed(seeds);
+ } else {
+ seedNumber = Math.floor(Math.random() * 10 ** 15);
+ }
+
+ return new MersenneTwister(seedNumber);
+}
+
+function randomValuesForUuid(prng) {
+ const randomValues = [];
+
+ for (let i = 0; i <= 3; i += 1) {
+ const buffer = new ArrayBuffer(4);
+ const view = new DataView(buffer);
+
+ view.setUint32(0, prng.randomNumber());
+
+ randomValues.push(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3));
+ }
+
+ return randomValues;
+}
+
+/**
+ * Get an array of UUIDv4s
+ * @param {Object} [options={}]
+ * @param {Seed[]} [options.seeds=[]] - A list of mixed strings or numbers to seed the UUIDv4 generator
+ * @param {Number} [options.count=1] - A total number of UUIDv4s to generate
+ * @returns {UUIDv4[]} An array of UUIDv4s
+ */
+export function uuids({ seeds = [], count = 1 } = {}) {
+ const rng = getPseudoRandomNumberGenerator(...seeds);
+ return (
+ // Create an array the same size as the number of UUIDs requested
+ Array(count)
+ .fill(0)
+ // Replace each slot in the array with a UUID which needs 16 (pseudo)random values to generate
+ .map(() => v4({ random: randomValuesForUuid(rng) }))
+ );
+}
diff --git a/app/assets/javascripts/lib/utils/vuex_module_mappers.js b/app/assets/javascripts/lib/utils/vuex_module_mappers.js
new file mode 100644
index 00000000000..95a794dd268
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/vuex_module_mappers.js
@@ -0,0 +1,91 @@
+import { mapValues, isString } from 'lodash';
+import { mapState, mapActions } from 'vuex';
+
+export const REQUIRE_STRING_ERROR_MESSAGE =
+ '`vuex_module_mappers` can only be used with an array of strings, or an object with string values. Consider using the regular `vuex` map helpers instead.';
+
+const normalizeFieldsToObject = (fields) => {
+ return Array.isArray(fields)
+ ? fields.reduce((acc, key) => Object.assign(acc, { [key]: key }), {})
+ : fields;
+};
+
+const mapVuexModuleFields = ({ namespaceSelector, fields, vuexHelper, selector } = {}) => {
+ // The `vuexHelper` needs an object which maps keys to field selector functions.
+ const map = mapValues(normalizeFieldsToObject(fields), (value) => {
+ if (!isString(value)) {
+ throw new Error(REQUIRE_STRING_ERROR_MESSAGE);
+ }
+
+ // We need to use a good ol' function to capture the right "this".
+ return function mappedFieldSelector(...args) {
+ const namespace = namespaceSelector(this);
+
+ return selector(namespace, value, ...args);
+ };
+ });
+
+ return vuexHelper(map);
+};
+
+/**
+ * Like `mapState`, but takes a function in the first param for selecting a namespace.
+ *
+ * ```
+ * computed: {
+ * ...mapVuexModuleState(vm => vm.vuexModule, ['foo']),
+ * }
+ * ```
+ *
+ * @param {Function} namespaceSelector
+ * @param {Array|Object} fields
+ */
+export const mapVuexModuleState = (namespaceSelector, fields) =>
+ mapVuexModuleFields({
+ namespaceSelector,
+ fields,
+ vuexHelper: mapState,
+ selector: (namespace, value, state) => state[namespace][value],
+ });
+
+/**
+ * Like `mapActions`, but takes a function in the first param for selecting a namespace.
+ *
+ * ```
+ * methods: {
+ * ...mapVuexModuleActions(vm => vm.vuexModule, ['fetchFoos']),
+ * }
+ * ```
+ *
+ * @param {Function} namespaceSelector
+ * @param {Array|Object} fields
+ */
+export const mapVuexModuleActions = (namespaceSelector, fields) =>
+ mapVuexModuleFields({
+ namespaceSelector,
+ fields,
+ vuexHelper: mapActions,
+ selector: (namespace, value, dispatch, ...args) => dispatch(`${namespace}/${value}`, ...args),
+ });
+
+/**
+ * Like `mapGetters`, but takes a function in the first param for selecting a namespace.
+ *
+ * ```
+ * computed: {
+ * ...mapGetters(vm => vm.vuexModule, ['hasSearchInfo']),
+ * }
+ * ```
+ *
+ * @param {Function} namespaceSelector
+ * @param {Array|Object} fields
+ */
+export const mapVuexModuleGetters = (namespaceSelector, fields) =>
+ mapVuexModuleFields({
+ namespaceSelector,
+ fields,
+ // `mapGetters` does not let us pass an object which maps to functions. Thankfully `mapState` does
+ // and gives us access to the getters.
+ vuexHelper: mapState,
+ selector: (namespace, value, state, getters) => getters[`${namespace}/${value}`],
+ });