Unverified Commit 1e8b4b80 authored by µKöff's avatar µKöff

Roughly migrated to TypeScript

parent b613d74c
Pipeline #96152091 passed with stage
in 1 minute and 38 seconds
### START: https://github.com/github/gitignore/blob/master/Node.gitignore ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# gatsby files
.cache/
public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
### END: https://github.com/github/gitignore/blob/master/Node.gitignore ###
\ No newline at end of file
stages:
- build
- deploy
cache:
key: "default"
paths:
- node_modules/
build_typescript:
image: node:latest
stage: build
script:
- npm install
- npm run build
artifacts:
paths:
- public
pages:
stage: deploy
script:
- echo ""
artifacts:
paths:
- public
only:
- master
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build",
"group": "build",
"problemMatcher": []
}
]
}
\ No newline at end of file
MIT License
Copyright (c) 2018 naseweis520
Copyright (c) 2018 μKöff
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......
# WikiRsolve
# Rsolver for Wikidata
......@@ -2,19 +2,17 @@
<html class="stickyfooter" lang="en">
<head>
<meta charset="utf-8" />
<meta content="nw520" name="author" />
<meta content="width=device-width,initial-scale=1,shrink-to-fit=no" name="viewport" />
<title>WikiRsolve &laquo; Wikidata Tools</title>
<title><%= htmlWebpackPlugin.options.title %></title>
<meta name="author" content="nw520" />
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous" />
<link rel="stylesheet" href="data/css/style.css" />
<link rel="stylesheet" href="data/components/tooltipster/4.0/css/tooltipster.bundle.min.css" />
<link rel="stylesheet" href="data/components/tooltipster/4.0/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-light.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-select@1.13.10/dist/css/bootstrap-select.min.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#">WikiRsolve</a>
<a class="navbar-brand" href="#"><%= htmlWebpackPlugin.options.title %></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggler" aria-controls="navbarToggler" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
......@@ -120,9 +118,9 @@
<footer class="footer">
<div class="container text-right">
<span class="text-muted">
Tool made by <a href="https://nw520.de/" target="_blank" rel="noopener">nw520</a> <small>(<a href="https://www.wikidata.org/wiki/User:Nw520" target="_blank" rel="noopener">@ Wikidata</a>)</small>.<br/>
Tool made by <a href="https://muekev.de/" target="_blank" rel="noopener">nw520</a> <small>(<a href="https://www.wikidata.org/wiki/User:Nw520" target="_blank" rel="noopener">@ Wikidata</a>)</small>.<br/>
Thanks to <a href="https://www.wikidata.org/wiki/User:Tagishsimon" target="_blank" rel="noopener">Tagishsimon</a> for providing me with a <a href="https://www.wikidata.org/wiki/Wikidata:Request_a_query/Archive/2018/05#Check_whether_a_given_list_of_element_IDs_fullfills_certain_criteria_and_return_list_of_matching_items" target="_blank" rel="noopener">SPARQL query</a>.<br/>
<a href="https://github.com/muekoeff/wikirsolve" target="_blank" rel="noopener">Source code @ GitHub</a> &bull; <a href="https://github.com/muekoeff/wikirsolve/issues" target="_blank" rel="noopener">Report bugs @ GitHub</a>
<a href="https://gitlab.com/muekoeff/rsolver/" target="_blank" rel="noopener">Source code @ GitLab</a> &bull; <a href="https://gitlab.com/muekoeff/rsolver//issues" target="_blank" rel="noopener">Report bugs @ GitLab</a>
</span>
</div>
</footer>
......@@ -196,10 +194,10 @@
</div>
<div class="modal-body">
<h2>Help</h2>
<p><b>WikiR<span style="color:#bbb">e</span>solve</b> is a tool for automatically resolving Wikidata entity ids of a given list of search queries.</p>
<p><b>R<span style="color:#bbb">e</span>solver</b> is a tool for automatically resolving Wikidata entity ids of a given list of search queries.</p>
<h3>Syntax</h3>
<p>The syntax of WikiRsolve is based on the one of <a href="https://tools.wmflabs.org/quickstatements/" target="_blank" rel="noopener">QuickStatements v1</a> and, if the operations succeed, will be processed to valid QuickStatements v1 statements.</p>
<p>The syntax of Rsolver is based on the one of <a href="https://tools.wmflabs.org/quickstatements/" target="_blank" rel="noopener">QuickStatements v1</a> and, if the operations succeed, will be processed to valid QuickStatements v1 statements.</p>
<p>A simple query which consists of only one <i><a href="#help_command_lines">command line</a></i> could for example look like this:<br/>
<code>Wikidata||P31=Q14827288<span class="highlight-tab"></span>operator|P|P1629=Q29933786<span class="highlight-tab"></span>Wikimedia Foundation|Q|^enwiki<span class="highlight-tab"></span>$P275<span class="highlight-tab"></span>$Q6938433</code>
</p>
......@@ -339,16 +337,6 @@
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script src="data/components/tooltipster/4.0/js/tooltipster.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-select@1.13.10/dist/js/bootstrap-select.min.js"></script>
<script src="data/js/condition.js"></script>
<script src="data/js/requestManager.js"></script>
<script src="data/js/settings.js"></script>
<script src="data/js/taskQueue.js"></script>
<script src="data/js/tuple.js"></script>
<script src="data/js/uiRow.js"></script>
<script src="data/js/utils.js"></script>
<script src="data/js/wikiUtils.js"></script>
<script src="data/js/script.js"></script>
</body>
</html>
class Condition {
constructor(mode, property, value) {
import Tuple from './tuple'
export default class Condition {
mode : string
property : string
value : string
constructor(mode : string, property : string, value : string) {
this.mode = mode;
this.property = property;
this.value = value;
}
static filterItem(tuple, elements, results, callback) {
static extendData(oldItem : any, newItem : any, langCode : string) {
if(typeof newItem.descriptions[langCode] != "undefined") oldItem.description = newItem.descriptions[langCode].value;
if(typeof newItem.labels[langCode] != "undefined") oldItem.label = newItem.labels[langCode].value;
}
static filterItem(tuple : Tuple, elements : any[], results : any, langCode : string, callback : (filteredItems : any) => void) {
var elementsNew = elements.slice(0);
$.ajax({
......@@ -13,7 +23,7 @@ class Condition {
request.setRequestHeader("Accept", "application/json, text/plain, */*");
},
data: {
"query": `SELECT ?item ?itemLabel WHERE { VALUES ?item {${resultsToIdList(results).join(" ")}} ${generateConditions(tuple).join("")} SERVICE wikibase:label { bd:serviceParam wikibase:language "${$("#setting-language-item").val().toLowerCase()},en". } }`
"query": `SELECT ?item ?itemLabel WHERE { VALUES ?item {${resultsToIdList(results).join(" ")}} ${generateConditions(tuple).join("")} SERVICE wikibase:label { bd:serviceParam wikibase:language "${langCode},en". } }`
},
type: "GET",
url: "https://query.wikidata.org/sparql"
......@@ -21,7 +31,7 @@ class Condition {
var sparqlMatches = sparqlResultToIdList(e);
$.each(elements, function(index, candidate) {
if(sparqlMatches.indexOf(candidate.id) > -1) {
extendData(elementsNew[index], results.entities[candidate.id]);
Condition.extendData(elementsNew[index], results.entities[candidate.id], langCode);
} else {
elementsNew[index] = null;
}
......@@ -34,9 +44,9 @@ class Condition {
callback(elements); // Don't filter
});
function generateConditions(tuple) {
var rules = [];
$.each(tuple.conditions, function(a, b) {
function generateConditions(tuple : Tuple) {
var rules : string[] = [];
$.each(<Condition[]>tuple.conditions, function(_, b) {
switch(b.mode) {
case "=":
rules.push(`?item wdt:${b.property} wd:${b.value} .`);
......@@ -48,23 +58,23 @@ class Condition {
});
return rules;
}
function sparqlResultToIdList(results) {
var idList = [];
function sparqlResultToIdList(results : any) {
var idList : string[] = [];
$.each(results.results.bindings, function(a, b) {
idList.push(b.item.value.replace("http://www.wikidata.org/entity/", ""));
});
return idList;
}
function resultsToIdList(results) {
var idList = [];
function resultsToIdList(results : any) {
var idList : string[] = [];
$.each(results.entities, function(index, candidate) {
idList.push(`wd:${candidate.id}`);
});
return idList;
}
}
static filterItems(tuple, elements, callback) {
var ids = [];
static filterItems(tuple : Tuple, elements : any[], langCode : string, callback: (filteredElements: any[]) => void) {
var ids : string[] = [];
$.each(elements, function(index, item) {
ids.push(item.id);
});
......@@ -73,14 +83,14 @@ class Condition {
data: {
"action": "wbgetentities",
"format": "json",
"languages": `${$("#setting-language-item").val().toLowerCase()}|en`,
"languages": `${(<string>$("#setting-language-item").val()).toLowerCase()}|en`,
"origin": "*",
"ids": ids.join("|")
},
url: "https://www.wikidata.org/w/api.php"
}).done(function(e) {
if(tuple.conditions.length > 0) {
Condition.filterItem(tuple, elements, e, callback);
Condition.filterItem(tuple, elements, e, langCode, callback);
} else {
callback(elements);
}
......@@ -89,7 +99,7 @@ class Condition {
callback(elements);
});
}
static parse(conditions) {
static parse(conditions : string) {
var rules = [];
if(conditions.startsWith("^")) {
rules.push(new Condition("^", "", conditions.substr(1)));
......
class RequestManager {
import Condition from './condition'
import Settings from './settings'
import TaskQueue from './taskQueue'
import Tuple from './tuple'
import Util from './util'
import WikiUtils from './wikiUtils'
export default class RequestManager {
requestQueue : TaskQueue<{tuple: Tuple, rowIndex: number, tupleIndex: number}>
constructor() {
this.requestQueue = new TaskQueue();
}
enqueueRequest(tuple, rowIndex, tupleIndex) {
enqueueRequest(tuple : Tuple, rowIndex : number, tupleIndex : number) {
this.requestQueue.enqueueTask(this._performRequest, {
tuple: tuple,
rowIndex: rowIndex,
......@@ -11,13 +20,12 @@ class RequestManager {
});
}
_performRequest(requestQueue, data) {
this.performing += 1;
_performRequest(requestQueue : TaskQueue<{tuple: Tuple, rowIndex: number, tupleIndex: number}>, data : {tuple: Tuple, rowIndex: number, tupleIndex: number}) {
data.tuple.ui.status = "working";
data.tuple.ui.update();
if(data.tuple.conditions.length == 1 && data.tuple.conditions[0].mode == "^") {
var host = WikiUtils.getHost(data.tuple.conditions[0].value);
if(data.tuple.conditions.length == 1 && (<Condition[]>data.tuple.conditions)[0].mode == "^") {
var host = WikiUtils.getHost((<Condition[]>data.tuple.conditions)[0].value);
if(host != null) {
$.ajax({
data: {
......@@ -57,7 +65,7 @@ class RequestManager {
"action": "wbsearchentities",
"format": "json",
"language": $("#setting-language-search").val(),
"limit": Utils.validateNumber($("#setting-disambiguation-candidatesnumber").val(), 7),
"limit": Util.validateNumber(Settings.getDisambiguationCandidatesnumber(), 7),
"origin": "*",
"search": data.tuple.searchquery,
"type": data.tuple.type.toLowerCase() == "q" ? "item" : "property",
......@@ -71,7 +79,7 @@ class RequestManager {
data.tuple.ui.status = "no results";
data.tuple.ui.update();
} else {
Condition.filterItems(data.tuple, e.search, finalize);
Condition.filterItems(data.tuple, e.search, Settings.getLanguageItem(), finalize);
}
}).fail(function(e) {
data.tuple.ui.status = "error";
......@@ -80,7 +88,7 @@ class RequestManager {
});
}
function finalize(items) {
function finalize(items : any[]) {
if(items.length == 1) {
data.tuple.ui.status = "success";
data.tuple.result = items[0].id;
......
class Settings {
import Util from './util'
require("tooltipster")
require("tooltipster/dist/css/tooltipster.bundle.css")
require("tooltipster/dist/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-light.min.css")
export default class Settings {
static attachLiveHandler() {
$("#setting-candidateremoval").change(function(e) {
if(this.checked) {
if((<HTMLInputElement>this).checked) {
Settings.disableConditionalStyle("candidateremoval");
} else {
Settings.enableConditionalStyle("candidateremoval", `.tool-candidateremoval {
......@@ -10,7 +15,7 @@ class Settings {
}
});
$("#setting-disambiguation-matchhighlight").change(function(e) {
if(this.checked) {
if((<HTMLInputElement>this).checked) {
Settings.enableConditionalStyle("disambiguation-matchhighlight", `.tool-matchhighlight {
background-color: rgba(255,255,0,.8);
}`);
......@@ -22,11 +27,11 @@ class Settings {
$("#setting-candidateremoval").change();
$("#setting-disambiguation-matchhighlight").change();
}
static disableConditionalStyle(name) {
static disableConditionalStyle(name : string) {
var element = $(`style[data-setting='${name}']`);
if(element.length != 0) element.remove();
}
static enableConditionalStyle(name, style) {
static enableConditionalStyle(name : string, style : string) {
if($(`style[data-setting='${name}']`).length == 0) $("head").append(`<style data-setting="${name}">${style}</style>`);
}
static export() {
......@@ -39,6 +44,12 @@ class Settings {
};
return settings;
}
static getDisambiguationCandidatesnumber() {
return Util.validateNumber(<number>$("#setting-disambiguation-candidatesnumber").val(), 7);
}
static getLanguageItem() {
return <string>$("#setting-language-item").val();
}
static initialize() {
$("#button-settings-geturl").click(function(e) {
e.preventDefault();
......@@ -95,14 +106,15 @@ class Settings {
}
}
function apply(settings) {
function apply(settings : any) {
assign(settings, "candidateremoval", x => $("#setting-candidateremoval").prop("checked", x));
assign(settings, "disambiguation-candidatesnumber", x => $("#setting-disambiguation-candidatesnumber").val(x));
assign(settings, "disambiguation-matchhighlight", x => $("#setting-disambiguation-matchhighlight").prop("checked", x));
assign(settings, "language-item", x => $("#setting-language-item").val(x));
assign(settings, "language-search", x => $("#setting-language-search").val(x));
}
function assign(settings, key, assignFunction) {
function assign(settings : any, key : string, assignFunction : (value : string) => void) {
if(typeof settings[key] != "undefined") assignFunction(settings[key]);
}
}
......
class TaskQueue {
constructor(max) {
export default class TaskQueue<DataT> {
max : number
nextPointer : number
performing : number
queue : [(taskQueue: TaskQueue<DataT>, data: DataT) => void, DataT][]
constructor(max? : number) {
this.max = (max || 5);
this.nextPointer = 0;
this.queue = [];
......@@ -9,7 +14,7 @@ class TaskQueue {
this.performing -= 1;
this.work();
}
enqueueTask(task, data) {
enqueueTask(task : (taskQueue: TaskQueue<DataT>, data: DataT) => void, data : DataT) {
if(typeof data == "undefined") data = null;
this.queue.push([task, data]);
this.work();
......
class Tuple {
constructor(searchquery, type, conditions) {
import UiRow from './uiRow'
import Condition from './condition'
export default class Tuple {
searchquery : string
type : string
conditions : string|Condition[]
result : string
ui : UiRow
constructor(searchquery : string, type? : string, conditions? : (string|Condition[])) {
this.searchquery = searchquery;
if(typeof type != "undefined" && type !== "") this.type = type; else this.type = "q";
if(typeof conditions != "undefined" && conditions !== "") this.conditions = conditions; else this.conditions = [];
......@@ -7,9 +16,10 @@ class Tuple {
this.result = null;
}
attachUi(ui) {
attachUi(ui : UiRow) {
this.ui = ui;
}
validate() {
if(this.type.toLowerCase() != "p" && this.type.toLowerCase() != "q") return false;
return true;
......
class UiRow {
constructor(rowIndex, tupleIndex, tuple, status, disambiguation, disambiguationRaw) {
import Tuple from './tuple'
import Util from './util'
export default class UiRow {
rowIndex : number
tupleIndex : number
tuple : Tuple
status : string
disambiguation : string[]
disambiguationCache : any
disambiguationCell : JQuery<HTMLElement>
resultCell : JQuery<HTMLElement>
statusCell : JQuery<HTMLElement>
constructor(rowIndex : number, tupleIndex : number, tuple : Tuple, status? : string, disambiguation? : string[], disambiguationRaw? : any) {
this.rowIndex = rowIndex;
this.tupleIndex = tupleIndex;
this.tuple = tuple;
if(typeof status != "undefined") this.status = status; else this.status = "pending";
if(typeof disambiguation != "undefined") this.disambiguation = disambiguation; else this.disambiguation = null;
if(typeof disambiguationRaw != "undefined") this.disambiguationRaw = disambiguationRaw; else this.disambiguationRaw = null;
if(typeof disambiguationRaw != "undefined") this.disambiguationCache = disambiguationRaw; else this.disambiguationCache = null;
this.disambiguationCell = null;
this.resultCell = null;
......@@ -18,24 +31,24 @@ class UiRow {
this.generateDisambiguation();
this.update();
}
generateDisambiguation(result) {
var output = [];
generateDisambiguation(result? : any[]) {
var output : string[] = [];
var ui = this;
if(typeof result == "undefined" && this.disambiguationRaw != null) result = this.disambiguationRaw;
if(typeof result != "undefined") this.disambiguationRaw = result;
if(typeof result == "undefined" && this.disambiguationCache != null) result = this.disambiguationCache;
if(typeof result != "undefined") this.disambiguationCache = result;
$.each(result, function(a, b) {
$.each(result, function(_, b) {
if(b != null) {
output.push(`<span class="${ui.tuple.result == b.id ? "mode-selected" : ""}" title="${b.description || "-"}"><a href="${b.concepturi}" data-item="${b.id}">${getHighlightedWord(b.label, ui.tuple.searchquery)}</a> <small>(<a href="${b.concepturi}" target="_blank">${b.id}</a>, ${b.description || "-"})</small></span>`);
}
});
this.disambiguation = output;
function getHighlightedWord(word, originalWord) {
function getHighlightedWord(word : string, originalWord : string) {
var indexOf = word.toLowerCase().indexOf(originalWord.toLowerCase());
word = _e(word);
originalWord = _e(originalWord);
word = Util.sanitize(word);
originalWord = Util.sanitize(originalWord);
if(indexOf >= 0) {
return `${word.substr(0, indexOf)}<span class="tool-matchhighlight">${word.substr(indexOf, originalWord.length)}</span>${word.substring(indexOf + originalWord.length)}`;
......@@ -52,7 +65,7 @@ class UiRow {
var $row = $(`<tr class="status-${this.status.replace(/ /g, "-")}" data-rowindex="${this.rowIndex}" data-tupleindex="${this.tupleIndex}"></tr>`);
$row.append(`<th scope="row">${this.rowIndex + 1}</th>`);
$row.append(this.statusCell);
$row.append(`<td>${_e(this.tuple.searchquery)}</td>`);
$row.append(`<td>${Util.sanitize(this.tuple.searchquery)}</td>`);
$row.append(this.resultCell);
$row.append(this.disambiguationCell);
......@@ -72,34 +85,34 @@ class UiRow {
attachRemoveListener(this);
if(this.disambiguation != null) attachDisambiguationListener(this);
function attachDisambiguationListener(ui) {
$(ui.getRow().children("td")[3]).find("a[data-item]").each(function(a, b) {
function attachDisambiguationListener(uiRow : UiRow) {
$(uiRow.getRow().children("td")[3]).find("a[data-item]").each(function(a, b) {
$(b).click(function(e) {
e.preventDefault();
ui.tuple.result = $(this).attr("data-item");
ui.status = "success";
ui.generateDisambiguation();
ui.update();
uiRow.tuple.result = $(this).attr("data-item");
uiRow.status = "success";
uiRow.generateDisambiguation();
uiRow.update();
});
});
}
function attachRemoveListener(ui) {
$(ui.getRow().find("a[data-action='remove-candidate']")).click(function(e) {
function attachRemoveListener(uiRow : UiRow) {
$(uiRow.getRow().find("a[data-action='remove-candidate']")).click(function(e) {
e.preventDefault();
ui.clearResult();
uiRow.clearResult();
});
}
function updateDisambiguationCell(uiRow) {
function updateDisambiguationCell(uiRow : UiRow) {
if(uiRow.disambiguationCell != null) {
uiRow.disambiguationCell.html(uiRow.disambiguation == null ? "" : uiRow.disambiguation.join("<br/>"));
}
}
function updateResultCell(uiRow) {
function updateResultCell(uiRow : UiRow) {
if(uiRow.resultCell != null) {
uiRow.resultCell.html(uiRow.tuple.result != null ? `<a href="https://www.wikidata.org/wiki/${uiRow.tuple.result}" target="_blank">${uiRow.tuple.result}</a><small class="tool-candidateremoval">&nbsp;[<a href="#" data-action="remove-candidate">&times;</a>]</small>` : "");
}
}
function updateStatusCell(uiRow) {
function updateStatusCell(uiRow : UiRow) {
if(uiRow.statusCell != null) {
uiRow.statusCell.find("span").text(uiRow.status);
}
......
class Utils {
static arrayUnique(array) {
export default class Util {
static arrayUnique(array : any[]) {
var a = array.concat();
for(var i=0; i<a.length; ++i) {
for(var j=i+1; j<a.length; ++j) {
......@@ -9,15 +9,16 @@ class Utils {
return a;
}
static copyTextToClipboard(text) {
static copyTextToClipboard(text : string) {
var textArea = document.createElement("textarea");
textArea.style.position = 'fixed';
textArea.style.top = 0;
textArea.style.left = 0;
textArea.style.top = '0';
textArea.style.left = '0';
textArea.style.width = '2em';
textArea.style.height = '2em';
textArea.style.padding = 0;
textArea.style.padding = '0';
textArea.style.border = 'none';
textArea.style.outline = 'none';
textArea.style.boxShadow = 'none';
......@@ -34,16 +35,31 @@ class Utils {
document.body.removeChild(textArea);
}
static validateNumber(i, fallback) {
// Source: https://stackoverflow.com/questions/295566/sanitize-rewrite-html-on-the-client-side
static sanitize(input: string) {
var tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*';
var tagOrComment = new RegExp(
'<(?:'
// Comment body.
+ '!--(?:(?:-*[^->])*--+|-?)'
// Special "raw text" elements whose content should be elided.
+ '|script\\b' + tagBody + '>[\\s\\S]*?</script\\s*'
+ '|style\\b' + tagBody + '>[\\s\\S]*?</style\\s*'
// Regular name
+ '|/?[a-z]'
+ tagBody
+ ')>',
'gi');
var oldHtml : string;
do {
oldHtml = input;
input = input.replace(tagOrComment, '');
} while (input !== oldHtml);
return input.replace(/</g, '&lt;');
}
static validateNumber(i : number, fallback : number) {
return (isNaN(i) ? fallback : i);
}
}
/**
* Sanitises a given string