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

knowledgepixels / nanodash / 28788771886

06 Jul 2026 11:38AM UTC coverage: 28.51% (+0.008%) from 28.502%
28788771886

Pull #535

github

web-flow
Merge d9131b781 into 571599d46
Pull Request #535: feat: persistent navigation context with back-link and post-publish forwarding

1811 of 7177 branches covered (25.23%)

Branch coverage included in aggregate %.

3710 of 12188 relevant lines covered (30.44%)

4.52 hits per line

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

0.0
src/main/java/com/knowledgepixels/nanodash/component/ProfileAccountPanel.java
1
package com.knowledgepixels.nanodash.component;
2

3
import com.knowledgepixels.nanodash.NanodashPreferences;
4
import com.knowledgepixels.nanodash.NanodashSession;
5
import com.knowledgepixels.nanodash.Utils;
6
import com.knowledgepixels.nanodash.domain.User;
7
import com.knowledgepixels.nanodash.page.ExplorePage;
8
import com.knowledgepixels.nanodash.page.ProfilePage;
9
import com.knowledgepixels.nanodash.page.PublishPage;
10
import com.knowledgepixels.nanodash.page.UserPage;
11
import org.apache.wicket.RestartResponseException;
12
import org.apache.wicket.markup.html.WebMarkupContainer;
13
import org.apache.wicket.markup.html.basic.Label;
14
import org.apache.wicket.markup.html.form.Form;
15
import org.apache.wicket.markup.html.form.TextField;
16
import org.apache.wicket.markup.html.link.ExternalLink;
17
import org.apache.wicket.markup.html.link.Link;
18
import org.apache.wicket.markup.html.list.ListItem;
19
import org.apache.wicket.markup.html.list.ListView;
20
import org.apache.wicket.markup.html.panel.FeedbackPanel;
21
import org.apache.wicket.markup.html.panel.Panel;
22
import org.apache.wicket.model.Model;
23
import org.apache.wicket.request.mapper.parameter.PageParameters;
24
import org.apache.wicket.util.string.Strings;
25
import org.apache.wicket.validation.validator.PatternValidator;
26
import org.nanopub.extra.setting.IntroNanopub;
27

28
import java.util.ArrayList;
29
import java.util.List;
30

31
/**
32
 * Account/identity controls for the current user's own About tab: a logout
33
 * button, and — only in local mode (running without ORCID authentication) — a
34
 * form to set or change the ORCID identifier. There is deliberately no login
35
 * button here (that belongs to the logged-out state); in ORCID-login mode the
36
 * identifier comes from authentication and the form is hidden.
37
 */
38
public class ProfileAccountPanel extends Panel {
39

40
    /**
41
     * @param id            the Wicket markup id
42
     * @param userIriString the IRI of the user whose (own) About page this is
43
     */
44
    public ProfileAccountPanel(String id, String userIriString) {
45
        super(id);
×
46
        final NanodashSession session = NanodashSession.get();
×
47
        session.loadProfileInfo();
×
48
        final NanodashPreferences prefs = NanodashPreferences.get();
×
49
        final boolean loginMode = prefs.isOrcidLoginMode();
×
50

51
        // Prominent onboarding CTA: a "Create Introduction" button shown only when the
52
        // user has a local key but no introduction from this site yet. Mirrors the
53
        // "Create Introduction" action of the 👉 Recommended-actions view (same intro
54
        // template + params), but surfaced as a primary button so it can't be missed.
55
        boolean canCreateIntro = session.getKeyPair() != null && session.getLocalIntroCount() == 0;
×
56
        String createIntroUrl = "";
×
57
        if (canCreateIntro) {
×
58
            String shortKey = Utils.getShortPubkeyName(session.getPubkeyString());
×
59
            String aboutUrl = UserPage.MOUNT_PATH + "?id=" + Utils.urlEncode(userIriString) + "&tab=about";
×
60
            createIntroUrl = PublishPage.MOUNT_PATH
×
61
                    + "?template=" + Utils.urlEncode("https://w3id.org/np/RAT8ayO62s4SFqDY1qjv24Iw0xarpbpc6zH68n7hRsAsA")
×
62
                    + "&template-version=latest"
63
                    + "&param_user=" + Utils.urlEncode(userIriString)
×
64
                    + "&param_public-key=" + Utils.urlEncode(session.getPubkeyString())
×
65
                    + "&param_key-declaration=" + Utils.urlEncode(shortKey)
×
66
                    + "&param_key-declaration-ref=" + Utils.urlEncode(shortKey)
×
67
                    + "&param_key-location=" + Utils.urlEncode(prefs.getWebsiteUrl())
×
68
                    + "&context=" + Utils.urlEncode(userIriString)
×
69
                    + "&postpub-redirect-url=" + Utils.urlEncode(aboutUrl)
×
70
                    + "&link-message=" + Utils.urlEncode("Check the checkbox at the end of this page and press 'Publish' to "
×
71
                            + "publish this introduction linking your ORCID identifier to the local key used on this site.");
72
        }
73
        ExternalLink createIntro = new ExternalLink("createIntro", createIntroUrl, "Create Introduction");
×
74
        createIntro.setVisible(canCreateIntro);
×
75
        add(createIntro);
×
76

77
        // Logout only makes sense with an ORCID-login session; in local mode
78
        // there is no login to end.
79
        Link<Void> logout = new Link<Void>("logout") {
×
80
            @Override
81
            public void onClick() {
82
                session.logout();
×
83
                throw new RestartResponseException(UserPage.class, new PageParameters().set("id", userIriString));
×
84
            }
85
        };
86
        logout.setVisible(loginMode);
×
87
        add(logout);
×
88

89
        Model<String> model = Model.of("");
×
90
        if (session.getUserIri() != null) {
×
91
            model.setObject(session.getUserIri().stringValue().replaceFirst("^https://orcid.org/", ""));
×
92
        }
93
        final TextField<String> orcidField = new TextField<>("orcidfield", model);
×
94
        orcidField.add(new PatternValidator(ProfilePage.ORCID_PATTERN));
×
95
        Form<Void> form = new Form<Void>("form") {
×
96
            @Override
97
            protected void onSubmit() {
98
                if (loginMode) return;
×
99
                session.setOrcid(orcidField.getModelObject());
×
100
                String newUserIri = "https://orcid.org/" + orcidField.getModelObject();
×
101
                session.invalidateNow();
×
102
                throw new RestartResponseException(UserPage.class,
×
103
                        new PageParameters().set("id", newUserIri).set("tab", "about"));
×
104
            }
105
        };
106
        form.add(orcidField);
×
107
        // Setting/changing the ORCID is only available in local mode (no ORCID
108
        // authentication); in ORCID-login mode the identifier comes from auth.
109
        form.setVisible(!loginMode);
×
110
        add(form);
×
111

112
        add(new FeedbackPanel("feedback"));
×
113

114
        // Signing key: public key + (local mode) the local key-file path.
115
        add(new ProfileSigItem("sigpart"));
×
116

117
        // Recommended actions — hard-coded here (formerly the 👉 Recommended-actions
118
        // view/query). One bullet per applicable case, computed from the session's
119
        // introduction/approval state. The "create" case is the Create Introduction
120
        // button above, so it has no bullet of its own.
121
        // All recommendations concern the site's local key, so only apply when there is one.
122
        List<String> recs = new ArrayList<>();
×
123
        if (session.getKeyPair() != null) {
×
124
            int localCount = session.getLocalIntroCount();
×
125
            boolean approved = session.isPubkeyApproved();
×
126
            // create: no introduction with the local key yet (the action is the Create
127
            // Introduction button above, but it gets a bullet too for context).
128
            if (localCount == 0) {
×
129
                recs.add("The local key from this site is not part of an introduction yet. Use the "
×
130
                        + "<em>'Create Introduction'</em> button above to link it to your identity.");
131
            }
132
            // get-approval: exactly one local introduction, not approved yet
133
            if (localCount == 1 && !approved) {
×
134
                String t = "Your introduction with the local key is not approved yet. Share it so an already "
×
135
                        + "approved user can approve it";
136
                IntroNanopub localIntro = session.getLocalIntro();
×
137
                if (localIntro != null) {
×
138
                    String introUri = localIntro.getNanopub().getUri().stringValue();
×
139
                    t += ": <a href=\"" + ExplorePage.MOUNT_PATH + "?id=" + Utils.urlEncode(introUri) + "\">"
×
140
                            + Strings.escapeMarkup(introUri) + "</a>.";
×
141
                } else {
×
142
                    t += ".";
×
143
                }
144
                recs.add(t);
×
145
            }
146
            // derive: no local introduction, but the user has introductions elsewhere.
147
            // This always co-occurs with the create bullet above (same localCount==0),
148
            // so it is phrased as the alternative to creating a fresh introduction.
149
            if (localCount == 0 && !session.getUserIntroNanopubs().isEmpty()) {
×
150
                recs.add("As you have introductions elsewhere, you can alternatively use "
×
151
                        + "<em>'derive new introduction'</em> from the row menu in the Introductions table below to "
152
                        + "declare those keys alongside the local key.");
153
            }
154
            // retract: more than one local introduction
155
            if (localCount > 1) {
×
156
                recs.add("You have multiple introductions from this site. Use <em>'retract'</em> from the row menu in "
×
157
                        + "the Introductions table below to remove the redundant ones.");
158
            }
159
            // update-approved: local key not approved, but the user has another approved key
160
            String localHash = session.getPubkeyhash();
×
161
            boolean hasAnotherApprovedKey = localHash != null && session.getUserIri() != null
×
162
                    && User.getPubkeyhashes(session.getUserIri(), true).stream().anyMatch(h -> !h.equals(localHash));
×
163
            if (localCount > 0 && !approved && hasAnotherApprovedKey) {
×
164
                recs.add("Your local key is not approved, but you have an approved introduction elsewhere. Add this "
×
165
                        + "site's local key to that approved introduction, at the site where you created it.");
166
            }
167
        }
168
        WebMarkupContainer recommendations = new WebMarkupContainer("recommendations");
×
169
        recommendations.setVisible(!recs.isEmpty());
×
170
        recommendations.add(new ListView<String>("recItems", recs) {
×
171
            @Override
172
            protected void populateItem(ListItem<String> item) {
173
                item.add(new Label("recText", item.getModelObject()).setEscapeModelStrings(false));
×
174
            }
×
175
        });
176
        add(recommendations);
×
177
    }
×
178

179
}
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