Skip to content

Commit e17e2fc

Browse files
wanling0000kopporsubhramitcalixtusInAnYan
authored
feat(git): add “Share to GitHub” flow (#13677)
* feat(git): add “Share to GitHub” flow * add module-info * Resolve conflicts * fix(git): add missing imports * test: init repo in SlrGitHandlerTest; fix localization keys * fix: fail fast on null/blank parameters * fix: Avoid silent failure in GitPreferences * refactor: avoid null init * add GitHandler class * fix: keep RuntimeException catch but using log WARN * Test Stacked branch * fix: checkstyle error * fix: add missing imports * Use BackgroundTask and field Validator * Refactor GitPreferences align with ProxyPreferences * Add https-only validator * Add TODOs and usage of StringUtil.isBlank * WIP - save intermediate GitPreferences refactor * WIP - save intermediate GitPreferences refactor * WIP * refactor: GitHub share dialog - Moved setValues() call to View.initialize() - Cleaned up typos and unused methods - Renamed packages for consistency - Switched from boxed to primitive types * Compile fix * Convert GitPreferences to record (as suggested by IntelliJ) * Fix git PAT storage * Add TODO note * Changes by subhr Co-authored-by: Subhramit Basu Bhowmick <[email protected]> * Changes by fwl Co-authored-by: Wanling Fu <[email protected]> * Revert "Convert GitPreferences to record (as suggested by IntelliJ)" This reverts commit f3bf354. * Compile fix * Disable non-working test * Add ing to ADR-0016 Co-authored-by: Carl Christian Snethlage <[email protected]> Co-authored-by: Ruslan <[email protected]> * Rewrite Optional to "" Co-authored-by: Carl Christian Snethlage <[email protected]> Co-authored-by: Ruslan <[email protected]> Co-authored-by: Subhramit Basu Bhowmick <[email protected]> Co-authored-by: Christoph <[email protected]> Co-authored-by: Wanling Fu <[email protected]> * Fix markdown lint * Link to ADR-0016 * Fix casing * Fix architecture Co-authored-by: Carl Christian Snethlage <[email protected]> * Fix typo Co-authored-by: Wanling Fu <[email protected]> * Refine comments Co-authored-by: Carl Christian Snethlage <[email protected]> * Remove TODO * More language cleanup * Fix empty line * Cleanup language again * Remove TODO * Fix localization * Remove separator * Git is first * Fix conversion * Use "notify" instead of a popup * Remove obsolete string --------- Co-authored-by: Oliver Kopp <[email protected]> Co-authored-by: Subhramit Basu Bhowmick <[email protected]> Co-authored-by: Carl Christian Snethlage <[email protected]> Co-authored-by: Ruslan <[email protected]> Co-authored-by: Christoph <[email protected]>
1 parent 80d5dac commit e17e2fc

31 files changed

+1142
-71
lines changed

docs/decisions/0016-mutable-preferences-objects.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,9 @@ To create an immutable preferences object every time seems to be a waste of time
1818
Chosen option: "Alter the exiting object", because the preferences objects are just wrappers around the basic preferences framework of JDK. They
1919
should be mutable on-the-fly similar to objects with a Builder inside and to be stored immediately again in the
2020
preferences.
21+
22+
### Consequences
23+
24+
* Import logic will be more hard as exising preferences objects have to be altered; and it is very hard to know which preference objects exactly are needed to be modified.
25+
* Cached variables need to be observables, too. AKA The cache needs to be observable.
26+
* There is NO "real" factory pattern for the preferences objects, as they are mutable --> they are passed via the constructor and long-lived

jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,13 @@ public enum StandardActions implements Action {
217217
GROUP_SUBGROUP_RENAME(Localization.lang("Rename subgroup"), KeyBinding.GROUP_SUBGROUP_RENAME),
218218
GROUP_ENTRIES_REMOVE(Localization.lang("Remove selected entries from this group")),
219219

220-
CLEAR_EMBEDDINGS_CACHE(Localization.lang("Clear embeddings cache"));
220+
CLEAR_EMBEDDINGS_CACHE(Localization.lang("Clear embeddings cache")),
221+
222+
GIT(Localization.lang("Git"), IconTheme.JabRefIcons.GIT_SYNC),
223+
GIT_PULL(Localization.lang("Pull")),
224+
GIT_PUSH(Localization.lang("Push")),
225+
GIT_COMMIT(Localization.lang("Commit")),
226+
GIT_SHARE(Localization.lang("Share this library to GitHub"));
221227

222228
private String text;
223229
private final String description;

jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.jabref.gui.externalfiles.AutoLinkFilesAction;
4040
import org.jabref.gui.externalfiles.DownloadFullTextAction;
4141
import org.jabref.gui.externalfiles.FindUnlinkedFilesAction;
42+
import org.jabref.gui.git.GitShareToGitHubAction;
4243
import org.jabref.gui.help.AboutAction;
4344
import org.jabref.gui.help.ErrorConsoleAction;
4445
import org.jabref.gui.help.HelpAction;
@@ -180,10 +181,19 @@ private void createMenu() {
180181

181182
new SeparatorMenuItem(),
182183

184+
// region: Sharing of the library
185+
186+
// TODO: Should be only enabled if not yet shared.
187+
factory.createSubMenu(StandardActions.GIT,
188+
factory.createMenuItem(StandardActions.GIT_SHARE, new GitShareToGitHubAction(dialogService, stateManager))
189+
),
190+
183191
factory.createSubMenu(StandardActions.REMOTE_DB,
184192
factory.createMenuItem(StandardActions.CONNECT_TO_SHARED_DB, new ConnectToSharedDatabaseCommand(frame, dialogService)),
185193
factory.createMenuItem(StandardActions.PULL_CHANGES_FROM_SHARED_DB, new PullChangesFromSharedAction(stateManager))),
186194

195+
// endregion
196+
187197
new SeparatorMenuItem(),
188198

189199
factory.createMenuItem(StandardActions.SHOW_PREFS, new ShowPreferencesAction(frame, dialogService)),

jabgui/src/main/java/org/jabref/gui/git/GitPullViewModel.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ public GitPullViewModel(GitSyncService syncService, GitStatusViewModel gitStatus
2525
public MergeResult pull() throws IOException, GitAPIException, JabRefException {
2626
Optional<BibDatabaseContext> databaseContextOpt = gitStatusViewModel.getDatabaseContext();
2727
if (databaseContextOpt.isEmpty()) {
28-
throw new JabRefException(Localization.lang("Cannot pull: No active BibDatabaseContext."));
28+
throw new JabRefException(Localization.lang("No library selected"));
2929
}
3030

3131
BibDatabaseContext localBibDatabaseContext = databaseContextOpt.get();
3232
Path bibFilePath = localBibDatabaseContext.getDatabasePath().orElseThrow(() ->
33-
new JabRefException(Localization.lang("Cannot pull: .bib file path missing in BibDatabaseContext."))
33+
new JabRefException(Localization.lang("Cannot pull: Please save the library to a file first."))
3434
);
3535

3636
MergeResult result = syncService.fetchAndMerge(localBibDatabaseContext, bibFilePath);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package org.jabref.gui.git;
2+
3+
import javafx.beans.binding.BooleanExpression;
4+
5+
import org.jabref.gui.DialogService;
6+
import org.jabref.gui.StateManager;
7+
import org.jabref.gui.actions.SimpleCommand;
8+
9+
import static org.jabref.gui.actions.ActionHelper.needsDatabase;
10+
11+
public class GitShareToGitHubAction extends SimpleCommand {
12+
private final DialogService dialogService;
13+
private final StateManager stateManager;
14+
15+
public GitShareToGitHubAction(
16+
DialogService dialogService,
17+
StateManager stateManager) {
18+
this.dialogService = dialogService;
19+
this.stateManager = stateManager;
20+
21+
this.executable.bind(this.enabledGitShare());
22+
}
23+
24+
@Override
25+
public void execute() {
26+
dialogService.showCustomDialogAndWait(new GitShareToGitHubDialogView());
27+
}
28+
29+
private BooleanExpression enabledGitShare() {
30+
// TODO: Determine the correct condition for enabling "Git Share". This currently only requires an open library.
31+
// In the future, this may need to check whether:
32+
// - the repo is initialized (because without a repository, the current implementation does not work -> future work)
33+
// - etc.
34+
// Can be called independent if a remote is configured or not -- it will be done in the dialog
35+
// HowTo: Inject the observables (or maybe the stateManager) containing these constraints
36+
return needsDatabase(stateManager);
37+
}
38+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package org.jabref.gui.git;
2+
3+
import javafx.application.Platform;
4+
import javafx.fxml.FXML;
5+
import javafx.scene.control.ButtonType;
6+
import javafx.scene.control.CheckBox;
7+
import javafx.scene.control.Label;
8+
import javafx.scene.control.PasswordField;
9+
import javafx.scene.control.TextField;
10+
import javafx.scene.control.Tooltip;
11+
12+
import org.jabref.gui.DialogService;
13+
import org.jabref.gui.StateManager;
14+
import org.jabref.gui.desktop.os.NativeDesktop;
15+
import org.jabref.gui.preferences.GuiPreferences;
16+
import org.jabref.gui.util.BaseDialog;
17+
import org.jabref.gui.util.IconValidationDecorator;
18+
import org.jabref.logic.l10n.Localization;
19+
import org.jabref.logic.util.TaskExecutor;
20+
21+
import com.airhacks.afterburner.views.ViewLoader;
22+
import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer;
23+
import jakarta.inject.Inject;
24+
25+
public class GitShareToGitHubDialogView extends BaseDialog<Void> {
26+
private static final String GITHUB_PAT_DOCS_URL =
27+
"https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens";
28+
29+
private static final String GITHUB_NEW_REPO_URL = "https://github.com/new";
30+
31+
@FXML private TextField repositoryUrl;
32+
@FXML private TextField username;
33+
@FXML private PasswordField personalAccessToken;
34+
@FXML private ButtonType shareButton;
35+
@FXML private Label patHelpIcon;
36+
@FXML private Tooltip patHelpTooltip;
37+
@FXML private CheckBox rememberSettingsCheck;
38+
@FXML private Label repoHelpIcon;
39+
@FXML private Tooltip repoHelpTooltip;
40+
41+
private GitShareToGitHubDialogViewModel viewModel;
42+
43+
@Inject
44+
private DialogService dialogService;
45+
46+
@Inject
47+
private StateManager stateManager;
48+
49+
@Inject
50+
private TaskExecutor taskExecutor;
51+
52+
@Inject
53+
private GuiPreferences preferences;
54+
55+
private final ControlsFxVisualizer visualizer = new ControlsFxVisualizer();
56+
57+
public GitShareToGitHubDialogView() {
58+
ViewLoader.view(this)
59+
.load()
60+
.setAsDialogPane(this);
61+
}
62+
63+
@FXML
64+
private void initialize() {
65+
this.viewModel = new GitShareToGitHubDialogViewModel(preferences.getGitPreferences(), stateManager, dialogService, taskExecutor);
66+
67+
this.setTitle(Localization.lang("Share this Library to GitHub"));
68+
69+
// See "javafx.md"
70+
this.setResultConverter(button -> {
71+
if (button != ButtonType.CANCEL) {
72+
// We do not want to use "OK", but we want to use a custom text instead.
73+
// JavaFX does not allow to alter the text of the "OK" button.
74+
// Therefore, we used another button type.
75+
// Since we have only two buttons, we can check for non-cancel here.
76+
shareToGitHub();
77+
}
78+
return null;
79+
});
80+
81+
patHelpTooltip.setText(
82+
Localization.lang("Click to open GitHub Personal Access Token documentation")
83+
);
84+
85+
username.setPromptText(Localization.lang("Your GitHub username"));
86+
personalAccessToken.setPromptText(Localization.lang("PAT with repo access"));
87+
88+
repoHelpTooltip.setText(
89+
Localization.lang("Create an empty repository on GitHub, then copy the HTTPS URL (ends with .git). Click to open GitHub.")
90+
);
91+
Tooltip.install(repoHelpIcon, repoHelpTooltip);
92+
repoHelpIcon.setOnMouseClicked(e ->
93+
NativeDesktop.openBrowserShowPopup(
94+
GITHUB_NEW_REPO_URL,
95+
dialogService,
96+
preferences.getExternalApplicationsPreferences()
97+
)
98+
);
99+
100+
Tooltip.install(patHelpIcon, patHelpTooltip);
101+
patHelpIcon.setOnMouseClicked(e ->
102+
NativeDesktop.openBrowserShowPopup(
103+
GITHUB_PAT_DOCS_URL,
104+
dialogService,
105+
preferences.getExternalApplicationsPreferences()
106+
)
107+
);
108+
109+
repositoryUrl.textProperty().bindBidirectional(viewModel.repositoryUrlProperty());
110+
username.textProperty().bindBidirectional(viewModel.usernameProperty());
111+
personalAccessToken.textProperty().bindBidirectional(viewModel.patProperty());
112+
rememberSettingsCheck.selectedProperty().bindBidirectional(viewModel.rememberPatProperty());
113+
114+
viewModel.setValues();
115+
116+
Platform.runLater(() -> {
117+
visualizer.setDecoration(new IconValidationDecorator());
118+
119+
visualizer.initVisualization(viewModel.repositoryUrlValidation(), repositoryUrl, true);
120+
visualizer.initVisualization(viewModel.githubUsernameValidation(), username, true);
121+
visualizer.initVisualization(viewModel.githubPatValidation(), personalAccessToken, true);
122+
});
123+
}
124+
125+
@FXML
126+
private void shareToGitHub() {
127+
viewModel.shareToGitHub(() -> this.close());
128+
}
129+
}

0 commit comments

Comments
 (0)