diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-20 02:18:09 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-20 02:18:09 +0300 |
commit | 6ed4ec3e0b1340f96b7c043ef51d1b33bbe85fde (patch) | |
tree | dc4d20fe6064752c0bd323187252c77e0a89144b /app/assets/javascripts/lib | |
parent | 9868dae7fc0655bd7ce4a6887d4e6d487690eeed (diff) |
Add latest changes from gitlab-org/gitlab@15-4-stable-eev15.4.0-rc42
Diffstat (limited to 'app/assets/javascripts/lib')
12 files changed, 201 insertions, 14 deletions
diff --git a/app/assets/javascripts/lib/dateformat.js b/app/assets/javascripts/lib/dateformat.js new file mode 100644 index 00000000000..1fd95dd03ab --- /dev/null +++ b/app/assets/javascripts/lib/dateformat.js @@ -0,0 +1,60 @@ +import dateFormat, { i18n, masks } from 'dateformat'; +import { s__, __ } from '~/locale'; + +i18n.dayNames = [ + __('Sun'), + __('Mon'), + __('Tue'), + __('Wed'), + __('Thu'), + __('Fri'), + __('Sat'), + __('Sunday'), + __('Monday'), + __('Tuesday'), + __('Wednesday'), + __('Thursday'), + __('Friday'), + __('Saturday'), +]; + +i18n.monthNames = [ + __('Jan'), + __('Feb'), + __('Mar'), + __('Apr'), + __('May'), + __('Jun'), + __('Jul'), + __('Aug'), + __('Sep'), + __('Oct'), + __('Nov'), + __('Dec'), + __('January'), + __('February'), + __('March'), + __('April'), + __('May'), + __('June'), + __('July'), + __('August'), + __('September'), + __('October'), + __('November'), + __('December'), +]; + +i18n.timeNames = [ + s__('Time|a'), + s__('Time|p'), + s__('Time|am'), + s__('Time|pm'), + s__('Time|A'), + s__('Time|P'), + s__('Time|AM'), + s__('Time|PM'), +]; + +export { masks }; +export default dateFormat; diff --git a/app/assets/javascripts/lib/dompurify.js b/app/assets/javascripts/lib/dompurify.js index 3e28ca2a0f7..6f24590f9e7 100644 --- a/app/assets/javascripts/lib/dompurify.js +++ b/app/assets/javascripts/lib/dompurify.js @@ -1,6 +1,8 @@ -import { sanitize as dompurifySanitize, addHook } from 'dompurify'; +import DOMPurify from 'dompurify'; import { getNormalizedURL, getBaseURL, relativePathToAbsolute } from '~/lib/utils/url_utility'; +const { sanitize: dompurifySanitize, addHook, isValidAttribute } = DOMPurify; + const defaultConfig = { // Safely allow SVG <use> tags ADD_TAGS: ['use', 'gl-emoji', 'copy-code'], @@ -94,4 +96,4 @@ addHook('afterSanitizeAttributes', (node) => { export const sanitize = (val, config) => dompurifySanitize(val, { ...defaultConfig, ...config }); -export { isValidAttribute } from 'dompurify'; +export { isValidAttribute }; diff --git a/app/assets/javascripts/lib/gfm/constants.js b/app/assets/javascripts/lib/gfm/constants.js new file mode 100644 index 00000000000..eaabeb2a767 --- /dev/null +++ b/app/assets/javascripts/lib/gfm/constants.js @@ -0,0 +1,10 @@ +export const TABLE_OF_CONTENTS_DOUBLE_BRACKET_OPEN_TOKEN = '[['; +export const TABLE_OF_CONTENTS_DOUBLE_BRACKET_MIDDLE_TOKEN = 'TOC'; +export const TABLE_OF_CONTENTS_DOUBLE_BRACKET_CLOSE_TOKEN = ']]'; +export const TABLE_OF_CONTENTS_SINGLE_BRACKET_TOKEN = '[TOC]'; + +export const MDAST_TEXT_NODE = 'text'; +export const MDAST_EMPHASIS_NODE = 'emphasis'; +export const MDAST_PARAGRAPH_NODE = 'paragraph'; + +export const GLFM_TABLE_OF_CONTENTS_NODE = 'tableOfContents'; diff --git a/app/assets/javascripts/lib/gfm/glfm_extensions/table_of_contents.js b/app/assets/javascripts/lib/gfm/glfm_extensions/table_of_contents.js new file mode 100644 index 00000000000..4d2484a657a --- /dev/null +++ b/app/assets/javascripts/lib/gfm/glfm_extensions/table_of_contents.js @@ -0,0 +1,85 @@ +import { first, last } from 'lodash'; +import { u } from 'unist-builder'; +import { visitParents, SKIP, CONTINUE } from 'unist-util-visit-parents'; +import { + TABLE_OF_CONTENTS_DOUBLE_BRACKET_CLOSE_TOKEN, + TABLE_OF_CONTENTS_DOUBLE_BRACKET_MIDDLE_TOKEN, + TABLE_OF_CONTENTS_DOUBLE_BRACKET_OPEN_TOKEN, + TABLE_OF_CONTENTS_SINGLE_BRACKET_TOKEN, + MDAST_TEXT_NODE, + MDAST_EMPHASIS_NODE, + MDAST_PARAGRAPH_NODE, + GLFM_TABLE_OF_CONTENTS_NODE, +} from '../constants'; + +const isTOCTextNode = ({ type, value }) => + type === MDAST_TEXT_NODE && value === TABLE_OF_CONTENTS_DOUBLE_BRACKET_MIDDLE_TOKEN; + +const isTOCEmphasisNode = ({ type, children }) => + type === MDAST_EMPHASIS_NODE && children.length === 1 && isTOCTextNode(first(children)); + +const isTOCDoubleSquareBracketOpenTokenTextNode = ({ type, value }) => + type === MDAST_TEXT_NODE && value.trim() === TABLE_OF_CONTENTS_DOUBLE_BRACKET_OPEN_TOKEN; + +const isTOCDoubleSquareBracketCloseTokenTextNode = ({ type, value }) => + type === MDAST_TEXT_NODE && value.trim() === TABLE_OF_CONTENTS_DOUBLE_BRACKET_CLOSE_TOKEN; + +/* + * Detects table of contents declaration with syntax [[_TOC_]] + */ +const isTableOfContentsDoubleSquareBracketSyntax = ({ children }) => { + if (children.length !== 3) { + return false; + } + + const [firstChild, middleChild, lastChild] = children; + + return ( + isTOCDoubleSquareBracketOpenTokenTextNode(firstChild) && + isTOCEmphasisNode(middleChild) && + isTOCDoubleSquareBracketCloseTokenTextNode(lastChild) + ); +}; + +/* + * Detects table of contents declaration with syntax [TOC] + */ +const isTableOfContentsSingleSquareBracketSyntax = ({ children }) => { + if (children.length !== 1) { + return false; + } + + const [firstChild] = children; + const { type, value } = firstChild; + + return type === MDAST_TEXT_NODE && value.trim() === TABLE_OF_CONTENTS_SINGLE_BRACKET_TOKEN; +}; + +const isTableOfContentsNode = (node) => + node.type === MDAST_PARAGRAPH_NODE && + (isTableOfContentsDoubleSquareBracketSyntax(node) || + isTableOfContentsSingleSquareBracketSyntax(node)); + +export default () => { + return (tree) => { + visitParents(tree, (node, ancestors) => { + const parent = last(ancestors); + + if (!parent) { + return CONTINUE; + } + + if (isTableOfContentsNode(node)) { + const index = parent.children.indexOf(node); + + parent.children[index] = u(GLFM_TABLE_OF_CONTENTS_NODE, { + position: node.position, + }); + } + + return SKIP; + }); + + return tree; + }; +}; diff --git a/app/assets/javascripts/lib/gfm/index.js b/app/assets/javascripts/lib/gfm/index.js index eaf653e9924..fad73f93c1a 100644 --- a/app/assets/javascripts/lib/gfm/index.js +++ b/app/assets/javascripts/lib/gfm/index.js @@ -6,6 +6,8 @@ import remarkFrontmatter from 'remark-frontmatter'; import remarkGfm from 'remark-gfm'; import remarkRehype, { all } from 'remark-rehype'; import rehypeRaw from 'rehype-raw'; +import glfmTableOfContents from './glfm_extensions/table_of_contents'; +import * as glfmMdastToHastHandlers from './mdast_to_hast_handlers/glfm_mdast_to_hast_handlers'; const skipFrontmatterHandler = (language) => (h, node) => h(node.position, 'frontmatter', { language }, [{ type: 'text', value: node.value }]); @@ -65,19 +67,22 @@ const skipRenderingHandlers = { all(h, node), ); }, + tableOfContents: (h, node) => h(node.position, 'tableOfContents'), toml: skipFrontmatterHandler('toml'), yaml: skipFrontmatterHandler('yaml'), json: skipFrontmatterHandler('json'), }; -const createParser = ({ skipRendering = [] }) => { +const createParser = ({ skipRendering }) => { return unified() .use(remarkParse) .use(remarkGfm) .use(remarkFrontmatter, ['yaml', 'toml', { type: 'json', marker: ';' }]) + .use(glfmTableOfContents) .use(remarkRehype, { allowDangerousHtml: true, handlers: { + ...glfmMdastToHastHandlers, ...pick(skipRenderingHandlers, skipRendering), }, }) @@ -99,13 +104,13 @@ const compilerFactory = (renderer) => * tree in any desired representation * * @param {String} params.markdown Markdown to parse - * @param {(tree: MDast -> any)} params.renderer A function that accepts mdast + * @param {Function} params.renderer A function that accepts mdast * AST tree and returns an object of any type that represents the result of * rendering the tree. See the references below to for more information * about MDast. * * MDastTree documentation https://github.com/syntax-tree/mdast - * @returns {Promise<any>} Returns a promise with the result of rendering + * @returns {Promise} Returns a promise with the result of rendering * the MDast tree */ export const render = async ({ markdown, renderer, skipRendering = [] }) => { diff --git a/app/assets/javascripts/lib/gfm/mdast_to_hast_handlers/glfm_mdast_to_hast_handlers.js b/app/assets/javascripts/lib/gfm/mdast_to_hast_handlers/glfm_mdast_to_hast_handlers.js new file mode 100644 index 00000000000..91b09e69405 --- /dev/null +++ b/app/assets/javascripts/lib/gfm/mdast_to_hast_handlers/glfm_mdast_to_hast_handlers.js @@ -0,0 +1 @@ +export const tableOfContents = (h, node) => h(node.position, 'nav'); diff --git a/app/assets/javascripts/lib/mermaid.js b/app/assets/javascripts/lib/mermaid.js index d621c9ddf9e..c72561ce69d 100644 --- a/app/assets/javascripts/lib/mermaid.js +++ b/app/assets/javascripts/lib/mermaid.js @@ -9,6 +9,7 @@ const setIframeRenderedSize = (h, w) => { const drawDiagram = (source) => { const element = document.getElementById('app'); const insertSvg = (svgCode) => { + // eslint-disable-next-line no-unsanitized/property element.innerHTML = svgCode; const height = parseInt(element.firstElementChild.getAttribute('height'), 10); 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 4e7086e62c5..6c5d4ecc901 100644 --- a/app/assets/javascripts/lib/utils/datetime/date_calculation_utility.js +++ b/app/assets/javascripts/lib/utils/datetime/date_calculation_utility.js @@ -142,9 +142,16 @@ export const dayInQuarter = (date, quarter) => { export const millisecondsPerDay = 1000 * 60 * 60 * 24; -export const getDayDifference = (a, b) => { - const date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate()); - const date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate()); +/** + * Calculates the number of days between 2 specified dates, excluding the current date + * + * @param {Date} startDate the earlier date that we will substract from the end date + * @param {Date} endDate the last date in the range + * @return {Number} number of days in between + */ +export const getDayDifference = (startDate, endDate) => { + const date1 = Date.UTC(startDate.getFullYear(), startDate.getMonth(), startDate.getDate()); + const date2 = Date.UTC(endDate.getFullYear(), endDate.getMonth(), endDate.getDate()); return Math.floor((date2 - date1) / millisecondsPerDay); }; @@ -208,6 +215,19 @@ export const newDateAsLocaleTime = (date) => { return new Date(`${date}${suffix}`); }; +/** + * Takes a Date object (where timezone could be GMT or EST) and + * returns a Date object with the same date but in UTC. + * + * @param {Date} date A Date object + * @returns {Date|null} A Date object with the same date but in UTC + */ +export const getDateWithUTC = (date) => { + return date instanceof Date + ? new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())) + : null; +}; + export const beginOfDayTime = 'T00:00:00Z'; export const endOfDayTime = 'T23:59:59Z'; 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 830f4604382..d07abb72210 100644 --- a/app/assets/javascripts/lib/utils/datetime/date_format_utility.js +++ b/app/assets/javascripts/lib/utils/datetime/date_format_utility.js @@ -1,5 +1,5 @@ -import dateFormat from 'dateformat'; import { isString, mapValues, reduce, isDate, unescape } from 'lodash'; +import dateFormat from '~/lib/dateformat'; import { roundToNearestHalf } from '~/lib/utils/common_utils'; import { sanitize } from '~/lib/dompurify'; import { s__, n__, __, sprintf } from '~/locale'; diff --git a/app/assets/javascripts/lib/utils/datetime_range.js b/app/assets/javascripts/lib/utils/datetime_range.js index 840cc4600fe..548f5a438df 100644 --- a/app/assets/javascripts/lib/utils/datetime_range.js +++ b/app/assets/javascripts/lib/utils/datetime_range.js @@ -1,5 +1,5 @@ -import dateformat from 'dateformat'; import { pick, omit, isEqual, isEmpty } from 'lodash'; +import dateformat from '~/lib/dateformat'; import { DATETIME_RANGE_TYPES } from './constants'; import { secondsToMilliseconds } from './datetime_utility'; diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js index 9f4e12a3010..48be8af3ff6 100644 --- a/app/assets/javascripts/lib/utils/text_markdown.js +++ b/app/assets/javascripts/lib/utils/text_markdown.js @@ -263,10 +263,12 @@ export function insertMarkdownText({ if (tag === LINK_TAG_PATTERN) { if (URL) { try { - new URL(selected); // eslint-disable-line no-new - // valid url - tag = '[text]({text})'; - select = 'text'; + const url = new URL(selected); + + if (url.origin !== 'null' || url.origin === null) { + tag = '[text]({text})'; + select = 'text'; + } } catch (e) { // ignore - no valid url } diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index 7b00995b2e5..59645d50e29 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -119,6 +119,7 @@ const getAverageCharWidth = memoize(function getAverageCharWidth(options = {}) { div.style.left = -1000; div.style.top = -1000; + // eslint-disable-next-line no-unsanitized/property div.innerHTML = chars; document.body.appendChild(div); |