blob: 011156bd6e474051a6d9be372e4e45d6a7b798f9 [file] [log] [blame]
"use strict";
/*
Todo:
- limit the view based on prefix matches
- highlight matching substrings
*/
// Don't let events bubble up
Event.prototype.halt = function () {
this.stopPropagation();
this.preventDefault();
};
// http://www.nlpado.de/~sebastian/software/ner_german.shtml
// http://www.cnts.ua.ac.be/conll2003/ner/
var namedEntities = [
["I-LOC", "I-LOC", "Location"],
["I-MISC", "I-MISC", "Miscellaneous"],
["I-ORG", "I-ORG", "Organization"],
["I-PER", "I-PER", "Person"]
];
// http://www.ids-mannheim.de/cosmas2/projekt/referenz/stts/morph.html
// http://nachhalt.sfb632.uni-potsdam.de/owl-docu/stts.html
var sttsArray = [
// "$.", "$(", "$,"
["ADJA","ADJA", "Attributive Adjective"],
["ADJD","ADJD", "Predicative Adjective"],
["ADV","ADV", "Adverb"],
["APPO","APPO", "Postposition"],
["APPR","APPR", "Preposition"],
["APPRART","APPRART", "Preposition with Determiner"],
["APZR","APZR","Right Circumposition"],
["ART","ART", "Determiner"],
["CARD","CARD", "Cardinal Number"],
["FM","FM", "Foreign Material"],
["ITJ","ITJ", "Interjection"],
["KOKOM","KOKOM", "Comparison Particle"],
["KON","KON", "Coordinating Conjuncion"],
["KOUI","KOUI", "Subordinating Conjunction with 'zu'"],
["KOUS","KOUS", "Subordinating Conjunction with Sentence"],
["NE","NE", "Named Entity"],
["NN","NN", "Normal Nomina"],
["PAV", "PAV", "Pronominal Adverb"],
["PDAT","PDAT","Attributive Demonstrative Pronoun"],
["PDS","PDS", "Substitutive Demonstrative Pronoun"],
["PIAT","PIAT", "Attributive Indefinite Pronoun without Determiner"],
["PIDAT","PIDAT", "Attributive Indefinite Pronoun with Determiner"],
["PIS","PIS", "Substitutive Indefinite Pronoun"],
["PPER","PPER", "Personal Pronoun"],
["PPOSAT","PPOSAT", "Attributive Possessive Pronoun"],
["PPOSS","PPOSS", "Substitutive Possessive Pronoun"],
["PRELAT","PRELAT", "Attributive Relative Pronoun"],
["PRELS","PRELS", "Substitutive Relative Pronoun"],
["PRF","PRF", "Reflexive Pronoun"],
["PROAV","PROAV", "Pronominal Adverb"],
["PTKA","PTKA","Particle with Adjective"],
["PTKANT","PTKANT", "Answering Particle"],
["PTKNEG","PTKNEG", "Negation Particle"],
["PTKVZ","PTKVZ", "Separated Verbal Particle"],
["PTKZU","PTKZU", "'zu' Particle"],
["PWAT","PWAT", "Attributive Interrogative Pronoun"],
["PWAV","PWAV", "Adverbial Interrogative Pronoun"],
["PWS","PWS", "Substitutive Interrogative Pronoun"],
["TRUNC","TRUNC","Truncated"],
["VAFIN","VAFIN", "Auxiliary Finite Verb"],
["VAINF","VAINF", "Auxiliary Infinite Verb"],
["VAIMP","VAIMP", "Auxiliary Finite Imperative Verb"],
["VAPP","VAPP", "Auxiliary Perfect Participle"],
["VMFIN","VMFIN", "Modal Finite Verb"],
["VMINF","VMINF", "Modal Infinite Verb"],
["VMPP","VMPP", "Modal Perfect Participle"],
["VVFIN","VVFIN","Finite Verb"],
["VVIMP","VVIMP", "Finite Imperative Verb"],
["VVINF","VVINF", "Infinite Verb"],
["VVIZU","VVIZU", "Infinite Verb with 'zu'"],
["VVPP","VVPP", "Perfect Participle"],
["XY", "XY", "Non-Word"]
];
var mateSttsArray = sttsArray.slice(0);
mateSttsArray.push(
["<root-POS>","<root-POS>","Root Part of Speech"]
);
var hintArray = {
"-" : [
["Connexor", "cnx/"],
["CoreNLP", "corenlp/"],
["Mate", "mate/"],
["OpenNLP", "opennlp/"],
["TreeTagger", "tt/"],
["Xerox Parser", "xip/"]
],
"corenlp/" : [
["Named Entity", "ne=" , "Combined"],
["Named Entity", "ne_dewac_175m_600=" , "ne_dewac_175m_600"],
["Named Entity", "ne_hgc_175m_600=", "ne_hgc_175m_600"]
],
"corenlp/ne=" : namedEntities,
"corenlp/ne_dewac_175m_600=" : namedEntities,
"corenlp/ne_hgc_175m_600=" : namedEntities,
"cnx/" : [
["Constituency", "c="],
["Lemma", "l="],
["Morphology", "m="],
["Part-of-Speech", "p="],
["Syntax", "syn="]
],
"cnx/c=" : [
["np", "np", "Nominal Phrase"]
],
// http://www.ids-mannheim.de/cosmas2/projekt/referenz/connexor/morph.html
"cnx/m=" : [
["Abbr","Abbr", "Nouns: Abbreviation"],
["CMP","CMP", "Adjective: Comparative"],
["IMP", "IMP", "Mood: Imperative"],
["IND", "IND", "Mood: Indicative"],
["INF", "INF", "Infinitive"],
["ORD","ORD", "Numeral: Ordinal"],
["PAST", "PAST", "Tense: past"],
["PCP", "PCP", "Participle"],
["PERF", "PERF", "Perfective Participle"],
["PL","PL", "Nouns: Plural"],
["PRES", "PRES", "Tense: present"],
["PROG", "PROG", "Progressive Participle"],
["Prop","Prop", "Nouns: Proper Noun"],
["SUB", "SUB", "Mood: Subjunctive"],
["SUP","SUP", "Adjective: Superlative"]
],
// http://www.ids-mannheim.de/cosmas2/projekt/referenz/connexor/morph.html
"cnx/p=" : [
["A", "A", "Adjective"],
["ADV", "ADV", "Adverb"],
["CC", "CC", "Coordination Marker"],
["CS", "CS", "Clause Marker"],
["DET", "DET", "Determiner"],
["INTERJ", "INTERJ", "Interjection"],
["N", "N", "Noun"],
["NUM", "NUM", "Numeral"],
["PREP", "PREP", "Preposition"],
["PRON", "PRON", "Pro-Nominal"],
["V", "V", "Verb"]
],
// http://www.ids-mannheim.de/cosmas2/projekt/referenz/connexor/syntax.html
"cnx/syn=" : [
["@ADVL", "@ADVL", "Adverbial Head"],
["@AUX", "@AUX", "Auxiliary Verb"],
["@CC", "@CC", "Coordination"]
["@MAIN", "@MAIN", "Main Verb"],
["@NH", "@NH", "Nominal Head"],
["@POSTMOD", "@POSTMOD", "Postmodifier"],
["@PREMARK", "@PREMARK", "Preposed Marker"],
["@PREMOD", "@POSTMOD", "Premodifier"]
],
"opennlp/" : [
["Part-of-Speech", "p="]
],
"opennlp/p=" : sttsArray,
"xip/" : [
["Constituency", "c="],
// Inactive: ["Dependency", "d="],
["Lemma", "l="],
["Part-of-Speech", "p="],
],
// "xip/c=" : [],
// Inactive: "xip/d=" : [],
// "xip/p=" : [],
"tt/" : [
["Lemma", "l="],
["Part-of-Speech", "p="]
],
"tt/p=" : sttsArray,
"mate/" : [
// Inactive: "d" : ["d=", "Dependency"],
["Lemma", "l="],
["Morphology", "m="],
["Part-of-Speech", "p="]
],
// Inactive: mate/d=
"mate/p=" : mateSttsArray,
"mate/m=" : [
["Case", "case:"],
["Degree", "degree:"],
["Gender", "gender:"],
["Mood", "mood:"],
["Number", "number:"],
["Person", "person:"],
["Tense","tense:"],
["No type", "<no-type>"]
],
"mate/m=case:" : [
["acc", "acc", "Accusative"],
["dat","dat", "Dative"],
["gen", "gen","Genitive"],
["nom","nom", "Nominative"],
["*","*", "Undefined"]
],
"mate/m=degree:" : [
["comp","comp", "Comparative"],
["pos","pos", "Positive"],
["sup","sup", "Superative"]
],
"mate/m=gender:" : [
["fem", "fem", "Feminium"],
["masc", "masc", "Masculinum"],
["neut","neut", "Neuter"],
["*","*","Undefined"]
],
"mate/m=mood:" : [
["imp","imp", "Imperative"],
["ind","ind", "Indicative"],
["subj","subj", "Subjunctive"]
],
"mate/m=number:" : [
["pl","pl","Plural"],
["sg","sg","Singular"],
["*","*","Undefined"]
],
"mate/m=person:" : [
["1","1", "First Person"],
["2","2", "Second Person"],
["3","3", "Third Person"]
],
"mate/m=tense:" : [
["past","past", "Past"],
["pres","pres", "Present"]
]
};
/**
* Analyze strings for prefixes
*/
var PrefixAnalyzer = {
_regex : new RegExp(
"(?:^|[^a-zA-Z0-9])" + // Anchor
"((?:[-a-zA-Z0-9]+?)\/" + // Foundry
"(?:" +
"(?:[^=:]+?)=" + // Layer
"(?:(?:[^:]+?):)?" + // Key
")?" +
")$"),
analyze : function (text) {
if (!this._regex.exec(text))
return undefined;
return RegExp.$1
}
};
/**
* Event handling after a key pressed
*/
function updateKey (that, e) {
var menu = that.menu();
switch (e.key) {
case 'Esc':
// Hide menu
menu.hide();
break;
case 'Down':
e.halt(); // No event propagation
// Menu is not active
if (!menu.active) {
that.popUp()
}
// Menu is active
else {
that.removePrefix();
menu.next();
};
break;
case "Up":
if (!menu.active)
break;
e.halt(); // No event propagation
that.removePrefix();
menu.prev();
break;
case "Enter":
if (!menu.active)
break;
e.halt(); // No event propagation
that.insertText(menu.getActiveItem().getAction());
that.removePrefix();
// Remove menu
menu.hide();
// Fill this with the correct value
// Todo: This is redundant with click function
var show;
if ((show = that.analyzeContext()) !== "-") {
menu.show(show);
menu.update(
e.target.getBoundingClientRect().right
);
};
break;
default:
if (!menu.active)
break;
// key stroke is not a character
if (e.key.length != 1) {
// Key stroke is not a text modifying key
if (e.key !== 'Shift' &&
e.key !== 'Up' &&
e.key !== 'Down' &&
e.key !== 'Enter' &&
e.key !== 'Alt' &&
e.key !== 'AltGraph' &&
e.key !== 'CapsLock') {
that.insertPrefix();
menu.hide();
};
break;
};
e.halt(); // No event propagation
// Try to identify prefix
if (menu.skipToPrefix(e.key))
break;
// Prefix not found
that.insertPrefix();
menu.hide();
};
};
// new hint object
var Hint = {
_search : undefined, // Return the search element
_mirror : undefined, // Return the mirror element
_menu : undefined,
_analyzer : undefined,
firstTry : true,
menu : function () {
// In case this wasn't defined yet
if (this._menu === undefined) {
this._menu = Object.create(Menu).init();
this._mirror.appendChild(this._menu.getElement());
};
return this._menu;
},
// Initialize the object
init : function () {
this._search = document.getElementById("q-field");
this._mirror = document.createElement("div");
this._mirror.setAttribute("id", "searchMirror");
this._mirror.appendChild(document.createElement("span"));
document.getElementsByTagName("body")[0].appendChild(this._mirror);
this._analyzer = Object.create(PrefixAnalyzer);
// Update positional information, in case the windows size changes
var that = this;
window.onresize = function () { that.reposition() };
// Add event listener for key pressed down
this._search.addEventListener(
"keydown",
function (e) {
updateKey(that, e)
},
false
);
// Reposition the mirror
this.reposition();
// Return object for chaining
return this;
},
// Popup method
popUp : function () {
if (this.active)
return;
// Reposition hint list on first try
if (this.firstTry)
this.reposition().firstTry = false;
// Update view
this.update();
// Fill this with the correct value
if (this.menu().show(this.analyzeContext()))
this.update(
this._search.getBoundingClientRect().right
);
else
this.hide();
this._search.focus();
},
// Reposition the mirror object
reposition : function () {
// Update style properties
var searchRect = this._search.getBoundingClientRect();
var searchStyle = window.getComputedStyle(this._search, null);
var mStyle = this._mirror.style;
mStyle.left = searchRect.left + "px";
mStyle.top = searchRect.bottom + "px";
mStyle.borderLeftColor = "transparent";
mStyle.height = "1px";
mStyle.paddingLeft = searchStyle.getPropertyValue("padding-left");
mStyle.marginLeft = searchStyle.getPropertyValue("margin-left");
mStyle.borderLeftWidth = searchStyle.getPropertyValue("border-left-width");
mStyle.borderLeftStyle = searchStyle.getPropertyValue("border-left-style");
mStyle.fontSize = searchStyle.getPropertyValue("font-size");
mStyle.fontFamily = searchStyle.getPropertyValue("font-family");
return this;
},
// Reposition the menu object
update : function () {
var s = this._search;
var start = s.selectionStart;
this._mirror.firstChild.textContent = s.value.substring(0, start);
},
analyzeContext : function () {
var context = this._splitInputText()[0];
if (context === undefined || context.length === 0)
return "-";
context = this._analyzer.analyze(context);
if (context === undefined || context.length === 0)
return "-";
return context;
},
_splitInputText : function () {
var s = this._search;
var value = s.value;
var start = s.selectionStart;
var begin = value.substring(0, start);
var end = value.substring(start, value.length);
return new Array(begin, end);
},
// Insert text at the current cursor position
insertText : function (text) {
var s = this._search;
var splitText = this._splitInputText();
s.value = splitText[0] + text + splitText[1];
s.selectionStart = (splitText[0] + text).length
s.selectionEnd = s.selectionStart;
this._mirror.firstChild.textContent = splitText[0] + text;
},
// Remove stored prefix
removePrefix : function () {
this.menu()._prefix = undefined;
},
// Insert stored prefix at current cursor position
insertPrefix : function () {
if (this.menu()._prefix === undefined)
return;
this.insertText(this.menu()._prefix);
}
};
/**
* Menu list
*/
var Menu = {
active : false,
_element : undefined,
_position : 0, // Position of menu item
_offset : 0, // Visual offset for chosen highlight
_size : 8, // Number of items to be shown
_items : [], // Items for menu
_name : undefined,
_prefix : undefined,
getElement : function () {
return this._element;
},
init : function () {
this._element = document.createElement("ul");
// Add onclick event
this._element.addEventListener("click", chooseHint, false);
this._element.style.opacity = 0;
this.active = false;
this._setDefaults();
return this;
},
update : function (searchRightPosition) {
var infoRightPosition = this._element.getBoundingClientRect().right;
if (infoRightPosition > searchRightPosition) {
this._element.style.marginLeft = '-' + (infoRightPosition - searchRightPosition) + 'px';
};
return this;
},
next : function () {
if (!this.active)
return;
this._clearView();
this._position++;
// In case the list is bigger than the view
if (this._items.length > this._size) {
if (this._position >= this._items.length) {
// Roll to top
this._offset = 0;
this._position = 0;
this._showItems(0);
}
else if (this._position >= (this._size + this._offset)) {
// Roll down
this._element.removeChild(this._element.firstChild);
this._offset++;
this._element.appendChild(this.getItem(this._position).getElement());
};
}
else if (this._position >= this._items.length) {
this._position = 0;
};
this._updateView();
},
prev : function () {
if (!this.active)
return;
this._clearView();
this._position--;
// In case the list is bigger than the view
if (this._items.length > this._size) {
if (this._position < 0) {
// roll to bottom
this._setToLast();
this._offset = (this._position - this._size) + 1;
this._showLastItems();
}
else if (this._position < this._offset) {
// roll up
this._element.removeChild(this._element.lastChild);
this._offset--;
this._element.insertBefore(
this.getItem(this._position).getElement(),
this._element.firstChild
);
};
}
else if (this._position < 0) {
this._setToLast();
};
this._updateView();
},
skipToPrefix : function (prefix) {
if (this._prefix === undefined)
this._prefix = prefix.toLocaleLowerCase();
else
this._prefix += prefix.toLocaleLowerCase();
var pos = 0;
var found = false;
var good = -1;
var test;
for (; pos < this._items.length; pos++) {
if ((test = this.getItem(pos).getLCName().indexOf(this._prefix)) !== -1) {
if (test === 0) {
found = true;
break;
};
good = pos;
};
};
// Perfect prefix
if (found)
return this.skipToPos(pos);
// At least infix
else if (good !== -1)
return this.skipToPos(good);
// No
return false;
},
skipToPos : function (index) {
if (!this.active)
return false;
if (index < 0 || index >= this._items.length)
return false;
this._clearView();
this._position = index;
if (index < this._offset || index >= (this._offset + this._size)) {
// Index is in the final frame
if (index >= (this._items.length - this._size)) {
this._offset = this._items.length - this._size;
this._showLastItems();
}
// Index is in the final frame
else {
this._offset = index;
this._showItems(index);
};
};
// Activate new position
this._updateView();
return true;
},
show : function (name) {
// The element is already given
if (this._name !== name) {
// Todo: store hints in hash
// Delete items
this._items.length = 0;
var items = hintArray[name];
// Hints not found
if (items === undefined)
return false;
var i;
for (i in items) {
var item = Object.create(MenuItem).init(items[i]);
this._items.push(item);
};
// Add classes for rolling menus
this.getItem(0).getElement().classList.add("no-more");
this.getItem(i).getElement().classList.add("no-more");
this._name = name;
};
this._showItems(0);
this._element.style.opacity = 1;
this._setDefaults();
this.active = true;
this._updateView();
return true;
},
hide : function () {
this._element.style.opacity = 0;
if (this.active)
this.getActiveItem().deactivate();
this._setDefaults();
this.active = false;
},
getActiveItem : function () {
return this._items[this._position];
},
getItem : function (index) {
return this._items[index];
},
getPrefix : function () {
return this._prefix;
},
_setDefaults : function () {
this._offset = 0;
this._position = 0;
this._prefix = undefined;
},
// Remove all visible list items
_deleteMenu : function () {
var child;
while (child = this._element.firstChild)
this._element.removeChild(child);
},
_clearView : function () {
var active = this.getActiveItem();
if (active !== undefined)
active.deactivate();
},
_updateView : function () {
var active = this.getActiveItem();
if (active !== undefined)
active.activate();
},
// Make all list items visible starting at a certain offset
_showItems : function (offset) {
this._deleteMenu();
for (var i = offset; i < this._size + offset; i++) {
if (i >= this._items.length)
break;
this._element.appendChild(
this._items[i].getElement()
)
};
},
// Make all final list items visible
_showLastItems : function () {
this._deleteMenu();
for (var i = (this._items.length - 1); i >= (this._items.length - this._size); i--) {
if (i < 0)
break;
if (!this._element.firstChild)
this._element.appendChild(this._items[i].getElement());
else
this._element.insertBefore(
this._items[i].getElement(),
this._element.firstChild
);
};
},
_setToLast : function () {
this._position = this._items.length - 1;
}
};
function chooseHint (e) {
var element = e.target;
while (element.nodeName == "STRONG" || element.nodeName == "SPAN") {
element = element.parentNode;
};
if (element === undefined || element.nodeName != "LI")
return;
var action = element.getAttribute('data-action');
hint.insertText(action);
var menu = hint.menu();
menu.hide();
// Fill this with the correct value
var show;
if ((show = hint.analyzeContext()) !== "-") {
menu.show(show);
menu.update(
hint._search.getBoundingClientRect().right
);
};
hint._search.focus();
};
var MenuItem = {
_name : undefined,
_lcname : undefined,
_desc : undefined,
_element : undefined,
_action : "",
activate : function () {
this._element.classList.add("active");
},
deactivate : function () {
this._element.classList.remove("active");
},
// Initialize this item
init : function (param) {
this._name = param[0];
this._action = param[1];
this._lcname = this._name.toLocaleLowerCase();
if (param.length > 2) {
this._desc = param[2];
this._lcname += " " + this._desc.toLocaleLowerCase();
};
return this;
},
// Created element of this item
getElement : function () {
if (this._element !== undefined)
return this._element;
var li = document.createElement("li");
li.setAttribute("data-action", this._action);
var name = document.createElement("strong");
name.appendChild(document.createTextNode(this._name));
li.appendChild(name);
if (this._desc !== undefined) {
var desc = document.createElement("span");
desc.appendChild(document.createTextNode(this._desc));
li.appendChild(desc);
};
this._element = li;
return this._element;
},
// Name of this item
getName : function () {
return this._name;
},
getLCName : function () {
return this._lcname;
},
// Description of this item
getDesc : function () {
return this._desc;
},
getAction : function () {
return this._action;
}
};