import React from 'react';
-import GlobalNav from './global-nav';
+import GlobalNav from './global/global-nav';
+import ComponentNav from './component/component-nav';
export default {
start(options) {
window.requestMessages().done(() => {
this.renderGlobalNav(options);
+ options.space === 'component' && this.renderComponentNav(options);
});
},
renderGlobalNav(options) {
const el = document.getElementById('global-navigation');
React.render(<GlobalNav {...options}/>, el);
+ },
+
+ renderComponentNav(options) {
+ const el = document.getElementById('context-navigation');
+ React.render(<ComponentNav {...options}/>, el);
}
};
--- /dev/null
+import React from 'react';
+import QualifierIcon from 'components/shared/qualifier-icon';
+
+export default React.createClass({
+ render() {
+ if (!this.props.breadcrumbs) {
+ return null;
+ }
+ const items = this.props.breadcrumbs.map((item, index) => {
+ const url = `${window.baseUrl}/dashboard/index?id=${encodeURIComponent(item.key)}`;
+ return (
+ <li key={index}>
+ <a href={url}>
+ <QualifierIcon qualifier={item.qualifier}/> {item.name}
+ </a>
+ </li>
+ );
+ });
+ return (
+ <ul className="nav navbar-nav nav-crumbs">{items}</ul>
+ );
+ }
+});
--- /dev/null
+import React from 'react';
+import Favorite from 'components/shared/favorite';
+
+export default React.createClass({
+ render() {
+ if (!this.props.canBeFavorite) {
+ return null;
+ }
+ return (
+ <div className="navbar-context-favorite">
+ <Favorite component={this.props.component} favorite={this.props.favorite}/>
+ </div>
+ );
+ }
+});
--- /dev/null
+import React from 'react';
+import DashboardNameMixin from '../dashboard-name-mixin';
+
+const SETTINGS_URLS = [
+ '/project/settings', '/project/profile', '/project/qualitygate', '/manual_measures/index',
+ '/action_plans/index', '/project/links', '/project_roles/index', '/project/history', '/project/key',
+ '/project/deletion'
+];
+
+const MORE_URLS = ['/dashboards', '/dashboard', '/plugins/resource'];
+
+export default React.createClass({
+ mixins: [DashboardNameMixin],
+
+ activeLink(url) {
+ return window.location.pathname.indexOf(window.baseUrl + url) === 0 ? 'active' : null;
+ },
+
+ renderLink(url, title, highlightUrl = url) {
+ let fullUrl = window.baseUrl + url;
+ return (
+ <li key={highlightUrl} className={this.activeLink(highlightUrl)}>
+ <a href={fullUrl}>{title}</a>
+ </li>
+ );
+ },
+
+ renderOverviewLink() {
+ const url = `/overview/index?id=${encodeURIComponent(this.props.component.key)}`;
+ return this.renderLink(url, window.t('overview.page'), '/overview');
+ },
+
+ renderComponentsLink() {
+ const url = `/components/index?id=${encodeURIComponent(this.props.component.key)}`;
+ return this.renderLink(url, window.t('components.page'), '/components');
+ },
+
+ renderComponentIssuesLink() {
+ const url = `/component_issues/index?id=${encodeURIComponent(this.props.component.key)}`;
+ return this.renderLink(url, window.t('issues.page'), '/component_issues');
+ },
+
+ renderAdministration() {
+ if (!this.props.conf.showSettings) {
+ return null;
+ }
+ let isSettingsActive = SETTINGS_URLS.some(url => {
+ return window.location.href.indexOf(url) !== -1;
+ }),
+ className = 'dropdown' + (isSettingsActive ? ' active' : '');
+ return (
+ <li className={className}>
+ <a className="dropdown-toggle navbar-admin-link" data-toggle="dropdown" href="#">
+ {window.t('layout.settings')} <i className="icon-dropdown"/></a>
+ <ul className="dropdown-menu">
+ {this.renderSettingsLink()}
+ {this.renderProfilesLink()}
+ {this.renderQualityGatesLink()}
+ {this.renderCustomMeasuresLink()}
+ {this.renderActionPlansLink()}
+ {this.renderLinksLink()}
+ {this.renderPermissionsLink()}
+ {this.renderHistoryLink()}
+ {this.renderUpdateKeyLink()}
+ {this.renderDeletionLink()}
+ {this.renderExtensions()}
+ </ul>
+ </li>
+ );
+ },
+
+ renderSettingsLink() {
+ const url = `/project/settings?id=${encodeURIComponent(this.props.component.key)}`;
+ return this.renderLink(url, window.t('project_settings.page'), '/project/settings');
+ },
+
+ renderProfilesLink() {
+ if (!this.props.conf.showQualityProfiles) {
+ return null;
+ }
+ const url = `/project/profile?id=${encodeURIComponent(this.props.component.key)}`;
+ return this.renderLink(url, window.t('project_quality_profiles.page'), '/project/profile');
+ },
+
+ renderQualityGatesLink() {
+ if (!this.props.conf.showQualityGates) {
+ return null;
+ }
+ const url = `/project/qualitygate?id=${encodeURIComponent(this.props.component.key)}`;
+ return this.renderLink(url, window.t('project_quality_gate.page'), '/project/qualitygate');
+ },
+
+ renderCustomMeasuresLink() {
+ if (!this.props.conf.showManualMeasures) {
+ return null;
+ }
+ const url = `/custom_measures?id=${encodeURIComponent(this.props.component.key)}`;
+ return this.renderLink(url, window.t('custom_measures.page'), '/custom_measures');
+ },
+
+ renderActionPlansLink() {
+ if (!this.props.conf.showActionPlans) {
+ return null;
+ }
+ const url = `/action_plans?id=${encodeURIComponent(this.props.component.key)}`;
+ return this.renderLink(url, window.t('action_plans.page'), '/action_plans');
+ },
+
+ renderLinksLink() {
+ if (!this.props.conf.showLinks) {
+ return null;
+ }
+ const url = `/project/links?id=${encodeURIComponent(this.props.component.key)}`;
+ return this.renderLink(url, window.t('project_links.page'), '/project/links');
+ },
+
+ renderPermissionsLink() {
+ if (!this.props.conf.showPermissions) {
+ return null;
+ }
+ const url = `/project_roles?id=${encodeURIComponent(this.props.component.key)}`;
+ return this.renderLink(url, window.t('permissions.page'), '/project_roles');
+ },
+
+ renderHistoryLink() {
+ if (!this.props.conf.showHistory) {
+ return null;
+ }
+ const url = `/project/history?id=${encodeURIComponent(this.props.component.key)}`;
+ return this.renderLink(url, window.t('project_history.page'), '/project/history');
+ },
+
+ renderUpdateKeyLink() {
+ if (!this.props.conf.showUpdateKey) {
+ return null;
+ }
+ const url = `/project/key?id=${encodeURIComponent(this.props.component.key)}`;
+ return this.renderLink(url, window.t('update_key.page'), '/project/key');
+ },
+
+ renderDeletionLink() {
+ if (!this.props.conf.showDeletion) {
+ return null;
+ }
+ const url = `/project/deletion?id=${encodeURIComponent(this.props.component.key)}`;
+ return this.renderLink(url, window.t('deletion.page'), '/project/deletion');
+ },
+
+ renderExtensions() {
+ let extensions = this.props.conf.extensions || [];
+ return extensions.map(e => {
+ return this.renderLink(e.url, e.name, e.url);
+ });
+ },
+
+ renderMore() {
+ let isActive = MORE_URLS.some(url => {
+ return window.location.href.indexOf(url) !== -1;
+ }),
+ className = 'dropdown' + (isActive ? ' active' : '');
+ return (
+ <li className={className}>
+ <a className="dropdown-toggle" data-toggle="dropdown" href="#">
+ {window.t('more')} <i className="icon-dropdown"></i>
+ </a>
+ <ul className="dropdown-menu">
+ {this.renderDashboards()}
+ {this.renderDashboardManagementLink()}
+ {this.renderTools()}
+ </ul>
+ </li>
+ );
+ },
+
+ renderDashboards() {
+ let dashboards = (this.props.component.dashboards || []).map(d => {
+ let url = `${window.baseUrl}/dashboard?id=${encodeURIComponent(this.props.component.key)}&did=${d.key}`;
+ let name = this.getLocalizedDashboardName(d.name);
+ return this.renderLink(url, name);
+ });
+ return [<li key="0" className="dropdown-header">{window.t('layout.dashboards')}</li>].concat(dashboards);
+ },
+
+ renderDashboardManagementLink() {
+ if (!window.SS.user) {
+ return null;
+ }
+ let url = `${window.baseUrl}/dashboards?resource=${encodeURIComponent(this.props.component.key)}`;
+ let name = window.t('dashboard.manage_dashboards');
+ return [
+ <li key="dashboard-divider" className="small-divider"></li>,
+ this.renderLink(url, name, '/dashboards')
+ ];
+ },
+
+ renderTools() {
+ let component = this.props.component;
+ if (!component.isComparable && !_.size(component.extensions)) {
+ return null;
+ }
+ let tools = [
+ <li key="tools-divider" className="divider"></li>,
+ <li key="tools" className="dropdown-header">Tools</li>
+ ];
+ if (component.isComparable) {
+ let compareUrl = `/comparison/index?resource=${component.key}`;
+ tools.push(this.renderLink(compareUrl, window.t('comparison.page')));
+ }
+ (component.extensions || []).forEach(e => {
+ tools.push(this.renderLink(e.url, e.name));
+ });
+ return tools;
+ },
+
+ render() {
+ return (
+ <ul className="nav navbar-nav nav-tabs">
+ {this.renderOverviewLink()}
+ {this.renderComponentsLink()}
+ {this.renderComponentIssuesLink()}
+ {this.renderAdministration()}
+ {this.renderMore()}
+ </ul>
+ );
+ }
+});
--- /dev/null
+import React from 'react';
+
+export default React.createClass({
+ render() {
+ const version = this.props.version ? `Version ${this.props.version}` : null;
+ const snapshotDate = this.props.snapshotDate ? moment(this.props.snapshotDate).format('LLL') : null;
+ return (
+ <div className="navbar-right navbar-context-meta">
+ {version} {snapshotDate}
+ </div>
+ );
+ }
+});
--- /dev/null
+import React from 'react';
+import ComponentNavFavorite from './component-nav-favorite';
+import ComponentNavBreadcrumbs from './component-nav-breadcrumbs';
+import ComponentNavMeta from './component-nav-meta';
+import ComponentNavMenu from './component-nav-menu';
+
+let $ = jQuery;
+
+export default React.createClass({
+ getInitialState() {
+ return { component: {}, conf: {} };
+ },
+
+ componentDidMount() {
+ this.loadDetails();
+ },
+
+ loadDetails() {
+ const url = `${window.baseUrl}/api/navigation/component`;
+ const data = { componentKey: this.props.componentKey };
+ $.get(url, data).done(r => {
+ this.setState({
+ component: r,
+ conf: r.configuration || {}
+ });
+ });
+ },
+
+ render() {
+ return (
+ <div className="container">
+ <ComponentNavFavorite
+ component={this.state.component.key}
+ favorite={this.state.component.isFavorite}
+ canBeFavorite={this.state.component.canBeFavorite}/>
+
+ <ComponentNavBreadcrumbs
+ breadcrumbs={this.state.component.breadcrumbs}/>
+
+ <ComponentNavMeta
+ version={this.state.component.version}
+ snapshotDate={this.state.component.snapshotDate}/>
+
+ <ComponentNavMenu
+ component={this.state.component}
+ conf={this.state.conf}/>
+ </div>
+ );
+ }
+});
--- /dev/null
+export default {
+ getLocalizedDashboardName(baseName) {
+ var l10nKey = 'dashboard.' + baseName + '.name';
+ var l10nLabel = window.t(l10nKey);
+ if (l10nLabel !== l10nKey) {
+ return l10nLabel;
+ } else {
+ return baseName;
+ }
+ }
+};
+++ /dev/null
-import React from 'react';
-
-export default React.createClass({
- renderLogo() {
- const url = this.props.logoUrl || `${window.baseUrl}/images/logo.svg`;
- const width = this.props.logoWidth || 30;
- const title = window.t('layout.sonar.slogan');
- return <img src={url} width={width} height="30" alt={title} title={title}/>
- },
-
- render() {
- const homeUrl = window.baseUrl + '/';
- const homeLinkClassName = 'navbar-brand' + (this.props.logoUrl ? ' navbar-brand-custom' : '');
- return (
- <div className="navbar-header">
- <a className={homeLinkClassName} href={homeUrl}>{this.renderLogo()}</a>
- </div>
- );
- }
-});
+++ /dev/null
-import React from 'react';
-
-export default React.createClass({
- getDefaultProps: function () {
- return { globalDashboards: [], globalPages: [] };
- },
-
- activeLink(url) {
- return window.location.pathname.indexOf(window.baseUrl + url) === 0 ? 'active' : null;
- },
-
- getLocalizedDashboardName(baseName) {
- var l10nKey = 'dashboard.' + baseName + '.name';
- var l10nLabel = window.t(l10nKey);
- if (l10nLabel !== l10nKey) {
- return l10nLabel;
- } else {
- return baseName;
- }
- },
-
- renderDashboardLink(dashboard) {
- const url = `${window.baseUrl}/dashboard/index?did=${encodeURIComponent(dashboard.key)}`;
- const name = this.getLocalizedDashboardName(dashboard.name);
- return (
- <li key={dashboard.name}>
- <a href={url}>{name}</a>
- </li>
- );
- },
-
- renderDashboardsManagementLink() {
- const url = `${window.baseUrl}/dashboards`;
- return (
- <li>
- <a href={url}>{window.t('dashboard.manage_dashboards')}</a>
- </li>
- );
- },
-
- renderDashboards() {
- const dashboards = this.props.globalDashboards.map(this.renderDashboardLink);
- const canManageDashboards = !!window.SS.user;
- return (
- <li className="dropdown">
- <a className="dropdown-toggle" data-toggle="dropdown" href="#">
- {window.t('layout.dashboards')} <span className="icon-dropdown"/>
- </a>
- <ul className="dropdown-menu">
- {dashboards}
- {canManageDashboards ? <li className="divider"/> : null}
- {canManageDashboards ? this.renderDashboardsManagementLink() : null}
- </ul>
- </li>
- );
- },
-
- renderIssuesLink() {
- const url = `${window.baseUrl}/issues/search`;
- return (
- <li className={this.activeLink('/issues')}>
- <a href={url}>{window.t('issues.page')}</a>
- </li>
- );
- },
-
- renderMeasuresLink() {
- const url = `${window.baseUrl}/measures/search?qualifiers[]=TRK`;
- return (
- <li className={this.activeLink('/measures')}>
- <a href={url}>{window.t('layout.measures')}</a>
- </li>
- );
- },
-
- renderRulesLink() {
- const url = `${window.baseUrl}/coding_rules`;
- return (
- <li className={this.activeLink('/coding_rules')}>
- <a href={url}>{window.t('coding_rules.page')}</a>
- </li>
- );
- },
-
- renderProfilesLink() {
- const url = `${window.baseUrl}/profiles`;
- return (
- <li className={this.activeLink('/profiles')}>
- <a href={url}>{window.t('quality_profiles.page')}</a>
- </li>
- );
- },
-
- renderQualityGatesLink() {
- const url = `${window.baseUrl}/quality_gates`;
- return (
- <li className={this.activeLink('/quality_gates')}>
- <a href={url}>{window.t('quality_gates.page')}</a>
- </li>
- );
- },
-
- renderAdministrationLink() {
- if (!window.SS.isUserAdmin) {
- return null;
- }
- const url = `${window.baseUrl}/settings`;
- return (
- <li className={this.activeLink('/settings')}>
- <a className="navbar-admin-link" href={url}>{window.t('layout.settings')}</a>
- </li>
- );
- },
-
- renderComparisonLink() {
- const url = `${window.baseUrl}/comparison`;
- return (
- <li className={this.activeLink('/comparison')}>
- <a href={url}>{window.t('comparison_global.page')}</a>
- </li>
- );
- },
-
- renderGlobalPageLink(globalPage, index) {
- const url = window.baseUrl + globalPage.url;
- return (
- <li key={index}>
- <a href={url}>{globalPage.name}</a>
- </li>
- );
- },
-
- renderMore() {
- const globalPages = this.props.globalPages.map(this.renderGlobalPageLink);
- return (
- <li className="dropdown">
- <a className="dropdown-toggle" data-toggle="dropdown" href="#">
- {window.t('more')} <span className="icon-dropdown"/>
- </a>
- <ul className="dropdown-menu">
- {this.renderComparisonLink()}
- {globalPages}
- </ul>
- </li>
- );
- },
-
- render() {
- return (
- <ul className="nav navbar-nav">
- {this.renderDashboards()}
- {this.renderIssuesLink()}
- {this.renderMeasuresLink()}
- {this.renderRulesLink()}
- {this.renderProfilesLink()}
- {this.renderQualityGatesLink()}
- {this.renderAdministrationLink()}
- {this.renderMore()}
- </ul>
- );
- }
-});
+++ /dev/null
-import React from 'react';
-import SearchView from './search-view';
-
-let $ = jQuery;
-
-function contains (root, node) {
- while (node) {
- if (node === root) {
- return true;
- }
- node = node.parentNode;
- }
- return false;
-}
-
-export default React.createClass({
- getInitialState() {
- return { open: false };
- },
-
- componentDidMount() {
- key('s', () => {
- this.openSearch();
- return false;
- });
- },
-
- componentWillUnmount() {
- this.closeSearch();
- key.unbind('s');
- },
-
- openSearch() {
- window.addEventListener('click', this.onClickOutside);
- this.setState({ open: true }, this.renderSearchView);
- },
-
- closeSearch() {
- window.removeEventListener('click', this.onClickOutside);
- this.resetSearchView();
- this.setState({ open: false });
- },
-
- renderSearchView() {
- let searchContainer = React.findDOMNode(this.refs.container);
- this.searchView = new SearchView({
- model: new Backbone.Model(this.props),
- hide: this.closeSearch
- });
- this.searchView.render().$el.appendTo(searchContainer);
- },
-
- resetSearchView() {
- this.searchView && this.searchView.destroy();
- },
-
- onClick(e) {
- e.preventDefault();
- this.state.open ? this.closeSearch() : this.openSearch();
- },
-
- onClickOutside(e) {
- if (!contains(React.findDOMNode(this.refs.dropdown), e.target)) {
- this.closeSearch();
- }
- },
-
- render() {
- const dropdownClassName = 'dropdown' + (this.state.open ? ' open' : '');
- return (
- <li ref="dropdown" className={dropdownClassName}>
- <a className="navbar-search-dropdown" href="#" onClick={this.onClick}>
- <i className="icon-search navbar-icon"/> <i className="icon-dropdown"/>
- </a>
- <div ref="container" className="dropdown-menu dropdown-menu-right"></div>
- </li>
- );
- }
-});
+++ /dev/null
-import React from 'react';
-import Avatar from 'components/shared/avatar';
-
-export default React.createClass({
- renderAuthenticated() {
- return (
- <li className="dropdown">
- <a className="dropdown-toggle" data-toggle="dropdown" href="#">
- <Avatar email={window.SS.userEmail} size={20}/>
- {window.SS.userName} <i className="icon-dropdown"/>
- </a>
- <ul className="dropdown-menu dropdown-menu-right">
- <li>
- <a href={`${window.baseUrl}/account/index`}>{window.t('layout.user_panel.my_profile')}</a>
- </li>
- <li>
- <a onClick={this.handleLogout} href="#">{window.t('layout.logout')}</a>
- </li>
- </ul>
- </li>
- );
- },
-
- renderAnonymous() {
- return (
- <li>
- <a onClick={this.handleLogin}>{window.t('layout.login')}</a>
- </li>
- );
- },
-
- handleLogin(e) {
- e.preventDefault();
- const returnTo = window.location.pathname + window.location.search;
- const loginUrl = `${window.baseUrl}/sessions/new?return_to=${encodeURIComponent(returnTo)}${window.location.hash}`;
- window.location = loginUrl;
- },
-
- handleLogout(e) {
- e.preventDefault();
- if (window.sonarRecentHistory) {
- window.sonarRecentHistory.clear();
- }
- const logoutUrl = `${window.baseUrl}/sessions/logout`;
- window.location = logoutUrl;
- },
-
- render() {
- const isUserAuthenticated = !!window.SS.user;
- return isUserAuthenticated ? this.renderAuthenticated() : this.renderAnonymous();
- }
-});
+++ /dev/null
-import React from 'react';
-import GlobalNavBranding from './global-nav-branding';
-import GlobalNavMenu from './global-nav-menu';
-import GlobalNavUser from './global-nav-user';
-import GlobalNavSearch from './global-nav-search';
-import ShortcutsHelpView from './shortcuts-help-view';
-
-let $ = jQuery;
-
-export default React.createClass({
- getInitialState() {
- return this.props;
- },
-
- componentDidMount() {
- this.loadGlobalNavDetails();
- },
-
- loadGlobalNavDetails() {
- $.get(`${window.baseUrl}/api/navigation/global`).done(r => {
- this.setState(r);
- });
- },
-
- openHelp(e) {
- e.preventDefault();
- new ShortcutsHelpView().render();
- },
-
- render() {
- return (
- <div className="container">
- <GlobalNavBranding {...this.state}/>
-
- <GlobalNavMenu {...this.state}/>
-
- <ul className="nav navbar-nav navbar-right">
- <GlobalNavUser {...this.state}/>
- <GlobalNavSearch {...this.state}/>
- <li>
- <a onClick={this.openHelp} href="#">
- <i className="icon-help navbar-icon"/>
- </a>
- </li>
- </ul>
- </div>
- );
- }
-});
--- /dev/null
+import React from 'react';
+
+export default React.createClass({
+ renderLogo() {
+ const url = this.props.logoUrl || `${window.baseUrl}/images/logo.svg`;
+ const width = this.props.logoWidth || 30;
+ const title = window.t('layout.sonar.slogan');
+ return <img src={url} width={width} height="30" alt={title} title={title}/>
+ },
+
+ render() {
+ const homeUrl = window.baseUrl + '/';
+ const homeLinkClassName = 'navbar-brand' + (this.props.logoUrl ? ' navbar-brand-custom' : '');
+ return (
+ <div className="navbar-header">
+ <a className={homeLinkClassName} href={homeUrl}>{this.renderLogo()}</a>
+ </div>
+ );
+ }
+});
--- /dev/null
+import React from 'react';
+import DashboardNameMixin from '../dashboard-name-mixin';
+
+export default React.createClass({
+ mixins: [DashboardNameMixin],
+
+ getDefaultProps: function () {
+ return { globalDashboards: [], globalPages: [] };
+ },
+
+ activeLink(url) {
+ return window.location.pathname.indexOf(window.baseUrl + url) === 0 ? 'active' : null;
+ },
+
+ renderDashboardLink(dashboard) {
+ const url = `${window.baseUrl}/dashboard/index?did=${encodeURIComponent(dashboard.key)}`;
+ const name = this.getLocalizedDashboardName(dashboard.name);
+ return (
+ <li key={dashboard.name}>
+ <a href={url}>{name}</a>
+ </li>
+ );
+ },
+
+ renderDashboardsManagementLink() {
+ const url = `${window.baseUrl}/dashboards`;
+ return (
+ <li>
+ <a href={url}>{window.t('dashboard.manage_dashboards')}</a>
+ </li>
+ );
+ },
+
+ renderDashboards() {
+ const dashboards = this.props.globalDashboards.map(this.renderDashboardLink);
+ const canManageDashboards = !!window.SS.user;
+ return (
+ <li className="dropdown">
+ <a className="dropdown-toggle" data-toggle="dropdown" href="#">
+ {window.t('layout.dashboards')} <span className="icon-dropdown"/>
+ </a>
+ <ul className="dropdown-menu">
+ {dashboards}
+ {canManageDashboards ? <li className="divider"/> : null}
+ {canManageDashboards ? this.renderDashboardsManagementLink() : null}
+ </ul>
+ </li>
+ );
+ },
+
+ renderIssuesLink() {
+ const url = `${window.baseUrl}/issues/search`;
+ return (
+ <li className={this.activeLink('/issues')}>
+ <a href={url}>{window.t('issues.page')}</a>
+ </li>
+ );
+ },
+
+ renderMeasuresLink() {
+ const url = `${window.baseUrl}/measures/search?qualifiers[]=TRK`;
+ return (
+ <li className={this.activeLink('/measures')}>
+ <a href={url}>{window.t('layout.measures')}</a>
+ </li>
+ );
+ },
+
+ renderRulesLink() {
+ const url = `${window.baseUrl}/coding_rules`;
+ return (
+ <li className={this.activeLink('/coding_rules')}>
+ <a href={url}>{window.t('coding_rules.page')}</a>
+ </li>
+ );
+ },
+
+ renderProfilesLink() {
+ const url = `${window.baseUrl}/profiles`;
+ return (
+ <li className={this.activeLink('/profiles')}>
+ <a href={url}>{window.t('quality_profiles.page')}</a>
+ </li>
+ );
+ },
+
+ renderQualityGatesLink() {
+ const url = `${window.baseUrl}/quality_gates`;
+ return (
+ <li className={this.activeLink('/quality_gates')}>
+ <a href={url}>{window.t('quality_gates.page')}</a>
+ </li>
+ );
+ },
+
+ renderAdministrationLink() {
+ if (!window.SS.isUserAdmin) {
+ return null;
+ }
+ const url = `${window.baseUrl}/settings`;
+ return (
+ <li className={this.activeLink('/settings')}>
+ <a className="navbar-admin-link" href={url}>{window.t('layout.settings')}</a>
+ </li>
+ );
+ },
+
+ renderComparisonLink() {
+ const url = `${window.baseUrl}/comparison`;
+ return (
+ <li className={this.activeLink('/comparison')}>
+ <a href={url}>{window.t('comparison_global.page')}</a>
+ </li>
+ );
+ },
+
+ renderGlobalPageLink(globalPage, index) {
+ const url = window.baseUrl + globalPage.url;
+ return (
+ <li key={index}>
+ <a href={url}>{globalPage.name}</a>
+ </li>
+ );
+ },
+
+ renderMore() {
+ const globalPages = this.props.globalPages.map(this.renderGlobalPageLink);
+ return (
+ <li className="dropdown">
+ <a className="dropdown-toggle" data-toggle="dropdown" href="#">
+ {window.t('more')} <span className="icon-dropdown"/>
+ </a>
+ <ul className="dropdown-menu">
+ {this.renderComparisonLink()}
+ {globalPages}
+ </ul>
+ </li>
+ );
+ },
+
+ render() {
+ return (
+ <ul className="nav navbar-nav">
+ {this.renderDashboards()}
+ {this.renderIssuesLink()}
+ {this.renderMeasuresLink()}
+ {this.renderRulesLink()}
+ {this.renderProfilesLink()}
+ {this.renderQualityGatesLink()}
+ {this.renderAdministrationLink()}
+ {this.renderMore()}
+ </ul>
+ );
+ }
+});
--- /dev/null
+import React from 'react';
+import SearchView from './search-view';
+
+let $ = jQuery;
+
+function contains (root, node) {
+ while (node) {
+ if (node === root) {
+ return true;
+ }
+ node = node.parentNode;
+ }
+ return false;
+}
+
+export default React.createClass({
+ getInitialState() {
+ return { open: false };
+ },
+
+ componentDidMount() {
+ key('s', () => {
+ this.openSearch();
+ return false;
+ });
+ },
+
+ componentWillUnmount() {
+ this.closeSearch();
+ key.unbind('s');
+ },
+
+ openSearch() {
+ window.addEventListener('click', this.onClickOutside);
+ this.setState({ open: true }, this.renderSearchView);
+ },
+
+ closeSearch() {
+ window.removeEventListener('click', this.onClickOutside);
+ this.resetSearchView();
+ this.setState({ open: false });
+ },
+
+ renderSearchView() {
+ let searchContainer = React.findDOMNode(this.refs.container);
+ this.searchView = new SearchView({
+ model: new Backbone.Model(this.props),
+ hide: this.closeSearch
+ });
+ this.searchView.render().$el.appendTo(searchContainer);
+ },
+
+ resetSearchView() {
+ this.searchView && this.searchView.destroy();
+ },
+
+ onClick(e) {
+ e.preventDefault();
+ this.state.open ? this.closeSearch() : this.openSearch();
+ },
+
+ onClickOutside(e) {
+ if (!contains(React.findDOMNode(this.refs.dropdown), e.target)) {
+ this.closeSearch();
+ }
+ },
+
+ render() {
+ const dropdownClassName = 'dropdown' + (this.state.open ? ' open' : '');
+ return (
+ <li ref="dropdown" className={dropdownClassName}>
+ <a className="navbar-search-dropdown" href="#" onClick={this.onClick}>
+ <i className="icon-search navbar-icon"/> <i className="icon-dropdown"/>
+ </a>
+ <div ref="container" className="dropdown-menu dropdown-menu-right"></div>
+ </li>
+ );
+ }
+});
--- /dev/null
+import React from 'react';
+import Avatar from 'components/shared/avatar';
+
+export default React.createClass({
+ renderAuthenticated() {
+ return (
+ <li className="dropdown">
+ <a className="dropdown-toggle" data-toggle="dropdown" href="#">
+ <Avatar email={window.SS.userEmail} size={20}/>
+ {window.SS.userName} <i className="icon-dropdown"/>
+ </a>
+ <ul className="dropdown-menu dropdown-menu-right">
+ <li>
+ <a href={`${window.baseUrl}/account/index`}>{window.t('layout.user_panel.my_profile')}</a>
+ </li>
+ <li>
+ <a onClick={this.handleLogout} href="#">{window.t('layout.logout')}</a>
+ </li>
+ </ul>
+ </li>
+ );
+ },
+
+ renderAnonymous() {
+ return (
+ <li>
+ <a onClick={this.handleLogin}>{window.t('layout.login')}</a>
+ </li>
+ );
+ },
+
+ handleLogin(e) {
+ e.preventDefault();
+ const returnTo = window.location.pathname + window.location.search;
+ const loginUrl = `${window.baseUrl}/sessions/new?return_to=${encodeURIComponent(returnTo)}${window.location.hash}`;
+ window.location = loginUrl;
+ },
+
+ handleLogout(e) {
+ e.preventDefault();
+ if (window.sonarRecentHistory) {
+ window.sonarRecentHistory.clear();
+ }
+ const logoutUrl = `${window.baseUrl}/sessions/logout`;
+ window.location = logoutUrl;
+ },
+
+ render() {
+ const isUserAuthenticated = !!window.SS.user;
+ return isUserAuthenticated ? this.renderAuthenticated() : this.renderAnonymous();
+ }
+});
--- /dev/null
+import React from 'react';
+import GlobalNavBranding from './global-nav-branding';
+import GlobalNavMenu from './global-nav-menu';
+import GlobalNavUser from './global-nav-user';
+import GlobalNavSearch from './global-nav-search';
+import ShortcutsHelpView from './shortcuts-help-view';
+
+let $ = jQuery;
+
+export default React.createClass({
+ getInitialState() {
+ return this.props;
+ },
+
+ componentDidMount() {
+ this.loadGlobalNavDetails();
+ },
+
+ loadGlobalNavDetails() {
+ $.get(`${window.baseUrl}/api/navigation/global`).done(r => {
+ this.setState(r);
+ });
+ },
+
+ openHelp(e) {
+ e.preventDefault();
+ new ShortcutsHelpView().render();
+ },
+
+ render() {
+ return (
+ <div className="container">
+ <GlobalNavBranding {...this.state}/>
+
+ <GlobalNavMenu {...this.state}/>
+
+ <ul className="nav navbar-nav navbar-right">
+ <GlobalNavUser {...this.state}/>
+ <GlobalNavSearch {...this.state}/>
+ <li>
+ <a onClick={this.openHelp} href="#">
+ <i className="icon-help navbar-icon"/>
+ </a>
+ </li>
+ </ul>
+ </div>
+ );
+ }
+});
--- /dev/null
+define([
+ 'components/common/selectable-collection-view',
+ '../templates'
+], function (SelectableCollectionView) {
+
+ var $ = jQuery,
+
+ SearchItemView = Marionette.ItemView.extend({
+ tagName: 'li',
+ template: Templates['nav-search-item'],
+
+ select: function () {
+ this.$el.addClass('active');
+ },
+
+ deselect: function () {
+ this.$el.removeClass('active');
+ },
+
+ submit: function () {
+ this.$('a')[0].click();
+ },
+
+ serializeData: function () {
+ return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
+ index: this.options.index
+ });
+ }
+ }),
+
+ SearchEmptyView = Marionette.ItemView.extend({
+ tagName: 'li',
+ template: Templates['nav-search-empty']
+ }),
+
+ SearchResultsView = SelectableCollectionView.extend({
+ className: 'menu',
+ tagName: 'ul',
+ childView: SearchItemView,
+ emptyView: SearchEmptyView
+ });
+
+ return Marionette.LayoutView.extend({
+ className: 'navbar-search',
+ tagName: 'form',
+ template: Templates['nav-search'],
+
+ regions: {
+ resultsRegion: '.js-search-results'
+ },
+
+ events: {
+ 'submit': 'onSubmit',
+ 'keydown .js-search-input': 'onKeyDown',
+ 'keyup .js-search-input': 'debouncedOnKeyUp'
+ },
+
+ initialize: function () {
+ var that = this;
+ this.results = new Backbone.Collection();
+ this.favorite = [];
+ if (window.SS.user) {
+ this.fetchFavorite().always(function () {
+ that.resetResultsToDefault();
+ });
+ } else {
+ this.resetResultsToDefault();
+ }
+ this.resultsView = new SearchResultsView({ collection: this.results });
+ this.debouncedOnKeyUp = _.debounce(this.onKeyUp, 400);
+ this._bufferedValue = '';
+ },
+
+ onRender: function () {
+ var that = this;
+ this.resultsRegion.show(this.resultsView);
+ setTimeout(function () {
+ that.$('.js-search-input').focus();
+ }, 0);
+ },
+
+ onKeyDown: function (e) {
+ if (e.keyCode === 38) {
+ this.resultsView.selectPrev();
+ return false;
+ }
+ if (e.keyCode === 40) {
+ this.resultsView.selectNext();
+ return false;
+ }
+ if (e.keyCode === 13) {
+ this.resultsView.submitCurrent();
+ return false;
+ }
+ if (e.keyCode === 27) {
+ this.options.hide();
+ return false;
+ }
+ },
+
+ onKeyUp: function () {
+ var value = this.$('.js-search-input').val();
+ if (value === this._bufferedValue) {
+ return;
+ }
+ this._bufferedValue = this.$('.js-search-input').val();
+ if (this.searchRequest != null) {
+ this.searchRequest.abort();
+ }
+ this.searchRequest = this.search(value);
+ },
+
+ onSubmit: function () {
+ return false;
+ },
+
+ fetchFavorite: function () {
+ var that = this;
+ return $.get(baseUrl + '/api/favourites').done(function (r) {
+ that.favorite = r.map(function (f) {
+ var isFile = ['FIL', 'UTS'].indexOf(f.qualifier) !== -1;
+ return {
+ url: baseUrl + '/dashboard/index?id=' + encodeURIComponent(f.key) + dashboardParameters(true),
+ name: isFile ? window.collapsedDirFromPath(f.lname) + window.fileFromPath(f.lname) : f.name,
+ icon: 'favorite'
+ };
+ });
+ that.favorite = _.sortBy(that.favorite, 'name');
+ });
+ },
+
+ resetResultsToDefault: function () {
+ var recentHistory = JSON.parse(localStorage.getItem('sonar_recent_history')),
+ history = (recentHistory || []).map(function (historyItem, index) {
+ return {
+ url: baseUrl + '/dashboard/index?id=' + encodeURIComponent(historyItem.key) + dashboardParameters(true),
+ name: historyItem.name,
+ q: historyItem.icon,
+ extra: index === 0 ? t('browsed_recently') : null
+ };
+ }),
+ favorite = _.first(this.favorite, 6).map(function (f, index) {
+ return _.extend(f, { extra: index === 0 ? t('favorite') : null });
+ }),
+ qualifiers = this.model.get('qualifiers').map(function (q, index) {
+ return {
+ url: baseUrl + '/all_projects?qualifier=' + encodeURIComponent(q),
+ name: t('qualifiers.all', q),
+ extra: index === 0 ? '' : null
+ };
+ });
+ this.results.reset([].concat(history, favorite, qualifiers));
+ },
+
+ search: function (q) {
+ if (q.length < 2) {
+ this.resetResultsToDefault();
+ return;
+ }
+ var that = this,
+ url = baseUrl + '/api/components/suggestions',
+ options = { s: q };
+ return $.get(url, options).done(function (r) {
+ var collection = [];
+ r.results.forEach(function (domain) {
+ domain.items.forEach(function (item, index) {
+ collection.push(_.extend(item, {
+ q: domain.q,
+ extra: index === 0 ? domain.name : null,
+ url: baseUrl + '/dashboard/index?id=' + encodeURIComponent(item.key) + dashboardParameters(true)
+ }));
+ });
+ });
+ that.results.reset([].concat(
+ that.getNavigationFindings(q),
+ that.getGlobalDashboardFindings(q),
+ that.getFavoriteFindings(q),
+ collection
+ ));
+ });
+ },
+
+ getNavigationFindings: function (q) {
+ var DEFAULT_ITEMS = [
+ { name: t('issues.page'), url: baseUrl + '/issues/search' },
+ { name: t('layout.measures'), url: baseUrl + '/measures/search?qualifiers[]=TRK' },
+ { name: t('coding_rules.page'), url: baseUrl + '/coding_rules' },
+ { name: t('quality_profiles.page'), url: baseUrl + '/profiles' },
+ { name: t('quality_gates.page'), url: baseUrl + '/quality_gates' },
+ { name: t('comparison_global.page'), url: baseUrl + '/comparison' }
+ ],
+ customItems = [];
+ if (window.SS.isUserAdmin) {
+ customItems.push({ name: t('layout.settings'), url: baseUrl + '/settings' });
+ }
+ var findings = [].concat(DEFAULT_ITEMS, customItems).filter(function (f) {
+ return f.name.match(new RegExp(q, 'i'));
+ });
+ if (findings.length > 0) {
+ findings[0].extra = t('navigation');
+ }
+ return _.first(findings, 6);
+ },
+
+ getGlobalDashboardFindings: function (q) {
+ var dashboards = this.model.get('globalDashboards') || [],
+ items = dashboards.map(function (d) {
+ return { name: d.name, url: baseUrl + '/dashboard/index?did=' + encodeURIComponent(d.key) };
+ });
+ var findings = items.filter(function (f) {
+ return f.name.match(new RegExp(q, 'i'));
+ });
+ if (findings.length > 0) {
+ findings[0].extra = t('dashboard.global_dashboards');
+ }
+ return _.first(findings, 6);
+ },
+
+ getFavoriteFindings: function (q) {
+ var findings = this.favorite.filter(function (f) {
+ return f.name.match(new RegExp(q, 'i'));
+ });
+ if (findings.length > 0) {
+ findings[0].extra = t('favorite');
+ }
+ return _.first(findings, 6);
+ }
+ });
+
+});
--- /dev/null
+define([
+ 'components/common/modals',
+ '../templates'
+], function (ModalView) {
+
+ return ModalView.extend({
+ className: 'modal modal-large',
+ template: Templates['nav-shortcuts-help']
+ });
+
+});
+++ /dev/null
-define([
- 'components/common/selectable-collection-view',
- './templates'
-], function (SelectableCollectionView) {
-
- var $ = jQuery,
-
- SearchItemView = Marionette.ItemView.extend({
- tagName: 'li',
- template: Templates['nav-search-item'],
-
- select: function () {
- this.$el.addClass('active');
- },
-
- deselect: function () {
- this.$el.removeClass('active');
- },
-
- submit: function () {
- this.$('a')[0].click();
- },
-
- serializeData: function () {
- return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
- index: this.options.index
- });
- }
- }),
-
- SearchEmptyView = Marionette.ItemView.extend({
- tagName: 'li',
- template: Templates['nav-search-empty']
- }),
-
- SearchResultsView = SelectableCollectionView.extend({
- className: 'menu',
- tagName: 'ul',
- childView: SearchItemView,
- emptyView: SearchEmptyView
- });
-
- return Marionette.LayoutView.extend({
- className: 'navbar-search',
- tagName: 'form',
- template: Templates['nav-search'],
-
- regions: {
- resultsRegion: '.js-search-results'
- },
-
- events: {
- 'submit': 'onSubmit',
- 'keydown .js-search-input': 'onKeyDown',
- 'keyup .js-search-input': 'debouncedOnKeyUp'
- },
-
- initialize: function () {
- var that = this;
- this.results = new Backbone.Collection();
- this.favorite = [];
- if (window.SS.user) {
- this.fetchFavorite().always(function () {
- that.resetResultsToDefault();
- });
- } else {
- this.resetResultsToDefault();
- }
- this.resultsView = new SearchResultsView({ collection: this.results });
- this.debouncedOnKeyUp = _.debounce(this.onKeyUp, 400);
- this._bufferedValue = '';
- },
-
- onRender: function () {
- var that = this;
- this.resultsRegion.show(this.resultsView);
- setTimeout(function () {
- that.$('.js-search-input').focus();
- }, 0);
- },
-
- onKeyDown: function (e) {
- if (e.keyCode === 38) {
- this.resultsView.selectPrev();
- return false;
- }
- if (e.keyCode === 40) {
- this.resultsView.selectNext();
- return false;
- }
- if (e.keyCode === 13) {
- this.resultsView.submitCurrent();
- return false;
- }
- if (e.keyCode === 27) {
- this.options.hide();
- return false;
- }
- },
-
- onKeyUp: function () {
- var value = this.$('.js-search-input').val();
- if (value === this._bufferedValue) {
- return;
- }
- this._bufferedValue = this.$('.js-search-input').val();
- if (this.searchRequest != null) {
- this.searchRequest.abort();
- }
- this.searchRequest = this.search(value);
- },
-
- onSubmit: function () {
- return false;
- },
-
- fetchFavorite: function () {
- var that = this;
- return $.get(baseUrl + '/api/favourites').done(function (r) {
- that.favorite = r.map(function (f) {
- var isFile = ['FIL', 'UTS'].indexOf(f.qualifier) !== -1;
- return {
- url: baseUrl + '/dashboard/index?id=' + encodeURIComponent(f.key) + dashboardParameters(true),
- name: isFile ? window.collapsedDirFromPath(f.lname) + window.fileFromPath(f.lname) : f.name,
- icon: 'favorite'
- };
- });
- that.favorite = _.sortBy(that.favorite, 'name');
- });
- },
-
- resetResultsToDefault: function () {
- var recentHistory = JSON.parse(localStorage.getItem('sonar_recent_history')),
- history = (recentHistory || []).map(function (historyItem, index) {
- return {
- url: baseUrl + '/dashboard/index?id=' + encodeURIComponent(historyItem.key) + dashboardParameters(true),
- name: historyItem.name,
- q: historyItem.icon,
- extra: index === 0 ? t('browsed_recently') : null
- };
- }),
- favorite = _.first(this.favorite, 6).map(function (f, index) {
- return _.extend(f, { extra: index === 0 ? t('favorite') : null });
- }),
- qualifiers = this.model.get('qualifiers').map(function (q, index) {
- return {
- url: baseUrl + '/all_projects?qualifier=' + encodeURIComponent(q),
- name: t('qualifiers.all', q),
- extra: index === 0 ? '' : null
- };
- });
- this.results.reset([].concat(history, favorite, qualifiers));
- },
-
- search: function (q) {
- if (q.length < 2) {
- this.resetResultsToDefault();
- return;
- }
- var that = this,
- url = baseUrl + '/api/components/suggestions',
- options = { s: q };
- return $.get(url, options).done(function (r) {
- var collection = [];
- r.results.forEach(function (domain) {
- domain.items.forEach(function (item, index) {
- collection.push(_.extend(item, {
- q: domain.q,
- extra: index === 0 ? domain.name : null,
- url: baseUrl + '/dashboard/index?id=' + encodeURIComponent(item.key) + dashboardParameters(true)
- }));
- });
- });
- that.results.reset([].concat(
- that.getNavigationFindings(q),
- that.getGlobalDashboardFindings(q),
- that.getFavoriteFindings(q),
- collection
- ));
- });
- },
-
- getNavigationFindings: function (q) {
- var DEFAULT_ITEMS = [
- { name: t('issues.page'), url: baseUrl + '/issues/search' },
- { name: t('layout.measures'), url: baseUrl + '/measures/search?qualifiers[]=TRK' },
- { name: t('coding_rules.page'), url: baseUrl + '/coding_rules' },
- { name: t('quality_profiles.page'), url: baseUrl + '/profiles' },
- { name: t('quality_gates.page'), url: baseUrl + '/quality_gates' },
- { name: t('comparison_global.page'), url: baseUrl + '/comparison' }
- ],
- customItems = [];
- if (window.SS.isUserAdmin) {
- customItems.push({ name: t('layout.settings'), url: baseUrl + '/settings' });
- }
- var findings = [].concat(DEFAULT_ITEMS, customItems).filter(function (f) {
- return f.name.match(new RegExp(q, 'i'));
- });
- if (findings.length > 0) {
- findings[0].extra = t('navigation');
- }
- return _.first(findings, 6);
- },
-
- getGlobalDashboardFindings: function (q) {
- var dashboards = this.model.get('globalDashboards') || [],
- items = dashboards.map(function (d) {
- return { name: d.name, url: baseUrl + '/dashboard/index?did=' + encodeURIComponent(d.key) };
- });
- var findings = items.filter(function (f) {
- return f.name.match(new RegExp(q, 'i'));
- });
- if (findings.length > 0) {
- findings[0].extra = t('dashboard.global_dashboards');
- }
- return _.first(findings, 6);
- },
-
- getFavoriteFindings: function (q) {
- var findings = this.favorite.filter(function (f) {
- return f.name.match(new RegExp(q, 'i'));
- });
- if (findings.length > 0) {
- findings[0].extra = t('favorite');
- }
- return _.first(findings, 6);
- }
- });
-
-});
+++ /dev/null
-define([
- 'components/common/modals',
- './templates'
-], function (ModalView) {
-
- return ModalView.extend({
- className: 'modal modal-large',
- template: Templates['nav-shortcuts-help']
- });
-
-});
--- /dev/null
+import React from 'react';
+
+let $ = jQuery;
+
+export default React.createClass({
+ propTypes: {
+ component: React.PropTypes.string.isRequired,
+ favorite: React.PropTypes.bool.isRequired
+ },
+
+ getInitialState() {
+ return { favorite: this.props.favorite };
+ },
+
+ toggleFavorite(e) {
+ e.preventDefault();
+ this.state.favorite ? this.removeFavorite() : this.addFavorite();
+ },
+
+ addFavorite() {
+ const url = `${window.baseUrl}/api/favourites`;
+ const data = { key: this.props.component };
+ $.ajax({ type: 'POST', url, data }).done(() => this.setState({ favorite: true }));
+ },
+
+ removeFavorite() {
+ const url = `${window.baseUrl}/api/favourites/${encodeURIComponent(this.props.component)}`;
+ $.ajax({ type: 'DELETE', url }).done(() => this.setState({ favorite: false }));
+ },
+
+ renderSVG() {
+ return (
+ <svg width="16" height="16" style={{ fillRule: 'evenodd', clipRule: 'evenodd', strokeLinejoin: 'round', strokeMiterlimit: 1.41421 }}>
+ <path d="M15.4275,5.77678C15.4275,5.90773 15.3501,6.05059 15.1953,6.20536L11.9542,9.36608L12.7221,13.8304C12.728,13.872 12.731,13.9316 12.731,14.0089C12.731,14.1339 12.6998,14.2396 12.6373,14.3259C12.5748,14.4122 12.484,14.4554 12.3649,14.4554C12.2518,14.4554 12.1328,14.4197 12.0078,14.3482L7.99888,12.2411L3.98995,14.3482C3.85901,14.4197 3.73996,14.4554 3.63281,14.4554C3.50781,14.4554 3.41406,14.4122 3.35156,14.3259C3.28906,14.2396 3.25781,14.1339 3.25781,14.0089C3.25781,13.9732 3.26377,13.9137 3.27567,13.8304L4.04353,9.36608L0.793531,6.20536C0.644719,6.04464 0.570313,5.90178 0.570313,5.77678C0.570313,5.55654 0.736979,5.41964 1.07031,5.36606L5.55245,4.71428L7.56138,0.651781C7.67447,0.407729 7.8203,0.285703 7.99888,0.285703C8.17745,0.285703 8.32328,0.407729 8.43638,0.651781L10.4453,4.71428L14.9274,5.36606C15.2608,5.41964 15.4274,5.55654 15.4274,5.77678L15.4275,5.77678Z"
+ style={{ fillRule: 'nonzero' }}/>
+ </svg>
+ )
+ },
+
+ render() {
+ const className = this.state.favorite ? 'icon-star icon-star-favorite' : 'icon-star';
+ return <a onClick={this.toggleFavorite} className={className} href="#">{this.renderSVG()}</a>;
+ }
+});
--- /dev/null
+import React from 'react';
+
+export default React.createClass({
+ render() {
+ if (!this.props.qualifier) {
+ return null;
+ }
+ var className = 'icon-qualifier-' + this.props.qualifier.toLowerCase();
+ return <i className={className}/>;
+ }
+});
.square(16px);
background-size: 16px 16px;
background: no-repeat center center;
+ .trans !important;
}
.icon-favorite {
background-image: url('data:image/svg+xml,%3Csvg%20width%3D%2216%22%20height%3D%2216%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20stroke-linejoin%3D%22round%22%20stroke-miterlimit%3D%221.414%22%3E%3Cpath%20d%3D%22M15.428%205.777c0%20.13-.078.274-.233.428l-3.24%203.16.767%204.465c.006.042.01.102.01.18%200%20.124-.032.23-.095.316-.062.086-.153.13-.272.13-.113%200-.232-.036-.357-.108l-4.01-2.107L3.99%2014.35c-.13.072-.25.107-.357.107-.125%200-.22-.043-.28-.13-.064-.085-.095-.19-.095-.316%200-.037.006-.096.018-.18l.768-4.464-3.25-3.16C.644%206.045.57%205.9.57%205.775c0-.22.167-.356.5-.41l4.482-.652L7.562.652c.112-.244.258-.366.437-.366.177%200%20.323.122.436.366l2.01%204.062%204.48.652c.335.054.5.19.5.41h.002z%22%20fill%3D%22%23F90%22%20fill-rule%3D%22nonzero%22%2F%3E%3C%2Fsvg%3E');
+ .rotate(72deg);
}
.icon-not-favorite {
background-image: url('data:image/svg+xml,%3Csvg%20width%3D%2216%22%20height%3D%2216%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20stroke-linejoin%3D%22round%22%20stroke-miterlimit%3D%221.414%22%3E%3Cpath%20d%3D%22M15.428%205.777c0%20.13-.078.274-.233.428l-3.24%203.16.767%204.465c.006.042.01.102.01.18%200%20.124-.032.23-.095.316-.062.086-.153.13-.272.13-.113%200-.232-.036-.357-.108l-4.01-2.107L3.99%2014.35c-.13.072-.25.107-.357.107-.125%200-.22-.043-.28-.13-.064-.085-.095-.19-.095-.316%200-.037.006-.096.018-.18l.768-4.464-3.25-3.16C.644%206.045.57%205.9.57%205.775c0-.22.167-.356.5-.41l4.482-.652L7.562.652c.112-.244.258-.366.437-.366.177%200%20.323.122.436.366l2.01%204.062%204.48.652c.335.054.5.19.5.41h.002z%22%20fill%3D%22%23CDCDCD%22%20fill-rule%3D%22nonzero%22%2F%3E%3C%2Fsvg%3E');
}
+.icon-star {
+ .trans !important;
+}
+
+.icon-star path {
+ stroke: #777;
+ stroke-width: sqrt(2);
+ stroke-opacity: 1;
+ fill-opacity: 0;
+ .trans;
+}
+
+.icon-star-favorite {
+ animation: spin .6s forwards;
+}
+
+.icon-star-favorite path {
+ fill: rgb(255, 153, 0);
+ stroke-opacity: 0;
+ fill-opacity: 1;
+}
+
+@keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(144deg);
+ }
+}
+
.icon-help:before {
content: "\f059";
color: @blue;
'test/unit/application.spec',
'test/unit/issue.spec',
'test/unit/overview/card.spec',
- 'test/unit/code-with-issue-locations-helper.spec'
+ 'test/unit/code-with-issue-locations-helper.spec',
+ 'test/unit/nav/component/component-nav-breadcrumbs.spec'
],
functionalSuites: [
loaderOptions: {
paths: {
- 'react': 'build/js/libs/third-party/react-with-addons'
+ 'react': '../../build/js/libs/third-party/react-with-addons'
+ },
+ map: {
+ '*': {
+ 'components/shared/qualifier-icon': '../../build/js/components/shared/qualifier-icon'
+ }
}
}
};
--- /dev/null
+define(function (require) {
+ var bdd = require('intern!bdd');
+ var assert = require('intern/chai!assert');
+
+ var React = require('react');
+ var TestUtils = React.addons.TestUtils;
+
+ var ComponentNavBreadcrumbs = require('build/js/apps/nav/component/component-nav-breadcrumbs');
+
+ bdd.describe('ComponentNavBreadcrumbs', function () {
+ bdd.it('should not render unless `props.breadcrumbs`', function () {
+ var result = React.renderToStaticMarkup(React.createElement(ComponentNavBreadcrumbs, null));
+ assert.equal(result, '<noscript></noscript>');
+ });
+
+ bdd.it('should not render breadcrumbs with one element', function () {
+ var breadcrumbs = [
+ { key: 'my-project', name: 'My Project', qualifier: 'TRK' }
+ ];
+ var result = TestUtils.renderIntoDocument(
+ React.createElement(ComponentNavBreadcrumbs, { breadcrumbs: breadcrumbs })
+ );
+ assert.equal(TestUtils.scryRenderedDOMComponentsWithTag(result, 'li').length, 1);
+ assert.equal(TestUtils.scryRenderedDOMComponentsWithTag(result, 'a').length, 1);
+ });
+ });
+});