Browse Source

HUE-6869 [editor] Enable internal links in the language reference details

Johan Ahlen 7 years ago
parent
commit
5ca9305322

File diff suppressed because it is too large
+ 0 - 0
desktop/core/src/desktop/static/desktop/css/hue-embedded.css


File diff suppressed because it is too large
+ 0 - 0
desktop/core/src/desktop/static/desktop/css/hue.css


File diff suppressed because it is too large
+ 0 - 0
desktop/core/src/desktop/static/desktop/css/hue3-extra.css


File diff suppressed because it is too large
+ 409 - 151
desktop/core/src/desktop/static/desktop/js/sqlImpalaLangRef.js


+ 16 - 4
desktop/core/src/desktop/static/desktop/less/hue-assist.less

@@ -572,7 +572,8 @@
 
 .assist-flex-fill,
 .assist-flex-half,
-.assist-flex-quarter {
+.assist-flex-quarter,
+.assist-flex-three-quarter {
   position: relative;
   white-space: nowrap;
   overflow-x: hidden;
@@ -589,7 +590,11 @@
 }
 
 .assist-flex-quarter {
-  .flex(1 1 25%);
+  .flex(0 1 25%);
+}
+
+.assist-flex-three-quarter {
+  .flex(0 1 75%);
 }
 
 .database-tree ul {
@@ -631,12 +636,19 @@
   box-shadow: none;
 }
 
-.assist-function-details {
+.assist-docs-topic-tree {
+  list-style: none;
+  overflow-x: hidden;
+  margin: 0 0 0 8px;
+}
+
+.assist-docs-details {
   border-top: 2px solid @cui-default-border-color;
   margin-top: 5px;
   margin-right: 10px;
   padding: 10px;
   white-space: normal;
+  overflow-x: auto;
 
   .sql-docs-inline-code {
     font-family: @font-family-monospace;
@@ -645,7 +657,7 @@
   .sql-docs-code-block {
     display: block;
     font-family: @font-family-monospace;
-    white-space: pre;
+    white-space: pre-wrap;
     margin-bottom: 10px;
     color: @cui-gray-700;
   }

+ 74 - 18
desktop/core/src/desktop/templates/assist.mako

@@ -2222,6 +2222,19 @@ from desktop.views import _ko
     })();
   </script>
 
+  <script type="text/html" id="language-reference-topic-tree">
+    <!-- ko if: $data.length -->
+    <ul class="assist-docs-topic-tree " data-bind="foreach: $data">
+      <li>
+        <a class="assist-field-link" href="javascript: void(0);" data-bind="css: { 'blue': $component.selectedTopic() === $data }, click: function () { $component.selectedTopic($data); }, toggle: open, text: title"></a>
+        <!-- ko if: open -->
+        <!-- ko template: { name: 'language-reference-topic-tree', data: children } --><!-- /ko -->
+        <!-- /ko -->
+      </li>
+    </ul>
+    <!-- /ko -->
+  </script>
+
   <script type="text/html" id="language-reference-panel-template">
     <div class="assist-inner-panel">
       <div class="assist-flex-panel">
@@ -2230,39 +2243,28 @@ from desktop.views import _ko
             <input class="clearable" type="text" placeholder="Filter..." data-bind="clearable: query, value: query, valueUpdate: 'afterkeydown'">
           </div>
         </div>
-        <div data-bind="css: { 'assist-flex-fill': !selectedTopic(), 'assist-flex-half': selectedTopic() }">
+        <div class="assist-docs-topics" data-bind="css: { 'assist-flex-fill': !selectedTopic(), 'assist-flex-quarter': selectedTopic() }">
           <!-- ko ifnot: query -->
-          <ul class="assist-functions" data-bind="foreach: availableTopics">
-            <li>
-              <a class="assist-field-link" href="javascript: void(0);" data-bind="css: { 'blue': $parent.selectedTopic() === $data }, click: function () { $parent.selectedTopic($data); }, toggle: open, text: title"></a>
-              <!-- ko if: open -->
-              <ul class="assist-functions" data-bind="foreach: children">
-                <li>
-                  <a class="assist-field-link" href="javascript: void(0);" data-bind="css: { 'blue': $parents[1].selectedTopic() === $data }, click: function () { $parents[1].selectedTopic($data); }, toggle: open, text: title"></a>
-                </li>
-              </ul>
-              <!-- /ko -->
-            </li>
-          </ul>
+          <!-- ko template: { name: 'language-reference-topic-tree', data: availableTopics } --><!-- /ko -->
           <!-- /ko -->
           <!-- ko if: query -->
           <!-- ko if: filteredTopics().length > 0 -->
-          <ul class="assist-functions" data-bind="foreach: filteredTopics">
+          <ul class="assist-docs-topic-tree" data-bind="foreach: filteredTopics">
             <li>
-              <a class="assist-field-link" href="javascript: void(0);" data-bind="css: { 'blue': $parent.selectedTopic() === $data }, click: function () { $parent.selectedTopic($data); }, html: titleMatch() || title"></a>
+              <a class="assist-field-link" href="javascript: void(0);" data-bind="css: { 'blue': $component.selectedTopic() === $data }, click: function () { $component.selectedTopic($data); }, html: titleMatch() || title"></a>
             </li>
           </ul>
           <!-- /ko -->
           <!-- ko if: filteredTopics().length === 0 -->
-          <ul class="assist-functions">
+          <ul class="assist-docs-topic-tree">
             <li class="assist-no-entries">${ _('No matches found. ') }</li>
           </ul>
           <!-- /ko -->
           <!-- /ko -->
         </div>
         <!-- ko if: selectedTopic -->
-        <div class="assist-flex-half assist-function-details" data-bind="with: selectedTopic">
-          <div class="assist-panel-close"><button class="close" data-bind="click: function() { $parent.selectedTopic(undefined); }">&times;</button></div>
+        <div class="assist-flex-three-quarter assist-docs-details" data-bind="with: selectedTopic">
+          <div class="assist-panel-close"><button class="close" data-bind="click: function() { $component.selectedTopic(undefined); }">&times;</button></div>
           <div class="assist-function-signature blue" data-bind="html: titleMatch() || title"></div>
           <div data-bind="html: bodyMatch() || body"></div>
         </div>
@@ -2329,6 +2331,7 @@ from desktop.views import _ko
     (function () {
       function LanguageReferencePanel (params, element) {
         var self = this;
+        self.disposals = [];
         self.availableTopics = impalaLangRefTopics;
         self.selectedTopic = ko.observable();
 
@@ -2365,9 +2368,62 @@ from desktop.views import _ko
             return a.title.localeCompare(b.title);
           });
           return flattenedTopics;
+        });
+
+        var selectedTopicSub = self.selectedTopic.subscribe(function () {
+          $(element).find('.assist-docs-details').scrollTop(0);
+        });
+
+        var querySub = self.query.subscribe(function () {
+          $(element).find('.assist-docs-topics').scrollTop(0);
+        });
+
+        self.disposals.push(function () {
+          selectedTopicSub.dispose();
+          querySub.dispose();
+        });
+
+        $(element).on('click.langref', function (event) {
+          if (event.target.className === 'lang-ref-link') {
+            var targetTopic = $(event.target).data('target').split('#');
+            var topicId = targetTopic[0];
+
+            var topicStack = [];
+            var findTopic = function (topics) {
+              topics.some(function (topic) {
+                topicStack.push(topic);
+                if (topic.id === topicId) {
+                  while (topicStack.length) {
+                    topicStack.pop().open(true);
+                  }
+                  self.query('');
+                  self.selectedTopic(topic);
+                  return true;
+                } else if (topic.children.length) {
+                  var inChild = findTopic(topic.children);
+                  if (inChild) {
+                    return true;
+                  }
+                }
+                topicStack.pop();
+              })
+            };
+            findTopic(self.availableTopics);
+          }
+        });
+
+        self.disposals.push(function () {
+          $(element).off('click.langref');
         })
       }
 
+      LanguageReferencePanel.prototype.dispose = function () {
+        var self = this;
+        while (self.disposals.length) {
+          self.disposals.pop()();
+        }
+      };
+
       ko.components.register('language-reference-panel', {
         viewModel: {
           createViewModel: function(params, componentInfo) {

+ 140 - 108
tools/sql-docs/impalaExtractor.js

@@ -21,6 +21,7 @@ let keyDefs = {};
 let pathToXref = {};
 let knownTitles = {};
 let xrefs = {};
+let conRefs = {};
 
 let handleChildNodes = (x, path, body) => {
   x.childNodes().forEach(y => {
@@ -65,17 +66,29 @@ let handleElement = (x, path, body) => {
         } else {
           body.push(x.attr('keyref').value())
         }
+      } else if (x.attr('conref') && x.attr('conref').value().indexOf('impala_common.xml') !== -1) {
+        var id = x.attr('conref').value().replace(/^.*common\//, '');
+        if (conRefs[id]) {
+          handleElement(conRefs[id], path, body);
+        } else {
+          console.log('concept ref not found with id: ' + id);
+        }
       }
     } else {
       switch (x.name()) {
         case 'title': { wrapHtmlElement(x, path, body, 'h4'); break; }
-        case 'p': { wrapHtmlElement(x, path, body, 'p'); break; }
+        case 'p': {
+          wrapHtmlElement(x, path, body, 'p');
+          break;
+        }
         case 'concept': {
-          wrapHtmlElement(x, path, body, 'div');
-          if (x.attr('id') && x.get('title')) {
-            var titleParts = [];
-            handleElement(x.get('title'), path, titleParts);
-            knownTitles[path.substring(path.indexOf('topics/')) + '#' + x.attr('id').value()] = titleParts.join('');
+          if (!x.attr('audience') || x.attr('audience').value() !== 'hidden') {
+            wrapHtmlElement(x, path, body, 'div');
+            if (x.attr('id') && x.get('title')) {
+              var titleParts = [];
+              handleElement(x.get('title'), path, titleParts);
+              knownTitles[path.substring(path.indexOf('topics/')) + '#' + x.attr('id').value()] = titleParts.join('');
+            }
           }
           break;
         }
@@ -106,16 +119,19 @@ let handleElement = (x, path, body) => {
         case 'filepath':
         case 'term': { wrapHtmlElement(x, path, body, 'span', 'sql-docs-variable'); break; }
         case 'note': { wrapHtmlElement(x, path, body, 'div', 'sql-docs-note'); break; }
-        case 'b': { wrapHtmlElement(x, path, body, 'b', 'sql-docs-variable'); break; }
-        case 'q':  { wrapHtmlElement(x, path, body, 'q', 'sql-docs-variable'); break; }
-        case 'i': { wrapHtmlElement(x, path, body, 'i', 'sql-docs-variable'); break; }
-        case 'sup': { wrapHtmlElement(x, path, body, 'sup', 'sql-docs-variable'); break; }
-        case 'ul': { wrapHtmlElement(x, path, body, 'ul', 'sql-docs-variable'); break; }
-        case 'li': { wrapHtmlElement(x, path, body, 'li', 'sql-docs-variable'); break; }
+        case 'b': { wrapHtmlElement(x, path, body, 'b'); break; }
+        case 'q':  { wrapHtmlElement(x, path, body, 'q'); break; }
+        case 'i': { wrapHtmlElement(x, path, body, 'i'); break; }
+        case 'sup': { wrapHtmlElement(x, path, body, 'sup'); break; }
+        case 'ul': { wrapHtmlElement(x, path, body, 'ul'); break; }
+        case 'li': { wrapHtmlElement(x, path, body, 'li'); break; }
         case 'indexterm':
         case 'metadata':
         case 'fig':
         case 'prolog':
+        case 'titlealts':
+        case 'uicontrol':
+        case 'table':
         case 'navtitle': break;
         default: console.log('Could not process element of type: ' + x.name() + ' in ' + path);
       }
@@ -136,7 +152,7 @@ let parseDml = (path) => {
         }
         xmlDoc.get('//title').remove();
         xmlDoc.childNodes().forEach(x => {
-          handleChildNodes(x, path, body);
+          handleElement(x, path, body);
         });
         resolve({ title: titleParts.join(''), body: body});
       } catch (err) {
@@ -172,7 +188,7 @@ class Topic {
   }
 }
 
-fs.readFile('../Impala/docs/impala_keydefs.ditamap', 'utf8', (err, keyDefRaw) => {
+ fs.readFile('../Impala/docs/impala_keydefs.ditamap', 'utf8', (err, keyDefRaw) => {
   if (err) {
     console.log('Could not find the Impala docs! (../Impala/docs/impala_keydefs.ditamap)');
     console.log('Make sure you have Impala checked out in an "Impala" folder next to the hue folder');
@@ -180,110 +196,126 @@ fs.readFile('../Impala/docs/impala_keydefs.ditamap', 'utf8', (err, keyDefRaw) =>
   }
 
   libxml.parseXmlString(keyDefRaw).get('//map').childNodes().forEach(x => {
-    if (x.name() === 'keydef' && x.attr('keys')) {
-      let valNode = x.get('topicmeta/keywords/keyword');
-      if (valNode) {
-        keyDefs[x.attr('keys').value()] = valNode.text();
-      } else if (x.attr('href')) {
-        xrefs[x.attr('keys')] = {
-          ref: x.attr('href').value(),
-          parsed: false,
-          external: x.attr('scope') && x.attr('scope').value() === 'external'
-        };
-        pathToXref[x.attr('href').value()] = xrefs[x.attr('keys')];
-      }
-    }
+   if (x.name() === 'keydef' && x.attr('keys')) {
+     let valNode = x.get('topicmeta/keywords/keyword');
+     if (valNode) {
+       keyDefs[x.attr('keys').value()] = valNode.text();
+     } else if (x.attr('href')) {
+       xrefs[x.attr('keys')] = {
+         ref: x.attr('href').value(),
+         parsed: false,
+         external: x.attr('scope') && x.attr('scope').value() === 'external'
+       };
+       pathToXref[x.attr('href').value()] = xrefs[x.attr('keys')];
+     }
+   }
   });
 
   let stringifyTopic = (topic, prefix) => {
-    let result = prefix + '{\n' + prefix + '  id: \'' + topic.ref + '\',\n' + prefix + '  title: \'' + topic.title + '\',\n' + prefix + '  weight: 1,\n' + prefix + '  bodyMatch: ko.observable(),\n' + prefix + '  open: ko.observable(false),\n' + prefix +'  titleMatch: ko.observable()';
+     let result = prefix + '{\n' + prefix + '  id: \'' + topic.ref + '\',\n' + prefix + '  title: \'' + topic.title + '\',\n' + prefix + '  weight: 1,\n' + prefix + '  bodyMatch: ko.observable(),\n' + prefix + '  open: ko.observable(false),\n' + prefix +'  titleMatch: ko.observable()';
 
-    if (topic.body.length) {
-      var bodyString = '';
-      topic.body.forEach(function (bodyElement) {
-        if (typeof bodyElement === 'string') {
-          bodyString += bodyElement;
-        } else if (bodyElement.xrefNode) {
-          if (bodyElement.xrefNode.attr('href')) {
-            if (bodyElement.xrefNode.attr('scope') && bodyElement.xrefNode.attr('scope').value() === 'external') {
-              bodyString += '<a target="_blank" href="' + bodyElement.xrefNode.attr('href').value() + '">' + bodyElement.xrefNode.text() + '</a>'
-            } else {
-              let href = bodyElement.xrefNode.attr('href').value();
-              if (href.indexOf('#') === 0) {
-                href = bodyElement.path.substring(bodyElement.path.indexOf('topics/')) + href;
-              } else if (href.indexOf('topics/') !== -1) {
-                href = href.substring(href.indexOf('topics')); // clean up [..]/topic/ etc.
-              } else {
-                href = 'topics/' + href;
-              }
-              var split = href.split('#');
+     if (topic.body.length) {
+       var bodyString = '';
+       topic.body.forEach(function (bodyElement) {
+         if (typeof bodyElement === 'string') {
+           bodyString += bodyElement;
+         } else if (bodyElement.xrefNode) {
+           if (bodyElement.xrefNode.attr('href')) {
+             if (bodyElement.xrefNode.attr('scope') && bodyElement.xrefNode.attr('scope').value() === 'external') {
+               bodyString += '<a target="_blank" href="' + bodyElement.xrefNode.attr('href').value() + '">' + bodyElement.xrefNode.text() + '</a>'
+             } else {
+               let href = bodyElement.xrefNode.attr('href').value();
+               if (href.indexOf('#') === 0) {
+                 href = bodyElement.path.substring(bodyElement.path.indexOf('topics/')) + href;
+               } else if (href.indexOf('topics/') !== -1) {
+                 href = href.substring(href.indexOf('topics')); // clean up [..]/topic/ etc.
+               } else {
+                 href = 'topics/' + href;
+               }
+               var split = href.split('#');
 
-              var unknown = false;
-              let title = href;
-              if (knownTitles[href]) {
-                title = bodyElement.xrefNode.text() || knownTitles[href];
-              } else if (knownTitles[split[0]]) {
-                title = bodyElement.xrefNode.text() || knownTitles[split[0]];
-              } else if (bodyElement.xrefNode.text()) {
-                unknown = true;
-                title = bodyElement.xrefNode.text();
-              } else if (split[1]) {
-                unknown = true;
-                title = split[1].replace(/_/g, ' ');
-              } else {
-                unknown = true;
-                title = href.replace('topics/', '').replace('.xml', '').replace(/_/g, ' ');
-              }
+               var unknown = false;
+               let title = href;
+               if (knownTitles[href]) {
+                 title = bodyElement.xrefNode.text() || knownTitles[href];
+               } else if (knownTitles[split[0]]) {
+                 title = bodyElement.xrefNode.text() || knownTitles[split[0]];
+               } else if (bodyElement.xrefNode.text()) {
+                 unknown = true;
+                 title = bodyElement.xrefNode.text();
+               } else if (split[1]) {
+                 unknown = true;
+                 title = split[1].replace(/_/g, ' ');
+               } else {
+                 unknown = true;
+                 title = href.replace('topics/', '').replace('.xml', '').replace(/_/g, ' ');
+               }
 
-              if (unknown) {
-                bodyString += '<span>"' + title + '"</span>'; // Unknown = not parsed reference as some docs are excluded
-              } else {
-                bodyString += '<a href="javascript: void(0);" class="lang-ref-link" data-target="' + href + '">' + title + '</a>';
-              }
-            }
-          }
+               if (unknown) {
+                 bodyString += '<span>' + title + '</span>'; // Unknown = not parsed reference as some docs are excluded
+               } else {
+                 bodyString += '<a href="javascript: void(0);" class="lang-ref-link" data-target="' + href + '">' + title + '</a>';
+               }
+             }
+           }
+         }
+       });
+       result += ',\n' + prefix + '  body: \'' + bodyString.replace(/([^\\])\\([^\\])/g, '$1\\\\$2').replace(/'/g, '\\\'').replace(/\n/g, '\' + \n' + prefix + '    \'') + '\''
+     }
+     if (topic.children.length) {
+       result += ',\n' + prefix + '  children: [\n';
+       let stringifiedChildren = [];
+       topic.children.forEach(child => {
+         stringifiedChildren.push(stringifyTopic(child, prefix + '  '))
+       });
+       result += stringifiedChildren.join(',\n');
+       result += prefix + ']';
+     } else {
+       result += ',\n' + prefix + '  children: []\n';
+     }
+     result += prefix + '}';
+     return result;
+   };
+
+  fs.readFile('../Impala/docs/shared/impala_common.xml', 'utf-8', (err, commonRaw) => {
+
+    let handleCommonChildren = children => {
+      children.forEach(child => {
+        if (child.attr('id')) {
+          conRefs[child.attr('id').value()] = child;
         }
-      });
-      result += ',\n' + prefix + '  body: \'' + bodyString.replace(/([^\\])\\([^\\])/g, '$1\\\\$2').replace(/'/g, '\\\'').replace(/\n/g, '\' + \n' + prefix + '    \'') + '\''
-    }
-    if (topic.children.length) {
-      result += ',\n' + prefix + '  children: [\n';
-      let stringifiedChildren = [];
-      topic.children.forEach(child => {
-        stringifiedChildren.push(stringifyTopic(child, prefix + '  '))
-      });
-      result += stringifiedChildren.join(',\n');
-      result += prefix + ']';
-    } else {
-      result += ',\n' + prefix + '  children: []\n';
-    }
-    result += prefix + '}';
-    return result;
-  };
+        if (child.childNodes().length) {
+          handleCommonChildren(child.childNodes());
+        }
+      })
+    };
 
-  fs.readFile('../Impala/docs/impala_sqlref.ditamap', 'utf8', (err, mapRaw) => {
-    let topics = [];
-    let promises = [];
+    handleCommonChildren(libxml.parseXmlString(commonRaw).get('//conbody').childNodes());
 
-    libxml.parseXmlString(mapRaw).get('//map').childNodes().forEach(x => {
-      if (x.name() === 'topicref' && x.attr('href').value() !== 'topics/impala_functions.xml') {
-        topics.push(new Topic(x.attr('href').value(), x, promises));
-      }
-    });
+    fs.readFile('../Impala/docs/impala_sqlref.ditamap', 'utf8', (err, mapRaw) => {
+      let topics = [];
+      let promises = [];
 
-    Promise.all(promises).then(() => {
-      fs.readFile('tools/jison/license.txt', 'utf-8', (err, licenseHeader) => {
-        let result = licenseHeader + '\n\n\n// NOTE: This is a generated file!\n// Run \'node tools/sql-docs/impalaExtractor.js\' to generate.\n\n\nvar impalaLangRefTopics = [\n';
-        let stringifiedTopics = [];
-        topics.forEach(topic => {
-          stringifiedTopics.push(stringifyTopic(topic, ''));
-        });
-        result += stringifiedTopics.join(',\n');
-        result += '\n];';
-        fs.writeFile('desktop/core/src/desktop/static/desktop/js/sqlImpalaLangRef.js', result, () => {
-          console.log('desktop/core/src/desktop/static/desktop/js/sqlImpalaLangRef.js updated!');
+      libxml.parseXmlString(mapRaw).get('//map').childNodes().forEach(x => {
+        if (x.name() === 'topicref' && x.attr('href').value() !== 'topics/impala_functions.xml') {
+          topics.push(new Topic(x.attr('href').value(), x, promises));
+        }
+      });
+
+      Promise.all(promises).then(() => {
+        fs.readFile('tools/jison/license.txt', 'utf-8', (err, licenseHeader) => {
+          let result = licenseHeader + '\n\n\n// NOTE: This is a generated file!\n// Run \'node tools/sql-docs/impalaExtractor.js\' to generate.\n\n\nvar impalaLangRefTopics = [\n';
+          let stringifiedTopics = [];
+          topics.forEach(topic => {
+            stringifiedTopics.push(stringifyTopic(topic, ''));
+          });
+          result += stringifiedTopics.join(',\n');
+          result += '\n];';
+          fs.writeFile('desktop/core/src/desktop/static/desktop/js/sqlImpalaLangRef.js', result, () => {
+            console.log('desktop/core/src/desktop/static/desktop/js/sqlImpalaLangRef.js updated!');
+          })
         })
-      })
+      });
     });
-  });
+  })
 });

Some files were not shown because too many files changed in this diff