diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /toolkit/components/prompts/content | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/components/prompts/content')
-rw-r--r-- | toolkit/components/prompts/content/commonDialog.css | 22 | ||||
-rw-r--r-- | toolkit/components/prompts/content/commonDialog.js | 62 | ||||
-rw-r--r-- | toolkit/components/prompts/content/commonDialog.xul | 97 | ||||
-rw-r--r-- | toolkit/components/prompts/content/selectDialog.js | 67 | ||||
-rw-r--r-- | toolkit/components/prompts/content/selectDialog.xul | 22 | ||||
-rw-r--r-- | toolkit/components/prompts/content/tabprompts.css | 35 | ||||
-rw-r--r-- | toolkit/components/prompts/content/tabprompts.xml | 352 |
7 files changed, 657 insertions, 0 deletions
diff --git a/toolkit/components/prompts/content/commonDialog.css b/toolkit/components/prompts/content/commonDialog.css new file mode 100644 index 000000000..89f88db7a --- /dev/null +++ b/toolkit/components/prompts/content/commonDialog.css @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */ + +#infoContainer { + max-width: 45em; +} + +#info\.body { + -moz-user-focus: normal; + -moz-user-select: text; + cursor: text !important; + white-space: pre-wrap; + unicode-bidi: plaintext; +} + +#loginLabel, #password1Label { + text-align: right; +} + diff --git a/toolkit/components/prompts/content/commonDialog.js b/toolkit/components/prompts/content/commonDialog.js new file mode 100644 index 000000000..ef4686654 --- /dev/null +++ b/toolkit/components/prompts/content/commonDialog.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var Ci = Components.interfaces; +var Cr = Components.results; +var Cc = Components.classes; +var Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/CommonDialog.jsm"); + +var propBag, args, Dialog; + +function commonDialogOnLoad() { + propBag = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2) + .QueryInterface(Ci.nsIWritablePropertyBag); + // Convert to a JS object + args = {}; + let propEnum = propBag.enumerator; + while (propEnum.hasMoreElements()) { + let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty); + args[prop.name] = prop.value; + } + + let dialog = document.documentElement; + + let ui = { + prompt : window, + loginContainer : document.getElementById("loginContainer"), + loginTextbox : document.getElementById("loginTextbox"), + loginLabel : document.getElementById("loginLabel"), + password1Container : document.getElementById("password1Container"), + password1Textbox : document.getElementById("password1Textbox"), + password1Label : document.getElementById("password1Label"), + infoBody : document.getElementById("info.body"), + infoTitle : document.getElementById("info.title"), + infoIcon : document.getElementById("info.icon"), + checkbox : document.getElementById("checkbox"), + checkboxContainer : document.getElementById("checkboxContainer"), + button3 : dialog.getButton("extra2"), + button2 : dialog.getButton("extra1"), + button1 : dialog.getButton("cancel"), + button0 : dialog.getButton("accept"), + focusTarget : window, + }; + + // limit the dialog to the screen width + document.getElementById("filler").maxWidth = screen.availWidth; + + Dialog = new CommonDialog(args, ui); + Dialog.onLoad(dialog); + // resize the window to the content + window.sizeToContent(); + window.getAttention(); +} + +function commonDialogOnUnload() { + // Convert args back into property bag + for (let propName in args) + propBag.setProperty(propName, args[propName]); +} diff --git a/toolkit/components/prompts/content/commonDialog.xul b/toolkit/components/prompts/content/commonDialog.xul new file mode 100644 index 000000000..990b26586 --- /dev/null +++ b/toolkit/components/prompts/content/commonDialog.xul @@ -0,0 +1,97 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://global/content/commonDialog.css" type="text/css"?> +<?xml-stylesheet href="chrome://global/skin/commonDialog.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://global/locale/commonDialog.dtd"> + +<dialog id="commonDialog" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + aria-describedby="info.body" + onunload="commonDialogOnUnload();" + ondialogaccept="Dialog.onButton0(); return true;" + ondialogcancel="Dialog.onButton1(); return true;" + ondialogextra1="Dialog.onButton2(); window.close();" + ondialogextra2="Dialog.onButton3(); window.close();" + buttonpack="center"> + + <script type="application/javascript" src="chrome://global/content/commonDialog.js"/> + <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/> + <script type="application/javascript"> + document.addEventListener("DOMContentLoaded", function() { + commonDialogOnLoad(); + }); + </script> + + <commandset id="selectEditMenuItems"> + <command id="cmd_copy" oncommand="goDoCommand('cmd_copy')" disabled="true"/> + <command id="cmd_selectAll" oncommand="goDoCommand('cmd_selectAll')"/> + </commandset> + + <popupset id="contentAreaContextSet"> + <menupopup id="contentAreaContextMenu" + onpopupshowing="goUpdateCommand('cmd_copy')"> + <menuitem id="context-copy" + label="©Cmd.label;" + accesskey="©Cmd.accesskey;" + command="cmd_copy" + disabled="true"/> + <menuitem id="context-selectall" + label="&selectAllCmd.label;" + accesskey="&selectAllCmd.accesskey;" + command="cmd_selectAll"/> + </menupopup> + </popupset> + + <hbox id="filler" style="min-width: 0%;"> + <spacer style="width: 29em;"/> + </hbox> + + <grid> + <columns> + <column/> + <column flex="1"/> + </columns> + + <rows> + <row> + <hbox id="iconContainer" align="start"> + <image id="info.icon" class="spaced"/> + </hbox> + <vbox id="infoContainer" +#ifndef XP_MACOSX + pack="center" +#endif + > + <!-- Only shown on OS X, since it has no dialog title --> + <description id="info.title" +#ifndef XP_MACOSX + hidden="true" +#else + style="margin-bottom: 1em" +#endif + /> + <description id="info.body" context="contentAreaContextMenu" noinitialfocus="true"/> + </vbox> + </row> + <row id="loginContainer" hidden="true" align="center"> + <label id="loginLabel" value="&editfield0.label;" control="loginTextbox"/> + <textbox id="loginTextbox"/> + </row> + <row id ="password1Container" hidden="true" align="center"> + <label id="password1Label" value="&editfield1.label;" control="password1Textbox"/> + <textbox type="password" id="password1Textbox"/> + </row> + <row id="checkboxContainer" hidden="true"> + <spacer/> + <checkbox id="checkbox" oncommand="Dialog.onCheckbox()"/> + </row> + </rows> + </grid> + +</dialog> diff --git a/toolkit/components/prompts/content/selectDialog.js b/toolkit/components/prompts/content/selectDialog.js new file mode 100644 index 000000000..7628dc8d9 --- /dev/null +++ b/toolkit/components/prompts/content/selectDialog.js @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var Ci = Components.interfaces; +var Cr = Components.results; +var Cc = Components.classes; +var Cu = Components.utils; + +var gArgs, listBox; + +function dialogOnLoad() { + gArgs = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2) + .QueryInterface(Ci.nsIWritablePropertyBag); + + let promptType = gArgs.getProperty("promptType"); + if (promptType != "select") { + Cu.reportError("selectDialog opened for unknown type: " + promptType); + window.close(); + } + + // Default to canceled. + gArgs.setProperty("ok", false); + + document.title = gArgs.getProperty("title"); + + let text = gArgs.getProperty("text"); + document.getElementById("info.txt").setAttribute("value", text); + + let items = gArgs.getProperty("list"); + listBox = document.getElementById("list"); + + for (let i = 0; i < items.length; i++) { + let str = items[i]; + if (str == "") + str = "<>"; + listBox.appendItem(str); + listBox.getItemAtIndex(i).addEventListener("dblclick", dialogDoubleClick, false); + } + listBox.selectedIndex = 0; + listBox.focus(); + + // resize the window to the content + window.sizeToContent(); + + // Move to the right location + moveToAlertPosition(); + centerWindowOnScreen(); + + // play sound + try { + Cc["@mozilla.org/sound;1"]. + createInstance(Ci.nsISound). + playEventSound(Ci.nsISound.EVENT_SELECT_DIALOG_OPEN); + } catch (e) { } +} + +function dialogOK() { + gArgs.setProperty("selected", listBox.selectedIndex); + gArgs.setProperty("ok", true); + return true; +} + +function dialogDoubleClick() { + dialogOK(); + window.close(); +} diff --git a/toolkit/components/prompts/content/selectDialog.xul b/toolkit/components/prompts/content/selectDialog.xul new file mode 100644 index 000000000..9b72bcfb0 --- /dev/null +++ b/toolkit/components/prompts/content/selectDialog.xul @@ -0,0 +1,22 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> +<!DOCTYPE dialog SYSTEM "chrome://global/locale/commonDialog.dtd"> + +<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="dialogOnLoad()" + ondialogaccept="return dialogOK();"> + + <script type="application/javascript" src="chrome://global/content/selectDialog.js" /> + <keyset id="dialogKeys"/> + <vbox style="width: 24em;margin: 5px;"> + <label id="info.txt"/> + <vbox> + <listbox id="list" rows="4" flex="1"/> + </vbox> + </vbox> +</dialog> diff --git a/toolkit/components/prompts/content/tabprompts.css b/toolkit/components/prompts/content/tabprompts.css new file mode 100644 index 000000000..c4b0f7593 --- /dev/null +++ b/toolkit/components/prompts/content/tabprompts.css @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Tab Modal Prompt boxes */ +tabmodalprompt { + width: 100%; + height: 100%; + -moz-box-pack: center; + -moz-box-orient: vertical; +} + +.mainContainer { + min-width: 20em; + min-height: 12em; + -moz-user-focus: normal; +} + +.info\.title { + margin-bottom: 1em !important; + font-weight: bold; +} + +.info\.body { + margin: 0 !important; + -moz-user-focus: normal; + -moz-user-select: text; + cursor: text !important; + white-space: pre-wrap; + unicode-bidi: plaintext; +} + +label[value=""] { + visibility: collapse; +} diff --git a/toolkit/components/prompts/content/tabprompts.xml b/toolkit/components/prompts/content/tabprompts.xml new file mode 100644 index 000000000..07c6c8efb --- /dev/null +++ b/toolkit/components/prompts/content/tabprompts.xml @@ -0,0 +1,352 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<!DOCTYPE bindings [ +<!ENTITY % commonDialogDTD SYSTEM "chrome://global/locale/commonDialog.dtd"> +<!ENTITY % dialogOverlayDTD SYSTEM "chrome://global/locale/dialogOverlay.dtd"> +%commonDialogDTD; +%dialogOverlayDTD; +]> + +<bindings id="tabPrompts" + xmlns="http://www.mozilla.org/xbl" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:xbl="http://www.mozilla.org/xbl"> + + <binding id="tabmodalprompt"> + + <resources> + <stylesheet src="chrome://global/content/tabprompts.css"/> + <stylesheet src="chrome://global/skin/tabprompts.css"/> + </resources> + + <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + role="dialog" + aria-describedby="info.body"> + + <!-- This is based on the guts of commonDialog.xul --> + <spacer flex="1"/> + <hbox pack="center"> + <vbox anonid="mainContainer" class="mainContainer"> + <grid class="topContainer" flex="1"> + <columns> + <column/> + <column flex="1"/> + </columns> + + <rows> + <vbox anonid="infoContainer" align="center" pack="center" flex="1"> + <description anonid="info.title" class="info.title" hidden="true" /> + <description anonid="info.body" class="info.body"/> + </vbox> + + <row anonid="loginContainer" hidden="true" align="center"> + <label anonid="loginLabel" value="&editfield0.label;" control="loginTextbox"/> + <textbox anonid="loginTextbox"/> + </row> + + <row anonid="password1Container" hidden="true" align="center"> + <label anonid="password1Label" value="&editfield1.label;" control="password1Textbox"/> + <textbox anonid="password1Textbox" type="password"/> + </row> + + <row anonid="checkboxContainer" hidden="true"> + <spacer/> + <checkbox anonid="checkbox"/> + </row> + + <xbl:children includes="row"/> + </rows> + </grid> + <xbl:children/> + <hbox class="buttonContainer"> +#ifdef XP_UNIX + <button anonid="button3" hidden="true"/> + <button anonid="button2" hidden="true"/> + <spacer anonid="buttonSpacer" flex="1"/> + <button anonid="button1" label="&cancelButton.label;"/> + <button anonid="button0" label="&okButton.label;"/> +#else + <button anonid="button3" hidden="true"/> + <spacer anonid="buttonSpacer" flex="1"/> + <button anonid="button0" label="&okButton.label;"/> + <button anonid="button2" hidden="true"/> + <button anonid="button1" label="&cancelButton.label;"/> +#endif + </hbox> + </vbox> + </hbox> + <spacer flex="2"/> + </xbl:content> + + <implementation implements="nsIDOMEventListener"> + <constructor> + <![CDATA[ + let self = this; + function getElement(anonid) { + return document.getAnonymousElementByAttribute(self, "anonid", anonid); + } + + this.ui = { + prompt : this, + loginContainer : getElement("loginContainer"), + loginTextbox : getElement("loginTextbox"), + loginLabel : getElement("loginLabel"), + password1Container : getElement("password1Container"), + password1Textbox : getElement("password1Textbox"), + password1Label : getElement("password1Label"), + infoBody : getElement("info.body"), + infoTitle : getElement("info.title"), + infoIcon : null, + checkbox : getElement("checkbox"), + checkboxContainer : getElement("checkboxContainer"), + button3 : getElement("button3"), + button2 : getElement("button2"), + button1 : getElement("button1"), + button0 : getElement("button0"), + // focusTarget (for BUTTON_DELAY_ENABLE) not yet supported + }; + + this.ui.button0.addEventListener("command", this.onButtonClick.bind(this, 0), false); + this.ui.button1.addEventListener("command", this.onButtonClick.bind(this, 1), false); + this.ui.button2.addEventListener("command", this.onButtonClick.bind(this, 2), false); + this.ui.button3.addEventListener("command", this.onButtonClick.bind(this, 3), false); + // Anonymous wrapper used here because |Dialog| doesn't exist until init() is called! + this.ui.checkbox.addEventListener("command", function() { self.Dialog.onCheckbox(); }, false); + this.isLive = false; + ]]> + </constructor> + <destructor> + <![CDATA[ + if (this.isLive) { + this.abortPrompt(); + } + ]]> + </destructor> + + <field name="ui"/> + <field name="args"/> + <field name="linkedTab"/> + <field name="onCloseCallback"/> + <field name="Dialog"/> + <field name="isLive"/> + <field name="availWidth"/> + <field name="availHeight"/> + <field name="minWidth"/> + <field name="minHeight"/> + + <method name="init"> + <parameter name="args"/> + <parameter name="linkedTab"/> + <parameter name="onCloseCallback"/> + <body> + <![CDATA[ + this.args = args; + this.linkedTab = linkedTab; + this.onCloseCallback = onCloseCallback; + + if (args.enableDelay) + throw "BUTTON_DELAY_ENABLE not yet supported for tab-modal prompts"; + + // We need to remove the prompt when the tab or browser window is closed or + // the page navigates, else we never unwind the event loop and that's sad times. + // Remember to cleanup in shutdownPrompt()! + this.isLive = true; + window.addEventListener("resize", this, false); + window.addEventListener("unload", this, false); + linkedTab.addEventListener("TabClose", this, false); + // Note: + // nsPrompter.js or in e10s mode browser-parent.js call abortPrompt, + // when the domWindow, for which the prompt was created, generates + // a "pagehide" event. + + let tmp = {}; + Components.utils.import("resource://gre/modules/CommonDialog.jsm", tmp); + this.Dialog = new tmp.CommonDialog(args, this.ui); + this.Dialog.onLoad(null); + + // Display the tabprompt title that shows the prompt origin when + // the prompt origin is not the same as that of the top window. + if (!args.showAlertOrigin) + this.ui.infoTitle.removeAttribute("hidden"); + + // TODO: should unhide buttonSpacer on Windows when there are 4 buttons. + // Better yet, just drop support for 4-button dialogs. (bug 609510) + + this.onResize(); + ]]> + </body> + </method> + + <method name="shutdownPrompt"> + <body> + <![CDATA[ + // remove our event listeners + try { + window.removeEventListener("resize", this, false); + window.removeEventListener("unload", this, false); + this.linkedTab.removeEventListener("TabClose", this, false); + } catch (e) { } + this.isLive = false; + // invoke callback + this.onCloseCallback(); + ]]> + </body> + </method> + + <method name="abortPrompt"> + <body> + <![CDATA[ + // Called from other code when the page changes. + this.Dialog.abortPrompt(); + this.shutdownPrompt(); + ]]> + </body> + </method> + + <method name="handleEvent"> + <parameter name="aEvent"/> + <body> + <![CDATA[ + switch (aEvent.type) { + case "resize": + this.onResize(); + break; + case "unload": + case "TabClose": + this.abortPrompt(); + break; + } + ]]> + </body> + </method> + + <method name="onResize"> + <body> + <![CDATA[ + let availWidth = this.clientWidth; + let availHeight = this.clientHeight; + if (availWidth == this.availWidth && availHeight == this.availHeight) + return; + this.availWidth = availWidth; + this.availHeight = availHeight; + + let self = this; + function getElement(anonid) { + return document.getAnonymousElementByAttribute(self, "anonid", anonid); + } + let main = getElement("mainContainer"); + let info = getElement("infoContainer"); + let body = this.ui.infoBody; + + // cap prompt dimensions at 60% width and 60% height of content area + if (!this.minWidth) + this.minWidth = parseInt(window.getComputedStyle(main).minWidth); + if (!this.minHeight) + this.minHeight = parseInt(window.getComputedStyle(main).minHeight); + let maxWidth = Math.max(Math.floor(availWidth * 0.6), this.minWidth) + + info.clientWidth - main.clientWidth; + let maxHeight = Math.max(Math.floor(availHeight * 0.6), this.minHeight) + + info.clientHeight - main.clientHeight; + body.style.maxWidth = maxWidth + "px"; + info.style.overflow = info.style.width = info.style.height = ""; + + // when prompt text is too long, use scrollbars + if (info.clientWidth > maxWidth) { + info.style.overflow = "auto"; + info.style.width = maxWidth + "px"; + } + if (info.clientHeight > maxHeight) { + info.style.overflow = "auto"; + info.style.height = maxHeight + "px"; + } + ]]> + </body> + </method> + + <method name="onButtonClick"> + <parameter name="buttonNum"/> + <body> + <![CDATA[ + // We want to do all the work her asynchronously off a Gecko + // runnable, because of situations like the one described in + // https://bugzilla.mozilla.org/show_bug.cgi?id=1167575#c35 : we + // get here off processing of an OS event and will also process + // one more Gecko runnable before we break out of the event loop + // spin whoever posted the prompt is doing. If we do all our + // work sync, we will exit modal state _before_ processing that + // runnable, and if exiting moral state posts a runnable we will + // incorrectly process that runnable before leaving our event + // loop spin. + Services.tm.mainThread.dispatch(() => { + this.Dialog["onButton" + buttonNum](); + this.shutdownPrompt(); + }, + Ci.nsIThread.DISPATCH_NORMAL); + ]]> + </body> + </method> + + <method name="onKeyAction"> + <parameter name="action"/> + <parameter name="event"/> + <body> + <![CDATA[ + if (event.defaultPrevented) + return; + + event.stopPropagation(); + if (action == "default") { + let bnum = this.args.defaultButtonNum || 0; + this.onButtonClick(bnum); + } else { // action == "cancel" + this.onButtonClick(1); // Cancel button + } + ]]> + </body> + </method> + </implementation> + + <handlers> + <!-- Based on dialog.xml handlers --> + <handler event="keypress" keycode="VK_RETURN" + group="system" action="this.onKeyAction('default', event);"/> + <handler event="keypress" keycode="VK_ESCAPE" + group="system" action="this.onKeyAction('cancel', event);"/> +#ifdef XP_MACOSX + <handler event="keypress" key="." modifiers="meta" + group="system" action="this.onKeyAction('cancel', event);"/> +#endif + <handler event="focus" phase="capturing"> + let bnum = this.args.defaultButtonNum || 0; + let defaultButton = this.ui["button" + bnum]; + + let { AppConstants } = + Components.utils.import("resource://gre/modules/AppConstants.jsm", {}); + if (AppConstants.platform == "macosx") { + // On OS X, the default button always stays marked as such (until + // the entire prompt blurs). + defaultButton.setAttribute("default", true); + } else { + // On other platforms, the default button is only marked as such + // when no other button has focus. XUL buttons on not-OSX will + // react to pressing enter as a command, so you can't trigger the + // default without tabbing to it or something that isn't a button. + let focusedDefault = (event.originalTarget == defaultButton); + let someButtonFocused = event.originalTarget instanceof Ci.nsIDOMXULButtonElement; + defaultButton.setAttribute("default", focusedDefault || !someButtonFocused); + } + </handler> + <handler event="blur"> + // If focus shifted to somewhere else in the browser, don't make + // the default button look active. + let bnum = this.args.defaultButtonNum || 0; + let button = this.ui["button" + bnum]; + button.setAttribute("default", false); + </handler> + </handlers> + + </binding> +</bindings> |