James Moger
2014-04-21 856f3fc2a8365c141d1418d3cfff502be233c104
Overhaul menu item classes and add AdminMenuExtension point
2 files added
11 files modified
656 ■■■■ changed files
src/main/java/com/gitblit/extensions/AdminMenuExtension.java 40 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/Menu.java 302 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties 1 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/PageRegistration.java 150 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/ActivityPage.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/DashboardPage.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/ProjectPage.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/ProjectsPage.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/RootPage.java 66 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/UserPage.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/DropDownMenu.java 33 ●●●● patch | view | raw | blame | history
src/site/plugins_extensions.mkd 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/extensions/AdminMenuExtension.java
New file
@@ -0,0 +1,40 @@
/*
 * Copyright 2014 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License 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.
 */
package com.gitblit.extensions;
import java.util.List;
import ro.fortsoft.pf4j.ExtensionPoint;
import com.gitblit.models.Menu.MenuItem;
import com.gitblit.models.UserModel;
/**
 * Extension point to contribute administration menu items.
 *
 * @author James Moger
 * @since 1.6.0
 *
 */
public abstract class AdminMenuExtension implements ExtensionPoint {
    /**
     * @param user
     * @since 1.6.0
     * @return a list of menu items
     */
    public abstract List<MenuItem> getMenuItems(UserModel user);
}
src/main/java/com/gitblit/models/Menu.java
New file
@@ -0,0 +1,302 @@
package com.gitblit.models;
import java.io.Serializable;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.WebPage;
import com.gitblit.utils.StringUtils;
public class Menu {
    /**
     * A MenuItem for a drop down menu.
     *
     * @author James Moger
     * @since 1.6.0
     */
    public abstract static class MenuItem implements Serializable {
        private static final long serialVersionUID = 1L;
        final String displayText;
        MenuItem(String displayText) {
            this.displayText = displayText;
        }
        @Override
        public int hashCode() {
            return displayText.hashCode();
        }
        @Override
        public boolean equals(Object o) {
            if (o instanceof MenuItem) {
                return hashCode() == o.hashCode();
            }
            return false;
        }
        @Override
        public String toString() {
            return displayText;
        }
    }
    /**
     * A divider for the menu.
     *
     * @since 1.6.0
     */
    public static class MenuDivider extends MenuItem {
        private static final long serialVersionUID = 1L;
        public MenuDivider() {
            super("");
        }
    }
    /**
     * A MenuItem for setting a parameter of the current url.
     *
     * @author James Moger
     *
     */
    public static class ParameterMenuItem extends MenuItem {
        private static final long serialVersionUID = 1L;
        final PageParameters parameters;
        final String parameter;
        final String value;
        final boolean isSelected;
        /**
         * @param displayText
         */
        public ParameterMenuItem(String displayText) {
            this(displayText, null, null, null);
        }
        /**
         * @param displayText
         * @param parameter
         * @param value
         */
        public ParameterMenuItem(String displayText, String parameter, String value) {
            this(displayText, parameter, value, null);
        }
        /**
         * @param displayText
         * @param parameter
         * @param value
         */
        public ParameterMenuItem(String displayText, String parameter, String value,
                PageParameters params) {
            super(displayText);
            this.parameter = parameter;
            this.value = value;
            if (params == null) {
                // no parameters specified
                parameters = new PageParameters();
                setParameter(parameter, value);
                isSelected = false;
            } else {
                parameters = new PageParameters(params);
                if (parameters.containsKey(parameter)) {
                    isSelected = params.getString(parameter).equals(value);
                    // set the new selection value
                    setParameter(parameter, value);
                } else {
                    // not currently selected
                    isSelected = false;
                    setParameter(parameter, value);
                }
            }
        }
        protected void setParameter(String parameter, String value) {
            if (!StringUtils.isEmpty(parameter)) {
                if (StringUtils.isEmpty(value)) {
                    this.parameters.remove(parameter);
                } else {
                    this.parameters.put(parameter, value);
                }
            }
        }
        public String formatParameter() {
            if (StringUtils.isEmpty(parameter) || StringUtils.isEmpty(value)) {
                return "";
            }
            return parameter + "=" + value;
        }
        public PageParameters getPageParameters() {
            return parameters;
        }
        public boolean isSelected() {
            return isSelected;
        }
        @Override
        public int hashCode() {
            if (StringUtils.isEmpty(displayText)) {
                return value.hashCode() + parameter.hashCode();
            }
            return displayText.hashCode();
        }
        @Override
        public boolean equals(Object o) {
            if (o instanceof MenuItem) {
                return hashCode() == o.hashCode();
            }
            return false;
        }
        @Override
        public String toString() {
            if (StringUtils.isEmpty(displayText)) {
                return formatParameter();
            }
            return displayText;
        }
    }
    /**
     * Menu item for toggling a parameter.
     *
     */
    public static class ToggleMenuItem extends ParameterMenuItem {
        private static final long serialVersionUID = 1L;
        /**
         * @param displayText
         * @param parameter
         * @param value
         */
        public ToggleMenuItem(String displayText, String parameter, String value,
                PageParameters params) {
            super(displayText, parameter, value, params);
            if (isSelected) {
                // already selected, so remove this enables toggling
                parameters.remove(parameter);
            }
        }
    }
    /**
     * Menu item for linking to another Wicket page.
     *
     * @since 1.6.0
     */
    public static class PageLinkMenuItem extends MenuItem {
        private static final long serialVersionUID = 1L;
        private final Class<? extends WebPage> pageClass;
        private final PageParameters params;
        /**
         * Page Link Item links to another page.
         *
         * @param displayText
         * @param pageClass
         * @since 1.6.0
         */
        public PageLinkMenuItem(String displayText, Class<? extends WebPage> pageClass) {
            this(displayText, pageClass, null);
        }
        /**
         * Page Link Item links to another page.
         *
         * @param displayText
         * @param pageClass
         * @param params
         * @since 1.6.0
         */
        public PageLinkMenuItem(String displayText, Class<? extends WebPage> pageClass, PageParameters params) {
            super(displayText);
            this.pageClass = pageClass;
            this.params = params;
        }
        /**
         * @return the page class
         * @since 1.6.0
         */
        public Class<? extends WebPage> getPageClass() {
            return pageClass;
        }
        /**
         * @return the page parameters
         * @since 1.6.0
         */
        public PageParameters getPageParameters() {
            return params;
        }
    }
    /**
     * Menu item to link to an external page.
     *
     * @since 1.6.0
     */
    public static class ExternalLinkMenuItem extends MenuItem {
        private static final long serialVersionUID = 1L;
        private final String href;
        private final boolean newWindow;
        /**
         * External Link Item links to something else.
         *
         * @param displayText
         * @param href
         * @since 1.6.0
         */
        public ExternalLinkMenuItem(String displayText, String href) {
            this(displayText, href, false);
        }
        /**
         * External Link Item links to something else.
         *
         * @param displayText
         * @param href
         * @since 1.6.0
         */
        public ExternalLinkMenuItem(String displayText, String href, boolean newWindow) {
            super(displayText);
            this.href = href;
            this.newWindow = newWindow;
        }
        /**
         * @since 1.6.0
         */
        public String getHref() {
            return href;
        }
        /**
         * @since 1.6.0
         */
        public boolean openInNewWindow() {
            return newWindow;
        }
    }
}
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -680,3 +680,4 @@
gb.overdue = overdue
gb.openMilestones = open milestones
gb.closedMilestones = closed milestones
gb.adminMenuItem = admin
src/main/java/com/gitblit/wicket/PageRegistration.java
@@ -22,7 +22,7 @@
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.WebPage;
import com.gitblit.utils.StringUtils;
import com.gitblit.models.Menu.MenuItem;
/**
 * Represents a page link registration for the topbar.
@@ -88,156 +88,12 @@
        private static final long serialVersionUID = 1L;
        public final List<DropDownMenuItem> menuItems;
        public final List<MenuItem> menuItems;
        public DropDownMenuRegistration(String translationKey, Class<? extends WebPage> pageClass) {
            super(translationKey, pageClass);
            menuItems = new ArrayList<DropDownMenuItem>();
            menuItems = new ArrayList<MenuItem>();
        }
    }
    /**
     * A MenuItem for the DropDownMenu.
     *
     * @author James Moger
     *
     */
    public static class DropDownMenuItem implements Serializable {
        private static final long serialVersionUID = 1L;
        final PageParameters parameters;
        final String displayText;
        final String parameter;
        final String value;
        final boolean isSelected;
        /**
         * Divider constructor.
         */
        public DropDownMenuItem() {
            this(null, null, null, null);
        }
        /**
         * Standard Menu Item constructor.
         *
         * @param displayText
         * @param parameter
         * @param value
         */
        public DropDownMenuItem(String displayText, String parameter, String value) {
            this(displayText, parameter, value, null);
        }
        /**
         * Standard Menu Item constructor that preserves aggregate parameters.
         *
         * @param displayText
         * @param parameter
         * @param value
         */
        public DropDownMenuItem(String displayText, String parameter, String value,
                PageParameters params) {
            this.displayText = displayText;
            this.parameter = parameter;
            this.value = value;
            if (params == null) {
                // no parameters specified
                parameters = new PageParameters();
                setParameter(parameter, value);
                isSelected = false;
            } else {
                parameters = new PageParameters(params);
                if (parameters.containsKey(parameter)) {
                    isSelected = params.getString(parameter).equals(value);
                    // set the new selection value
                    setParameter(parameter, value);
                } else {
                    // not currently selected
                    isSelected = false;
                    setParameter(parameter, value);
                }
            }
        }
        protected void setParameter(String parameter, String value) {
            if (!StringUtils.isEmpty(parameter)) {
                if (StringUtils.isEmpty(value)) {
                    this.parameters.remove(parameter);
                } else {
                    this.parameters.put(parameter, value);
                }
            }
        }
        public String formatParameter() {
            if (StringUtils.isEmpty(parameter) || StringUtils.isEmpty(value)) {
                return "";
            }
            return parameter + "=" + value;
        }
        public PageParameters getPageParameters() {
            return parameters;
        }
        public boolean isDivider() {
            return displayText == null && value == null && parameter == null;
        }
        public boolean isSelected() {
            return isSelected;
        }
        @Override
        public int hashCode() {
            if (isDivider()) {
                // divider menu item
                return super.hashCode();
            }
            if (StringUtils.isEmpty(displayText)) {
                return value.hashCode() + parameter.hashCode();
            }
            return displayText.hashCode();
        }
        @Override
        public boolean equals(Object o) {
            if (o instanceof DropDownMenuItem) {
                return hashCode() == o.hashCode();
            }
            return false;
        }
        @Override
        public String toString() {
            if (StringUtils.isEmpty(displayText)) {
                return formatParameter();
            }
            return displayText;
        }
    }
    public static class DropDownToggleItem extends DropDownMenuItem {
        private static final long serialVersionUID = 1L;
        /**
         * Toggle Menu Item constructor that preserves aggregate parameters.
         *
         * @param displayText
         * @param parameter
         * @param value
         */
        public DropDownToggleItem(String displayText, String parameter, String value,
                PageParameters params) {
            super(displayText, parameter, value, params);
            if (isSelected) {
                // already selected, so remove this enables toggling
                parameters.remove(parameter);
            }
        }
    }
}
src/main/java/com/gitblit/wicket/pages/ActivityPage.java
@@ -31,6 +31,7 @@
import com.gitblit.Keys;
import com.gitblit.models.Activity;
import com.gitblit.models.Menu.ParameterMenuItem;
import com.gitblit.models.Metric;
import com.gitblit.models.RepositoryModel;
import com.gitblit.utils.ActivityUtils;
@@ -38,7 +39,6 @@
import com.gitblit.wicket.CacheControl;
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.PageRegistration;
import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.charting.Chart;
@@ -153,7 +153,7 @@
        if (filters.menuItems.size() > 0) {
            // Reset Filter
            filters.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
            filters.menuItems.add(new ParameterMenuItem(getString("gb.reset")));
        }
        pages.add(filters);
    }
@@ -209,7 +209,7 @@
        }
        charts.addChart(chart);
        // active repositories pie chart
        // active repositories pie chart
        chart = charts.createPieChart("chartRepositories", getString("gb.activeRepositories"),
                getString("gb.repository"), getString("gb.commits"));
        for (Metric metric : repositoryMetrics.values()) {
src/main/java/com/gitblit/wicket/pages/DashboardPage.java
@@ -36,6 +36,7 @@
import com.gitblit.Keys;
import com.gitblit.models.DailyLogEntry;
import com.gitblit.models.Menu.ParameterMenuItem;
import com.gitblit.models.Metric;
import com.gitblit.models.RefLogEntry;
import com.gitblit.models.RepositoryCommit;
@@ -46,7 +47,6 @@
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebApp;
import com.gitblit.wicket.PageRegistration;
import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
import com.gitblit.wicket.charting.Chart;
import com.gitblit.wicket.charting.Charts;
@@ -152,7 +152,7 @@
        if (menu.menuItems.size() > 0) {
            // Reset Filter
            menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
            menu.menuItems.add(new ParameterMenuItem(getString("gb.reset")));
        }
        pages.add(menu);
src/main/java/com/gitblit/wicket/pages/ProjectPage.java
@@ -26,6 +26,9 @@
import org.apache.wicket.markup.html.link.ExternalLink;
import com.gitblit.Keys;
import com.gitblit.models.Menu.MenuDivider;
import com.gitblit.models.Menu.MenuItem;
import com.gitblit.models.Menu.ParameterMenuItem;
import com.gitblit.models.ProjectModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
@@ -38,7 +41,6 @@
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.GitblitRedirectException;
import com.gitblit.wicket.PageRegistration;
import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.FilterableRepositoryList;
@@ -172,7 +174,7 @@
        if (menu.menuItems.size() > 0) {
            // Reset Filter
            menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), "p", WicketUtils.getProjectName(params)));
            menu.menuItems.add(new ParameterMenuItem(getString("gb.reset"), "p", WicketUtils.getProjectName(params)));
        }
        pages.add(menu);
@@ -202,8 +204,8 @@
        return null;
    }
    protected List<DropDownMenuItem> getProjectsMenu() {
        List<DropDownMenuItem> menu = new ArrayList<DropDownMenuItem>();
    protected List<MenuItem> getProjectsMenu() {
        List<MenuItem> menu = new ArrayList<MenuItem>();
        List<ProjectModel> projects = new ArrayList<ProjectModel>();
        for (ProjectModel model : getProjectModels()) {
            if (!model.isUserProject()) {
@@ -230,11 +232,11 @@
        }
        for (ProjectModel project : projects) {
            menu.add(new DropDownMenuItem(project.getDisplayName(), "p", project.name));
            menu.add(new ParameterMenuItem(project.getDisplayName(), "p", project.name));
        }
        if (showAllProjects) {
            menu.add(new DropDownMenuItem());
            menu.add(new DropDownMenuItem("all projects", null, null));
            menu.add(new MenuDivider());
            menu.add(new ParameterMenuItem("all projects"));
        }
        return menu;
    }
src/main/java/com/gitblit/wicket/pages/ProjectsPage.java
@@ -24,10 +24,10 @@
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import com.gitblit.Keys;
import com.gitblit.models.Menu.ParameterMenuItem;
import com.gitblit.models.ProjectModel;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.PageRegistration;
import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.LinkPanel;
@@ -128,7 +128,7 @@
        if (menu.menuItems.size() > 0) {
            // Reset Filter
            menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
            menu.menuItems.add(new ParameterMenuItem(getString("gb.reset")));
        }
        pages.add(menu);
src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java
@@ -29,6 +29,7 @@
import org.eclipse.jgit.lib.Constants;
import com.gitblit.Keys;
import com.gitblit.models.Menu.ParameterMenuItem;
import com.gitblit.models.RepositoryModel;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;
@@ -36,7 +37,6 @@
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.PageRegistration;
import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.RepositoriesPanel;
@@ -105,7 +105,7 @@
        if (menu.menuItems.size() > 0) {
            // Reset Filter
            menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
            menu.menuItems.add(new ParameterMenuItem(getString("gb.reset")));
        }
        pages.add(menu);
src/main/java/com/gitblit/wicket/pages/RootPage.java
@@ -47,6 +47,12 @@
import com.gitblit.Constants;
import com.gitblit.Keys;
import com.gitblit.extensions.AdminMenuExtension;
import com.gitblit.models.Menu.MenuDivider;
import com.gitblit.models.Menu.MenuItem;
import com.gitblit.models.Menu.PageLinkMenuItem;
import com.gitblit.models.Menu.ParameterMenuItem;
import com.gitblit.models.Menu.ToggleMenuItem;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
@@ -54,8 +60,7 @@
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.PageRegistration;
import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
import com.gitblit.wicket.PageRegistration.DropDownToggleItem;
import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
import com.gitblit.wicket.SessionlessForm;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.GravatarImage;
@@ -164,9 +169,6 @@
            add(new Label("userPanel").setVisible(false));
        }
        boolean showRegistrations = app().federation().canFederate()
                && app().settings().getBoolean(Keys.web.showFederationRegistrations, false);
        // navigation links
        List<PageRegistration> pages = new ArrayList<PageRegistration>();
        if (!authenticateView || (authenticateView && isLoggedIn)) {
@@ -181,11 +183,29 @@
            if (allowLucene) {
                pages.add(new PageRegistration("gb.search", LuceneSearchPage.class));
            }
            UserModel user = GitBlitWebSession.get().getUser();
            if (showAdmin) {
                pages.add(new PageRegistration("gb.users", UsersPage.class));
            }
            if (showAdmin || showRegistrations) {
                pages.add(new PageRegistration("gb.federation", FederationPage.class));
                // admin dropdown menu
                DropDownMenuRegistration adminMenu = new DropDownMenuRegistration("gb.adminMenuItem", MyDashboardPage.class);
                adminMenu.menuItems.add(new PageLinkMenuItem(getString("gb.users"), UsersPage.class));
                boolean showRegistrations = app().federation().canFederate()
                        && app().settings().getBoolean(Keys.web.showFederationRegistrations, false);
                if (showRegistrations) {
                    adminMenu.menuItems.add(new PageLinkMenuItem(getString("gb.federation"), FederationPage.class));
                }
                // allow plugins to contribute admin menu items
                List<AdminMenuExtension> extensions = app().plugins().getExtensions(AdminMenuExtension.class);
                for (AdminMenuExtension ext : extensions) {
                    adminMenu.menuItems.add(new MenuDivider());
                    adminMenu.menuItems.addAll(ext.getMenuItems(user));
                }
                pages.add(adminMenu);
            }
            if (!authenticateView || (authenticateView && isLoggedIn)) {
@@ -289,9 +309,9 @@
    }
    protected List<DropDownMenuItem> getRepositoryFilterItems(PageParameters params) {
    protected List<com.gitblit.models.Menu.MenuItem> getRepositoryFilterItems(PageParameters params) {
        final UserModel user = GitBlitWebSession.get().getUser();
        Set<DropDownMenuItem> filters = new LinkedHashSet<DropDownMenuItem>();
        Set<MenuItem> filters = new LinkedHashSet<MenuItem>();
        List<RepositoryModel> repositories = getRepositoryModels();
        // accessible repositories by federation set
@@ -310,11 +330,11 @@
            List<String> sets = new ArrayList<String>(setMap.keySet());
            Collections.sort(sets);
            for (String set : sets) {
                filters.add(new DropDownToggleItem(MessageFormat.format("{0} ({1})", set,
                filters.add(new ToggleMenuItem(MessageFormat.format("{0} ({1})", set,
                        setMap.get(set).get()), "set", set, params));
            }
            // divider
            filters.add(new DropDownMenuItem());
            filters.add(new MenuDivider());
        }
        // user's team memberships
@@ -322,11 +342,11 @@
            List<TeamModel> teams = new ArrayList<TeamModel>(user.teams);
            Collections.sort(teams);
            for (TeamModel team : teams) {
                filters.add(new DropDownToggleItem(MessageFormat.format("{0} ({1})", team.name,
                filters.add(new ToggleMenuItem(MessageFormat.format("{0} ({1})", team.name,
                        team.repositories.size()), "team", team.name, params));
            }
            // divider
            filters.add(new DropDownMenuItem());
            filters.add(new MenuDivider());
        }
        // custom filters
@@ -337,18 +357,18 @@
            for (String expression : expressions) {
                if (!StringUtils.isEmpty(expression)) {
                    addedExpression = true;
                    filters.add(new DropDownToggleItem(null, "x", expression, params));
                    filters.add(new ToggleMenuItem(null, "x", expression, params));
                }
            }
            // if we added any custom expressions, add a divider
            if (addedExpression) {
                filters.add(new DropDownMenuItem());
                filters.add(new MenuDivider());
            }
        }
        return new ArrayList<DropDownMenuItem>(filters);
        return new ArrayList<MenuItem>(filters);
    }
    protected List<DropDownMenuItem> getTimeFilterItems(PageParameters params) {
    protected List<MenuItem> getTimeFilterItems(PageParameters params) {
        // days back choices - additive parameters
        int daysBack = app().settings().getInteger(Keys.web.activityDuration, 7);
        int maxDaysBack = app().settings().getInteger(Keys.web.activityDurationMaximum, 30);
@@ -369,7 +389,7 @@
            clonedParams.put("db",  daysBack);
        }
        List<DropDownMenuItem> items = new ArrayList<DropDownMenuItem>();
        List<MenuItem> items = new ArrayList<MenuItem>();
        Set<Integer> choicesSet = new TreeSet<Integer>(app().settings().getIntegers(Keys.web.activityDurationChoices));
        if (choicesSet.isEmpty()) {
             choicesSet.addAll(Arrays.asList(1, 3, 7, 14, 21, 28));
@@ -379,13 +399,13 @@
        String lastDaysPattern = getString("gb.lastNDays");
        for (Integer db : choices) {
            if (db == 1) {
                items.add(new DropDownMenuItem(getString("gb.time.today"), "db", db.toString(), clonedParams));
                items.add(new ParameterMenuItem(getString("gb.time.today"), "db", db.toString(), clonedParams));
            } else {
                String txt = MessageFormat.format(lastDaysPattern, db);
                items.add(new DropDownMenuItem(txt, "db", db.toString(), clonedParams));
                items.add(new ParameterMenuItem(txt, "db", db.toString(), clonedParams));
            }
        }
        items.add(new DropDownMenuItem());
        items.add(new MenuDivider());
        return items;
    }
src/main/java/com/gitblit/wicket/pages/UserPage.java
@@ -29,6 +29,7 @@
import org.eclipse.jgit.lib.PersonIdent;
import com.gitblit.Keys;
import com.gitblit.models.Menu.ParameterMenuItem;
import com.gitblit.models.ProjectModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
@@ -37,7 +38,6 @@
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.GitblitRedirectException;
import com.gitblit.wicket.PageRegistration;
import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.GravatarImage;
@@ -140,7 +140,7 @@
        if (menu.menuItems.size() > 0) {
            // Reset Filter
            menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
            menu.menuItems.add(new ParameterMenuItem(getString("gb.reset")));
        }
        pages.add(menu);
src/main/java/com/gitblit/wicket/panels/DropDownMenu.java
@@ -21,7 +21,11 @@
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
import com.gitblit.models.Menu.MenuDivider;
import com.gitblit.models.Menu.ExternalLinkMenuItem;
import com.gitblit.models.Menu.MenuItem;
import com.gitblit.models.Menu.PageLinkMenuItem;
import com.gitblit.models.Menu.ParameterMenuItem;
import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
import com.gitblit.wicket.WicketUtils;
@@ -33,26 +37,39 @@
        super(id);
        add(new Label("label", label).setRenderBodyOnly(true));
        ListDataProvider<DropDownMenuItem> items = new ListDataProvider<DropDownMenuItem>(
        ListDataProvider<MenuItem> items = new ListDataProvider<MenuItem>(
                menu.menuItems);
        DataView<DropDownMenuItem> view = new DataView<DropDownMenuItem>("menuItems", items) {
        DataView<MenuItem> view = new DataView<MenuItem>("menuItems", items) {
            private static final long serialVersionUID = 1L;
            @Override
            public void populateItem(final Item<DropDownMenuItem> item) {
                DropDownMenuItem entry = item.getModelObject();
                if (entry.isDivider()) {
            public void populateItem(final Item<MenuItem> item) {
                MenuItem entry = item.getModelObject();
                if (entry instanceof PageLinkMenuItem) {
                    // link to another Wicket page
                    PageLinkMenuItem pageLink = (PageLinkMenuItem) entry;
                    item.add(new LinkPanel("menuItem", null, null, pageLink.toString(), pageLink.getPageClass(),
                            pageLink.getPageParameters(), false).setRenderBodyOnly(true));
                } else if (entry instanceof ExternalLinkMenuItem) {
                    // link to a specified href
                    ExternalLinkMenuItem extLink = (ExternalLinkMenuItem) entry;
                    item.add(new LinkPanel("menuItem", null, extLink.toString(), extLink.getHref(),
                            extLink.openInNewWindow()).setRenderBodyOnly(true));
                } else if (entry instanceof MenuDivider) {
                    // divider
                    item.add(new Label("menuItem").setRenderBodyOnly(true));
                    WicketUtils.setCssClass(item, "divider");
                } else {
                    ParameterMenuItem parameter = (ParameterMenuItem) entry;
                    // parameter link for the current page
                    String icon = null;
                    if (entry.isSelected()) {
                    if (parameter.isSelected()) {
                        icon = "icon-ok";
                    } else {
                        icon = "icon-ok-white";
                    }
                    item.add(new LinkPanel("menuItem", icon, null, entry.toString(), menu.pageClass,
                            entry.getPageParameters(), false).setRenderBodyOnly(true));
                            parameter.getPageParameters(), false).setRenderBodyOnly(true));
                }
            }
        };
src/site/plugins_extensions.mkd
@@ -205,3 +205,29 @@
}
```
### Admin Menu Items
*SINCE 1.6.0*
You can provide your own admin menu items by subclassing the *AdminMenuExtension* class.
```java
import java.util.Arrays;
import java.util.List;
import ro.fortsoft.pf4j.Extension;
import com.gitblit.extensions.AdminMenuExtension;
import com.gitblit.models.Menu.ExternalLinkMenuItem;
import com.gitblit.models.Menu.MenuItem;
import com.gitblit.models.UserModel;
@Extension
public class MyAdminMenuContributor extends AdminMenuExtension {
    @Override
    public List<MenuItem> getMenuItems(UserModel user) {
        return Arrays.asList((MenuItem) new ExternalLinkMenuItem("Github", String.format("https://github.com/%s", user.username));
    }
}
```