blob: ee5e344d3a443bd2c7739726f50a7abcb487a711 [file] [log] [blame]
Nils Diewald19ccee92014-12-08 11:30:08 +00001/**
Nils Diewald5c5a7472015-04-02 22:13:38 +00002 * Hint menu for Kalamar.
Nils Diewald47f366b2015-04-15 20:06:35 +00003 * Based on menu object.
Nils Diewald19ccee92014-12-08 11:30:08 +00004 *
5 * @author Nils Diewald
6 */
Akron308db382016-05-30 22:34:07 +02007/*
Akron65c74352016-09-02 17:23:39 +02008 * TODO: Check for cnx/syn=
Akron308db382016-05-30 22:34:07 +02009 * TODO: List can be shown when prefix is like 'base/s=pcorenlp/'
10 * TODO: Sometimes the drop-down box down vanish when list is shown
11 * TODO: Create should expect an input text field
12 * TODO: Embed only one single menu (not multiple)
Akron02360e42016-06-07 13:41:12 +020013 * By holding the current menu in _active
14 * TODO: show() should accept a context field (especially for no-context fields,
15 * like fragments)
16 * TODO: Improve context analyzer from hint!
17 * TODO: Marked annotations should be addable to "fragments"
Akron308db382016-05-30 22:34:07 +020018 */
Akronda5bd3a2020-10-16 17:37:49 +020019
20"use strict";
21
Nils Diewald0e6992a2015-04-14 20:13:52 +000022define([
23 'hint/input',
24 'hint/menu',
25 'hint/contextanalyzer',
Akron00cd4d12016-05-31 21:01:11 +020026 'hint/alert',
Nils Diewald0e6992a2015-04-14 20:13:52 +000027 'util'
Helgeef0a26e2023-11-22 16:59:17 +010028], function (inputClass,
Akronc14cbfc2018-08-31 13:15:55 +020029 menuClass,
30 analyzerClass,
31 alertClass) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000032
Helgeef0a26e2023-11-22 16:59:17 +010033 //needed for localization
34 const loc = KorAP.Locale;
35 loc.HINT_noAnnot = loc.HINT_noAnnot || 'The assistant can not be displayed.';
36
Nils Diewald0e6992a2015-04-14 20:13:52 +000037 // Initialize hint array
Nils Diewald5c5a7472015-04-02 22:13:38 +000038
39 /**
40 * KorAP.Hint.create({
41 * inputField : node,
42 * context : context regex
43 * });
44 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000045 return {
Nils Diewald5c5a7472015-04-02 22:13:38 +000046
Nils Diewald0e6992a2015-04-14 20:13:52 +000047 /**
48 * Create new hint helper.
49 */
Nils Diewald5c5a7472015-04-02 22:13:38 +000050 create : function (param) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000051 return Object.create(this)._init(param);
Nils Diewald5c5a7472015-04-02 22:13:38 +000052 },
53
Akronda5bd3a2020-10-16 17:37:49 +020054
Nils Diewald0e6992a2015-04-14 20:13:52 +000055 // Initialize hint helper
Nils Diewald5c5a7472015-04-02 22:13:38 +000056 _init : function (param) {
57 param = param || {};
58
59 // Holds all menus per prefix context
Leo Repp9b26ba92021-09-17 17:38:22 +020060 this._menuCollection = {};
Akron00cd4d12016-05-31 21:01:11 +020061 this._alert = alertClass.create();
Akronda5bd3a2020-10-16 17:37:49 +020062
63 // Active may either hold an alert or a menu
Akron02360e42016-06-07 13:41:12 +020064 this._active = null;
Nils Diewald5c5a7472015-04-02 22:13:38 +000065
Akron80055992017-12-20 16:30:52 +010066 // No annotation helper available
67 if (!KorAP.annotationHelper) {
68 console.log("No annotationhelper defined");
69 return;
70 };
Akronfac16472018-07-26 16:47:21 +020071
72 /**
73 * @define {regex} Regular expression for context
74 */
75 KorAP.context = KorAP.context ||
76 "(?:^|[^-_a-zA-Z0-9])" + // Anchor
77 "((?:[-_a-zA-Z0-9]+?)\/" + // Foundry
78 "(?:" +
79 "(?:[-_a-zA-Z0-9]+?)=" + // Layer
80 "(?:"+
81 "(?:[^:=\/ ]+?):|" + // Key
82 "(?:[^-=\/ ]+?)-" + // Node
83 ")?" +
84 ")?" +
85 ")$";
Akron80055992017-12-20 16:30:52 +010086
Nils Diewald5c5a7472015-04-02 22:13:38 +000087 // Get input field
Akronda5bd3a2020-10-16 17:37:49 +020088 const qfield = param["inputField"] || document.getElementById("q-field");
Nils Diewald0e6992a2015-04-14 20:13:52 +000089 if (!qfield)
Akron8eaeb2e2016-08-29 18:26:28 +020090 return null;
Nils Diewald0e6992a2015-04-14 20:13:52 +000091
Akron00cd4d12016-05-31 21:01:11 +020092 // Create input field
Nils Diewald0e6992a2015-04-14 20:13:52 +000093 this._inputField = inputClass.create(qfield);
Nils Diewald5c5a7472015-04-02 22:13:38 +000094
Akron00cd4d12016-05-31 21:01:11 +020095 // create alert
Akronda5bd3a2020-10-16 17:37:49 +020096 const that = this;
97
98 const c = this._inputField.container();
Akron00cd4d12016-05-31 21:01:11 +020099 c.appendChild(this._alert.element());
Akronda5bd3a2020-10-16 17:37:49 +0200100 c.addEventListener('click', function (e) {
Akronc14cbfc2018-08-31 13:15:55 +0200101 if (!this.classList.contains('active')) {
102 that.show(false);
103 };
Akrondadd1d12021-11-12 16:20:28 +0100104 e.halt();
Nils Diewald47f366b2015-04-15 20:06:35 +0000105 });
106
Nils Diewald47f366b2015-04-15 20:06:35 +0000107 // Move infobox
Akronda5bd3a2020-10-16 17:37:49 +0200108 const inputFieldElement = this._inputField.element();
Akron00cd4d12016-05-31 21:01:11 +0200109 inputFieldElement.addEventListener("keyup", this.update.bind(this));
110 inputFieldElement.addEventListener("click", this.update.bind(this));
111
112 // Add event listener for key pressed down
Akronda5bd3a2020-10-16 17:37:49 +0200113 let _down = function (e) {
114 if (_codeFromEvent(e) === 40) {
115 this.show(false);
116 e.halt();
117 };
118 };
119
Akron00cd4d12016-05-31 21:01:11 +0200120 inputFieldElement.addEventListener(
Akronc14cbfc2018-08-31 13:15:55 +0200121 "keydown", _down.bind(this), false
122 );
123
124 // Add touch events
125 inputFieldElement.addEventListener(
126 'touchstart',
127 this._touch.bind(this),
128 false
129 );
130 inputFieldElement.addEventListener(
131 'touchend',
132 this._touch.bind(this),
133 false
134 );
135 inputFieldElement.addEventListener(
136 'touchmove',
137 this._touch.bind(this),
138 false
Akron00cd4d12016-05-31 21:01:11 +0200139 );
Nils Diewald5c5a7472015-04-02 22:13:38 +0000140
141 // Set Analyzer for context
Nils Diewald0e6992a2015-04-14 20:13:52 +0000142 this._analyzer = analyzerClass.create(
Akronc14cbfc2018-08-31 13:15:55 +0200143 param["context"] || KorAP.context
Nils Diewald5c5a7472015-04-02 22:13:38 +0000144 );
Nils Diewald19ccee92014-12-08 11:30:08 +0000145 return this;
146 },
147
Akron308db382016-05-30 22:34:07 +0200148
149 /**
150 * Return the input field attached to the hint helper.
151 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000152 inputField : function () {
153 return this._inputField;
154 },
Nils Diewald19ccee92014-12-08 11:30:08 +0000155
Akron308db382016-05-30 22:34:07 +0200156
157 /**
Akrondadd1d12021-11-12 16:20:28 +0100158 * Alert at a specific character position.
Akron308db382016-05-30 22:34:07 +0200159 */
Akron00cd4d12016-05-31 21:01:11 +0200160 alert : function (charPos, msg) {
Akronda5bd3a2020-10-16 17:37:49 +0200161 const t = this;
Akron00cd4d12016-05-31 21:01:11 +0200162 if (arguments.length === 0)
Akronda5bd3a2020-10-16 17:37:49 +0200163 return t._alert;
Akron00cd4d12016-05-31 21:01:11 +0200164
165 // Do not alert if already alerted!
Akronda5bd3a2020-10-16 17:37:49 +0200166 if (t._alert.active)
Akronc14cbfc2018-08-31 13:15:55 +0200167 return false;
Akron00cd4d12016-05-31 21:01:11 +0200168
169 // Move to the correct position
Akronda5bd3a2020-10-16 17:37:49 +0200170 t._inputField.moveto(charPos);
Akron00cd4d12016-05-31 21:01:11 +0200171
172 // Set container to active (aka hide the hint helper button)
173
Akronda5bd3a2020-10-16 17:37:49 +0200174 t._alert.show(msg);
175 t.active(t._alert);
Akron00cd4d12016-05-31 21:01:11 +0200176 return true;
Akron308db382016-05-30 22:34:07 +0200177 },
Akron8eaeb2e2016-08-29 18:26:28 +0200178
Akron00cd4d12016-05-31 21:01:11 +0200179
Akronda5bd3a2020-10-16 17:37:49 +0200180 /**
181 * Update input field.
182 */
Akron00cd4d12016-05-31 21:01:11 +0200183 update : function () {
184 this._inputField.update();
Akron02360e42016-06-07 13:41:12 +0200185 if (this._alert.hide())
Akronc14cbfc2018-08-31 13:15:55 +0200186 this.active(null);
Akron00cd4d12016-05-31 21:01:11 +0200187 },
188
189
Nils Diewald5c5a7472015-04-02 22:13:38 +0000190 /**
Nils Diewald5c5a7472015-04-02 22:13:38 +0000191 * Return hint menu and probably init based on an action
192 */
193 menu : function (action) {
Akrondadd1d12021-11-12 16:20:28 +0100194
Leo Repp9b26ba92021-09-17 17:38:22 +0200195 if (this._menuCollection[action] === undefined) {
Akronda5bd3a2020-10-16 17:37:49 +0200196
Akronc14cbfc2018-08-31 13:15:55 +0200197 // No matching hint menu
198 if (KorAP.annotationHelper[action] === undefined)
199 return;
Nils Diewald5c5a7472015-04-02 22:13:38 +0000200
Akronc14cbfc2018-08-31 13:15:55 +0200201 // Create matching hint menu
Leo Repp9b26ba92021-09-17 17:38:22 +0200202 this._menuCollection[action] = menuClass.create(
Akronc14cbfc2018-08-31 13:15:55 +0200203 this, action, KorAP.annotationHelper[action]
204 );
Nils Diewald5c5a7472015-04-02 22:13:38 +0000205 };
206
207 // Return matching hint menu
Leo Repp9b26ba92021-09-17 17:38:22 +0200208 return this._menuCollection[action];
Nils Diewald5c5a7472015-04-02 22:13:38 +0000209 },
210
Akronda5bd3a2020-10-16 17:37:49 +0200211
Nils Diewald5c5a7472015-04-02 22:13:38 +0000212 /**
213 * Get the correct menu based on the context
214 */
215 contextMenu : function (ifContext) {
Akronda5bd3a2020-10-16 17:37:49 +0200216 const noC = ifContext ? undefined : this.menu("-");
Akron1a5a5872016-09-05 20:17:14 +0200217
Akron02360e42016-06-07 13:41:12 +0200218 // Get context (aka left text)
Akronda5bd3a2020-10-16 17:37:49 +0200219 let context = this._inputField.context();
Akronee9ef4a2016-06-03 12:50:08 +0200220
Akron65c74352016-09-02 17:23:39 +0200221 if (context === undefined || context.length === 0) {
Akronda5bd3a2020-10-16 17:37:49 +0200222 return noC;
Akron65c74352016-09-02 17:23:39 +0200223 };
Nils Diewald5c5a7472015-04-02 22:13:38 +0000224
Akron02360e42016-06-07 13:41:12 +0200225 // Get context (aka left text matching regex)
Nils Diewald5c5a7472015-04-02 22:13:38 +0000226 context = this._analyzer.test(context);
Akronee9ef4a2016-06-03 12:50:08 +0200227
Akron1a5a5872016-09-05 20:17:14 +0200228 if (context === undefined || context.length == 0) {
Akronda5bd3a2020-10-16 17:37:49 +0200229 return noC;
Akron1a5a5872016-09-05 20:17:14 +0200230 };
Nils Diewald5c5a7472015-04-02 22:13:38 +0000231
Akronda5bd3a2020-10-16 17:37:49 +0200232 return this.menu(context) || noC;
Nils Diewald5c5a7472015-04-02 22:13:38 +0000233 },
234
Akronda5bd3a2020-10-16 17:37:49 +0200235
Akron8eaeb2e2016-08-29 18:26:28 +0200236 /**
237 * Activate a certain menu.
238 * If a menu is passed, the menu will be activated.
239 * If null is passed, the active menu will be deactivated.
240 * If nothing is passed, returns the active menu.
241 */
Akron02360e42016-06-07 13:41:12 +0200242 active : function (obj) {
Akronda5bd3a2020-10-16 17:37:49 +0200243
Akron8eaeb2e2016-08-29 18:26:28 +0200244 // A menu or null was passed
Akronda5bd3a2020-10-16 17:37:49 +0200245 if (arguments.length === 1) {
246 const c = this._inputField.container();
Akrondadd1d12021-11-12 16:20:28 +0100247
Akron8eaeb2e2016-08-29 18:26:28 +0200248 // Make the menu active
Akronc14cbfc2018-08-31 13:15:55 +0200249 if (obj !== null) {
250 c.classList.add('active');
251 this._active = obj;
252 }
Akron8eaeb2e2016-08-29 18:26:28 +0200253
254 // Make the menu inactive
Akronc14cbfc2018-08-31 13:15:55 +0200255 else {
256 c.classList.remove('active');
257 this._active = null;
258 }
Akron00cd4d12016-05-31 21:01:11 +0200259 };
Akron8eaeb2e2016-08-29 18:26:28 +0200260
261 // Return
Akron00cd4d12016-05-31 21:01:11 +0200262 return this._active;
263 },
264
Nils Diewald5c5a7472015-04-02 22:13:38 +0000265
266 /**
Akron308db382016-05-30 22:34:07 +0200267 * Show the menu.
Akron65c74352016-09-02 17:23:39 +0200268 * Remove all old menus.
Akron02360e42016-06-07 13:41:12 +0200269 *
270 * @param {boolean} Boolean value to indicate if context
271 * is necessary (true) or if the main context should
272 * be shown if context fails.
Helgeef0a26e2023-11-22 16:59:17 +0100273 *
Nils Diewald5c5a7472015-04-02 22:13:38 +0000274 */
275 show : function (ifContext) {
Helgeef0a26e2023-11-22 16:59:17 +0100276 if(KorAP.annotationHelper["-"].length == 0){
277 this.alert(0,loc.HINT_noAnnot);
278 return;
279 }
Akron1a5a5872016-09-05 20:17:14 +0200280 // Remove the active object
281 this._unshow();
Akronc14cbfc2018-08-31 13:15:55 +0200282
Nils Diewald5c5a7472015-04-02 22:13:38 +0000283 // Get the menu
Akronda5bd3a2020-10-16 17:37:49 +0200284 let menu;
Nils Diewald5c5a7472015-04-02 22:13:38 +0000285 if (menu = this.contextMenu(ifContext)) {
Akronda5bd3a2020-10-16 17:37:49 +0200286
Akronc14cbfc2018-08-31 13:15:55 +0200287 this.active(menu);
Akron02360e42016-06-07 13:41:12 +0200288
Akronda5bd3a2020-10-16 17:37:49 +0200289 let e = menu.element();
Akrondadd1d12021-11-12 16:20:28 +0100290 // Chrome may send a blur on the old menu here
Akronda5bd3a2020-10-16 17:37:49 +0200291 this._active.element().blur();
292 this._inputField.container().appendChild(e);
Akroncae907d2018-08-20 17:22:15 +0200293
Akronc14cbfc2018-08-31 13:15:55 +0200294 menu.show();
295 menu.focus();
296 // Focus on input field
297 // this.inputField.element.focus();
Akron65c74352016-09-02 17:23:39 +0200298 }
Akronda5bd3a2020-10-16 17:37:49 +0200299
Akron65c74352016-09-02 17:23:39 +0200300 else {
301 this._inputField.element().focus();
Nils Diewald19ccee92014-12-08 11:30:08 +0000302 };
Akron00cd4d12016-05-31 21:01:11 +0200303 },
Akron8eaeb2e2016-08-29 18:26:28 +0200304
Akronda5bd3a2020-10-16 17:37:49 +0200305
Akron02360e42016-06-07 13:41:12 +0200306 // This will get the context of the field
307 getContext : function () {},
308
Akron65c74352016-09-02 17:23:39 +0200309
310 /**
311 * Deactivate the current menu and focus on the input field.
312 */
313 unshow : function () {
Akron1a5a5872016-09-05 20:17:14 +0200314 this._unshow();
315 this._inputField.element().focus();
316 },
Akron65c74352016-09-02 17:23:39 +0200317
Akronc14cbfc2018-08-31 13:15:55 +0200318
319 // Catch touch events
320 _touch : function (e) {
Akronc14cbfc2018-08-31 13:15:55 +0200321 if (e.type === 'touchstart') {
Akronda5bd3a2020-10-16 17:37:49 +0200322 this._lastTouch = e.touches[0].clientY;
Akronc14cbfc2018-08-31 13:15:55 +0200323 }
Akronda5bd3a2020-10-16 17:37:49 +0200324
Akronc14cbfc2018-08-31 13:15:55 +0200325 else if (e.type === 'touchend') {
326 this._lastTouch = undefined;
327 }
Akronda5bd3a2020-10-16 17:37:49 +0200328
Akronc14cbfc2018-08-31 13:15:55 +0200329 else if (e.type == 'touchmove') {
Akronda5bd3a2020-10-16 17:37:49 +0200330 if ((this._lastTouch + 10) < e.touches[0].clientY) {
Akronc14cbfc2018-08-31 13:15:55 +0200331 this.show();
332 this._lastTouch = undefined;
333 };
334 e.halt();
335 }
336 },
337
Akronda5bd3a2020-10-16 17:37:49 +0200338
339 // Unshow the hint menu
Akron1a5a5872016-09-05 20:17:14 +0200340 _unshow : function () {
Akron65c74352016-09-02 17:23:39 +0200341 if (this.active() !== null) {
Akronda5bd3a2020-10-16 17:37:49 +0200342
Akron65c74352016-09-02 17:23:39 +0200343 // This does not work for alert currently!
Akron1a5a5872016-09-05 20:17:14 +0200344 if (!this._alert.active) {
Akroncae907d2018-08-20 17:22:15 +0200345
Akronda5bd3a2020-10-16 17:37:49 +0200346 // TODO: This does not work for webkit?
347 this._inputField
348 .container()
349 .removeChild(this._active.element());
Akronc14cbfc2018-08-31 13:15:55 +0200350 }
Akronda5bd3a2020-10-16 17:37:49 +0200351
Akron1a5a5872016-09-05 20:17:14 +0200352 else {
353 this._unshowAlert();
354 };
355
Akron65c74352016-09-02 17:23:39 +0200356 this.active(null);
357 };
Akronda5bd3a2020-10-16 17:37:49 +0200358 },
359
360 // Unshow alert
361 _unshowAlert : function () {
362 this._alert.hide();
363 this.active(null);
Nils Diewald19ccee92014-12-08 11:30:08 +0000364 }
365 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000366});