diff --git a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytFileTree.java b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytFileTree.java index 37538fae..bfe92912 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytFileTree.java +++ b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytFileTree.java @@ -64,6 +64,10 @@ public boolean isEmpty() { return rows.isEmpty(); } + public List getRows() { + return List.copyOf(rows); + } + @Override public List getChildren() { return childNodes; @@ -228,19 +232,32 @@ private static void drawHorizontalLine(RenderContext context, int xStart, int xE context.fillRect(new LytRect(left, y, width, CONNECTOR_THICKNESS), color); } - private static class Row { + public static class Row { - final List slots; + private final List slots; @Nullable - final LytBlock iconBlock; - final LytParagraph payload; - int rowY; - int rowHeight; + private final LytBlock iconBlock; + private final LytParagraph payload; + private int rowY; + private int rowHeight; Row(List slots, @Nullable LytBlock iconBlock, LytParagraph payload) { this.slots = slots; this.iconBlock = iconBlock; this.payload = payload; } + + public List getSlots() { + return slots; + } + + @Nullable + public LytBlock getIconBlock() { + return iconBlock; + } + + public LytParagraph getPayload() { + return payload; + } } } diff --git a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidFlowchart.java b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidFlowchart.java index b64b8f8a..a19cc870 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidFlowchart.java +++ b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidFlowchart.java @@ -24,6 +24,8 @@ public class LytMermaidFlowchart extends LytVBox implements InteractiveElement { @Getter private final LytCodeBlockToolbar toolbar = new LytCodeBlockToolbar(); private final LytMermaidFlowchartCanvas canvas; + @Getter + private final Map nodeContent; public LytMermaidFlowchart(FlowchartDocument flowchart, String sourceText) { this(flowchart, sourceText, Collections.emptyMap()); @@ -32,6 +34,7 @@ public LytMermaidFlowchart(FlowchartDocument flowchart, String sourceText) { public LytMermaidFlowchart(FlowchartDocument flowchart, String sourceText, Map nodeContent) { this.flowchart = flowchart; this.sourceText = sourceText != null ? sourceText : ""; + this.nodeContent = nodeContent != null ? Map.copyOf(nodeContent) : Collections.emptyMap(); this.canvas = new LytMermaidFlowchartCanvas( flowchart, nodeContent != null ? nodeContent : Collections.emptyMap()); diff --git a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidMindmap.java b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidMindmap.java index 57cc1765..46255903 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidMindmap.java +++ b/src/main/java/com/hfstudio/guidenh/guide/document/block/LytMermaidMindmap.java @@ -22,6 +22,7 @@ public class LytMermaidMindmap extends LytVBox implements InteractiveElement { private final String sourceText; private final LytCodeBlockToolbar toolbar = new LytCodeBlockToolbar(); private final LytMermaidMindmapCanvas canvas; + private final Map nodeContent; public LytMermaidMindmap(MindmapDocument mindmap, String sourceText) { this(mindmap, sourceText, Collections.emptyMap()); @@ -30,6 +31,7 @@ public LytMermaidMindmap(MindmapDocument mindmap, String sourceText) { public LytMermaidMindmap(MindmapDocument mindmap, String sourceText, Map nodeContent) { this.mindmap = mindmap; this.sourceText = sourceText != null ? sourceText : ""; + this.nodeContent = nodeContent != null ? Map.copyOf(nodeContent) : Collections.emptyMap(); this.canvas = new LytMermaidMindmapCanvas(mindmap, nodeContent != null ? nodeContent : Collections.emptyMap()); setPadding(6); diff --git a/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytBarChart.java b/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytBarChart.java index 06221ae7..d4f69b6f 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytBarChart.java +++ b/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytBarChart.java @@ -43,6 +43,22 @@ public class LytBarChart extends LytChartBase implements DebugComponent { private ChartAxisOptions yAxis = new ChartAxisOptions(); private float barWidthRatio = 0.7f; + public String[] getCategories() { + return categories.clone(); + } + + public ChartAxisOptions getXAxis() { + return xAxis; + } + + public ChartAxisOptions getYAxis() { + return yAxis; + } + + public float getBarWidthRatio() { + return barWidthRatio; + } + private LytRect plotCache = LytRect.empty(); private AxisRange xRangeCache; diff --git a/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytChartBase.java b/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytChartBase.java index ae221814..99519821 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytChartBase.java +++ b/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytChartBase.java @@ -70,6 +70,14 @@ public abstract class LytChartBase extends LytBlock implements InteractiveElemen @Setter private int cornerLegendBackgroundColor = CornerLegendRenderer.DEFAULT_BACKGROUND; + public int getExplicitWidth() { + return explicitWidth; + } + + public int getExplicitHeight() { + return explicitHeight; + } + /** Currently hovered hit key; {@code -1} means none. The exact semantics is decided by each subclass. */ protected int hoveredKey = -1; diff --git a/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytColumnChart.java b/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytColumnChart.java index c8a92d20..76d6d6e1 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytColumnChart.java +++ b/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytColumnChart.java @@ -43,6 +43,22 @@ public class LytColumnChart extends LytChartBase implements DebugComponent { private ChartAxisOptions yAxis = new ChartAxisOptions(); private float barWidthRatio = 0.7f; + public String[] getCategories() { + return categories.clone(); + } + + public ChartAxisOptions getXAxis() { + return xAxis; + } + + public ChartAxisOptions getYAxis() { + return yAxis; + } + + public float getBarWidthRatio() { + return barWidthRatio; + } + private LytRect plotCache = LytRect.empty(); private AxisRange yRangeCache; diff --git a/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytLineChart.java b/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytLineChart.java index 76f30a59..8f7fc649 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytLineChart.java +++ b/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytLineChart.java @@ -35,6 +35,26 @@ public class LytLineChart extends LytChartBase { @Setter private boolean showPoints = true; + public String[] getCategories() { + return categories.clone(); + } + + public ChartAxisOptions getXAxis() { + return xAxis; + } + + public ChartAxisOptions getYAxis() { + return yAxis; + } + + public boolean isNumericX() { + return numericX; + } + + public boolean isShowPoints() { + return showPoints; + } + private LytRect plotCache = LytRect.empty(); private AxisRange xRangeCache; private AxisRange yRangeCache; diff --git a/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytPieChart.java b/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytPieChart.java index adb5af01..bfe41ca1 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytPieChart.java +++ b/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytPieChart.java @@ -28,6 +28,14 @@ public class LytPieChart extends LytChartBase implements DebugComponent { @Setter private boolean clockwise = true; + public float getStartAngleDeg() { + return startAngleDeg; + } + + public boolean isClockwise() { + return clockwise; + } + private float cxCache; private float cyCache; private float radiusCache; diff --git a/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytScatterChart.java b/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytScatterChart.java index bc1cb94f..7d3c13c0 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytScatterChart.java +++ b/src/main/java/com/hfstudio/guidenh/guide/document/block/chart/LytScatterChart.java @@ -24,6 +24,14 @@ public class LytScatterChart extends LytChartBase { private ChartAxisOptions xAxis = new ChartAxisOptions(); private ChartAxisOptions yAxis = new ChartAxisOptions(); + public ChartAxisOptions getXAxis() { + return xAxis; + } + + public ChartAxisOptions getYAxis() { + return yAxis; + } + private LytRect plotCache = LytRect.empty(); private AxisRange xRangeCache; private AxisRange yRangeCache; diff --git a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSitePageAssetExporter.java b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSitePageAssetExporter.java index 99a6cbe9..899e2121 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSitePageAssetExporter.java +++ b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSitePageAssetExporter.java @@ -22,12 +22,18 @@ public interface AssetLoader { private final GuideSiteAssetRegistry assets; private final AssetLoader assetLoader; + private final GuideSiteLatexExporter latexExporter; private final Map exportedResourcePaths = new HashMap<>(); private final Map exportedSoundPaths = new HashMap<>(); public GuideSitePageAssetExporter(GuideSiteAssetRegistry assets, AssetLoader assetLoader) { this.assets = assets; this.assetLoader = assetLoader; + this.latexExporter = new GuideSiteLatexExporter(assets); + } + + public GuideSiteLatexExporter latexExporter() { + return latexExporter; } public String resolveImageSrc(String rawUrl, @Nullable ResourceLocation currentPageId) { diff --git a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteSceneAnnotationSerializer.java b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteSceneAnnotationSerializer.java index a21f2769..072c32fd 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteSceneAnnotationSerializer.java +++ b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/GuideSiteSceneAnnotationSerializer.java @@ -4,11 +4,13 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import net.minecraft.client.Minecraft; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.ResourceLocation; import org.jetbrains.annotations.Nullable; @@ -21,19 +23,55 @@ import com.hfstudio.guidenh.guide.PageAnchor; import com.hfstudio.guidenh.guide.color.ColorUtils; import com.hfstudio.guidenh.guide.color.ColorValue; +import com.hfstudio.guidenh.guide.compiler.GuideItemReferenceResolver; +import com.hfstudio.guidenh.guide.compiler.tags.BlockImageCompiler.BlockImagePlaceholder; +import com.hfstudio.guidenh.guide.compiler.tags.ItemImageCompiler.ItemImagePlaceholder; +import com.hfstudio.guidenh.guide.compiler.tags.MermaidCompiler.MermaidPlaceholder; +import com.hfstudio.guidenh.guide.document.block.ImageRegionAnnotation; +import com.hfstudio.guidenh.guide.document.block.LytAlertBox; +import com.hfstudio.guidenh.guide.document.block.LytAlignedBlock; import com.hfstudio.guidenh.guide.document.block.LytBlock; +import com.hfstudio.guidenh.guide.document.block.LytCodeBlock; +import com.hfstudio.guidenh.guide.document.block.LytContentTabsBlock; +import com.hfstudio.guidenh.guide.document.block.LytDetailsBlock; import com.hfstudio.guidenh.guide.document.block.LytDocument; +import com.hfstudio.guidenh.guide.document.block.LytDocumentFloat; +import com.hfstudio.guidenh.guide.document.block.LytFileTree; +import com.hfstudio.guidenh.guide.document.block.LytGuiSprite; +import com.hfstudio.guidenh.guide.document.block.LytHBox; import com.hfstudio.guidenh.guide.document.block.LytHeading; import com.hfstudio.guidenh.guide.document.block.LytImage; +import com.hfstudio.guidenh.guide.document.block.LytImageBlock; import com.hfstudio.guidenh.guide.document.block.LytItemGrid; import com.hfstudio.guidenh.guide.document.block.LytItemImage; +import com.hfstudio.guidenh.guide.document.block.LytLatexBlock; +import com.hfstudio.guidenh.guide.document.block.LytLatexDisplayBlock; import com.hfstudio.guidenh.guide.document.block.LytList; import com.hfstudio.guidenh.guide.document.block.LytListItem; +import com.hfstudio.guidenh.guide.document.block.LytMermaidFlowchart; +import com.hfstudio.guidenh.guide.document.block.LytMermaidMindmap; import com.hfstudio.guidenh.guide.document.block.LytNode; import com.hfstudio.guidenh.guide.document.block.LytParagraph; +import com.hfstudio.guidenh.guide.document.block.LytQuoteBox; +import com.hfstudio.guidenh.guide.document.block.LytSizeBox; import com.hfstudio.guidenh.guide.document.block.LytSlot; import com.hfstudio.guidenh.guide.document.block.LytSlotGrid; +import com.hfstudio.guidenh.guide.document.block.LytStructureView; import com.hfstudio.guidenh.guide.document.block.LytThematicBreak; +import com.hfstudio.guidenh.guide.document.block.LytWidthBox; +import com.hfstudio.guidenh.guide.document.block.chart.ChartAxisOptions; +import com.hfstudio.guidenh.guide.document.block.chart.ChartSeries; +import com.hfstudio.guidenh.guide.document.block.chart.LytBarChart; +import com.hfstudio.guidenh.guide.document.block.chart.LytChartBase; +import com.hfstudio.guidenh.guide.document.block.chart.LytColumnChart; +import com.hfstudio.guidenh.guide.document.block.chart.LytLineChart; +import com.hfstudio.guidenh.guide.document.block.chart.LytPieChart; +import com.hfstudio.guidenh.guide.document.block.chart.LytScatterChart; +import com.hfstudio.guidenh.guide.document.block.chart.PieInsetSpec; +import com.hfstudio.guidenh.guide.document.block.chart.PieSlice; +import com.hfstudio.guidenh.guide.document.block.functiongraph.LytFunctionGraph; +import com.hfstudio.guidenh.guide.document.block.recipes.LytGenericRecipeBox; +import com.hfstudio.guidenh.guide.document.block.recipes.LytStandardRecipeBox; import com.hfstudio.guidenh.guide.document.block.table.LytTable; import com.hfstudio.guidenh.guide.document.block.table.LytTableCell; import com.hfstudio.guidenh.guide.document.block.table.LytTableRow; @@ -45,11 +83,18 @@ import com.hfstudio.guidenh.guide.document.flow.LytFlowSpan; import com.hfstudio.guidenh.guide.document.flow.LytFlowText; import com.hfstudio.guidenh.guide.document.flow.LytSpoilerSpan; +import com.hfstudio.guidenh.guide.document.flow.LytTooltipSpan; import com.hfstudio.guidenh.guide.document.interaction.ContentTooltip; import com.hfstudio.guidenh.guide.document.interaction.GuideTooltip; import com.hfstudio.guidenh.guide.document.interaction.ItemTooltip; import com.hfstudio.guidenh.guide.document.interaction.ItemTooltipAppender; import com.hfstudio.guidenh.guide.document.interaction.TextTooltip; +import com.hfstudio.guidenh.guide.internal.item.GuideDisplayItemStacks; +import com.hfstudio.guidenh.guide.internal.markdown.FileTreeParser.SlotKind; +import com.hfstudio.guidenh.guide.internal.mermaid.MermaidDiagramType; +import com.hfstudio.guidenh.guide.internal.mermaid.flowchart.FlowchartParser; +import com.hfstudio.guidenh.guide.internal.mermaid.mindmap.MindmapParser; +import com.hfstudio.guidenh.guide.internal.structure.GuideTextNbtCodec; import com.hfstudio.guidenh.guide.internal.tooltip.GuideItemTooltipLines; import com.hfstudio.guidenh.guide.scene.GuidebookSceneLayerSelection; import com.hfstudio.guidenh.guide.scene.LytGuidebookScene; @@ -677,6 +722,156 @@ private static void appendBlock(StringBuilder html, @Nullable LytNode node, allowNestedItemTooltips); return; } + case LytImageBlock imageBlock -> { + appendImageBlock(html, imageBlock, currentPageId, assetExporter, itemIconResolver, templates); + return; + } + case BlockImagePlaceholder blockImage -> { + appendBlockImage(html, blockImage, currentPageId, assetExporter, itemIconResolver, templates, + allowNestedItemTooltips); + return; + } + case LytMermaidMindmap mindmap -> { + appendMermaidMindmap(html, mindmap, currentPageId, assetExporter, itemIconResolver, templates, + allowNestedItemTooltips); + return; + } + case LytMermaidFlowchart flowchart -> { + appendMermaidFlowchart(html, flowchart, currentPageId, assetExporter, itemIconResolver, templates, + allowNestedItemTooltips); + return; + } + case MermaidPlaceholder mermaid -> { + appendMermaidPlaceholder(html, mermaid, currentPageId, assetExporter, itemIconResolver, templates, + allowNestedItemTooltips); + return; + } + case LytCodeBlock codeBlock -> { + appendCodeBlock(html, codeBlock, currentPageId, assetExporter, itemIconResolver, templates, + allowNestedItemTooltips); + return; + } + case LytLatexDisplayBlock latex -> { + appendLatex(html, latex.getFormula(), latex.getFillColorArgb(), latex.getSourceScale(), + latex.getUserScale(), latex.getLatexTooltip(), true, latex.getOffsetX(), latex.getOffsetY(), + currentPageId, assetExporter, itemIconResolver, templates); + return; + } + case LytLatexBlock latex -> { + appendLatex(html, latex.getFormula(), latex.getFillColorArgb(), latex.getSourceScale(), + latex.getUserScale(), latex.getLatexTooltip(), false, latex.getOffsetX(), latex.getOffsetY(), + currentPageId, assetExporter, itemIconResolver, templates); + return; + } + case LytFileTree fileTree -> { + appendFileTree(html, fileTree, currentPageId, assetExporter, itemIconResolver, templates, + allowNestedItemTooltips); + return; + } + case LytStructureView structure -> { + appendStructureView(html, structure, itemIconResolver, currentPageId, assetExporter, templates, + allowNestedItemTooltips); + return; + } + case LytGuiSprite sprite -> { + appendGuiSprite(html, sprite, assetExporter); + return; + } + case LytStandardRecipeBox recipe -> { + html.append("
"); + appendBlockChildren(html, recipe, currentPageId, assetExporter, itemIconResolver, templates, + allowNestedItemTooltips); + html.append("
"); + return; + } + case LytGenericRecipeBox recipe -> { + html.append("
"); + appendBlockChildren(html, recipe, currentPageId, assetExporter, itemIconResolver, templates, + allowNestedItemTooltips); + html.append("
"); + return; + } + case LytColumnChart column -> { + html.append(renderColumnChart(column)); + return; + } + case LytBarChart bar -> { + html.append(renderBarChart(bar)); + return; + } + case LytLineChart line -> { + html.append(renderLineChart(line)); + return; + } + case LytPieChart pie -> { + html.append(renderPieChart(pie)); + return; + } + case LytScatterChart scatter -> { + html.append(renderScatterChart(scatter)); + return; + } + case LytFunctionGraph functionGraph -> { + html.append(GuideSiteGraphRenderer.renderFunctionGraph( + functionGraph, + assetExporter != null ? assetExporter.latexExporter() : null)); + return; + } + case LytDetailsBlock details -> { + html.append("
"); + appendBlockChildren(html, details.getSummaryBox(), currentPageId, assetExporter, itemIconResolver, + templates, allowNestedItemTooltips); + html.append("
"); + if (details.isOpen()) { + appendBlockChildren(html, details.getContentBox(), currentPageId, assetExporter, + itemIconResolver, templates, allowNestedItemTooltips); + } + html.append("
"); + return; + } + case LytContentTabsBlock tabs -> { + appendContentTabs(html, tabs, currentPageId, assetExporter, itemIconResolver, templates, + allowNestedItemTooltips); + return; + } + case LytQuoteBox quote -> { + html.append("
"); + appendBlockChildren(html, quote, currentPageId, assetExporter, itemIconResolver, templates, + allowNestedItemTooltips); + html.append("
"); + return; + } + case LytAlertBox alert -> { + html.append("
"); + appendBlockChildren(html, alert, currentPageId, assetExporter, itemIconResolver, templates, + allowNestedItemTooltips); + html.append("
"); + return; + } + case LytAlignedBlock aligned -> { + html.append("
"); + appendBlockChildren(html, aligned, currentPageId, assetExporter, itemIconResolver, templates, + allowNestedItemTooltips); + html.append("
"); + return; + } + case LytDocumentFloat documentFloat -> { + appendBlockChildren(html, node, currentPageId, assetExporter, itemIconResolver, templates, + allowNestedItemTooltips); + return; + } + case LytSizeBox sizeBox -> { + appendBlockChildren(html, node, currentPageId, assetExporter, itemIconResolver, templates, + allowNestedItemTooltips); + return; + } + case ItemImagePlaceholder itemImage -> { + appendItemImage(html, itemImage, currentPageId, assetExporter, itemIconResolver, templates, + allowNestedItemTooltips); + return; + } case LytParagraph paragraph -> { appendParagraph( html, @@ -711,6 +906,17 @@ private static void appendBlock(StringBuilder html, @Nullable LytNode node, allowNestedItemTooltips); return; } + case LytHBox row -> { + html.append("
"); + for (LytNode child : row.getChildren()) { + appendBlock(html, child, currentPageId, assetExporter, itemIconResolver, templates, + allowNestedItemTooltips); + } + html.append("
"); + return; + } case LytThematicBreak lytThematicBreak -> { html.append("
"); return; @@ -791,25 +997,47 @@ private static void appendBlock(StringBuilder html, @Nullable LytNode node, allowNestedItemTooltips); } } + if ((children == null || children.isEmpty()) && node.getTextContent().isEmpty()) { + html.append("") + .append(escapeHtml(node.getClass().getSimpleName())) + .append(""); + } } private static void appendParagraph(StringBuilder html, LytParagraph paragraph, String tagName, @Nullable ResourceLocation currentPageId, @Nullable GuideSitePageAssetExporter assetExporter, GuideSiteItemIconResolver itemIconResolver, @Nullable GuideSiteTemplateRegistry templates, boolean allowNestedItemTooltips) { - html.append("<") - .append(tagName); - appendParagraphStyleAttribute(html, paragraph.resolveStyle()); - if (paragraph instanceof LytHeading heading) { - String anchor = GuideSiteHrefResolver.headingAnchor(heading.getTextContent()); - if (!anchor.isEmpty()) { - html.append(" id=\"") - .append(escapeAttribute(anchor)) - .append("\""); - } - } - html.append(">"); + boolean open = false; + boolean emitted = false; for (LytFlowContent content : paragraph.getContent()) { + boolean blockLayout = "p".equals(tagName) && content instanceof LytFlowInlineBlock inline + && (inline.getBlock() instanceof LytHBox || inline.getBlock() instanceof LytWidthBox); + if (blockLayout) { + if (open) { + html.append("

"); + open = false; + } + } else if (!open) { + if (emitted && content instanceof LytFlowText text + && text.getText() + .isBlank()) { + continue; + } + html.append("<") + .append(tagName); + appendParagraphStyleAttribute(html, paragraph.resolveStyle()); + if (paragraph instanceof LytHeading heading) { + String anchor = GuideSiteHrefResolver.headingAnchor(heading.getTextContent()); + if (!anchor.isEmpty()) { + html.append(" id=\"") + .append(escapeAttribute(anchor)) + .append("\""); + } + } + html.append(">"); + open = true; + } appendFlowContent( html, content, @@ -818,10 +1046,19 @@ private static void appendParagraph(StringBuilder html, LytParagraph paragraph, itemIconResolver, templates, allowNestedItemTooltips); + emitted = true; + } + if (open) { + html.append(""); + } else if (!emitted) { + html.append("<") + .append(tagName) + .append(">"); } - html.append(""); } private static void appendList(StringBuilder html, LytList list, @Nullable ResourceLocation currentPageId, @@ -963,7 +1200,8 @@ private static void appendFlowContent(StringBuilder html, @Nullable LytFlowConte itemIconResolver, templates, allowNestedItemTooltips); - case LytFlowLink link -> appendLink(html, link, currentPageId, itemIconResolver, templates, allowNestedItemTooltips); + case LytFlowLink link -> appendLink(html, link, currentPageId, assetExporter, itemIconResolver, + templates, allowNestedItemTooltips); case LytFlowSpan span -> appendStyledFlowContainer( html, span, @@ -1010,8 +1248,8 @@ private static void appendInlineBlock(StringBuilder html, LytFlowInlineBlock inl } private static void appendLink(StringBuilder html, LytFlowLink link, @Nullable ResourceLocation currentPageId, - GuideSiteItemIconResolver itemIconResolver, @Nullable GuideSiteTemplateRegistry templates, - boolean allowNestedItemTooltips) { + @Nullable GuideSitePageAssetExporter assetExporter, GuideSiteItemIconResolver itemIconResolver, + @Nullable GuideSiteTemplateRegistry templates, boolean allowNestedItemTooltips) { String href = null; PageAnchor pageAnchor = link.getPageAnchor(); if (pageAnchor != null) { @@ -1028,7 +1266,7 @@ private static void appendLink(StringBuilder html, LytFlowLink link, @Nullable R link, href != null && !href.isEmpty() ? "a" : "span", currentPageId, - null, + assetExporter, itemIconResolver, templates, allowNestedItemTooltips, @@ -1041,14 +1279,37 @@ private static void appendStyledFlowContainer(StringBuilder html, LytFlowSpan sp boolean allowNestedItemTooltips, @Nullable String href) { html.append("<") .append(tagName); + String templateId = null; + if (templates != null && span instanceof LytTooltipSpan tooltipSpan) { + templateId = createTemplateId( + tooltipSpan.getTooltip(0, 0) + .orElse(null), + templates, + currentPageId, + assetExporter, + itemIconResolver); + } if ("a".equals(tagName) && href != null && !href.isEmpty()) { html.append(" href=\"") .append(escapeAttribute(href)) .append("\""); } boolean insideSpoiler = isInsideSpoiler(span); - if (span instanceof LytSpoilerSpan) { - html.append(" class=\"guide-spoiler\" tabindex=\"0\""); + boolean spoiler = span instanceof LytSpoilerSpan; + if (templateId != null || spoiler) { + html.append(" class=\"") + .append(templateId != null ? "guide-tooltip" : "") + .append(templateId != null && spoiler ? " " : "") + .append(spoiler ? "guide-spoiler" : "") + .append("\""); + } + if (templateId != null) { + html.append(" data-template=\"") + .append(escapeAttribute(templateId)) + .append("\""); + } + if (spoiler) { + html.append(" tabindex=\"0\""); } appendInlineStyleAttribute(html, span.resolveStyle(), insideSpoiler); html.append(">"); @@ -1071,6 +1332,766 @@ private static boolean isInsideSpoiler(LytFlowContent content) { return content.findAncestor(LytSpoilerSpan.class) != null; } + private static void appendBlockChildren(StringBuilder html, LytNode node, + @Nullable ResourceLocation currentPageId, @Nullable GuideSitePageAssetExporter assetExporter, + GuideSiteItemIconResolver itemIconResolver, @Nullable GuideSiteTemplateRegistry templates, + boolean allowNestedItemTooltips) { + for (LytNode child : node.getChildren()) { + if (child instanceof LytBlock childBlock) { + appendBlock( + html, + childBlock, + currentPageId, + assetExporter, + itemIconResolver, + templates, + allowNestedItemTooltips); + } + } + } + + private static void appendCodeBlock(StringBuilder html, LytCodeBlock codeBlock, + @Nullable ResourceLocation currentPageId, @Nullable GuideSitePageAssetExporter assetExporter, + GuideSiteItemIconResolver itemIconResolver, @Nullable GuideSiteTemplateRegistry templates, + boolean allowNestedItemTooltips) { + String language = codeBlock.getDetectedLanguageId(); + if ((language == null || language.isBlank() || "text".equalsIgnoreCase(language)) + && codeBlock.getLanguageFenceName() != null + && !codeBlock.getLanguageFenceName() + .isBlank()) { + language = codeBlock.getLanguageFenceName(); + } + String source = codeBlock.getCodeText() != null ? codeBlock.getCodeText() : ""; + if ("csv".equalsIgnoreCase(language)) { + html.append(GuideSiteGraphRenderer.renderCsvTable(source, true)); + return; + } + if ("tree".equalsIgnoreCase(language) || "filetree".equalsIgnoreCase(language)) { + html.append(GuideSiteGraphRenderer.renderFileTree(source)); + return; + } + if ("mermaid".equalsIgnoreCase(language)) { + appendMermaidSource(html, source); + return; + } + html.append("
 0) {
+                html.append(" style=\"max-height:")
+                    .append(codeBlock.getForcedBodyHeight())
+                    .append("px;overflow:auto;\"");
+            }
+            html.append(">")
+                .append(escapeHtml(source))
+                .append("
"); + } + + private static void appendMermaidSource(StringBuilder html, String source) { + try { + MermaidDiagramType type = MermaidDiagramType.detect(source); + if (type == MermaidDiagramType.MINDMAP) { + html.append(GuideSiteGraphRenderer.renderMermaidTree(MindmapParser.parse(source))); + } else if (type == MermaidDiagramType.FLOWCHART) { + html.append(GuideSiteGraphRenderer.renderFlowchart(FlowchartParser.parse(source))); + } else { + html.append("
")
+                        .append(escapeHtml(source))
+                        .append("
"); + } + } catch (RuntimeException exception) { + html.append("
")
+                    .append(escapeHtml(source))
+                    .append("
"); + } + } + + private static void appendFileTree(StringBuilder html, LytFileTree fileTree, + @Nullable ResourceLocation currentPageId, @Nullable GuideSitePageAssetExporter assetExporter, + GuideSiteItemIconResolver itemIconResolver, @Nullable GuideSiteTemplateRegistry templates, + boolean allowNestedItemTooltips) { + html.append("
"); + for (LytFileTree.Row row : fileTree.getRows()) { + html.append("
"); + html.append("") + .append(escapeHtml(fileTreePrefix(row.getSlots()))) + .append(""); + if (row.getIconBlock() != null) { + html.append(""); + appendBlock( + html, + row.getIconBlock(), + currentPageId, + assetExporter, + itemIconResolver, + templates, + allowNestedItemTooltips); + html.append(""); + } + html.append(""); + appendBlock( + html, + row.getPayload(), + currentPageId, + assetExporter, + itemIconResolver, + templates, + allowNestedItemTooltips); + html.append(""); + html.append("
"); + } + html.append("
"); + } + + private static String fileTreePrefix(List slots) { + StringBuilder prefix = new StringBuilder(); + for (SlotKind slot : slots) { + switch (slot) { + case VERTICAL -> prefix.append("| "); + case BRANCH -> prefix.append("|-- "); + case LAST_BRANCH -> prefix.append("`-- "); + case EMPTY -> prefix.append(" "); + } + } + return prefix.toString(); + } + + private static void appendGuiSprite(StringBuilder html, LytGuiSprite sprite, + @Nullable GuideSitePageAssetExporter assetExporter) { + int width = Math.max( + 1, + sprite.getSize() + .width()); + int height = Math.max( + 1, + sprite.getSize() + .height()); + if (sprite.getSprite() == null || assetExporter == null) { + html.append(""); + return; + } + String src = assetExporter.exportResource( + sprite.getSprite() + .getTexture()); + if (src.isEmpty()) { + html.append(""); + return; + } + html.append(""); + } + + private static void appendStructureView(StringBuilder html, LytStructureView structure, + GuideSiteItemIconResolver itemIconResolver, @Nullable ResourceLocation currentPageId, + @Nullable GuideSitePageAssetExporter assetExporter, @Nullable GuideSiteTemplateRegistry templates, + boolean allowNestedItemTooltips) { + html.append("
"); + for (LytStructureView.BlockEntry entry : structure.getBlocks()) { + GuideSiteExportedItem item = GuideSiteItemSupport.export(entry.stack, itemIconResolver); + String templateId = allowNestedItemTooltips && templates != null + ? createNestedItemTemplateId(entry.stack, currentPageId, assetExporter, itemIconResolver, templates) + : null; + html.append(""); + GuideSiteItemHtml.appendIcon(html, item, "guide-structure-block-icon"); + html.append(""); + } + html.append("
"); + } + + private static void appendLatex(StringBuilder html, String formula, int fillColorArgb, float sourceScale, + float userScale, @Nullable GuideTooltip tooltip, boolean display, int offsetX, int offsetY, + @Nullable ResourceLocation currentPageId, @Nullable GuideSitePageAssetExporter assetExporter, + GuideSiteItemIconResolver itemIconResolver, @Nullable GuideSiteTemplateRegistry templates) { + GuideSiteLatexExporter.ExportedLatex exported = assetExporter != null ? assetExporter.latexExporter() + .export(formula, fillColorArgb, sourceScale) : null; + String templateId = templates != null + ? createTemplateId( + tooltip != null ? tooltip : new TextTooltip(formula), + templates, + currentPageId, + assetExporter, + itemIconResolver) + : null; + String tag = display ? "div" : "span"; + html.append("<") + .append(tag) + .append(" class=\"guide-latex ") + .append(display ? "guide-latex-display" : "guide-latex-inline"); + if (templateId != null) { + html.append(" guide-tooltip\" data-template=\"") + .append(escapeAttribute(templateId)); + } else { + html.append("\""); + } + if (offsetX != 0 || offsetY != 0) { + html.append(" style=\"transform:translate(") + .append(offsetX) + .append("px,") + .append(offsetY) + .append("px)\""); + } + html.append(">"); + if (exported != null) { + int width = Math.max(1, Math.round(exported.widthPx() * userScale)); + int height = Math.max(1, Math.round(exported.heightPx() * userScale)); + html.append("\"")"); + } else { + html.append(escapeHtml(formula)); + } + html.append(""); + } + + private static void appendContentTabs(StringBuilder html, LytContentTabsBlock tabs, + @Nullable ResourceLocation currentPageId, @Nullable GuideSitePageAssetExporter assetExporter, + GuideSiteItemIconResolver itemIconResolver, @Nullable GuideSiteTemplateRegistry templates, + boolean allowNestedItemTooltips) { + html.append("
"); + List children = tabs.getChildren(); + if (!children.isEmpty() && children.getFirst() instanceof LytParagraph title) { + appendBlock( + html, + title, + currentPageId, + assetExporter, + itemIconResolver, + templates, + allowNestedItemTooltips); + } + html.append("
"); + for (int i = 1; i < children.size(); i++) { + appendBlock( + html, + children.get(i), + currentPageId, + assetExporter, + itemIconResolver, + templates, + allowNestedItemTooltips); + } + html.append("
"); + } + + private static GuideSiteGraphRenderer.ChartStyle chartStyle(LytChartBase chart, + @Nullable ChartAxisOptions xAxis, @Nullable ChartAxisOptions yAxis, float barWidthRatio, + float pieStartAngle, boolean pieClockwise) { + return new GuideSiteGraphRenderer.ChartStyle( + chart.getExplicitWidth(), + chart.getExplicitHeight(), + chart.getBackgroundColor(), + chart.getBorderColor(), + chart.getTitle(), + chart.getLegendPosition(), + chart.getLabelPosition(), + chart.getLabelColor(), + SiteChartAxis.from(xAxis, GuideSiteGraphRenderer.DEFAULT_GRID_COLOR), + SiteChartAxis.from(yAxis, GuideSiteGraphRenderer.DEFAULT_GRID_COLOR), + chart.getTitleColor(), + barWidthRatio, + pieStartAngle, + pieClockwise); + } + + private static List seriesData(List series) { + List result = new ArrayList<>(); + for (ChartSeries item : series) { + result.add( + new GuideSiteGraphRenderer.SeriesData(item.getName(), item.getColor(), item.getXs(), item.getYs())); + } + return result; + } + + private static String[] categories(String[] values) { + return values != null ? values : new String[0]; + } + + private static String renderColumnChart(LytColumnChart chart) { + GuideSiteGraphRenderer.ChartStyle style = chartStyle( + chart, + chart.getXAxis(), + chart.getYAxis(), + chart.getBarWidthRatio(), + -90f, + true); + GuideSiteGraphRenderer.PieInsetData inset = chart.getPieInset() != null ? pieInsetData(chart.getPieInset()) + : null; + List series = seriesData(chart.getSeries()); + for (ChartSeries overlay : chart.getLineOverlays()) { + series.add( + new GuideSiteGraphRenderer.SeriesData( + overlay.getName(), + overlay.getColor(), + overlay.getXs(), + overlay.getYs(), + GuideSiteGraphRenderer.SeriesData.TYPE_LINE)); + } + return GuideSiteGraphRenderer + .renderColumnChart(style, categories(chart.getCategories()), series, inset, null); + } + + private static String renderBarChart(LytBarChart chart) { + return GuideSiteGraphRenderer.renderBarChart( + chartStyle(chart, chart.getXAxis(), chart.getYAxis(), chart.getBarWidthRatio(), -90f, true), + categories(chart.getCategories()), + seriesData(chart.getSeries())); + } + + private static String renderLineChart(LytLineChart chart) { + return GuideSiteGraphRenderer.renderLineChart( + chartStyle(chart, chart.getXAxis(), chart.getYAxis(), 0.7f, -90f, true), + categories(chart.getCategories()), + seriesData(chart.getSeries()), + chart.isNumericX(), + chart.isShowPoints(), + chart.getCornerLegendPosition(), + chart.getCornerLegendWidth(), + chart.getCornerLegendHeight(), + chart.getCornerLegendBackgroundColor()); + } + + private static String renderPieChart(LytPieChart chart) { + List slices = new ArrayList<>(); + for (PieSlice slice : chart.getSlices()) { + slices.add(new GuideSiteGraphRenderer.SliceData(slice.getLabel(), slice.getValue(), slice.getColor())); + } + return GuideSiteGraphRenderer.renderPieChart( + chartStyle(chart, null, null, 0.7f, chart.getStartAngleDeg(), chart.isClockwise()), + slices); + } + + private static String renderScatterChart(LytScatterChart chart) { + return GuideSiteGraphRenderer.renderScatterChart( + chartStyle(chart, chart.getXAxis(), chart.getYAxis(), 0.7f, -90f, true), + seriesData(chart.getSeries()), + chart.getCornerLegendPosition(), + chart.getCornerLegendWidth(), + chart.getCornerLegendHeight(), + chart.getCornerLegendBackgroundColor()); + } + + private static GuideSiteGraphRenderer.PieInsetData pieInsetData(PieInsetSpec inset) { + List slices = new ArrayList<>(); + for (PieSlice slice : inset.getSlices()) { + slices.add(new GuideSiteGraphRenderer.SliceData(slice.getLabel(), slice.getValue(), slice.getColor())); + } + return new GuideSiteGraphRenderer.PieInsetData( + slices, + inset.getSize(), + inset.getPosition() + .name() + .toLowerCase(Locale.ROOT) + .replace('_', '-'), + inset.getTitle()); + } + + private static void appendImageBlock(StringBuilder html, LytImageBlock image, + @Nullable ResourceLocation currentPageId, @Nullable GuideSitePageAssetExporter assetExporter, + GuideSiteItemIconResolver itemIconResolver, @Nullable GuideSiteTemplateRegistry templates) { + String source = image.getSrc(); + if (source == null || source.isBlank()) { + return; + } + String src = source; + if (assetExporter != null) { + try { + src = assetExporter.exportResource(new ResourceLocation(source)); + } catch (IllegalArgumentException e) { + GuideDebugLog.warnAlways("[GuideNH] [SceneExport] Invalid tooltip image source: {}", source); + return; + } + } + if (src.isEmpty()) { + GuideDebugLog.warnAlways("[GuideNH] [SceneExport] Missing tooltip image: {}", source); + return; + } + + boolean cropped = image.getCropWidth() > 0 && image.getCropHeight() > 0; + int displayWidth = image.getDisplayWidth(); + int displayHeight = image.getDisplayHeight(); + if (cropped) { + if (displayWidth <= 0 && displayHeight > 0) { + displayWidth = Math.max( + 1, + (int) Math.round(displayHeight * image.getCropWidth() / (double) image.getCropHeight())); + } else if (displayWidth <= 0) { + displayWidth = Math.max(1, (int) Math.round(image.getCropWidth() * image.getScaleX())); + } + if (displayHeight <= 0) { + displayHeight = Math + .max(1, (int) Math.round(displayWidth * image.getCropHeight() / (double) image.getCropWidth())); + } + } + + html.append(" 0) { + html.append(" style=\"width:") + .append(displayWidth) + .append("px;\""); + } + html.append(">\"") 0 && displayWidth <= 0) { + html.append(" data-display-height=\"") + .append(displayHeight) + .append("\""); + } + html.append(" loading=\"lazy\" decoding=\"async\">"); + for (ImageRegionAnnotation annotation : image.getAnnotations()) { + String templateId = templates != null + ? createTemplateId( + annotation.getTooltip(), + templates, + currentPageId, + assetExporter, + itemIconResolver) + : null; + html.append(""); + } + html.append(""); + if (image.getTitle() != null && !image.getTitle() + .isEmpty()) { + html.append("") + .append(escapeHtml(image.getTitle())) + .append(""); + } + html.append(""); + } + + private static void appendBlockImage(StringBuilder html, BlockImagePlaceholder blockImage, + @Nullable ResourceLocation currentPageId, @Nullable GuideSitePageAssetExporter assetExporter, + GuideSiteItemIconResolver itemIconResolver, @Nullable GuideSiteTemplateRegistry templates, + boolean allowNestedItemTooltips) { + GuideItemReferenceResolver.ResolvedBlockReference block = GuideItemReferenceResolver + .resolveBlockReference("minecraft", blockImage.id, blockImage.ore); + if (block == null || block.stack() == null) { + html.append("") + .append( + escapeHtml( + blockImage.id != null ? blockImage.id + : blockImage.ore != null ? blockImage.ore : "BlockImage")) + .append(""); + return; + } + ItemStack stack = block.stack() + .copy(); + if (blockImage.meta != Integer.MIN_VALUE && stack.getItem() != null) { + stack.setItemDamage(blockImage.meta); + } + if (blockImage.nbt != null && !blockImage.nbt.isBlank()) { + try { + NBTTagCompound explicitTag = GuideTextNbtCodec.readTextSafeCompound(blockImage.nbt.trim()); + if (stack.stackTagCompound == null) { + stack.stackTagCompound = explicitTag; + } else { + for (String key : explicitTag.func_150296_c()) { + stack.stackTagCompound.setTag(key, explicitTag.getTag(key)); + } + } + } catch (Exception exception) { + GuideDebugLog.warnAlways( + "[GuideNH] [SceneExport] Invalid tooltip BlockImage NBT: {}", + blockImage.id != null ? blockImage.id : blockImage.ore); + } + } + String templateId = allowNestedItemTooltips && templates != null + ? createNestedItemTemplateId(stack, currentPageId, assetExporter, itemIconResolver, templates) + : null; + GuideSiteExportedItem item = GuideSiteItemSupport.export(stack, itemIconResolver); + html.append(""); + GuideSiteItemHtml.appendIcon(html, item, "guide-inline-item-icon", blockImage.scale); + html.append(""); + } + + private static void appendMermaidMindmap(StringBuilder html, LytMermaidMindmap mindmap, + @Nullable ResourceLocation currentPageId, @Nullable GuideSitePageAssetExporter assetExporter, + GuideSiteItemIconResolver itemIconResolver, @Nullable GuideSiteTemplateRegistry templates, + boolean allowNestedItemTooltips) { + Map nodeHtml = renderMermaidNodeContent( + mindmap.getNodeContent(), + currentPageId, + assetExporter, + itemIconResolver, + templates, + allowNestedItemTooltips); + html.append(GuideSiteGraphRenderer.renderMermaidTree(mindmap.getMindmap(), nodeHtml)); + } + + private static void appendMermaidFlowchart(StringBuilder html, LytMermaidFlowchart flowchart, + @Nullable ResourceLocation currentPageId, @Nullable GuideSitePageAssetExporter assetExporter, + GuideSiteItemIconResolver itemIconResolver, @Nullable GuideSiteTemplateRegistry templates, + boolean allowNestedItemTooltips) { + Map nodeHtml = renderMermaidNodeContent( + flowchart.getNodeContent(), + currentPageId, + assetExporter, + itemIconResolver, + templates, + allowNestedItemTooltips); + html.append(GuideSiteGraphRenderer.renderFlowchart(flowchart.getFlowchart(), nodeHtml)); + } + + private static void appendMermaidPlaceholder(StringBuilder html, MermaidPlaceholder mermaid, + @Nullable ResourceLocation currentPageId, @Nullable GuideSitePageAssetExporter assetExporter, + GuideSiteItemIconResolver itemIconResolver, @Nullable GuideSiteTemplateRegistry templates, + boolean allowNestedItemTooltips) { + String source = mermaid.sourceText != null ? mermaid.sourceText.trim() : ""; + if (source.isEmpty()) { + html.append("
Mermaid source is unavailable for this tooltip.
"); + return; + } + try { + Map nodeHtml = renderMermaidNodeContent( + mermaid.nodeContentBlocks, + currentPageId, + assetExporter, + itemIconResolver, + templates, + allowNestedItemTooltips); + if (MermaidDiagramType.MINDMAP == mermaid.diagramType) { + html.append(GuideSiteGraphRenderer.renderMermaidTree(MindmapParser.parse(source), nodeHtml)); + } else if (MermaidDiagramType.FLOWCHART == mermaid.diagramType) { + html.append(GuideSiteGraphRenderer.renderFlowchart(FlowchartParser.parse(source), nodeHtml)); + } else { + html.append("
")
+                        .append(escapeHtml(source))
+                        .append("
"); + } + } catch (RuntimeException exception) { + html.append("
")
+                    .append(escapeHtml(source))
+                    .append("
"); + } + } + + private static Map renderMermaidNodeContent(Map nodeContent, + @Nullable ResourceLocation currentPageId, @Nullable GuideSitePageAssetExporter assetExporter, + GuideSiteItemIconResolver itemIconResolver, @Nullable GuideSiteTemplateRegistry templates, + boolean allowNestedItemTooltips) { + Map result = new LinkedHashMap<>(); + for (Map.Entry entry : nodeContent.entrySet()) { + StringBuilder body = new StringBuilder(); + appendBlock( + body, + entry.getValue(), + currentPageId, + assetExporter, + itemIconResolver, + templates, + allowNestedItemTooltips); + result.put(entry.getKey(), body.toString()); + } + return result; + } + + private static void appendItemImage(StringBuilder html, ItemImagePlaceholder image, + @Nullable ResourceLocation currentPageId, @Nullable GuideSitePageAssetExporter assetExporter, + GuideSiteItemIconResolver itemIconResolver, @Nullable GuideSiteTemplateRegistry templates, + boolean allowNestedItemTooltips) { + ItemStack stack = GuideDisplayItemStacks.resolveItemStack(image.itemId, "minecraft"); + if (stack == null && image.ore != null) { + stack = GuideDisplayItemStacks.resolveOreStack(image.ore); + } + if (stack == null) { + html.append("") + .append(escapeHtml(image.itemId)) + .append(""); + return; + } + if (image.nbt != null && !image.nbt.isBlank()) { + try { + NBTTagCompound explicitTag = GuideTextNbtCodec.readTextSafeCompound(image.nbt.trim()); + if (stack.stackTagCompound == null) { + stack.stackTagCompound = explicitTag; + } else { + for (String key : explicitTag.func_150296_c()) { + stack.stackTagCompound.setTag(key, explicitTag.getTag(key)); + } + } + } catch (Exception e) { + GuideDebugLog.warnAlways("[GuideNH] [SceneExport] Invalid tooltip ItemImage NBT: {}", image.itemId); + } + } + + GuideSiteExportedItem item = GuideSiteItemSupport.export(stack, itemIconResolver); + String templateId = image.showTooltip && allowNestedItemTooltips && templates != null + ? createNestedItemTemplateId(stack, currentPageId, assetExporter, itemIconResolver, templates) + : null; + html.append(""); + String label = image.labelPosition != null ? stack.getDisplayName() : null; + if (label != null && "left".equals(image.labelPosition)) { + appendItemImageLabel(html, label, image); + } + if (!Boolean.FALSE.equals(image.showIcon)) { + GuideSiteItemHtml.appendIcon(html, item, "guide-inline-item-icon", image.scale); + } + if (label != null && !"left".equals(image.labelPosition)) { + appendItemImageLabel(html, label, image); + } + html.append(""); + } + + private static void appendItemImageLabel(StringBuilder html, String label, ItemImagePlaceholder image) { + String formatted = image.labelFormat != null && image.labelFormat.contains("%s") + ? image.labelFormat.replace("%s", label) + : image.labelFormat != null ? image.labelFormat : label; + html.append("") + .append(escapeHtml(formatted)) + .append(""); + } + private static void appendImage(StringBuilder html, LytImage image, @Nullable GuideSitePageAssetExporter assetExporter) { String src = ""; diff --git a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/SiteChartAxis.java b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/SiteChartAxis.java index 057a1411..8523b2cf 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/SiteChartAxis.java +++ b/src/main/java/com/hfstudio/guidenh/guide/siteexport/site/SiteChartAxis.java @@ -7,6 +7,7 @@ import com.hfstudio.guidenh.guide.compiler.tags.MdxAttrs; import com.hfstudio.guidenh.guide.compiler.tags.chart.ChartAttrParser; +import com.hfstudio.guidenh.guide.document.block.chart.ChartAxisOptions; import com.hfstudio.guidenh.libs.mdast.mdx.model.MdxJsxElementFields; public class SiteChartAxis { @@ -60,6 +61,21 @@ public static SiteChartAxis automatic(int defaultGridColor) { return new SiteChartAxis(null, null, null, null, null, null, true, defaultGridColor); } + public static SiteChartAxis from(ChartAxisOptions axis, int defaultGridColor) { + if (axis == null) { + return automatic(defaultGridColor); + } + return new SiteChartAxis( + axis.getLabel(), + axis.getMin(), + axis.getMax(), + axis.getStep(), + axis.getTickFormat(), + axis.getUnit(), + axis.isGridVisible(), + axis.getGridColor()); + } + @Nullable public String label() { return label; diff --git a/src/main/resources/assets/guidenh/siteexport/app.css b/src/main/resources/assets/guidenh/siteexport/app.css index ad667e0c..68d3f059 100644 --- a/src/main/resources/assets/guidenh/siteexport/app.css +++ b/src/main/resources/assets/guidenh/siteexport/app.css @@ -896,11 +896,44 @@ p { border-width: calc(1px * var(--gui-scale)); border-image-slice: 1; background: rgba(16, 0, 16, 0.94); + box-sizing: border-box; padding: calc(2px * var(--gui-scale)); line-height: 1; max-width: calc(220px * var(--gui-scale)); } +.guide-tooltip-popup:has(.guide-tooltip-layout) { + width: max-content; + max-width: min(calc(320px * var(--gui-scale)), calc(100vw - 24px)); +} + +.guide-tooltip-layout { + max-width: 100%; +} + +.guide-tooltip-layout > * { + min-width: 0; +} + +.guide-gui-sprite-placeholder { + display: inline-block; + min-width: 16px; + min-height: 16px; + background: rgba(128, 128, 128, 0.18); + border: 1px solid rgba(128, 128, 128, 0.5); + vertical-align: middle; +} + +.guide-gui-sprite { + vertical-align: middle; + image-rendering: pixelated; +} + +.guide-export-placeholder { + color: var(--guide-text-muted, rgba(190, 190, 190, 0.9)); + font-style: italic; +} + .guide-tooltip-popup::before { content: ""; position: absolute; @@ -950,6 +983,7 @@ p { border-width: calc(1px * var(--gui-scale)); border-image-slice: 1; background: rgba(16, 0, 16, 0.94); + box-sizing: border-box; padding: calc(2px * var(--gui-scale)); line-height: 1; max-width: calc(120px * var(--gui-scale));