• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

knowledgepixels / nanodash / 24787895260

22 Apr 2026 03:44PM UTC coverage: 17.496%. Remained the same
24787895260

push

github

web-flow
Merge pull request #451 from knowledgepixels/fix-titlebar-nav-from-connector

fix: use absolute nav links in TitleBar

899 of 6170 branches covered (14.57%)

Branch coverage included in aggregate %.

2155 of 11285 relevant lines covered (19.1%)

2.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

25.61
src/main/java/com/knowledgepixels/nanodash/component/TitleBar.java
1
package com.knowledgepixels.nanodash.component;
2

3
import java.util.ArrayList;
4
import java.util.List;
5

6
import org.apache.wicket.behavior.AttributeAppender;
7
import org.apache.wicket.markup.html.WebMarkupContainer;
8
import org.apache.wicket.markup.html.WebPage;
9
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
10
import org.apache.wicket.markup.html.panel.Panel;
11
import org.apache.wicket.markup.repeater.Item;
12
import org.apache.wicket.markup.repeater.data.DataView;
13
import org.apache.wicket.markup.repeater.data.ListDataProvider;
14

15
import com.knowledgepixels.nanodash.NanodashPageRef;
16
import com.knowledgepixels.nanodash.NanodashPreferences;
17
import com.knowledgepixels.nanodash.Utils;
18
import com.knowledgepixels.nanodash.page.NanodashPage;
19
import com.knowledgepixels.nanodash.page.PublishPage;
20
import com.knowledgepixels.nanodash.page.QueryListPage;
21
import com.knowledgepixels.nanodash.page.SpaceListPage;
22
import com.knowledgepixels.nanodash.page.UserListPage;
23

24
/**
25
 * TitleBar is the top bar of the Nanodash application, which contains
26
 * navigation elements such as profile, my channel, users, connectors,
27
 * publish, query, and breadcrumb navigation.
28
 */
29
public class TitleBar extends Panel {
30

31
    private String highlight;
32

33
    /**
34
     * Constructs a TitleBar with the specified id, page, highlight element,
35
     * and an array of path references for breadcrumb navigation.
36
     *
37
     * @param id        the component id
38
     * @param page      the current Nanodash page
39
     * @param highlight the id of the element to highlight
40
     * @param pathRefs  an array of NanodashPageRef for breadcrumb navigation
41
     */
42
    public TitleBar(String id, NanodashPage page, String highlight, NanodashPageRef... pathRefs) {
43
        super(id);
9✔
44
        this.highlight = highlight;
9✔
45
        add(new ProfileItem("profile", page));
39✔
46

47
        createNavLink("users", UserListPage.class);
15✔
48
        createNavLink("connectors", SpaceListPage.class);
15✔
49
        createNavLink("publish", PublishPage.class).setVisible(!NanodashPreferences.get().isReadOnlyMode());
33!
50
        createNavLink("query", QueryListPage.class);
15✔
51

52
        WebMarkupContainer breadcrumbPath = new WebMarkupContainer("breadcrumbpath");
15✔
53
        breadcrumbPath.setVisible(pathRefs.length > 0);
21!
54
        if (pathRefs.length > 0) {
9!
55
            final String[] displayLabels = simplifyBreadcrumbLabels(pathRefs);
×
56
            breadcrumbPath.add(pathRefs[0].createComponent("firstpathelement", displayLabels[0]));
×
57
            // Getting serialization exception if not using 'new ArrayList<...>(...)' here:
58
            List<NanodashPageRef> morePathElements = new ArrayList<NanodashPageRef>(Utils.subList(pathRefs, 1, pathRefs.length));
×
59
            breadcrumbPath.add(new DataView<NanodashPageRef>("morepathelements", new ListDataProvider<NanodashPageRef>(morePathElements)) {
×
60

61
                @Override
62
                protected void populateItem(Item<NanodashPageRef> item) {
63
                    int index = (int) item.getIndex() + 1;
×
64
                    item.add(item.getModelObject().createComponent("furtherpathelement", displayLabels[index]));
×
65
                }
×
66

67
            });
68
        } else {
×
69
            breadcrumbPath.setVisible(false);
12✔
70
        }
71
        add(breadcrumbPath);
27✔
72
    }
3✔
73

74
    /**
75
     * Computes shortened display labels for a breadcrumb path.
76
     *
77
     * Two simplifications are applied:
78
     * <ul>
79
     *   <li>For each non-root crumb, the part it shares with its parent label
80
     *       is stripped (e.g. parent "Knowledge Pixels", child "Knowledge
81
     *       Pixels Incubator" renders as "Incubator"). The shared part is the
82
     *       longest common character prefix, but only stripped when it covers
83
     *       (almost) all of the parent and ends on a non-letter/digit boundary
84
     *       in the child — this also catches singular/plural variations like
85
     *       parent "Nano Sessions", child "Nano Session #30" → "#30".</li>
86
     *   <li>Any ": " in a label and everything after it is removed (e.g.
87
     *       "Incubator 1: Some title" becomes "Incubator 1"). Applied to all
88
     *       crumbs, including the first.</li>
89
     * </ul>
90
     *
91
     * The parent-prefix comparison uses the original (un-simplified) labels,
92
     * so that simplifications on the parent don't interfere with the child.
93
     */
94
    static String[] simplifyBreadcrumbLabels(NanodashPageRef[] pathRefs) {
95
        String[] displayLabels = new String[pathRefs.length];
×
96
        for (int i = 0; i < pathRefs.length; i++) {
×
97
            String label = pathRefs[i].getLabel();
×
98
            if (label != null) {
×
99
                if (i > 0) {
×
100
                    label = stripParentPrefix(pathRefs[i - 1].getLabel(), label);
×
101
                }
102
                int colonIdx = label.indexOf(": ");
×
103
                if (colonIdx > 0) {
×
104
                    label = label.substring(0, colonIdx);
×
105
                }
106
            }
107
            displayLabels[i] = label;
×
108
        }
109
        return displayLabels;
×
110
    }
111

112
    /**
113
     * If the child label appears to restate the parent's "topic" at the start,
114
     * strips that shared prefix and returns the remainder. Otherwise returns
115
     * the child label unchanged.
116
     */
117
    private static String stripParentPrefix(String parent, String child) {
118
        if (parent == null || parent.isEmpty() || child == null) return child;
×
119
        int lcp = 0;
×
120
        int max = Math.min(parent.length(), child.length());
×
121
        while (lcp < max && parent.charAt(lcp) == child.charAt(lcp)) lcp++;
×
122
        // Require a substantial match: at least 3 chars, and within 2 chars of
123
        // the full parent length (so things like "Sessions" vs "Session" still
124
        // match, but "Nanopublication Sessions" vs "Nano Session #30" doesn't).
125
        if (lcp < 3 || lcp < parent.length() - 2) return child;
×
126
        // The boundary in the child must not fall mid-word, otherwise we'd be
127
        // chopping off a partial word like "Foo Bar" -> "Foo Baz Quux" -> "z Quux".
128
        if (lcp >= child.length() || Character.isLetterOrDigit(child.charAt(lcp))) return child;
×
129
        String remainder = child.substring(lcp).replaceAll("^\\s+", "");
×
130
        if (remainder.isEmpty()) return child;
×
131
        return remainder;
×
132
    }
133

134
    private BookmarkablePageLink<Void> createNavLink(String id, Class<? extends WebPage> pageClass) {
135
        BookmarkablePageLink<Void> link = new BookmarkablePageLink<>(id, pageClass);
18✔
136
        if (id.equals(highlight)) {
15!
137
            link.add(new AttributeAppender("class", "selected"));
×
138
        }
139
        add(link);
27✔
140
        return link;
6✔
141
    }
142

143
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc