BigW Consortium Gitlab
Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
G
gitlab-ce
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Forest Godfrey
gitlab-ce
Commits
1ff3f1a4
Commit
1ff3f1a4
authored
Nov 13, 2017
by
Filipa Lacerda
Committed by
Phil Hughes
Nov 13, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Export text utils as ES6 modules
parent
c6a48f3f
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
326 additions
and
301 deletions
+326
-301
abuse_reports.js
app/assets/javascripts/abuse_reports.js
+3
-1
footer.js
app/assets/javascripts/boards/components/modal/footer.js
+2
-1
commits.js
app/assets/javascripts/commits.js
+3
-1
create_label.js
app/assets/javascripts/create_label.js
+2
-1
cycle_analytics_store.js
...sets/javascripts/cycle_analytics/cycle_analytics_store.js
+2
-2
environment_item.vue
.../javascripts/environments/components/environment_item.vue
+2
-2
gl_form.js
app/assets/javascripts/gl_form.js
+3
-2
issue.js
app/assets/javascripts/issue.js
+2
-2
common_utils.js
app/assets/javascripts/lib/utils/common_utils.js
+0
-1
datetime_utility.js
app/assets/javascripts/lib/utils/datetime_utility.js
+3
-2
text_markdown.js
app/assets/javascripts/lib/utils/text_markdown.js
+153
-0
text_utility.js
app/assets/javascripts/lib/utils/text_utility.js
+44
-184
main.js
app/assets/javascripts/main.js
+0
-1
merge_request.js
app/assets/javascripts/merge_request.js
+2
-1
action_component.vue
...vascripts/pipelines/components/graph/action_component.vue
+2
-2
mr_widget_header.js
...s/vue_merge_request_widget/components/mr_widget_header.js
+2
-2
wikis.js
app/assets/javascripts/wikis.js
+2
-1
text-utils.yml
changelogs/unreleased/text-utils.yml
+5
-0
text_markdown_spec.js
spec/javascripts/lib/utils/text_markdown_spec.js
+62
-0
text_utility_spec.js
spec/javascripts/lib/utils/text_utility_spec.js
+32
-84
latinise.js
vendor/assets/javascripts/latinise.js
+0
-11
No files found.
app/assets/javascripts/abuse_reports.js
View file @
1ff3f1a4
import
{
truncate
}
from
'./lib/utils/text_utility'
;
const
MAX_MESSAGE_LENGTH
=
500
;
const
MESSAGE_CELL_SELECTOR
=
'.abuse-reports .message'
;
...
...
@@ -15,7 +17,7 @@ export default class AbuseReports {
if
(
reportMessage
.
length
>
MAX_MESSAGE_LENGTH
)
{
$messageCellElement
.
data
(
'original-message'
,
reportMessage
);
$messageCellElement
.
data
(
'message-truncated'
,
'true'
);
$messageCellElement
.
text
(
window
.
gl
.
text
.
truncate
(
reportMessage
,
MAX_MESSAGE_LENGTH
));
$messageCellElement
.
text
(
truncate
(
reportMessage
,
MAX_MESSAGE_LENGTH
));
}
}
...
...
app/assets/javascripts/boards/components/modal/footer.js
View file @
1ff3f1a4
...
...
@@ -3,6 +3,7 @@
import
Vue
from
'vue'
;
import
Flash
from
'../../../flash'
;
import
'./lists_dropdown'
;
import
{
pluralize
}
from
'../../../lib/utils/text_utility'
;
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
...
...
@@ -21,7 +22,7 @@ gl.issueBoards.ModalFooter = Vue.extend({
submitText
()
{
const
count
=
ModalStore
.
selectedCount
();
return
`Add
${
count
>
0
?
count
:
''
}
${
gl
.
text
.
pluralize
(
'issue'
,
count
)}
`
;
return
`Add
${
count
>
0
?
count
:
''
}
${
pluralize
(
'issue'
,
count
)}
`
;
},
},
methods
:
{
...
...
app/assets/javascripts/commits.js
View file @
1ff3f1a4
...
...
@@ -3,6 +3,8 @@
prefer-template, object-shorthand, prefer-arrow-callback */
/* global Pager */
import
{
pluralize
}
from
'./lib/utils/text_utility'
;
export
default
(
function
()
{
const
CommitsList
=
{};
...
...
@@ -86,7 +88,7 @@ export default (function () {
// Update commits count in the previous commits header.
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'
));
...
...
app/assets/javascripts/create_label.js
View file @
1ff3f1a4
/* eslint-disable func-names, prefer-arrow-callback */
import
Api
from
'./api'
;
import
{
humanize
}
from
'./lib/utils/text_utility'
;
export
default
class
CreateLabelDropdown
{
constructor
(
$el
,
namespacePath
,
projectPath
)
{
...
...
@@ -107,7 +108,7 @@ export default class CreateLabelDropdown {
errors
=
label
.
message
;
}
else
{
errors
=
Object
.
keys
(
label
.
message
).
map
(
key
=>
`
${
gl
.
text
.
humanize
(
key
)}
${
label
.
message
[
key
].
join
(
', '
)}
`
,
`
${
humanize
(
key
)}
${
label
.
message
[
key
].
join
(
', '
)}
`
,
).
join
(
'<br/>'
);
}
...
...
app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
View file @
1ff3f1a4
/* eslint-disable no-param-reassign */
import
{
__
}
from
'../locale'
;
import
'../lib/utils/text_utility'
;
import
{
dasherize
}
from
'../lib/utils/text_utility'
;
import
DEFAULT_EVENT_OBJECTS
from
'./default_event_objects'
;
const
EMPTY_STAGE_TEXTS
=
{
...
...
@@ -36,7 +36,7 @@ export default {
});
newData
.
stages
.
forEach
((
item
)
=>
{
const
stageSlug
=
gl
.
text
.
dasherize
(
item
.
name
.
toLowerCase
());
const
stageSlug
=
dasherize
(
item
.
name
.
toLowerCase
());
item
.
active
=
false
;
item
.
isUserAllowed
=
data
.
permissions
[
stageSlug
];
item
.
emptyStageText
=
EMPTY_STAGE_TEXTS
[
stageSlug
];
...
...
app/assets/javascripts/environments/components/environment_item.vue
View file @
1ff3f1a4
...
...
@@ -2,7 +2,7 @@
import
Timeago
from
'timeago.js'
;
import
_
from
'underscore'
;
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
ExternalUrlComponent
from
'./environment_external_url.vue'
;
import
StopComponent
from
'./environment_stop.vue'
;
...
...
@@ -139,7 +139,7 @@ export default {
if
(
this
.
hasManualActions
)
{
return
this
.
model
.
last_deployment
.
manual_actions
.
map
((
action
)
=>
{
const
parsedAction
=
{
name
:
gl
.
text
.
humanize
(
action
.
name
),
name
:
humanize
(
action
.
name
),
play_path
:
action
.
play_path
,
playable
:
action
.
playable
,
};
...
...
app/assets/javascripts/gl_form.js
View file @
1ff3f1a4
...
...
@@ -2,6 +2,7 @@
import
GfmAutoComplete
from
'./gfm_auto_complete'
;
import
dropzoneInput
from
'./dropzone_input'
;
import
textUtils
from
'./lib/utils/text_markdown'
;
export
default
class
GLForm
{
constructor
(
form
,
enableGFM
=
false
)
{
...
...
@@ -46,7 +47,7 @@ export default class GLForm {
}
// form and textarea event listeners
this
.
addEventListeners
();
gl
.
text
.
init
(
this
.
form
);
textUtils
.
init
(
this
.
form
);
// hide discard button
this
.
form
.
find
(
'.js-note-discard'
).
hide
();
this
.
form
.
show
();
...
...
@@ -85,7 +86,7 @@ export default class GLForm {
clearEventListeners
()
{
this
.
textarea
.
off
(
'focus'
);
this
.
textarea
.
off
(
'blur'
);
gl
.
text
.
removeListeners
(
this
.
form
);
textUtils
.
removeListeners
(
this
.
form
);
}
addEventListeners
()
{
...
...
app/assets/javascripts/issue.js
View file @
1ff3f1a4
/* 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
'~
/lib/utils/text_utility'
;
import
{
addDelimiter
}
from
'.
/lib/utils/text_utility'
;
import
Flash
from
'./flash'
;
import
TaskList
from
'./task_list'
;
import
CreateMergeRequestDropdown
from
'./create_merge_request_dropdown'
;
...
...
@@ -73,7 +73,7 @@ export default class Issue {
let
numProjectIssues
=
Number
(
projectIssuesCounter
.
first
().
text
().
trim
().
replace
(
/
[^\d]
/
,
''
));
numProjectIssues
=
isClosed
?
numProjectIssues
-
1
:
numProjectIssues
+
1
;
projectIssuesCounter
.
text
(
gl
.
text
.
addDelimiter
(
numProjectIssues
));
projectIssuesCounter
.
text
(
addDelimiter
(
numProjectIssues
));
if
(
this
.
createMergeRequestDropdown
)
{
if
(
isClosed
)
{
...
...
app/assets/javascripts/lib/utils/common_utils.js
View file @
1ff3f1a4
...
...
@@ -172,7 +172,6 @@ export const getSelectedFragment = () => {
return
documentFragment
;
};
// TODO: Update this name, there is a gl.text.insertText function.
export
const
insertText
=
(
target
,
text
)
=>
{
// Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas
const
selectionStart
=
target
.
selectionStart
;
...
...
app/assets/javascripts/lib/utils/datetime_utility.js
View file @
1ff3f1a4
...
...
@@ -2,6 +2,7 @@
import
timeago
from
'timeago.js'
;
import
dateFormat
from
'vendor/date.format'
;
import
{
pluralize
}
from
'./text_utility'
;
import
{
lang
,
...
...
@@ -143,9 +144,9 @@ export function timeIntervalInWords(intervalInSeconds) {
let
text
=
''
;
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
{
text
=
`
${
seconds
}
${
gl
.
text
.
pluralize
(
'second'
,
seconds
)}
`
;
text
=
`
${
seconds
}
${
pluralize
(
'second'
,
seconds
)}
`
;
}
return
text
;
}
app/assets/javascripts/lib/utils/text_markdown.js
0 → 100644
View file @
1ff3f1a4
/* 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
;
app/assets/javascripts/lib/utils/text_utility.js
View file @
1ff3f1a4
/* 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 */
import
'vendor/latinise'
;
var
base
;
var
w
=
window
;
if
(
w
.
gl
==
null
)
{
w
.
gl
=
{};
}
if
((
base
=
w
.
gl
).
text
==
null
)
{
base
.
text
=
{};
}
gl
.
text
.
addDelimiter
=
function
(
text
)
{
return
text
?
text
.
toString
().
replace
(
/
\B(?=(\d{3})
+
(?!\d))
/g
,
","
)
:
text
;
};
/**
* Adds a , to a string composed by numbers, at every 3 chars.
*
* 2333 -> 2,333
* 232324 -> 232,324
*
* @param {String} text
* @returns {String}
*/
export
const
addDelimiter
=
text
=>
(
text
?
text
.
toString
().
replace
(
/
\B(?=(\d{3})
+
(?!\d))
/g
,
','
)
:
text
);
/**
* Returns '99+' for numbers bigger than 99.
...
...
@@ -20,178 +15,43 @@ gl.text.addDelimiter = function(text) {
* @param {Number} count
* @return {Number|String}
*/
export
function
highCountTrim
(
count
)
{
return
count
>
99
?
'99+'
:
count
;
}
gl
.
text
.
randomString
=
function
()
{
return
Math
.
random
().
toString
(
36
).
substring
(
7
);
};
gl
.
text
.
replaceRange
=
function
(
s
,
start
,
end
,
substitute
)
{
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
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
)))
{
currentLineEmpty
=
true
;
}
}
startChar
=
!
wrap
&&
!
currentLineEmpty
&&
textArea
.
selectionStart
>
0
?
'
\
n'
:
''
;
export
const
highCountTrim
=
count
=>
(
count
>
99
?
'99+'
:
count
);
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
:
' '
);
}
/**
* Converst first char to uppercase and replaces undercores with spaces
* @param {String} string
* @requires {String}
*/
export
const
humanize
=
string
=>
string
.
charAt
(
0
).
toUpperCase
()
+
string
.
replace
(
/_/g
,
' '
).
slice
(
1
);
if
(
removedFirstNewLine
)
{
insertText
=
'
\
n'
+
insertText
;
}
/**
* Adds an 's' to the end of the string when count is bigger than 0
* @param {String} str
* @param {Number} count
* @returns {String}
*/
export
const
pluralize
=
(
str
,
count
)
=>
str
+
(
count
>
1
||
count
===
0
?
's'
:
''
);
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
);
}
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
;
}
/**
* Removes accents and converts to lower case
* @param {String} str
* @returns {String}
*/
export
const
slugify
=
str
=>
str
.
trim
().
toLowerCase
();
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
();
};
app/assets/javascripts/main.js
View file @
1ff3f1a4
...
...
@@ -30,7 +30,6 @@ import './commit/image_file';
import
{
handleLocationHash
}
from
'./lib/utils/common_utils'
;
import
'./lib/utils/datetime_utility'
;
import
'./lib/utils/pretty_time'
;
import
'./lib/utils/text_utility'
;
import
'./lib/utils/url_utility'
;
// behaviors
...
...
app/assets/javascripts/merge_request.js
View file @
1ff3f1a4
...
...
@@ -5,6 +5,7 @@ import 'vendor/jquery.waitforimages';
import
TaskList
from
'./task_list'
;
import
'./merge_request_tabs'
;
import
IssuablesHelper
from
'./helpers/issuables_helper'
;
import
{
addDelimiter
}
from
'./lib/utils/text_utility'
;
(
function
()
{
this
.
MergeRequest
=
(
function
()
{
...
...
@@ -124,7 +125,7 @@ import IssuablesHelper from './helpers/issuables_helper';
const
$el
=
$
(
'.nav-links .js-merge-counter'
);
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
()
{
...
...
app/assets/javascripts/pipelines/components/graph/action_component.vue
View file @
1ff3f1a4
<
script
>
import
tooltip
from
'../../../vue_shared/directives/tooltip'
;
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.
* TODO: Remove UJS from here and use an async request instead.
...
...
@@ -39,7 +39,7 @@
computed
:
{
cssClass
()
{
const
actionIconDash
=
gl
.
text
.
dasherize
(
this
.
actionIcon
);
const
actionIconDash
=
dasherize
(
this
.
actionIcon
);
return
`
${
actionIconDash
}
js-icon-
${
actionIconDash
}
`
;
},
},
...
...
app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
View file @
1ff3f1a4
import
tooltip
from
'../../vue_shared/directives/tooltip'
;
import
'../../lib/utils/text_utility'
;
import
{
pluralize
}
from
'../../lib/utils/text_utility'
;
export
default
{
name
:
'MRWidgetHeader'
,
...
...
@@ -14,7 +14,7 @@ export default {
return
this
.
mr
.
divergedCommitsCount
>
0
;
},
commitsText
()
{
return
gl
.
text
.
pluralize
(
'commit'
,
this
.
mr
.
divergedCommitsCount
);
return
pluralize
(
'commit'
,
this
.
mr
.
divergedCommitsCount
);
},
branchNameClipboardData
()
{
// This supports code in app/assets/javascripts/copy_to_clipboard.js that
...
...
app/assets/javascripts/wikis.js
View file @
1ff3f1a4
import
bp
from
'./breakpoints'
;
import
{
slugify
}
from
'./lib/utils/text_utility'
;
export
default
class
Wikis
{
constructor
()
{
...
...
@@ -23,7 +24,7 @@ export default class Wikis {
if
(
!
this
.
newWikiForm
)
return
;
const
slugInput
=
this
.
newWikiForm
.
querySelector
(
'#new_wiki_path'
);
const
slug
=
gl
.
text
.
slugify
(
slugInput
.
value
);
const
slug
=
slugify
(
slugInput
.
value
);
if
(
slug
.
length
>
0
)
{
const
wikisPath
=
slugInput
.
getAttribute
(
'data-wikis-path'
);
...
...
changelogs/unreleased/text-utils.yml
0 → 100644
View file @
1ff3f1a4
---
title
:
Export text utils functions as es6 module and add tests
merge_request
:
author
:
type
:
other
spec/javascripts/lib/utils/text_markdown_spec.js
0 → 100644
View file @
1ff3f1a4
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
}
* `
);
});
});
});
spec/javascripts/lib/utils/text_utility_spec.js
View file @
1ff3f1a4
import
{
highCountTrim
}
from
'~/lib/utils/text_utility'
;
import
*
as
textUtils
from
'~/lib/utils/text_utility'
;
describe
(
'text_utility'
,
()
=>
{
describe
(
'gl.text.getTextWidth'
,
()
=>
{
it
(
'returns zero width when no text is passed'
,
()
=>
{
expect
(
gl
.
text
.
getTextWidth
(
''
)).
toBe
(
0
);
describe
(
'addDelimiter'
,
()
=>
{
it
(
'should add a delimiter to the given string'
,
()
=>
{
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'
,
()
=>
{
expect
(
gl
.
text
.
getTextWidth
(
''
,
'100px sans-serif'
)).
toBe
(
0
);
});
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'
);
it
(
'should not add a delimiter if string contains no numbers'
,
()
=>
{
expect
(
textUtils
.
addDelimiter
(
'aaaa'
)).
toEqual
(
'aaaa'
);
});
});
describe
(
'highCountTrim'
,
()
=>
{
it
(
'returns 99+ for count >= 100'
,
()
=>
{
expect
(
highCountTrim
(
105
)).
toBe
(
'99+'
);
expect
(
highCountTrim
(
100
)).
toBe
(
'99+'
);
expect
(
textUtils
.
highCountTrim
(
105
)).
toBe
(
'99+'
);
expect
(
textUtils
.
highCountTrim
(
100
)).
toBe
(
'99+'
);
});
it
(
'returns exact number for count < 100'
,
()
=>
{
expect
(
highCountTrim
(
45
)).
toBe
(
45
);
expect
(
textUtils
.
highCountTrim
(
45
)).
toBe
(
45
);
});
});
describe
(
'gl.text.insertText'
,
()
=>
{
let
textArea
;
beforeAll
(()
=>
{
textArea
=
document
.
createElement
(
'textarea'
);
document
.
querySelector
(
'body'
).
appendChild
(
textArea
);
textArea
.
focus
();
describe
(
'humanize'
,
()
=>
{
it
(
'should remove underscores and uppercase the first letter'
,
()
=>
{
expect
(
textUtils
.
humanize
(
'foo_bar'
)).
toEqual
(
'Foo bar'
);
});
});
afterAll
(()
=>
{
textArea
.
parentNode
.
removeChild
(
textArea
);
describe
(
'pluralize'
,
()
=>
{
it
(
'should pluralize given string'
,
()
=>
{
expect
(
textUtils
.
pluralize
(
'test'
,
2
)).
toBe
(
'tests'
);
});
describe
(
'without selection'
,
()
=>
{
it
(
'inserts the tag on an empty line'
,
()
=>
{
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'
,
()
=>
{
const
initialValue
=
'some text'
;
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'
,
()
=>
{
const
initialValue
=
' '
;
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'
,
()
=>
{
const
initialValue
=
'
\
t
\
t
\
t'
;
it
(
'should pluralize when count is 0'
,
()
=>
{
expect
(
textUtils
.
pluralize
(
'test'
,
0
)).
toBe
(
'tests'
);
});
textArea
.
value
=
initialValue
;
textArea
.
setSelectionRange
(
initialValue
.
length
,
initialValue
.
length
);
it
(
'should not pluralize when count is 1'
,
()
=>
{
expect
(
textUtils
.
pluralize
(
'test'
,
1
)).
toBe
(
'test'
);
});
});
gl
.
text
.
insertText
(
textArea
,
textArea
.
value
,
'*'
,
null
,
''
,
false
);
describe
(
'dasherize'
,
()
=>
{
it
(
'should replace underscores with dashes'
,
()
=>
{
expect
(
textUtils
.
dasherize
(
'foo_bar_foo'
)).
toEqual
(
'foo-bar-foo'
);
});
});
expect
(
textArea
.
value
).
toEqual
(
`
${
initialValue
}
* `
);
});
describe
(
'slugify'
,
()
=>
{
it
(
'should remove accents and convert to lower case'
,
()
=>
{
expect
(
textUtils
.
slugify
(
'João'
)).
toEqual
(
'joão'
);
});
});
});
vendor/assets/javascripts/latinise.js
deleted
100644 → 0
View file @
c6a48f3f
// 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"
:
"ff"
,
"ffi"
:
"ffi"
,
"ffl"
:
"ffl"
,
"fi"
:
"fi"
,
"fl"
:
"fl"
,
"ij"
:
"ij"
,
"œ"
:
"oe"
,
"st"
:
"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
;
});
};
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment