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'); + }); +});