diff options
Diffstat (limited to 'app/assets/javascripts/issues/show/utils.js')
-rw-r--r-- | app/assets/javascripts/issues/show/utils.js | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/app/assets/javascripts/issues/show/utils.js b/app/assets/javascripts/issues/show/utils.js index 05b06586362..7742a015836 100644 --- a/app/assets/javascripts/issues/show/utils.js +++ b/app/assets/javascripts/issues/show/utils.js @@ -1,4 +1,6 @@ +import { TITLE_LENGTH_MAX } from '~/issues/constants'; import { COLON, HYPHEN, NEWLINE } from '~/lib/utils/text_utility'; +import { __ } from '~/locale'; /** * Returns the start and end `sourcepos` rows, converted to zero-based numbering. @@ -93,3 +95,136 @@ export const convertDescriptionWithNewSort = (description, list) => { return descriptionLines.join(NEWLINE); }; + +const bulletTaskListItemRegex = /^\s*[-*]\s+\[.]\s+/; +const numericalTaskListItemRegex = /^\s*[0-9]\.\s+\[.]\s+/; +const codeMarkdownRegex = /^\s*`.*`\s*$/; +const imageOrLinkMarkdownRegex = /^\s*!?\[.*\)\s*$/; + +/** + * Checks whether the line of markdown contains a task list item, + * i.e. `- [ ]`, `* [ ]`, or `1. [ ]`. + * + * @param {String} line A line of markdown + * @returns {boolean} `true` if the line contains a task list item, otherwise `false` + */ +const containsTaskListItem = (line) => + bulletTaskListItemRegex.test(line) || numericalTaskListItemRegex.test(line); + +/** + * Deletes a task list item from the description. + * + * Starting from the task list item, it deletes each line until it hits a nested + * task list item and reduces the indentation of each line from this line onwards. + * + * For example, for a given description like: + * + * <pre> + * 1. [ ] item 1 + * + * paragraph text + * + * 1. [ ] item 2 + * + * paragraph text + * + * 1. [ ] item 3 + * </pre> + * + * Then when prompted to delete item 1, this function will return: + * + * <pre> + * 1. [ ] item 2 + * + * paragraph text + * + * 1. [ ] item 3 + * </pre> + * + * @param {String} description Description in markdown format + * @param {String} sourcepos Source position in format `23:3-23:14` + * @returns {{newDescription: String, taskDescription: String, taskTitle: String}} Object with: + * + * - `newDescription` property that contains markdown with the deleted task list item omitted + * - `taskDescription` property that contains the description of the deleted task list item + * - `taskTitle` property that contains the title of the deleted task list item + */ +export const deleteTaskListItem = (description, sourcepos) => { + const descriptionLines = description.split(NEWLINE); + const [startIndex, endIndex] = getSourceposRows(sourcepos); + + const firstLine = descriptionLines[startIndex]; + const firstLineIndentation = firstLine.length - firstLine.trimStart().length; + + const taskTitle = firstLine + .replace(bulletTaskListItemRegex, '') + .replace(numericalTaskListItemRegex, ''); + const taskDescription = []; + + let indentation = 0; + let linesToDelete = 1; + let reduceIndentation = false; + + for (let i = startIndex + 1; i <= endIndex; i += 1) { + if (reduceIndentation) { + descriptionLines[i] = descriptionLines[i].slice(indentation); + } else if (containsTaskListItem(descriptionLines[i])) { + reduceIndentation = true; + const currentLine = descriptionLines[i]; + const currentLineIndentation = currentLine.length - currentLine.trimStart().length; + indentation = currentLineIndentation - firstLineIndentation; + descriptionLines[i] = descriptionLines[i].slice(indentation); + } else { + taskDescription.push(descriptionLines[i].trimStart()); + linesToDelete += 1; + } + } + + descriptionLines.splice(startIndex, linesToDelete); + + return { + newDescription: descriptionLines.join(NEWLINE), + taskDescription: taskDescription.join(NEWLINE) || undefined, + taskTitle, + }; +}; + +/** + * Given a title and description for a task: + * + * - Moves characters beyond the 255 character limit from the title to the description + * - Moves a pure markdown title to the description and gives the title the value `Untitled` + * + * @param {String} taskTitle The task title + * @param {String} taskDescription The task description + * @returns {{description: String, title: String}} An object with the formatted task title and description + */ +export const extractTaskTitleAndDescription = (taskTitle, taskDescription) => { + const isTitleOnlyMarkdown = + codeMarkdownRegex.test(taskTitle) || imageOrLinkMarkdownRegex.test(taskTitle); + + if (isTitleOnlyMarkdown) { + return { + title: __('Untitled'), + description: taskDescription + ? taskTitle.concat(NEWLINE, NEWLINE, taskDescription) + : taskTitle, + }; + } + + const isTitleTooLong = taskTitle.length > TITLE_LENGTH_MAX; + + if (isTitleTooLong) { + return { + title: taskTitle.slice(0, TITLE_LENGTH_MAX), + description: taskDescription + ? taskTitle.slice(TITLE_LENGTH_MAX).concat(NEWLINE, NEWLINE, taskDescription) + : taskTitle.slice(TITLE_LENGTH_MAX), + }; + } + + return { + title: taskTitle, + description: taskDescription, + }; +}; |