blob: ab84ed35a813def82fc25645f0fe273f2034fe9b [file] [log] [blame]
Akron479994e2018-07-02 13:21:44 +02001/**
2 * The plugin system is based
Akron22598cd2019-12-09 14:59:03 +01003 * on registered services (iframes) from
Akron479994e2018-07-02 13:21:44 +02004 * foreign services.
5 * The server component spawns new iframes and
6 * listens to them.
7 *
8 * @author Nils Diewald
9 */
Akrone51eaa32020-11-10 09:35:53 +010010"use strict";
Akron479994e2018-07-02 13:21:44 +020011
Akronda32e7a2021-11-16 17:28:57 +010012define(['plugin/widget', 'plugin/service', 'state', 'state/manager', 'pageInfo', 'util'], function (widgetClass, serviceClass, stateClass, stateManagerClass, pageInfoClass) {
Akron479994e2018-07-02 13:21:44 +020013
Akron2d0d96d2019-11-18 19:49:50 +010014 KorAP.Panel = KorAP.Panel || {};
15
Akron7d18d8e2024-11-08 11:08:48 +010016 const d = document;
Akroned223be2024-12-10 13:01:46 +010017
Akron22598cd2019-12-09 14:59:03 +010018 // Contains all servicess to address with
Akrona6c32b92018-07-02 18:39:42 +020019 // messages to them
Akron22598cd2019-12-09 14:59:03 +010020 var services = {};
21 var plugins = {};
Akron10a47962018-07-12 21:17:10 +020022
23 // TODO:
24 // These should better be panels and every panel
25 // has a buttonGroup
Akron2d0d96d2019-11-18 19:49:50 +010026
27 // List of panels with dynamic buttons, i.e.
28 // panels that may occur multiple times.
Akron7c6e05f2018-07-12 19:08:13 +020029 var buttons = {
30 match : []
31 };
Akron2d0d96d2019-11-18 19:49:50 +010032
33 // List of panels with static buttons, i.e.
34 // panels that occur only once.
35 var buttonsSingle = {
hebasta043e96f2019-11-28 12:33:00 +010036 query : [],
37 result : []
Akron22598cd2019-12-09 14:59:03 +010038 }
Akron10a47962018-07-12 21:17:10 +020039
Akrone8e2c952018-07-04 13:43:12 +020040 // This is a counter to limit acceptable incoming messages
41 // to a certain amount. For every message, this counter will
42 // be decreased (down to 0), for every second this will be
43 // increased (up to 100).
44 // Once a widget surpasses the limit, it will be killed
45 // and called suspicious.
46 var maxMessages = 100;
47 var limits = {};
48
49 // TODO:
50 // It may be useful to establish a watcher that pings
Akrone1c27f62018-07-20 11:42:59 +020051 // all widgets every second to see if it is still alive,
52 // otherwise kill
Akrone8e2c952018-07-04 13:43:12 +020053
Akrone1c27f62018-07-20 11:42:59 +020054 // Load Plugin server
Akron479994e2018-07-02 13:21:44 +020055 return {
56
57 /**
58 * Create new plugin management system
59 */
60 create : function () {
61 return Object.create(this)._init();
62 },
63
64 /*
Akron76dd8d32018-07-06 09:30:22 +020065 * Initialize the plugin manager
Akron479994e2018-07-02 13:21:44 +020066 */
67 _init : function () {
Akron7d18d8e2024-11-08 11:08:48 +010068 this._ql = d.getElementById("ql-field");
69 this._q = d.getElementById("q-field")
70 this._cutoff = d.getElementById("q-cutoff-field");
71
Akroned223be2024-12-10 13:01:46 +010072 // State manager undefined
73 this._states = KorAP.States ? KorAP.States :
74 // Not serialized state manager
75 stateManagerClass.create(document.createElement('input'));
76
Akron479994e2018-07-02 13:21:44 +020077 return this;
78 },
79
80 /**
Akron7c6e05f2018-07-12 19:08:13 +020081 * Register a plugin described as a JSON object.
82 *
83 * This is work in progress.
Akron10a47962018-07-12 21:17:10 +020084 *
85 * Example:
86 *
87 * KorAP.Plugin.register({
88 * 'name' : 'CatContent',
89 * 'desc' : 'Some content about cats',
90 * 'about' : 'https://localhost:5678/',
91 * 'embed' : [{
92 * 'panel' : 'match',
93 * 'title' : loc.TRANSLATE,
94 * 'classes' : ['translate']
95 * 'onClick' : {
96 * 'action' : 'addWidget',
97 * 'template' : 'https://localhost:5678/?match={matchid}',
98 * }
Akron22598cd2019-12-09 14:59:03 +010099 * },{
100 * 'title' : 'glemm',
101 * 'panel' : 'query',
102 * 'onClick' : {
103 * 'action' : 'toggle',
104 * 'state' : 'glemm',
105 * 'service' : {
106 * 'id' : 'glemm',
107 * 'template' : 'https://localhost:5678/'
108 * }
109 * }
Akron10a47962018-07-12 21:17:10 +0200110 * }]
111 * });
112 *
Akron7c6e05f2018-07-12 19:08:13 +0200113 */
114 register : function (obj) {
Akron7c6e05f2018-07-12 19:08:13 +0200115 // TODO:
Akron10a47962018-07-12 21:17:10 +0200116 // These fields need to be localized for display by a structure like
117 // { de : { name : '..' }, en : { .. } }
Akronda32e7a2021-11-16 17:28:57 +0100118 const name = obj["name"];
Akron7c6e05f2018-07-12 19:08:13 +0200119
Akron10a47962018-07-12 21:17:10 +0200120 if (!name)
121 throw new Error("Missing name of plugin");
122
Akron3d013802020-10-07 15:03:38 +0200123 var desc = obj["desc"];
124
Akron7c6e05f2018-07-12 19:08:13 +0200125 // Register plugin by name
126 var plugin = plugins[name] = {
127 name : name,
Akron3d013802020-10-07 15:03:38 +0200128 desc : desc,
Akron7c6e05f2018-07-12 19:08:13 +0200129 about : obj["about"],
Akron22598cd2019-12-09 14:59:03 +0100130 widgets : [],
131 services : []
Akron7c6e05f2018-07-12 19:08:13 +0200132 };
Akron10a47962018-07-12 21:17:10 +0200133
134 if (typeof obj["embed"] !== 'object')
135 throw new Error("Embedding of plugin is no list");
Akron7c6e05f2018-07-12 19:08:13 +0200136
137 // Embed all embeddings of the plugin
Akrone1c27f62018-07-20 11:42:59 +0200138 var that = this;
Akron678c26f2020-10-09 08:52:50 +0200139 obj["embed"].forEach(function(embed) {
Akron10a47962018-07-12 21:17:10 +0200140
141 if (typeof embed !== 'object')
142 throw new Error("Embedding of plugin is no object");
143
Akron7c6e05f2018-07-12 19:08:13 +0200144 // Needs to be localized as well
Akron22598cd2019-12-09 14:59:03 +0100145 let title = embed["title"];
146 let panel = embed["panel"];
147 let onClick = embed["onClick"];
hebasta40a85cf2020-07-15 18:10:08 +0200148 let icon = embed["icon"];
149
Akron22598cd2019-12-09 14:59:03 +0100150 if (!panel || !(buttons[panel] || buttonsSingle[panel]))
Akronba09ed22020-10-01 16:01:45 +0200151 throw new Error("Panel for plugin is invalid");
Akron7c6e05f2018-07-12 19:08:13 +0200152
153 // The embedding will open a widget
Akronba09ed22020-10-01 16:01:45 +0200154 if (!onClick["action"] ||
155 onClick["action"] == "addWidget" ||
156 onClick["action"] == "setWidget") {
Akron22598cd2019-12-09 14:59:03 +0100157
158 let cb = function (e) {
Akron7c6e05f2018-07-12 19:08:13 +0200159
Akrone1c27f62018-07-20 11:42:59 +0200160 // "this" is bind to the panel
Akronba09ed22020-10-01 16:01:45 +0200161 // "this".button is the button
162 // "that" is the server object
Akrone1c27f62018-07-20 11:42:59 +0200163
Akronb0ae8412026-02-24 11:47:52 +0100164 // Active-check click: load iframe as a background service,
165 // not as a visible widget.
166 if (this.button._activeClick && 'active' in this.button) {
167
168 // Service already running - just return
169 // (active.roll() was already called in buttongroup)
170 if (this.button['widgetID'] &&
171 services[this.button['widgetID']]) {
172 return;
173 };
174
175 // For addWidget mode (no state), just toggle - no service
176 if (!('state' in this.button)) {
177 return;
178 };
179
180 // Create a background service (iframe in services container)
181 let id = that.addService({
182 "name": name,
183 "src": onClick["template"],
184 "permissions": onClick["permissions"]
185 });
186 plugin["widgets"].push(id);
187
188 this.button['widgetID'] = id;
189
190 // Store panel reference so 'get Active' works
191 services[id].panel = this;
192
193 let activeState = this.button.active;
194 let iframe = services[id].load();
195
196 iframe.onload = function () {
197 activeState.associate({
198 setState : function (value) {
199 if (services[id]) {
200 services[id].sendMsg({
201 action: 'state',
202 key : 'active',
203 value : value
204 });
205 };
206 }
207 });
208 };
209
210 return;
211 };
212
Akronba09ed22020-10-01 16:01:45 +0200213 // The button has a state and the state is associated to the
214 // a intermediate object to toggle the view
215 if ('state' in this.button && this.button.state.associates() > 0) {
216
Akronda32e7a2021-11-16 17:28:57 +0100217 const s = this.button.state;
Akronfcf89db2020-10-01 17:40:20 +0200218
219 // The associated service is existent
220 if (services[this.button['widgetID']]) {
Akron237abc42020-10-07 14:14:52 +0200221 s.roll();
Akronfcf89db2020-10-01 17:40:20 +0200222 return;
223 }
224
225 // The service is not existent
226 else {
227
228 // Remove broken state associations
229 s.clear();
Akronba09ed22020-10-01 16:01:45 +0200230 s.set(true);
Akronfcf89db2020-10-01 17:40:20 +0200231 }
Akronba09ed22020-10-01 16:01:45 +0200232 };
233
Akronb0ae8412026-02-24 11:47:52 +0100234 // If a background service exists (from active-check),
235 // close it before creating the widget
236 if ('active' in this.button &&
237 this.button['widgetID'] &&
238 services[this.button['widgetID']] &&
239 !services[this.button['widgetID']].isWidget) {
240 that._closeService(this.button['widgetID']);
241 this.button['widgetID'] = undefined;
242 };
243
Akron7c6e05f2018-07-12 19:08:13 +0200244 // Add the widget to the panel
Akronbb891982020-10-05 16:07:18 +0200245 let id = that.addWidget(this, {
246 "name": name,
247 "src": onClick["template"], // that._interpolateURI(onClick["template"], this.match);
Akron3d013802020-10-07 15:03:38 +0200248 "permissions": onClick["permissions"],
Akronb0ae8412026-02-24 11:47:52 +0100249 "desc":desc,
250 "panel":panel
Akronbb891982020-10-05 16:07:18 +0200251 });
Akron7c6e05f2018-07-12 19:08:13 +0200252 plugin["widgets"].push(id);
Akronb0ae8412026-02-24 11:47:52 +0100253
Akronba09ed22020-10-01 16:01:45 +0200254 // If a state exists, associate with a mediator object
255 if ('state' in this.button) {
Akronfcf89db2020-10-01 17:40:20 +0200256 this.button['widgetID'] = id;
Akronba09ed22020-10-01 16:01:45 +0200257 this.button.state.associate({
258 setState : function (value) {
259 // Minimize the widget
260 if (value == false) {
261 services[id].minimize();
262 }
263 else {
264 services[id].show();
265 };
266 }
267 });
Akronb0ae8412026-02-24 11:47:52 +0100268 };
269
270 // If an active state exists, associate it with the widget
271 // so active-state changes can be reflected in the iframe.
272 if ('active' in this.button) {
273 this.button['widgetID'] = id;
274 let first = true;
275 this.button.active.associate({
276 setState : function (value) {
277 if (first) {
278 first = false;
279 return;
280 };
281 if (services[id]) {
282 services[id].sendMsg({
283 action: 'state',
284 key : 'active',
285 value : value
286 });
287 };
288 }
289 });
290 };
Akron7c6e05f2018-07-12 19:08:13 +0200291 };
292
Akron22598cd2019-12-09 14:59:03 +0100293
Akronba09ed22020-10-01 16:01:45 +0200294 // Button object
Akron83a58bc2024-11-08 09:55:19 +0100295 let obj = {'cls':embed["classes"], 'icon': icon };
296
297 if (embed['desc'] != undefined)
298 obj['desc'] = embed['desc'];
Akronba09ed22020-10-01 16:01:45 +0200299
Akronb0ae8412026-02-24 11:47:52 +0100300 if (onClick['active'] !== undefined) {
301 obj['active'] = stateClass.create([true, false]);
302 obj['active'].setIfNotYet(onClick['active'] ? true : false);
303 };
304
Akronba09ed22020-10-01 16:01:45 +0200305 if (onClick["action"] && onClick["action"] == "setWidget") {
306
Akronda32e7a2021-11-16 17:28:57 +0100307 // Create a boolean state value,
308 // that initializes to true == opened
Akron237abc42020-10-07 14:14:52 +0200309 obj['state'] = stateClass.create([true, false]);
Akronda32e7a2021-11-16 17:28:57 +0100310 obj['state'].setIfNotYet(true);
Akronba09ed22020-10-01 16:01:45 +0200311 };
312
Akron2d0d96d2019-11-18 19:49:50 +0100313 // Add to dynamic button list (e.g. for matches)
314 if (buttons[panel]) {
Akronba09ed22020-10-01 16:01:45 +0200315 buttons[panel].push([title, obj, cb]);
Akron2d0d96d2019-11-18 19:49:50 +0100316 }
317
318 // Add to static button list (e.g. for query) already loaded
319 else if (KorAP.Panel[panel]) {
Akron37ea1192021-07-28 10:40:14 +0200320 KorAP.Panel[panel].actions().add(title, obj, cb);
Akron2d0d96d2019-11-18 19:49:50 +0100321 }
322
323 // Add to static button list (e.g. for query) not yet loaded
324 else {
Akronba09ed22020-10-01 16:01:45 +0200325 buttonsSingle[panel].push([title, obj, cb]);
Akron2d0d96d2019-11-18 19:49:50 +0100326 }
Akron22598cd2019-12-09 14:59:03 +0100327 }
Akronba09ed22020-10-01 16:01:45 +0200328
Akron22598cd2019-12-09 14:59:03 +0100329 else if (onClick["action"] == "toggle") {
330
Akron237abc42020-10-07 14:14:52 +0200331 // TODO:
332 // Accept a "value" list here for toggling, which should
333 // also allow for "rolling" through states via CSS classes
334 // as 'toggle-true', 'toggle-false' etc.
Akroned223be2024-12-10 13:01:46 +0100335 let state = that._states.newState(
Akronda32e7a2021-11-16 17:28:57 +0100336 (onClick["state"] ? onClick["state"] : name),
337 [true, false],
338 onClick["default"]
339 );
Akrona70b6892021-11-04 14:23:24 +0100340
Akron83a58bc2024-11-08 09:55:19 +0100341 let obj = {'cls':["title"], 'icon': icon};
342
343 if (embed['desc'] != undefined)
344 obj['desc'] = embed['desc'];
345
Akron22598cd2019-12-09 14:59:03 +0100346 // TODO:
347 // Lazy registration (see above!)
Akron83a58bc2024-11-08 09:55:19 +0100348 KorAP.Panel[panel].actions().addToggle(title, obj, state);
Akron22598cd2019-12-09 14:59:03 +0100349
Akron22598cd2019-12-09 14:59:03 +0100350 // Add the service
Akronbb891982020-10-05 16:07:18 +0200351 let id = this.addService({
352 "name" : name,
353 // TODO:
354 // Use the "service" keyword
355 "src" : onClick["template"],
356 "permissions" : onClick["permissions"]
357 });
Akronfb11a962020-10-05 12:12:55 +0200358
Akron22598cd2019-12-09 14:59:03 +0100359 // TODO:
360 // This is a bit stupid to get the service window
Akronc3003642020-03-30 10:19:14 +0200361 let service = services[id];
362 let iframe = service.load();
Akron22598cd2019-12-09 14:59:03 +0100363
364 // Create object to communicate the toggle state
365 // once the iframe is loaded.
366 iframe.onload = function () {
367 let sendToggle = {
368 setState : function (val) {
Akronc3003642020-03-30 10:19:14 +0200369 service.sendMsg({
Akron22598cd2019-12-09 14:59:03 +0100370 action: 'state',
371 key : onClick['state'],
372 value : val
Akronc3003642020-03-30 10:19:14 +0200373 });
Akron22598cd2019-12-09 14:59:03 +0100374 }
375 };
376
377 // Associate object with the state
378 state.associate(sendToggle);
379 };
380
381 plugin["services"].push(id);
Akron7c6e05f2018-07-12 19:08:13 +0200382 };
Akron678c26f2020-10-09 08:52:50 +0200383 }, this);
Akron7c6e05f2018-07-12 19:08:13 +0200384 },
385
Akron7c6e05f2018-07-12 19:08:13 +0200386 // TODO:
387 // Interpolate URIs similar to https://tools.ietf.org/html/rfc6570
388 // but as simple as possible
389 _interpolateURI : function (uri, obj) {
390 // ...
391 },
392
393
394 /**
Akron4a703872018-07-26 10:59:41 +0200395 * Get named button group - better rename to "action"
Akron7c6e05f2018-07-12 19:08:13 +0200396 */
397 buttonGroup : function (name) {
Akron2d0d96d2019-11-18 19:49:50 +0100398 if (buttons[name] != undefined) {
399 return buttons[name];
400 } else if (buttonsSingle[name] != undefined) {
401 return buttonsSingle[name];
402 };
403 return [];
404 },
405
406 /**
407 * Clear named button group - better rename to "action"
408 */
409 clearButtonGroup : function (name) {
410 if (buttons[name] != undefined) {
411 buttons[name] = [];
412 } else if (buttonsSingle[name] != undefined) {
413 buttonsSingle[name] = [];
414 }
Akron7c6e05f2018-07-12 19:08:13 +0200415 },
Akron479994e2018-07-02 13:21:44 +0200416
Akron22598cd2019-12-09 14:59:03 +0100417 // Optionally initialize the service mechanism and get an ID
418 _getServiceID : function () {
Akron4a703872018-07-26 10:59:41 +0200419
Akron22598cd2019-12-09 14:59:03 +0100420 // Is it the first service?
Akron76dd8d32018-07-06 09:30:22 +0200421 if (!this._listener) {
422
423 /*
424 * Establish the global 'message' hook.
425 */
426 this._listener = this._receiveMsg.bind(this);
427 window.addEventListener("message", this._listener);
428
Akron22598cd2019-12-09 14:59:03 +0100429 // Every second increase the limits of all registered services
Akron76dd8d32018-07-06 09:30:22 +0200430 this._timer = window.setInterval(function () {
Akron678c26f2020-10-09 08:52:50 +0200431 for (let i = 0; i < limits.length; i++) {
Akron76dd8d32018-07-06 09:30:22 +0200432 if (limits[i]++ >= maxMessages) {
433 limits[i] = maxMessages;
434 }
435 }
436 }, 1000);
437 };
438
Akron22598cd2019-12-09 14:59:03 +0100439 // Create a unique random ID per service
440 return 'id-' + this._randomID();
441 },
442
443 /**
444 * Add a service in a certain panel and return the id.
445 */
Akronbb891982020-10-05 16:07:18 +0200446 addService : function (data) {
447 if (!data["src"])
Akron22598cd2019-12-09 14:59:03 +0100448 return;
449
450 let id = this._getServiceID();
451
Akronbb891982020-10-05 16:07:18 +0200452 data["id"] = id;
453
Akron22598cd2019-12-09 14:59:03 +0100454 // Create a new service
Akronbb891982020-10-05 16:07:18 +0200455 let service = serviceClass.create(data);
Akron22598cd2019-12-09 14:59:03 +0100456
Akron22598cd2019-12-09 14:59:03 +0100457 services[id] = service;
458 limits[id] = maxMessages;
459
Akron22598cd2019-12-09 14:59:03 +0100460 // Add service to panel
461 this.element().appendChild(
462 service.load()
463 );
464
465 return id;
466 },
467
468
469 /**
470 * Open a new widget view in a certain panel and return
471 * the id.
472 */
Akronbb891982020-10-05 16:07:18 +0200473 addWidget : function (panel, data) {
474 // panel, name, src, permissions
Akron22598cd2019-12-09 14:59:03 +0100475
476 let id = this._getServiceID();
Akrona6c32b92018-07-02 18:39:42 +0200477
Akronbb891982020-10-05 16:07:18 +0200478 data["id"] = id;
479
Akrona6c32b92018-07-02 18:39:42 +0200480 // Create a new widget
Akronbb891982020-10-05 16:07:18 +0200481 var widget = widgetClass.create(data);
Akrona6c32b92018-07-02 18:39:42 +0200482
483 // Store the widget based on the identifier
Akron22598cd2019-12-09 14:59:03 +0100484 services[id] = widget;
Akrona99315e2018-07-03 22:56:45 +0200485 limits[id] = maxMessages;
Akrona6c32b92018-07-02 18:39:42 +0200486
Akron4a703872018-07-26 10:59:41 +0200487 widget._mgr = this;
488
Akrone1c27f62018-07-20 11:42:59 +0200489 // Add widget to panel
490 panel.add(widget);
Akronb43c8c62018-07-04 18:27:28 +0200491
492 return id;
Akron479994e2018-07-02 13:21:44 +0200493 },
494
Akron4a703872018-07-26 10:59:41 +0200495
496 /**
Akron22598cd2019-12-09 14:59:03 +0100497 * Get service by identifier
Akron4a703872018-07-26 10:59:41 +0200498 */
Akron22598cd2019-12-09 14:59:03 +0100499 service : function (id) {
500 return services[id];
Akron4a703872018-07-26 10:59:41 +0200501 },
502
503
Akron22598cd2019-12-09 14:59:03 +0100504 // Receive a call from an embedded service.
Akrone8e2c952018-07-04 13:43:12 +0200505 // The handling needs to be very careful,
506 // as this can easily become a security nightmare.
Akron479994e2018-07-02 13:21:44 +0200507 _receiveMsg : function (e) {
Akronbc94a9c2021-04-15 00:07:35 +0200508
Akron479994e2018-07-02 13:21:44 +0200509 // Get event data
510 var d = e.data;
511
Akrona99315e2018-07-03 22:56:45 +0200512 // If no data given - fail
513 // (probably check that it's an assoc array)
514 if (!d)
515 return;
516
517 // e.origin is probably set and okay - CHECK!
Akronbc94a9c2021-04-15 00:07:35 +0200518 // TODO: Check e.origin is in the list of registered participants
519 // if (e.origin !== "http://example.com:8080")
520 // return;
Akron479994e2018-07-02 13:21:44 +0200521
Akronbc94a9c2021-04-15 00:07:35 +0200522
Akrona99315e2018-07-03 22:56:45 +0200523 // Get origin ID
524 var id = d["originID"];
525
526 // If no origin ID given - fail
527 if (!id)
528 return;
529
Akron22598cd2019-12-09 14:59:03 +0100530 // Get the service
531 let service = services[id];
Akrona6c32b92018-07-02 18:39:42 +0200532
Akron22598cd2019-12-09 14:59:03 +0100533 // If the addressed service does not exist - fail
534 if (!service)
Akrona6c32b92018-07-02 18:39:42 +0200535 return;
536
Akrona99315e2018-07-03 22:56:45 +0200537 // Check for message limits
538 if (limits[id]-- < 0) {
Akrone8e2c952018-07-04 13:43:12 +0200539
Akron22598cd2019-12-09 14:59:03 +0100540 // Kill service
541 KorAP.log(0, 'Suspicious action by service', service.src);
Akron7c6e05f2018-07-12 19:08:13 +0200542
543 // TODO:
544 // Potentially kill the whole plugin!
Akron4a703872018-07-26 10:59:41 +0200545
Akron22598cd2019-12-09 14:59:03 +0100546 // This removes all connections before closing the service
547 this._closeService(service.id);
548
549 // if (service.isWidget)
550 service.close();
551
Akrona99315e2018-07-03 22:56:45 +0200552 return;
553 };
Akrona6c32b92018-07-02 18:39:42 +0200554
Akron479994e2018-07-02 13:21:44 +0200555 // Resize the iframe
Akron22598cd2019-12-09 14:59:03 +0100556 switch (d.action) {
557 case 'resize':
558 if (service.isWidget)
559 service.resize(d);
560 break;
Akron479994e2018-07-02 13:21:44 +0200561
562 // Log message from iframe
Akron22598cd2019-12-09 14:59:03 +0100563 case 'log':
564 KorAP.log(d.code, d.msg, service.src);
565 break;
Akron51ee6232019-12-17 21:00:05 +0100566
567 // Modify pipes
568 case 'pipe':
Akron910828a2025-06-27 15:38:48 +0200569 let j = d.job;
570 if (
571 ((j == 'del-after' || j == 'add-after') &&
572 KorAP.ResponsePipe == undefined) ||
573 KorAP.Pipe == undefined) {
574
575 KorAP.log(0,"No Pipe established");
576 break;
577 };
578
579 if (j == 'del') {
580 KorAP.Pipe.remove(d.service);
581 } else if (j == 'del-after') {
582 KorAP.ResponsePipe.remove(d.service);
583 } else if (j == 'add-after') {
584 KorAP.ResponsePipe.append(d.service);
585 } else {
586 KorAP.Pipe.append(d.service);
Akron51ee6232019-12-17 21:00:05 +0100587 };
588 break;
Akronc3003642020-03-30 10:19:14 +0200589
590 // Get information from the embedding platform
591 case 'get':
Akron45308ce2020-08-28 14:10:23 +0200592
593 // Get KoralQuery
Akronc3003642020-03-30 10:19:14 +0200594 if (d.key == 'KQ') {
595 if (KorAP.koralQuery !== undefined) {
596 d["value"] = KorAP.koralQuery;
597 };
Akron45308ce2020-08-28 14:10:23 +0200598 }
599
Akron26d57f22021-09-10 16:48:57 +0200600 // Get Query information from form
Akron45308ce2020-08-28 14:10:23 +0200601 else if (d.key == 'QueryForm') {
602 let doc = document;
603 let v = d["value"] = {};
604
605 var el;
606 if (el = doc.getElementById('q-field')) {
607 v["q"] = el.value;
608 };
609 if (el = doc.getElementById('ql-field')) {
610 v["ql"] = el.value;
611 };
612 if (el = KorAP.vc) {
613 v["cq"] = el.toQuery();
614 };
Akronb759ee92024-11-19 18:02:56 +0100615 if (el = KorAP.Hint) {
616 v["selection"] = KorAP.Hint.selectionRange();
617 };
Akron432972b2020-09-18 17:05:53 +0200618 }
619
Akron338b4d42022-12-20 13:59:22 +0100620 // Get text sigle from match
621 else if (d.key == 'textSigle') {
622 if (service.panel.type != "match") {
623 KorAP.log(0, "Service can only be called on matches", service.src);
624 return;
625 };
626 let v = d["value"] = {};
627 v["value"] = service.panel._match.textSigle;
628 }
629
Akron432972b2020-09-18 17:05:53 +0200630 // Get Query information from parameters
631 else if (d.key == 'QueryParam') {
632
633 // Supported in all modern browsers
634 var p = new URLSearchParams(window.location.search);
635 let v = d["value"] = {};
Akron4de759f2021-10-13 10:46:45 +0200636 v["search"] = window.location.search; // readonly
Akron432972b2020-09-18 17:05:53 +0200637 v["q"] = p.get('q');
638 v["ql"] = p.get('ql');
639 v["cq"] = p.get('cq');
Akron26d57f22021-09-10 16:48:57 +0200640 }
641
642 // Get pagination information
643 else if (d.key == 'Pagination') {
644 const pi = pageInfoClass.create();
645 let v = d["value"] = {};
646 v["page"] = pi.page();
647 v["total"] = pi.total();
648 v["count"] = pi.count();
Akronb0ae8412026-02-24 11:47:52 +0100649 }
650
651 // Get active toggle state of the widget button
652 else if (d.key == 'Active') {
653 let button = services[d["originID"]].panel.button;
654 if (button && button.active) {
655 d["value"] = button.active.get();
656 };
Akronec4bbfa2021-09-15 15:00:59 +0200657 };
Akronc3003642020-03-30 10:19:14 +0200658
Akronec4bbfa2021-09-15 15:00:59 +0200659 // data needs to be mirrored
660 if (d._id) {
661 service.sendMsg(d);
662 };
663
664 break;
665
Akron7d18d8e2024-11-08 11:08:48 +0100666 case 'set':
Akrone03faf62024-11-14 11:51:04 +0100667
668 // Get Query information from data
Akron7d18d8e2024-11-08 11:08:48 +0100669 if (d.key == 'QueryForm') {
670 let v = d["value"];
Akrone03faf62024-11-14 11:51:04 +0100671
Akron7d18d8e2024-11-08 11:08:48 +0100672 if (v["q"] != undefined && this._q) {
673 this._q.value = v["q"];
674 };
675
676 // Set query language field
677 // Identical to tutorial.js
Akrone03faf62024-11-14 11:51:04 +0100678 if (v["ql"] != undefined) {
679 if (KorAP.QLmenu) {
680 KorAP.QLmenu.selectValue(v["ql"]);
681 }
Akron7d18d8e2024-11-08 11:08:48 +0100682
Akrone03faf62024-11-14 11:51:04 +0100683 else if (this._ql) {
684 let found = Array.from(this._ql.options).find(o => o.value === v["ql"]);
685 if (found)
686 found.selected = true;
687 };
Akron7d18d8e2024-11-08 11:08:48 +0100688 };
689
690 window.scrollTo(0, 0);
691 // if (v["cq"] != undefined) {};
692 }
693
Akronb0ae8412026-02-24 11:47:52 +0100694 else if (d.key == "Title") {
695 // TODO: Only support "Add title"!
696 let v = d["value"];
697 services[d["originID"]].panel.button.changeTitle(v);
698 }
699
700 else if (d.key == "Active") {
701 let v = d["value"];
702 let button = services[d["originID"]].panel.button;
703 if (button && button.active) {
704 if (v !== undefined) {
705 button.active.set(v);
706 } else {
707 button.active.roll();
708 };
709 };
710 };
711
Akron7d18d8e2024-11-08 11:08:48 +0100712 break;
713
Akronec4bbfa2021-09-15 15:00:59 +0200714 // Redirect to a different page relative to the current
715 case 'redirect':
716 const url = new URL(window.location);
717
718 // Currently this only accepts search parameters
719 if (d["queryParam"]) {
720 url.search = new URLSearchParams(d["queryParam"]);
721 };
722
723 window.location = url.toString();
724 break;
Akrona6c32b92018-07-02 18:39:42 +0200725 };
726
727 // TODO:
728 // Close
Akron479994e2018-07-02 13:21:44 +0200729 },
730
Akron22598cd2019-12-09 14:59:03 +0100731 // Close the service
732 _closeService : function (id) {
Akron4a703872018-07-26 10:59:41 +0200733 delete limits[id];
Akronda32e7a2021-11-16 17:28:57 +0100734
Akron22598cd2019-12-09 14:59:03 +0100735 // Close the iframe
736 if (services[id] && services[id]._closeIframe) {
Akronda32e7a2021-11-16 17:28:57 +0100737
Akron22598cd2019-12-09 14:59:03 +0100738 services[id]._closeIframe();
739
740 // Remove from list
741 delete services[id];
742 };
743
Akron76dd8d32018-07-06 09:30:22 +0200744
745 // Remove listeners in case no widget
746 // is available any longer
747 if (Object.keys(limits).length == 0)
748 this._removeListener();
749 },
750
Akrona6c32b92018-07-02 18:39:42 +0200751 // Get a random identifier
752 _randomID : function () {
753 return randomID(20);
Akronb43c8c62018-07-04 18:27:28 +0200754 },
755
Akron76dd8d32018-07-06 09:30:22 +0200756 // Remove the listener
757 _removeListener : function () {
758 window.clearInterval(this._timer);
759 this._timer = undefined;
760 window.removeEventListener("message", this._listener);
761 this._listener = undefined;
762 },
763
Akron22598cd2019-12-09 14:59:03 +0100764 /**
765 * Return the service element.
766 */
767 element : function () {
Akron24aa0052020-11-10 11:00:34 +0100768 if (!this._el) {
769 this._el = document.createElement('div');
770 this._el.setAttribute("id", "services");
Akron22598cd2019-12-09 14:59:03 +0100771 }
Akron24aa0052020-11-10 11:00:34 +0100772 return this._el;
Akron22598cd2019-12-09 14:59:03 +0100773 },
Akronda32e7a2021-11-16 17:28:57 +0100774
775
776 // Return states object
777 states : function () {
Akroned223be2024-12-10 13:01:46 +0100778 return this._states;
Akronda32e7a2021-11-16 17:28:57 +0100779 },
780
Akronb43c8c62018-07-04 18:27:28 +0200781 // Destructor, just for testing scenarios
782 destroy : function () {
783 limits = {};
Akron678c26f2020-10-09 08:52:50 +0200784 Object.keys(services).forEach(
785 s => services[s].close()
786 );
Akron22598cd2019-12-09 14:59:03 +0100787 services = {};
Akron678c26f2020-10-09 08:52:50 +0200788 Object.keys(buttons).forEach(
789 b => buttons[b] = []
790 );
791 Object.keys(buttonsSingle).forEach(
792 b => buttonsSingle[b] = []
793 );
Akron22598cd2019-12-09 14:59:03 +0100794
Akron24aa0052020-11-10 11:00:34 +0100795 if (this._el) {
796 let e = this._el;
Akron22598cd2019-12-09 14:59:03 +0100797 if (e.parentNode) {
798 e.parentNode.removeChild(e);
799 };
Akron24aa0052020-11-10 11:00:34 +0100800 this._el = null;
Akron22598cd2019-12-09 14:59:03 +0100801 };
Akron76dd8d32018-07-06 09:30:22 +0200802 this._removeListener();
Akron479994e2018-07-02 13:21:44 +0200803 }
Akrone1c27f62018-07-20 11:42:59 +0200804 };
Akron479994e2018-07-02 13:21:44 +0200805});