Export text utils as ES6 modules
This commit is contained in:
parent
c6a48f3f92
commit
1ff3f1a4f7
|
@ -1,3 +1,5 @@
|
||||||
|
import { truncate } from './lib/utils/text_utility';
|
||||||
|
|
||||||
const MAX_MESSAGE_LENGTH = 500;
|
const MAX_MESSAGE_LENGTH = 500;
|
||||||
const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
|
const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
|
||||||
|
|
||||||
|
@ -15,7 +17,7 @@ export default class AbuseReports {
|
||||||
if (reportMessage.length > MAX_MESSAGE_LENGTH) {
|
if (reportMessage.length > MAX_MESSAGE_LENGTH) {
|
||||||
$messageCellElement.data('original-message', reportMessage);
|
$messageCellElement.data('original-message', reportMessage);
|
||||||
$messageCellElement.data('message-truncated', 'true');
|
$messageCellElement.data('message-truncated', 'true');
|
||||||
$messageCellElement.text(window.gl.text.truncate(reportMessage, MAX_MESSAGE_LENGTH));
|
$messageCellElement.text(truncate(reportMessage, MAX_MESSAGE_LENGTH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Flash from '../../../flash';
|
import Flash from '../../../flash';
|
||||||
import './lists_dropdown';
|
import './lists_dropdown';
|
||||||
|
import { pluralize } from '../../../lib/utils/text_utility';
|
||||||
|
|
||||||
const ModalStore = gl.issueBoards.ModalStore;
|
const ModalStore = gl.issueBoards.ModalStore;
|
||||||
|
|
||||||
|
@ -21,7 +22,7 @@ gl.issueBoards.ModalFooter = Vue.extend({
|
||||||
submitText() {
|
submitText() {
|
||||||
const count = ModalStore.selectedCount();
|
const count = ModalStore.selectedCount();
|
||||||
|
|
||||||
return `Add ${count > 0 ? count : ''} ${gl.text.pluralize('issue', count)}`;
|
return `Add ${count > 0 ? count : ''} ${pluralize('issue', count)}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
prefer-template, object-shorthand, prefer-arrow-callback */
|
prefer-template, object-shorthand, prefer-arrow-callback */
|
||||||
/* global Pager */
|
/* global Pager */
|
||||||
|
|
||||||
|
import { pluralize } from './lib/utils/text_utility';
|
||||||
|
|
||||||
export default (function () {
|
export default (function () {
|
||||||
const CommitsList = {};
|
const CommitsList = {};
|
||||||
|
|
||||||
|
@ -86,7 +88,7 @@ export default (function () {
|
||||||
|
|
||||||
// Update commits count in the previous commits header.
|
// Update commits count in the previous commits header.
|
||||||
commitsCount += Number($(processedData).nextUntil('li.js-commit-header').first().find('li.commit').length);
|
commitsCount += Number($(processedData).nextUntil('li.js-commit-header').first().find('li.commit').length);
|
||||||
$commitsHeadersLast.find('span.commits-count').text(`${commitsCount} ${gl.text.pluralize('commit', commitsCount)}`);
|
$commitsHeadersLast.find('span.commits-count').text(`${commitsCount} ${pluralize('commit', commitsCount)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
gl.utils.localTimeAgo($processedData.find('.js-timeago'));
|
gl.utils.localTimeAgo($processedData.find('.js-timeago'));
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/* eslint-disable func-names, prefer-arrow-callback */
|
/* eslint-disable func-names, prefer-arrow-callback */
|
||||||
import Api from './api';
|
import Api from './api';
|
||||||
|
import { humanize } from './lib/utils/text_utility';
|
||||||
|
|
||||||
export default class CreateLabelDropdown {
|
export default class CreateLabelDropdown {
|
||||||
constructor($el, namespacePath, projectPath) {
|
constructor($el, namespacePath, projectPath) {
|
||||||
|
@ -107,7 +108,7 @@ export default class CreateLabelDropdown {
|
||||||
errors = label.message;
|
errors = label.message;
|
||||||
} else {
|
} else {
|
||||||
errors = Object.keys(label.message).map(key =>
|
errors = Object.keys(label.message).map(key =>
|
||||||
`${gl.text.humanize(key)} ${label.message[key].join(', ')}`,
|
`${humanize(key)} ${label.message[key].join(', ')}`,
|
||||||
).join('<br/>');
|
).join('<br/>');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
|
|
||||||
import { __ } from '../locale';
|
import { __ } from '../locale';
|
||||||
import '../lib/utils/text_utility';
|
import { dasherize } from '../lib/utils/text_utility';
|
||||||
import DEFAULT_EVENT_OBJECTS from './default_event_objects';
|
import DEFAULT_EVENT_OBJECTS from './default_event_objects';
|
||||||
|
|
||||||
const EMPTY_STAGE_TEXTS = {
|
const EMPTY_STAGE_TEXTS = {
|
||||||
|
@ -36,7 +36,7 @@ export default {
|
||||||
});
|
});
|
||||||
|
|
||||||
newData.stages.forEach((item) => {
|
newData.stages.forEach((item) => {
|
||||||
const stageSlug = gl.text.dasherize(item.name.toLowerCase());
|
const stageSlug = dasherize(item.name.toLowerCase());
|
||||||
item.active = false;
|
item.active = false;
|
||||||
item.isUserAllowed = data.permissions[stageSlug];
|
item.isUserAllowed = data.permissions[stageSlug];
|
||||||
item.emptyStageText = EMPTY_STAGE_TEXTS[stageSlug];
|
item.emptyStageText = EMPTY_STAGE_TEXTS[stageSlug];
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import Timeago from 'timeago.js';
|
import Timeago from 'timeago.js';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||||
import '../../lib/utils/text_utility';
|
import { humanize } from '../../lib/utils/text_utility';
|
||||||
import ActionsComponent from './environment_actions.vue';
|
import ActionsComponent from './environment_actions.vue';
|
||||||
import ExternalUrlComponent from './environment_external_url.vue';
|
import ExternalUrlComponent from './environment_external_url.vue';
|
||||||
import StopComponent from './environment_stop.vue';
|
import StopComponent from './environment_stop.vue';
|
||||||
|
@ -139,7 +139,7 @@ export default {
|
||||||
if (this.hasManualActions) {
|
if (this.hasManualActions) {
|
||||||
return this.model.last_deployment.manual_actions.map((action) => {
|
return this.model.last_deployment.manual_actions.map((action) => {
|
||||||
const parsedAction = {
|
const parsedAction = {
|
||||||
name: gl.text.humanize(action.name),
|
name: humanize(action.name),
|
||||||
play_path: action.play_path,
|
play_path: action.play_path,
|
||||||
playable: action.playable,
|
playable: action.playable,
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import GfmAutoComplete from './gfm_auto_complete';
|
import GfmAutoComplete from './gfm_auto_complete';
|
||||||
import dropzoneInput from './dropzone_input';
|
import dropzoneInput from './dropzone_input';
|
||||||
|
import textUtils from './lib/utils/text_markdown';
|
||||||
|
|
||||||
export default class GLForm {
|
export default class GLForm {
|
||||||
constructor(form, enableGFM = false) {
|
constructor(form, enableGFM = false) {
|
||||||
|
@ -46,7 +47,7 @@ export default class GLForm {
|
||||||
}
|
}
|
||||||
// form and textarea event listeners
|
// form and textarea event listeners
|
||||||
this.addEventListeners();
|
this.addEventListeners();
|
||||||
gl.text.init(this.form);
|
textUtils.init(this.form);
|
||||||
// hide discard button
|
// hide discard button
|
||||||
this.form.find('.js-note-discard').hide();
|
this.form.find('.js-note-discard').hide();
|
||||||
this.form.show();
|
this.form.show();
|
||||||
|
@ -85,7 +86,7 @@ export default class GLForm {
|
||||||
clearEventListeners() {
|
clearEventListeners() {
|
||||||
this.textarea.off('focus');
|
this.textarea.off('focus');
|
||||||
this.textarea.off('blur');
|
this.textarea.off('blur');
|
||||||
gl.text.removeListeners(this.form);
|
textUtils.removeListeners(this.form);
|
||||||
}
|
}
|
||||||
|
|
||||||
addEventListeners() {
|
addEventListeners() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
|
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
|
||||||
import 'vendor/jquery.waitforimages';
|
import 'vendor/jquery.waitforimages';
|
||||||
import '~/lib/utils/text_utility';
|
import { addDelimiter } from './lib/utils/text_utility';
|
||||||
import Flash from './flash';
|
import Flash from './flash';
|
||||||
import TaskList from './task_list';
|
import TaskList from './task_list';
|
||||||
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
|
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
|
||||||
|
@ -73,7 +73,7 @@ export default class Issue {
|
||||||
|
|
||||||
let numProjectIssues = Number(projectIssuesCounter.first().text().trim().replace(/[^\d]/, ''));
|
let numProjectIssues = Number(projectIssuesCounter.first().text().trim().replace(/[^\d]/, ''));
|
||||||
numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
|
numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
|
||||||
projectIssuesCounter.text(gl.text.addDelimiter(numProjectIssues));
|
projectIssuesCounter.text(addDelimiter(numProjectIssues));
|
||||||
|
|
||||||
if (this.createMergeRequestDropdown) {
|
if (this.createMergeRequestDropdown) {
|
||||||
if (isClosed) {
|
if (isClosed) {
|
||||||
|
|
|
@ -172,7 +172,6 @@ export const getSelectedFragment = () => {
|
||||||
return documentFragment;
|
return documentFragment;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Update this name, there is a gl.text.insertText function.
|
|
||||||
export const insertText = (target, text) => {
|
export const insertText = (target, text) => {
|
||||||
// Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas
|
// Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas
|
||||||
const selectionStart = target.selectionStart;
|
const selectionStart = target.selectionStart;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import timeago from 'timeago.js';
|
import timeago from 'timeago.js';
|
||||||
import dateFormat from 'vendor/date.format';
|
import dateFormat from 'vendor/date.format';
|
||||||
|
import { pluralize } from './text_utility';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
lang,
|
lang,
|
||||||
|
@ -143,9 +144,9 @@ export function timeIntervalInWords(intervalInSeconds) {
|
||||||
let text = '';
|
let text = '';
|
||||||
|
|
||||||
if (minutes >= 1) {
|
if (minutes >= 1) {
|
||||||
text = `${minutes} ${gl.text.pluralize('minute', minutes)} ${seconds} ${gl.text.pluralize('second', seconds)}`;
|
text = `${minutes} ${pluralize('minute', minutes)} ${seconds} ${pluralize('second', seconds)}`;
|
||||||
} else {
|
} else {
|
||||||
text = `${seconds} ${gl.text.pluralize('second', seconds)}`;
|
text = `${seconds} ${pluralize('second', seconds)}`;
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
/* eslint-disable import/prefer-default-export, func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */
|
||||||
|
|
||||||
|
const textUtils = {};
|
||||||
|
|
||||||
|
textUtils.selectedText = function(text, textarea) {
|
||||||
|
return text.substring(textarea.selectionStart, textarea.selectionEnd);
|
||||||
|
};
|
||||||
|
|
||||||
|
textUtils.lineBefore = function(text, textarea) {
|
||||||
|
var split;
|
||||||
|
split = text.substring(0, textarea.selectionStart).trim().split('\n');
|
||||||
|
return split[split.length - 1];
|
||||||
|
};
|
||||||
|
|
||||||
|
textUtils.lineAfter = function(text, textarea) {
|
||||||
|
return text.substring(textarea.selectionEnd).trim().split('\n')[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
textUtils.blockTagText = function(text, textArea, blockTag, selected) {
|
||||||
|
var lineAfter, lineBefore;
|
||||||
|
lineBefore = this.lineBefore(text, textArea);
|
||||||
|
lineAfter = this.lineAfter(text, textArea);
|
||||||
|
if (lineBefore === blockTag && lineAfter === blockTag) {
|
||||||
|
// To remove the block tag we have to select the line before & after
|
||||||
|
if (blockTag != null) {
|
||||||
|
textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
|
||||||
|
textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1);
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
} else {
|
||||||
|
return blockTag + "\n" + selected + "\n" + blockTag;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
textUtils.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
|
||||||
|
var insertText, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
|
||||||
|
removedLastNewLine = false;
|
||||||
|
removedFirstNewLine = false;
|
||||||
|
currentLineEmpty = false;
|
||||||
|
|
||||||
|
// Remove the first newline
|
||||||
|
if (selected.indexOf('\n') === 0) {
|
||||||
|
removedFirstNewLine = true;
|
||||||
|
selected = selected.replace(/\n+/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the last newline
|
||||||
|
if (textArea.selectionEnd - textArea.selectionStart > selected.replace(/\n$/, '').length) {
|
||||||
|
removedLastNewLine = true;
|
||||||
|
selected = selected.replace(/\n$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedSplit = selected.split('\n');
|
||||||
|
|
||||||
|
if (!wrap) {
|
||||||
|
lastNewLine = textArea.value.substr(0, textArea.selectionStart).lastIndexOf('\n');
|
||||||
|
|
||||||
|
// Check whether the current line is empty or consists only of spaces(=handle as empty)
|
||||||
|
if (/^\s*$/.test(textArea.value.substring(lastNewLine, textArea.selectionStart))) {
|
||||||
|
currentLineEmpty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : '';
|
||||||
|
|
||||||
|
if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) {
|
||||||
|
if (blockTag != null && blockTag !== '') {
|
||||||
|
insertText = this.blockTagText(text, textArea, blockTag, selected);
|
||||||
|
} else {
|
||||||
|
insertText = selectedSplit.map(function(val) {
|
||||||
|
if (val.indexOf(tag) === 0) {
|
||||||
|
return "" + (val.replace(tag, ''));
|
||||||
|
} else {
|
||||||
|
return "" + tag + val;
|
||||||
|
}
|
||||||
|
}).join('\n');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
insertText = "" + startChar + tag + selected + (wrap ? tag : ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedFirstNewLine) {
|
||||||
|
insertText = '\n' + insertText;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedLastNewLine) {
|
||||||
|
insertText += '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.queryCommandSupported('insertText')) {
|
||||||
|
inserted = document.execCommand('insertText', false, insertText);
|
||||||
|
}
|
||||||
|
if (!inserted) {
|
||||||
|
try {
|
||||||
|
document.execCommand("ms-beginUndoUnit");
|
||||||
|
} catch (error) {}
|
||||||
|
textArea.value = this.replaceRange(text, textArea.selectionStart, textArea.selectionEnd, insertText);
|
||||||
|
try {
|
||||||
|
document.execCommand("ms-endUndoUnit");
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
return this.moveCursor(textArea, tag, wrap, removedLastNewLine);
|
||||||
|
};
|
||||||
|
|
||||||
|
textUtils.moveCursor = function(textArea, tag, wrapped, removedLastNewLine) {
|
||||||
|
var pos;
|
||||||
|
if (!textArea.setSelectionRange) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (textArea.selectionStart === textArea.selectionEnd) {
|
||||||
|
if (wrapped) {
|
||||||
|
pos = textArea.selectionStart - tag.length;
|
||||||
|
} else {
|
||||||
|
pos = textArea.selectionStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedLastNewLine) {
|
||||||
|
pos -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return textArea.setSelectionRange(pos, pos);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
textUtils.updateText = function(textArea, tag, blockTag, wrap) {
|
||||||
|
var $textArea, selected, text;
|
||||||
|
$textArea = $(textArea);
|
||||||
|
textArea = $textArea.get(0);
|
||||||
|
text = $textArea.val();
|
||||||
|
selected = this.selectedText(text, textArea);
|
||||||
|
$textArea.focus();
|
||||||
|
return this.insertText(textArea, text, tag, blockTag, selected, wrap);
|
||||||
|
};
|
||||||
|
|
||||||
|
textUtils.init = function(form) {
|
||||||
|
var self;
|
||||||
|
self = this;
|
||||||
|
return $('.js-md', form).off('click').on('click', function() {
|
||||||
|
var $this;
|
||||||
|
$this = $(this);
|
||||||
|
return self.updateText($this.closest('.md-area').find('textarea'), $this.data('md-tag'), $this.data('md-block'), !$this.data('md-prepend'));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
textUtils.removeListeners = function(form) {
|
||||||
|
return $('.js-md', form).off('click');
|
||||||
|
};
|
||||||
|
|
||||||
|
textUtils.replaceRange = function(s, start, end, substitute) {
|
||||||
|
return s.substring(0, start) + substitute + s.substring(end);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default textUtils;
|
|
@ -1,18 +1,13 @@
|
||||||
/* eslint-disable import/prefer-default-export, func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */
|
/**
|
||||||
|
* Adds a , to a string composed by numbers, at every 3 chars.
|
||||||
import 'vendor/latinise';
|
*
|
||||||
|
* 2333 -> 2,333
|
||||||
var base;
|
* 232324 -> 232,324
|
||||||
var w = window;
|
*
|
||||||
if (w.gl == null) {
|
* @param {String} text
|
||||||
w.gl = {};
|
* @returns {String}
|
||||||
}
|
*/
|
||||||
if ((base = w.gl).text == null) {
|
export const addDelimiter = text => (text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : text);
|
||||||
base.text = {};
|
|
||||||
}
|
|
||||||
gl.text.addDelimiter = function(text) {
|
|
||||||
return text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : text;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns '99+' for numbers bigger than 99.
|
* Returns '99+' for numbers bigger than 99.
|
||||||
|
@ -20,178 +15,43 @@ gl.text.addDelimiter = function(text) {
|
||||||
* @param {Number} count
|
* @param {Number} count
|
||||||
* @return {Number|String}
|
* @return {Number|String}
|
||||||
*/
|
*/
|
||||||
export function highCountTrim(count) {
|
export const highCountTrim = count => (count > 99 ? '99+' : count);
|
||||||
return count > 99 ? '99+' : count;
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.text.randomString = function() {
|
/**
|
||||||
return Math.random().toString(36).substring(7);
|
* Converst first char to uppercase and replaces undercores with spaces
|
||||||
};
|
* @param {String} string
|
||||||
gl.text.replaceRange = function(s, start, end, substitute) {
|
* @requires {String}
|
||||||
return s.substring(0, start) + substitute + s.substring(end);
|
|
||||||
};
|
|
||||||
gl.text.getTextWidth = function(text, font) {
|
|
||||||
/**
|
|
||||||
* Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
|
|
||||||
*
|
|
||||||
* @param {String} text The text to be rendered.
|
|
||||||
* @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
|
|
||||||
*
|
|
||||||
* @see http://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
|
|
||||||
*/
|
*/
|
||||||
// re-use canvas object for better performance
|
export const humanize = string => string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
|
||||||
var canvas = gl.text.getTextWidth.canvas || (gl.text.getTextWidth.canvas = document.createElement('canvas'));
|
|
||||||
var context = canvas.getContext('2d');
|
|
||||||
context.font = font;
|
|
||||||
return context.measureText(text).width;
|
|
||||||
};
|
|
||||||
gl.text.selectedText = function(text, textarea) {
|
|
||||||
return text.substring(textarea.selectionStart, textarea.selectionEnd);
|
|
||||||
};
|
|
||||||
gl.text.lineBefore = function(text, textarea) {
|
|
||||||
var split;
|
|
||||||
split = text.substring(0, textarea.selectionStart).trim().split('\n');
|
|
||||||
return split[split.length - 1];
|
|
||||||
};
|
|
||||||
gl.text.lineAfter = function(text, textarea) {
|
|
||||||
return text.substring(textarea.selectionEnd).trim().split('\n')[0];
|
|
||||||
};
|
|
||||||
gl.text.blockTagText = function(text, textArea, blockTag, selected) {
|
|
||||||
var lineAfter, lineBefore;
|
|
||||||
lineBefore = this.lineBefore(text, textArea);
|
|
||||||
lineAfter = this.lineAfter(text, textArea);
|
|
||||||
if (lineBefore === blockTag && lineAfter === blockTag) {
|
|
||||||
// To remove the block tag we have to select the line before & after
|
|
||||||
if (blockTag != null) {
|
|
||||||
textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
|
|
||||||
textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1);
|
|
||||||
}
|
|
||||||
return selected;
|
|
||||||
} else {
|
|
||||||
return blockTag + "\n" + selected + "\n" + blockTag;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
gl.text.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
|
|
||||||
var insertText, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
|
|
||||||
removedLastNewLine = false;
|
|
||||||
removedFirstNewLine = false;
|
|
||||||
currentLineEmpty = false;
|
|
||||||
|
|
||||||
// Remove the first newline
|
/**
|
||||||
if (selected.indexOf('\n') === 0) {
|
* Adds an 's' to the end of the string when count is bigger than 0
|
||||||
removedFirstNewLine = true;
|
* @param {String} str
|
||||||
selected = selected.replace(/\n+/, '');
|
* @param {Number} count
|
||||||
}
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
export const pluralize = (str, count) => str + (count > 1 || count === 0 ? 's' : '');
|
||||||
|
|
||||||
// Remove the last newline
|
/**
|
||||||
if (textArea.selectionEnd - textArea.selectionStart > selected.replace(/\n$/, '').length) {
|
* Replaces underscores with dashes
|
||||||
removedLastNewLine = true;
|
* @param {*} str
|
||||||
selected = selected.replace(/\n$/, '');
|
* @returns {String}
|
||||||
}
|
*/
|
||||||
|
export const dasherize = str => str.replace(/[_\s]+/g, '-');
|
||||||
|
|
||||||
selectedSplit = selected.split('\n');
|
/**
|
||||||
|
* Removes accents and converts to lower case
|
||||||
|
* @param {String} str
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
export const slugify = str => str.trim().toLowerCase();
|
||||||
|
|
||||||
if (!wrap) {
|
/**
|
||||||
lastNewLine = textArea.value.substr(0, textArea.selectionStart).lastIndexOf('\n');
|
* Truncates given text
|
||||||
|
*
|
||||||
|
* @param {String} string
|
||||||
|
* @param {Number} maxLength
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
export const truncate = (string, maxLength) => `${string.substr(0, (maxLength - 3))}...`;
|
||||||
|
|
||||||
// Check whether the current line is empty or consists only of spaces(=handle as empty)
|
|
||||||
if (/^\s*$/.test(textArea.value.substring(lastNewLine, textArea.selectionStart))) {
|
|
||||||
currentLineEmpty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : '';
|
|
||||||
|
|
||||||
if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) {
|
|
||||||
if (blockTag != null && blockTag !== '') {
|
|
||||||
insertText = this.blockTagText(text, textArea, blockTag, selected);
|
|
||||||
} else {
|
|
||||||
insertText = selectedSplit.map(function(val) {
|
|
||||||
if (val.indexOf(tag) === 0) {
|
|
||||||
return "" + (val.replace(tag, ''));
|
|
||||||
} else {
|
|
||||||
return "" + tag + val;
|
|
||||||
}
|
|
||||||
}).join('\n');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
insertText = "" + startChar + tag + selected + (wrap ? tag : ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removedFirstNewLine) {
|
|
||||||
insertText = '\n' + insertText;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removedLastNewLine) {
|
|
||||||
insertText += '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.queryCommandSupported('insertText')) {
|
|
||||||
inserted = document.execCommand('insertText', false, insertText);
|
|
||||||
}
|
|
||||||
if (!inserted) {
|
|
||||||
try {
|
|
||||||
document.execCommand("ms-beginUndoUnit");
|
|
||||||
} catch (error) {}
|
|
||||||
textArea.value = this.replaceRange(text, textArea.selectionStart, textArea.selectionEnd, insertText);
|
|
||||||
try {
|
|
||||||
document.execCommand("ms-endUndoUnit");
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
|
||||||
return this.moveCursor(textArea, tag, wrap, removedLastNewLine);
|
|
||||||
};
|
|
||||||
gl.text.moveCursor = function(textArea, tag, wrapped, removedLastNewLine) {
|
|
||||||
var pos;
|
|
||||||
if (!textArea.setSelectionRange) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (textArea.selectionStart === textArea.selectionEnd) {
|
|
||||||
if (wrapped) {
|
|
||||||
pos = textArea.selectionStart - tag.length;
|
|
||||||
} else {
|
|
||||||
pos = textArea.selectionStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removedLastNewLine) {
|
|
||||||
pos -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return textArea.setSelectionRange(pos, pos);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
gl.text.updateText = function(textArea, tag, blockTag, wrap) {
|
|
||||||
var $textArea, selected, text;
|
|
||||||
$textArea = $(textArea);
|
|
||||||
textArea = $textArea.get(0);
|
|
||||||
text = $textArea.val();
|
|
||||||
selected = this.selectedText(text, textArea);
|
|
||||||
$textArea.focus();
|
|
||||||
return this.insertText(textArea, text, tag, blockTag, selected, wrap);
|
|
||||||
};
|
|
||||||
gl.text.init = function(form) {
|
|
||||||
var self;
|
|
||||||
self = this;
|
|
||||||
return $('.js-md', form).off('click').on('click', function() {
|
|
||||||
var $this;
|
|
||||||
$this = $(this);
|
|
||||||
return self.updateText($this.closest('.md-area').find('textarea'), $this.data('md-tag'), $this.data('md-block'), !$this.data('md-prepend'));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
gl.text.removeListeners = function(form) {
|
|
||||||
return $('.js-md', form).off('click');
|
|
||||||
};
|
|
||||||
gl.text.humanize = function(string) {
|
|
||||||
return string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
|
|
||||||
};
|
|
||||||
gl.text.pluralize = function(str, count) {
|
|
||||||
return str + (count > 1 || count === 0 ? 's' : '');
|
|
||||||
};
|
|
||||||
gl.text.truncate = function(string, maxLength) {
|
|
||||||
return string.substr(0, (maxLength - 3)) + '...';
|
|
||||||
};
|
|
||||||
gl.text.dasherize = function(str) {
|
|
||||||
return str.replace(/[_\s]+/g, '-');
|
|
||||||
};
|
|
||||||
gl.text.slugify = function(str) {
|
|
||||||
return str.trim().toLowerCase().latinise();
|
|
||||||
};
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ import './commit/image_file';
|
||||||
import { handleLocationHash } from './lib/utils/common_utils';
|
import { handleLocationHash } from './lib/utils/common_utils';
|
||||||
import './lib/utils/datetime_utility';
|
import './lib/utils/datetime_utility';
|
||||||
import './lib/utils/pretty_time';
|
import './lib/utils/pretty_time';
|
||||||
import './lib/utils/text_utility';
|
|
||||||
import './lib/utils/url_utility';
|
import './lib/utils/url_utility';
|
||||||
|
|
||||||
// behaviors
|
// behaviors
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'vendor/jquery.waitforimages';
|
||||||
import TaskList from './task_list';
|
import TaskList from './task_list';
|
||||||
import './merge_request_tabs';
|
import './merge_request_tabs';
|
||||||
import IssuablesHelper from './helpers/issuables_helper';
|
import IssuablesHelper from './helpers/issuables_helper';
|
||||||
|
import { addDelimiter } from './lib/utils/text_utility';
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
this.MergeRequest = (function() {
|
this.MergeRequest = (function() {
|
||||||
|
@ -124,7 +125,7 @@ import IssuablesHelper from './helpers/issuables_helper';
|
||||||
const $el = $('.nav-links .js-merge-counter');
|
const $el = $('.nav-links .js-merge-counter');
|
||||||
const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0);
|
const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0);
|
||||||
|
|
||||||
$el.text(gl.text.addDelimiter(count));
|
$el.text(addDelimiter(count));
|
||||||
};
|
};
|
||||||
|
|
||||||
MergeRequest.prototype.hideCloseButton = function() {
|
MergeRequest.prototype.hideCloseButton = function() {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import tooltip from '../../../vue_shared/directives/tooltip';
|
import tooltip from '../../../vue_shared/directives/tooltip';
|
||||||
import icon from '../../../vue_shared/components/icon.vue';
|
import icon from '../../../vue_shared/components/icon.vue';
|
||||||
|
import { dasherize } from '../../../lib/utils/text_utility';
|
||||||
/**
|
/**
|
||||||
* Renders either a cancel, retry or play icon pointing to the given path.
|
* Renders either a cancel, retry or play icon pointing to the given path.
|
||||||
* TODO: Remove UJS from here and use an async request instead.
|
* TODO: Remove UJS from here and use an async request instead.
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
cssClass() {
|
cssClass() {
|
||||||
const actionIconDash = gl.text.dasherize(this.actionIcon);
|
const actionIconDash = dasherize(this.actionIcon);
|
||||||
return `${actionIconDash} js-icon-${actionIconDash}`;
|
return `${actionIconDash} js-icon-${actionIconDash}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import tooltip from '../../vue_shared/directives/tooltip';
|
import tooltip from '../../vue_shared/directives/tooltip';
|
||||||
import '../../lib/utils/text_utility';
|
import { pluralize } from '../../lib/utils/text_utility';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MRWidgetHeader',
|
name: 'MRWidgetHeader',
|
||||||
|
@ -14,7 +14,7 @@ export default {
|
||||||
return this.mr.divergedCommitsCount > 0;
|
return this.mr.divergedCommitsCount > 0;
|
||||||
},
|
},
|
||||||
commitsText() {
|
commitsText() {
|
||||||
return gl.text.pluralize('commit', this.mr.divergedCommitsCount);
|
return pluralize('commit', this.mr.divergedCommitsCount);
|
||||||
},
|
},
|
||||||
branchNameClipboardData() {
|
branchNameClipboardData() {
|
||||||
// This supports code in app/assets/javascripts/copy_to_clipboard.js that
|
// This supports code in app/assets/javascripts/copy_to_clipboard.js that
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import bp from './breakpoints';
|
import bp from './breakpoints';
|
||||||
|
import { slugify } from './lib/utils/text_utility';
|
||||||
|
|
||||||
export default class Wikis {
|
export default class Wikis {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -23,7 +24,7 @@ export default class Wikis {
|
||||||
if (!this.newWikiForm) return;
|
if (!this.newWikiForm) return;
|
||||||
|
|
||||||
const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
|
const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
|
||||||
const slug = gl.text.slugify(slugInput.value);
|
const slug = slugify(slugInput.value);
|
||||||
|
|
||||||
if (slug.length > 0) {
|
if (slug.length > 0) {
|
||||||
const wikisPath = slugInput.getAttribute('data-wikis-path');
|
const wikisPath = slugInput.getAttribute('data-wikis-path');
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Export text utils functions as es6 module and add tests
|
||||||
|
merge_request:
|
||||||
|
author:
|
||||||
|
type: other
|
|
@ -0,0 +1,62 @@
|
||||||
|
import textUtils from '~/lib/utils/text_markdown';
|
||||||
|
|
||||||
|
describe('init markdown', () => {
|
||||||
|
let textArea;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
textArea = document.createElement('textarea');
|
||||||
|
document.querySelector('body').appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
textArea.parentNode.removeChild(textArea);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('without selection', () => {
|
||||||
|
it('inserts the tag on an empty line', () => {
|
||||||
|
const initialValue = '';
|
||||||
|
|
||||||
|
textArea.value = initialValue;
|
||||||
|
textArea.selectionStart = 0;
|
||||||
|
textArea.selectionEnd = 0;
|
||||||
|
|
||||||
|
textUtils.insertText(textArea, textArea.value, '*', null, '', false);
|
||||||
|
|
||||||
|
expect(textArea.value).toEqual(`${initialValue}* `);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('inserts the tag on a new line if the current one is not empty', () => {
|
||||||
|
const initialValue = 'some text';
|
||||||
|
|
||||||
|
textArea.value = initialValue;
|
||||||
|
textArea.setSelectionRange(initialValue.length, initialValue.length);
|
||||||
|
|
||||||
|
textUtils.insertText(textArea, textArea.value, '*', null, '', false);
|
||||||
|
|
||||||
|
expect(textArea.value).toEqual(`${initialValue}\n* `);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('inserts the tag on the same line if the current line only contains spaces', () => {
|
||||||
|
const initialValue = ' ';
|
||||||
|
|
||||||
|
textArea.value = initialValue;
|
||||||
|
textArea.setSelectionRange(initialValue.length, initialValue.length);
|
||||||
|
|
||||||
|
textUtils.insertText(textArea, textArea.value, '*', null, '', false);
|
||||||
|
|
||||||
|
expect(textArea.value).toEqual(`${initialValue}* `);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('inserts the tag on the same line if the current line only contains tabs', () => {
|
||||||
|
const initialValue = '\t\t\t';
|
||||||
|
|
||||||
|
textArea.value = initialValue;
|
||||||
|
textArea.setSelectionRange(initialValue.length, initialValue.length);
|
||||||
|
|
||||||
|
textUtils.insertText(textArea, textArea.value, '*', null, '', false);
|
||||||
|
|
||||||
|
expect(textArea.value).toEqual(`${initialValue}* `);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,109 +1,57 @@
|
||||||
import { highCountTrim } from '~/lib/utils/text_utility';
|
import * as textUtils from '~/lib/utils/text_utility';
|
||||||
|
|
||||||
describe('text_utility', () => {
|
describe('text_utility', () => {
|
||||||
describe('gl.text.getTextWidth', () => {
|
describe('addDelimiter', () => {
|
||||||
it('returns zero width when no text is passed', () => {
|
it('should add a delimiter to the given string', () => {
|
||||||
expect(gl.text.getTextWidth('')).toBe(0);
|
expect(textUtils.addDelimiter('1234')).toEqual('1,234');
|
||||||
|
expect(textUtils.addDelimiter('222222')).toEqual('222,222');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns zero width when no text is passed and font is passed', () => {
|
it('should not add a delimiter if string contains no numbers', () => {
|
||||||
expect(gl.text.getTextWidth('', '100px sans-serif')).toBe(0);
|
expect(textUtils.addDelimiter('aaaa')).toEqual('aaaa');
|
||||||
});
|
|
||||||
|
|
||||||
it('returns width when text is passed', () => {
|
|
||||||
expect(gl.text.getTextWidth('foo') > 0).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns bigger width when font is larger', () => {
|
|
||||||
const largeFont = gl.text.getTextWidth('foo', '100px sans-serif');
|
|
||||||
const regular = gl.text.getTextWidth('foo', '10px sans-serif');
|
|
||||||
expect(largeFont > regular).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('gl.text.pluralize', () => {
|
|
||||||
it('returns pluralized', () => {
|
|
||||||
expect(gl.text.pluralize('test', 2)).toBe('tests');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns pluralized when count is 0', () => {
|
|
||||||
expect(gl.text.pluralize('test', 0)).toBe('tests');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not return pluralized', () => {
|
|
||||||
expect(gl.text.pluralize('test', 1)).toBe('test');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('highCountTrim', () => {
|
describe('highCountTrim', () => {
|
||||||
it('returns 99+ for count >= 100', () => {
|
it('returns 99+ for count >= 100', () => {
|
||||||
expect(highCountTrim(105)).toBe('99+');
|
expect(textUtils.highCountTrim(105)).toBe('99+');
|
||||||
expect(highCountTrim(100)).toBe('99+');
|
expect(textUtils.highCountTrim(100)).toBe('99+');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns exact number for count < 100', () => {
|
it('returns exact number for count < 100', () => {
|
||||||
expect(highCountTrim(45)).toBe(45);
|
expect(textUtils.highCountTrim(45)).toBe(45);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('gl.text.insertText', () => {
|
describe('humanize', () => {
|
||||||
let textArea;
|
it('should remove underscores and uppercase the first letter', () => {
|
||||||
|
expect(textUtils.humanize('foo_bar')).toEqual('Foo bar');
|
||||||
beforeAll(() => {
|
});
|
||||||
textArea = document.createElement('textarea');
|
|
||||||
document.querySelector('body').appendChild(textArea);
|
|
||||||
textArea.focus();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
describe('pluralize', () => {
|
||||||
textArea.parentNode.removeChild(textArea);
|
it('should pluralize given string', () => {
|
||||||
|
expect(textUtils.pluralize('test', 2)).toBe('tests');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('without selection', () => {
|
it('should pluralize when count is 0', () => {
|
||||||
it('inserts the tag on an empty line', () => {
|
expect(textUtils.pluralize('test', 0)).toBe('tests');
|
||||||
const initialValue = '';
|
|
||||||
|
|
||||||
textArea.value = initialValue;
|
|
||||||
textArea.selectionStart = 0;
|
|
||||||
textArea.selectionEnd = 0;
|
|
||||||
|
|
||||||
gl.text.insertText(textArea, textArea.value, '*', null, '', false);
|
|
||||||
|
|
||||||
expect(textArea.value).toEqual(`${initialValue}* `);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('inserts the tag on a new line if the current one is not empty', () => {
|
it('should not pluralize when count is 1', () => {
|
||||||
const initialValue = 'some text';
|
expect(textUtils.pluralize('test', 1)).toBe('test');
|
||||||
|
});
|
||||||
textArea.value = initialValue;
|
|
||||||
textArea.setSelectionRange(initialValue.length, initialValue.length);
|
|
||||||
|
|
||||||
gl.text.insertText(textArea, textArea.value, '*', null, '', false);
|
|
||||||
|
|
||||||
expect(textArea.value).toEqual(`${initialValue}\n* `);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('inserts the tag on the same line if the current line only contains spaces', () => {
|
describe('dasherize', () => {
|
||||||
const initialValue = ' ';
|
it('should replace underscores with dashes', () => {
|
||||||
|
expect(textUtils.dasherize('foo_bar_foo')).toEqual('foo-bar-foo');
|
||||||
textArea.value = initialValue;
|
});
|
||||||
textArea.setSelectionRange(initialValue.length, initialValue.length);
|
|
||||||
|
|
||||||
gl.text.insertText(textArea, textArea.value, '*', null, '', false);
|
|
||||||
|
|
||||||
expect(textArea.value).toEqual(`${initialValue}* `);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('inserts the tag on the same line if the current line only contains tabs', () => {
|
describe('slugify', () => {
|
||||||
const initialValue = '\t\t\t';
|
it('should remove accents and convert to lower case', () => {
|
||||||
|
expect(textUtils.slugify('João')).toEqual('joão');
|
||||||
textArea.value = initialValue;
|
|
||||||
textArea.setSelectionRange(initialValue.length, initialValue.length);
|
|
||||||
|
|
||||||
gl.text.insertText(textArea, textArea.value, '*', null, '', false);
|
|
||||||
|
|
||||||
expect(textArea.value).toEqual(`${initialValue}* `);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue