diff --git a/src/main/java/io/jenkins/plugins/customizable_header/AbstractLink.java b/src/main/java/io/jenkins/plugins/customizable_header/AbstractLink.java new file mode 100644 index 0000000..f7fc330 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/customizable_header/AbstractLink.java @@ -0,0 +1,20 @@ +package io.jenkins.plugins.customizable_header; + +import hudson.ExtensionList; +import hudson.ExtensionPoint; +import hudson.model.AbstractDescribableImpl; +import jenkins.model.Jenkins; + +public abstract class AbstractLink extends AbstractDescribableImpl implements ExtensionPoint { + + public static ExtensionList all() { + return Jenkins.get().getExtensionList(AbstractLink.class); + } + + public abstract String getType(); + + @Override + public LinkDescriptor getDescriptor() { + return (LinkDescriptor) super.getDescriptor(); + } +} diff --git a/src/main/java/io/jenkins/plugins/customizable_header/AppNavLink.java b/src/main/java/io/jenkins/plugins/customizable_header/AppNavLink.java index e5de8a3..acab9a2 100644 --- a/src/main/java/io/jenkins/plugins/customizable_header/AppNavLink.java +++ b/src/main/java/io/jenkins/plugins/customizable_header/AppNavLink.java @@ -2,7 +2,6 @@ import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; -import hudson.model.AbstractDescribableImpl; import hudson.model.Descriptor; import io.jenkins.plugins.customizable_header.logo.ImageLogo; import io.jenkins.plugins.customizable_header.logo.Logo; @@ -13,7 +12,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.List; -import java.util.Objects; import java.util.stream.Collectors; import org.jenkins.ui.symbol.SymbolRequest; import org.kohsuke.stapler.DataBoundConstructor; @@ -24,7 +22,7 @@ import org.kohsuke.stapler.export.ExportedBean; @ExportedBean -public class AppNavLink extends AbstractDescribableImpl implements Comparable { +public class AppNavLink extends AbstractLink { private String url; private String label; @@ -86,6 +84,12 @@ public void setLogo(Logo logo) { } + @Exported + @Override + public String getType() { + return "link"; + } + @Exported public String getLinkUrl() { try { @@ -151,34 +155,12 @@ private static String extractPluginNameFromIconSrc(String iconSrc) { return ""; } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AppNavLink that = (AppNavLink) o; - return Objects.equals(url, that.url) && Objects.equals(label, that.label); - } - - @Override - public int hashCode() { - return Objects.hash(url, label); - } - - @Override - public int compareTo(AppNavLink other) { - int labelCompare = label.compareToIgnoreCase(other.label); - if (labelCompare != 0) { - return labelCompare; - } - return url.compareTo(other.url); - } - @Extension - public static class DescriptorImpl extends Descriptor { + public static class DescriptorImpl extends LinkDescriptor { @Override @NonNull public String getDisplayName() { - return ""; + return "Link"; } public List> getLogoDescriptors() { diff --git a/src/main/java/io/jenkins/plugins/customizable_header/CustomHeaderConfiguration.java b/src/main/java/io/jenkins/plugins/customizable_header/CustomHeaderConfiguration.java index 03012a2..b74778d 100644 --- a/src/main/java/io/jenkins/plugins/customizable_header/CustomHeaderConfiguration.java +++ b/src/main/java/io/jenkins/plugins/customizable_header/CustomHeaderConfiguration.java @@ -19,6 +19,7 @@ import java.net.URI; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.logging.Level; @@ -60,7 +61,7 @@ public class CustomHeaderConfiguration extends GlobalConfiguration { private final List systemMessages = new ArrayList<>(); - private List links = new ArrayList<>(); + private List links = new ArrayList<>(); private static final transient Symbol star = new Symbol("symbol-star plugin-ionicons-api"); @@ -108,7 +109,16 @@ private static List getFavorites(User user) { fav.setColor("jenkins-!-color-yellow"); favorites.add(fav); }); - Collections.sort(favorites); + favorites.sort(new Comparator() { + @Override + public int compare(AppNavLink o1, AppNavLink o2) { + int labelCompare = o1.getLabel().compareToIgnoreCase(o2.getLabel()); + if (labelCompare != 0) { + return labelCompare; + } + return o1.getUrl().compareTo(o2.getUrl()); + } + }); return favorites; } @@ -179,12 +189,12 @@ public void deleteSystemMessage(String id) { save(); } - public List getLinks() { + public List getLinks() { return links; } @DataBoundSetter - public void setLinks(List links) { + public void setLinks(List links) { this.links = links; save(); } @@ -220,9 +230,9 @@ public boolean hasLinks() { return hasFavorites() || hasAppLinks() || hasUserLinks(); } - public List getUserLinks() { + public List getUserLinks() { User user = User.current(); - List links = null; + List links = null; if (user != null) { UserHeader userHeader = user.getProperty(UserHeader.class); links = userHeader.getLinks(); diff --git a/src/main/java/io/jenkins/plugins/customizable_header/HeaderRootAction.java b/src/main/java/io/jenkins/plugins/customizable_header/HeaderRootAction.java index 85edb5e..cf0ad2b 100644 --- a/src/main/java/io/jenkins/plugins/customizable_header/HeaderRootAction.java +++ b/src/main/java/io/jenkins/plugins/customizable_header/HeaderRootAction.java @@ -112,18 +112,23 @@ public void doDismissMessage(@QueryParameter String uid) throws IOException { @ExportedBean static class Links implements HttpResponse { @Exported(inline = true) - public List getLinks() { - List links = new ArrayList<>(); - links.addAll(CustomHeaderConfiguration.get().getLinks()); - links.addAll(CustomHeaderConfiguration.get().getUserLinks()); + public List getLinks() { + List links = new ArrayList<>(CustomHeaderConfiguration.get().getLinks()); + List userLinks = CustomHeaderConfiguration.get().getUserLinks(); + if (!userLinks.isEmpty()) { + if (!(userLinks.get(0) instanceof LinkSeparator)) { + links.add(new LinkSeparator()); + } + links.addAll(userLinks); + } + List favorites = CustomHeaderConfiguration.get().getFavorites(); + if (!favorites.isEmpty()) { + links.add(new LinkSeparator()); + links.addAll(favorites); + } return links; } - @Exported(inline = true) - public List getFavorites() { - return CustomHeaderConfiguration.get().getFavorites(); - } - @Override public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object o) throws IOException, ServletException { diff --git a/src/main/java/io/jenkins/plugins/customizable_header/LinkDescriptor.java b/src/main/java/io/jenkins/plugins/customizable_header/LinkDescriptor.java new file mode 100644 index 0000000..e2c525a --- /dev/null +++ b/src/main/java/io/jenkins/plugins/customizable_header/LinkDescriptor.java @@ -0,0 +1,11 @@ +package io.jenkins.plugins.customizable_header; + +import hudson.DescriptorExtensionList; +import hudson.model.Descriptor; +import jenkins.model.Jenkins; + +public class LinkDescriptor extends Descriptor { + public static DescriptorExtensionList all() { + return Jenkins.get().getDescriptorList(AbstractLink.class); + } +} diff --git a/src/main/java/io/jenkins/plugins/customizable_header/LinkSeparator.java b/src/main/java/io/jenkins/plugins/customizable_header/LinkSeparator.java new file mode 100644 index 0000000..152018b --- /dev/null +++ b/src/main/java/io/jenkins/plugins/customizable_header/LinkSeparator.java @@ -0,0 +1,43 @@ +package io.jenkins.plugins.customizable_header; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.export.Exported; +import org.kohsuke.stapler.export.ExportedBean; + +@ExportedBean +public class LinkSeparator extends AbstractLink { + + private String title; + + @DataBoundConstructor + public LinkSeparator() { + } + + @DataBoundSetter + public void setTitle(String title) { + this.title = title; + } + + @Exported + public String getTitle() { + return title; + } + + @Exported + @Override + public String getType() { + return "separator"; + } + + @Extension + public static class DescriptorImpl extends LinkDescriptor { + @Override + @NonNull + public String getDisplayName() { + return "Separator"; + } + } +} diff --git a/src/main/java/io/jenkins/plugins/customizable_header/UserHeader.java b/src/main/java/io/jenkins/plugins/customizable_header/UserHeader.java index debd19d..e3e3ccd 100644 --- a/src/main/java/io/jenkins/plugins/customizable_header/UserHeader.java +++ b/src/main/java/io/jenkins/plugins/customizable_header/UserHeader.java @@ -27,7 +27,7 @@ public class UserHeader extends UserProperty { private HeaderSelector headerSelector; - private List links = new ArrayList<>(); + private List links = new ArrayList<>(); private Set dismissedMessages = new HashSet<>(); @@ -71,12 +71,12 @@ public boolean isOverwriteHeader() { return overwriteHeader; } - public List getLinks() { + public List getLinks() { return links; } @DataBoundSetter - public void setLinks(List links) { + public void setLinks(List links) { this.links = links; } diff --git a/src/main/js/app-nav/index.js b/src/main/js/app-nav/index.js index 6245f27..31ae633 100644 --- a/src/main/js/app-nav/index.js +++ b/src/main/js/app-nav/index.js @@ -29,49 +29,56 @@ function createElementFromHtml(html) { }; function menuItem(options) { - const label = xmlEscape(options.label); - let linkIcon = ""; - if (options.external) { - linkIcon = generateSVGIcon("external-link").outerHTML; - } - let color = ""; - if (options.color) { - color = options.color; + let item = null; + if (options.type === "separator") { + if (options.title) { + item = createElementFromHtml(` +
${xmlEscape(options.title)}
+ `); + } else { + item = separator(); + } + } else { + const label = xmlEscape(options.label); + let linkIcon = ""; + if (options.external) { + linkIcon = generateSVGIcon("external-link").outerHTML; + } + let color = ""; + if (options.color) { + color = options.color; + } + item = createElementFromHtml(` + + ${ + options.iconUrl + ? `
${ + options.iconXml + ? options.iconXml + : `` + }
` + : `` + } + ${label} ${linkIcon} +
+ `); } - const item = createElementFromHtml(` - - ${ - options.iconUrl - ? `
${ - options.iconXml - ? options.iconXml - : `` - }
` - : `` - } - ${label} ${linkIcon} -
- `) return item; }; -function generateItems(links, favorites) { +function separator() { + return createElementFromHtml( + `
`, + ); +} + +function generateItems(links) { const menuItems = document.createElement("div"); menuItems.classList.add("jenkins-dropdown"); links.map((item) => { return menuItem(item); }) .forEach((item) => menuItems.appendChild(item)); - if (favorites.length != 0 && links.length != 0) { - const separator = createElementFromHtml( - `
`, - ); - menuItems.appendChild(separator); - } - favorites.map((item) => { - return menuItem(item); - }) - .forEach((item) => menuItems.appendChild(item)); return menuItems; } @@ -80,7 +87,7 @@ function callback(element, instance) { const href = element.dataset.href; fetch(href).then((response) => response.json()).then(json => { - instance.setContent(generateItems(json.links, json.favorites)); + instance.setContent(generateItems(json.links)); }) .catch((error) => console.log(`AppNav request failed: ${error}`)) .finally(() => (instance.loaded = true)); diff --git a/src/main/resources/io/jenkins/plugins/customizable_header/CustomHeaderConfiguration/config.jelly b/src/main/resources/io/jenkins/plugins/customizable_header/CustomHeaderConfiguration/config.jelly index 5bde8fb..7c35d83 100644 --- a/src/main/resources/io/jenkins/plugins/customizable_header/CustomHeaderConfiguration/config.jelly +++ b/src/main/resources/io/jenkins/plugins/customizable_header/CustomHeaderConfiguration/config.jelly @@ -27,9 +27,7 @@ - - - + diff --git a/src/main/resources/io/jenkins/plugins/customizable_header/LinkSeparator/config.jelly b/src/main/resources/io/jenkins/plugins/customizable_header/LinkSeparator/config.jelly new file mode 100644 index 0000000..51b1fe4 --- /dev/null +++ b/src/main/resources/io/jenkins/plugins/customizable_header/LinkSeparator/config.jelly @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/io/jenkins/plugins/customizable_header/LinkSeparator/help-title.html b/src/main/resources/io/jenkins/plugins/customizable_header/LinkSeparator/help-title.html new file mode 100644 index 0000000..0faece3 --- /dev/null +++ b/src/main/resources/io/jenkins/plugins/customizable_header/LinkSeparator/help-title.html @@ -0,0 +1 @@ +When left empty a line will be used to separate the links. \ No newline at end of file diff --git a/src/main/resources/io/jenkins/plugins/customizable_header/UserHeader/config.jelly b/src/main/resources/io/jenkins/plugins/customizable_header/UserHeader/config.jelly index f9a43c2..fe8409a 100644 --- a/src/main/resources/io/jenkins/plugins/customizable_header/UserHeader/config.jelly +++ b/src/main/resources/io/jenkins/plugins/customizable_header/UserHeader/config.jelly @@ -9,9 +9,7 @@ - - - +