feat(credits): render the credits from a single markdown file
src/main/resources/userguide/credits.md now lists every shipped library with its license (read from the dependencies' POMs), the recognition models and the map and place data. CreditsView, opened from the ? in Settings, renders it through the new MarkdownPage helper (per-language loading, links opened in the browser), which the search help popup now shares, and the release's distrib step copies it to distrib/CREDITS.md in the same commit as the release note. MarkdownToBBCode learns [text](url) links, inside bold too. URLs are quoted in the generated BBCode: AtlantaFX's parser reads a tag ending in "/]" as self-closing, so a URL with a trailing slash broke the whole page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011xpLSeYKKHX6o16jzYLgZv
This commit is contained in:
@@ -86,6 +86,13 @@ natives (130 MB → ~24 MB for macOS arm64) and a jlink'ed runtime (~64 MB) —
|
||||
|
||||
To re-measure: `unzip -v target/pholio.jar | sort -k3 -n -r | head -20` (third column: compressed size).
|
||||
|
||||
### Credits
|
||||
|
||||
`src/main/resources/userguide/credits.md` is the single list of credits — shipped libraries with their
|
||||
licenses, recognition models, map and place data. The application shows it from the `?` in Settings
|
||||
(`CreditsView`), and each release copies it to `distrib/CREDITS.md`. Add a library or data source there when
|
||||
you add one to the application.
|
||||
|
||||
### Useful scripts
|
||||
|
||||
- `python3 tools/icons/make-macos-icon.py` — regenerates the macOS icon, `icon.png` and `pholio.icns`
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.icroco.pholio.ui.common;
|
||||
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A bundled Markdown page (the user guide and credits under {@code src/main/resources/userguide}) shown
|
||||
* natively: loaded in the current language when a translation exists, converted by {@link MarkdownToBBCode}
|
||||
* and laid out by AtlantaFX's {@link BBCodeParser}.
|
||||
*/
|
||||
public final class MarkdownPage {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MarkdownPage.class);
|
||||
|
||||
private MarkdownPage() {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code basePath_<language>.md} when it exists, otherwise {@code basePath.md}; empty if neither does.
|
||||
*
|
||||
* @param basePath classpath location without extension, e.g. {@code /userguide/credits}
|
||||
*/
|
||||
public static String load(String basePath, Locale locale) {
|
||||
for (String resource : new String[]{basePath + "_" + locale.getLanguage() + ".md", basePath + ".md"}) {
|
||||
try (InputStream in = MarkdownPage.class.getResourceAsStream(resource)) {
|
||||
if (in != null) {
|
||||
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.warn("Could not read {}", resource, e);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Lays {@code markdown} out; every link hands its URL to {@code onLink} when clicked (BBCodeParser
|
||||
* builds links as {@link Hyperlink}s carrying their URL as user data, with no action of their own).
|
||||
*/
|
||||
public static VBox render(String markdown, Consumer<String> onLink) {
|
||||
VBox page = BBCodeParser.createLayout(MarkdownToBBCode.convert(markdown));
|
||||
page.lookupAll(".hyperlink").forEach(node -> {
|
||||
if (node instanceof Hyperlink link && link.getUserData() instanceof String url) {
|
||||
link.setOnAction(event -> onLink.accept(url));
|
||||
}
|
||||
});
|
||||
return page;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,8 @@ import java.util.regex.Pattern;
|
||||
* maintained once, as plain Markdown readable anywhere, and still shown natively in the application.
|
||||
*
|
||||
* <p>Supported: {@code #}/{@code ##}/{@code ###} headings, paragraphs (consecutive lines joined, blank
|
||||
* line between two), {@code -}/{@code *} bullet lists, {@code **bold**} and {@code `code`}. Anything
|
||||
* line between two), {@code -}/{@code *} bullet lists, {@code **bold**}, {@code `code`} and
|
||||
* {@code [links](https://…)} — rendered as {@code Hyperlink}s whose URL is their user data. Anything
|
||||
* else is passed through as plain text — enough for a guide page, not a general-purpose Markdown engine.
|
||||
*/
|
||||
public final class MarkdownToBBCode {
|
||||
@@ -19,9 +20,10 @@ public final class MarkdownToBBCode {
|
||||
private static final Pattern HEADING = Pattern.compile("^(#{1,3})\\s+(.*)$");
|
||||
private static final Pattern BULLET = Pattern.compile("^[-*]\\s+(.*)$");
|
||||
/**
|
||||
* Code first in the alternation, so {@code **} inside a code span stays literal instead of turning bold.
|
||||
* Code first in the alternation, so {@code **} or a link inside a code span stays literal. Bold content
|
||||
* is converted again, so a link can sit inside bold text ({@code **[Pholio](…)**}).
|
||||
*/
|
||||
private static final Pattern INLINE = Pattern.compile("`([^`]+)`|\\*\\*(.+?)\\*\\*");
|
||||
private static final Pattern INLINE = Pattern.compile("`([^`]+)`|\\[([^\\]]+)\\]\\(([^)\\s]+)\\)|\\*\\*(.+?)\\*\\*");
|
||||
|
||||
private MarkdownToBBCode() {
|
||||
}
|
||||
@@ -98,6 +100,10 @@ public final class MarkdownToBBCode {
|
||||
|
||||
private static String inline(String text) {
|
||||
return INLINE.matcher(text).replaceAll(match -> Matcher.quoteReplacement(
|
||||
match.group(1) != null ? "[code]" + match.group(1) + "[/code]" : "[b]" + match.group(2) + "[/b]"));
|
||||
match.group(1) != null ? "[code]" + match.group(1) + "[/code]"
|
||||
// Quoted: BBCodeParser reads a tag ending in "/]" as self-closing, so an unquoted URL with a
|
||||
// trailing slash would swallow the link. The parser strips the quotes from the value.
|
||||
: match.group(2) != null ? "[url=\"" + match.group(3) + "\"]" + match.group(2) + "[/url]"
|
||||
: "[b]" + inline(match.group(4)) + "[/b]"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,42 @@
|
||||
package org.icroco.pholio.ui.shell;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import javafx.application.HostServices;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.icroco.pholio.infra.i18n.I18nService;
|
||||
import org.icroco.pholio.ui.common.Disposable;
|
||||
import org.icroco.pholio.ui.common.FxUtils;
|
||||
import org.icroco.pholio.ui.common.MarkdownPage;
|
||||
import org.icroco.pholio.ui.common.UiView;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* "About" — the open source libraries this application is built on, plus the map data attribution
|
||||
* {@code GeocodingProviderEditView}'s own OpenStreetMap-backed lookups require. Opened from the help icon
|
||||
* in {@link SettingsView#header()}; a plain informational panel, dismissed by its own close button (or the
|
||||
* usual click-outside/Escape any non-persistent {@link ModalService} dialog already allows).
|
||||
* "About" — the credits page, opened from the help icon in {@link SettingsView#header()}: the open source
|
||||
* libraries Pholio ships, the recognition models and the map/place data it relies on, with their licenses.
|
||||
*
|
||||
* <p>The content is {@code src/main/resources/userguide/credits.md}, the single source of those credits:
|
||||
* rendered here through {@link MarkdownPage}, and copied as {@code CREDITS.md} into the public distrib
|
||||
* repository by the release's {@code distrib} step. Update that file, not this view. Its links open in the
|
||||
* user's browser through {@link HostServices} — absent in tests, where they simply do nothing.
|
||||
*/
|
||||
@UiView
|
||||
public class CreditsView extends StackPane implements Disposable {
|
||||
|
||||
/**
|
||||
* Proper nouns, not translated. Runtime dependencies only (see {@code pom.xml}) — build/test-only
|
||||
* tooling (JUnit, ArchUnit, JMH, the Maven plugins…) credits no part of the running application.
|
||||
*/
|
||||
private static final List<String> OPEN_SOURCE_LIBRARIES = List.of(
|
||||
"Spring Boot", "JavaFX", "AtlantaFX", "GemFx", "Ikonli",
|
||||
"H2 Database", "Flyway", "Jackson", "MapStruct", "Jilt", "Lombok",
|
||||
"picocli", "Caffeine", "metadata-extractor", "Apache Commons Imaging",
|
||||
"XMPCore", "ONNX Runtime", "jSystemThemeDetector", "threeten-extra", "gatherers4j"
|
||||
);
|
||||
private static final String PAGE = "/userguide/credits";
|
||||
|
||||
private final Label title = new Label();
|
||||
private final Label openSourceHeader = new Label();
|
||||
private final Label mapDataHeader = new Label();
|
||||
private final Label mapDataCredit = new Label();
|
||||
private final Button closeButton = new Button();
|
||||
private final Label title = new Label();
|
||||
private final Button closeButton = new Button();
|
||||
|
||||
public CreditsView(I18nService i18n, ModalService modalService) {
|
||||
public CreditsView(I18nService i18n, ModalService modalService, @Nullable HostServices hostServices) {
|
||||
getStyleClass().addAll("modal-card", Styles.BG_DEFAULT, Styles.ELEVATED_2);
|
||||
setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE);
|
||||
|
||||
@@ -55,20 +46,15 @@ public class CreditsView extends StackPane implements Disposable {
|
||||
HBox header = new HBox(10, infoIcon, title);
|
||||
header.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
openSourceHeader.getStyleClass().add(Styles.TEXT_BOLD);
|
||||
openSourceHeader.textProperty().bind(i18n.binding("credits.openSource.header"));
|
||||
VBox libraries = new VBox(2, OPEN_SOURCE_LIBRARIES.stream().map(CreditsView::libraryLabel).toArray(Label[]::new));
|
||||
|
||||
mapDataHeader.getStyleClass().add(Styles.TEXT_BOLD);
|
||||
mapDataHeader.textProperty().bind(i18n.binding("credits.mapData.header"));
|
||||
mapDataCredit.setWrapText(true);
|
||||
mapDataCredit.getStyleClass().add(Styles.TEXT_MUTED);
|
||||
mapDataCredit.textProperty().bind(i18n.binding("credits.mapData.openStreetMap"));
|
||||
|
||||
VBox body = new VBox(6, openSourceHeader, libraries, new Separator(), mapDataHeader, mapDataCredit);
|
||||
ScrollPane scroll = new ScrollPane(body);
|
||||
VBox page = MarkdownPage.render(MarkdownPage.load(PAGE, i18n.locale()), url -> {
|
||||
if (hostServices != null) {
|
||||
hostServices.showDocument(url);
|
||||
}
|
||||
});
|
||||
page.getStyleClass().add("credits-page");
|
||||
ScrollPane scroll = new ScrollPane(page);
|
||||
scroll.setFitToWidth(true);
|
||||
scroll.setPrefHeight(280);
|
||||
scroll.setPrefHeight(420);
|
||||
|
||||
closeButton.textProperty().bind(i18n.binding("action.close"));
|
||||
closeButton.getStyleClass().add(Styles.ACCENT);
|
||||
@@ -78,23 +64,14 @@ public class CreditsView extends StackPane implements Disposable {
|
||||
|
||||
VBox card = new VBox(16, header, scroll, footer);
|
||||
card.setPadding(new Insets(24));
|
||||
card.setPrefWidth(420);
|
||||
card.setPrefWidth(560);
|
||||
|
||||
getChildren().add(card);
|
||||
}
|
||||
|
||||
private static Label libraryLabel(String name) {
|
||||
Label label = new Label("• " + name);
|
||||
label.getStyleClass().add(Styles.TEXT_MUTED);
|
||||
return label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
title.textProperty().unbind();
|
||||
openSourceHeader.textProperty().unbind();
|
||||
mapDataHeader.textProperty().unbind();
|
||||
mapDataCredit.textProperty().unbind();
|
||||
closeButton.textProperty().unbind();
|
||||
closeButton.setOnAction(null);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.icroco.pholio.ui.shell;
|
||||
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
@@ -9,17 +8,11 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Popup;
|
||||
import javafx.stage.Window;
|
||||
import org.icroco.pholio.infra.i18n.I18nService;
|
||||
import org.icroco.pholio.ui.common.MarkdownToBBCode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.icroco.pholio.ui.common.MarkdownPage;
|
||||
|
||||
/**
|
||||
* The search bar's syntax guide: {@code userguide/search-syntax.md} (or its {@code _<language>} variant),
|
||||
* rendered through {@link MarkdownToBBCode} and AtlantaFX's {@link BBCodeParser}, in a popup that drops
|
||||
* rendered through {@link MarkdownPage}, in a popup that drops
|
||||
* down from {@link AppHeaderBar}'s search field — same left edge and width, flush against its bottom edge —
|
||||
* so it reads as the field unfolding rather than as a separate window.
|
||||
*
|
||||
@@ -28,8 +21,6 @@ import java.nio.charset.StandardCharsets;
|
||||
*/
|
||||
final class SearchHelpPopup extends Popup {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SearchHelpPopup.class);
|
||||
|
||||
private static final String PAGE = "/userguide/search-syntax";
|
||||
|
||||
/**
|
||||
@@ -63,7 +54,7 @@ final class SearchHelpPopup extends Popup {
|
||||
if (anchor.getScene() == null) {
|
||||
return;
|
||||
}
|
||||
VBox page = BBCodeParser.createLayout(MarkdownToBBCode.convert(loadPage()));
|
||||
VBox page = MarkdownPage.render(MarkdownPage.load(PAGE, i18n.locale()), url -> {});
|
||||
page.getStyleClass().add("search-help-page");
|
||||
page.setPadding(new Insets(PADDING));
|
||||
scroll.setContent(page);
|
||||
@@ -76,18 +67,4 @@ final class SearchHelpPopup extends Popup {
|
||||
scroll.setPrefHeight(Math.min(page.prefHeight(width), available));
|
||||
show(anchor, anchorBounds.getMinX(), anchorBounds.getMaxY());
|
||||
}
|
||||
|
||||
private String loadPage() {
|
||||
for (String resource : new String[]{PAGE + "_" + i18n.locale().getLanguage() + ".md", PAGE + ".md"}) {
|
||||
try (InputStream in = SearchHelpPopup.class.getResourceAsStream(resource)) {
|
||||
if (in != null) {
|
||||
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.warn("Could not read search guide {}", resource, e);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 243 KiB |
@@ -228,9 +228,6 @@ settings.appearance.language.en=English
|
||||
settings.ai.provider.none=None
|
||||
# --- Credits (About dialog, opened from the preferences header's help icon) ---
|
||||
credits.title=Credits
|
||||
credits.openSource.header=Open source libraries
|
||||
credits.mapData.header=Map data
|
||||
credits.mapData.openStreetMap=Address search and map data © OpenStreetMap contributors, available under the Open Database License (ODbL).
|
||||
# --- Onboarding (first-run coach marks) ---
|
||||
onboarding.nav.title=Your library lives here
|
||||
onboarding.nav.text=Browse your photos, albums, tags and more from this rail.
|
||||
|
||||
@@ -231,9 +231,6 @@ settings.appearance.language.en=English
|
||||
settings.ai.provider.none=Aucun
|
||||
# --- Crédits (boîte de dialogue "À propos", ouverte depuis l'icône d'aide des préférences) ---
|
||||
credits.title=Crédits
|
||||
credits.openSource.header=Bibliothèques open source
|
||||
credits.mapData.header=Données cartographiques
|
||||
credits.mapData.openStreetMap=Recherche d'adresse et données cartographiques © contributeurs d'OpenStreetMap, disponibles sous licence Open Database License (ODbL).
|
||||
# --- Présentation initiale (coach marks) ---
|
||||
onboarding.nav.title=Votre photothèque est ici
|
||||
onboarding.nav.text=Parcourez vos photos, albums, mots-clés et plus depuis ce panneau.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Credits
|
||||
|
||||
Pholio stands on the work of many open source projects and open data providers. Thank you to all of them.
|
||||
|
||||
## Open source libraries
|
||||
|
||||
Libraries shipped inside Pholio, with their license.
|
||||
|
||||
- **[Spring Boot](https://spring.io/projects/spring-boot)**, **[Spring Framework](https://spring.io/projects/spring-framework)** and **[Spring Data JDBC](https://spring.io/projects/spring-data-jdbc)** — Apache License 2.0
|
||||
- **[OpenJFX](https://openjfx.io)** — GPL v2 with Classpath Exception
|
||||
- **[AtlantaFX](https://github.com/mkpaz/atlantafx)** — MIT
|
||||
- **[AtlantaFX Themes](https://github.com/dlsc-software-consulting-gmbh/themes)**, **[GemsFX](https://github.com/dlsc-software-consulting-gmbh/GemsFX)** and **[PickerFX](https://github.com/dlsc-software-consulting-gmbh/PickerFX)** by DLSC — Apache License 2.0
|
||||
- **[ControlsFX](https://controlsfx.github.io)** — BSD 3-Clause
|
||||
- **[ValidatorFX](https://github.com/effad/ValidatorFX)** — BSD 3-Clause
|
||||
- **[Ikonli](https://github.com/kordamp/ikonli)**, with the Material Design and Feather icon packs — Apache License 2.0
|
||||
- **[DevToolsFX](https://github.com/mkpaz/devtoolsfx)** — MIT
|
||||
- **[H2 Database](https://h2database.com)** — MPL 2.0 / EPL 1.0
|
||||
- **[Flyway](https://github.com/flyway/flyway)** — Apache License 2.0
|
||||
- **[Jackson](https://github.com/FasterXML/jackson)** — Apache License 2.0
|
||||
- **[MapStruct](https://mapstruct.org)** — Apache License 2.0
|
||||
- **[picocli](https://picocli.info)** — Apache License 2.0
|
||||
- **[Caffeine](https://github.com/ben-manes/caffeine)** — Apache License 2.0
|
||||
- **[metadata-extractor](https://github.com/drewnoakes/metadata-extractor)** — Apache License 2.0
|
||||
- **[Apache Commons Imaging](https://commons.apache.org/proper/commons-imaging/)** and **[Apache Commons Codec](https://commons.apache.org/proper/commons-codec/)** — Apache License 2.0
|
||||
- **[Adobe XMPCore](https://github.com/adobe/XMP-Toolkit-SDK)** — BSD 3-Clause
|
||||
- **[TwelveMonkeys ImageIO](https://github.com/haraldk/TwelveMonkeys)** — BSD 3-Clause
|
||||
- **[ONNX Runtime](https://onnxruntime.ai)** — MIT
|
||||
- **[Byte Buddy](https://bytebuddy.net)** — Apache License 2.0
|
||||
- **[Java Native Access](https://github.com/java-native-access/jna)** — Apache License 2.0 / LGPL 2.1
|
||||
- **[OSHI](https://github.com/oshi/oshi)** — MIT
|
||||
- **[jSystemThemeDetector](https://github.com/Dansoftowner/jSystemThemeDetector)** and **[JFA](https://github.com/0x4a616e/jfa)** — Apache License 2.0
|
||||
- **[ThreeTen-Extra](https://www.threeten.org/threeten-extra/)** — BSD 3-Clause
|
||||
- **[Gatherers4j](https://github.com/tginsberg/gatherers4j)** — Apache License 2.0
|
||||
- **[JSpecify](https://jspecify.dev)** — Apache License 2.0
|
||||
- **[SLF4J](https://www.slf4j.org)** — MIT, and **[Logback](https://logback.qos.ch)** — EPL 2.0 / LGPL 2.1
|
||||
|
||||
Built with, but not shipped: **[Lombok](https://projectlombok.org)** (MIT) and **[Jilt](https://github.com/skinny85/jilt)** (Apache License 2.0).
|
||||
|
||||
## Recognition models
|
||||
|
||||
Face and animal recognition run entirely on your computer, with these models, used exactly as published.
|
||||
|
||||
- **[YuNet](https://github.com/opencv/opencv_zoo/tree/main/models/face_detection_yunet)** face detection, from OpenCV Zoo — MIT
|
||||
- **[SFace](https://github.com/opencv/opencv_zoo/tree/main/models/face_recognition_sface)** face recognition, from OpenCV Zoo — Apache License 2.0
|
||||
- **[YOLOX-Nano](https://github.com/Megvii-BaseDetection/YOLOX)** object detection, by Megvii — Apache License 2.0
|
||||
|
||||
## Map and place data
|
||||
|
||||
- Offline place names come from **[GeoNames](https://www.geonames.org)**, licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/).
|
||||
- Map tiles © **[OpenStreetMap](https://www.openstreetmap.org/copyright)** contributors, available under the Open Database License (ODbL).
|
||||
- When configured, online place search is provided by **[LocationIQ](https://locationiq.com)**.
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.icroco.pholio.ui.common;
|
||||
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.icroco.pholio.ui.FxTestToolkit;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.icroco.pholio.ui.FxTestToolkit.onFxThread;
|
||||
import static org.icroco.pholio.ui.FxTestToolkit.runOnFxThread;
|
||||
|
||||
class MarkdownPageTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
FxTestToolkit.requireToolkit();
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadsTheTranslationWhenThereIsOneAndFallsBackOtherwise() {
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
softly.assertThat(MarkdownPage.load("/userguide/search-syntax", Locale.FRENCH)).startsWith("# Rechercher");
|
||||
softly.assertThat(MarkdownPage.load("/userguide/search-syntax", Locale.GERMAN)).startsWith("# Searching");
|
||||
softly.assertThat(MarkdownPage.load("/userguide/credits", Locale.FRENCH)).startsWith("# Credits");
|
||||
softly.assertThat(MarkdownPage.load("/userguide/does-not-exist", Locale.ENGLISH)).isEmpty();
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void creditsLinksAreHyperlinksThatReportTheirUrlWhenClicked() {
|
||||
List<String> opened = new ArrayList<>();
|
||||
VBox page = onFxThread(() -> MarkdownPage.render(MarkdownPage.load("/userguide/credits", Locale.ENGLISH), opened::add));
|
||||
List<Hyperlink> links = onFxThread(() -> page.lookupAll(".hyperlink").stream()
|
||||
.filter(Hyperlink.class::isInstance)
|
||||
.map(Hyperlink.class::cast)
|
||||
.toList());
|
||||
Hyperlink onnx = links.stream().filter(link -> "ONNX Runtime".equals(link.getText())).findFirst().orElseThrow();
|
||||
|
||||
runOnFxThread(onnx::fire);
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
softly.assertThat(links).hasSizeGreaterThan(30);
|
||||
softly.assertThat(onnx.getUserData()).isEqualTo("https://onnxruntime.ai");
|
||||
softly.assertThat(opened).containsExactly("https://onnxruntime.ai");
|
||||
softly.assertAll();
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,37 @@ class MarkdownToBBCodeTest {
|
||||
+ "After the list."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertsLinksIncludingInsideBold() {
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
softly.assertThat(MarkdownToBBCode.convert("See [H2](https://h2database.com)."))
|
||||
.isEqualTo("See [url=\"https://h2database.com\"]H2[/url].");
|
||||
softly.assertThat(MarkdownToBBCode.convert("- **[Spring Boot](https://spring.io)** — Apache"))
|
||||
.isEqualTo("[ul][li][b][url=\"https://spring.io\"]Spring Boot[/url][/b] — Apache[/li][/ul]");
|
||||
softly.assertThat(MarkdownToBBCode.convert("`[not](a link)`")).isEqualTo("[code][not](a link)[/code]");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void bundledCreditsConvert() throws IOException {
|
||||
try (InputStream in = MarkdownToBBCodeTest.class.getResourceAsStream("/userguide/credits.md")) {
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
softly.assertThat(in).isNotNull();
|
||||
if (in != null) {
|
||||
try {
|
||||
String bbcode = MarkdownToBBCode.convert(new String(in.readAllBytes(), StandardCharsets.UTF_8));
|
||||
softly.assertThat(bbcode).startsWith("[heading=3]Credits[/heading]")
|
||||
.contains("[url=\"https://onnxruntime.ai\"]ONNX Runtime[/url]")
|
||||
.doesNotContain("](http");
|
||||
}
|
||||
catch (IOException e) {
|
||||
softly.fail("could not read credits.md", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void boldMarkersInsideCodeStayLiteral() {
|
||||
SoftAssertions.assertSoftly(softly -> softly.assertThat(MarkdownToBBCode.convert("`**x**`"))
|
||||
|
||||
@@ -38,8 +38,10 @@ import java.util.regex.Pattern;
|
||||
* {@code repo}, described by that release note, with the jar (renamed {@code pholio-<version>.jar}),
|
||||
* its SHA-256 and the note itself as assets, through the {@code gh} CLI;
|
||||
* <li>{@code tag <pom>} — commits the POM alone and tags that commit {@code v<version>}, locally only;
|
||||
* <li>{@code distrib <pom> <distribDir>} — commits that release note alone in the {@code distrib/}
|
||||
* worktree and pushes it (to {@code Imag-In/Pholio}'s {@code main}, per that worktree's upstream);
|
||||
* <li>{@code distrib <pom> <distribDir>} — commits that release note in the {@code distrib/} worktree,
|
||||
* together with {@code CREDITS.md} refreshed from {@code src/main/resources/userguide/credits.md} (the
|
||||
* one source of the credits, also shown in the application), and pushes (to {@code Imag-In/Pholio}'s
|
||||
* {@code main}, per that worktree's upstream);
|
||||
* <li>{@code next <current> <yyyy-mm>} — prints the version that would follow {@code current}; the pure
|
||||
* rule, for tests;
|
||||
* <li>{@code render-notes <version> <yyyy-mm-dd> <previousTag|->} — prints the note {@link #renderNotes}
|
||||
@@ -274,10 +276,14 @@ public final class Release {
|
||||
if (!Files.exists(distribDir.resolve(".git"))) {
|
||||
throw new IllegalStateException(distribDir + " is not the distrib worktree (git worktree add --orphan -b distrib distrib)");
|
||||
}
|
||||
String note = distribDir.relativize(notePath(distribDir.resolve("release_note"), version)).toString();
|
||||
run(List.of("git", "add", "--", note), distribDir, false);
|
||||
// Like tag: that one file only, whatever else is pending in the worktree.
|
||||
run(List.of("git", "commit", "-m", "docs(release): notes for " + version, "--", note), distribDir, false);
|
||||
String note = distribDir.relativize(notePath(distribDir.resolve("release_note"), version)).toString();
|
||||
String credits = "CREDITS.md";
|
||||
Files.copy(pom.toAbsolutePath().getParent().resolve("src/main/resources/userguide/credits.md"),
|
||||
distribDir.resolve(credits), StandardCopyOption.REPLACE_EXISTING);
|
||||
run(List.of("git", "add", "--", note, credits), distribDir, false);
|
||||
// Like tag: those files only, whatever else is pending in the worktree (an unchanged CREDITS.md
|
||||
// simply adds nothing to the commit).
|
||||
run(List.of("git", "commit", "-m", "docs(release): notes for " + version, "--", note, credits), distribDir, false);
|
||||
run(List.of("git", "push"), distribDir, false);
|
||||
System.out.println("[release] committed and pushed " + note + " from " + distribDir);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user