Commit 00a212de authored by Phil Hughes's avatar Phil Hughes

Merge branch 'text-utils' into 'master'

Export text utils as ES6 modules

Closes #40018 and #37851

See merge request gitlab-org/gitlab-ce!15320
parents c6a48f3f 1ff3f1a4
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 { ...@@ -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 @@ ...@@ -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({ ...@@ -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 @@ ...@@ -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 () { ...@@ -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'));
......
/* 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 { ...@@ -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/>');
} }
......
/* 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 { ...@@ -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 @@ ...@@ -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 { ...@@ -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 @@ ...@@ -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 { ...@@ -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 { ...@@ -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() {
......
/* 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 { ...@@ -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 = () => { ...@@ -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 @@ ...@@ -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) { ...@@ -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;
} }
/* 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;
/* 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) { ...@@ -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) {
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))) { * Adds an 's' to the end of the string when count is bigger than 0
currentLineEmpty = true; * @param {String} str
} * @param {Number} count
} * @returns {String}
*/
startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : ''; export const pluralize = (str, count) => str + (count > 1 || count === 0 ? 's' : '');
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'; * Replaces underscores with dashes
} * @param {*} str
* @returns {String}
*/
export const dasherize = str => str.replace(/[_\s]+/g, '-');
if (document.queryCommandSupported('insertText')) { /**
inserted = document.execCommand('insertText', false, insertText); * Removes accents and converts to lower case
} * @param {String} str
if (!inserted) { * @returns {String}
try { */
document.execCommand("ms-beginUndoUnit"); export const slugify = str => str.trim().toLowerCase();
} 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; * Truncates given text
} *
* @param {String} string
* @param {Number} maxLength
* @returns {String}
*/
export const truncate = (string, maxLength) => `${string.substr(0, (maxLength - 3))}...`;
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'; ...@@ -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'; ...@@ -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'; ...@@ -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() {
......
<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 @@ ...@@ -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}`;
}, },
}, },
......
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 { ...@@ -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
......
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 { ...@@ -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');
......
---
title: Export text utils functions as es6 module and add tests
merge_request:
author:
type: other
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}* `);
});
});
});
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(() => {
textArea.parentNode.removeChild(textArea);
}); });
describe('without selection', () => { describe('pluralize', () => {
it('inserts the tag on an empty line', () => { it('should pluralize given string', () => {
const initialValue = ''; expect(textUtils.pluralize('test', 2)).toBe('tests');
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 pluralize when count is 0', () => {
const initialValue = 'some text'; expect(textUtils.pluralize('test', 0)).toBe('tests');
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', () => { it('should not pluralize when count is 1', () => {
const initialValue = ' '; 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}* `);
}); });
it('inserts the tag on the same line if the current line only contains tabs', () => { describe('dasherize', () => {
const initialValue = '\t\t\t'; 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}* `);
}); });
describe('slugify', () => {
it('should remove accents and convert to lower case', () => {
expect(textUtils.slugify('João')).toEqual('joão');
}); });
}); });
}); });
// Converting text to basic latin (aka removing accents)
//
// Based on: http://semplicewebsites.com/removing-accents-javascript
//
var Latinise = {
map: {"Á":"A","Ă":"A","":"A","":"A","":"A","":"A","":"A","Ǎ":"A","Â":"A","":"A","":"A","":"A","":"A","":"A","Ä":"A","Ǟ":"A","Ȧ":"A","Ǡ":"A","":"A","Ȁ":"A","À":"A","":"A","Ȃ":"A","Ā":"A","Ą":"A","Å":"A","Ǻ":"A","":"A","Ⱥ":"A","Ã":"A","":"AA","Æ":"AE","Ǽ":"AE","Ǣ":"AE","":"AO","":"AU","":"AV","":"AV","":"AY","":"B","":"B","Ɓ":"B","":"B","Ƀ":"B","Ƃ":"B","Ć":"C","Č":"C","Ç":"C","":"C","Ĉ":"C","Ċ":"C","Ƈ":"C","Ȼ":"C","Ď":"D","":"D","":"D","":"D","":"D","Ɗ":"D","":"D","Dz":"D","Dž":"D","Đ":"D","Ƌ":"D","DZ":"DZ","DŽ":"DZ","É":"E","Ĕ":"E","Ě":"E","Ȩ":"E","":"E","Ê":"E","":"E","":"E","":"E","":"E","":"E","":"E","Ë":"E","Ė":"E","":"E","Ȅ":"E","È":"E","":"E","Ȇ":"E","Ē":"E","":"E","":"E","Ę":"E","Ɇ":"E","":"E","":"E","":"ET","":"F","Ƒ":"F","Ǵ":"G","Ğ":"G","Ǧ":"G","Ģ":"G","Ĝ":"G","Ġ":"G","Ɠ":"G","":"G","Ǥ":"G","":"H","Ȟ":"H","":"H","Ĥ":"H","":"H","":"H","":"H","":"H","Ħ":"H","Í":"I","Ĭ":"I","Ǐ":"I","Î":"I","Ï":"I","":"I","İ":"I","":"I","Ȉ":"I","Ì":"I","":"I","Ȋ":"I","Ī":"I","Į":"I","Ɨ":"I","Ĩ":"I","":"I","":"D","":"F","":"G","":"R","":"S","":"T","":"IS","Ĵ":"J","Ɉ":"J","":"K","Ǩ":"K","Ķ":"K","":"K","":"K","":"K","Ƙ":"K","":"K","":"K","":"K","Ĺ":"L","Ƚ":"L","Ľ":"L","Ļ":"L","":"L","":"L","":"L","":"L","":"L","":"L","Ŀ":"L","":"L","Lj":"L","Ł":"L","LJ":"LJ","":"M","":"M","":"M","":"M","Ń":"N","Ň":"N","Ņ":"N","":"N","":"N","":"N","Ǹ":"N","Ɲ":"N","":"N","Ƞ":"N","Nj":"N","Ñ":"N","NJ":"NJ","Ó":"O","Ŏ":"O","Ǒ":"O","Ô":"O","":"O","":"O","":"O","":"O","":"O","Ö":"O","Ȫ":"O","Ȯ":"O","Ȱ":"O","":"O","Ő":"O","Ȍ":"O","Ò":"O","":"O","Ơ":"O","":"O","":"O","":"O","":"O","":"O","Ȏ":"O","":"O","":"O","Ō":"O","":"O","":"O","Ɵ":"O","Ǫ":"O","Ǭ":"O","Ø":"O","Ǿ":"O","Õ":"O","":"O","":"O","Ȭ":"O","Ƣ":"OI","":"OO","Ɛ":"E","Ɔ":"O","Ȣ":"OU","":"P","":"P","":"P","Ƥ":"P","":"P","":"P","":"P","":"Q","":"Q","Ŕ":"R","Ř":"R","Ŗ":"R","":"R","":"R","":"R","Ȑ":"R","Ȓ":"R","":"R","Ɍ":"R","":"R","":"C","Ǝ":"E","Ś":"S","":"S","Š":"S","":"S","Ş":"S","Ŝ":"S","Ș":"S","":"S","":"S","":"S","":"SS","Ť":"T","Ţ":"T","":"T","Ț":"T","Ⱦ":"T","":"T","":"T","Ƭ":"T","":"T","Ʈ":"T","Ŧ":"T","":"A","":"L","Ɯ":"M","Ʌ":"V","":"TZ","Ú":"U","Ŭ":"U","Ǔ":"U","Û":"U","":"U","Ü":"U","Ǘ":"U","Ǚ":"U","Ǜ":"U","Ǖ":"U","":"U","":"U","Ű":"U","Ȕ":"U","Ù":"U","":"U","Ư":"U","":"U","":"U","":"U","":"U","":"U","Ȗ":"U","Ū":"U","":"U","Ų":"U","Ů":"U","Ũ":"U","":"U","":"U","":"V","":"V","Ʋ":"V","":"V","":"VY","":"W","Ŵ":"W","":"W","":"W","":"W","":"W","":"W","":"X","":"X","Ý":"Y","Ŷ":"Y","Ÿ":"Y","":"Y","":"Y","":"Y","Ƴ":"Y","":"Y","":"Y","Ȳ":"Y","Ɏ":"Y","":"Y","Ź":"Z","Ž":"Z","":"Z","":"Z","Ż":"Z","":"Z","Ȥ":"Z","":"Z","Ƶ":"Z","IJ":"IJ","Œ":"OE","":"A","":"AE","ʙ":"B","":"B","":"C","":"D","":"E","":"F","ɢ":"G","ʛ":"G","ʜ":"H","ɪ":"I","ʁ":"R","":"J","":"K","ʟ":"L","":"L","":"M","ɴ":"N","":"O","ɶ":"OE","":"O","":"OU","":"P","ʀ":"R","":"N","":"R","":"S","":"T","":"E","":"R","":"U","":"V","":"W","ʏ":"Y","":"Z","á":"a","ă":"a","":"a","":"a","":"a","":"a","":"a","ǎ":"a","â":"a","":"a","":"a","":"a","":"a","":"a","ä":"a","ǟ":"a","ȧ":"a","ǡ":"a","":"a","ȁ":"a","à":"a","":"a","ȃ":"a","ā":"a","ą":"a","":"a","":"a","å":"a","ǻ":"a","":"a","":"a","ã":"a","":"aa","æ":"ae","ǽ":"ae","ǣ":"ae","":"ao","":"au","":"av","":"av","":"ay","":"b","":"b","ɓ":"b","":"b","":"b","":"b","ƀ":"b","ƃ":"b","ɵ":"o","ć":"c","č":"c","ç":"c","":"c","ĉ":"c","ɕ":"c","ċ":"c","ƈ":"c","ȼ":"c","ď":"d","":"d","":"d","ȡ":"d","":"d","":"d","ɗ":"d","":"d","":"d","":"d","":"d","đ":"d","ɖ":"d","ƌ":"d","ı":"i","ȷ":"j","ɟ":"j","ʄ":"j","dz":"dz","dž":"dz","é":"e","ĕ":"e","ě":"e","ȩ":"e","":"e","ê":"e","ế":"e","":"e","":"e","":"e","":"e","":"e","ë":"e","ė":"e","":"e","ȅ":"e","è":"e","":"e","ȇ":"e","ē":"e","":"e","":"e","":"e","ę":"e","":"e","ɇ":"e","":"e","":"e","":"et","":"f","ƒ":"f","":"f","":"f","ǵ":"g","ğ":"g","ǧ":"g","ģ":"g","ĝ":"g","ġ":"g","ɠ":"g","":"g","":"g","ǥ":"g","":"h","ȟ":"h","":"h","ĥ":"h","":"h","":"h","":"h","":"h","ɦ":"h","":"h","ħ":"h","ƕ":"hv","í":"i","ĭ":"i","ǐ":"i","î":"i","ï":"i","":"i","":"i","ȉ":"i","ì":"i","":"i","ȋ":"i","ī":"i","į":"i","":"i","ɨ":"i","ĩ":"i","":"i","":"d","":"f","":"g","":"r","":"s","":"t","":"is","ǰ":"j","ĵ":"j","ʝ":"j","ɉ":"j","":"k","ǩ":"k","ķ":"k","":"k","":"k","":"k","ƙ":"k","":"k","":"k","":"k","":"k","ĺ":"l","ƚ":"l","ɬ":"l","ľ":"l","ļ":"l","":"l","ȴ":"l","":"l","":"l","":"l","":"l","":"l","ŀ":"l","ɫ":"l","":"l","ɭ":"l","ł":"l","lj":"lj","ſ":"s","":"s","":"s","":"s","ḿ":"m","":"m","":"m","ɱ":"m","":"m","":"m","ń":"n","ň":"n","ņ":"n","":"n","ȵ":"n","":"n","":"n","ǹ":"n","ɲ":"n","":"n","ƞ":"n","":"n","":"n","ɳ":"n","ñ":"n","nj":"nj","ó":"o","ŏ":"o","ǒ":"o","ô":"o","":"o","":"o","":"o","":"o","":"o","ö":"o","ȫ":"o","ȯ":"o","ȱ":"o","":"o","ő":"o","ȍ":"o","ò":"o","":"o","ơ":"o","":"o","":"o","":"o","":"o","":"o","ȏ":"o","":"o","":"o","":"o","ō":"o","":"o","":"o","ǫ":"o","ǭ":"o","ø":"o","ǿ":"o","õ":"o","":"o","":"o","ȭ":"o","ƣ":"oi","":"oo","ɛ":"e","":"e","ɔ":"o","":"o","ȣ":"ou","":"p","":"p","":"p","ƥ":"p","":"p","":"p","":"p","":"p","":"p","":"q","ʠ":"q","ɋ":"q","":"q","ŕ":"r","ř":"r","ŗ":"r","":"r","":"r","":"r","ȑ":"r","ɾ":"r","":"r","ȓ":"r","":"r","ɼ":"r","":"r","":"r","ɍ":"r","ɽ":"r","":"c","":"c","ɘ":"e","ɿ":"r","ś":"s","":"s","š":"s","":"s","ş":"s","ŝ":"s","ș":"s","":"s","":"s","":"s","ʂ":"s","":"s","":"s","ȿ":"s","ɡ":"g","ß":"ss","":"o","":"o","":"u","ť":"t","ţ":"t","":"t","ț":"t","ȶ":"t","":"t","":"t","":"t","":"t","ƭ":"t","":"t","":"t","ƫ":"t","ʈ":"t","ŧ":"t","":"th","ɐ":"a","":"ae","ǝ":"e","":"g","ɥ":"h","ʮ":"h","ʯ":"h","":"i","ʞ":"k","":"l","ɯ":"m","ɰ":"m","":"oe","ɹ":"r","ɻ":"r","ɺ":"r","":"r","ʇ":"t","ʌ":"v","ʍ":"w","ʎ":"y","":"tz","ú":"u","ŭ":"u","ǔ":"u","û":"u","":"u","ü":"u","ǘ":"u","ǚ":"u","ǜ":"u","ǖ":"u","":"u","":"u","ű":"u","ȕ":"u","ù":"u","":"u","ư":"u","":"u","":"u","":"u","":"u","":"u","ȗ":"u","ū":"u","":"u","ų":"u","":"u","ů":"u","ũ":"u","":"u","":"u","":"ue","":"um","":"v","":"v","ṿ":"v","ʋ":"v","":"v","":"v","":"v","":"vy","":"w","ŵ":"w","":"w","":"w","":"w","":"w","":"w","":"w","":"x","":"x","":"x","ý":"y","ŷ":"y","ÿ":"y","":"y","":"y","":"y","ƴ":"y","":"y","ỿ":"y","ȳ":"y","":"y","ɏ":"y","":"y","ź":"z","ž":"z","":"z","ʑ":"z","":"z","ż":"z","":"z","ȥ":"z","":"z","":"z","":"z","ʐ":"z","ƶ":"z","ɀ":"z","":"ff","":"ffi","":"ffl","":"fi","":"fl","ij":"ij","œ":"oe","":"st","":"a","":"e","":"i","":"j","":"o","":"r","":"u","":"v","":"x"}
};
String.prototype.latinise = function() {
return this.replace(/[^A-Za-z0-9]/g, function(x) { return Latinise.map[x] || x; });
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment