diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js
index 29c11c2e..8c0f9a5a 100644
--- a/web/pgadmin/browser/static/js/browser.js
+++ b/web/pgadmin/browser/static/js/browser.js
@@ -12,7 +12,7 @@ define('pgadmin.browser', [
'sources/gettext', 'sources/url_for', 'require', 'jquery', 'underscore', 'underscore.string',
'bootstrap', 'sources/pgadmin', 'pgadmin.alertifyjs', 'bundled_codemirror',
'sources/check_node_visibility', './toolbar', 'pgadmin.help',
- 'sources/csrf', 'pgadmin.browser.utils',
+ 'sources/csrf', 'sources/utils', 'pgadmin.browser.utils',
'wcdocker', 'jquery.contextmenu', 'jquery.aciplugin', 'jquery.acitree',
'pgadmin.browser.preferences', 'pgadmin.browser.messages',
'pgadmin.browser.menu', 'pgadmin.browser.panel', 'pgadmin.browser.layout',
@@ -24,7 +24,7 @@ define('pgadmin.browser', [
tree,
gettext, url_for, require, $, _, S,
Bootstrap, pgAdmin, Alertify, codemirror,
- checkNodeVisibility, toolBar, help, csrfToken
+ checkNodeVisibility, toolBar, help, csrfToken, pgadminUtils,
) {
window.jQuery = window.$ = $;
// Some scripts do export their object in the window only.
@@ -102,6 +102,38 @@ define('pgadmin.browser', [
b.tree = $('#tree').aciTree('api');
b.treeMenu.register($('#tree'));
+
+ b.treeMenu.registerDraggableType({
+ 'table partition type sequence package view mview foreign_table edbvar' : (data, item)=>{
+ return pgadminUtils.fully_qualify(b, data, item);
+ },
+ 'schema column' : (data)=>{
+ return pgadminUtils.quote_ident(data.label);
+ },
+ 'edbfunc function edbproc procedure' : (data, item)=>{
+ let newData = {
+ ...data,
+ },
+ bracketPos = newData.label.lastIndexOf('(');
+
+ /* Strip the bracket */
+ newData.label = newData.label.substr(0, bracketPos);
+ let dropVal = pgadminUtils.fully_qualify(b, newData, item);
+ dropVal = dropVal + '()';
+
+ let curPos = dropVal.length;
+ /* If it has params */
+ if(data.label.substr(bracketPos).length > 2) {
+ curPos = curPos - 1;
+ }
+ return {
+ text: dropVal,
+ cur: {
+ from: curPos, to: curPos,
+ },
+ };
+ },
+ });
};
// Extend the browser class attributes
diff --git a/web/pgadmin/static/js/tree/tree.js b/web/pgadmin/static/js/tree/tree.js
index 782f4d59..99397ef4 100644
--- a/web/pgadmin/static/js/tree/tree.js
+++ b/web/pgadmin/static/js/tree/tree.js
@@ -8,6 +8,7 @@
//////////////////////////////////////////////////////////////////////////
import {isValidData} from 'sources/utils';
+import $ from 'jquery';
export class TreeNode {
constructor(id, data, domNode, parent) {
@@ -97,6 +98,87 @@ export class Tree {
constructor() {
this.rootNode = new TreeNode(undefined, {});
this.aciTreeApi = undefined;
+ this.draggableTypes = {};
+ }
+
+ /*
+ *
+ * The dropDetailsFunc should return an object of sample
+ * {text: 'xyz', cur: {from:0, to:0} where text is the drop text and
+ * cur is selection range of text after dropping. If returned as
+ * string, by default cursor will be set to the end of text
+ */
+ registerDraggableType(typeOrTypeDict, dropDetailsFunc=null) {
+ if(typeof typeOrTypeDict == 'object') {
+ Object.keys(typeOrTypeDict).forEach((type)=>{
+ this.registerDraggableType(type, typeOrTypeDict[type]);
+ });
+ } else {
+ if(dropDetailsFunc != null) {
+ typeOrTypeDict.replace(/ +/, ' ').split(' ').forEach((type)=>{
+ this.draggableTypes[type] = dropDetailsFunc;
+ });
+ }
+ }
+ }
+
+ getDraggable(type) {
+ if(this.draggableTypes[type]) {
+ return this.draggableTypes[type];
+ } else {
+ return null;
+ }
+ }
+
+ prepareDraggable(data, item) {
+ let dropDetailsFunc = this.getDraggable(data._type);
+
+ if(dropDetailsFunc != null) {
+ item.find('.aciTreeItem')
+ .attr('draggable', true)
+ .on('dragstart', (e)=> {
+ let dropDetails = dropDetailsFunc(data, item);
+ let origEvent = e.originalEvent;
+
+ if(typeof dropDetails == 'string') {
+ dropDetails = {
+ text:dropDetails,
+ cur:{
+ from:dropDetails.length,
+ to: dropDetails.length,
+ },
+ };
+ } else {
+ if(!dropDetails.cur) {
+ dropDetails = {
+ ...dropDetails,
+ cur:{
+ from:dropDetails.text.length,
+ to: dropDetails.text.length,
+ },
+ };
+ }
+ }
+
+ origEvent.dataTransfer.setData('text', JSON.stringify(dropDetails));
+
+ /* setDragImage is not supported in IE. We leave it to
+ * its default look and feel
+ */
+ if(origEvent.dataTransfer.setDragImage) {
+ let dragItem = $(`
+
+ ${dropDetails.text}
+
`
+ );
+
+ $('body .drag-tree-node').remove();
+ $('body').append(dragItem);
+
+ origEvent.dataTransfer.setDragImage(dragItem[0], 0, 0);
+ }
+ });
+ }
}
addNewNode(id, data, domNode, parentPath) {
@@ -163,6 +245,9 @@ export class Tree {
if (eventName === 'added') {
const id = api.getId(item);
const data = api.itemData(item);
+
+ this.prepareDraggable(data, item);
+
const parentId = this.translateTreeNodeIdFromACITree(api.parent(item));
this.addNewNode(id, data, item, parentId);
}
diff --git a/web/pgadmin/static/js/utils.js b/web/pgadmin/static/js/utils.js
index 1c58a9eb..5c908afb 100644
--- a/web/pgadmin/static/js/utils.js
+++ b/web/pgadmin/static/js/utils.js
@@ -8,6 +8,7 @@
//////////////////////////////////////////////////////////////////////////
import _ from 'underscore';
+import { getTreeNodeHierarchyFromIdentifier } from 'sources/tree/pgadmin_tree_node';
export function parseShortcutValue(obj) {
var shortcut = '';
@@ -83,3 +84,50 @@ export function getGCD(inp_arr) {
export function getMod(no, divisor) {
return ((no % divisor) + divisor) % divisor;
}
+
+export function quote_ident(value) {
+ /* check if the string is number or not */
+ let quoteIt = false;
+ if (!isNaN(parseInt(value))){
+ quoteIt = true;
+ }
+
+ if(value.search(/[^a-z0-9_]/g) > -1) {
+ quoteIt = true;
+ }
+
+ if(quoteIt) {
+ return `"${value}"`;
+ } else {
+ return value;
+ }
+}
+
+export function fully_qualify(pgBrowser, data, item) {
+ const parentData = getTreeNodeHierarchyFromIdentifier.call(pgBrowser, item);
+ let namespace = '';
+
+ if (parentData.schema !== undefined) {
+ namespace = quote_ident(parentData.schema.label);
+ }
+ else if (parentData.view !== undefined) {
+ namespace = quote_ident(parentData.view.label);
+ }
+ else if (parentData.catalog !== undefined) {
+ namespace = quote_ident(parentData.catalog.label);
+ }
+
+ if (parentData.package !== undefined && data._type != 'package') {
+ if(namespace == '') {
+ namespace = quote_ident(parentData.package.label);
+ } else {
+ namespace += '.' + quote_ident(parentData.package.label);
+ }
+ }
+
+ if(namespace != '') {
+ return namespace + '.' + quote_ident(data.label);
+ } else {
+ return quote_ident(data.label);
+ }
+}
diff --git a/web/pgadmin/static/scss/_pgadmin.style.scss b/web/pgadmin/static/scss/_pgadmin.style.scss
index ed426545..f5a8877c 100644
--- a/web/pgadmin/static/scss/_pgadmin.style.scss
+++ b/web/pgadmin/static/scss/_pgadmin.style.scss
@@ -983,3 +983,15 @@ table.table-empty-rows{
padding: 0px !important;
position: absolute;
}
+
+.drag-tree-node {
+ position: absolute;
+ top:-100px;
+ left:0;
+ z-index: 99999;
+ color: $input-focus-color;
+ background: $input-bg;
+ border: $input-border-width solid $input-focus-border-color;
+ border-radius: $input-border-radius;
+ padding: $input-btn-padding-y $input-btn-padding-x;
+}
diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
index a8560574..ef468f63 100644
--- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
@@ -341,8 +341,31 @@ define('tools.querytool', [
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
extraKeys: pgBrowser.editor_shortcut_keys,
scrollbarStyle: 'simple',
+ dragDrop: false,
});
+ if(self.handler.is_query_tool) {
+ self.query_tool_obj.setOption('dragDrop', true);
+ self.query_tool_obj.on('drop', (editor, e) => {
+ var cursor = editor.coordsChar({
+ left: e.x,
+ top: e.y,
+ });
+ var dropDetails = JSON.parse(e.dataTransfer.getData('text'));
+ e.codemirrorIgnore = true;
+ e.dataTransfer.clearData('text');
+ editor.replaceRange(dropDetails.text, cursor);
+ editor.focus();
+ editor.setSelection({
+ ...cursor,
+ ch: cursor.ch + dropDetails.cur.from,
+ },{
+ ...cursor,
+ ch: cursor.ch +dropDetails.cur.to,
+ });
+ });
+ }
+
pgBrowser.Events.on('pgadmin:query_tool:sql_panel:focus', ()=>{
self.query_tool_obj.focus();
});
diff --git a/web/regression/javascript/pgadmin_utils_spec.js b/web/regression/javascript/pgadmin_utils_spec.js
index 02bd5478..7df78d29 100644
--- a/web/regression/javascript/pgadmin_utils_spec.js
+++ b/web/regression/javascript/pgadmin_utils_spec.js
@@ -7,7 +7,7 @@
//
//////////////////////////////////////////////////////////////
-import { getEpoch, getGCD, getMod } from 'sources/utils';
+import { getEpoch, getGCD, getMod, quote_ident } from 'sources/utils';
describe('getEpoch', function () {
it('should return non zero', function () {
@@ -51,3 +51,20 @@ describe('getMod', function () {
expect(getMod(-7,5)).toEqual(3);
});
});
+
+describe('quote_ident', function () {
+ it('normal string', function () {
+ expect(quote_ident('abcd')).toEqual('abcd');
+ });
+
+ it('contains certain characters string', function () {
+ expect(quote_ident('Abcd')).toEqual('"Abcd"');
+ expect(quote_ident('abc$d')).toEqual('"abc$d"');
+ expect(quote_ident('ab cd')).toEqual('"ab cd"');
+ });
+
+ it('starts with number', function () {
+ expect(quote_ident('1a')).toEqual('"1a"');
+ expect(quote_ident('a1')).toEqual('a1');
+ });
+});