]> source.dussan.org Git - sonarqube.git/commitdiff
drop unused web components
authorStas Vilchik <stas.vilchik@sonarsource.com>
Tue, 13 Mar 2018 09:51:29 +0000 (10:51 +0100)
committerSonarTech <sonartech@sonarsource.com>
Thu, 22 Mar 2018 11:37:48 +0000 (12:37 +0100)
47 files changed:
server/sonar-web/config/webpack.config.js
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx
server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavMenu-test.tsx
server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx
server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsNav-test.tsx
server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap
server/sonar-web/src/main/js/app/styles/components/modals.css
server/sonar-web/src/main/js/app/styles/select2-sonar.css [deleted file]
server/sonar-web/src/main/js/app/styles/select2.css [deleted file]
server/sonar-web/src/main/js/app/styles/sonar.css
server/sonar-web/src/main/js/apps/coding-rules/styles.css
server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.js
server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js
server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChart.js
server/sonar-web/src/main/js/apps/issues/components/App.js
server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.js
server/sonar-web/src/main/js/apps/projects/visualizations/Risk.tsx
server/sonar-web/src/main/js/apps/projects/visualizations/SimpleBubbleChart.tsx
server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/SimpleBubbleChart-test.tsx
server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Risk-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/SimpleBubbleChart-test.tsx.snap
server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRowActions-test.tsx.snap
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.tsx
server/sonar-web/src/main/js/apps/system/components/PageActions.tsx
server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap
server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx
server/sonar-web/src/main/js/components/SourceViewer/styles.css
server/sonar-web/src/main/js/components/charts/BubbleChart.d.ts
server/sonar-web/src/main/js/components/charts/BubbleChart.js
server/sonar-web/src/main/js/components/charts/__tests__/work-cloud-test.js [deleted file]
server/sonar-web/src/main/js/components/charts/bar-chart.js
server/sonar-web/src/main/js/components/charts/donut-chart.js
server/sonar-web/src/main/js/components/charts/line-chart.js
server/sonar-web/src/main/js/components/charts/word-cloud.js [deleted file]
server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx
server/sonar-web/src/main/js/components/mixins/tooltips-mixin.js [deleted file]
server/sonar-web/src/main/js/components/shared/ComplexityDistribution.js [deleted file]
server/sonar-web/src/main/js/libs/third-party/bootstrap/dropdown.js [deleted file]
server/sonar-web/src/main/js/libs/third-party/bootstrap/tooltip.js [deleted file]
server/sonar-web/src/main/js/libs/third-party/select2.js [deleted file]

index 3d1e7aba4e683eb20125a388a8bf1ffed0c5a391..5a7513de97165253187cfcfeadd8dbfd2ca39b56 100644 (file)
@@ -59,10 +59,7 @@ module.exports = ({ production = true, fast = false }) => ({
       'backbone',
       'backbone.marionette',
       'handlebars/runtime',
-      './src/main/js/libs/third-party/jquery-ui.js',
-      './src/main/js/libs/third-party/select2.js',
-      './src/main/js/libs/third-party/bootstrap/tooltip.js',
-      './src/main/js/libs/third-party/bootstrap/dropdown.js'
+      './src/main/js/libs/third-party/jquery-ui.js'
     ].filter(Boolean),
 
     app: [
index 80f9ebb31def7a11a11f0dbf5d8438bd3bf52456..3a78c92dc46a37231b4c2b233ebb58924cfe61c0 100644 (file)
@@ -22,6 +22,7 @@ import { Link } from 'react-router';
 import * as classNames from 'classnames';
 import * as PropTypes from 'prop-types';
 import { BranchLike, Component, Extension } from '../../../types';
+import Dropdown from '../../../../components/controls/Dropdown';
 import NavBarTabs from '../../../../components/nav/NavBarTabs';
 import {
   isShortLivingBranch,
@@ -182,17 +183,21 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
     }
 
     return (
-      <li className="dropdown">
-        <a
-          className={classNames('dropdown-toggle', { active: isSettingsActive })}
-          id="component-navigation-admin"
-          data-toggle="dropdown"
-          href="#">
-          {translate('layout.settings')}&nbsp;
-          <i className="icon-dropdown" />
-        </a>
-        <ul className="dropdown-menu">{adminLinks}</ul>
-      </li>
+      <Dropdown data-test="extensions">
+        {({ onToggleClick, open }) => (
+          <li className={classNames('dropdown', { open })}>
+            <a
+              className={classNames('dropdown-toggle', { active: isSettingsActive || open })}
+              href="#"
+              id="component-navigation-admin"
+              onClick={onToggleClick}>
+              {translate('layout.settings')}
+              <i className="icon-dropdown little-spacer-left" />
+            </a>
+            <ul className="dropdown-menu">{adminLinks}</ul>
+          </li>
+        )}
+      </Dropdown>
     );
   }
 
@@ -416,17 +421,21 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
     }
 
     return (
-      <li className="dropdown">
-        <a
-          className="dropdown-toggle"
-          id="component-navigation-more"
-          data-toggle="dropdown"
-          href="#">
-          {translate('more')}&nbsp;
-          <i className="icon-dropdown" />
-        </a>
-        <ul className="dropdown-menu">{extensions.map(e => this.renderExtension(e, false))}</ul>
-      </li>
+      <Dropdown data-test="admin-extensions">
+        {({ onToggleClick, open }) => (
+          <li className={classNames('dropdown', { open })}>
+            <a
+              className={classNames('dropdown-toggle', { active: open })}
+              href="#"
+              id="component-navigation-more"
+              onClick={onToggleClick}>
+              {translate('more')}
+              <i className="icon-dropdown little-spacer-left" />
+            </a>
+            <ul className="dropdown-menu">{extensions.map(e => this.renderExtension(e, false))}</ul>
+          </li>
+        )}
+      </Dropdown>
     );
   }
 
index ef502e33aad34f238902bbfa8e75606698532e24..4ac93c4fb004f991b7b569463060666681495e82 100644 (file)
@@ -38,11 +38,11 @@ it('should work with extensions', () => {
     configuration: { showSettings: true, extensions: [{ key: 'foo', name: 'Foo' }] },
     extensions: [{ key: 'component-foo', name: 'ComponentFoo' }]
   };
-  expect(
-    shallow(<ComponentNavMenu branchLike={mainBranch} component={component} />, {
-      context: { branchesEnabled: true }
-    })
-  ).toMatchSnapshot();
+  const wrapper = shallow(<ComponentNavMenu branchLike={mainBranch} component={component} />, {
+    context: { branchesEnabled: true }
+  });
+  expect(wrapper.find('Dropdown[data-test="extensions"]').dive()).toMatchSnapshot();
+  expect(wrapper.find('Dropdown[data-test="admin-extensions"]').dive()).toMatchSnapshot();
 });
 
 it('should work with multiple extensions', () => {
@@ -57,11 +57,11 @@ it('should work with multiple extensions', () => {
       { key: 'component-bar', name: 'ComponentBar' }
     ]
   };
-  expect(
-    shallow(<ComponentNavMenu branchLike={mainBranch} component={component} />, {
-      context: { branchesEnabled: true }
-    })
-  ).toMatchSnapshot();
+  const wrapper = shallow(<ComponentNavMenu branchLike={mainBranch} component={component} />, {
+    context: { branchesEnabled: true }
+  });
+  expect(wrapper.find('Dropdown[data-test="extensions"]').dive()).toMatchSnapshot();
+  expect(wrapper.find('Dropdown[data-test="admin-extensions"]').dive()).toMatchSnapshot();
 });
 
 it('should work for short-living branches', () => {
index 5665e5ab9b40e11316718eb900879befce1bc8c5..249f20e49dc28ada6a16dc85cfe476d275b14370 100644 (file)
@@ -89,102 +89,9 @@ exports[`should work for all qualifiers 1`] = `
       project_activity.page
     </Link>
   </li>
-  <li
-    className="dropdown"
-  >
-    <a
-      className="dropdown-toggle"
-      data-toggle="dropdown"
-      href="#"
-      id="component-navigation-admin"
-    >
-      layout.settings
-      Â 
-      <i
-        className="icon-dropdown"
-      />
-    </a>
-    <ul
-      className="dropdown-menu"
-    >
-      <li
-        key="settings"
-      >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/settings",
-              "query": Object {
-                "id": "foo",
-              },
-            }
-          }
-        >
-          project_settings.page
-        </Link>
-      </li>
-      <li
-        key="branches"
-      >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/branches",
-              "query": Object {
-                "id": "foo",
-              },
-            }
-          }
-        >
-          project_branches.page
-        </Link>
-      </li>
-      <li
-        key="webhooks"
-      >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/webhooks",
-              "query": Object {
-                "id": "foo",
-              },
-            }
-          }
-        >
-          webhooks.page
-        </Link>
-      </li>
-      <li
-        key="project_delete"
-      >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/deletion",
-              "query": Object {
-                "id": "foo",
-              },
-            }
-          }
-        >
-          deletion.page
-        </Link>
-      </li>
-    </ul>
-  </li>
+  <Dropdown
+    data-test="extensions"
+  />
 </NavBarTabs>
 `;
 
@@ -277,45 +184,9 @@ exports[`should work for all qualifiers 2`] = `
       project_activity.page
     </Link>
   </li>
-  <li
-    className="dropdown"
-  >
-    <a
-      className="dropdown-toggle"
-      data-toggle="dropdown"
-      href="#"
-      id="component-navigation-admin"
-    >
-      layout.settings
-      Â 
-      <i
-        className="icon-dropdown"
-      />
-    </a>
-    <ul
-      className="dropdown-menu"
-    >
-      <li
-        key="settings"
-      >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/settings",
-              "query": Object {
-                "id": "foo",
-              },
-            }
-          }
-        >
-          project_settings.page
-        </Link>
-      </li>
-    </ul>
-  </li>
+  <Dropdown
+    data-test="extensions"
+  />
 </NavBarTabs>
 `;
 
@@ -408,45 +279,9 @@ exports[`should work for all qualifiers 3`] = `
       project_activity.page
     </Link>
   </li>
-  <li
-    className="dropdown"
-  >
-    <a
-      className="dropdown-toggle"
-      data-toggle="dropdown"
-      href="#"
-      id="component-navigation-admin"
-    >
-      layout.settings
-      Â 
-      <i
-        className="icon-dropdown"
-      />
-    </a>
-    <ul
-      className="dropdown-menu"
-    >
-      <li
-        key="project_delete"
-      >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/deletion",
-              "query": Object {
-                "id": "foo",
-              },
-            }
-          }
-        >
-          deletion.page
-        </Link>
-      </li>
-    </ul>
-  </li>
+  <Dropdown
+    data-test="extensions"
+  />
 </NavBarTabs>
 `;
 
@@ -631,45 +466,9 @@ exports[`should work for all qualifiers 5`] = `
       project_activity.page
     </Link>
   </li>
-  <li
-    className="dropdown"
-  >
-    <a
-      className="dropdown-toggle"
-      data-toggle="dropdown"
-      href="#"
-      id="component-navigation-admin"
-    >
-      layout.settings
-      Â 
-      <i
-        className="icon-dropdown"
-      />
-    </a>
-    <ul
-      className="dropdown-menu"
-    >
-      <li
-        key="project_delete"
-      >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/deletion",
-              "query": Object {
-                "id": "foo",
-              },
-            }
-          }
-        >
-          deletion.page
-        </Link>
-      </li>
-    </ul>
-  </li>
+  <Dropdown
+    data-test="extensions"
+  />
 </NavBarTabs>
 `;
 
@@ -911,531 +710,355 @@ exports[`should work for short-living branches 1`] = `
 `;
 
 exports[`should work with extensions 1`] = `
-<NavBarTabs>
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/dashboard",
-          "query": Object {
-            "id": "foo",
-          },
-        }
-      }
-    >
-      overview.page
-    </Link>
-  </li>
-  <li>
-    <Link
-      activeClassName="active"
-      className=""
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/project/issues",
-          "query": Object {
-            "id": "foo",
-            "resolved": "false",
-          },
-        }
-      }
-    >
-      issues.page
-    </Link>
-  </li>
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/component_measures",
-          "query": Object {
-            "id": "foo",
-          },
-        }
-      }
-    >
-      layout.measures
-    </Link>
-  </li>
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/code",
-          "query": Object {
-            "id": "foo",
-          },
-        }
-      }
-    >
-      code.page
-    </Link>
-  </li>
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/project/activity",
-          "query": Object {
-            "id": "foo",
-          },
-        }
-      }
-    >
-      project_activity.page
-    </Link>
-  </li>
-  <li
-    className="dropdown"
+<li
+  className="dropdown"
+>
+  <a
+    className="dropdown-toggle"
+    href="#"
+    id="component-navigation-admin"
+    onClick={[Function]}
   >
-    <a
-      className="dropdown-toggle"
-      data-toggle="dropdown"
-      href="#"
-      id="component-navigation-admin"
-    >
-      layout.settings
-      Â 
-      <i
-        className="icon-dropdown"
-      />
-    </a>
-    <ul
-      className="dropdown-menu"
+    layout.settings
+    <i
+      className="icon-dropdown little-spacer-left"
+    />
+  </a>
+  <ul
+    className="dropdown-menu"
+  >
+    <li
+      key="settings"
     >
-      <li
-        key="settings"
-      >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/settings",
-              "query": Object {
-                "id": "foo",
-              },
-            }
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to={
+          Object {
+            "pathname": "/project/settings",
+            "query": Object {
+              "id": "foo",
+            },
           }
-        >
-          project_settings.page
-        </Link>
-      </li>
-      <li
-        key="branches"
+        }
       >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/branches",
-              "query": Object {
-                "id": "foo",
-              },
-            }
+        project_settings.page
+      </Link>
+    </li>
+    <li
+      key="branches"
+    >
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to={
+          Object {
+            "pathname": "/project/branches",
+            "query": Object {
+              "id": "foo",
+            },
           }
-        >
-          project_branches.page
-        </Link>
-      </li>
-      <li
-        key="webhooks"
+        }
       >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/webhooks",
-              "query": Object {
-                "id": "foo",
-              },
-            }
+        project_branches.page
+      </Link>
+    </li>
+    <li
+      key="webhooks"
+    >
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to={
+          Object {
+            "pathname": "/project/webhooks",
+            "query": Object {
+              "id": "foo",
+            },
           }
-        >
-          webhooks.page
-        </Link>
-      </li>
-      <li
-        key="foo"
+        }
       >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/admin/extension/foo",
-              "query": Object {
-                "id": "foo",
-              },
-            }
+        webhooks.page
+      </Link>
+    </li>
+    <li
+      key="foo"
+    >
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to={
+          Object {
+            "pathname": "/project/admin/extension/foo",
+            "query": Object {
+              "id": "foo",
+            },
           }
-        >
-          Foo
-        </Link>
-      </li>
-      <li
-        key="project_delete"
+        }
       >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/deletion",
-              "query": Object {
-                "id": "foo",
-              },
-            }
+        Foo
+      </Link>
+    </li>
+    <li
+      key="project_delete"
+    >
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to={
+          Object {
+            "pathname": "/project/deletion",
+            "query": Object {
+              "id": "foo",
+            },
           }
-        >
-          deletion.page
-        </Link>
-      </li>
-    </ul>
-  </li>
-  <li
-    className="dropdown"
+        }
+      >
+        deletion.page
+      </Link>
+    </li>
+  </ul>
+</li>
+`;
+
+exports[`should work with extensions 2`] = `
+<li
+  className="dropdown"
+>
+  <a
+    className="dropdown-toggle"
+    href="#"
+    id="component-navigation-more"
+    onClick={[Function]}
   >
-    <a
-      className="dropdown-toggle"
-      data-toggle="dropdown"
-      href="#"
-      id="component-navigation-more"
-    >
-      more
-      Â 
-      <i
-        className="icon-dropdown"
-      />
-    </a>
-    <ul
-      className="dropdown-menu"
+    more
+    <i
+      className="icon-dropdown little-spacer-left"
+    />
+  </a>
+  <ul
+    className="dropdown-menu"
+  >
+    <li
+      key="component-foo"
     >
-      <li
-        key="component-foo"
-      >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/extension/component-foo",
-              "query": Object {
-                "id": "foo",
-              },
-            }
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to={
+          Object {
+            "pathname": "/project/extension/component-foo",
+            "query": Object {
+              "id": "foo",
+            },
           }
-        >
-          ComponentFoo
-        </Link>
-      </li>
-    </ul>
-  </li>
-</NavBarTabs>
+        }
+      >
+        ComponentFoo
+      </Link>
+    </li>
+  </ul>
+</li>
 `;
 
 exports[`should work with multiple extensions 1`] = `
-<NavBarTabs>
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/dashboard",
-          "query": Object {
-            "id": "foo",
-          },
-        }
-      }
-    >
-      overview.page
-    </Link>
-  </li>
-  <li>
-    <Link
-      activeClassName="active"
-      className=""
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/project/issues",
-          "query": Object {
-            "id": "foo",
-            "resolved": "false",
-          },
-        }
-      }
-    >
-      issues.page
-    </Link>
-  </li>
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/component_measures",
-          "query": Object {
-            "id": "foo",
-          },
-        }
-      }
-    >
-      layout.measures
-    </Link>
-  </li>
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/code",
-          "query": Object {
-            "id": "foo",
-          },
-        }
-      }
-    >
-      code.page
-    </Link>
-  </li>
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/project/activity",
-          "query": Object {
-            "id": "foo",
-          },
-        }
-      }
-    >
-      project_activity.page
-    </Link>
-  </li>
-  <li
-    className="dropdown"
+<li
+  className="dropdown"
+>
+  <a
+    className="dropdown-toggle"
+    href="#"
+    id="component-navigation-admin"
+    onClick={[Function]}
   >
-    <a
-      className="dropdown-toggle"
-      data-toggle="dropdown"
-      href="#"
-      id="component-navigation-admin"
-    >
-      layout.settings
-      Â 
-      <i
-        className="icon-dropdown"
-      />
-    </a>
-    <ul
-      className="dropdown-menu"
+    layout.settings
+    <i
+      className="icon-dropdown little-spacer-left"
+    />
+  </a>
+  <ul
+    className="dropdown-menu"
+  >
+    <li
+      key="settings"
     >
-      <li
-        key="settings"
-      >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/settings",
-              "query": Object {
-                "id": "foo",
-              },
-            }
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to={
+          Object {
+            "pathname": "/project/settings",
+            "query": Object {
+              "id": "foo",
+            },
           }
-        >
-          project_settings.page
-        </Link>
-      </li>
-      <li
-        key="branches"
+        }
       >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/branches",
-              "query": Object {
-                "id": "foo",
-              },
-            }
+        project_settings.page
+      </Link>
+    </li>
+    <li
+      key="branches"
+    >
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to={
+          Object {
+            "pathname": "/project/branches",
+            "query": Object {
+              "id": "foo",
+            },
           }
-        >
-          project_branches.page
-        </Link>
-      </li>
-      <li
-        key="webhooks"
+        }
       >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/webhooks",
-              "query": Object {
-                "id": "foo",
-              },
-            }
+        project_branches.page
+      </Link>
+    </li>
+    <li
+      key="webhooks"
+    >
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to={
+          Object {
+            "pathname": "/project/webhooks",
+            "query": Object {
+              "id": "foo",
+            },
           }
-        >
-          webhooks.page
-        </Link>
-      </li>
-      <li
-        key="foo"
+        }
       >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/admin/extension/foo",
-              "query": Object {
-                "id": "foo",
-              },
-            }
+        webhooks.page
+      </Link>
+    </li>
+    <li
+      key="foo"
+    >
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to={
+          Object {
+            "pathname": "/project/admin/extension/foo",
+            "query": Object {
+              "id": "foo",
+            },
           }
-        >
-          Foo
-        </Link>
-      </li>
-      <li
-        key="bar"
+        }
       >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/admin/extension/bar",
-              "query": Object {
-                "id": "foo",
-              },
-            }
+        Foo
+      </Link>
+    </li>
+    <li
+      key="bar"
+    >
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to={
+          Object {
+            "pathname": "/project/admin/extension/bar",
+            "query": Object {
+              "id": "foo",
+            },
           }
-        >
-          Bar
-        </Link>
-      </li>
-      <li
-        key="project_delete"
+        }
       >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/deletion",
-              "query": Object {
-                "id": "foo",
-              },
-            }
+        Bar
+      </Link>
+    </li>
+    <li
+      key="project_delete"
+    >
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to={
+          Object {
+            "pathname": "/project/deletion",
+            "query": Object {
+              "id": "foo",
+            },
           }
-        >
-          deletion.page
-        </Link>
-      </li>
-    </ul>
-  </li>
-  <li
-    className="dropdown"
+        }
+      >
+        deletion.page
+      </Link>
+    </li>
+  </ul>
+</li>
+`;
+
+exports[`should work with multiple extensions 2`] = `
+<li
+  className="dropdown"
+>
+  <a
+    className="dropdown-toggle"
+    href="#"
+    id="component-navigation-more"
+    onClick={[Function]}
   >
-    <a
-      className="dropdown-toggle"
-      data-toggle="dropdown"
-      href="#"
-      id="component-navigation-more"
-    >
-      more
-      Â 
-      <i
-        className="icon-dropdown"
-      />
-    </a>
-    <ul
-      className="dropdown-menu"
+    more
+    <i
+      className="icon-dropdown little-spacer-left"
+    />
+  </a>
+  <ul
+    className="dropdown-menu"
+  >
+    <li
+      key="component-foo"
     >
-      <li
-        key="component-foo"
-      >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/extension/component-foo",
-              "query": Object {
-                "id": "foo",
-              },
-            }
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to={
+          Object {
+            "pathname": "/project/extension/component-foo",
+            "query": Object {
+              "id": "foo",
+            },
           }
-        >
-          ComponentFoo
-        </Link>
-      </li>
-      <li
-        key="component-bar"
+        }
       >
-        <Link
-          activeClassName="active"
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to={
-            Object {
-              "pathname": "/project/extension/component-bar",
-              "query": Object {
-                "id": "foo",
-              },
-            }
+        ComponentFoo
+      </Link>
+    </li>
+    <li
+      key="component-bar"
+    >
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to={
+          Object {
+            "pathname": "/project/extension/component-bar",
+            "query": Object {
+              "id": "foo",
+            },
           }
-        >
-          ComponentBar
-        </Link>
-      </li>
-    </ul>
-  </li>
-</NavBarTabs>
+        }
+      >
+        ComponentBar
+      </Link>
+    </li>
+  </ul>
+</li>
 `;
index dacf97dc27f47f308278450062966032b98afee7..443f074225cd02a0fdf70fb8688dde746d6e0379 100644 (file)
  */
 import * as React from 'react';
 import { Link } from 'react-router';
+import * as classNames from 'classnames';
 import { isLoggedIn, CurrentUser, AppState, Extension } from '../../../../app/types';
 import { translate } from '../../../../helpers/l10n';
 import { getQualityGatesUrl, getBaseUrl } from '../../../../helpers/urls';
 import { isMySet } from '../../../../apps/issues/utils';
+import Dropdown from '../../../../components/controls/Dropdown';
 
 interface Props {
   appState: AppState;
@@ -151,13 +153,21 @@ export default class GlobalNavMenu extends React.PureComponent<Props> {
       return null;
     }
     return (
-      <li className="dropdown">
-        <a className="dropdown-toggle" id="global-navigation-more" data-toggle="dropdown" href="#">
-          {translate('more')}&nbsp;
-          <span className="icon-dropdown" />
-        </a>
-        <ul className="dropdown-menu">{withoutPortfolios.map(this.renderGlobalPageLink)}</ul>
-      </li>
+      <Dropdown>
+        {({ onToggleClick, open }) => (
+          <li className={classNames('dropdown', { open })}>
+            <a
+              className={classNames('dropdown-toggle', { active: open })}
+              href="#"
+              id="global-navigation-more"
+              onClick={onToggleClick}>
+              {translate('more')}
+              <span className="icon-dropdown little-spacer-left" />
+            </a>
+            <ul className="dropdown-menu">{withoutPortfolios.map(this.renderGlobalPageLink)}</ul>
+          </li>
+        )}
+      </Dropdown>
     );
   }
 
index 5f3a4d7e656a0687ec04cbfebfdba099925dd557..22fa861f34f540c0ebd37b317004aa0a5515d016 100644 (file)
@@ -32,7 +32,7 @@ it('should work with extensions', () => {
   const wrapper = shallow(
     <GlobalNavMenu appState={appState} currentUser={currentUser} location={{ pathname: '' }} />
   );
-  expect(wrapper).toMatchSnapshot();
+  expect(wrapper.find('Dropdown').dive()).toMatchSnapshot();
 });
 
 it('should show administration menu if the user has the rights', () => {
index 34dd8f91396541c9905e15e9ca2fe94d0de6b1e5..277143865734d045f059602b72d3fe139313ab3e 100644 (file)
@@ -77,98 +77,34 @@ exports[`should show administration menu if the user has the rights 1`] = `
 `;
 
 exports[`should work with extensions 1`] = `
-<ul
-  className="global-navbar-menu pull-left"
+<li
+  className="dropdown"
 >
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/projects"
-    >
-      projects.page
-    </Link>
-  </li>
-  <li>
-    <Link
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/issues",
-          "query": Object {
-            "resolved": "false",
-          },
-        }
-      }
-    >
-      issues.page
-    </Link>
-  </li>
-  <li>
-    <Link
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/coding_rules"
-    >
-      coding_rules.page
-    </Link>
-  </li>
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/profiles"
-    >
-      quality_profiles.page
-    </Link>
-  </li>
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/quality_gates",
-        }
-      }
-    >
-      quality_gates.page
-    </Link>
-  </li>
-  <li
-    className="dropdown"
+  <a
+    className="dropdown-toggle"
+    href="#"
+    id="global-navigation-more"
+    onClick={[Function]}
   >
-    <a
-      className="dropdown-toggle"
-      data-toggle="dropdown"
-      href="#"
-      id="global-navigation-more"
-    >
-      more
-      Â 
-      <span
-        className="icon-dropdown"
-      />
-    </a>
-    <ul
-      className="dropdown-menu"
+    more
+    <span
+      className="icon-dropdown little-spacer-left"
+    />
+  </a>
+  <ul
+    className="dropdown-menu"
+  >
+    <li
+      key="foo"
     >
-      <li
-        key="foo"
+      <Link
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to="/extension/foo"
       >
-        <Link
-          onlyActiveOnIndex={false}
-          style={Object {}}
-          to="/extension/foo"
-        >
-          Foo
-        </Link>
-      </li>
-    </ul>
-  </li>
-</ul>
+        Foo
+      </Link>
+    </li>
+  </ul>
+</li>
 `;
index 688bce71d8e08cff3d4ad0a3ae0f605bcb6135d7..3064c54212e7867404e37bbe79f239ec48cec6c5 100644 (file)
@@ -27,6 +27,7 @@ import NavBarTabs from '../../../../components/nav/NavBarTabs';
 import { EditionStatus } from '../../../../api/marketplace';
 import { Extension } from '../../../types';
 import { translate } from '../../../../helpers/l10n';
+import Dropdown from '../../../../components/controls/Dropdown';
 
 interface Props {
   editionStatus?: EditionStatus;
@@ -82,118 +83,135 @@ export default class SettingsNav extends React.PureComponent<Props> {
 
   renderConfigurationTab() {
     const { organizationsEnabled } = this.props;
-    const configurationClassNames = classNames('dropdown-toggle', {
-      active:
-        !this.isSecurityActive() &&
-        !this.isProjectsActive() &&
-        !this.isSystemActive() &&
-        !this.isSomethingActive(['/admin/extension/license/support']) &&
-        !this.isMarketplace()
-    });
     const extensionsWithoutSupport = this.props.extensions.filter(
       extension => extension.key !== 'license/support'
     );
     return (
-      <li className="dropdown">
-        <a
-          className={configurationClassNames}
-          data-toggle="dropdown"
-          href="#"
-          id="settings-navigation-configuration">
-          {translate('sidebar.project_settings')} <i className="icon-dropdown" />
-        </a>
-        <ul className="dropdown-menu">
-          <li>
-            <IndexLink activeClassName="active" to="/admin/settings">
-              {translate('settings.page')}
-            </IndexLink>
-          </li>
-          <li>
-            <IndexLink activeClassName="active" to="/admin/settings/encryption">
-              {translate('property.category.security.encryption')}
-            </IndexLink>
+      <Dropdown>
+        {({ onToggleClick, open }) => (
+          <li className={classNames('dropdown', { open })}>
+            <a
+              className={classNames('dropdown-toggle', {
+                active:
+                  open ||
+                  (!this.isSecurityActive() &&
+                    !this.isProjectsActive() &&
+                    !this.isSystemActive() &&
+                    !this.isSomethingActive(['/admin/extension/license/support']) &&
+                    !this.isMarketplace())
+              })}
+              href="#"
+              id="settings-navigation-configuration"
+              onClick={onToggleClick}>
+              {translate('sidebar.project_settings')}
+              <i className="icon-dropdown little-spacer-left" />
+            </a>
+            <ul className="dropdown-menu">
+              <li>
+                <IndexLink activeClassName="active" to="/admin/settings">
+                  {translate('settings.page')}
+                </IndexLink>
+              </li>
+              <li>
+                <IndexLink activeClassName="active" to="/admin/settings/encryption">
+                  {translate('property.category.security.encryption')}
+                </IndexLink>
+              </li>
+              <li>
+                <IndexLink activeClassName="active" to="/admin/custom_metrics">
+                  {translate('custom_metrics.page')}
+                </IndexLink>
+              </li>
+              {!organizationsEnabled && (
+                <li>
+                  <IndexLink activeClassName="active" to="/admin/webhooks">
+                    {translate('webhooks.page')}
+                  </IndexLink>
+                </li>
+              )}
+              {extensionsWithoutSupport.map(this.renderExtension)}
+            </ul>
           </li>
-          <li>
-            <IndexLink activeClassName="active" to="/admin/custom_metrics">
-              {translate('custom_metrics.page')}
-            </IndexLink>
-          </li>
-          {!organizationsEnabled && (
-            <li>
-              <IndexLink activeClassName="active" to="/admin/webhooks">
-                {translate('webhooks.page')}
-              </IndexLink>
-            </li>
-          )}
-          {extensionsWithoutSupport.map(this.renderExtension)}
-        </ul>
-      </li>
+        )}
+      </Dropdown>
     );
   }
 
   renderProjectsTab() {
     const { organizationsEnabled } = this.props;
-    const projectsClassName = classNames('dropdown-toggle', { active: this.isProjectsActive() });
     return (
-      <li className="dropdown">
-        <a className={projectsClassName} data-toggle="dropdown" href="#">
-          {translate('sidebar.projects')} <i className="icon-dropdown" />
-        </a>
-        <ul className="dropdown-menu">
-          {!organizationsEnabled && (
-            <li>
-              <IndexLink activeClassName="active" to="/admin/projects_management">
-                {translate('management')}
-              </IndexLink>
-            </li>
-          )}
-          <li>
-            <IndexLink activeClassName="active" to="/admin/background_tasks">
-              {translate('background_tasks.page')}
-            </IndexLink>
+      <Dropdown>
+        {({ onToggleClick, open }) => (
+          <li className={classNames('dropdown', { open })}>
+            <a
+              className={classNames('dropdown-toggle', { active: open || this.isProjectsActive() })}
+              href="#"
+              onClick={onToggleClick}>
+              {translate('sidebar.projects')} <i className="icon-dropdown" />
+            </a>
+            <ul className="dropdown-menu">
+              {!organizationsEnabled && (
+                <li>
+                  <IndexLink activeClassName="active" to="/admin/projects_management">
+                    {translate('management')}
+                  </IndexLink>
+                </li>
+              )}
+              <li>
+                <IndexLink activeClassName="active" to="/admin/background_tasks">
+                  {translate('background_tasks.page')}
+                </IndexLink>
+              </li>
+            </ul>
           </li>
-        </ul>
-      </li>
+        )}
+      </Dropdown>
     );
   }
 
   renderSecurityTab() {
     const { organizationsEnabled } = this.props;
-    const securityClassName = classNames('dropdown-toggle', { active: this.isSecurityActive() });
     return (
-      <li className="dropdown">
-        <a className={securityClassName} data-toggle="dropdown" href="#">
-          {translate('sidebar.security')} <i className="icon-dropdown" />
-        </a>
-        <ul className="dropdown-menu">
-          <li>
-            <IndexLink activeClassName="active" to="/admin/users">
-              {translate('users.page')}
-            </IndexLink>
+      <Dropdown>
+        {({ onToggleClick, open }) => (
+          <li className={classNames('dropdown', { open })}>
+            <a
+              className={classNames('dropdown-toggle', { active: open || this.isSecurityActive() })}
+              href="#"
+              onClick={onToggleClick}>
+              {translate('sidebar.security')} <i className="icon-dropdown" />
+            </a>
+            <ul className="dropdown-menu">
+              <li>
+                <IndexLink activeClassName="active" to="/admin/users">
+                  {translate('users.page')}
+                </IndexLink>
+              </li>
+              {!organizationsEnabled && (
+                <li>
+                  <IndexLink activeClassName="active" to="/admin/groups">
+                    {translate('user_groups.page')}
+                  </IndexLink>
+                </li>
+              )}
+              {!organizationsEnabled && (
+                <li>
+                  <IndexLink activeClassName="active" to="/admin/permissions">
+                    {translate('global_permissions.page')}
+                  </IndexLink>
+                </li>
+              )}
+              {!organizationsEnabled && (
+                <li>
+                  <IndexLink activeClassName="active" to="/admin/permission_templates">
+                    {translate('permission_templates')}
+                  </IndexLink>
+                </li>
+              )}
+            </ul>
           </li>
-          {!organizationsEnabled && (
-            <li>
-              <IndexLink activeClassName="active" to="/admin/groups">
-                {translate('user_groups.page')}
-              </IndexLink>
-            </li>
-          )}
-          {!organizationsEnabled && (
-            <li>
-              <IndexLink activeClassName="active" to="/admin/permissions">
-                {translate('global_permissions.page')}
-              </IndexLink>
-            </li>
-          )}
-          {!organizationsEnabled && (
-            <li>
-              <IndexLink activeClassName="active" to="/admin/permission_templates">
-                {translate('permission_templates')}
-              </IndexLink>
-            </li>
-          )}
-        </ul>
-      </li>
+        )}
+      </Dropdown>
     );
   }
 
index 6deee4fd798a07c76bd72dadc27d3dcfed8e9e3b..e589b8794942fbc1bde0ea738c0b73cdbc813f15 100644 (file)
@@ -27,4 +27,5 @@ it('should work with extensions', () => {
     <SettingsNav extensions={extensions} location={{}} organizationsEnabled={false} />
   );
   expect(wrapper).toMatchSnapshot();
+  expect(wrapper.find('Dropdown').map(x => x.dive())).toMatchSnapshot();
 });
index a80573d7d9d8c99e6bbaa6f3e6f9c42d81caa90b..439e362124e82430b909e46d8a390d412da10914 100644 (file)
@@ -13,156 +13,9 @@ exports[`should work with extensions 1`] = `
     </h1>
   </header>
   <NavBarTabs>
-    <li
-      className="dropdown"
-    >
-      <a
-        className="dropdown-toggle active"
-        data-toggle="dropdown"
-        href="#"
-        id="settings-navigation-configuration"
-      >
-        sidebar.project_settings
-         
-        <i
-          className="icon-dropdown"
-        />
-      </a>
-      <ul
-        className="dropdown-menu"
-      >
-        <li>
-          <IndexLink
-            activeClassName="active"
-            to="/admin/settings"
-          >
-            settings.page
-          </IndexLink>
-        </li>
-        <li>
-          <IndexLink
-            activeClassName="active"
-            to="/admin/settings/encryption"
-          >
-            property.category.security.encryption
-          </IndexLink>
-        </li>
-        <li>
-          <IndexLink
-            activeClassName="active"
-            to="/admin/custom_metrics"
-          >
-            custom_metrics.page
-          </IndexLink>
-        </li>
-        <li>
-          <IndexLink
-            activeClassName="active"
-            to="/admin/webhooks"
-          >
-            webhooks.page
-          </IndexLink>
-        </li>
-        <li
-          key="foo"
-        >
-          <Link
-            activeClassName="active"
-            onlyActiveOnIndex={false}
-            style={Object {}}
-            to="/admin/extension/foo"
-          >
-            Foo
-          </Link>
-        </li>
-      </ul>
-    </li>
-    <li
-      className="dropdown"
-    >
-      <a
-        className="dropdown-toggle"
-        data-toggle="dropdown"
-        href="#"
-      >
-        sidebar.security
-         
-        <i
-          className="icon-dropdown"
-        />
-      </a>
-      <ul
-        className="dropdown-menu"
-      >
-        <li>
-          <IndexLink
-            activeClassName="active"
-            to="/admin/users"
-          >
-            users.page
-          </IndexLink>
-        </li>
-        <li>
-          <IndexLink
-            activeClassName="active"
-            to="/admin/groups"
-          >
-            user_groups.page
-          </IndexLink>
-        </li>
-        <li>
-          <IndexLink
-            activeClassName="active"
-            to="/admin/permissions"
-          >
-            global_permissions.page
-          </IndexLink>
-        </li>
-        <li>
-          <IndexLink
-            activeClassName="active"
-            to="/admin/permission_templates"
-          >
-            permission_templates
-          </IndexLink>
-        </li>
-      </ul>
-    </li>
-    <li
-      className="dropdown"
-    >
-      <a
-        className="dropdown-toggle"
-        data-toggle="dropdown"
-        href="#"
-      >
-        sidebar.projects
-         
-        <i
-          className="icon-dropdown"
-        />
-      </a>
-      <ul
-        className="dropdown-menu"
-      >
-        <li>
-          <IndexLink
-            activeClassName="active"
-            to="/admin/projects_management"
-          >
-            management
-          </IndexLink>
-        </li>
-        <li>
-          <IndexLink
-            activeClassName="active"
-            to="/admin/background_tasks"
-          >
-            background_tasks.page
-          </IndexLink>
-        </li>
-      </ul>
-    </li>
+    <Dropdown />
+    <Dropdown />
+    <Dropdown />
     <li>
       <IndexLink
         activeClassName="active"
@@ -182,3 +35,157 @@ exports[`should work with extensions 1`] = `
   </NavBarTabs>
 </ContextNavBar>
 `;
+
+exports[`should work with extensions 2`] = `
+Array [
+  <li
+    className="dropdown"
+  >
+    <a
+      className="dropdown-toggle active"
+      href="#"
+      id="settings-navigation-configuration"
+      onClick={[Function]}
+    >
+      sidebar.project_settings
+      <i
+        className="icon-dropdown little-spacer-left"
+      />
+    </a>
+    <ul
+      className="dropdown-menu"
+    >
+      <li>
+        <IndexLink
+          activeClassName="active"
+          to="/admin/settings"
+        >
+          settings.page
+        </IndexLink>
+      </li>
+      <li>
+        <IndexLink
+          activeClassName="active"
+          to="/admin/settings/encryption"
+        >
+          property.category.security.encryption
+        </IndexLink>
+      </li>
+      <li>
+        <IndexLink
+          activeClassName="active"
+          to="/admin/custom_metrics"
+        >
+          custom_metrics.page
+        </IndexLink>
+      </li>
+      <li>
+        <IndexLink
+          activeClassName="active"
+          to="/admin/webhooks"
+        >
+          webhooks.page
+        </IndexLink>
+      </li>
+      <li
+        key="foo"
+      >
+        <Link
+          activeClassName="active"
+          onlyActiveOnIndex={false}
+          style={Object {}}
+          to="/admin/extension/foo"
+        >
+          Foo
+        </Link>
+      </li>
+    </ul>
+  </li>,
+  <li
+    className="dropdown"
+  >
+    <a
+      className="dropdown-toggle"
+      href="#"
+      onClick={[Function]}
+    >
+      sidebar.security
+       
+      <i
+        className="icon-dropdown"
+      />
+    </a>
+    <ul
+      className="dropdown-menu"
+    >
+      <li>
+        <IndexLink
+          activeClassName="active"
+          to="/admin/users"
+        >
+          users.page
+        </IndexLink>
+      </li>
+      <li>
+        <IndexLink
+          activeClassName="active"
+          to="/admin/groups"
+        >
+          user_groups.page
+        </IndexLink>
+      </li>
+      <li>
+        <IndexLink
+          activeClassName="active"
+          to="/admin/permissions"
+        >
+          global_permissions.page
+        </IndexLink>
+      </li>
+      <li>
+        <IndexLink
+          activeClassName="active"
+          to="/admin/permission_templates"
+        >
+          permission_templates
+        </IndexLink>
+      </li>
+    </ul>
+  </li>,
+  <li
+    className="dropdown"
+  >
+    <a
+      className="dropdown-toggle"
+      href="#"
+      onClick={[Function]}
+    >
+      sidebar.projects
+       
+      <i
+        className="icon-dropdown"
+      />
+    </a>
+    <ul
+      className="dropdown-menu"
+    >
+      <li>
+        <IndexLink
+          activeClassName="active"
+          to="/admin/projects_management"
+        >
+          management
+        </IndexLink>
+      </li>
+      <li>
+        <IndexLink
+          activeClassName="active"
+          to="/admin/background_tasks"
+        >
+          background_tasks.page
+        </IndexLink>
+      </li>
+    </ul>
+  </li>,
+]
+`;
index dbe8a64cb9815449a0877352a77bd7b91870a089..21d73c39a996baed3be1ca2d5aa7d1e4f82059d4 100644 (file)
@@ -113,10 +113,6 @@ ul.modal-head-metadata li {
   padding: 10px;
 }
 
-.modal-body-select2 {
-  margin-bottom: 10px;
-}
-
 .modal-body .notes {
   height: auto;
 }
diff --git a/server/sonar-web/src/main/js/app/styles/select2-sonar.css b/server/sonar-web/src/main/js/app/styles/select2-sonar.css
deleted file mode 100644 (file)
index 5987cc3..0000000
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-.select2-container {
-  vertical-align: middle;
-}
-
-.select2-container .select2-choice {
-  height: var(--controlHeight);
-  line-height: 22px;
-  border-color: var(--gray80);
-  border-radius: 2px;
-  box-sizing: border-box;
-  background: #fff;
-  font-size: var(--smallFontSize);
-  text-align: left;
-}
-
-.select2-container .select2-choice,
-.select2-container .select2-choices {
-  transition: border-color 0.2s ease;
-}
-
-.select2-container .select2-choice abbr {
-  top: 4px;
-}
-
-.select2-container .select2-choice div {
-  width: 19px;
-  border: none;
-  border-radius: 0;
-  background: #fff;
-}
-
-.select2-container .select2-choice div b {
-  top: 4px;
-  background-position: 1px -1px;
-}
-
-.select2-dropdown-open .select2-choice div b {
-  background-position: -17px -1px;
-}
-
-.select2-container .select2-choice span i {
-  position: relative;
-  top: 2px;
-}
-
-.select2-container-active .select2-choice,
-.select2-container-active .select2-choices {
-  border-color: var(--blue);
-  box-shadow: none;
-}
-
-.select2-dropdown-open .select2-choice {
-  box-shadow: none;
-}
-
-.select2-drop {
-  z-index: var(--dropdownMenuZIndex);
-  border-color: var(--gray80);
-  border-radius: 0;
-}
-
-.select2-drop-active {
-  border-color: var(--blue);
-}
-
-.select2-dropdown-open.select2-drop-above .select2-choice,
-.select2-dropdown-open.select2-drop-above .select2-choices {
-  border-color: var(--blue);
-  border-radius: 0;
-  background: #fff;
-}
-
-.select2-drop.select2-drop-above.select2-drop-active {
-  border-color: var(--blue);
-  border-radius: 0;
-}
-
-.select2-drop.select2-drop-above .select2-search input {
-  margin-top: 0;
-}
-
-.select2-results {
-  margin: 0;
-  padding: 5px 0;
-  border-top: 1px solid var(--gray80);
-}
-
-.select2-results .select2-result-label {
-  height: 20px;
-  line-height: 20px;
-  padding: 0 8px;
-  color: var(--baseFontColor);
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-
-.select2-results .select2-no-results,
-.select2-results .select2-searching,
-.select2-results .select2-selection-limit,
-.select2-more-results.select2-active {
-  height: 20px;
-  line-height: 20px;
-  padding: 0 10px;
-}
-
-.select2-results .select2-highlighted {
-  background: transparent;
-  color: var(--baseFontColor);
-}
-
-.select2-results .select2-highlighted .select2-result-label {
-  background: #e2e2e2;
-}
-
-.select2-search {
-  padding: 4px;
-}
-
-.select2-search input {
-  height: 20px;
-  padding: 0 7px;
-  border-color: var(--gray80);
-  background: #fff !important;
-}
-
-.select2-container-multi .select2-choices {
-  min-height: 19px;
-  padding-bottom: 1px;
-  border-color: var(--gray80);
-  background: #fff;
-}
-
-.select2-container-multi.select2-container-active .select2-choices {
-  border-color: var(--blue);
-  box-shadow: none;
-}
-
-.select2-container-multi .select2-choices .select2-search-field input {
-  height: 16px;
-  padding: 0 3px;
-}
-
-.select2-container-multi .select2-choices .select2-search-choice {
-  margin: 1px 1px 0 1px;
-  padding: 1px 5px 2px 18px;
-  border-radius: 0;
-  border-color: var(--gray80);
-  background: var(--gray94);
-  box-shadow: none;
-}
-
-.select2-search-choice-close {
-  top: 2px;
-}
-
-.select2-search-choice-close,
-.select2-container .select2-choice abbr,
-.select2-container .select2-choice div b {
-  background-image: url('../images/select2x2.png');
-  background-size: 60px 40px;
-}
-
-.select2-search input.select2-active,
-.select2-more-results.select2-active,
-.select2-container-multi .select2-choices .select2-search-field input.select2-active {
-  background-image: url('../images/loading.gif');
-}
-
-.select2-offscreen {
-  left: 0;
-  top: -100000px;
-}
diff --git a/server/sonar-web/src/main/js/app/styles/select2.css b/server/sonar-web/src/main/js/app/styles/select2.css
deleted file mode 100644 (file)
index f24b98d..0000000
+++ /dev/null
@@ -1,493 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-/*
-Version: 3.2 Timestamp: Mon Sep 10 10:38:04 PDT 2012
-*/
-.select2-container {
-  position: relative;
-  display: inline-block;
-  vertical-align: top;
-}
-
-.select2-container,
-.select2-drop,
-.select2-search,
-.select2-search input {
-  box-sizing: border-box;
-}
-
-.select2-container .select2-choice {
-  background: #fff linear-gradient(to bottom, #eee 0%, #fff 50%);
-  border-radius: 4px;
-  background-clip: padding-box;
-  border: 1px solid #aaa;
-  display: block;
-  overflow: hidden;
-  white-space: nowrap;
-  position: relative;
-  height: 26px;
-  line-height: 26px;
-  padding: 0 0 0 8px;
-  color: var(--baseFontColor);
-  text-decoration: none;
-}
-
-.select2-container.select2-drop-above .select2-choice {
-  border-bottom-color: #aaa;
-  border-radius: 0 0 4px 4px;
-  background-image: linear-gradient(to bottom, #eeeeee 0%, #ffffff 90%);
-}
-
-.select2-container .select2-choice span {
-  margin-right: 26px;
-  display: block;
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-}
-
-.select2-container .select2-choice abbr {
-  display: block;
-  position: absolute;
-  right: 26px;
-  top: 8px;
-  width: 12px;
-  height: 12px;
-  font-size: 1px;
-  background: url('../images/select2.png') right top no-repeat;
-  cursor: pointer;
-  text-decoration: none;
-  border: 0;
-  outline: 0;
-}
-
-.select2-container .select2-choice abbr:hover {
-  background-position: right -11px;
-  cursor: pointer;
-}
-
-.select2-drop {
-  background: #fff;
-  color: #000;
-  border: 1px solid #aaa;
-  border-top: 0;
-  position: absolute;
-  top: 100%;
-  box-shadow: 0 4px 5px rgba(0, 0, 0, 0.15);
-  z-index: 9999;
-  width: 100%;
-  margin-top: -1px;
-  border-radius: 0 0 4px 4px;
-}
-
-.select2-drop.select2-drop-above {
-  border-radius: 4px 4px 0 0;
-  margin-top: 1px;
-  border-top: 1px solid #aaa;
-  border-bottom: 0;
-  box-shadow: 0 -4px 5px rgba(0, 0, 0, 0.15);
-}
-
-.select2-container .select2-choice div {
-  border-radius: 0 4px 4px 0;
-  background-clip: padding-box;
-  background: #ccc linear-gradient(to bottom, #ccc 0%, #eee 60%);
-  border-left: 1px solid #aaa;
-  position: absolute;
-  right: 0;
-  top: 0;
-  display: block;
-  height: 100%;
-  width: 18px;
-}
-
-.select2-container .select2-choice div b {
-  background: url('../images/select2.png') no-repeat 0 1px;
-  display: block;
-  width: 100%;
-  height: 100%;
-}
-
-.select2-search {
-  display: inline-block;
-  white-space: nowrap;
-  z-index: 10000;
-  min-height: 26px;
-  width: 100%;
-  margin: 0;
-  padding-left: 4px;
-  padding-right: 4px;
-}
-
-.select2-search-hidden {
-  display: block;
-  position: absolute;
-  left: -10000px;
-}
-
-.select2-search input {
-  background: #fff url('../images/select2.png') no-repeat 100% -22px,
-    linear-gradient(to bottom, #fff 85%, #eee 99%);
-  padding: 4px 20px 4px 5px;
-  outline: 0;
-  border: 1px solid #aaa;
-  font-family: sans-serif;
-  font-size: 1em;
-  width: 100%;
-  margin: 0;
-  height: auto !important;
-  min-height: 26px;
-  box-shadow: none;
-  border-radius: 0;
-}
-
-.select2-drop.select2-drop-above .select2-search input {
-  margin-top: 4px;
-}
-
-.select2-search input.select2-active {
-  background: #ffff f url('../images/loading.gif') no-repeat 100%,
-    linear-gradient(to bottom, #fff 85%, #eee 99%);
-}
-
-.select2-container-active .select2-choice,
-.select2-container-active .select2-choices {
-  box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
-  border: 1px solid #5897fb;
-  outline: none;
-}
-
-.select2-dropdown-open .select2-choice {
-  border: 1px solid #aaa;
-  border-bottom-color: transparent;
-  box-shadow: 0 1px 0 #fff inset;
-  background: #eee linear-gradient(to bottom, #fff 0%, #eee 50%);
-  border-bottom-left-radius: 0;
-  border-bottom-right-radius: 0;
-}
-
-.select2-dropdown-open .select2-choice div {
-  background: transparent;
-  border-left: none;
-}
-
-.select2-dropdown-open .select2-choice div b {
-  background-position: -18px 1px;
-}
-
-/* results */
-.select2-results {
-  margin: 4px 4px 4px 0;
-  padding: 0 0 0 4px;
-  position: relative;
-  overflow-x: hidden;
-  overflow-y: auto;
-  max-height: 200px;
-}
-
-.select2-results ul.select2-result-sub {
-  margin: 0 0 0 0;
-}
-
-.select2-results ul.select2-result-sub > li .select2-result-label {
-  padding-left: 20px;
-}
-
-.select2-results ul.select2-result-sub ul.select2-result-sub > li .select2-result-label {
-  padding-left: 40px;
-}
-
-.select2-results
-  ul.select2-result-sub
-  ul.select2-result-sub
-  ul.select2-result-sub
-  > li
-  .select2-result-label {
-  padding-left: 60px;
-}
-
-.select2-results
-  ul.select2-result-sub
-  ul.select2-result-sub
-  ul.select2-result-sub
-  ul.select2-result-sub
-  > li
-  .select2-result-label {
-  padding-left: 80px;
-}
-
-.select2-results
-  ul.select2-result-sub
-  ul.select2-result-sub
-  ul.select2-result-sub
-  ul.select2-result-sub
-  ul.select2-result-sub
-  > li
-  .select2-result-label {
-  padding-left: 100px;
-}
-
-.select2-results
-  ul.select2-result-sub
-  ul.select2-result-sub
-  ul.select2-result-sub
-  ul.select2-result-sub
-  ul.select2-result-sub
-  ul.select2-result-sub
-  > li
-  .select2-result-label {
-  padding-left: 110px;
-}
-
-.select2-results
-  ul.select2-result-sub
-  ul.select2-result-sub
-  ul.select2-result-sub
-  ul.select2-result-sub
-  ul.select2-result-sub
-  ul.select2-result-sub
-  ul.select2-result-sub
-  > li
-  .select2-result-label {
-  padding-left: 120px;
-}
-
-.select2-results li {
-  list-style: none;
-  display: list-item;
-}
-
-.select2-results li.select2-result-with-children > .select2-result-label {
-  font-weight: bold;
-}
-
-.select2-results .select2-result-label {
-  padding: 3px 7px 4px;
-  margin: 0;
-  cursor: pointer;
-}
-
-.select2-results .select2-highlighted {
-  background: #3875d7;
-  color: #fff;
-}
-
-.select2-results li em {
-  background: #feffde;
-  font-style: normal;
-}
-
-.select2-results .select2-highlighted em {
-  background: transparent;
-}
-
-.select2-results .select2-no-results,
-.select2-results .select2-searching,
-.select2-results .select2-selection-limit {
-  background: #f4f4f4;
-  display: list-item;
-}
-
-.select2-results .select2-disabled {
-  display: none;
-}
-
-.select2-more-results.select2-active {
-  background: #f4f4f4 url('../images/loading.gif') no-repeat 100%;
-}
-
-.select2-more-results {
-  background: #f4f4f4;
-  display: list-item;
-}
-
-/* disabled styles */
-.select2-container.select2-container-disabled .select2-choice {
-  background-color: #f4f4f4;
-  background-image: none;
-  border: 1px solid #ddd;
-  cursor: default;
-}
-
-.select2-container.select2-container-disabled .select2-choice div {
-  background-color: #f4f4f4;
-  background-image: none;
-  border-left: 0;
-}
-
-/* multiselect */
-.select2-container-multi .select2-choices {
-  background: #fff linear-gradient(to bottom, #eee 1%, #fff 15%);
-  border: 1px solid #aaa;
-  margin: 0;
-  padding: 0;
-  cursor: text;
-  overflow: hidden;
-  height: auto !important;
-  height: 1%;
-  position: relative;
-}
-
-.select2-container-multi .select2-choices {
-  min-height: 26px;
-}
-
-.select2-container-multi.select2-container-active .select2-choices {
-  box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
-  border: 1px solid #5897fb;
-  outline: none;
-}
-
-.select2-container-multi .select2-choices li {
-  float: left;
-  list-style: none;
-}
-
-.select2-container-multi .select2-choices .select2-search-field {
-  white-space: nowrap;
-  margin: 0;
-  padding: 0;
-}
-
-.select2-container-multi .select2-choices .select2-search-field input {
-  color: #666;
-  background: transparent !important;
-  font-family: sans-serif;
-  font-size: 100%;
-  height: 15px;
-  padding: 5px;
-  margin: 1px 0;
-  outline: 0;
-  border: 0;
-  box-shadow: none;
-}
-
-.select2-container-multi .select2-choices .select2-search-field input.select2-active {
-  background: #fff url('../images/loading.gif') no-repeat 100% !important;
-}
-
-.select2-default {
-  color: #999 !important;
-}
-
-.select2-container-multi .select2-choices .select2-search-choice {
-  border-radius: 3px;
-  background-clip: padding-box;
-  background: #e4e4e4 linear-gradient(to bottom, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
-  box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
-  color: #333;
-  border: 1px solid #aaaaaa;
-  line-height: var(--baseFontSize);
-  padding: 3px 5px 3px 18px;
-  margin: 3px 0 3px 5px;
-  position: relative;
-  cursor: default;
-}
-
-.select2-container-multi .select2-choices .select2-search-choice span {
-  cursor: default;
-}
-
-.select2-container-multi .select2-choices .select2-search-choice-focus {
-  background: #d4d4d4;
-}
-
-.select2-search-choice-close {
-  display: block;
-  position: absolute;
-  right: 3px;
-  top: 4px;
-  width: 12px;
-  height: 13px;
-  font-size: 1px;
-  background: url('../images/select2.png') right top no-repeat;
-  outline: none;
-}
-
-.select2-container-multi .select2-search-choice-close {
-  left: 3px;
-}
-
-.select2-container-multi
-  .select2-choices
-  .select2-search-choice
-  .select2-search-choice-close:hover {
-  background-position: right -11px;
-}
-
-.select2-container-multi
-  .select2-choices
-  .select2-search-choice-focus
-  .select2-search-choice-close {
-  background-position: right -11px;
-}
-
-/* disabled styles */
-.select2-container-multi.select2-container-disabled .select2-choices {
-  background-color: #f4f4f4;
-  background-image: none;
-  border: 1px solid #ddd;
-  cursor: default;
-}
-
-.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice {
-  background-image: none;
-  background-color: #f4f4f4;
-  border: 1px solid #ddd;
-  padding: 3px 5px 3px 5px;
-}
-
-.select2-container-multi.select2-container-disabled
-  .select2-choices
-  .select2-search-choice
-  .select2-search-choice-close {
-  display: none;
-}
-
-/* end multiselect */
-.select2-result-selectable .select2-match,
-.select2-result-unselectable .select2-result-selectable .select2-match {
-  text-decoration: underline;
-}
-
-.select2-result-unselectable .select2-match {
-  text-decoration: none;
-}
-
-.select2-offscreen {
-  position: absolute;
-  left: -10000px;
-}
-
-/* Retina-ize icons */
-@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
-  .select2-search input,
-  .select2-search-choice-close,
-  .select2-container .select2-choice abbr,
-  .select2-container .select2-choice div b {
-    background-image: url(../images/select2x2.png) !important;
-    background-repeat: no-repeat !important;
-    background-size: 60px 40px !important;
-  }
-
-  .select2-search input {
-    background-position: 100% -21px !important;
-  }
-}
index eda7ed09c448ef6ad7f0cd84e577c0cbb2cbe835..f48136b4f1fb6a4969acdc65ee9f311fad4406c4 100644 (file)
@@ -18,8 +18,6 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 @import './jquery-ui.css';
-@import './select2.css';
-@import './select2-sonar.css';
 
 @import './init/base.css';
 @import './init/type.css';
index 5647de8b17c0b4b39e55c3c283d0d919daeeb4cd..b553e50550734402fe200926316bd8b3284b734c 100644 (file)
   font-size: var(--smallFontSize);
 }
 
-.coding-rules-detail-property .select2-search-field {
-  line-height: 1;
-}
-
 .coding-rules-detail-tag + .coding-rules-detail-tag {
   margin-left: 10px;
 }
index 1998460996ea99363feb7f07e8062bcac3400b21..54d1e9750318f5e2d9ddfba27a1c486d8a77417b 100644 (file)
@@ -95,10 +95,6 @@ export default class MeasureContentContainer extends React.PureComponent {
     const metricKeys = [metric.key];
     if (metric.key === 'ncloc') {
       metricKeys.push('ncloc_language_distribution');
-    } else if (metric.key === 'function_complexity') {
-      metricKeys.push('function_complexity_distribution');
-    } else if (metric.key === 'file_complexity') {
-      metricKeys.push('file_complexity_distribution');
     }
 
     fetchMeasures(selected || rootComponent.key, metricKeys, branchLike).then(
index 5c809374492d82a25a3b2fc4c4cb3d09e3f01f7c..abff84b4d2cad900cc0fcafff9534c34e8eeff52 100644 (file)
@@ -21,7 +21,6 @@
 import React from 'react';
 import { Link } from 'react-router';
 import LeakPeriodLegend from './LeakPeriodLegend';
-import ComplexityDistribution from '../../../components/shared/ComplexityDistribution';
 import HistoryIcon from '../../../components/icons-components/HistoryIcon';
 import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
 import LanguageDistributionContainer from '../../../components/charts/LanguageDistributionContainer';
@@ -94,18 +93,6 @@ export default function MeasureHeader(props /*: Props*/) {
             />
           </div>
         )}
-      {secondaryMeasure &&
-        secondaryMeasure.metric.key === 'function_complexity_distribution' && (
-          <div className="measure-details-secondary">
-            <ComplexityDistribution distribution={secondaryMeasure.value} of="function" />
-          </div>
-        )}
-      {secondaryMeasure &&
-        secondaryMeasure.metric.key === 'file_complexity_distribution' && (
-          <div className="measure-details-secondary">
-            <ComplexityDistribution distribution={secondaryMeasure.value} of="file" />
-          </div>
-        )}
     </div>
   );
 }
index 74fb563743e1e878711b3387a03411239f000b0c..4c6ae9e9b4ea461bfb243680ba820f6e0135a82b 100644 (file)
@@ -84,7 +84,16 @@ export default class BubbleChart extends React.PureComponent {
         }
       });
     }
-    return `<div class="text-left">${inner.join('<br/>')}</div>`;
+    return (
+      <div className="text-left">
+        {inner.map((line, index) => (
+          <React.Fragment key={index}>
+            {line}
+            {index < inner.length - 1 && <br />}
+          </React.Fragment>
+        ))}
+      </div>
+    );
   }
 
   handleBubbleClick = (component /*: ComponentEnhanced */) =>
index 85bf0fa458ee4ff13475f601b022ddc7f7826a64..1eb850bc266f27f9f6f35e7efa79e94133abd1ff 100644 (file)
@@ -23,6 +23,7 @@ import Helmet from 'react-helmet';
 import key from 'keymaster';
 import { keyBy, union, without } from 'lodash';
 import PropTypes from 'prop-types';
+import classNames from 'classnames';
 import PageActions from './PageActions';
 import MyIssuesFilter from './MyIssuesFilter';
 import IssuesList from './IssuesList';
@@ -55,10 +56,12 @@ import {
   CurrentUser
 } from '../utils'; */
 import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication';
+import Dropdown from '../../../components/controls/Dropdown';
 import ListFooter from '../../../components/controls/ListFooter';
 import EmptySearch from '../../../components/common/EmptySearch';
 import FiltersHeader from '../../../components/common/FiltersHeader';
 import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
+import { Button } from '../../../components/ui/buttons';
 import {
   isShortLivingBranch,
   isSameBranchLike,
@@ -710,9 +713,7 @@ export default class App extends React.PureComponent {
     this.setState({ bulkChange: null });
   };
 
-  handleBulkChangeClick = (e /*: Event & { target: HTMLElement } */) => {
-    e.preventDefault();
-    e.target.blur();
+  handleBulkChangeClick = () => {
     this.openBulkChange('all');
   };
 
@@ -779,28 +780,32 @@ export default class App extends React.PureComponent {
           thirdState={thirdState}
         />
         {checked.length > 0 ? (
-          <div className="dropdown display-inline-block">
-            <button id="issues-bulk-change" data-toggle="dropdown">
-              {translate('bulk_change')}
-              <i className="icon-dropdown little-spacer-left" />
-            </button>
-            <ul className="dropdown-menu">
-              <li>
-                <a href="#" onClick={this.handleBulkChangeClick}>
-                  {translateWithParameters('issues.bulk_change', paging ? paging.total : 0)}
-                </a>
-              </li>
-              <li>
-                <a href="#" onClick={this.handleBulkChangeSelectedClick}>
-                  {translateWithParameters('issues.bulk_change_selected', checked.length)}
-                </a>
-              </li>
-            </ul>
-          </div>
+          <Dropdown>
+            {({ onToggleClick, open }) => (
+              <div className={classNames('dropdown display-inline-block', { open })}>
+                <Button id="issues-bulk-change" onClick={onToggleClick}>
+                  {translate('bulk_change')}
+                  <i className="icon-dropdown little-spacer-left" />
+                </Button>
+                <ul className="dropdown-menu">
+                  <li>
+                    <a href="#" onClick={this.handleBulkChangeClick}>
+                      {translateWithParameters('issues.bulk_change', paging ? paging.total : 0)}
+                    </a>
+                  </li>
+                  <li>
+                    <a href="#" onClick={this.handleBulkChangeSelectedClick}>
+                      {translateWithParameters('issues.bulk_change_selected', checked.length)}
+                    </a>
+                  </li>
+                </ul>
+              </div>
+            )}
+          </Dropdown>
         ) : (
-          <button id="issues-bulk-change" onClick={this.handleBulkChangeClick}>
+          <Button id="issues-bulk-change" onClick={this.handleBulkChangeClick}>
             {translate('bulk_change')}
-          </button>
+          </Button>
         )}
         {bulkChange != null && (
           <BulkChangeModal
index e4ecafbf742fa89c2df7501c9483563917fc272b..ed43efee2e35137f8ae115091473d20c989545c0 100644 (file)
@@ -171,14 +171,16 @@ export default class CreationDateFacet extends React.PureComponent {
         endDate = createdBefore && parseDate(createdBefore);
       }
 
-      let tooltip =
-        formatMeasure(stats[start], 'SHORT_INT') +
-        '<br/>' +
-        formatDate(startDate, longFormatterOption);
       const tooltipEndDate = endDate || Date.now();
-      if (!isSameDay(tooltipEndDate, startDate)) {
-        tooltip += ' â€“ ' + formatDate(tooltipEndDate, longFormatterOption);
-      }
+      const tooltip = (
+        <React.Fragment>
+          {formatMeasure(stats[start], 'SHORT_INT')}
+          <br />
+          {formatDate(startDate, longFormatterOption)}
+          {!isSameDay(tooltipEndDate, startDate) &&
+            ` - ${formatDate(tooltipEndDate, longFormatterOption)}`}
+        </React.Fragment>
+      );
 
       return {
         createdAfter: startDate,
index 13ae5e5b9b008f3faaf4cec73084cafcb9e5a7ef..d7c004af86c5952eddb805ecb576c8996b6e725e 100644 (file)
@@ -45,7 +45,13 @@ export default class Risk extends React.PureComponent<Props> {
   getMetricTooltip(metric: { key: string; type: string }, value?: number) {
     const name = translate('metric', metric.key, 'name');
     const formattedValue = value != null ? formatMeasure(value, metric.type) : '–';
-    return `<div>${name}: ${formattedValue}</div>`;
+    return (
+      <div>
+        {name}
+        {': '}
+        {formattedValue}
+      </div>
+    );
   }
 
   getTooltip(
@@ -57,18 +63,26 @@ export default class Risk extends React.PureComponent<Props> {
     color2?: number
   ) {
     const fullProjectName =
-      this.props.displayOrganizations && project.organization
-        ? `${project.organization.name} / <strong>${project.name}</strong>`
-        : `<strong>${project.name}</strong>`;
-    const inner = [
-      `<div class="little-spacer-bottom">${fullProjectName}</div>`,
-      this.getMetricTooltip({ key: COLOR_METRIC_1, type: COLOR_METRIC_TYPE }, color1),
-      this.getMetricTooltip({ key: COLOR_METRIC_2, type: COLOR_METRIC_TYPE }, color2),
-      this.getMetricTooltip({ key: Y_METRIC, type: Y_METRIC_TYPE }, y),
-      this.getMetricTooltip({ key: X_METRIC, type: X_METRIC_TYPE }, x),
-      this.getMetricTooltip({ key: SIZE_METRIC, type: SIZE_METRIC_TYPE }, size)
-    ].join('');
-    return `<div class="text-left">${inner}</div>`;
+      this.props.displayOrganizations && project.organization ? (
+        <>
+          {project.organization.name}
+          {' / '}
+          <strong>{project.name}</strong>
+        </>
+      ) : (
+        <strong>{project.name}</strong>
+      );
+
+    return (
+      <div className="text-left">
+        <div className="little-spacer-bottom">{fullProjectName}</div>
+        {this.getMetricTooltip({ key: COLOR_METRIC_1, type: COLOR_METRIC_TYPE }, color1)}
+        {this.getMetricTooltip({ key: COLOR_METRIC_2, type: COLOR_METRIC_TYPE }, color2)}
+        {this.getMetricTooltip({ key: Y_METRIC, type: Y_METRIC_TYPE }, y)}
+        {this.getMetricTooltip({ key: X_METRIC, type: X_METRIC_TYPE }, x)}
+        {this.getMetricTooltip({ key: SIZE_METRIC, type: SIZE_METRIC_TYPE }, size)}
+      </div>
+    );
   }
 
   render() {
index 28af230caf51a3938bc0d3949184901b86a9ae6f..9b9d8af06aa31295d20a11d11c103934339a9d71 100644 (file)
@@ -45,28 +45,37 @@ export default class SimpleBubbleChart extends React.PureComponent<Props> {
   getMetricTooltip(metric: Metric, value?: number) {
     const name = translate('metric', metric.key, 'name');
     const formattedValue = value != null ? formatMeasure(value, metric.type) : '–';
-    return `<div>${name}: ${formattedValue}</div>`;
+    return (
+      <div>
+        {name}
+        {': '}
+        {formattedValue}
+      </div>
+    );
   }
 
   getTooltip(project: Project, x?: number, y?: number, size?: number, color?: number) {
     const fullProjectName =
-      this.props.displayOrganizations && project.organization
-        ? `${project.organization.name} / <strong>${project.name}</strong>`
-        : `<strong>${project.name}</strong>`;
-
-    const inner = [
-      `<div class="little-spacer-bottom">${fullProjectName}</div>`,
-      this.getMetricTooltip(this.props.xMetric, x),
-      this.getMetricTooltip(this.props.yMetric, y),
-      this.getMetricTooltip(this.props.sizeMetric, size)
-    ];
+      this.props.displayOrganizations && project.organization ? (
+        <>
+          {project.organization.name}
+          {' / '}
+          <strong>{project.name}</strong>
+        </>
+      ) : (
+        <strong>{project.name}</strong>
+      );
 
-    if (color) {
-      // if `color` is defined then `this.props.colorMetric` is defined too
-      this.getMetricTooltip({ key: this.props.colorMetric!, type: 'RATING' }, color);
-    }
-
-    return `<div class="text-left">${inner.join('')}</div>`;
+    return (
+      <div className="text-left">
+        <div className="little-spacer-bottom">{fullProjectName}</div>
+        {this.getMetricTooltip(this.props.xMetric, x)}
+        {this.getMetricTooltip(this.props.yMetric, y)}
+        {this.getMetricTooltip(this.props.sizeMetric, size)}
+        {/* if `color` is defined then `this.props.colorMetric` is defined too */}
+        {color && this.getMetricTooltip({ key: this.props.colorMetric!, type: 'RATING' }, color)}
+      </div>
+    );
   }
 
   render() {
index 3a19728ba669264f4c5fd073b52dd7d096ce306c..e76c73076f0429f9a84b08dd4b4658f3ae158dd4 100644 (file)
@@ -24,7 +24,8 @@ import SimpleBubbleChart from '../SimpleBubbleChart';
 it('renders', () => {
   const project1 = {
     key: 'foo',
-    measures: { complexity: '17.2', coverage: '53.5', ncloc: '1734' },
+    // eslint-disable-next-line camelcase
+    measures: { complexity: '17.2', coverage: '53.5', ncloc: '1734', security_rating: '2' },
     name: 'Foo',
     tags: [],
     visibility: 'public'
index 733d95af43f2c595e3cdf1787c0e24a0de084a89..ecc8cc1fede93fb112e097dc91d508e198318975 100644 (file)
@@ -22,7 +22,42 @@ exports[`renders 1`] = `
             },
           },
           "size": 1734,
-          "tooltip": "<div class=\\"text-left\\"><div class=\\"little-spacer-bottom\\"><strong>Foo</strong></div><div>metric.reliability_rating.name: â€“</div><div>metric.security_rating.name: â€“</div><div>metric.coverage.name: 53.5%</div><div>metric.sqale_index.name: â€“</div><div>metric.ncloc.name: 1.7short_number_suffix.k</div></div>",
+          "tooltip": <div
+            className="text-left"
+          >
+            <div
+              className="little-spacer-bottom"
+            >
+              <strong>
+                Foo
+              </strong>
+            </div>
+            <div>
+              metric.reliability_rating.name
+              : 
+              â€“
+            </div>
+            <div>
+              metric.security_rating.name
+              : 
+              â€“
+            </div>
+            <div>
+              metric.coverage.name
+              : 
+              53.5%
+            </div>
+            <div>
+              metric.sqale_index.name
+              : 
+              â€“
+            </div>
+            <div>
+              metric.ncloc.name
+              : 
+              1.7short_number_suffix.k
+            </div>
+          </div>,
           "x": 0,
           "y": 53.5,
         },
index b6e9c87e4446b837644e072b2f9541d05740f72a..1c661eb169095ca3de9d24a67859f91fe91aaa4d 100644 (file)
@@ -13,7 +13,7 @@ exports[`renders 1`] = `
     items={
       Array [
         Object {
-          "color": undefined,
+          "color": "#b0d513",
           "key": "foo",
           "link": Object {
             "pathname": "/dashboard",
@@ -22,7 +22,37 @@ exports[`renders 1`] = `
             },
           },
           "size": 1734,
-          "tooltip": "<div class=\\"text-left\\"><div class=\\"little-spacer-bottom\\"><strong>Foo</strong></div><div>metric.complexity.name: 17</div><div>metric.coverage.name: 53.5%</div><div>metric.ncloc.name: 1,734</div></div>",
+          "tooltip": <div
+            className="text-left"
+          >
+            <div
+              className="little-spacer-bottom"
+            >
+              <strong>
+                Foo
+              </strong>
+            </div>
+            <div>
+              metric.complexity.name
+              : 
+              17
+            </div>
+            <div>
+              metric.coverage.name
+              : 
+              53.5%
+            </div>
+            <div>
+              metric.ncloc.name
+              : 
+              1,734
+            </div>
+            <div>
+              metric.security_rating.name
+              : 
+              B
+            </div>
+          </div>,
           "x": 17.2,
           "y": 53.5,
         },
index 2c58557ac4fcac23d3ab573267db14cf27736c25..4a66fe59a8c27b15d6aa1f10f3c296a7661587b2 100644 (file)
@@ -76,7 +76,7 @@ export default class ProjectRowActions extends React.PureComponent<Props, State>
     );
   };
 
-  handleDropdownClick = () => {
+  handleDropdownOpen = () => {
     if (this.state.hasAccess === undefined && !this.state.loading) {
       this.fetchPermissions();
     }
@@ -106,7 +106,7 @@ export default class ProjectRowActions extends React.PureComponent<Props, State>
     const { hasAccess } = this.state;
 
     return (
-      <ActionsDropdown onToggleClick={this.handleDropdownClick}>
+      <ActionsDropdown onOpen={this.handleDropdownOpen}>
         {hasAccess === true && (
           <ActionsDropdownItem to={getComponentPermissionsUrl(this.props.project.key)}>
             {translate('edit_permissions')}
index 4c9e74dc1b12da3612671197e87950ddff7272dd..b30e553d91d0555a52058afac23e3b0acceb8158 100644 (file)
@@ -44,7 +44,7 @@ it('restores access', async () => {
   const wrapper = shallowRender();
   expect(wrapper).toMatchSnapshot();
 
-  wrapper.prop<Function>('onToggleClick')();
+  wrapper.prop<Function>('onOpen')();
   await waitAndUpdate(wrapper);
   expect(wrapper).toMatchSnapshot();
 
index b824855ce963672407ecea81cb9a23b18ef414a5..61ea2fee3de017c74eff23a7942d5b93a00d9f26 100644 (file)
@@ -19,7 +19,7 @@ exports[`applies permission template 1`] = `
 
 exports[`restores access 1`] = `
 <ActionsDropdown
-  onToggleClick={[Function]}
+  onOpen={[Function]}
 >
   <ActionsDropdownItem
     className="js-apply-template"
@@ -32,7 +32,7 @@ exports[`restores access 1`] = `
 
 exports[`restores access 2`] = `
 <ActionsDropdown
-  onToggleClick={[Function]}
+  onOpen={[Function]}
 >
   <ActionsDropdownItem
     className="js-restore-access"
@@ -51,7 +51,7 @@ exports[`restores access 2`] = `
 
 exports[`restores access 3`] = `
 <ActionsDropdown
-  onToggleClick={[Function]}
+  onOpen={[Function]}
 >
   <ActionsDropdownItem
     className="js-restore-access"
index 994946161c36bba095dac18c9224c7fc25e423fd..80ef81fbb546f68569008bfad45f3bcf93542d31 100644 (file)
  */
 import * as React from 'react';
 import { IndexLink } from 'react-router';
+import * as classNames from 'classnames';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { getProfilesPath, getProfilesForLanguagePath } from '../utils';
+import Dropdown from '../../../components/controls/Dropdown';
 
 interface Props {
   currentFilter?: string;
@@ -28,67 +30,55 @@ interface Props {
   organization: string | null;
 }
 
-export default class ProfilesListHeader extends React.PureComponent<Props> {
-  renderFilterToggle() {
-    const { languages, currentFilter } = this.props;
-    const currentLanguage = currentFilter && languages.find(l => l.key === currentFilter);
-
-    const label = currentLanguage
-      ? translateWithParameters('quality_profiles.x_Profiles', currentLanguage.name)
-      : translate('quality_profiles.all_profiles');
-
-    return (
-      <a
-        className="dropdown-toggle link-no-underline js-language-filter"
-        href="#"
-        data-toggle="dropdown">
-        {label} <i className="icon-dropdown" />
-      </a>
-    );
+export default function ProfilesListHeader({ currentFilter, languages, organization }: Props) {
+  if (languages.length < 2) {
+    return null;
   }
 
-  renderFilterMenu() {
-    return (
-      <ul className="dropdown-menu">
-        <li>
-          <IndexLink to={getProfilesPath(this.props.organization)}>
-            {translate('quality_profiles.all_profiles')}
-          </IndexLink>
-        </li>
-        {this.props.languages.map(language => (
-          <li key={language.key}>
-            <IndexLink
-              to={getProfilesForLanguagePath(language.key, this.props.organization)}
-              className="js-language-filter-option"
-              data-language={language.key}>
-              {language.name}
-            </IndexLink>
-          </li>
-        ))}
-      </ul>
-    );
-  }
+  const currentLanguage = currentFilter && languages.find(l => l.key === currentFilter);
 
-  render() {
-    if (this.props.languages.length < 2) {
-      return null;
-    }
+  // if unknown language, then
+  if (currentFilter && !currentLanguage) {
+    return null;
+  }
 
-    const { languages, currentFilter } = this.props;
-    const currentLanguage = currentFilter && languages.find(l => l.key === currentFilter);
+  const label = currentLanguage
+    ? translateWithParameters('quality_profiles.x_Profiles', currentLanguage.name)
+    : translate('quality_profiles.all_profiles');
 
-    // if unknown language, then
-    if (currentFilter && !currentLanguage) {
-      return null;
-    }
+  return (
+    <header className="quality-profiles-list-header clearfix">
+      <Dropdown>
+        {({ onToggleClick, open }) => (
+          <div className={classNames('dropdown', { open })}>
+            <a
+              className="dropdown-toggle link-no-underline js-language-filter"
+              href="#"
+              onClick={onToggleClick}>
+              {label}
+              <i className="icon-dropdown little-spacer-left" />
+            </a>
 
-    return (
-      <header className="quality-profiles-list-header clearfix">
-        <div className="dropdown">
-          {this.renderFilterToggle()}
-          {this.renderFilterMenu()}
-        </div>
-      </header>
-    );
-  }
+            <ul className="dropdown-menu">
+              <li>
+                <IndexLink to={getProfilesPath(organization)}>
+                  {translate('quality_profiles.all_profiles')}
+                </IndexLink>
+              </li>
+              {languages.map(language => (
+                <li key={language.key}>
+                  <IndexLink
+                    className="js-language-filter-option"
+                    data-language={language.key}
+                    to={getProfilesForLanguagePath(language.key, organization)}>
+                    {language.name}
+                  </IndexLink>
+                </li>
+              ))}
+            </ul>
+          </div>
+        )}
+      </Dropdown>
+    </header>
+  );
 }
index 7b718fc5ad20717a36b07f4387d1b1572d03d9de..e34bbf7e10e7d87e229c48977b371b0972c9cd6c 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import * as classNames from 'classnames';
 import ChangeLogLevelForm from './ChangeLogLevelForm';
 import RestartForm from '../../../components/common/RestartForm';
 import { getFileNameSuffix } from '../utils';
+import Dropdown from '../../../components/controls/Dropdown';
 import { EditButton, Button } from '../../../components/ui/buttons';
 import { getBaseUrl } from '../../../helpers/urls';
 import { translate } from '../../../helpers/l10n';
@@ -100,51 +102,54 @@ export default class PageActions extends React.PureComponent<Props, State> {
           />
         </span>
         {this.props.canDownloadLogs && (
-          <div className="display-inline-block dropdown spacer-left">
-            {/* TODO use Dropdown component */}
-            <Button data-toggle="dropdown">
-              {translate('system.download_logs')}
-              <i className="icon-dropdown little-spacer-left" />
-            </Button>
-            <ul className="dropdown-menu">
-              <li>
-                <a
-                  download="sonarqube_app.log"
-                  href={logsUrl + '?process=app'}
-                  id="logs-link"
-                  target="_blank">
-                  Main Process
-                </a>
-              </li>
-              <li>
-                <a
-                  download="sonarqube_ce.log"
-                  href={logsUrl + '?process=ce'}
-                  id="ce-logs-link"
-                  target="_blank">
-                  Compute Engine
-                </a>
-              </li>
-              <li>
-                <a
-                  download="sonarqube_es.log"
-                  href={logsUrl + '?process=es'}
-                  id="es-logs-link"
-                  target="_blank">
-                  Search Engine
-                </a>
-              </li>
-              <li>
-                <a
-                  download="sonarqube_web.log"
-                  href={logsUrl + '?process=web'}
-                  id="web-logs-link"
-                  target="_blank">
-                  Web Server
-                </a>
-              </li>
-            </ul>
-          </div>
+          <Dropdown>
+            {({ onToggleClick, open }) => (
+              <div className={classNames('display-inline-block dropdown spacer-left', { open })}>
+                <Button onClick={onToggleClick}>
+                  {translate('system.download_logs')}
+                  <i className="icon-dropdown little-spacer-left" />
+                </Button>
+                <ul className="dropdown-menu">
+                  <li>
+                    <a
+                      download="sonarqube_app.log"
+                      href={logsUrl + '?process=app'}
+                      id="logs-link"
+                      target="_blank">
+                      Main Process
+                    </a>
+                  </li>
+                  <li>
+                    <a
+                      download="sonarqube_ce.log"
+                      href={logsUrl + '?process=ce'}
+                      id="ce-logs-link"
+                      target="_blank">
+                      Compute Engine
+                    </a>
+                  </li>
+                  <li>
+                    <a
+                      download="sonarqube_es.log"
+                      href={logsUrl + '?process=es'}
+                      id="es-logs-link"
+                      target="_blank">
+                      Search Engine
+                    </a>
+                  </li>
+                  <li>
+                    <a
+                      download="sonarqube_web.log"
+                      href={logsUrl + '?process=web'}
+                      id="web-logs-link"
+                      target="_blank">
+                      Web Server
+                    </a>
+                  </li>
+                </ul>
+              </div>
+            )}
+          </Dropdown>
         )}
         <a
           className="button spacer-left"
index 5bf79f1de3da749e8b533a9660dcf09728ee02bd..44f9d93a7a827607dcbee9fc633cba687c0022f4 100644 (file)
@@ -27,7 +27,9 @@ jest.mock('../../utils', () => ({
 }));
 
 it('should render correctly', () => {
-  expect(getWrapper({ serverId: 'MyServerId' })).toMatchSnapshot();
+  const wrapper = getWrapper({ serverId: 'MyServerId' });
+  expect(wrapper).toMatchSnapshot();
+  expect(wrapper.find('Dropdown').dive()).toMatchSnapshot();
 });
 
 it('should render without restart and log download', () => {
index 252b7a95398953f405684e6e5a434781c11bcdbb..3c698c9ba1900267d64a790f03ffc39629be1768 100644 (file)
@@ -22,62 +22,7 @@ exports[`should render correctly 1`] = `
       onClick={[Function]}
     />
   </span>
-  <div
-    className="display-inline-block dropdown spacer-left"
-  >
-    <Button
-      data-toggle="dropdown"
-    >
-      system.download_logs
-      <i
-        className="icon-dropdown little-spacer-left"
-      />
-    </Button>
-    <ul
-      className="dropdown-menu"
-    >
-      <li>
-        <a
-          download="sonarqube_app.log"
-          href="/api/system/logs?process=app"
-          id="logs-link"
-          target="_blank"
-        >
-          Main Process
-        </a>
-      </li>
-      <li>
-        <a
-          download="sonarqube_ce.log"
-          href="/api/system/logs?process=ce"
-          id="ce-logs-link"
-          target="_blank"
-        >
-          Compute Engine
-        </a>
-      </li>
-      <li>
-        <a
-          download="sonarqube_es.log"
-          href="/api/system/logs?process=es"
-          id="es-logs-link"
-          target="_blank"
-        >
-          Search Engine
-        </a>
-      </li>
-      <li>
-        <a
-          download="sonarqube_web.log"
-          href="/api/system/logs?process=web"
-          id="web-logs-link"
-          target="_blank"
-        >
-          Web Server
-        </a>
-      </li>
-    </ul>
-  </div>
+  <Dropdown />
   <a
     className="button spacer-left"
     download="sonarqube-support-info-filesuffix(MyServerId).json"
@@ -98,6 +43,65 @@ exports[`should render correctly 1`] = `
 </div>
 `;
 
+exports[`should render correctly 2`] = `
+<div
+  className="display-inline-block dropdown spacer-left"
+>
+  <Button
+    onClick={[Function]}
+  >
+    system.download_logs
+    <i
+      className="icon-dropdown little-spacer-left"
+    />
+  </Button>
+  <ul
+    className="dropdown-menu"
+  >
+    <li>
+      <a
+        download="sonarqube_app.log"
+        href="/api/system/logs?process=app"
+        id="logs-link"
+        target="_blank"
+      >
+        Main Process
+      </a>
+    </li>
+    <li>
+      <a
+        download="sonarqube_ce.log"
+        href="/api/system/logs?process=ce"
+        id="ce-logs-link"
+        target="_blank"
+      >
+        Compute Engine
+      </a>
+    </li>
+    <li>
+      <a
+        download="sonarqube_es.log"
+        href="/api/system/logs?process=es"
+        id="es-logs-link"
+        target="_blank"
+      >
+        Search Engine
+      </a>
+    </li>
+    <li>
+      <a
+        download="sonarqube_web.log"
+        href="/api/system/logs?process=web"
+        id="web-logs-link"
+        target="_blank"
+      >
+        Web Server
+      </a>
+    </li>
+  </ul>
+</div>
+`;
+
 exports[`should render without restart and log download 1`] = `
 <div
   className="page-actions"
index 72ac6fa5bcd5ca82151c0d54f348af33a3fcf675..f0fe9ec18abf2385030c7963aa7a3c3154fb2d4a 100644 (file)
@@ -21,10 +21,14 @@ import { stringify } from 'querystring';
 import * as React from 'react';
 import { Link } from 'react-router';
 import * as PropTypes from 'prop-types';
+import * as classNames from 'classnames';
 import MeasuresOverlay from './components/MeasuresOverlay';
 import { SourceViewerFile, BranchLike } from '../../app/types';
 import QualifierIcon from '../shared/QualifierIcon';
+import Dropdown from '../controls/Dropdown';
 import FavoriteContainer from '../controls/FavoriteContainer';
+import ListIcon from '../icons-components/ListIcon';
+import { ButtonIcon } from '../ui/buttons';
 import { WorkspaceContext } from '../workspace/context';
 import {
   getPathUrlAsString,
@@ -124,50 +128,55 @@ export default class SourceViewerHeader extends React.PureComponent<Props, State
           </div>
         </div>
 
-        <div className="dropdown source-viewer-header-actions">
-          <a
-            className="js-actions icon-list dropdown-toggle"
-            data-toggle="dropdown"
-            title={translate('component_viewer.more_actions')}
-          />
-          <ul className="dropdown-menu dropdown-menu-right">
-            <li>
-              <a className="js-measures" href="#" onClick={this.handleShowMeasuresClick}>
-                {translate('component_viewer.show_details')}
-              </a>
-              {this.state.measuresOverlay && (
-                <MeasuresOverlay
-                  branchLike={this.props.branchLike}
-                  onClose={this.handleMeasuresOverlayClose}
-                  sourceViewerFile={this.props.sourceViewerFile}
-                />
-              )}
-            </li>
-            <li>
-              <a
-                className="js-new-window"
-                href={getPathUrlAsString({
-                  pathname: '/component',
-                  query: { id: key, ...getBranchLikeQuery(this.props.branchLike) }
-                })}
-                target="_blank">
-                {translate('component_viewer.new_window')}
-              </a>
-            </li>
-            {!workspace && (
-              <li>
-                <a className="js-workspace" href="#" onClick={this.openInWorkspace}>
-                  {translate('component_viewer.open_in_workspace')}
-                </a>
-              </li>
-            )}
-            <li>
-              <a className="js-raw-source" href={rawSourcesLink} target="_blank">
-                {translate('component_viewer.show_raw_source')}
-              </a>
-            </li>
-          </ul>
-        </div>
+        <Dropdown>
+          {({ onToggleClick, open }) => (
+            <div className={classNames('dropdown source-viewer-header-actions', { open })}>
+              <ButtonIcon
+                className="js-actions"
+                onClick={onToggleClick}
+                tooltip={translate('component_viewer.more_actions')}>
+                <ListIcon />
+              </ButtonIcon>
+              <ul className="dropdown-menu dropdown-menu-right">
+                <li>
+                  <a className="js-measures" href="#" onClick={this.handleShowMeasuresClick}>
+                    {translate('component_viewer.show_details')}
+                  </a>
+                  {this.state.measuresOverlay && (
+                    <MeasuresOverlay
+                      branchLike={this.props.branchLike}
+                      onClose={this.handleMeasuresOverlayClose}
+                      sourceViewerFile={this.props.sourceViewerFile}
+                    />
+                  )}
+                </li>
+                <li>
+                  <a
+                    className="js-new-window"
+                    href={getPathUrlAsString({
+                      pathname: '/component',
+                      query: { id: key, ...getBranchLikeQuery(this.props.branchLike) }
+                    })}
+                    target="_blank">
+                    {translate('component_viewer.new_window')}
+                  </a>
+                </li>
+                {!workspace && (
+                  <li>
+                    <a className="js-workspace" href="#" onClick={this.openInWorkspace}>
+                      {translate('component_viewer.open_in_workspace')}
+                    </a>
+                  </li>
+                )}
+                <li>
+                  <a className="js-raw-source" href={rawSourcesLink} target="_blank">
+                    {translate('component_viewer.show_raw_source')}
+                  </a>
+                </li>
+              </ul>
+            </div>
+          )}
+        </Dropdown>
 
         <div className="source-viewer-header-measures">
           {isUnitTest && (
index 73521086ad3e49efc9a0cc31029613cb8c8c81af..60a57ab69ba023ee90e4dc20c28c5e9d3ad250c9 100644 (file)
   float: right;
   display: block;
   margin-left: 25px;
-  padding: 13px 5px;
+  padding: 8px 5px;
+}
+
+.source-viewer-header-actions svg {
+  margin-top: 2px;
 }
 
 .source-viewer-header-more-actions {
index d2f4947fd5fc9d3a9c0287c02b3d3e9d38898f7c..f428115c4b35f35f9a1dc3f32f9ee2a52871bf27 100644 (file)
@@ -26,7 +26,7 @@ interface Item {
   color?: string;
   key?: string;
   link?: any;
-  tooltip?: string;
+  tooltip?: React.ReactNode;
 }
 
 interface Props {
index 6245210b1b0c208a40e3c0b8fbd59e58ad1065e1..9cb5681debf1e701f7e4154bdd99000ada901cbb 100644 (file)
@@ -24,7 +24,7 @@ import { min, max } from 'd3-array';
 import { scaleLinear } from 'd3-scale';
 import { sortBy, uniq } from 'lodash';
 import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
-import { TooltipsContainer } from '../mixins/tooltips-mixin';
+import Tooltip from '../controls/Tooltip';
 
 /*::
 type Scale = {
@@ -42,7 +42,7 @@ export class Bubble extends React.PureComponent {
     link?: string,
     onClick: (?string) => void,
     r: number,
-    tooltip?: string,
+    tooltip?: string | React$Element<*>,
     x: number,
     y: number
   };
@@ -55,23 +55,12 @@ export class Bubble extends React.PureComponent {
   };
 
   render() {
-    const tooltipAttrs = this.props.tooltip
-      ? {
-          'data-toggle': 'tooltip',
-          title: this.props.tooltip
-        }
-      : {};
-
     let circle = (
       <circle
-        {...tooltipAttrs}
-        onClick={this.props.onClick ? this.handleClick : undefined}
         className="bubble-chart-bubble"
+        onClick={this.props.onClick ? this.handleClick : undefined}
         r={this.props.r}
-        style={{
-          fill: this.props.color,
-          stroke: this.props.color
-        }}
+        style={{ fill: this.props.color, stroke: this.props.color }}
         transform={`translate(${this.props.x}, ${this.props.y})`}
       />
     );
@@ -81,9 +70,9 @@ export class Bubble extends React.PureComponent {
     }
 
     return this.props.tooltip ? (
-      <TooltipsContainer>
+      <Tooltip overlay={this.props.tooltip}>
         <g>{circle}</g>
-      </TooltipsContainer>
+      </Tooltip>
     ) : (
       circle
     );
@@ -99,7 +88,7 @@ export default class BubbleChart extends React.PureComponent {
       color?: string,
       key?: string,
       link?: string,
-      tooltip?: string
+      tooltip?: string | React$Element<*>
     |}>,
     sizeRange?: [number, number],
     displayXGrid: boolean,
diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/work-cloud-test.js b/server/sonar-web/src/main/js/components/charts/__tests__/work-cloud-test.js
deleted file mode 100644 (file)
index 61c92f9..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import React from 'react';
-import { shallow } from 'enzyme';
-import { WordCloud, Word } from '../word-cloud';
-
-it('should display', () => {
-  const items = [
-    { size: 10, link: '#', text: 'SonarQube :: Server' },
-    { size: 30, link: '#', text: 'SonarQube :: Web' },
-    { size: 20, link: '#', text: 'SonarQube :: Search' }
-  ];
-  const chart = shallow(<WordCloud items={items} width={100} height={100} />);
-  expect(chart.find(Word).length).toBe(3);
-});
index 14bec0cad3e5c9bf74ab046368c7e1a21e60cf6c..a312cefc95a90b83801a55cc8769a2b148c2d17f 100644 (file)
@@ -22,8 +22,8 @@ import createReactClass from 'create-react-class';
 import PropTypes from 'prop-types';
 import { max } from 'd3-array';
 import { scaleLinear, scaleBand } from 'd3-scale';
-import { ResizeMixin } from './../mixins/resize-mixin';
-import { TooltipsContainer } from './../mixins/tooltips-mixin';
+import Tooltip from '../controls/Tooltip';
+import { ResizeMixin } from '../mixins/resize-mixin';
 
 export const BarChart = createReactClass({
   displayName: 'BarChart',
@@ -74,24 +74,25 @@ export const BarChart = createReactClass({
       const x = Math.round(xScale(point.x) + xScale.bandwidth() / 2);
       const y = yScale.range()[0];
       const d = this.props.data[index];
-      const tooltipAtts = {};
-      if (d.tooltip) {
-        tooltipAtts['title'] = d.tooltip;
-        tooltipAtts['data-toggle'] = 'tooltip';
-      }
-      return (
+      const text = (
         <text
-          {...tooltipAtts}
-          key={index}
           className="bar-chart-tick"
-          x={x}
-          y={y}
           dy="1.5em"
+          key={index}
           onClick={this.props.onBarClick && this.handleClick.bind(this, point)}
-          style={{ cursor: this.props.onBarClick ? 'pointer' : 'default' }}>
+          style={{ cursor: this.props.onBarClick ? 'pointer' : 'default' }}
+          x={x}
+          y={y}>
           {tick}
         </text>
       );
+      return d.tooltip ? (
+        <Tooltip key={index} overlay={d.tooltip}>
+          {text}
+        </Tooltip>
+      ) : (
+        text
+      );
     });
     return <g>{ticks}</g>;
   },
@@ -105,24 +106,25 @@ export const BarChart = createReactClass({
       const x = Math.round(xScale(point.x) + xScale.bandwidth() / 2);
       const y = yScale(point.y);
       const d = this.props.data[index];
-      const tooltipAtts = {};
-      if (d.tooltip) {
-        tooltipAtts['title'] = d.tooltip;
-        tooltipAtts['data-toggle'] = 'tooltip';
-      }
-      return (
+      const text = (
         <text
-          key={index}
           className="bar-chart-tick"
-          x={x}
-          y={y}
           dy="-1em"
+          key={index}
           onClick={this.props.onBarClick && this.handleClick.bind(this, point)}
           style={{ cursor: this.props.onBarClick ? 'pointer' : 'default' }}
-          {...tooltipAtts}>
+          x={x}
+          y={y}>
           {value}
         </text>
       );
+      return d.tooltip ? (
+        <Tooltip key={index} overlay={d.tooltip}>
+          {text}
+        </Tooltip>
+      ) : (
+        text
+      );
     });
     return <g>{ticks}</g>;
   },
@@ -133,24 +135,25 @@ export const BarChart = createReactClass({
       const maxY = yScale.range()[0];
       const y = Math.round(yScale(d.y)) - /* minimum bar height */ 1;
       const height = maxY - y;
-      const tooltipAtts = {};
-      if (d.tooltip) {
-        tooltipAtts['title'] = d.tooltip;
-        tooltipAtts['data-toggle'] = 'tooltip';
-      }
-      return (
+      const rect = (
         <rect
-          key={index}
           className="bar-chart-bar"
-          {...tooltipAtts}
-          x={x}
-          y={y}
-          width={this.props.barsWidth}
           height={height}
+          key={index}
           onClick={this.props.onBarClick && this.handleClick.bind(this, d)}
           style={{ cursor: this.props.onBarClick ? 'pointer' : 'default' }}
+          width={this.props.barsWidth}
+          x={x}
+          y={y}
         />
       );
+      return d.tooltip ? (
+        <Tooltip key={index} overlay={d.tooltip}>
+          {rect}
+        </Tooltip>
+      ) : (
+        rect
+      );
     });
     return <g>{bars}</g>;
   },
@@ -178,15 +181,13 @@ export const BarChart = createReactClass({
       .range([availableHeight, 0]);
 
     return (
-      <TooltipsContainer>
-        <svg className="bar-chart" width={this.state.width} height={this.state.height}>
-          <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}>
-            {this.renderXTicks(xScale, yScale)}
-            {this.renderXValues(xScale, yScale)}
-            {this.renderBars(xScale, yScale)}
-          </g>
-        </svg>
-      </TooltipsContainer>
+      <svg className="bar-chart" height={this.state.height} width={this.state.width}>
+        <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}>
+          {this.renderXTicks(xScale, yScale)}
+          {this.renderXValues(xScale, yScale)}
+          {this.renderBars(xScale, yScale)}
+        </g>
+      </svg>
     );
   }
 });
index eaa0c4ce6fdd9eda764f94ddd69f662b551a5622..08fd9b0bb247ed8c6866c4e0ae0740fa78362080 100644 (file)
@@ -22,7 +22,6 @@ import createReactClass from 'create-react-class';
 import PropTypes from 'prop-types';
 import { arc as d3Arc, pie as d3Pie } from 'd3-shape';
 import { ResizeMixin } from './../mixins/resize-mixin';
-import { TooltipsMixin } from './../mixins/tooltips-mixin';
 
 function Sector(props) {
   const arc = d3Arc()
@@ -38,7 +37,7 @@ export const DonutChart = createReactClass({
     data: PropTypes.arrayOf(PropTypes.object).isRequired
   },
 
-  mixins: [ResizeMixin, TooltipsMixin],
+  mixins: [ResizeMixin],
 
   getDefaultProps() {
     return { thickness: 6, padding: [0, 0, 0, 0] };
@@ -65,17 +64,17 @@ export const DonutChart = createReactClass({
     const sectors = pie(this.props.data).map((d, i) => {
       return (
         <Sector
-          key={i}
           data={d}
-          radius={radius}
           fill={this.props.data[i].fill}
+          key={i}
+          radius={radius}
           thickness={this.props.thickness}
         />
       );
     });
 
     return (
-      <svg className="donut-chart" width={this.state.width} height={this.state.height}>
+      <svg className="donut-chart" height={this.state.height} width={this.state.width}>
         <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}>
           <g transform={`translate(${radius}, ${radius})`}>{sectors}</g>
         </g>
index bb9a7a880984229bd2e9f072361d0e393e4822f4..af99622368bacc76f4bd9124350450271eb9c991 100644 (file)
@@ -24,7 +24,6 @@ import { extent, max } from 'd3-array';
 import { scaleLinear } from 'd3-scale';
 import { area as d3Area, line as d3Line, curveBasis } from 'd3-shape';
 import { ResizeMixin } from './../mixins/resize-mixin';
-import { TooltipsMixin } from './../mixins/tooltips-mixin';
 
 export const LineChart = createReactClass({
   displayName: 'LineChart',
@@ -41,7 +40,7 @@ export const LineChart = createReactClass({
     height: PropTypes.number
   },
 
-  mixins: [ResizeMixin, TooltipsMixin],
+  mixins: [ResizeMixin],
 
   getDefaultProps() {
     return {
diff --git a/server/sonar-web/src/main/js/components/charts/word-cloud.js b/server/sonar-web/src/main/js/components/charts/word-cloud.js
deleted file mode 100644 (file)
index 71f9ca1..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import React from 'react';
-import createReactClass from 'create-react-class';
-import PropTypes from 'prop-types';
-import { max } from 'd3-array';
-import { scaleLinear } from 'd3-scale';
-import { sortBy } from 'lodash';
-import { TooltipsMixin } from './../mixins/tooltips-mixin';
-
-export function Word(props) {
-  let tooltipAttrs = {};
-  if (props.tooltip) {
-    tooltipAttrs = {
-      'data-toggle': 'tooltip',
-      title: props.tooltip
-    };
-  }
-  return (
-    <a {...tooltipAttrs} style={{ fontSize: props.size }} href={props.link}>
-      {props.text}
-    </a>
-  );
-}
-
-Word.propTypes = {
-  size: PropTypes.number.isRequired,
-  text: PropTypes.string.isRequired,
-  tooltip: PropTypes.string,
-  link: PropTypes.string.isRequired
-};
-
-export const WordCloud = createReactClass({
-  displayName: 'WordCloud',
-
-  propTypes: {
-    items: PropTypes.arrayOf(PropTypes.object).isRequired,
-    sizeRange: PropTypes.arrayOf(PropTypes.number)
-  },
-
-  mixins: [TooltipsMixin],
-
-  getDefaultProps() {
-    return {
-      sizeRange: [10, 24]
-    };
-  },
-
-  render() {
-    const len = this.props.items.length;
-    const sortedItems = sortBy(this.props.items, (item, idx) => {
-      const index = len - idx;
-      return (index % 2) * (len - index) + index / 2;
-    });
-
-    const sizeScale = scaleLinear()
-      .domain([0, max(this.props.items, d => d.size)])
-      .range(this.props.sizeRange);
-    const words = sortedItems.map((item, index) => (
-      <Word
-        key={index}
-        text={item.text}
-        size={sizeScale(item.size)}
-        link={item.link}
-        tooltip={item.tooltip}
-      />
-    ));
-    return <div className="word-cloud">{words}</div>;
-  }
-});
index a20842524a6133bf570ef573dae9f448d8a30522..f1173c88e752c359ad6a3cdbf6e4fcd7be0db28e 100644 (file)
@@ -21,6 +21,7 @@ import * as React from 'react';
 import * as classNames from 'classnames';
 import { Link } from 'react-router';
 import { LocationDescriptor } from 'history';
+import Dropdown from './Dropdown';
 import SettingsIcon from '../icons-components/SettingsIcon';
 import { Button } from '../ui/buttons';
 
@@ -29,31 +30,33 @@ interface Props {
   children: React.ReactNode;
   menuClassName?: string;
   menuPosition?: 'left' | 'right';
-  // TODO: replace with `onOpen` & `onClose`
-  onToggleClick?: () => void;
+  onOpen?: () => void;
   small?: boolean;
   toggleClassName?: string;
 }
 
 export default function ActionsDropdown({ menuPosition = 'right', ...props }: Props) {
   return (
-    <div className={classNames('dropdown', props.className)}>
-      <Button
-        className={classNames('dropdown-toggle', props.toggleClassName, {
-          'button-small': props.small
-        })}
-        data-toggle="dropdown"
-        onClick={props.onToggleClick}>
-        <SettingsIcon className="text-text-bottom" />
-        <i className="icon-dropdown little-spacer-left" />
-      </Button>
-      <ul
-        className={classNames('dropdown-menu', props.menuClassName, {
-          'dropdown-menu-right': menuPosition === 'right'
-        })}>
-        {props.children}
-      </ul>
-    </div>
+    <Dropdown onOpen={props.onOpen}>
+      {({ onToggleClick, open }) => (
+        <div className={classNames('dropdown', props.className, { open })}>
+          <Button
+            className={classNames('dropdown-toggle', props.toggleClassName, {
+              'button-small': props.small
+            })}
+            onClick={onToggleClick}>
+            <SettingsIcon className="text-text-bottom" />
+            <i className="icon-dropdown little-spacer-left" />
+          </Button>
+          <ul
+            className={classNames('dropdown-menu', props.menuClassName, {
+              'dropdown-menu-right': menuPosition === 'right'
+            })}>
+            {props.children}
+          </ul>
+        </div>
+      )}
+    </Dropdown>
   );
 }
 
diff --git a/server/sonar-web/src/main/js/components/mixins/tooltips-mixin.js b/server/sonar-web/src/main/js/components/mixins/tooltips-mixin.js
deleted file mode 100644 (file)
index ae61a8f..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import $ from 'jquery';
-import React from 'react';
-import ReactDOM from 'react-dom';
-
-export const TooltipsMixin = {
-  componentDidMount() {
-    this.initTooltips();
-  },
-
-  componentWillUpdate() {
-    this.hideTooltips();
-  },
-
-  componentDidUpdate() {
-    this.initTooltips();
-  },
-
-  componentWillUnmount() {
-    this.destroyTooltips();
-  },
-
-  initTooltips() {
-    if ($.fn && $.fn.tooltip) {
-      $('[data-toggle="tooltip"]', ReactDOM.findDOMNode(this)).tooltip({
-        container: 'body',
-        placement: 'bottom',
-        html: true
-      });
-    }
-  },
-
-  hideTooltips() {
-    if ($.fn && $.fn.tooltip) {
-      $('[data-toggle="tooltip"]', ReactDOM.findDOMNode(this)).tooltip('hide');
-    }
-  },
-
-  destroyTooltips() {
-    if ($.fn && $.fn.tooltip) {
-      $('[data-toggle="tooltip"]', ReactDOM.findDOMNode(this)).tooltip('destroy');
-    }
-  }
-};
-
-export class TooltipsContainer extends React.PureComponent {
-  componentDidMount() {
-    this.initTooltips();
-  }
-
-  componentWillUpdate() {
-    this.destroyTooltips();
-  }
-
-  componentDidUpdate() {
-    this.initTooltips();
-  }
-
-  componentWillUnmount() {
-    this.destroyTooltips();
-  }
-
-  initTooltips = () => {
-    if ($.fn && $.fn.tooltip) {
-      const options = Object.assign(
-        { container: 'body', placement: 'bottom', html: true },
-        this.props.options
-      );
-      $('[data-toggle="tooltip"]', ReactDOM.findDOMNode(this)).tooltip(options);
-    }
-  };
-
-  hideTooltips = () => {
-    if ($.fn && $.fn.tooltip) {
-      $('[data-toggle="tooltip"]', ReactDOM.findDOMNode(this)).tooltip('hide');
-    }
-  };
-
-  destroyTooltips = () => {
-    if ($.fn && $.fn.tooltip) {
-      $('[data-toggle="tooltip"]', ReactDOM.findDOMNode(this)).tooltip('destroy');
-    }
-  };
-
-  render() {
-    return this.props.children;
-  }
-}
diff --git a/server/sonar-web/src/main/js/components/shared/ComplexityDistribution.js b/server/sonar-web/src/main/js/components/shared/ComplexityDistribution.js
deleted file mode 100644 (file)
index 31ad96f..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import React from 'react';
-import PropTypes from 'prop-types';
-import { BarChart } from '../charts/bar-chart';
-import { formatMeasure } from '../../helpers/measures';
-import { translateWithParameters } from '../../helpers/l10n';
-
-const HEIGHT = 80;
-
-export default class ComplexityDistribution extends React.PureComponent {
-  static propTypes = {
-    distribution: PropTypes.string.isRequired,
-    of: PropTypes.string.isRequired
-  };
-
-  renderBarChart = () => {
-    const data = this.props.distribution.split(';').map((point, index) => {
-      const tokens = point.split('=');
-      const y = parseInt(tokens[1], 10);
-      const value = parseInt(tokens[0], 10);
-      return {
-        x: index,
-        y,
-        value,
-        tooltip: translateWithParameters(`overview.complexity_tooltip.${this.props.of}`, y, value)
-      };
-    });
-
-    const xTicks = data.map(point => point.value);
-
-    const xValues = data.map(point => formatMeasure(point.y, 'INT'));
-
-    return (
-      <BarChart
-        data={data}
-        xTicks={xTicks}
-        xValues={xValues}
-        height={HEIGHT}
-        barsWidth={20}
-        padding={[25, 10, 25, 10]}
-      />
-    );
-  };
-
-  render() {
-    return (
-      <div className="overview-bar-chart" style={{ height: HEIGHT }}>
-        {this.renderBarChart()}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/libs/third-party/bootstrap/dropdown.js b/server/sonar-web/src/main/js/libs/third-party/bootstrap/dropdown.js
deleted file mode 100644 (file)
index 3573bd5..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-+function ($) {
-  'use strict';
-
-  // DROPDOWN CLASS DEFINITION
-  // =========================
-
-  var backdrop = '.dropdown-backdrop'
-  var toggle   = '[data-toggle="dropdown"]'
-  var Dropdown = function (element) {
-    $(element).on('click.bs.dropdown', this.toggle)
-  }
-
-  Dropdown.VERSION = '3.3.1'
-
-  Dropdown.prototype.toggle = function (e) {
-    var $this = $(this)
-
-    if ($this.is('.disabled, :disabled')) return
-
-    var $parent  = getParent($this)
-    var isActive = $parent.hasClass('open')
-
-    clearMenus()
-
-    if (!isActive) {
-      if ('ontouchstart' in document.documentElement) {
-        // if mobile we use a backdrop because click events don't delegate
-        $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
-      }
-
-      var relatedTarget = { relatedTarget: this }
-      $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
-
-      if (e.isDefaultPrevented()) return
-
-      $this
-          .trigger('focus')
-          .attr('aria-expanded', 'true')
-
-      $parent
-          .toggleClass('open')
-          .trigger('shown.bs.dropdown', relatedTarget)
-    }
-
-    return false
-  }
-
-  Dropdown.prototype.keydown = function (e) {
-    if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
-
-    var $this = $(this)
-
-    e.preventDefault()
-    e.stopPropagation()
-
-    if ($this.is('.disabled, :disabled')) return
-
-    var $parent  = getParent($this)
-    var isActive = $parent.hasClass('open')
-
-    if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {
-      if (e.which == 27) $parent.find(toggle).trigger('focus')
-      return $this.trigger('click')
-    }
-
-    var desc = ' li:not(.divider):visible a'
-    var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc)
-
-    if (!$items.length) return
-
-    var index = $items.index(e.target)
-
-    if (e.which == 38 && index > 0)                 index--                        // up
-    if (e.which == 40 && index < $items.length - 1) index++                        // down
-    if (!~index)                                      index = 0
-
-    $items.eq(index).trigger('focus')
-  }
-
-  function clearMenus(e) {
-    if (e && e.which === 3) return
-    $(backdrop).remove()
-    $(toggle).each(function () {
-      var $this         = $(this)
-      var $parent       = getParent($this)
-      var relatedTarget = { relatedTarget: this }
-
-      if (!$parent.hasClass('open')) return
-
-      $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
-
-      if (e.isDefaultPrevented()) return
-
-      $this.attr('aria-expanded', 'false')
-      $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
-    })
-  }
-
-  function getParent($this) {
-    var selector = $this.attr('data-target')
-
-    if (!selector) {
-      selector = $this.attr('href')
-      selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
-    }
-
-    var $parent = selector && $(selector)
-
-    return $parent && $parent.length ? $parent : $this.parent()
-  }
-
-
-  // DROPDOWN PLUGIN DEFINITION
-  // ==========================
-
-  function Plugin(option) {
-    return this.each(function () {
-      var $this = $(this)
-      var data  = $this.data('bs.dropdown')
-
-      if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
-      if (typeof option == 'string') data[option].call($this)
-    })
-  }
-
-  var old = $.fn.dropdown
-
-  $.fn.dropdown             = Plugin
-  $.fn.dropdown.Constructor = Dropdown
-
-
-  // DROPDOWN NO CONFLICT
-  // ====================
-
-  $.fn.dropdown.noConflict = function () {
-    $.fn.dropdown = old
-    return this
-  }
-
-
-  // APPLY TO STANDARD DROPDOWN ELEMENTS
-  // ===================================
-
-  $(document)
-      .on('click.bs.dropdown.data-api', clearMenus)
-      .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
-      .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
-      .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
-      .on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown)
-      .on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown)
-
-}(jQuery);
diff --git a/server/sonar-web/src/main/js/libs/third-party/bootstrap/tooltip.js b/server/sonar-web/src/main/js/libs/third-party/bootstrap/tooltip.js
deleted file mode 100644 (file)
index 535ce42..0000000
+++ /dev/null
@@ -1,487 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-+function ($) {
-  'use strict';
-
-  // TOOLTIP PUBLIC CLASS DEFINITION
-  // ===============================
-
-  var Tooltip = function (element, options) {
-    this.type       =
-    this.options    =
-    this.enabled    =
-    this.timeout    =
-    this.hoverState =
-    this.$element   = null
-
-    this.init('tooltip', element, options)
-  }
-
-  Tooltip.VERSION  = '3.3.1'
-
-  Tooltip.TRANSITION_DURATION = 150
-
-  Tooltip.DEFAULTS = {
-    animation: true,
-    placement: 'top',
-    selector: false,
-    template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
-    trigger: 'hover focus',
-    title: '',
-    delay: 0,
-    html: false,
-    container: false,
-    viewport: {
-      selector: 'body',
-      padding: 0
-    }
-  }
-
-  Tooltip.prototype.init = function (type, element, options) {
-    this.enabled   = true
-    this.type      = type
-    this.$element  = $(element)
-    this.options   = this.getOptions(options)
-    this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)
-
-    var triggers = this.options.trigger.split(' ')
-
-    for (var i = triggers.length; i--;) {
-      var trigger = triggers[i]
-
-      if (trigger == 'click') {
-        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
-      } else if (trigger != 'manual') {
-        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'
-        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
-
-        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
-        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
-      }
-    }
-
-    this.options.selector ?
-      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
-      this.fixTitle()
-  }
-
-  Tooltip.prototype.getDefaults = function () {
-    return Tooltip.DEFAULTS
-  }
-
-  Tooltip.prototype.getOptions = function (options) {
-    options = $.extend({}, this.getDefaults(), this.$element.data(), options)
-
-    if (options.delay && typeof options.delay == 'number') {
-      options.delay = {
-        show: options.delay,
-        hide: options.delay
-      }
-    }
-
-    return options
-  }
-
-  Tooltip.prototype.getDelegateOptions = function () {
-    var options  = {}
-    var defaults = this.getDefaults()
-
-    this._options && $.each(this._options, function (key, value) {
-      if (defaults[key] != value) options[key] = value
-    })
-
-    return options
-  }
-
-  Tooltip.prototype.enter = function (obj) {
-    var self = obj instanceof this.constructor ?
-      obj : $(obj.currentTarget).data('bs.' + this.type)
-
-    if (self && self.$tip && self.$tip.is(':visible')) {
-      self.hoverState = 'in'
-      return
-    }
-
-    if (!self) {
-      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
-      $(obj.currentTarget).data('bs.' + this.type, self)
-    }
-
-    clearTimeout(self.timeout)
-
-    self.hoverState = 'in'
-
-    if (!self.options.delay || !self.options.delay.show) return self.show()
-
-    self.timeout = setTimeout(function () {
-      if (self.hoverState == 'in') self.show()
-    }, self.options.delay.show)
-  }
-
-  Tooltip.prototype.leave = function (obj) {
-    var self = obj instanceof this.constructor ?
-      obj : $(obj.currentTarget).data('bs.' + this.type)
-
-    if (!self) {
-      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
-      $(obj.currentTarget).data('bs.' + this.type, self)
-    }
-
-    clearTimeout(self.timeout)
-
-    self.hoverState = 'out'
-
-    if (!self.options.delay || !self.options.delay.hide) return self.hide()
-
-    self.timeout = setTimeout(function () {
-      if (self.hoverState == 'out') self.hide()
-    }, self.options.delay.hide)
-  }
-
-  Tooltip.prototype.show = function () {
-    var e = $.Event('show.bs.' + this.type)
-
-    if (this.hasContent() && this.enabled) {
-      this.$element.trigger(e)
-
-      var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
-      if (e.isDefaultPrevented() || !inDom) return
-      var that = this
-
-      var $tip = this.tip()
-
-      var tipId = this.getUID(this.type)
-
-      this.setContent()
-      $tip.attr('id', tipId)
-      this.$element.attr('aria-describedby', tipId)
-
-      if (this.options.animation) $tip.addClass('fade')
-
-      var placement = typeof this.options.placement == 'function' ?
-        this.options.placement.call(this, $tip[0], this.$element[0]) :
-        this.options.placement
-
-      var autoToken = /\s?auto?\s?/i
-      var autoPlace = autoToken.test(placement)
-      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
-
-      $tip
-        .detach()
-        .css({ top: 0, left: 0, display: 'block' })
-        .addClass(placement)
-        .data('bs.' + this.type, this)
-
-      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
-
-      var pos          = this.getPosition()
-      var actualWidth  = $tip[0].offsetWidth
-      var actualHeight = $tip[0].offsetHeight
-
-      if (autoPlace) {
-        var orgPlacement = placement
-        var $container   = this.options.container ? $(this.options.container) : this.$element.parent()
-        var containerDim = this.getPosition($container)
-
-        placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top'    :
-                    placement == 'top'    && pos.top    - actualHeight < containerDim.top    ? 'bottom' :
-                    placement == 'right'  && pos.right  + actualWidth  > containerDim.width  ? 'left'   :
-                    placement == 'left'   && pos.left   - actualWidth  < containerDim.left   ? 'right'  :
-                    placement
-
-        $tip
-          .removeClass(orgPlacement)
-          .addClass(placement)
-      }
-
-      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
-
-      this.applyPlacement(calculatedOffset, placement)
-
-      var complete = function () {
-        var prevHoverState = that.hoverState
-        that.$element.trigger('shown.bs.' + that.type)
-        that.hoverState = null
-
-        if (prevHoverState == 'out') that.leave(that)
-      }
-
-      $.support.transition && this.$tip.hasClass('fade') ?
-        $tip
-          .one('bsTransitionEnd', complete)
-          .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
-        complete()
-    }
-  }
-
-  Tooltip.prototype.applyPlacement = function (offset, placement) {
-    var $tip   = this.tip()
-    var width  = $tip[0].offsetWidth
-    var height = $tip[0].offsetHeight
-
-    // manually read margins because getBoundingClientRect includes difference
-    var marginTop = parseInt($tip.css('margin-top'), 10)
-    var marginLeft = parseInt($tip.css('margin-left'), 10)
-
-    // we must check for NaN for ie 8/9
-    if (isNaN(marginTop))  marginTop  = 0
-    if (isNaN(marginLeft)) marginLeft = 0
-
-    offset.top  = offset.top  + marginTop
-    offset.left = offset.left + marginLeft
-
-    // $.fn.offset doesn't round pixel values
-    // so we use setOffset directly with our own function B-0
-    $.offset.setOffset($tip[0], $.extend({
-      using: function (props) {
-        $tip.css({
-          top: Math.round(props.top),
-          left: Math.round(props.left)
-        })
-      }
-    }, offset), 0)
-
-    $tip.addClass('in')
-
-    // check to see if placing tip in new offset caused the tip to resize itself
-    var actualWidth  = $tip[0].offsetWidth
-    var actualHeight = $tip[0].offsetHeight
-
-    if (placement == 'top' && actualHeight != height) {
-      offset.top = offset.top + height - actualHeight
-    }
-
-    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
-
-    if (delta.left) offset.left += delta.left
-    else offset.top += delta.top
-
-    var isVertical          = /top|bottom/.test(placement)
-    var arrowDelta          = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
-    var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
-
-    $tip.offset(offset)
-    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
-  }
-
-  Tooltip.prototype.replaceArrow = function (delta, dimension, isHorizontal) {
-    this.arrow()
-      .css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
-      .css(isHorizontal ? 'top' : 'left', '')
-  }
-
-  Tooltip.prototype.setContent = function () {
-    var $tip  = this.tip()
-    var title = this.getTitle()
-
-    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
-    $tip.removeClass('fade in top bottom left right')
-  }
-
-  Tooltip.prototype.hide = function (callback) {
-    var that = this
-    var $tip = this.tip()
-    var e    = $.Event('hide.bs.' + this.type)
-
-    function complete() {
-      $tip.detach()
-      that.$element
-        .removeAttr('aria-describedby')
-        .trigger('hidden.bs.' + that.type)
-      callback && callback()
-    }
-
-    this.$element.trigger(e)
-
-    if (e.isDefaultPrevented()) return
-
-    $tip.removeClass('in')
-
-    $.support.transition && this.$tip.hasClass('fade') ?
-      $tip
-        .one('bsTransitionEnd', complete)
-        .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
-      complete()
-
-    this.hoverState = null
-
-    return this
-  }
-
-  Tooltip.prototype.fixTitle = function () {
-    var $e = this.$element
-    if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {
-      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
-    }
-  }
-
-  Tooltip.prototype.hasContent = function () {
-    return this.getTitle()
-  }
-
-  Tooltip.prototype.getPosition = function ($element) {
-    $element   = $element || this.$element
-
-    var el     = $element[0]
-    var isBody = el.tagName == 'BODY'
-
-    var elRect    = el.getBoundingClientRect()
-    if (elRect.width == null) {
-      // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
-      elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
-    }
-    var elOffset  = isBody ? { top: 0, left: 0 } : $element.offset()
-    var scroll    = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
-    var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
-
-    return $.extend({}, elRect, scroll, outerDims, elOffset)
-  }
-
-  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
-    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2  } :
-           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2  } :
-           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
-        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width   }
-
-  }
-
-  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
-    var delta = { top: 0, left: 0 }
-    if (!this.$viewport) return delta
-
-    var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
-    var viewportDimensions = this.getPosition(this.$viewport)
-
-    if (/right|left/.test(placement)) {
-      var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll
-      var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
-      if (topEdgeOffset < viewportDimensions.top) { // top overflow
-        delta.top = viewportDimensions.top - topEdgeOffset
-      } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
-        delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
-      }
-    } else {
-      var leftEdgeOffset  = pos.left - viewportPadding
-      var rightEdgeOffset = pos.left + viewportPadding + actualWidth
-      if (leftEdgeOffset < viewportDimensions.left) { // left overflow
-        delta.left = viewportDimensions.left - leftEdgeOffset
-      } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
-        delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
-      }
-    }
-
-    return delta
-  }
-
-  Tooltip.prototype.getTitle = function () {
-    var title
-    var $e = this.$element
-    var o  = this.options
-
-    title = $e.attr('data-original-title')
-      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
-
-    return title
-  }
-
-  Tooltip.prototype.getUID = function (prefix) {
-    do prefix += ~~(Math.random() * 1000000)
-    while (document.getElementById(prefix))
-    return prefix
-  }
-
-  Tooltip.prototype.tip = function () {
-    return (this.$tip = this.$tip || $(this.options.template))
-  }
-
-  Tooltip.prototype.arrow = function () {
-    return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
-  }
-
-  Tooltip.prototype.enable = function () {
-    this.enabled = true
-  }
-
-  Tooltip.prototype.disable = function () {
-    this.enabled = false
-  }
-
-  Tooltip.prototype.toggleEnabled = function () {
-    this.enabled = !this.enabled
-  }
-
-  Tooltip.prototype.toggle = function (e) {
-    var self = this
-    if (e) {
-      self = $(e.currentTarget).data('bs.' + this.type)
-      if (!self) {
-        self = new this.constructor(e.currentTarget, this.getDelegateOptions())
-        $(e.currentTarget).data('bs.' + this.type, self)
-      }
-    }
-
-    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
-  }
-
-  Tooltip.prototype.destroy = function () {
-    var that = this
-    clearTimeout(this.timeout)
-    this.hide(function () {
-      that.$element.off('.' + that.type).removeData('bs.' + that.type)
-    })
-  }
-
-
-  // TOOLTIP PLUGIN DEFINITION
-  // =========================
-
-  function Plugin(option) {
-    return this.each(function () {
-      var $this    = $(this)
-      var data     = $this.data('bs.tooltip')
-      var options  = typeof option == 'object' && option
-      var selector = options && options.selector
-
-      if (!data && option == 'destroy') return
-      if (selector) {
-        if (!data) $this.data('bs.tooltip', (data = {}))
-        if (!data[selector]) data[selector] = new Tooltip(this, options)
-      } else {
-        if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
-      }
-      if (typeof option == 'string') data[option]()
-    })
-  }
-
-  var old = $.fn.tooltip
-
-  $.fn.tooltip             = Plugin
-  $.fn.tooltip.Constructor = Tooltip
-
-
-  // TOOLTIP NO CONFLICT
-  // ===================
-
-  $.fn.tooltip.noConflict = function () {
-    $.fn.tooltip = old
-    return this
-  }
-
-}(jQuery);
diff --git a/server/sonar-web/src/main/js/libs/third-party/select2.js b/server/sonar-web/src/main/js/libs/third-party/select2.js
deleted file mode 100644 (file)
index 26488c4..0000000
+++ /dev/null
@@ -1,2429 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-/*
- Copyright 2012 Igor Vaynberg
-
- Version: 3.2 Timestamp: Mon Sep 10 10:38:04 PDT 2012
-
- Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in
- compliance with the License. You may obtain a copy of the License in the LICENSE file, or at:
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software distributed under the License is
- distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and limitations under the License.
- */
- (function ($) {
-       if(typeof $.fn.each2 == "undefined"){
-               $.fn.extend({
-                       /*
-                       * 4-10 times faster .each replacement
-                       * use it carefully, as it overrides jQuery context of element on each iteration
-                       */
-                       each2 : function (c) {
-                               var j = $([0]), i = -1, l = this.length;
-                               while (
-                                       ++i < l
-                                       && (j.context = j[0] = this[i])
-                                       && c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object
-                               );
-                               return this;
-                       }
-               });
-       }
-})(jQuery);
-
-(function ($, undefined) {
-    "use strict";
-    /*global document, window, jQuery, console */
-
-    if (window.Select2 !== undefined) {
-        return;
-    }
-
-    var KEY, AbstractSelect2, SingleSelect2, MultiSelect2, nextUid, sizer;
-
-    KEY = {
-        TAB: 9,
-        ENTER: 13,
-        ESC: 27,
-        SPACE: 32,
-        LEFT: 37,
-        UP: 38,
-        RIGHT: 39,
-        DOWN: 40,
-        SHIFT: 16,
-        CTRL: 17,
-        ALT: 18,
-        PAGE_UP: 33,
-        PAGE_DOWN: 34,
-        HOME: 36,
-        END: 35,
-        BACKSPACE: 8,
-        DELETE: 46,
-        isArrow: function (k) {
-            k = k.which ? k.which : k;
-            switch (k) {
-            case KEY.LEFT:
-            case KEY.RIGHT:
-            case KEY.UP:
-            case KEY.DOWN:
-                return true;
-            }
-            return false;
-        },
-        isControl: function (e) {
-            var k = e.which;
-            switch (k) {
-            case KEY.SHIFT:
-            case KEY.CTRL:
-            case KEY.ALT:
-                return true;
-            }
-
-            if (e.metaKey) return true;
-
-            return false;
-        },
-        isFunctionKey: function (k) {
-            k = k.which ? k.which : k;
-            return k >= 112 && k <= 123;
-        }
-    };
-
-    nextUid=(function() { var counter=1; return function() { return counter++; }; }());
-
-    function indexOf(value, array) {
-        var i = 0, l = array.length, v;
-
-        if (typeof value === "undefined") {
-          return -1;
-        }
-
-        if (value.constructor === String) {
-            for (; i < l; i = i + 1) if (value.localeCompare(array[i]) === 0) return i;
-        } else {
-            for (; i < l; i = i + 1) {
-                v = array[i];
-                if (v.constructor === String) {
-                    if (v.localeCompare(value) === 0) return i;
-                } else {
-                    if (v === value) return i;
-                }
-            }
-        }
-        return -1;
-    }
-
-    /**
-     * Compares equality of a and b taking into account that a and b may be strings, in which case localeCompare is used
-     * @param a
-     * @param b
-     */
-    function equal(a, b) {
-        if (a === b) return true;
-        if (a === undefined || b === undefined) return false;
-        if (a === null || b === null) return false;
-        if (a.constructor === String) return a.localeCompare(b) === 0;
-        if (b.constructor === String) return b.localeCompare(a) === 0;
-        return false;
-    }
-
-    /**
-     * Splits the string into an array of values, trimming each value. An empty array is returned for nulls or empty
-     * strings
-     * @param string
-     * @param separator
-     */
-    function splitVal(string, separator) {
-        var val, i, l;
-        if (string === null || string.length < 1) return [];
-        val = string.split(separator);
-        for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]);
-        return val;
-    }
-
-    function getSideBorderPadding(element) {
-        return element.outerWidth() - element.width();
-    }
-
-    function installKeyUpChangeEvent(element) {
-        var key="keyup-change-value";
-        element.bind("keydown", function () {
-            if ($.data(element, key) === undefined) {
-                $.data(element, key, element.val());
-            }
-        });
-        element.bind("keyup", function () {
-            var val= $.data(element, key);
-            if (val !== undefined && element.val() !== val) {
-                $.removeData(element, key);
-                element.trigger("keyup-change");
-            }
-        });
-    }
-
-    $(document).delegate("body", "mousemove", function (e) {
-        $.data(document, "select2-lastpos", {x: e.pageX, y: e.pageY});
-    });
-
-    /**
-     * filters mouse events so an event is fired only if the mouse moved.
-     *
-     * filters out mouse events that occur when mouse is stationary but
-     * the elements under the pointer are scrolled.
-     */
-    function installFilteredMouseMove(element) {
-           element.bind("mousemove", function (e) {
-            var lastpos = $.data(document, "select2-lastpos");
-            if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) {
-                $(e.target).trigger("mousemove-filtered", e);
-            }
-        });
-    }
-
-    /**
-     * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made
-     * within the last quietMillis milliseconds.
-     *
-     * @param quietMillis number of milliseconds to wait before invoking fn
-     * @param fn function to be debounced
-     * @param ctx object to be used as this reference within fn
-     * @return debounced version of fn
-     */
-    function debounce(quietMillis, fn, ctx) {
-        ctx = ctx || undefined;
-        var timeout;
-        return function () {
-            var args = arguments;
-            window.clearTimeout(timeout);
-            timeout = window.setTimeout(function() {
-                fn.apply(ctx, args);
-            }, quietMillis);
-        };
-    }
-
-    /**
-     * A simple implementation of a thunk
-     * @param formula function used to lazily initialize the thunk
-     * @return {Function}
-     */
-    function thunk(formula) {
-        var evaluated = false,
-            value;
-        return function() {
-            if (evaluated === false) { value = formula(); evaluated = true; }
-            return value;
-        };
-    };
-
-    function installDebouncedScroll(threshold, element) {
-        var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);});
-        element.bind("scroll", function (e) {
-            if (indexOf(e.target, element.get()) >= 0) notify(e);
-        });
-    }
-
-    function killEvent(event) {
-        event.preventDefault();
-        event.stopPropagation();
-    }
-
-    function measureTextWidth(e) {
-        if (!sizer){
-               var style = e[0].currentStyle || window.getComputedStyle(e[0], null);
-               sizer = $("<div></div>").css({
-                   position: "absolute",
-                   left: "-10000px",
-                   top: "-10000px",
-                   display: "none",
-                   fontSize: style.fontSize,
-                   fontFamily: style.fontFamily,
-                   fontStyle: style.fontStyle,
-                   fontWeight: style.fontWeight,
-                   letterSpacing: style.letterSpacing,
-                   textTransform: style.textTransform,
-                   whiteSpace: "nowrap"
-               });
-               $("body").append(sizer);
-        }
-        sizer.text(e.val());
-        return sizer.width();
-    }
-
-    function markMatch(text, term, markup) {
-        var match=text.toUpperCase().indexOf(term.toUpperCase()),
-            tl=term.length;
-
-        if (match<0) {
-            markup.push(text);
-            return;
-        }
-
-        markup.push(text.substring(0, match));
-        markup.push("<span class='select2-match'>");
-        markup.push(text.substring(match, match + tl));
-        markup.push("</span>");
-        markup.push(text.substring(match + tl, text.length));
-    }
-
-    /**
-     * Produces an ajax-based query function
-     *
-     * @param options object containing configuration paramters
-     * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax
-     * @param options.url url for the data
-     * @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url.
-     * @param options.dataType request data type: ajax, jsonp, other datatatypes supported by jQuery's $.ajax function or the transport function if specified
-     * @param options.traditional a boolean flag that should be true if you wish to use the traditional style of param serialization for the ajax request
-     * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often
-     * @param options.results a function(remoteData, pageNumber) that converts data returned form the remote request to the format expected by Select2.
-     *      The expected format is an object containing the following keys:
-     *      results array of objects that will be used as choices
-     *      more (optional) boolean indicating whether there are more results available
-     *      Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true}
-     */
-    function ajax(options) {
-        var timeout, // current scheduled but not yet executed request
-            requestSequence = 0, // sequence used to drop out-of-order responses
-            handler = null,
-            quietMillis = options.quietMillis || 100;
-
-        return function (query) {
-            window.clearTimeout(timeout);
-            timeout = window.setTimeout(function () {
-                requestSequence += 1; // increment the sequence
-                var requestNumber = requestSequence, // this request's sequence number
-                    data = options.data, // ajax data function
-                    transport = options.transport || $.ajax,
-                    traditional = options.traditional || false,
-                    type = options.type || 'GET'; // set type of request (GET or POST)
-
-                data = data.call(this, query.term, query.page, query.context);
-
-                if( null !== handler) { handler.abort(); }
-
-                handler = transport.call(null, {
-                    url: options.url,
-                    dataType: options.dataType,
-                    data: data,
-                    type: type,
-                    traditional: traditional,
-                    success: function (data) {
-                        if (requestNumber < requestSequence) {
-                            return;
-                        }
-                        // TODO 3.0 - replace query.page with query so users have access to term, page, etc.
-                        var results = options.results(data, query.page);
-                        query.callback(results);
-                    }
-                });
-            }, quietMillis);
-        };
-    }
-
-    /**
-     * Produces a query function that works with a local array
-     *
-     * @param options object containing configuration parameters. The options parameter can either be an array or an
-     * object.
-     *
-     * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys.
-     *
-     * If the object form is used ti is assumed that it contains 'data' and 'text' keys. The 'data' key should contain
-     * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text'
-     * key can either be a String in which case it is expected that each element in the 'data' array has a key with the
-     * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract
-     * the text.
-     */
-    function local(options) {
-        var data = options, // data elements
-            dataText,
-            text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search
-
-        if (!$.isArray(data)) {
-            text = data.text;
-            // if text is not a function we assume it to be a key name
-            if (!$.isFunction(text)) {
-              dataText = data.text; // we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available
-              text = function (item) { return item[dataText]; };
-            }
-            data = data.results;
-        }
-
-        return function (query) {
-            var t = query.term, filtered = { results: [] }, process;
-            if (t === "") {
-                query.callback({results: data});
-                return;
-            }
-
-            process = function(datum, collection) {
-                var group, attr;
-                datum = datum[0];
-                if (datum.children) {
-                    group = {};
-                    for (attr in datum) {
-                        if (datum.hasOwnProperty(attr)) group[attr]=datum[attr];
-                    }
-                    group.children=[];
-                    $(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); });
-                    if (group.children.length) {
-                        collection.push(group);
-                    }
-                } else {
-                    if (query.matcher(t, text(datum))) {
-                        collection.push(datum);
-                    }
-                }
-            };
-
-            $(data).each2(function(i, datum) { process(datum, filtered.results); });
-            query.callback(filtered);
-        };
-    }
-
-    // TODO javadoc
-    function tags(data) {
-        // TODO even for a function we should probably return a wrapper that does the same object/string check as
-        // the function for arrays. otherwise only functions that return objects are supported.
-        if ($.isFunction(data)) {
-            return data;
-        }
-
-        // if not a function we assume it to be an array
-
-        return function (query) {
-            var t = query.term, filtered = {results: []};
-            $(data).each(function () {
-                var isObject = this.text !== undefined,
-                    text = isObject ? this.text : this;
-                if (t === "" || query.matcher(t, text)) {
-                    filtered.results.push(isObject ? this : {id: this, text: this});
-                }
-            });
-            query.callback(filtered);
-        };
-    }
-
-    /**
-     * Checks if the formatter function should be used.
-     *
-     * Throws an error if it is not a function. Returns true if it should be used,
-     * false if no formatting should be performed.
-     *
-     * @param formatter
-     */
-    function checkFormatter(formatter, formatterName) {
-        if ($.isFunction(formatter)) return true;
-        if (!formatter) return false;
-        throw new Error("formatterName must be a function or a falsy value");
-    }
-
-    function evaluate(val) {
-        return $.isFunction(val) ? val() : val;
-    }
-
-    function countResults(results) {
-        var count = 0;
-        $.each(results, function(i, item) {
-            if (item.children) {
-                count += countResults(item.children);
-            } else {
-                count++;
-            }
-        });
-        return count;
-    }
-
-    /**
-     * Default tokenizer. This function uses breaks the input on substring match of any string from the
-     * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those
-     * two options have to be defined in order for the tokenizer to work.
-     *
-     * @param input text user has typed so far or pasted into the search field
-     * @param selection currently selected choices
-     * @param selectCallback function(choice) callback tho add the choice to selection
-     * @param opts select2's opts
-     * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value
-     */
-    function defaultTokenizer(input, selection, selectCallback, opts) {
-        var original = input, // store the original so we can compare and know if we need to tell the search to update its text
-            dupe = false, // check for whether a token we extracted represents a duplicate selected choice
-            token, // token
-            index, // position at which the separator was found
-            i, l, // looping variables
-            separator; // the matched separator
-
-        if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined;
-
-        while (true) {
-            index = -1;
-
-            for (i = 0, l = opts.tokenSeparators.length; i < l; i++) {
-                separator = opts.tokenSeparators[i];
-                index = input.indexOf(separator);
-                if (index >= 0) break;
-            }
-
-            if (index < 0) break; // did not find any token separator in the input string, bail
-
-            token = input.substring(0, index);
-            input = input.substring(index + separator.length);
-
-            if (token.length > 0) {
-                token = opts.createSearchChoice(token, selection);
-                if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) {
-                    dupe = false;
-                    for (i = 0, l = selection.length; i < l; i++) {
-                        if (equal(opts.id(token), opts.id(selection[i]))) {
-                            dupe = true; break;
-                        }
-                    }
-
-                    if (!dupe) selectCallback(token);
-                }
-            }
-        }
-
-        if (original.localeCompare(input) != 0) return input;
-    }
-
-    /**
-     * blurs any Select2 container that has focus when an element outside them was clicked or received focus
-     *
-     * also takes care of clicks on label tags that point to the source element
-     */
-    $(document).ready(function () {
-        $(document).delegate("body", "mousedown touchend", function (e) {
-            var target = $(e.target).closest("div.select2-container").get(0), attr;
-            if (target) {
-                $(document).find("div.select2-container-active").each(function () {
-                    if (this !== target) $(this).data("select2").blur();
-                });
-            } else {
-                target = $(e.target).closest("div.select2-drop").get(0);
-                $(document).find("div.select2-drop-active").each(function () {
-                    if (this !== target) $(this).data("select2").blur();
-                });
-            }
-
-            target=$(e.target);
-            attr = target.attr("for");
-            if ("LABEL" === e.target.tagName && attr && attr.length > 0) {
-                target = $("#"+attr);
-                target = target.data("select2");
-                if (target !== undefined) { target.focus(); e.preventDefault();}
-            }
-        });
-    });
-
-    /**
-     * Creates a new class
-     *
-     * @param superClass
-     * @param methods
-     */
-    function clazz(SuperClass, methods) {
-        var constructor = function () {};
-        constructor.prototype = new SuperClass;
-        constructor.prototype.constructor = constructor;
-        constructor.prototype.parent = SuperClass.prototype;
-        constructor.prototype = $.extend(constructor.prototype, methods);
-        return constructor;
-    }
-
-    AbstractSelect2 = clazz(Object, {
-
-        // abstract
-        bind: function (func) {
-            var self = this;
-            return function () {
-                func.apply(self, arguments);
-            };
-        },
-
-        // abstract
-        init: function (opts) {
-            var results, search, resultsSelector = ".select2-results";
-
-            // prepare options
-            this.opts = opts = this.prepareOpts(opts);
-
-            this.id=opts.id;
-
-            // destroy if called on an existing component
-            if (opts.element.data("select2") !== undefined &&
-                opts.element.data("select2") !== null) {
-                this.destroy();
-            }
-
-            this.enabled=true;
-            this.container = this.createContainer();
-
-            this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid());
-            this.containerSelector="#"+this.containerId.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1');
-            this.container.attr("id", this.containerId);
-
-            // cache the body so future lookups are cheap
-            this.body = thunk(function() { return opts.element.closest("body"); });
-
-            if (opts.element.attr("class") !== undefined) {
-                this.container.addClass(opts.element.attr("class").replace(/validate\[[\S ]+] ?/, ''));
-            }
-
-            this.container.css(evaluate(opts.containerCss));
-            this.container.addClass(evaluate(opts.containerCssClass));
-
-            // swap container for the element
-            this.opts.element
-                .data("select2", this)
-                .hide()
-                .before(this.container);
-            this.container.data("select2", this);
-
-            this.dropdown = this.container.find(".select2-drop");
-            this.dropdown.addClass(evaluate(opts.dropdownCssClass));
-            this.dropdown.data("select2", this);
-
-            this.results = results = this.container.find(resultsSelector);
-            this.search = search = this.container.find("input.select2-input");
-
-            search.attr("tabIndex", this.opts.element.attr("tabIndex"));
-
-            this.resultsPage = 0;
-            this.context = null;
-
-            // initialize the container
-            this.initContainer();
-            this.initContainerWidth();
-
-            installFilteredMouseMove(this.results);
-            this.dropdown.delegate(resultsSelector, "mousemove-filtered", this.bind(this.highlightUnderEvent));
-
-            installDebouncedScroll(80, this.results);
-            this.dropdown.delegate(resultsSelector, "scroll-debounced", this.bind(this.loadMoreIfNeeded));
-
-            // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
-            if ($.fn.mousewheel) {
-                results.mousewheel(function (e, delta, deltaX, deltaY) {
-                    var top = results.scrollTop(), height;
-                    if (deltaY > 0 && top - deltaY <= 0) {
-                        results.scrollTop(0);
-                        killEvent(e);
-                    } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) {
-                        results.scrollTop(results.get(0).scrollHeight - results.height());
-                        killEvent(e);
-                    }
-                });
-            }
-
-            installKeyUpChangeEvent(search);
-            search.bind("keyup-change", this.bind(this.updateResults));
-            search.bind("focus", function () { search.addClass("select2-focused"); if (search.val() === " ") search.val(""); });
-            search.bind("blur", function () { search.removeClass("select2-focused");});
-
-            this.dropdown.delegate(resultsSelector, "mouseup", this.bind(function (e) {
-                if ($(e.target).closest(".select2-result-selectable:not(.select2-disabled)").length > 0) {
-                    this.highlightUnderEvent(e);
-                    this.selectHighlighted(e);
-                } else {
-                    this.focusSearch();
-                }
-                killEvent(e);
-            }));
-
-            // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening
-            // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's
-            // dom it will trigger the popup close, which is not what we want
-            this.dropdown.bind("click mouseup mousedown", function (e) { e.stopPropagation(); });
-
-            if ($.isFunction(this.opts.initSelection)) {
-                // initialize selection based on the current value of the source element
-                this.initSelection();
-
-                // if the user has provided a function that can set selection based on the value of the source element
-                // we monitor the change event on the element and trigger it, allowing for two way synchronization
-                this.monitorSource();
-            }
-
-            if (opts.element.is(":disabled") || opts.element.is("[readonly='readonly']")) this.disable();
-        },
-
-        // abstract
-        destroy: function () {
-            var select2 = this.opts.element.data("select2");
-            if (select2 !== undefined) {
-                select2.container.remove();
-                select2.dropdown.remove();
-                select2.opts.element
-                    .removeData("select2")
-                    .unbind(".select2")
-                    .show();
-            }
-        },
-
-        // abstract
-        prepareOpts: function (opts) {
-            var element, select, idKey, ajaxUrl;
-
-            element = opts.element;
-
-            if (element.get(0).tagName.toLowerCase() === "select") {
-                this.select = select = opts.element;
-            }
-
-            if (select) {
-                // these options are not allowed when attached to a select because they are picked up off the element itself
-                $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () {
-                    if (this in opts) {
-                        throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a <select> element.");
-                    }
-                });
-            }
-
-            opts = $.extend({}, {
-                populateResults: function(container, results, query) {
-                    var populate,  data, result, children, id=this.opts.id, self=this;
-
-                    populate=function(results, container, depth) {
-
-                        var i, l, result, selectable, compound, node, label, innerContainer, formatted;
-                        for (i = 0, l = results.length; i < l; i = i + 1) {
-
-                            result=results[i];
-                            selectable=id(result) !== undefined;
-                            compound=result.children && result.children.length > 0;
-
-                            node=$("<li></li>");
-                            node.addClass("select2-results-dept-"+depth);
-                            node.addClass("select2-result");
-                            node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable");
-                            if (compound) { node.addClass("select2-result-with-children"); }
-                            node.addClass(self.opts.formatResultCssClass(result));
-
-                            label=$("<div></div>");
-                            label.addClass("select2-result-label");
-
-                            formatted=opts.formatResult(result, label, query);
-                            if (formatted!==undefined) {
-                                label.html(self.opts.escapeMarkup(formatted));
-                            }
-
-                            node.append(label);
-
-                            if (compound) {
-
-                                innerContainer=$("<ul></ul>");
-                                innerContainer.addClass("select2-result-sub");
-                                populate(result.children, innerContainer, depth+1);
-                                node.append(innerContainer);
-                            }
-
-                            node.data("select2-data", result);
-                            container.append(node);
-                        }
-                    };
-
-                    populate(results, container, 0);
-                }
-            }, $.fn.select2.defaults, opts);
-
-            if (typeof(opts.id) !== "function") {
-                idKey = opts.id;
-                opts.id = function (e) { return e[idKey]; };
-            }
-
-            if (select) {
-                opts.query = this.bind(function (query) {
-                    var data = { results: [], more: false },
-                        term = query.term,
-                        children, firstChild, process;
-
-                    process=function(element, collection) {
-                        var group;
-                        if (element.is("option")) {
-                            if (query.matcher(term, element.text(), element)) {
-                                collection.push({id:element.attr("value"), text:element.text(), element: element.get(), css: element.attr("class")});
-                            }
-                        } else if (element.is("optgroup")) {
-                            group={text:element.attr("label"), children:[], element: element.get(), css: element.attr("class")};
-                            element.children().each2(function(i, elm) { process(elm, group.children); });
-                            if (group.children.length>0) {
-                                collection.push(group);
-                            }
-                        }
-                    };
-
-                    children=element.children();
-
-                    // ignore the placeholder option if there is one
-                    if (this.getPlaceholder() !== undefined && children.length > 0) {
-                        firstChild = children[0];
-                        if ($(firstChild).text() === "") {
-                            children=children.not(firstChild);
-                        }
-                    }
-
-                    children.each2(function(i, elm) { process(elm, data.results); });
-
-                    query.callback(data);
-                });
-                // this is needed because inside val() we construct choices from options and there id is hardcoded
-                opts.id=function(e) { return e.id; };
-                opts.formatResultCssClass = function(data) { return data.css; }
-            } else {
-                if (!("query" in opts)) {
-                    if ("ajax" in opts) {
-                        ajaxUrl = opts.element.data("ajax-url");
-                        if (ajaxUrl && ajaxUrl.length > 0) {
-                            opts.ajax.url = ajaxUrl;
-                        }
-                        opts.query = ajax(opts.ajax);
-                    } else if ("data" in opts) {
-                        opts.query = local(opts.data);
-                    } else if ("tags" in opts) {
-                        opts.query = tags(opts.tags);
-                        opts.createSearchChoice = function (term) { return {id: term, text: term}; };
-                        opts.initSelection = function (element, callback) {
-                            var data = [];
-                            $(splitVal(element.val(), opts.separator)).each(function () {
-                                var id = this, text = this, tags=opts.tags;
-                                if ($.isFunction(tags)) tags=tags();
-                                $(tags).each(function() { if (equal(this.id, id)) { text = this.text; return false; } });
-                                data.push({id: id, text: text});
-                            });
-
-                            callback(data);
-                        };
-                    }
-                }
-            }
-            if (typeof(opts.query) !== "function") {
-                throw "query function not defined for Select2 " + opts.element.attr("id");
-            }
-
-            return opts;
-        },
-
-        /**
-         * Monitor the original element for changes and update select2 accordingly
-         */
-        // abstract
-        monitorSource: function () {
-            this.opts.element.bind("change.select2", this.bind(function (e) {
-                if (this.opts.element.data("select2-change-triggered") !== true) {
-                    this.initSelection();
-                }
-            }));
-        },
-
-        /**
-         * Triggers the change event on the source element
-         */
-        // abstract
-        triggerChange: function (details) {
-
-            details = details || {};
-            details= $.extend({}, details, { type: "change", val: this.val() });
-            // prevents recursive triggering
-            this.opts.element.data("select2-change-triggered", true);
-            this.opts.element.trigger(details);
-            this.opts.element.data("select2-change-triggered", false);
-
-            // some validation frameworks ignore the change event and listen instead to keyup, click for selects
-            // so here we trigger the click event manually
-            this.opts.element.click();
-
-            // ValidationEngine ignorea the change event and listens instead to blur
-            // so here we trigger the blur event manually if so desired
-            if (this.opts.blurOnChange)
-                this.opts.element.blur();
-        },
-
-
-        // abstract
-        enable: function() {
-            if (this.enabled) return;
-
-            this.enabled=true;
-            this.container.removeClass("select2-container-disabled");
-        },
-
-        // abstract
-        disable: function() {
-            if (!this.enabled) return;
-
-            this.close();
-
-            this.enabled=false;
-            this.container.addClass("select2-container-disabled");
-        },
-
-        // abstract
-        opened: function () {
-            return this.container.hasClass("select2-dropdown-open");
-        },
-
-        // abstract
-        positionDropdown: function() {
-            var offset = this.container.offset(),
-                height = this.container.outerHeight(),
-                width = this.container.outerWidth(),
-                dropHeight = this.dropdown.outerHeight(),
-                viewportBottom = $(window).scrollTop() + document.documentElement.clientHeight,
-                dropTop = offset.top + height,
-                dropLeft = offset.left,
-                enoughRoomBelow = dropTop + dropHeight <= viewportBottom,
-                enoughRoomAbove = (offset.top - dropHeight) >= this.body().scrollTop(),
-                aboveNow = this.dropdown.hasClass("select2-drop-above"),
-                bodyOffset,
-                above,
-                css;
-
-            // console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow);
-            // console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body().scrollTop(), "enough?", enoughRoomAbove);
-
-            // fix positioning when body has an offset and is not position: static
-
-            if (this.body().css('position') !== 'static') {
-                bodyOffset = this.body().offset();
-                dropTop -= bodyOffset.top;
-                dropLeft -= bodyOffset.left;
-            }
-
-            // always prefer the current above/below alignment, unless there is not enough room
-
-            if (aboveNow) {
-                above = true;
-                if (!enoughRoomAbove && enoughRoomBelow) above = false;
-            } else {
-                above = false;
-                if (!enoughRoomBelow && enoughRoomAbove) above = true;
-            }
-
-            if (above) {
-                dropTop = offset.top - dropHeight;
-                this.container.addClass("select2-drop-above");
-                this.dropdown.addClass("select2-drop-above");
-            }
-            else {
-                this.container.removeClass("select2-drop-above");
-                this.dropdown.removeClass("select2-drop-above");
-            }
-
-            css = $.extend({
-                top: dropTop,
-                left: dropLeft,
-                width: width
-            }, evaluate(this.opts.dropdownCss));
-
-            this.dropdown.css(css);
-        },
-
-        // abstract
-        shouldOpen: function() {
-            var event;
-
-            if (this.opened()) return false;
-
-            event = $.Event("open");
-            this.opts.element.trigger(event);
-            return !event.isDefaultPrevented();
-        },
-
-        // abstract
-        clearDropdownAlignmentPreference: function() {
-            // clear the classes used to figure out the preference of where the dropdown should be opened
-            this.container.removeClass("select2-drop-above");
-            this.dropdown.removeClass("select2-drop-above");
-        },
-
-        /**
-         * Opens the dropdown
-         *
-         * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example,
-         * the dropdown is already open, or if the 'open' event listener on the element called preventDefault().
-         */
-        // abstract
-        open: function () {
-
-            if (!this.shouldOpen()) return false;
-
-            window.setTimeout(this.bind(this.opening), 1);
-
-            return true;
-        },
-
-        /**
-         * Performs the opening of the dropdown
-         */
-        // abstract
-        opening: function() {
-            var cid = this.containerId, selector = this.containerSelector,
-                scroll = "scroll." + cid, resize = "resize." + cid;
-
-            setTimeout(function () {
-                this.container.parents().each(function() {
-                    $(this).bind(scroll, function() {
-                        var s2 = $(selector);
-                        if (s2.length == 0) {
-                            $(this).unbind(scroll);
-                        }
-                        s2.select2("close");
-                    });
-                });
-            }.bind(this), 100);
-
-            $(window).bind(resize, function() {
-                var s2 = $(selector);
-                if (s2.length == 0) {
-                    $(window).unbind(resize);
-                }
-                s2.select2("close");
-            });
-
-            this.clearDropdownAlignmentPreference();
-
-            if (this.search.val() === " ") { this.search.val(""); }
-
-            this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
-
-            this.updateResults(true);
-
-            if(this.dropdown[0] !== this.body().children().last()[0]) {
-                this.dropdown.detach().appendTo(this.body());
-            }
-
-            this.dropdown.show();
-
-            this.positionDropdown();
-            this.dropdown.addClass("select2-drop-active");
-
-            this.ensureHighlightVisible();
-
-            this.focusSearch();
-        },
-
-        // abstract
-        close: function () {
-            if (!this.opened()) return;
-
-            var self = this;
-
-            this.container.parents().each(function() {
-                $(this).unbind("scroll." + self.containerId);
-            });
-            $(window).unbind("resize." + this.containerId);
-
-            this.clearDropdownAlignmentPreference();
-
-            this.dropdown.hide();
-            this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active");
-            this.results.empty();
-            this.clearSearch();
-
-            this.opts.element.trigger($.Event("close"));
-        },
-
-        // abstract
-        clearSearch: function () {
-
-        },
-
-        // abstract
-        ensureHighlightVisible: function () {
-            var results = this.results, children, index, child, hb, rb, y, more;
-
-            index = this.highlight();
-
-            if (index < 0) return;
-
-            if (index == 0) {
-
-                // if the first element is highlighted scroll all the way to the top,
-                // that way any unselectable headers above it will also be scrolled
-                // into view
-
-                results.scrollTop(0);
-                return;
-            }
-
-            children = results.find(".select2-result-selectable");
-
-            child = $(children[index]);
-
-            hb = child.offset().top + child.outerHeight();
-
-            // if this is the last child lets also make sure select2-more-results is visible
-            if (index === children.length - 1) {
-                more = results.find("li.select2-more-results");
-                if (more.length > 0) {
-                    hb = more.offset().top + more.outerHeight();
-                }
-            }
-
-            rb = results.offset().top + results.outerHeight();
-            if (hb > rb) {
-                results.scrollTop(results.scrollTop() + (hb - rb));
-            }
-            y = child.offset().top - results.offset().top;
-
-            // make sure the top of the element is visible
-            if (y < 0) {
-                results.scrollTop(results.scrollTop() + y); // y is negative
-            }
-        },
-
-        // abstract
-        moveHighlight: function (delta) {
-            var choices = this.results.find(".select2-result-selectable"),
-                index = this.highlight();
-
-            while (index > -1 && index < choices.length) {
-                index += delta;
-                var choice = $(choices[index]);
-                if (choice.hasClass("select2-result-selectable") && !choice.hasClass("select2-disabled")) {
-                    this.highlight(index);
-                    break;
-                }
-            }
-        },
-
-        // abstract
-        highlight: function (index) {
-            var choices = this.results.find(".select2-result-selectable").not(".select2-disabled");
-
-            if (arguments.length === 0) {
-                return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
-            }
-
-            if (index >= choices.length) index = choices.length - 1;
-            if (index < 0) index = 0;
-
-            choices.removeClass("select2-highlighted");
-
-            $(choices[index]).addClass("select2-highlighted");
-            this.ensureHighlightVisible();
-
-        },
-
-        // abstract
-        countSelectableResults: function() {
-            return this.results.find(".select2-result-selectable").not(".select2-disabled").length;
-        },
-
-        // abstract
-        highlightUnderEvent: function (event) {
-            var el = $(event.target).closest(".select2-result-selectable");
-            if (el.length > 0 && !el.is(".select2-highlighted")) {
-                       var choices = this.results.find('.select2-result-selectable');
-                this.highlight(choices.index(el));
-            } else if (el.length == 0) {
-                // if we are over an unselectable item remove al highlights
-                this.results.find(".select2-highlighted").removeClass("select2-highlighted");
-            }
-        },
-
-        // abstract
-        loadMoreIfNeeded: function () {
-            var results = this.results,
-                more = results.find("li.select2-more-results"),
-                below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
-                offset = -1, // index of first element without data
-                page = this.resultsPage + 1,
-                self=this,
-                term=this.search.val(),
-                context=this.context;
-
-            if (more.length === 0) return;
-            below = more.offset().top - results.offset().top - results.height();
-
-            if (below <= 0) {
-                more.addClass("select2-active");
-                this.opts.query({
-                        term: term,
-                        page: page,
-                        context: context,
-                        matcher: this.opts.matcher,
-                        callback: this.bind(function (data) {
-
-                    // ignore a response if the select2 has been closed before it was received
-                    if (!self.opened()) return;
-
-
-                    self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context});
-
-                    if (data.more===true) {
-                        more.detach().appendTo(results).text(self.opts.formatLoadMore(page+1));
-                        window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
-                    } else {
-                        more.remove();
-                    }
-                    self.positionDropdown();
-                    self.resultsPage = page;
-                })});
-            }
-        },
-
-        /**
-         * Default tokenizer function which does nothing
-         */
-        tokenize: function() {
-
-        },
-
-        /**
-         * @param initial whether or not this is the call to this method right after the dropdown has been opened
-         */
-        // abstract
-        updateResults: function (initial) {
-            var search = this.search, results = this.results, opts = this.opts, data, self=this, input;
-
-            // if the search is currently hidden we do not alter the results
-            if (initial !== true && (this.showSearchInput === false || !this.opened())) {
-                return;
-            }
-
-            search.addClass("select2-active");
-
-            function postRender() {
-                results.scrollTop(0);
-                search.removeClass("select2-active");
-                self.positionDropdown();
-            }
-
-            function render(html) {
-                results.html(self.opts.escapeMarkup(html));
-                postRender();
-            }
-
-            if (opts.maximumSelectionSize >=1) {
-                data = this.data();
-                if ($.isArray(data) && data.length >= opts.maximumSelectionSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) {
-                   render("<li class='select2-selection-limit'>" + opts.formatSelectionTooBig(opts.maximumSelectionSize) + "</li>");
-                   return;
-                }
-            }
-
-            if (search.val().length < opts.minimumInputLength && checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) {
-                render("<li class='select2-no-results'>" + opts.formatInputTooShort(search.val(), opts.minimumInputLength) + "</li>");
-                return;
-            }
-            else {
-                render("<li class='select2-searching'>" + opts.formatSearching() + "</li>");
-            }
-
-            // give the tokenizer a chance to pre-process the input
-            input = this.tokenize();
-            if (input != undefined && input != null) {
-                search.val(input);
-            }
-
-            this.resultsPage = 1;
-            opts.query({
-                    term: search.val(),
-                    page: this.resultsPage,
-                    context: null,
-                    matcher: opts.matcher,
-                    callback: this.bind(function (data) {
-                var def; // default choice
-
-                // ignore a response if the select2 has been closed before it was received
-                if (!this.opened()) return;
-
-                // save context, if any
-                this.context = (data.context===undefined) ? null : data.context;
-
-                // create a default choice and prepend it to the list
-                if (this.opts.createSearchChoice && search.val() !== "") {
-                    def = this.opts.createSearchChoice.call(null, search.val(), data.results);
-                    if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) {
-                        if ($(data.results).filter(
-                            function () {
-                                return equal(self.id(this), self.id(def));
-                            }).length === 0) {
-                            data.results.unshift(def);
-                        }
-                    }
-                }
-
-                if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) {
-                    render("<li class='select2-no-results'>" + opts.formatNoMatches(search.val()) + "</li>");
-                    return;
-                }
-
-                results.empty();
-                self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null});
-
-                if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) {
-                    results.append("<li class='select2-more-results'>" + self.opts.escapeMarkup(opts.formatLoadMore(this.resultsPage)) + "</li>");
-                    window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
-                }
-
-                this.postprocessResults(data, initial);
-
-                postRender();
-            })});
-        },
-
-        // abstract
-        cancel: function () {
-            this.close();
-        },
-
-        // abstract
-        blur: function () {
-            this.close();
-            this.container.removeClass("select2-container-active");
-            this.dropdown.removeClass("select2-drop-active");
-            // synonymous to .is(':focus'), which is available in jquery >= 1.6
-            if (this.search[0] === document.activeElement) { this.search.blur(); }
-            this.clearSearch();
-            this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
-        },
-
-        // abstract
-        focusSearch: function () {
-            // need to do it here as well as in timeout so it works in IE
-            this.search.show();
-            this.search.focus();
-
-            /* we do this in a timeout so that current event processing can complete before this code is executed.
-             this makes sure the search field is focussed even if the current event would blur it */
-            window.setTimeout(this.bind(function () {
-                // reset the value so IE places the cursor at the end of the input box
-                this.search.show();
-                this.search.focus();
-                this.search.val(this.search.val());
-            }), 10);
-        },
-
-        // abstract
-        selectHighlighted: function () {
-            var index=this.highlight(),
-                highlighted=this.results.find(".select2-highlighted").not(".select2-disabled"),
-                data = highlighted.closest('.select2-result-selectable').data("select2-data");
-            if (data) {
-                highlighted.addClass("select2-disabled");
-                this.highlight(index);
-                this.onSelect(data);
-            }
-        },
-
-        // abstract
-        getPlaceholder: function () {
-            return this.opts.element.attr("placeholder") ||
-                this.opts.element.attr("data-placeholder") || // jquery 1.4 compat
-                this.opts.element.data("placeholder") ||
-                this.opts.placeholder;
-        },
-
-        /**
-         * Get the desired width for the container element.  This is
-         * derived first from option `width` passed to select2, then
-         * the inline 'style' on the original element, and finally
-         * falls back to the jQuery calculated element width.
-         */
-        // abstract
-        initContainerWidth: function () {
-            function resolveContainerWidth() {
-                var style, attrs, matches, i, l;
-
-                if (this.opts.width === "off") {
-                    return null;
-                } else if (this.opts.width === "element"){
-                    return this.opts.element.outerWidth() === 0 ? 'auto' : this.opts.element.outerWidth() + 'px';
-                } else if (this.opts.width === "copy" || this.opts.width === "resolve") {
-                    // check if there is inline style on the element that contains width
-                    style = this.opts.element.attr('style');
-                    if (style !== undefined) {
-                        attrs = style.split(';');
-                        for (i = 0, l = attrs.length; i < l; i = i + 1) {
-                            matches = attrs[i].replace(/\s/g, '')
-                                .match(/width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/);
-                            if (matches !== null && matches.length >= 1)
-                                return matches[1];
-                        }
-                    }
-
-                    if (this.opts.width === "resolve") {
-                        // next check if css('width') can resolve a width that is percent based, this is sometimes possible
-                        // when attached to input type=hidden or elements hidden via css
-                        style = this.opts.element.css('width');
-                        if (style.indexOf("%") > 0) return style;
-
-                        // finally, fallback on the calculated width of the element
-                        return (this.opts.element.outerWidth() === 0 ? 'auto' : this.opts.element.outerWidth() + 'px');
-                    }
-
-                    return null;
-                } else if ($.isFunction(this.opts.width)) {
-                    return this.opts.width();
-                } else {
-                    return this.opts.width;
-               }
-            };
-
-            var width = resolveContainerWidth.call(this);
-            if (width !== null) {
-                this.container.attr("style", "width: "+width);
-            }
-        }
-    });
-
-    SingleSelect2 = clazz(AbstractSelect2, {
-
-        // single
-
-               createContainer: function () {
-            var container = $("<div></div>", {
-                "class": "select2-container"
-            }).html([
-                "    <a href='#' onclick='return false;' class='select2-choice'>",
-                "   <span></span><abbr class='select2-search-choice-close' style='display:none;'></abbr>",
-                "   <div><b></b></div>" ,
-                "</a>",
-                "    <div class='select2-drop select2-offscreen'>" ,
-                "   <div class='select2-search'>" ,
-                "       <input type='text' autocomplete='off' class='select2-input'/>" ,
-                "   </div>" ,
-                "   <ul class='select2-results'>" ,
-                "   </ul>" ,
-                "</div>"].join(""));
-            return container;
-        },
-
-        // single
-        opening: function () {
-            this.search.show();
-            this.parent.opening.apply(this, arguments);
-            this.dropdown.removeClass("select2-offscreen");
-        },
-
-        // single
-        close: function () {
-            if (!this.opened()) return;
-            console.log('closing...');
-            this.parent.close.apply(this, arguments);
-            this.dropdown.removeAttr("style").addClass("select2-offscreen").insertAfter(this.selection).show();
-        },
-
-        // single
-        focus: function () {
-            this.close();
-            this.selection.focus();
-        },
-
-        // single
-        isFocused: function () {
-            return this.selection[0] === document.activeElement;
-        },
-
-        // single
-        cancel: function () {
-            this.parent.cancel.apply(this, arguments);
-            this.selection.focus();
-        },
-
-        // single
-        initContainer: function () {
-
-            var selection,
-                container = this.container,
-                dropdown = this.dropdown,
-                clickingInside = false;
-
-            this.selection = selection = container.find(".select2-choice");
-
-            this.search.bind("keydown", this.bind(function (e) {
-                if (!this.enabled) return;
-
-                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
-                    // prevent the page from scrolling
-                    killEvent(e);
-                    return;
-                }
-
-                if (this.opened()) {
-                    switch (e.which) {
-                        case KEY.UP:
-                        case KEY.DOWN:
-                            this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
-                            killEvent(e);
-                            return;
-                        case KEY.TAB:
-                        case KEY.ENTER:
-                            this.selectHighlighted();
-                            killEvent(e);
-                            return;
-                        case KEY.ESC:
-                            this.cancel(e);
-                            killEvent(e);
-                            return;
-                    }
-                } else {
-
-                    if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
-                        return;
-                    }
-
-                    if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
-                        return;
-                    }
-
-                    this.open();
-
-                    if (e.which === KEY.ENTER) {
-                        // do not propagate the event otherwise we open, and propagate enter which closes
-                        return;
-                    }
-                }
-            }));
-
-            this.search.bind("focus", this.bind(function() {
-                this.selection.attr("tabIndex", "-1");
-            }));
-            this.search.bind("blur", this.bind(function() {
-                if (!this.opened()) this.container.removeClass("select2-container-active");
-                window.setTimeout(this.bind(function() { this.selection.attr("tabIndex", this.opts.element.attr("tabIndex")); }), 10);
-            }));
-
-            selection.bind("mousedown", this.bind(function (e) {
-                clickingInside = true;
-
-                if (this.opened()) {
-                    this.close();
-                    this.selection.focus();
-                } else if (this.enabled) {
-                    this.open();
-                }
-
-                clickingInside = false;
-            }));
-
-            dropdown.bind("mousedown", this.bind(function() { this.search.focus(); }));
-
-            selection.bind("focus", this.bind(function() {
-                this.container.addClass("select2-container-active");
-                // hide the search so the tab key does not focus on it
-                this.search.attr("tabIndex", "-1");
-            }));
-
-            selection.bind("blur", this.bind(function() {
-                if (!this.opened()) {
-                    this.container.removeClass("select2-container-active");
-                }
-                window.setTimeout(this.bind(function() { this.search.attr("tabIndex", this.opts.element.attr("tabIndex")); }), 10);
-            }));
-
-            selection.bind("keydown", this.bind(function(e) {
-                if (!this.enabled) return;
-
-                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
-                    // prevent the page from scrolling
-                    killEvent(e);
-                    return;
-                }
-
-                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
-                 || e.which === KEY.ESC) {
-                    return;
-                }
-
-                if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
-                    return;
-                }
-
-                if (e.which == KEY.DELETE) {
-                    if (this.opts.allowClear) {
-                        this.clear();
-                    }
-                    return;
-                }
-
-                this.open();
-
-                if (e.which === KEY.ENTER) {
-                    // do not propagate the event otherwise we open, and propagate enter which closes
-                    killEvent(e);
-                    return;
-                }
-
-                // do not set the search input value for non-alpha-numeric keys
-                // otherwise pressing down results in a '(' being set in the search field
-                if (e.which < 48 ) { // '0' == 48
-                    killEvent(e);
-                    return;
-                }
-
-                var keyWritten = String.fromCharCode(e.which).toLowerCase();
-
-                if (e.shiftKey) {
-                    keyWritten = keyWritten.toUpperCase();
-                }
-
-                // focus the field before calling val so the cursor ends up after the value instead of before
-                this.search.focus();
-                this.search.val(keyWritten);
-
-                // prevent event propagation so it doesnt replay on the now focussed search field and result in double key entry
-                killEvent(e);
-            }));
-
-            selection.delegate("abbr", "mousedown", this.bind(function (e) {
-                if (!this.enabled) return;
-                this.clear();
-                killEvent(e);
-                this.close();
-                this.triggerChange();
-                this.selection.focus();
-            }));
-
-            this.setPlaceholder();
-
-            this.search.bind("focus", this.bind(function() {
-                this.container.addClass("select2-container-active");
-            }));
-        },
-
-        // single
-        clear: function() {
-            this.opts.element.val("");
-            this.selection.find("span").empty();
-            this.selection.removeData("select2-data");
-            this.setPlaceholder();
-        },
-
-        /**
-         * Sets selection based on source element's value
-         */
-        // single
-        initSelection: function () {
-            var selected;
-            if (this.opts.element.val() === "") {
-                this.close();
-                this.setPlaceholder();
-            } else {
-                var self = this;
-                this.opts.initSelection.call(null, this.opts.element, function(selected){
-                    if (selected !== undefined && selected !== null) {
-                        self.updateSelection(selected);
-                        self.close();
-                        self.setPlaceholder();
-                    }
-                });
-            }
-        },
-
-        // single
-        prepareOpts: function () {
-            var opts = this.parent.prepareOpts.apply(this, arguments);
-
-            if (opts.element.get(0).tagName.toLowerCase() === "select") {
-                // install the selection initializer
-                opts.initSelection = function (element, callback) {
-                    var selected = element.find(":selected");
-                    // a single select box always has a value, no need to null check 'selected'
-                    if ($.isFunction(callback))
-                        callback({id: selected.attr("value"), text: selected.text()});
-                };
-            }
-
-            return opts;
-        },
-
-        // single
-        setPlaceholder: function () {
-            var placeholder = this.getPlaceholder();
-
-            if (this.opts.element.val() === "" && placeholder !== undefined) {
-
-                // check for a first blank option if attached to a select
-                if (this.select && this.select.find("option:first").text() !== "") return;
-
-                this.selection.find("span").html(this.opts.escapeMarkup(placeholder));
-
-                this.selection.addClass("select2-default");
-
-                this.selection.find("abbr").hide();
-            }
-        },
-
-        // single
-        postprocessResults: function (data, initial) {
-            var selected = 0, self = this, showSearchInput = true;
-
-            // find the selected element in the result list
-
-            this.results.find(".select2-result-selectable").each2(function (i, elm) {
-                if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) {
-                    selected = i;
-                    return false;
-                }
-            });
-
-            // and highlight it
-
-            this.highlight(selected);
-
-            // hide the search box if this is the first we got the results and there are a few of them
-
-            if (initial === true) {
-                showSearchInput = this.showSearchInput = countResults(data.results) >= this.opts.minimumResultsForSearch;
-                this.dropdown.find(".select2-search")[showSearchInput ? "removeClass" : "addClass"]("select2-search-hidden");
-
-                //add "select2-with-searchbox" to the container if search box is shown
-                $(this.dropdown, this.container)[showSearchInput ? "addClass" : "removeClass"]("select2-with-searchbox");
-            }
-
-        },
-
-        // single
-        onSelect: function (data) {
-            var old = this.opts.element.val();
-
-            this.opts.element.val(this.id(data));
-            this.updateSelection(data);
-            this.close();
-            this.selection.focus();
-
-            if (!equal(old, this.id(data))) { this.triggerChange(); }
-        },
-
-        // single
-        updateSelection: function (data) {
-
-            var container=this.selection.find("span"), formatted;
-
-            this.selection.data("select2-data", data);
-
-            container.empty();
-            formatted=this.opts.formatSelection(data, container);
-            if (formatted !== undefined) {
-                container.append(this.opts.escapeMarkup(formatted));
-            }
-
-            this.selection.removeClass("select2-default");
-
-            if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
-                this.selection.find("abbr").show();
-            }
-        },
-
-        // single
-        val: function () {
-            var val, data = null, self = this;
-
-            if (arguments.length === 0) {
-                return this.opts.element.val();
-            }
-
-            val = arguments[0];
-
-            if (this.select) {
-                this.select
-                    .val(val)
-                    .find(":selected").each2(function (i, elm) {
-                        data = {id: elm.attr("value"), text: elm.text()};
-                        return false;
-                    });
-                this.updateSelection(data);
-                this.setPlaceholder();
-            } else {
-                if (this.opts.initSelection === undefined) {
-                    throw new Error("cannot call val() if initSelection() is not defined");
-                }
-                // val is an id. !val is true for [undefined,null,'']
-                if (!val) {
-                    this.clear();
-                    return;
-                }
-                this.opts.element.val(val);
-                this.opts.initSelection(this.opts.element, function(data){
-                    self.opts.element.val(!data ? "" : self.id(data));
-                    self.updateSelection(data);
-                    self.setPlaceholder();
-                });
-            }
-        },
-
-        // single
-        clearSearch: function () {
-            this.search.val("");
-        },
-
-        // single
-        data: function(value) {
-            var data;
-
-            if (arguments.length === 0) {
-                data = this.selection.data("select2-data");
-                if (data == undefined) data = null;
-                return data;
-            } else {
-                if (!value || value === "") {
-                    this.clear();
-                } else {
-                    this.opts.element.val(!value ? "" : this.id(value));
-                    this.updateSelection(value);
-                }
-            }
-        }
-    });
-
-    MultiSelect2 = clazz(AbstractSelect2, {
-
-        // multi
-        createContainer: function () {
-            var container = $("<div></div>", {
-                "class": "select2-container select2-container-multi"
-            }).html([
-                "    <ul class='select2-choices'>",
-                //"<li class='select2-search-choice'><span>California</span><a href="javascript:void(0)" class="select2-search-choice-close"></a></li>" ,
-                "  <li class='select2-search-field'>" ,
-                "    <input type='text' autocomplete='off' class='select2-input'>" ,
-                "  </li>" ,
-                "</ul>" ,
-                "<div class='select2-drop select2-drop-multi' style='display:none;'>" ,
-                "   <ul class='select2-results'>" ,
-                "   </ul>" ,
-                "</div>"].join(""));
-                       return container;
-        },
-
-        // multi
-        prepareOpts: function () {
-            var opts = this.parent.prepareOpts.apply(this, arguments);
-
-            // TODO validate placeholder is a string if specified
-
-            if (opts.element.get(0).tagName.toLowerCase() === "select") {
-                // install sthe selection initializer
-                opts.initSelection = function (element,callback) {
-
-                    var data = [];
-                    element.find(":selected").each2(function (i, elm) {
-                        data.push({id: elm.attr("value"), text: elm.text()});
-                    });
-
-                    if ($.isFunction(callback))
-                        callback(data);
-                };
-            }
-
-            return opts;
-        },
-
-        // multi
-        initContainer: function () {
-
-            var selector = ".select2-choices", selection;
-
-            this.searchContainer = this.container.find(".select2-search-field");
-            this.selection = selection = this.container.find(selector);
-
-            this.search.bind("keydown", this.bind(function (e) {
-                if (!this.enabled) return;
-
-                if (e.which === KEY.BACKSPACE && this.search.val() === "") {
-                    this.close();
-
-                    var choices,
-                        selected = selection.find(".select2-search-choice-focus");
-                    if (selected.length > 0) {
-                        this.unselect(selected.first());
-                        this.search.width(10);
-                        killEvent(e);
-                        return;
-                    }
-
-                    choices = selection.find(".select2-search-choice");
-                    if (choices.length > 0) {
-                        choices.last().addClass("select2-search-choice-focus");
-                    }
-                } else {
-                    selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
-                }
-
-                if (this.opened()) {
-                    switch (e.which) {
-                    case KEY.UP:
-                    case KEY.DOWN:
-                        this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
-                        killEvent(e);
-                        return;
-                    case KEY.ENTER:
-                    case KEY.TAB:
-                        this.selectHighlighted();
-                        killEvent(e);
-                        return;
-                    case KEY.ESC:
-                        this.cancel(e);
-                        killEvent(e);
-                        return;
-                    }
-                }
-
-                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
-                 || e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
-                    return;
-                }
-
-                if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
-                    return;
-                }
-
-                this.open();
-
-                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
-                    // prevent the page from scrolling
-                    killEvent(e);
-                }
-            }));
-
-            this.search.bind("keyup", this.bind(this.resizeSearch));
-
-            this.search.bind("blur", this.bind(function(e) {
-                this.container.removeClass("select2-container-active");
-                this.search.removeClass("select2-focused");
-                this.clearSearch();
-                e.stopImmediatePropagation();
-            }));
-
-            this.container.delegate(selector, "mousedown", this.bind(function (e) {
-                if (!this.enabled) return;
-                if ($(e.target).closest(".select2-search-choice").length > 0) {
-                    // clicked inside a select2 search choice, do not open
-                    return;
-                }
-                this.clearPlaceholder();
-                this.open();
-                this.focusSearch();
-                e.preventDefault();
-            }));
-
-            this.container.delegate(selector, "focus", this.bind(function () {
-                if (!this.enabled) return;
-                this.container.addClass("select2-container-active");
-                this.dropdown.addClass("select2-drop-active");
-                this.clearPlaceholder();
-            }));
-
-            // set the placeholder if necessary
-            this.clearSearch();
-        },
-
-        // multi
-        enable: function() {
-            if (this.enabled) return;
-
-            this.parent.enable.apply(this, arguments);
-
-            this.search.removeAttr("disabled");
-        },
-
-        // multi
-        disable: function() {
-            if (!this.enabled) return;
-
-            this.parent.disable.apply(this, arguments);
-
-            this.search.attr("disabled", true);
-        },
-
-        // multi
-        initSelection: function () {
-            var data;
-            if (this.opts.element.val() === "") {
-                this.updateSelection([]);
-                this.close();
-                // set the placeholder if necessary
-                this.clearSearch();
-            }
-            if (this.select || this.opts.element.val() !== "") {
-                var self = this;
-                this.opts.initSelection.call(null, this.opts.element, function(data){
-                    if (data !== undefined && data !== null) {
-                        self.updateSelection(data);
-                        self.close();
-                        // set the placeholder if necessary
-                        self.clearSearch();
-                    }
-                });
-            }
-        },
-
-        // multi
-        clearSearch: function () {
-            var placeholder = this.getPlaceholder();
-
-            if (placeholder !== undefined  && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) {
-                this.search.val(placeholder).addClass("select2-default");
-                // stretch the search box to full width of the container so as much of the placeholder is visible as possible
-                this.resizeSearch();
-            } else {
-                // we set this to " " instead of "" and later clear it on focus() because there is a firefox bug
-                // that does not properly render the caret when the field starts out blank
-                this.search.val(" ").width(10);
-            }
-        },
-
-        // multi
-        clearPlaceholder: function () {
-            if (this.search.hasClass("select2-default")) {
-                this.search.val("").removeClass("select2-default");
-            } else {
-                // work around for the space character we set to avoid firefox caret bug
-                if (this.search.val() === " ") this.search.val("");
-            }
-        },
-
-        // multi
-        opening: function () {
-            this.parent.opening.apply(this, arguments);
-
-            this.clearPlaceholder();
-                       this.resizeSearch();
-            this.focusSearch();
-        },
-
-        // multi
-        close: function () {
-            if (!this.opened()) return;
-            this.parent.close.apply(this, arguments);
-        },
-
-        // multi
-        focus: function () {
-            this.close();
-            this.search.focus();
-        },
-
-        // multi
-        isFocused: function () {
-            return this.search.hasClass("select2-focused");
-        },
-
-        // multi
-        updateSelection: function (data) {
-            var ids = [], filtered = [], self = this;
-
-            // filter out duplicates
-            $(data).each(function () {
-                if (indexOf(self.id(this), ids) < 0) {
-                    ids.push(self.id(this));
-                    filtered.push(this);
-                }
-            });
-            data = filtered;
-
-            this.selection.find(".select2-search-choice").remove();
-            $(data).each(function () {
-                self.addSelectedChoice(this);
-            });
-            self.postprocessResults();
-        },
-
-        tokenize: function() {
-            var input = this.search.val();
-            input = this.opts.tokenizer(input, this.data(), this.bind(this.onSelect), this.opts);
-            if (input != null && input != undefined) {
-                this.search.val(input);
-                if (input.length > 0) {
-                    this.open();
-                }
-            }
-
-        },
-
-        // multi
-        onSelect: function (data) {
-            this.addSelectedChoice(data);
-            if (this.select) { this.postprocessResults(); }
-
-            if (this.opts.closeOnSelect) {
-                this.close();
-                this.search.width(10);
-            } else {
-                if (this.countSelectableResults()>0) {
-                    this.search.width(10);
-                    this.resizeSearch();
-                    this.positionDropdown();
-                } else {
-                    // if nothing left to select close
-                    this.close();
-                }
-            }
-
-            // since its not possible to select an element that has already been
-            // added we do not need to check if this is a new element before firing change
-            this.triggerChange({ added: data });
-
-            this.focusSearch();
-        },
-
-        // multi
-        cancel: function () {
-            this.close();
-            this.focusSearch();
-        },
-
-        // multi
-        addSelectedChoice: function (data) {
-            var choice=$(
-                    "<li class='select2-search-choice'>" +
-                    "    <div></div>" +
-                    "    <a href='#' onclick='return false;' class='select2-search-choice-close' tabindex='-1'></a>" +
-                    "</li>"),
-                id = this.id(data),
-                val = this.getVal(),
-                formatted;
-
-            formatted=this.opts.formatSelection(data, choice);
-            choice.find("div").replaceWith("<div>"+this.opts.escapeMarkup(formatted)+"</div>");
-            choice.find(".select2-search-choice-close")
-                .bind("mousedown", killEvent)
-                .bind("click dblclick", this.bind(function (e) {
-                if (!this.enabled) return;
-
-                $(e.target).closest(".select2-search-choice").fadeOut('fast', this.bind(function(){
-                    this.unselect($(e.target));
-                    this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
-                    this.close();
-                    this.focusSearch();
-                })).dequeue();
-                killEvent(e);
-            })).bind("focus", this.bind(function () {
-                if (!this.enabled) return;
-                this.container.addClass("select2-container-active");
-                this.dropdown.addClass("select2-drop-active");
-            }));
-
-            choice.data("select2-data", data);
-            choice.insertBefore(this.searchContainer);
-
-            val.push(id);
-            this.setVal(val);
-        },
-
-        // multi
-        unselect: function (selected) {
-            var val = this.getVal(),
-                data,
-                index;
-
-            selected = selected.closest(".select2-search-choice");
-
-            if (selected.length === 0) {
-                throw "Invalid argument: " + selected + ". Must be .select2-search-choice";
-            }
-
-            data = selected.data("select2-data");
-
-            index = indexOf(this.id(data), val);
-
-            if (index >= 0) {
-                val.splice(index, 1);
-                this.setVal(val);
-                if (this.select) this.postprocessResults();
-            }
-            selected.remove();
-            this.triggerChange({ removed: data });
-        },
-
-        // multi
-        postprocessResults: function () {
-            var val = this.getVal(),
-                choices = this.results.find(".select2-result-selectable"),
-                compound = this.results.find(".select2-result-with-children"),
-                self = this;
-
-            choices.each2(function (i, choice) {
-                var id = self.id(choice.data("select2-data"));
-                if (indexOf(id, val) >= 0) {
-                    choice.addClass("select2-disabled").removeClass("select2-result-selectable");
-                } else {
-                    choice.removeClass("select2-disabled").addClass("select2-result-selectable");
-                }
-            });
-
-            compound.each2(function(i, e) {
-                if (e.find(".select2-result-selectable").length==0) {
-                    e.addClass("select2-disabled");
-                } else {
-                    e.removeClass("select2-disabled");
-                }
-            });
-
-            choices.each2(function (i, choice) {
-                if (!choice.hasClass("select2-disabled") && choice.hasClass("select2-result-selectable")) {
-                    self.highlight(0);
-                    return false;
-                }
-            });
-
-        },
-
-        // multi
-        resizeSearch: function () {
-
-            var minimumWidth, left, maxWidth, containerLeft, searchWidth,
-               sideBorderPadding = getSideBorderPadding(this.search);
-
-            minimumWidth = measureTextWidth(this.search) + 10;
-
-            left = this.search.offset().left;
-
-            maxWidth = this.selection.width();
-            containerLeft = this.selection.offset().left;
-
-            searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding;
-            if (searchWidth < minimumWidth) {
-                searchWidth = maxWidth - sideBorderPadding;
-            }
-
-            if (searchWidth < 40) {
-                searchWidth = maxWidth - sideBorderPadding;
-            }
-            this.search.width(searchWidth);
-        },
-
-        // multi
-        getVal: function () {
-            var val;
-            if (this.select) {
-                val = this.select.val();
-                return val === null ? [] : val;
-            } else {
-                val = this.opts.element.val();
-                return splitVal(val, this.opts.separator);
-            }
-        },
-
-        // multi
-        setVal: function (val) {
-            var unique;
-            if (this.select) {
-                this.select.val(val);
-            } else {
-                unique = [];
-                // filter out duplicates
-                $(val).each(function () {
-                    if (indexOf(this, unique) < 0) unique.push(this);
-                });
-                this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator));
-            }
-        },
-
-        // multi
-        val: function () {
-            var val, data = [], self=this;
-
-            if (arguments.length === 0) {
-                return this.getVal();
-            }
-
-            val = arguments[0];
-
-            if (!val) {
-                this.opts.element.val("");
-                this.updateSelection([]);
-                this.clearSearch();
-                return;
-            }
-
-            // val is a list of ids
-            this.setVal(val);
-
-            if (this.select) {
-                this.select.find(":selected").each(function () {
-                    data.push({id: $(this).attr("value"), text: $(this).text()});
-                });
-                this.updateSelection(data);
-            } else {
-                if (this.opts.initSelection === undefined) {
-                    throw new Error("val() cannot be called if initSelection() is not defined")
-                }
-
-                this.opts.initSelection(this.opts.element, function(data){
-                    var ids=$(data).map(self.id);
-                    self.setVal(ids);
-                    self.updateSelection(data);
-                    self.clearSearch();
-                });
-            }
-            this.clearSearch();
-        },
-
-        // multi
-        onSortStart: function() {
-            if (this.select) {
-                throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");
-            }
-
-            // collapse search field into 0 width so its container can be collapsed as well
-            this.search.width(0);
-            // hide the container
-            this.searchContainer.hide();
-        },
-
-        // multi
-        onSortEnd:function() {
-
-            var val=[], self=this;
-
-            // show search and move it to the end of the list
-            this.searchContainer.show();
-            // make sure the search container is the last item in the list
-            this.searchContainer.appendTo(this.searchContainer.parent());
-            // since we collapsed the width in dragStarted, we resize it here
-            this.resizeSearch();
-
-            // update selection
-
-            this.selection.find(".select2-search-choice").each(function() {
-                val.push(self.opts.id($(this).data("select2-data")));
-            });
-            this.setVal(val);
-            this.triggerChange();
-        },
-
-        // multi
-        data: function(values) {
-            var self=this, ids;
-            if (arguments.length === 0) {
-                 return this.selection
-                     .find(".select2-search-choice")
-                     .map(function() { return $(this).data("select2-data"); })
-                     .get();
-            } else {
-                if (!values) { values = []; }
-                ids = $.map(values, function(e) { return self.opts.id(e)});
-                this.setVal(ids);
-                this.updateSelection(values);
-                this.clearSearch();
-            }
-        }
-    });
-
-    $.fn.select2 = function () {
-
-        var args = Array.prototype.slice.call(arguments, 0),
-            opts,
-            select2,
-            value, multiple, allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "onSortStart", "onSortEnd", "enable", "disable", "positionDropdown", "data"];
-
-        this.each(function () {
-            if (args.length === 0 || typeof(args[0]) === "object") {
-                opts = args.length === 0 ? {} : $.extend({}, args[0]);
-                opts.element = $(this);
-
-                if (opts.element.get(0).tagName.toLowerCase() === "select") {
-                    multiple = opts.element.attr("multiple");
-                } else {
-                    multiple = opts.multiple || false;
-                    if ("tags" in opts) {opts.multiple = multiple = true;}
-                }
-
-                select2 = multiple ? new MultiSelect2() : new SingleSelect2();
-                select2.init(opts);
-            } else if (typeof(args[0]) === "string") {
-
-                if (indexOf(args[0], allowedMethods) < 0) {
-                    throw "Unknown method: " + args[0];
-                }
-
-                value = undefined;
-                select2 = $(this).data("select2");
-                if (select2 === undefined) return;
-                if (args[0] === "container") {
-                    value=select2.container;
-                } else {
-                    value = select2[args[0]].apply(select2, args.slice(1));
-                }
-                if (value !== undefined) {return false;}
-            } else {
-                throw "Invalid arguments to select2 plugin: " + args;
-            }
-        });
-        return (value === undefined) ? this : value;
-    };
-
-    // plugin defaults, accessible to users
-    $.fn.select2.defaults = {
-        width: "copy",
-        closeOnSelect: true,
-        openOnEnter: true,
-        containerCss: {},
-        dropdownCss: {},
-        containerCssClass: "",
-        dropdownCssClass: "",
-        formatResult: function(result, container, query) {
-            var markup=[];
-            markMatch(result.text, query.term, markup);
-            return markup.join("");
-        },
-        formatSelection: function (data, container) {
-            return data ? data.text : undefined;
-        },
-        formatResultCssClass: function(data) {return undefined;},
-        formatNoMatches: function () { return "No matches found"; },
-        formatInputTooShort: function (input, min) { return "Please enter " + (min - input.length) + " more characters"; },
-        formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); },
-        formatLoadMore: function (pageNumber) { return "Loading more results..."; },
-        formatSearching: function () { return "Searching..."; },
-        minimumResultsForSearch: 0,
-        minimumInputLength: 0,
-        maximumSelectionSize: 0,
-        id: function (e) { return e.id; },
-        matcher: function(term, text) {
-            return text.toUpperCase().indexOf(term.toUpperCase()) >= 0;
-        },
-        separator: ",",
-        tokenSeparators: [],
-        tokenizer: defaultTokenizer,
-        escapeMarkup: function (markup) {
-            if (markup && typeof(markup) === "string") {
-                return markup.replace(/&/g, "&amp;");
-            }
-            return markup;
-        },
-        blurOnChange: false
-    };
-
-    // exports
-    window.Select2 = {
-        query: {
-            ajax: ajax,
-            local: local,
-            tags: tags
-        }, util: {
-            debounce: debounce,
-            markMatch: markMatch
-        }, "class": {
-            "abstract": AbstractSelect2,
-            "single": SingleSelect2,
-            "multi": MultiSelect2
-        }
-    };
-
-}(jQuery));