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>2022-10-20 12:40:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-10-20 12:40:42 +0300
commitee664acb356f8123f4f6b00b73c1e1cf0866c7fb (patch)
treef8479f94a28f66654c6a4f6fb99bad6b4e86a40e /app/assets/javascripts/lib
parent62f7d5c5b69180e82ae8196b7b429eeffc8e7b4f (diff)
Add latest changes from gitlab-org/gitlab@15-5-stable-eev15.5.0-rc42
Diffstat (limited to 'app/assets/javascripts/lib')
-rw-r--r--app/assets/javascripts/lib/dompurify.js13
-rw-r--r--app/assets/javascripts/lib/utils/autosave.js40
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js51
-rw-r--r--app/assets/javascripts/lib/utils/datetime/date_calculation_utility.js18
-rw-r--r--app/assets/javascripts/lib/utils/datetime/date_format_utility.js26
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js61
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js14
7 files changed, 139 insertions, 84 deletions
diff --git a/app/assets/javascripts/lib/dompurify.js b/app/assets/javascripts/lib/dompurify.js
index 6f24590f9e7..27760e483aa 100644
--- a/app/assets/javascripts/lib/dompurify.js
+++ b/app/assets/javascripts/lib/dompurify.js
@@ -3,12 +3,21 @@ import { getNormalizedURL, getBaseURL, relativePathToAbsolute } from '~/lib/util
const { sanitize: dompurifySanitize, addHook, isValidAttribute } = DOMPurify;
-const defaultConfig = {
+export const defaultConfig = {
// Safely allow SVG <use> tags
ADD_TAGS: ['use', 'gl-emoji', 'copy-code'],
// Prevent possible XSS attacks with data-* attributes used by @rails/ujs
// See https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1421
- FORBID_ATTR: ['data-remote', 'data-url', 'data-type', 'data-method'],
+ FORBID_ATTR: [
+ 'data-remote',
+ 'data-url',
+ 'data-type',
+ 'data-method',
+ 'data-disable-with',
+ 'data-disabled',
+ 'data-disable',
+ 'data-turbo',
+ ],
FORBID_TAGS: ['style', 'mstyle'],
ALLOW_UNKNOWN_PROTOCOLS: true,
};
diff --git a/app/assets/javascripts/lib/utils/autosave.js b/app/assets/javascripts/lib/utils/autosave.js
index dac1da743a2..01316be06a2 100644
--- a/app/assets/javascripts/lib/utils/autosave.js
+++ b/app/assets/javascripts/lib/utils/autosave.js
@@ -1,8 +1,27 @@
+import { isString } from 'lodash';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
+const normalizeKey = (autosaveKey) => {
+ let normalizedKey;
+
+ if (Array.isArray(autosaveKey) && autosaveKey.every(isString)) {
+ normalizedKey = autosaveKey.join('/');
+ } else if (isString(autosaveKey)) {
+ normalizedKey = autosaveKey;
+ } else {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ throw new Error('Invalid autosave key');
+ }
+
+ return `autosave/${normalizedKey}`;
+};
+
+const lockVersionKey = (autosaveKey) => `${normalizeKey(autosaveKey)}/lockVersion`;
+
export const clearDraft = (autosaveKey) => {
try {
- window.localStorage.removeItem(`autosave/${autosaveKey}`);
+ window.localStorage.removeItem(normalizeKey(autosaveKey));
+ window.localStorage.removeItem(lockVersionKey(autosaveKey));
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
@@ -11,7 +30,17 @@ export const clearDraft = (autosaveKey) => {
export const getDraft = (autosaveKey) => {
try {
- return window.localStorage.getItem(`autosave/${autosaveKey}`);
+ return window.localStorage.getItem(normalizeKey(autosaveKey));
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error(e);
+ return null;
+ }
+};
+
+export const getLockVersion = (autosaveKey) => {
+ try {
+ return window.localStorage.getItem(lockVersionKey(autosaveKey));
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
@@ -19,9 +48,12 @@ export const getDraft = (autosaveKey) => {
}
};
-export const updateDraft = (autosaveKey, text) => {
+export const updateDraft = (autosaveKey, text, lockVersion) => {
try {
- window.localStorage.setItem(`autosave/${autosaveKey}`, text);
+ window.localStorage.setItem(normalizeKey(autosaveKey), text);
+ if (lockVersion) {
+ window.localStorage.setItem(lockVersionKey(autosaveKey), lockVersion);
+ }
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 7925a10344a..4448a106bb6 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -60,6 +60,15 @@ export const disableButtonIfEmptyField = (fieldSelector, buttonSelector, eventNa
});
};
+/**
+ * Return the given element's offset height, or 0 if the element doesn't exist.
+ * Probably not useful outside of handleLocationHash.
+ *
+ * @param {HTMLElement} element The element to measure.
+ * @returns {number} The element's offset height.
+ */
+const getElementOffsetHeight = (element) => element?.offsetHeight ?? 0;
+
// automatically adjust scroll position for hash urls taking the height of the navbar into account
// https://github.com/twitter/bootstrap/issues/1768
export const handleLocationHash = () => {
@@ -84,40 +93,26 @@ export const handleLocationHash = () => {
const fixedIssuableTitle = document.querySelector('.issue-sticky-header');
let adjustment = 0;
- if (fixedNav) adjustment -= fixedNav.offsetHeight;
-
- if (target && target.scrollIntoView) {
- target.scrollIntoView(true);
- }
- if (fixedTabs) {
- adjustment -= fixedTabs.offsetHeight;
- }
-
- if (fixedDiffStats) {
- adjustment -= fixedDiffStats.offsetHeight;
- }
-
- if (performanceBar) {
- adjustment -= performanceBar.offsetHeight;
- }
-
- if (diffFileHeader) {
- adjustment -= diffFileHeader.offsetHeight;
- }
-
- if (versionMenusContainer) {
- adjustment -= versionMenusContainer.offsetHeight;
- }
+ adjustment -= getElementOffsetHeight(fixedNav);
+ adjustment -= getElementOffsetHeight(fixedTabs);
+ adjustment -= getElementOffsetHeight(fixedDiffStats);
+ adjustment -= getElementOffsetHeight(performanceBar);
+ adjustment -= getElementOffsetHeight(diffFileHeader);
+ adjustment -= getElementOffsetHeight(versionMenusContainer);
if (isInIssuePage()) {
- adjustment -= fixedIssuableTitle.offsetHeight;
+ adjustment -= getElementOffsetHeight(fixedIssuableTitle);
}
if (isInMRPage()) {
adjustment -= topPadding;
}
+ if (target?.scrollIntoView) {
+ target.scrollIntoView(true);
+ }
+
setTimeout(() => {
window.scrollBy(0, adjustment);
});
@@ -172,7 +167,7 @@ export const contentTop = () => {
return size;
},
- () => getOuterHeight('.merge-request-tabs'),
+ () => getOuterHeight('.merge-request-sticky-header, .merge-request-tabs'),
() => getOuterHeight('.js-diff-files-changed'),
() => getOuterHeight('.issue-sticky-header.gl-fixed'),
({ desktop }) => {
@@ -180,7 +175,9 @@ export const contentTop = () => {
let size;
if (desktop && diffsTabIsActive) {
- size = getOuterHeight('.diff-file .file-title-flex-parent:not([style="display:none"])');
+ size = getOuterHeight(
+ '.diffs .diff-file .file-title-flex-parent:not([style="display:none"])',
+ );
}
return size;
diff --git a/app/assets/javascripts/lib/utils/datetime/date_calculation_utility.js b/app/assets/javascripts/lib/utils/datetime/date_calculation_utility.js
index 6c5d4ecc901..c11cf1a7882 100644
--- a/app/assets/javascripts/lib/utils/datetime/date_calculation_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime/date_calculation_utility.js
@@ -271,24 +271,6 @@ export const secondsToMilliseconds = (seconds) => seconds * 1000;
export const secondsToDays = (seconds) => Math.round(seconds / 86400);
/**
- * Converts a numeric utc offset in seconds to +/- hours
- * ie -32400 => -9 hours
- * ie -12600 => -3.5 hours
- *
- * @param {Number} offset UTC offset in seconds as a integer
- *
- * @return {String} the + or - offset in hours
- */
-export const secondsToHours = (offset) => {
- const parsed = parseInt(offset, 10);
- if (Number.isNaN(parsed) || parsed === 0) {
- return `0`;
- }
- const num = offset / 3600;
- return parseInt(num, 10) !== num ? num.toFixed(1) : num;
-};
-
-/**
* Returns the date `n` days after the date provided
*
* @param {Date} date the initial date
diff --git a/app/assets/javascripts/lib/utils/datetime/date_format_utility.js b/app/assets/javascripts/lib/utils/datetime/date_format_utility.js
index d07abb72210..737c18d1bce 100644
--- a/app/assets/javascripts/lib/utils/datetime/date_format_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime/date_format_utility.js
@@ -406,3 +406,29 @@ export const durationTimeFormatted = (duration) => {
return `${hh}:${mm}:${ss}`;
};
+
+/**
+ * Converts a numeric utc offset in seconds to +/- hours
+ * ie -32400 => -9 hours
+ * ie -12600 => -3.5 hours
+ *
+ * @param {Number} offset UTC offset in seconds as a integer
+ *
+ * @return {String} the + or - offset in hours, e.g. `- 10`, `0`, `+ 4`
+ */
+export const formatUtcOffset = (offset) => {
+ const parsed = parseInt(offset, 10);
+ if (Number.isNaN(parsed) || parsed === 0) {
+ return `0`;
+ }
+ const prefix = offset > 0 ? '+' : '-';
+ return `${prefix} ${Math.abs(offset / 3600)}`;
+};
+
+/**
+ * Returns formatted timezone
+ *
+ * @param {Object} timezone item with offset and name
+ * @returns {String} the UTC timezone with the offset, e.g. `[UTC + 2] Berlin`
+ */
+export const formatTimezone = ({ offset, name }) => `[UTC ${formatUtcOffset(offset)}] ${name}`;
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 48be8af3ff6..3894ec36a0b 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -391,13 +391,15 @@ function updateText({ textArea, tag, cursorOffset, blockTag, wrap, select, tagCo
/**
* Indents selected lines to the right by 2 spaces
*
- * @param {Object} textArea - the targeted text area
+ * @param {Object} textArea - jQuery object with the targeted text area
*/
-function indentLines(textArea) {
+function indentLines($textArea) {
+ const textArea = $textArea.get(0);
const { lines, selectionStart, selectionEnd, startPos, endPos } = linesFromSelection(textArea);
const shiftedLines = [];
let totalAdded = 0;
+ textArea.focus();
textArea.setSelectionRange(startPos, endPos);
lines.forEach((line) => {
@@ -418,13 +420,15 @@ function indentLines(textArea) {
*
* @param {Object} textArea - the targeted text area
*/
-function outdentLines(textArea) {
+function outdentLines($textArea) {
+ const textArea = $textArea.get(0);
const { lines, selectionStart, selectionEnd, startPos, endPos } = linesFromSelection(textArea);
const shiftedLines = [];
let totalRemoved = 0;
let removedFromFirstline = -1;
let removedFromLine = 0;
+ textArea.focus();
textArea.setSelectionRange(startPos, endPos);
lines.forEach((line) => {
@@ -460,28 +464,10 @@ function outdentLines(textArea) {
);
}
-function handleIndentOutdent(e, textArea) {
- if (e.altKey || e.ctrlKey || e.shiftKey) return;
- if (!e.metaKey) return;
-
- switch (e.key) {
- case ']':
- e.preventDefault();
- indentLines(textArea);
- break;
- case '[':
- e.preventDefault();
- outdentLines(textArea);
- break;
- default:
- break;
- }
-}
-
/* eslint-disable @gitlab/require-i18n-strings */
function handleSurroundSelectedText(e, textArea) {
if (!gon.markdown_surround_selection) return;
- if (e.metaKey) return;
+ if (e.metaKey || e.ctrlKey) return;
if (textArea.selectionStart === textArea.selectionEnd) return;
const keys = {
@@ -532,6 +518,7 @@ function continueOlText(listLineMatch, nextLineMatch) {
}
function handleContinueList(e, textArea) {
+ if (!gon.markdown_automatic_lists) return;
if (!(e.key === 'Enter')) return;
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
if (textArea.selectionStart !== textArea.selectionEnd) return;
@@ -586,7 +573,6 @@ export function keypressNoteText(e) {
if ($(textArea).atwho?.('isSelecting')) return;
- handleIndentOutdent(e, textArea);
handleContinueList(e, textArea);
handleSurroundSelectedText(e, textArea);
}
@@ -600,15 +586,26 @@ export function compositionEndNoteText() {
}
export function updateTextForToolbarBtn($toolbarBtn) {
- return updateText({
- textArea: $toolbarBtn.closest('.md-area').find('textarea'),
- tag: $toolbarBtn.data('mdTag'),
- cursorOffset: $toolbarBtn.data('mdCursorOffset'),
- blockTag: $toolbarBtn.data('mdBlock'),
- wrap: !$toolbarBtn.data('mdPrepend'),
- select: $toolbarBtn.data('mdSelect'),
- tagContent: $toolbarBtn.attr('data-md-tag-content'),
- });
+ const $textArea = $toolbarBtn.closest('.md-area').find('textarea');
+
+ switch ($toolbarBtn.data('mdCommand')) {
+ case 'indentLines':
+ indentLines($textArea);
+ break;
+ case 'outdentLines':
+ outdentLines($textArea);
+ break;
+ default:
+ return updateText({
+ textArea: $textArea,
+ tag: $toolbarBtn.data('mdTag'),
+ cursorOffset: $toolbarBtn.data('mdCursorOffset'),
+ blockTag: $toolbarBtn.data('mdBlock'),
+ wrap: !$toolbarBtn.data('mdPrepend'),
+ select: $toolbarBtn.data('mdSelect'),
+ tagContent: $toolbarBtn.attr('data-md-tag-content'),
+ });
+ }
}
export function addMarkdownListeners(form) {
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 59645d50e29..367180714df 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -1,5 +1,5 @@
import { isString, memoize } from 'lodash';
-
+import { base64ToBuffer, bufferToBase64 } from '~/authentication/webauthn/util';
import {
TRUNCATE_WIDTH_DEFAULT_WIDTH,
TRUNCATE_WIDTH_DEFAULT_FONT_SIZE,
@@ -513,3 +513,15 @@ export const limitedCounterWithDelimiter = (count) => {
return count > limit ? '1,000+' : count;
};
+
+// Encoding UTF8 ⇢ base64
+export function base64EncodeUnicode(str) {
+ const encoder = new TextEncoder('utf8');
+ return bufferToBase64(encoder.encode(str));
+}
+
+// Decoding base64 ⇢ UTF8
+export function base64DecodeUnicode(str) {
+ const decoder = new TextDecoder('utf8');
+ return decoder.decode(base64ToBuffer(str));
+}