1) Added support for dynamic tab size. Fixes #4231
2) Fixed an issue where a long file name is not visible on the process watcher dialog. Fixes #5950
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 96 KiB |
BIN
docs/en_US/images/preferences_browser_tab_settings.png
Normal file
|
After Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 145 KiB |
@@ -106,6 +106,23 @@ Use fields on the *Properties* panel to specify browser properties:
|
||||
* Provide a value in the *Maximum job history rows* field to limit the number of
|
||||
rows to show on the statistics tab for pgAgent jobs. The default is 250.
|
||||
|
||||
Use field on *Tab settings* panel to specify the tab related properties.
|
||||
|
||||
.. image:: images/preferences_browser_tab_settings.png
|
||||
:alt: Preferences dialog browser properties section
|
||||
:align: center
|
||||
|
||||
* Use *Debugger tab title placeholder* field to customize the Debugger tab title.
|
||||
|
||||
* When the *Dynamic tab size* If set to True, the tabs will take full size as per the title, it will also applicable for already opened tabs
|
||||
|
||||
* When the *Open in new browser tab* filed is selected for Query tool, Schema Diff or Debugger, it will
|
||||
open in a new browser tab when invoked.
|
||||
|
||||
* Use the *Query tool tab title placeholder* field to customize the query tool tab title.
|
||||
|
||||
* Use *View/Edit tab title placeholder* field to customize the View/Edit Data tab title.
|
||||
|
||||
The Dashboards Node
|
||||
*******************
|
||||
|
||||
@@ -155,15 +172,6 @@ The Debugger Node
|
||||
|
||||
Expand the *Debugger* node to specify your debugger display preferences.
|
||||
|
||||
.. image:: images/preferences_debugger_display.png
|
||||
:alt: Preferences dialog debugger display options
|
||||
:align: center
|
||||
|
||||
* Use *Debugger tab title placeholder* field to customize the Debugger tab title.
|
||||
|
||||
* When the *Open in new browser tab* switch is set to *True*, the Debugger will
|
||||
open in a new browser tab when invoked.
|
||||
|
||||
Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the
|
||||
debugger window navigation:
|
||||
|
||||
@@ -277,19 +285,12 @@ Tool display.
|
||||
* Use the *Connection status refresh rate* field to specify the number of
|
||||
seconds between connection/transaction status updates.
|
||||
|
||||
* When the *Open in new browser tab* switch is set to *True*, each new instance
|
||||
of the Query Tool will open in a new browser tab.
|
||||
|
||||
* Use the *Query info notifier timeout* field to control the behaviour of the
|
||||
notifier that is displayed when query execution completes. A value of *-1*
|
||||
will disable the notifier, and a value of 0 will display it until clicked. If
|
||||
a positive value above zero is specified, the notifier will be displayed for
|
||||
the specified number of seconds. The default is *5*.
|
||||
|
||||
* Use the *Query tool tab title placeholder* field to customize the query tool tab title.
|
||||
|
||||
* Use *View/Edit tab title placeholder* field to customize the View/Edit Data tab title.
|
||||
|
||||
.. image:: images/preferences_sql_editor.png
|
||||
:alt: Preferences dialog sqleditor editor settings
|
||||
:align: center
|
||||
@@ -427,8 +428,6 @@ Use the *Ignore owner* switch to ignores the owner while comparing the objects.
|
||||
Use the *Ignore whitespaces* switch to ignores the whitespaces while comparing
|
||||
the string objects. Whitespace includes space, tabs, and CRLF.
|
||||
|
||||
Use the *Open in new browser tab* switch to indicate if you would like Schema Diff
|
||||
to open in a new tab.
|
||||
|
||||
The Storage Node
|
||||
****************
|
||||
|
||||
@@ -11,6 +11,7 @@ New features
|
||||
|
||||
| `Issue #3318 <https://redmine.postgresql.org/issues/3318>`_ - Added support to download utility files at the client-side.
|
||||
| `Issue #4230 <https://redmine.postgresql.org/issues/4230>`_ - Added support to rename query tool and debugger tabs title.
|
||||
| `Issue #4231 <https://redmine.postgresql.org/issues/4231>`_ - Added support for dynamic tab size.
|
||||
| `Issue #4232 <https://redmine.postgresql.org/issues/4232>`_ - Added tab title placeholder for Query Tool, View/Edit Data, and Debugger.
|
||||
| `Issue #5891 <https://redmine.postgresql.org/issues/5891>`_ - Added support to compare schemas and databases in schema diff.
|
||||
|
||||
@@ -32,4 +33,5 @@ Bug fixes
|
||||
| `Issue #5914 <https://redmine.postgresql.org/issues/5914>`_ - Fixed an issue where a mismatch in the value of 'Estimated row' for functions.
|
||||
| `Issue #5919 <https://redmine.postgresql.org/issues/5919>`_ - Added security related enhancements.
|
||||
| `Issue #5923 <https://redmine.postgresql.org/issues/5923>`_ - Fixed an issue where non-closeable tabs are getting closed.
|
||||
| `Issue #5950 <https://redmine.postgresql.org/issues/5950>`_ - Fixed an issue where a long file name is not visible on the process watcher dialog.
|
||||
| `Issue #5953 <https://redmine.postgresql.org/issues/5953>`_ - Fixed an issue where connection to the server is on wait state if a different user is provided.
|
||||
|
||||
46
web/migrations/versions/81c7ffeffeee_.py
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
"""empty message
|
||||
|
||||
Revision ID: 81c7ffeffeee
|
||||
Revises: 398697dc9550
|
||||
Create Date: 2020-11-02 09:46:51.250338
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from pgadmin.model import db, Preferences
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '81c7ffeffeee'
|
||||
down_revision = '398697dc9550'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
"""
|
||||
Delete older preferences open new tab for Query tool, Debugger,
|
||||
and Schema diff.
|
||||
"""
|
||||
qt_open_tab_setting = Preferences.query.filter_by(
|
||||
name='new_browser_tab').order_by(Preferences.id.desc()).first()
|
||||
debugger_tab_setting = Preferences.query.filter_by(
|
||||
name='debugger_new_browser_tab').order_by(Preferences.id.desc()).first()
|
||||
schema_diff_tab_setting = Preferences.query.filter_by(
|
||||
name='schema_diff_new_browser_tab').order_by(
|
||||
Preferences.id.desc()).first()
|
||||
|
||||
if qt_open_tab_setting:
|
||||
db.session.delete(qt_open_tab_setting)
|
||||
if debugger_tab_setting:
|
||||
db.session.delete(debugger_tab_setting)
|
||||
if schema_diff_tab_setting:
|
||||
db.session.delete(schema_diff_tab_setting)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def downgrade():
|
||||
# pgAdmin only upgrades, downgrade not implemented.
|
||||
pass
|
||||
@@ -8,7 +8,8 @@
|
||||
##########################################################################
|
||||
from flask_babelex import gettext
|
||||
from pgadmin.utils.constants import PREF_LABEL_DISPLAY,\
|
||||
PREF_LABEL_KEYBOARD_SHORTCUTS
|
||||
PREF_LABEL_KEYBOARD_SHORTCUTS, PREF_LABEL_TABS_SETTINGS, \
|
||||
PREF_LABEL_OPTIONS
|
||||
import config
|
||||
|
||||
LOCK_LAYOUT_LEVEL = {
|
||||
@@ -441,3 +442,70 @@ def register_browser_preferences(self):
|
||||
category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
|
||||
fields=fields
|
||||
)
|
||||
|
||||
self.dynamic_tab_title = self.preference.register(
|
||||
'tab settings', 'dynamic_tabs',
|
||||
gettext("Dynamic tab size"), 'boolean', False,
|
||||
category_label=PREF_LABEL_TABS_SETTINGS,
|
||||
help_str=gettext(
|
||||
'If set to True, the tabs will take full size as per the title, '
|
||||
'it will also applicable for already opened tabs')
|
||||
)
|
||||
|
||||
self.qt_tab_title = self.preference.register(
|
||||
'tab settings', 'qt_tab_title_placeholder',
|
||||
gettext("Query tool tab title"),
|
||||
'text', '%DATABASE%/%USERNAME%@%SERVER%',
|
||||
category_label=PREF_LABEL_DISPLAY,
|
||||
help_str=gettext(
|
||||
'Supported placeholders are %DATABASE%, %USERNAME%, and %SERVER%. '
|
||||
'Users can provide any string with or without placeholders of'
|
||||
' their choice. The blank title will be revert back to the'
|
||||
' default title with placeholders.'
|
||||
)
|
||||
)
|
||||
|
||||
self.ve_edt_tab_title = self.preference.register(
|
||||
'tab settings', 'vw_edt_tab_title_placeholder',
|
||||
gettext("View/Edit data tab title"),
|
||||
'text', '%SCHEMA%.%TABLE%/%DATABASE%/%USERNAME%@%SERVER%',
|
||||
category_label=PREF_LABEL_DISPLAY,
|
||||
help_str=gettext(
|
||||
'Supported placeholders are %SCHEMA%, %TABLE%, %DATABASE%, '
|
||||
'%USERNAME%, and %SERVER%. Users can provide any string with or '
|
||||
'without placeholders of their choice. The blank title will be '
|
||||
'revert back to the default title with placeholders.'
|
||||
)
|
||||
)
|
||||
|
||||
self.debugger_tab_title = self.preference.register(
|
||||
'tab settings', 'debugger_tab_title_placeholder',
|
||||
gettext("Debugger tab title"),
|
||||
'text', '%FUNCTION%(%ARGS%)',
|
||||
category_label=PREF_LABEL_DISPLAY,
|
||||
help_str=gettext(
|
||||
'Supported placeholders are %FUNCTION%, %ARGS%, %SCHEMA% and'
|
||||
' %DATABASE%. Users can provide any string with or '
|
||||
'without placeholders of their choice. The blank title will be'
|
||||
' revert back to the default title with placeholders.'
|
||||
)
|
||||
)
|
||||
|
||||
self.open_in_new_tab = self.preference.register(
|
||||
'tab settings', 'new_browser_tab_open',
|
||||
gettext("Open in new browser tab"), 'select2', None,
|
||||
category_label=PREF_LABEL_OPTIONS,
|
||||
options=[{'label': gettext('Query Tool'), 'value': 'qt'},
|
||||
{'label': gettext('Debugger'), 'value': 'debugger'},
|
||||
{'label': gettext('Schema Diff'), 'value': 'schema_diff'}],
|
||||
help_str=gettext('Select Query Tool, Debugger, or Schema Diff from '
|
||||
'the drop-down to set open in new browser tab for '
|
||||
'that particular module.'),
|
||||
select2={
|
||||
'multiple': True, 'allowClear': False,
|
||||
'tags': True, 'first_empty': False,
|
||||
'selectOnClose': False, 'emptyOptions': True,
|
||||
'tokenSeparators': [','],
|
||||
'placeholder': gettext('Select open new tab...')
|
||||
}
|
||||
)
|
||||
|
||||
@@ -102,6 +102,7 @@ $bgproc-container-pad: 2px;
|
||||
border: $border-width solid $input-border-color;
|
||||
border-radius: $input-border-radius;
|
||||
background: $input-disabled-bg;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.pg-panel-content .bg-process-footer {
|
||||
|
||||
@@ -11,10 +11,11 @@ define('pgadmin.preferences', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
|
||||
'pgadmin.alertifyjs', 'sources/pgadmin', 'pgadmin.backform',
|
||||
'pgadmin.browser', 'sources/modify_animation',
|
||||
'tools/datagrid/static/js/show_query_tool',
|
||||
'sources/tree/pgadmin_tree_save_state',
|
||||
], function(
|
||||
gettext, url_for, $, _, Backbone, Alertify, pgAdmin, Backform, pgBrowser,
|
||||
modifyAnimation
|
||||
modifyAnimation, showQueryTool
|
||||
) {
|
||||
// This defines the Preference/Options Dialog for pgAdmin IV.
|
||||
|
||||
@@ -272,6 +273,32 @@ define('pgadmin.preferences', [
|
||||
}
|
||||
p.options = opts;
|
||||
return 'select2';
|
||||
case 'select2':
|
||||
var select_opts = [];
|
||||
_.each(p.options, function(o) {
|
||||
if ('label' in o && 'value' in o) {
|
||||
let push_var = {
|
||||
'label': o.label,
|
||||
'value': o.value,
|
||||
};
|
||||
push_var['label'] = o.label;
|
||||
push_var['value'] = o.value;
|
||||
|
||||
if('preview_src' in o) {
|
||||
push_var['preview_src'] = o.preview_src;
|
||||
}
|
||||
select_opts.push(push_var);
|
||||
} else {
|
||||
select_opts.push({
|
||||
'label': o,
|
||||
'value': o,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
p.options = select_opts;
|
||||
return 'select2';
|
||||
|
||||
case 'multiline':
|
||||
return 'textarea';
|
||||
case 'switch':
|
||||
@@ -470,6 +497,11 @@ define('pgadmin.preferences', [
|
||||
let modulesChanged = {};
|
||||
_.each(changed, (val, key)=> {
|
||||
let pref = pgBrowser.get_preference_for_id(Number(key));
|
||||
|
||||
if(pref['name'] == 'dynamic_tabs') {
|
||||
showQueryTool._set_dynamic_tab(pgBrowser, !pref['value']);
|
||||
}
|
||||
|
||||
if(!modulesChanged[pref.module]) {
|
||||
modulesChanged[pref.module] = true;
|
||||
}
|
||||
|
||||
@@ -1021,6 +1021,7 @@ table.table-empty-rows{
|
||||
.ajs-content {
|
||||
.tab-content {
|
||||
bottom: 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -313,6 +313,10 @@
|
||||
max-width: 125px;
|
||||
}
|
||||
|
||||
.wcPanelTab-dynamic {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.wcPanelTabActive > div {
|
||||
width: auto !important;
|
||||
max-width: 100%;
|
||||
|
||||
@@ -211,7 +211,6 @@ define('pgadmin.datagrid', [
|
||||
},
|
||||
|
||||
launch_grid: function(trans_id, panel_url, is_query_tool, panel_title, sURL=null, sql_filter=null) {
|
||||
var self = this;
|
||||
|
||||
let queryToolForm = `
|
||||
<form id="queryToolForm" action="${panel_url}" method="post">
|
||||
@@ -233,7 +232,9 @@ define('pgadmin.datagrid', [
|
||||
</script>
|
||||
`;
|
||||
|
||||
if (self.preferences.new_browser_tab) {
|
||||
var browser_preferences = pgBrowser.get_preferences_for_module('browser');
|
||||
var open_new_tab = browser_preferences.new_browser_tab_open;
|
||||
if (open_new_tab && open_new_tab.includes('qt')) {
|
||||
var newWin = window.open('', '_blank');
|
||||
if(newWin) {
|
||||
newWin.document.write(queryToolForm);
|
||||
@@ -248,6 +249,8 @@ define('pgadmin.datagrid', [
|
||||
var propertiesPanel = pgBrowser.docker.findPanels('properties');
|
||||
var queryToolPanel = pgBrowser.docker.addPanel('frm_datagrid', wcDocker.DOCK.STACKED, propertiesPanel[0]);
|
||||
|
||||
showQueryTool._set_dynamic_tab(pgBrowser, browser_preferences['dynamic_tabs']);
|
||||
|
||||
// Set panel title and icon
|
||||
panelTitleFunc.setQueryToolDockerTitle(queryToolPanel, is_query_tool, _.unescape(panel_title));
|
||||
queryToolPanel.focus();
|
||||
|
||||
@@ -20,7 +20,7 @@ function isServerInformationAvailable(parentData) {
|
||||
}
|
||||
|
||||
export function getPanelTitle(pgBrowser, selected_item=null, custom_title=null) {
|
||||
var preferences = pgBrowser.get_preferences_for_module('sqleditor');
|
||||
var preferences = pgBrowser.get_preferences_for_module('browser');
|
||||
if(selected_item == null) {
|
||||
selected_item = pgBrowser.treeMenu.selected();
|
||||
}
|
||||
@@ -39,11 +39,15 @@ export function getPanelTitle(pgBrowser, selected_item=null, custom_title=null)
|
||||
qt_title_placeholder = preferences['qt_tab_title_placeholder'];
|
||||
}
|
||||
|
||||
qt_title_placeholder = qt_title_placeholder.replace(new RegExp('%DATABASE%'), db_label);
|
||||
qt_title_placeholder = qt_title_placeholder.replace(new RegExp('%USERNAME%'), parentData.server.user.name);
|
||||
qt_title_placeholder = qt_title_placeholder.replace(new RegExp('%SERVER%'), parentData.server.label);
|
||||
var title_data = {
|
||||
'database': db_label,
|
||||
'username': parentData.server.user.name,
|
||||
'server': parentData.server.label,
|
||||
'type': 'query_tool',
|
||||
};
|
||||
var title = generateTitle(qt_title_placeholder, title_data);
|
||||
|
||||
return _.escape(qt_title_placeholder);
|
||||
return _.escape(title);
|
||||
}
|
||||
|
||||
export function setQueryToolDockerTitle(panel, is_query_tool, panel_title, is_file) {
|
||||
@@ -66,3 +70,25 @@ export function setQueryToolDockerTitle(panel, is_query_tool, panel_title, is_fi
|
||||
panel.title('<span title="'+ _.escape(panel_tooltip) +'">'+ _.escape(panel_title) +'</span>');
|
||||
panel.icon(panel_icon);
|
||||
}
|
||||
|
||||
export function generateTitle(title_placeholder, title_data) {
|
||||
|
||||
if(title_data.type == 'query_tool') {
|
||||
title_placeholder = title_placeholder.replace(new RegExp('%DATABASE%'), title_data.database);
|
||||
title_placeholder = title_placeholder.replace(new RegExp('%USERNAME%'), title_data.username);
|
||||
title_placeholder = title_placeholder.replace(new RegExp('%SERVER%'), title_data.server);
|
||||
} else if(title_data.type == 'datagrid') {
|
||||
title_placeholder = title_placeholder.replace(new RegExp('%DATABASE%'), title_data.database);
|
||||
title_placeholder = title_placeholder.replace(new RegExp('%USERNAME%'), title_data.username);
|
||||
title_placeholder = title_placeholder.replace(new RegExp('%SERVER%'), title_data.server);
|
||||
title_placeholder = title_placeholder.replace(new RegExp('%SCHEMA%'), title_data.schema);
|
||||
title_placeholder = title_placeholder.replace(new RegExp('%TABLE%'), title_data.table);
|
||||
} else if(title_data.type == 'debugger') {
|
||||
title_placeholder = title_placeholder.replace(new RegExp('%FUNCTION%'), title_data.function_name);
|
||||
title_placeholder = title_placeholder.replace(new RegExp('%ARGS%'), title_data.args);
|
||||
title_placeholder = title_placeholder.replace(new RegExp('%SCHEMA%'), title_data.schema);
|
||||
title_placeholder = title_placeholder.replace(new RegExp('%DATABASE%'), title_data.database);
|
||||
}
|
||||
|
||||
return _.escape(title_placeholder);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import gettext from '../../../../static/js/gettext';
|
||||
import url_for from '../../../../static/js/url_for';
|
||||
import {getTreeNodeHierarchyFromIdentifier} from '../../../../static/js/tree/pgadmin_tree_node';
|
||||
import {getDatabaseLabel} from './datagrid_panel_title';
|
||||
import {getDatabaseLabel, generateTitle} from './datagrid_panel_title';
|
||||
import CodeMirror from 'bundled_codemirror';
|
||||
import * as SqlEditorUtils from 'sources/sqleditor_utils';
|
||||
import $ from 'jquery';
|
||||
@@ -280,7 +280,7 @@ function hasSchemaOrCatalogOrViewInformation(parentData) {
|
||||
|
||||
export function generateDatagridTitle(pgBrowser, aciTreeIdentifier, custom_title=null) {
|
||||
//const baseTitle = getPanelTitle(pgBrowser, aciTreeIdentifier);
|
||||
var preferences = pgBrowser.get_preferences_for_module('sqleditor');
|
||||
var preferences = pgBrowser.get_preferences_for_module('browser');
|
||||
const parentData = getTreeNodeHierarchyFromIdentifier.call(
|
||||
pgBrowser,
|
||||
aciTreeIdentifier
|
||||
@@ -297,11 +297,16 @@ export function generateDatagridTitle(pgBrowser, aciTreeIdentifier, custom_title
|
||||
dtg_title_placeholder = preferences['vw_edt_tab_title_placeholder'];
|
||||
}
|
||||
|
||||
dtg_title_placeholder = dtg_title_placeholder.replace(new RegExp('%DATABASE%'), db_label);
|
||||
dtg_title_placeholder = dtg_title_placeholder.replace(new RegExp('%USERNAME%'), parentData.server.user.name);
|
||||
dtg_title_placeholder = dtg_title_placeholder.replace(new RegExp('%SERVER%'), parentData.server.label);
|
||||
dtg_title_placeholder = dtg_title_placeholder.replace(new RegExp('%SCHEMA%'), namespaceName);
|
||||
dtg_title_placeholder = dtg_title_placeholder.replace(new RegExp('%TABLE%'), node.getData().label);
|
||||
|
||||
return _.escape(dtg_title_placeholder);
|
||||
var title_data = {
|
||||
'database': db_label,
|
||||
'username': parentData.server.user.name,
|
||||
'server': parentData.server.label,
|
||||
'schema': namespaceName,
|
||||
'table': node.getData().label,
|
||||
'type': 'datagrid',
|
||||
};
|
||||
var title = generateTitle(dtg_title_placeholder, title_data);
|
||||
|
||||
return _.escape(title);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import url_for from '../../../../static/js/url_for';
|
||||
import {getTreeNodeHierarchyFromIdentifier} from '../../../../static/js/tree/pgadmin_tree_node';
|
||||
import {getPanelTitle} from './datagrid_panel_title';
|
||||
import {getRandomInt} from 'sources/utils';
|
||||
import $ from 'jquery';
|
||||
|
||||
function hasDatabaseInformation(parentData) {
|
||||
return parentData.database;
|
||||
@@ -96,3 +97,24 @@ export function launchDataGrid(datagrid, transId, gridUrl, queryToolTitle, sURL,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function _set_dynamic_tab(pgBrowser, value){
|
||||
var datagrid_panels = pgBrowser.docker.findPanels('frm_datagrid');
|
||||
datagrid_panels.forEach(panel => {
|
||||
if(value) {
|
||||
$('#' + panel.$title.index() + ' div:first').addClass('wcPanelTab-dynamic');
|
||||
} else {
|
||||
$('#' + panel.$title.index() + ' div:first').removeClass('wcPanelTab-dynamic');
|
||||
}
|
||||
});
|
||||
|
||||
var debugger_panels = pgBrowser.docker.findPanels('frm_debugger');
|
||||
debugger_panels.forEach(panel => {
|
||||
if(value) {
|
||||
$('#' + panel.$title.index() + ' div:first').addClass('wcPanelTab-dynamic');
|
||||
} else {
|
||||
$('#' + panel.$title.index() + ' div:first').removeClass('wcPanelTab-dynamic');
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -77,39 +77,6 @@ class DebuggerModule(PgAdminModule):
|
||||
return scripts
|
||||
|
||||
def register_preferences(self):
|
||||
self.open_in_new_tab = self.preference.register(
|
||||
'display', 'debugger_new_browser_tab',
|
||||
gettext("Open in new browser tab"), 'boolean', False,
|
||||
category_label=PREF_LABEL_DISPLAY,
|
||||
help_str=gettext('If set to True, the Debugger '
|
||||
'will be opened in a new browser tab.')
|
||||
)
|
||||
|
||||
self.tab_title = self.preference.register(
|
||||
'display', 'debugger_tab_title_placeholder',
|
||||
gettext("Tab title"),
|
||||
'text', '%FUNCTION%(%ARGS%)',
|
||||
category_label=PREF_LABEL_DISPLAY,
|
||||
help_str=gettext(
|
||||
'Supported placeholders are %FUNCTION%, %ARGS%, %SCHEMA% and'
|
||||
' %DATABASE%. Users can provide any string with or '
|
||||
'without placeholders of their choice. The blank title will be'
|
||||
' revert back to the default title with placeholders.'
|
||||
)
|
||||
)
|
||||
|
||||
self.tab_title = self.preference.register(
|
||||
'display', 'debugger_tab_title_placeholder',
|
||||
gettext("Debugger tab title placeholder"),
|
||||
'text', '%FUNCTION%/%SCHEMA%/%DATABASE%',
|
||||
category_label=PREF_LABEL_DISPLAY,
|
||||
help_str=gettext(
|
||||
'Supported placeholders: FUNCTION, SCHEMA and DATABASE. '
|
||||
'You can also provide any string with or '
|
||||
'without placeholders'
|
||||
)
|
||||
)
|
||||
|
||||
self.preference.register(
|
||||
'keyboard_shortcuts', 'btn_start',
|
||||
gettext('Accesskey (Continue/Start)'), 'keyboardshortcut',
|
||||
|
||||
@@ -334,8 +334,7 @@ define([
|
||||
var t = pgBrowser.tree,
|
||||
i = item || t.selected(),
|
||||
d = i && i.length == 1 ? t.itemData(i) : undefined,
|
||||
node = d && pgBrowser.Nodes[d._type],
|
||||
self = this;
|
||||
node = d && pgBrowser.Nodes[d._type];
|
||||
|
||||
if (!d)
|
||||
return;
|
||||
@@ -410,8 +409,9 @@ define([
|
||||
var url = url_for('debugger.direct', {
|
||||
'trans_id': res.data.debuggerTransId,
|
||||
});
|
||||
|
||||
if (self.preferences.debugger_new_browser_tab) {
|
||||
var browser_preferences = pgBrowser.get_preferences_for_module('browser');
|
||||
var open_new_tab = browser_preferences.new_browser_tab_open;
|
||||
if (open_new_tab && open_new_tab.includes('debugger')) {
|
||||
window.open(url, '_blank');
|
||||
} else {
|
||||
pgBrowser.Events.once(
|
||||
@@ -427,7 +427,7 @@ define([
|
||||
panel = pgBrowser.docker.addPanel(
|
||||
'frm_debugger', wcDocker.DOCK.STACKED, dashboardPanel[0]
|
||||
);
|
||||
debuggerUtils.setDebuggerTitle(panel, self.preferences, treeInfo.function.label, treeInfo.schema.label, treeInfo.database.label);
|
||||
debuggerUtils.setDebuggerTitle(panel, browser_preferences, treeInfo.function.label, treeInfo.schema.label, treeInfo.database.label);
|
||||
|
||||
panel.focus();
|
||||
|
||||
@@ -448,7 +448,8 @@ define([
|
||||
// We will execute this function when user clicks on the OK button
|
||||
function(evt, value) {
|
||||
if(value) {
|
||||
debuggerUtils.setDebuggerTitle(panel, self.preferences, treeInfo.function.label, treeInfo.schema.label, treeInfo.database.label, value);
|
||||
let browser_preferences = pgBrowser.get_preferences_for_module('browser');
|
||||
debuggerUtils.setDebuggerTitle(panel, browser_preferences, treeInfo.function.label, treeInfo.schema.label, treeInfo.database.label, value);
|
||||
}
|
||||
},
|
||||
// We will execute this function when user clicks on the Cancel
|
||||
@@ -478,8 +479,7 @@ define([
|
||||
var t = pgBrowser.tree,
|
||||
i = item || t.selected(),
|
||||
d = i && i.length == 1 ? t.itemData(i) : undefined,
|
||||
node = d && pgBrowser.Nodes[d._type],
|
||||
self = this;
|
||||
node = d && pgBrowser.Nodes[d._type];
|
||||
|
||||
if (!d)
|
||||
return;
|
||||
@@ -549,7 +549,9 @@ define([
|
||||
'trans_id': trans_id,
|
||||
});
|
||||
|
||||
if (self.preferences.debugger_new_browser_tab) {
|
||||
var browser_preferences = pgBrowser.get_preferences_for_module('browser');
|
||||
var open_new_tab = browser_preferences.new_browser_tab_open;
|
||||
if (open_new_tab && open_new_tab.includes('debugger')) {
|
||||
window.open(url, '_blank');
|
||||
} else {
|
||||
pgBrowser.Events.once(
|
||||
@@ -565,7 +567,7 @@ define([
|
||||
panel = pgBrowser.docker.addPanel(
|
||||
'frm_debugger', wcDocker.DOCK.STACKED, dashboardPanel[0]
|
||||
);
|
||||
debuggerUtils.setDebuggerTitle(panel, self.preferences, newTreeInfo.function.label, newTreeInfo.schema.label, newTreeInfo.database.label);
|
||||
debuggerUtils.setDebuggerTitle(panel, browser_preferences, newTreeInfo.function.label, newTreeInfo.schema.label, newTreeInfo.database.label);
|
||||
|
||||
panel.focus();
|
||||
|
||||
@@ -586,7 +588,8 @@ define([
|
||||
// We will execute this function when user clicks on the OK button
|
||||
function(evt, value) {
|
||||
if(value) {
|
||||
debuggerUtils.setDebuggerTitle(panel, self.preferences, treeInfo.function.label, treeInfo.schema.label, treeInfo.database.label, value);
|
||||
let browser_preferences = pgBrowser.get_preferences_for_module('browser');
|
||||
debuggerUtils.setDebuggerTitle(panel, browser_preferences, treeInfo.function.label, treeInfo.schema.label, treeInfo.database.label, value);
|
||||
}
|
||||
},
|
||||
// We will execute this function when user clicks on the Cancel
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
define([
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
|
||||
'pgadmin.alertifyjs', 'sources/pgadmin', 'pgadmin.browser',
|
||||
'pgadmin.backgrid', 'sources/window', 'pgadmin.tools.debugger.utils', 'wcdocker',
|
||||
'pgadmin.backgrid', 'sources/window', 'pgadmin.tools.debugger.utils',
|
||||
'tools/datagrid/static/js/show_query_tool', 'wcdocker',
|
||||
], function(
|
||||
gettext, url_for, $, _, Backbone, Alertify, pgAdmin, pgBrowser, Backgrid, pgWindow, debuggerUtils
|
||||
gettext, url_for, $, _, Backbone, Alertify, pgAdmin, pgBrowser, Backgrid,
|
||||
pgWindow, debuggerUtils, showQueryTool
|
||||
) {
|
||||
|
||||
var wcDocker = window.wcDocker;
|
||||
@@ -759,7 +761,9 @@ define([
|
||||
}
|
||||
);
|
||||
|
||||
if (self.preferences.debugger_new_browser_tab) {
|
||||
var browserPreferences = pgWindow.default.pgAdmin.Browser.get_preferences_for_module('browser');
|
||||
var open_new_tab = browserPreferences.new_browser_tab_open;
|
||||
if (open_new_tab && open_new_tab.includes('debugger')) {
|
||||
window.open(url, '_blank');
|
||||
} else {
|
||||
pgBrowser.Events.once(
|
||||
@@ -773,7 +777,10 @@ define([
|
||||
panel = pgBrowser.docker.addPanel(
|
||||
'frm_debugger', wcDocker.DOCK.STACKED, dashboardPanel[0]
|
||||
);
|
||||
debuggerUtils.setDebuggerTitle(panel, self.preferences, treeInfo.function.label, treeInfo.schema.label, treeInfo.database.label);
|
||||
var browser_pref = pgBrowser.get_preferences_for_module('browser');
|
||||
debuggerUtils.setDebuggerTitle(panel, browser_pref, treeInfo.function.label, treeInfo.schema.label, treeInfo.database.label);
|
||||
|
||||
showQueryTool._set_dynamic_tab(pgBrowser, browser_pref['dynamic_tabs']);
|
||||
|
||||
panel.focus();
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
import {generateTitle} from '../../../datagrid/static/js/datagrid_panel_title';
|
||||
|
||||
function setFocusToDebuggerEditor(editor, command) {
|
||||
const TAB = 9;
|
||||
@@ -59,12 +60,15 @@ function setDebuggerTitle(panel, preferences, function_name, schema_name, databa
|
||||
args = args.concat(args_list[0]);
|
||||
}
|
||||
|
||||
debugger_title_placeholder = debugger_title_placeholder.replace(new RegExp('%FUNCTION%'), function_name);
|
||||
debugger_title_placeholder = debugger_title_placeholder.replace(new RegExp('%ARGS%'), args);
|
||||
debugger_title_placeholder = debugger_title_placeholder.replace(new RegExp('%SCHEMA%'), schema_name);
|
||||
debugger_title_placeholder = debugger_title_placeholder.replace(new RegExp('%DATABASE%'), database_name);
|
||||
|
||||
panel.title('<span>'+ _.escape(debugger_title_placeholder) +'</span>');
|
||||
var title_data = {
|
||||
'function_name': function_name,
|
||||
'args': args,
|
||||
'schema': schema_name,
|
||||
'database': database_name,
|
||||
'type': 'debugger',
|
||||
};
|
||||
var title = generateTitle(debugger_title_placeholder, title_data);
|
||||
panel.title('<span>'+ _.escape(title) +'</span>');
|
||||
}
|
||||
|
||||
function get_function_name(function_name) {
|
||||
|
||||
@@ -1835,7 +1835,9 @@ define([
|
||||
/* If debugger is in a new tab, event fired is not available
|
||||
* instead, a poller is set up who will check
|
||||
*/
|
||||
if(self.preferences.debugger_new_browser_tab) {
|
||||
var browser_preferences = browser.get_preferences_for_module('browser');
|
||||
var open_new_tab = browser_preferences.new_browser_tab_open;
|
||||
if (open_new_tab && open_new_tab.includes('debugger')) {
|
||||
pgBrowser.bind_beforeunload();
|
||||
let pollIntervalId = setInterval(()=>{
|
||||
if(pgWindow.default.pgAdmin) {
|
||||
|
||||
@@ -77,13 +77,6 @@ class SchemaDiffModule(PgAdminModule):
|
||||
]
|
||||
|
||||
def register_preferences(self):
|
||||
self.preference.register(
|
||||
'display', 'schema_diff_new_browser_tab',
|
||||
gettext("Open in new browser tab"), 'boolean', False,
|
||||
category_label=PREF_LABEL_DISPLAY,
|
||||
help_str=gettext('If set to True, the Schema Diff '
|
||||
'will be opened in a new browser tab.')
|
||||
)
|
||||
|
||||
self.preference.register(
|
||||
'display', 'ignore_whitespaces',
|
||||
|
||||
@@ -106,7 +106,9 @@ define('pgadmin.schemadiff', [
|
||||
},
|
||||
baseUrl = url_for('schema_diff.panel', url_params);
|
||||
|
||||
if (this.preferences.schema_diff_new_browser_tab) {
|
||||
var browser_preferences = pgBrowser.get_preferences_for_module('browser');
|
||||
var open_new_tab = browser_preferences.new_browser_tab_open;
|
||||
if (open_new_tab && open_new_tab.includes('schema_diff')) {
|
||||
window.open(baseUrl, '_blank');
|
||||
} else {
|
||||
|
||||
|
||||
@@ -95,7 +95,9 @@ define('tools.querytool', [
|
||||
this.handler['col_size'] = {};
|
||||
let browser = pgWindow.default.pgAdmin.Browser;
|
||||
this.preferences = browser.get_preferences_for_module('sqleditor');
|
||||
this.browser_preferences = browser.get_preferences_for_module('browser');
|
||||
this.handler.preferences = this.preferences;
|
||||
this.handler.browser_preferences = this.browser_preferences;
|
||||
this.connIntervalId = null;
|
||||
this.layout = opts.layout;
|
||||
this.set_server_version(opts.server_ver);
|
||||
@@ -484,7 +486,8 @@ define('tools.querytool', [
|
||||
}, 200);
|
||||
});
|
||||
|
||||
if (!self.preferences.new_browser_tab) {
|
||||
var open_new_tab = self.browser_preferences.new_browser_tab_open;
|
||||
if (open_new_tab && open_new_tab.includes('qt')) {
|
||||
// Listen on the panel closed event and notify user to save modifications.
|
||||
_.each(pgWindow.default.pgAdmin.Browser.docker.findPanels('frm_datagrid'), function(p) {
|
||||
if (p.isVisible()) {
|
||||
@@ -723,7 +726,9 @@ define('tools.querytool', [
|
||||
/* If sql editor is in a new tab, event fired is not available
|
||||
* instead, a poller is set up who will check
|
||||
*/
|
||||
if(self.preferences.new_browser_tab) {
|
||||
//var browser_qt_preferences = pgBrowser.get_preferences_for_module('browser');
|
||||
var open_new_tab_qt = self.browser_preferences.new_browser_tab_open;
|
||||
if(open_new_tab_qt && open_new_tab_qt.includes('qt')) {
|
||||
pgBrowser.bind_beforeunload();
|
||||
setInterval(()=>{
|
||||
if(pgWindow.default.pgAdmin) {
|
||||
@@ -2394,7 +2399,8 @@ define('tools.querytool', [
|
||||
}).fail((xhr, status, error)=>{
|
||||
if (xhr.status === 410) {
|
||||
//checking for Query tool in new window.
|
||||
if(self.preferences.new_browser_tab) {
|
||||
var open_new_tab = self.browser_preferences.new_browser_tab_open;
|
||||
if(open_new_tab && open_new_tab.includes('qt')) {
|
||||
pgBrowser.report_error(gettext('Error fetching rows - %s.', xhr.statusText), xhr.responseJSON.errormsg, undefined, window.close);
|
||||
} else {
|
||||
pgBrowser.report_error(gettext('Error fetching rows - %s.', xhr.statusText), xhr.responseJSON.errormsg, undefined, self.close.bind(self));
|
||||
@@ -2635,7 +2641,8 @@ define('tools.querytool', [
|
||||
pgAdmin, self, jqx, null, [], false
|
||||
);
|
||||
if (msg) {
|
||||
if(self.preferences.new_browser_tab) {
|
||||
var open_new_tab = self.browser_preferences.new_browser_tab_open;
|
||||
if(open_new_tab && open_new_tab.includes('qt')) {
|
||||
pgBrowser.report_error(gettext('Error fetching SQL for script - %s.', jqx.statusText), jqx.responseJSON.errormsg, undefined, window.close);
|
||||
} else {
|
||||
pgBrowser.report_error(gettext('Error fetching SQL for script - %s.', jqx.statusText), jqx.responseJSON.errormsg, undefined, self.close.bind(self));
|
||||
@@ -3687,8 +3694,8 @@ define('tools.querytool', [
|
||||
// Set panel title.
|
||||
setTitle: function(title, is_file) {
|
||||
var self = this;
|
||||
|
||||
if (self.preferences.new_browser_tab) {
|
||||
var open_new_tab = self.browser_preferences.new_browser_tab_open;
|
||||
if(open_new_tab && open_new_tab.includes('qt')) {
|
||||
window.document.title = title;
|
||||
} else {
|
||||
_.each(pgWindow.default.pgAdmin.Browser.docker.findPanels('frm_datagrid'), function(p) {
|
||||
@@ -3850,7 +3857,8 @@ define('tools.querytool', [
|
||||
var title = self.gridView.current_file.replace(/^.*[\\\/]/g, '') + ' *';
|
||||
self.setTitle(title, true);
|
||||
} else {
|
||||
if (self.preferences.new_browser_tab) {
|
||||
var open_new_tab = self.browser_preferences.new_browser_tab_open;
|
||||
if(open_new_tab && open_new_tab.includes('qt')) {
|
||||
title = window.document.title + ' *';
|
||||
} else {
|
||||
// Find the title of the visible panel
|
||||
@@ -3918,7 +3926,8 @@ define('tools.querytool', [
|
||||
if (arguments.length > 0 && arguments[arguments.length - 1] == 'connect') {
|
||||
reconnect = true;
|
||||
}
|
||||
newConnectionHandler.dialog(self, reconnect, self.preferences);
|
||||
|
||||
newConnectionHandler.dialog(self, reconnect, self.browser_preferences);
|
||||
},
|
||||
// This function will include the filter by selection.
|
||||
_include_filter: function() {
|
||||
@@ -4570,8 +4579,8 @@ define('tools.querytool', [
|
||||
|
||||
_show_query_tool: function() {
|
||||
var self = this;
|
||||
|
||||
if(self.preferences.new_browser_tab) {
|
||||
var open_new_tab = self.browser_preferences.new_browser_tab_open;
|
||||
if (open_new_tab && open_new_tab.includes('qt')) {
|
||||
is_main_window_alive();
|
||||
}
|
||||
this._open_query_tool(self);
|
||||
|
||||
@@ -34,14 +34,6 @@ def register_query_tool_preferences(self):
|
||||
)
|
||||
)
|
||||
|
||||
self.open_in_new_tab = self.preference.register(
|
||||
'display', 'new_browser_tab',
|
||||
gettext("Open in new browser tab"), 'boolean', False,
|
||||
category_label=PREF_LABEL_DISPLAY,
|
||||
help_str=gettext('If set to True, the Query Tool '
|
||||
'will be opened in a new browser tab.')
|
||||
)
|
||||
|
||||
self.explain_verbose = self.preference.register(
|
||||
'Explain', 'explain_verbose',
|
||||
gettext("Verbose output?"), 'boolean', False,
|
||||
@@ -300,55 +292,6 @@ def register_query_tool_preferences(self):
|
||||
'transaction status.')
|
||||
)
|
||||
|
||||
self.qt_tab_title = self.preference.register(
|
||||
'display', 'qt_tab_title_placeholder',
|
||||
gettext("Query tool tab title"),
|
||||
'text', '%DATABASE%/%USERNAME%@%SERVER%',
|
||||
category_label=PREF_LABEL_DISPLAY,
|
||||
help_str=gettext(
|
||||
'Supported placeholders are %DATABASE%, %USERNAME%, and %SERVER%. '
|
||||
'Users can provide any string with or without placeholders of'
|
||||
' their choice. The blank title will be revert back to the'
|
||||
' default title with placeholders.'
|
||||
)
|
||||
)
|
||||
|
||||
self.ve_edt_tab_title = self.preference.register(
|
||||
'display', 'vw_edt_tab_title_placeholder',
|
||||
gettext("View/Edit data tab title"),
|
||||
'text', '%SCHEMA%.%TABLE%/%DATABASE%/%USERNAME%@%SERVER%',
|
||||
category_label=PREF_LABEL_DISPLAY,
|
||||
help_str=gettext(
|
||||
'Supported placeholders are %SCHEMA%, %TABLE%, %DATABASE%, '
|
||||
'%USERNAME%, and %SERVER%. Users can provide any string with or '
|
||||
'without placeholders of their choice. The blank title will be '
|
||||
'revert back to the default title with placeholders.'
|
||||
)
|
||||
)
|
||||
|
||||
self.qt_tab_title = self.preference.register(
|
||||
'display', 'qt_tab_title_placeholder',
|
||||
gettext("Query tool tab title placeholder"),
|
||||
'text', '%DATABASE%/%USERNAME%@%SERVER%',
|
||||
category_label=PREF_LABEL_DISPLAY,
|
||||
help_str=gettext(
|
||||
'Supported placeholders: DATABASE, USERNAME and SERVER. '
|
||||
'You can also provide any string with or without placeholders.'
|
||||
)
|
||||
)
|
||||
|
||||
self.ve_edt_tab_title = self.preference.register(
|
||||
'display', 'vw_edt_tab_title_placeholder',
|
||||
gettext("View/Edit tab title placeholder"),
|
||||
'text', '%SCHEMA%.%TABLE%/%DATABASE%/%USERNAME%@%SERVER%',
|
||||
category_label=PREF_LABEL_DISPLAY,
|
||||
help_str=gettext(
|
||||
'Supported placeholders: SCHEMA, TABLE, DATABASE, USERNAME and '
|
||||
'SERVER. You can also provide any string with or '
|
||||
'without placeholders.'
|
||||
)
|
||||
)
|
||||
|
||||
self.connection_status = self.preference.register(
|
||||
'display', 'connection_status_fetch_time',
|
||||
gettext("Connection status refresh rate"), 'integer', 2,
|
||||
|
||||
@@ -24,6 +24,7 @@ PREF_LABEL_EDITOR = gettext('Editor')
|
||||
PREF_LABEL_CSV_TXT = gettext('CSV/TXT Output')
|
||||
PREF_LABEL_RESULTS_GRID = gettext('Results grid')
|
||||
PREF_LABEL_SQL_FORMATTING = gettext('SQL formatting')
|
||||
PREF_LABEL_TABS_SETTINGS = gettext('Tab settings')
|
||||
|
||||
PGADMIN_NODE = 'pgadmin.node.%s'
|
||||
UNAUTH_REQ = "Unauthorized request."
|
||||
|
||||
@@ -120,6 +120,13 @@ class _Preference(object):
|
||||
if self.select2 and self.select2['tags']:
|
||||
return res.value
|
||||
return self.default
|
||||
if self._type == 'select2':
|
||||
if res.value:
|
||||
res.value = res.value.replace('[', '')
|
||||
res.value = res.value.replace(']', '')
|
||||
res.value = res.value.replace('\'', '')
|
||||
return [val.strip() for val in res.value.split(',')]
|
||||
return None
|
||||
if self._type == 'text' and res.value == '' and not self.allow_blanks:
|
||||
return self.default
|
||||
|
||||
@@ -413,7 +420,7 @@ class Preferences(object):
|
||||
assert _type in (
|
||||
'boolean', 'integer', 'numeric', 'date', 'datetime',
|
||||
'options', 'multiline', 'switch', 'node', 'text', 'radioModern',
|
||||
'keyboardshortcut'
|
||||
'keyboardshortcut', 'select2'
|
||||
), "Type cannot be found in the defined list!"
|
||||
|
||||
(cat['preferences'])[name] = res = _Preference(
|
||||
|
||||
@@ -18,7 +18,7 @@ var dummy_cache = [
|
||||
{
|
||||
id: 1,
|
||||
mid: 1,
|
||||
module:'sqleditor',
|
||||
module:'browser',
|
||||
name:'qt_tab_title_placeholder',
|
||||
value: '%DATABASE%/%USERNAME%@%SERVER%',
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ var dummy_cache = [
|
||||
{
|
||||
id: 1,
|
||||
mid: 1,
|
||||
module:'sqleditor',
|
||||
module:'browser',
|
||||
name:'vw_edt_tab_title_placeholder',
|
||||
value: '%SCHEMA%.%TABLE%/%DATABASE%/%USERNAME%@%SERVER%',
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ var dummy_cache = [
|
||||
{
|
||||
id: 1,
|
||||
mid: 1,
|
||||
module:'sqleditor',
|
||||
module:'browser',
|
||||
name:'qt_tab_title_placeholder',
|
||||
value: '%DATABASE%/%USERNAME%@%SERVER%',
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ var dummy_cache = [
|
||||
{
|
||||
id: 1,
|
||||
mid: 1,
|
||||
module:'sqleditor',
|
||||
module:'browser',
|
||||
name:'qt_tab_title_placeholder',
|
||||
value: '%DATABASE%/%USERNAME%@%SERVER%',
|
||||
},
|
||||
|
||||