summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/lib/utils
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/lib/utils')
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js58
-rw-r--r--app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue20
-rw-r--r--app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js4
-rw-r--r--app/assets/javascripts/lib/utils/cookies.js8
-rw-r--r--app/assets/javascripts/lib/utils/datetime/timeago_utility.js2
-rw-r--r--app/assets/javascripts/lib/utils/dom_utils.js14
-rw-r--r--app/assets/javascripts/lib/utils/http_status.js1
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js17
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js4
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js13
-rw-r--r--app/assets/javascripts/lib/utils/users_cache.js11
11 files changed, 133 insertions, 19 deletions
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 96d019f62f2..1ed0cc3130b 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -4,15 +4,15 @@
import { GlBreakpointInstance as breakpointInstance } from '@gitlab/ui/dist/utils';
import $ from 'jquery';
-import Cookies from 'js-cookie';
import { isFunction, defer } from 'lodash';
+import Cookies from '~/lib/utils/cookies';
import { SCOPED_LABEL_DELIMITER } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
import { convertToCamelCase, convertToSnakeCase } from './text_utility';
import { isObject } from './type_utility';
import { getLocationHash } from './url_utility';
export const getPagePath = (index = 0) => {
- const { page = '' } = document?.body?.dataset;
+ const { page = '' } = document.body.dataset;
return page.split(':')[index];
};
@@ -105,7 +105,7 @@ export const handleLocationHash = () => {
}
if (isInIssuePage()) {
- adjustment -= fixedIssuableTitle?.offsetHeight;
+ adjustment -= fixedIssuableTitle.offsetHeight;
}
if (isInMRPage()) {
@@ -157,7 +157,7 @@ export const contentTop = () => {
() => getOuterHeight('#js-peek'),
() => getOuterHeight('.navbar-gitlab'),
({ desktop }) => {
- const container = document.querySelector('.line-resolve-all-container');
+ const container = document.querySelector('.discussions-counter');
let size = 0;
if (!desktop && container) {
@@ -282,23 +282,51 @@ export const getSelectedFragment = (restrictToNode) => {
return documentFragment;
};
+function execInsertText(text) {
+ if (text === '') return document.execCommand('delete');
+
+ return document.execCommand('insertText', false, text);
+}
+
+/**
+ * This method inserts text into a textarea/input field.
+ * Uses `execCommand` if supported
+ *
+ * @param {HTMLElement} target - textarea/input to have text inserted into
+ * @param {String | function} text - text to be inserted
+ */
export const insertText = (target, text) => {
- // Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas
const { selectionStart, selectionEnd, value } = target;
-
const textBefore = value.substring(0, selectionStart);
const textAfter = value.substring(selectionEnd, value.length);
-
const insertedText = text instanceof Function ? text(textBefore, textAfter) : text;
- const newText = textBefore + insertedText + textAfter;
- // eslint-disable-next-line no-param-reassign
- target.value = newText;
- // eslint-disable-next-line no-param-reassign
- target.selectionStart = selectionStart + insertedText.length;
-
- // eslint-disable-next-line no-param-reassign
- target.selectionEnd = selectionStart + insertedText.length;
+ // The `execCommand` is officially deprecated. However, for `insertText`,
+ // there is currently no alternative. We need to use it in order to trigger
+ // the browser's undo tracking when we insert text.
+ // Per https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand on 2022-04-11,
+ // The Clipboard API can be used instead of execCommand in many cases,
+ // but execCommand is still sometimes useful. In particular, the Clipboard
+ // API doesn't replace the insertText command
+ // So we attempt to use it if possible. Otherwise, fall back to just replacing
+ // the value as before. In this case, Undo will be broken with inserted text.
+ // Testing on older versions of Firefox:
+ // 87 and below: does not work and falls through to just replacing value.
+ // 87 was released in Mar of 2021
+ // 89 and above: works well
+ // 89 was released in May of 2021
+ if (!execInsertText(insertedText)) {
+ const newText = textBefore + insertedText + textAfter;
+
+ // eslint-disable-next-line no-param-reassign
+ target.value = newText;
+
+ // eslint-disable-next-line no-param-reassign
+ target.selectionStart = selectionStart + insertedText.length;
+
+ // eslint-disable-next-line no-param-reassign
+ target.selectionEnd = selectionStart + insertedText.length;
+ }
// Trigger autosave
target.dispatchEvent(new Event('input'));
diff --git a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue
index 1d8eb73d3d7..3788d8ab20c 100644
--- a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue
+++ b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue
@@ -3,7 +3,6 @@ import { GlModal, GlSafeHtmlDirective } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
- cancelAction: { text: __('Cancel') },
directives: {
SafeHtml: GlSafeHtmlDirective,
},
@@ -36,6 +35,16 @@ export default {
required: false,
default: 'confirm',
},
+ cancelText: {
+ type: String,
+ required: false,
+ default: __('Cancel'),
+ },
+ cancelVariant: {
+ type: String,
+ required: false,
+ default: 'default',
+ },
modalHtmlMessage: {
type: String,
required: false,
@@ -71,7 +80,14 @@ export default {
};
},
cancelAction() {
- return this.hideCancel ? null : this.$options.cancelAction;
+ return this.hideCancel
+ ? null
+ : {
+ text: this.cancelText,
+ attributes: {
+ variant: this.cancelVariant,
+ },
+ };
},
shouldShowHeader() {
return Boolean(this.title?.length);
diff --git a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js
index 1adb6f9c26f..173116062c9 100644
--- a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js
+++ b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js
@@ -7,6 +7,8 @@ export function confirmAction(
primaryBtnText,
secondaryBtnVariant,
secondaryBtnText,
+ cancelBtnVariant,
+ cancelBtnText,
modalHtmlMessage,
title,
hideCancel,
@@ -28,6 +30,8 @@ export function confirmAction(
secondaryVariant: secondaryBtnVariant,
primaryVariant: primaryBtnVariant,
primaryText: primaryBtnText,
+ cancelVariant: cancelBtnVariant,
+ cancelText: cancelBtnText,
title,
modalHtmlMessage,
hideCancel,
diff --git a/app/assets/javascripts/lib/utils/cookies.js b/app/assets/javascripts/lib/utils/cookies.js
new file mode 100644
index 00000000000..be0491376c9
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/cookies.js
@@ -0,0 +1,8 @@
+import CookiesBuilder from 'js-cookie';
+
+// set default path for cookies
+const Cookies = CookiesBuilder.withAttributes({
+ path: gon.relative_url_root || '/',
+});
+
+export default Cookies;
diff --git a/app/assets/javascripts/lib/utils/datetime/timeago_utility.js b/app/assets/javascripts/lib/utils/datetime/timeago_utility.js
index 095a29a2eff..05f34db662a 100644
--- a/app/assets/javascripts/lib/utils/datetime/timeago_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime/timeago_utility.js
@@ -7,7 +7,7 @@ import { formatDate } from './date_format_utility';
*
* see https://github.com/hustcc/timeago.js/tree/v3.0.0/locales
*/
-const timeagoLanguageCode = languageCode().replace(/-/g, '_');
+export const timeagoLanguageCode = languageCode().replace(/-/g, '_');
/**
* Registers timeago locales
diff --git a/app/assets/javascripts/lib/utils/dom_utils.js b/app/assets/javascripts/lib/utils/dom_utils.js
index b52a736f153..4262329aae7 100644
--- a/app/assets/javascripts/lib/utils/dom_utils.js
+++ b/app/assets/javascripts/lib/utils/dom_utils.js
@@ -90,6 +90,20 @@ export const getParents = (element) => {
return parents;
};
+export const getParentByTagName = (element, tagName) => {
+ let parent = element.parentNode;
+
+ do {
+ if (parent.nodeName?.toLowerCase() === tagName?.toLowerCase()) {
+ return parent;
+ }
+
+ parent = parent.parentElement;
+ } while (parent);
+
+ return undefined;
+};
+
/**
* This method takes a HTML element and an object of attributes
* to save repeated calls to `setAttribute` when multiple
diff --git a/app/assets/javascripts/lib/utils/http_status.js b/app/assets/javascripts/lib/utils/http_status.js
index 6b9be34235b..c5190592bb6 100644
--- a/app/assets/javascripts/lib/utils/http_status.js
+++ b/app/assets/javascripts/lib/utils/http_status.js
@@ -22,6 +22,7 @@ const httpStatusCodes = {
METHOD_NOT_ALLOWED: 405,
CONFLICT: 409,
GONE: 410,
+ PAYLOAD_TOO_LARGE: 413,
UNPROCESSABLE_ENTITY: 422,
TOO_MANY_REQUESTS: 429,
INTERNAL_SERVER_ERROR: 500,
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 52fa90c7791..243de48948c 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -14,6 +14,8 @@ const LIST_LINE_HEAD_PATTERN = /^(?<indent>\s*)(?<leader>((?<isUl>[*+-])|(?<isOl
// detect a horizontal rule that might be mistaken for a list item (not full pattern for an <hr>)
const HR_PATTERN = /^((\s{0,3}-+\s*-+\s*-+\s*[\s-]*)|(\s{0,3}\*+\s*\*+\s*\*+\s*[\s*]*))$/;
+let compositioningNoteText = false;
+
function selectedText(text, textarea) {
return text.substring(textarea.selectionStart, textarea.selectionEnd);
}
@@ -363,10 +365,11 @@ function continueOlText(result, nextLineResult) {
}
function handleContinueList(e, textArea) {
- if (!gon.features?.markdownContinueLists) return;
if (!(e.key === 'Enter')) return;
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
if (textArea.selectionStart !== textArea.selectionEnd) return;
+ // prevent unintended line breaks were inserted using Japanese IME on MacOS
+ if (compositioningNoteText) return;
const currentLine = lineBefore(textArea.value, textArea, false);
const result = currentLine.match(LIST_LINE_HEAD_PATTERN);
@@ -420,6 +423,14 @@ export function keypressNoteText(e) {
handleSurroundSelectedText(e, textArea);
}
+export function compositionStartNoteText() {
+ compositioningNoteText = true;
+}
+
+export function compositionEndNoteText() {
+ compositioningNoteText = false;
+}
+
export function updateTextForToolbarBtn($toolbarBtn) {
return updateText({
textArea: $toolbarBtn.closest('.md-area').find('textarea'),
@@ -435,6 +446,8 @@ export function updateTextForToolbarBtn($toolbarBtn) {
export function addMarkdownListeners(form) {
$('.markdown-area', form)
.on('keydown', keypressNoteText)
+ .on('compositionstart', compositionStartNoteText)
+ .on('compositionend', compositionEndNoteText)
.each(function attachTextareaShortcutHandlers() {
Shortcuts.initMarkdownEditorShortcuts($(this), updateTextForToolbarBtn);
});
@@ -474,6 +487,8 @@ export function addEditorMarkdownListeners(editor) {
export function removeMarkdownListeners(form) {
$('.markdown-area', form)
.off('keydown', keypressNoteText)
+ .off('compositionstart', compositionStartNoteText)
+ .off('compositionend', compositionEndNoteText)
.each(function removeTextareaShortcutHandlers() {
Shortcuts.removeMarkdownEditorShortcuts($(this));
});
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 419afa0a0a9..dad9cbcb6f6 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -6,6 +6,10 @@ import {
} from '~/lib/utils/constants';
import { allSingleQuotes } from '~/lib/utils/regexp';
+export const COLON = ':';
+export const HYPHEN = '-';
+export const NEWLINE = '\n';
+
/**
* Adds a , to a string composed by numbers, at every 3 chars.
*
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 335cd6a16e5..ff60fd2aecb 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -420,6 +420,19 @@ export function isSafeURL(url) {
}
/**
+ * Returns the sanitized url when not safe
+ *
+ * @param {String} url
+ * @returns {String}
+ */
+export function sanitizeUrl(url) {
+ if (!isSafeURL(url)) {
+ return 'about:blank';
+ }
+ return url;
+}
+
+/**
* Returns a normalized url
*
* https://gitlab.com/foo/../baz => https://gitlab.com/baz
diff --git a/app/assets/javascripts/lib/utils/users_cache.js b/app/assets/javascripts/lib/utils/users_cache.js
index 54f69ef8e1b..bd000bb26fe 100644
--- a/app/assets/javascripts/lib/utils/users_cache.js
+++ b/app/assets/javascripts/lib/utils/users_cache.js
@@ -35,6 +35,17 @@ class UsersCache extends Cache {
// missing catch is intentional, error handling depends on use case
}
+ updateById(userId, data) {
+ if (!this.hasData(userId)) {
+ return;
+ }
+
+ this.internalStorage[userId] = {
+ ...this.internalStorage[userId],
+ ...data,
+ };
+ }
+
retrieveStatusById(userId) {
if (this.hasData(userId) && this.get(userId).status) {
return Promise.resolve(this.get(userId).status);