diff options
| author | shedaniel <daniel@shedaniel.me> | 2022-02-17 15:38:59 +0800 |
|---|---|---|
| committer | shedaniel <daniel@shedaniel.me> | 2022-02-18 10:24:53 +0800 |
| commit | 732e0640dbf1dbe4b98d2a7f8682e9d10dcfac8f (patch) | |
| tree | 9d93217409cc6387b9a90d576320f792bb7b1365 /runtime/src/main/java | |
| parent | e0c9b792af72d971bdf59debd1f065521c37c126 (diff) | |
| download | RoughlyEnoughItems-732e0640dbf1dbe4b98d2a7f8682e9d10dcfac8f.tar.gz RoughlyEnoughItems-732e0640dbf1dbe4b98d2a7f8682e9d10dcfac8f.tar.bz2 RoughlyEnoughItems-732e0640dbf1dbe4b98d2a7f8682e9d10dcfac8f.zip | |
Implement changelog, close #772
Diffstat (limited to 'runtime/src/main/java')
8 files changed, 1858 insertions, 13 deletions
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java index 6f9c5a085..485b90271 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java @@ -80,7 +80,6 @@ import net.minecraft.nbt.TagParser; import net.minecraft.network.chat.*; import net.minecraft.util.Mth; import net.minecraft.world.InteractionResult; -import org.apache.commons.lang3.mutable.MutableLong; import org.jetbrains.annotations.ApiStatus; import java.util.*; @@ -371,7 +370,6 @@ public class ConfigManagerImpl implements ConfigManager { ((GlobalizedClothConfigScreen) screen).listWidget.children().add(0, (AbstractConfigEntry) feedbackEntry); ((GlobalizedClothConfigScreen) screen).listWidget.children().add(0, (AbstractConfigEntry) new EmptyEntry(4)); ScreenHooks.addRenderableWidget(screen, new Button(screen.width - 104, 4, 100, 20, new TranslatableComponent("text.rei.credits"), button -> { - MutableLong current = new MutableLong(0); CreditsScreen creditsScreen = new CreditsScreen(screen); Minecraft.getInstance().setScreen(creditsScreen); })); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/ScreenOverlayImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/ScreenOverlayImpl.java index 067e8d32e..7724f6d0a 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/ScreenOverlayImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/ScreenOverlayImpl.java @@ -62,6 +62,7 @@ import me.shedaniel.rei.impl.client.ClientHelperImpl; import me.shedaniel.rei.impl.client.REIRuntimeImpl; import me.shedaniel.rei.impl.client.config.ConfigManagerImpl; import me.shedaniel.rei.impl.client.config.ConfigObjectImpl; +import me.shedaniel.rei.impl.client.gui.changelog.ChangelogLoader; import me.shedaniel.rei.impl.client.gui.craftable.CraftableFilter; import me.shedaniel.rei.impl.client.gui.dragging.CurrentDraggingStack; import me.shedaniel.rei.impl.client.gui.modules.Menu; @@ -265,6 +266,21 @@ public class ScreenOverlayImpl extends ScreenOverlay { .containsMousePredicate((button, point) -> button.getBounds().contains(point) && isNotInExclusionZones(point.x, point.y)) .tooltipLine(new TranslatableComponent("text.rei.previous_page")) .focusable(false)); + Button changelogButton; + widgets.add(changelogButton = Widgets.createButton(new Rectangle(bounds.x + bounds.width - 18 - 18, bounds.y + (ConfigObject.getInstance().getSearchFieldLocation() == SearchFieldLocation.TOP_SIDE ? 24 : 0) + 5, 16, 16), new TranslatableComponent("")) + .onClick(button -> { + ChangelogLoader.show(); + }) + .containsMousePredicate((button, point) -> button.getBounds().contains(point) && isNotInExclusionZones(point.x, point.y)) + .tooltipLine(new TranslatableComponent("text.rei.changelog.title")) + .focusable(false)); + widgets.add(Widgets.createDrawableWidget((helper, matrices, mouseX, mouseY, delta) -> { + helper.setBlitOffset(helper.getBlitOffset() + 1); + RenderSystem.setShaderTexture(0, CHEST_GUI_TEXTURE); + Rectangle bounds = changelogButton.getBounds(); + helper.blit(matrices, bounds.x + 1, bounds.y + 2, !ChangelogLoader.hasVisited() ? 28 : 14, 0, 14, 14); + helper.setBlitOffset(helper.getBlitOffset() - 1); + })); widgets.add(rightButton = Widgets.createButton(new Rectangle(bounds.x + bounds.width - 18, bounds.y + (ConfigObject.getInstance().getSearchFieldLocation() == SearchFieldLocation.TOP_SIDE ? 24 : 0) + 5, 16, 16), new TranslatableComponent("text.rei.right_arrow")) .onClick(button -> { ENTRY_LIST_WIDGET.nextPage(); @@ -332,6 +348,7 @@ public class ScreenOverlayImpl extends ScreenOverlay { helper.setBlitOffset(helper.getBlitOffset() + 1); RenderSystem.setShaderTexture(0, CHEST_GUI_TEXTURE); helper.blit(matrices, configButtonArea.x + 3, configButtonArea.y + 3, 0, 0, 14, 14); + helper.setBlitOffset(helper.getBlitOffset() - 1); }) ), 0, 0, 600 @@ -349,7 +366,7 @@ public class ScreenOverlayImpl extends ScreenOverlay { }), 0, 0, 600))); } if (!ConfigObject.getInstance().isEntryListWidgetScrolled()) { - widgets.add(Widgets.createClickableLabel(new Point(bounds.x + (bounds.width / 2), bounds.y + (ConfigObject.getInstance().getSearchFieldLocation() == SearchFieldLocation.TOP_SIDE ? 24 : 0) + 10), NarratorChatListener.NO_TITLE, label -> { + widgets.add(Widgets.createClickableLabel(new Point(bounds.x + ((bounds.width - 18) / 2), bounds.y + (ConfigObject.getInstance().getSearchFieldLocation() == SearchFieldLocation.TOP_SIDE ? 24 : 0) + 10), NarratorChatListener.NO_TITLE, label -> { if (!Screen.hasShiftDown()) { ENTRY_LIST_WIDGET.setPage(0); ENTRY_LIST_WIDGET.updateEntriesPosition(); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/changelog/ChangelogLoader.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/changelog/ChangelogLoader.java new file mode 100644 index 000000000..cf0b3fc10 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/changelog/ChangelogLoader.java @@ -0,0 +1,117 @@ +package me.shedaniel.rei.impl.client.gui.changelog; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import dev.architectury.platform.Platform; +import me.shedaniel.rei.impl.client.gui.error.ErrorsEntryListWidget; +import me.shedaniel.rei.impl.client.gui.error.ErrorsScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TranslatableComponent; +import org.apache.commons.io.IOUtils; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Function; + +public class ChangelogLoader { + public interface Builder { + default void add(Component component) { + add(width -> new ErrorsEntryListWidget.TextEntry(component, width)); + } + + void add(Function<Integer, ErrorsEntryListWidget.Entry> function); + } + + private static Boolean visited = false; + + public static boolean hasVisited() { + if (visited == null) { + visited = false; + File file = Platform.getConfigFolder().resolve("roughlyenoughitems/changelog.txt").toFile(); + if (file.exists()) { + try (InputStreamReader reader = new FileReader(file)) { + String version = IOUtils.toString(reader).trim(); + + InputStream changesJsonStream = ChangelogLoader.class.getClassLoader().getResourceAsStream("roughlyenoughitems.changes.json"); + if (changesJsonStream != null) { + JsonObject object = JsonParser.parseReader(new InputStreamReader(changesJsonStream)) + .getAsJsonObject(); + String currentVersion = object.getAsJsonPrimitive("version").getAsString(); + if (currentVersion.equals(version)) { + visited = true; + } + } + } catch (IOException e) { + } + } + } + + return visited; + } + + public static void show() { + class BuilderImpl implements Builder { + private final List<Object> components = new ArrayList<>(); + + @Override + public void add(Function<Integer, ErrorsEntryListWidget.Entry> function) { + components.add(function); + } + } + + visited = true; + BuilderImpl builder = new BuilderImpl(); + + InputStream changesJsonStream = ChangelogLoader.class.getClassLoader().getResourceAsStream("roughlyenoughitems.changes.json"); + if (changesJsonStream == null) { + builder.add(new TranslatableComponent("rei.changelog.error.missingChangelogFile")); + } else { + JsonObject object = JsonParser.parseReader(new InputStreamReader(changesJsonStream)) + .getAsJsonObject(); + String version = object.getAsJsonPrimitive("version").getAsString(); + Path file = Platform.getConfigFolder().resolve("roughlyenoughitems/changelog.txt"); + try { + Files.write(file, version.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } catch (IOException e) { + e.printStackTrace(); + } + + InputStream changelogStream = ChangelogLoader.class.getClassLoader().getResourceAsStream("roughlyenoughitems/" + version + "/changelog.md"); + + if (changelogStream == null) { + builder.add(new TranslatableComponent("rei.changelog.error.missingChangelogFile")); + } else { + try { + JParseDown parseDown = new JParseDown(); + LinkedList<JParseDown.Block> blocks = parseDown.linesElements(IOUtils.readLines(changelogStream, StandardCharsets.UTF_8).toArray(new String[0])); + for (JParseDown.Block block : blocks) { + if (block.autoBreak) { + builder.add(width -> new ErrorsEntryListWidget.EmptyEntry(6)); + } + Builder blockBuilder = builder; + if (block instanceof JParseDown.BlockHeader) { + blockBuilder = function -> { + builder.add(width -> new ErrorsEntryListWidget.ScaledEntry(function.apply(Math.round(width / 1.5F)), 1.5F)); + }; + } + JParseDownToMinecraft.build(blockBuilder, block); + if (block.autoBreak) { + builder.add(width -> new ErrorsEntryListWidget.EmptyEntry(6)); + } + } + } catch (IOException e) { + builder.add(new TranslatableComponent("rei.changelog.error.failedToReadChangelogFile")); + } + } + } + + Minecraft.getInstance().setScreen(new ErrorsScreen(new TranslatableComponent("text.rei.changelog.title"), builder.components, Minecraft.getInstance().screen, true)); + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/changelog/JParseDown.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/changelog/JParseDown.java new file mode 100644 index 000000000..6165f6a7a --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/changelog/JParseDown.java @@ -0,0 +1,1316 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.gui.changelog; + +import java.util.*; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/* +Copyright (c) 2019 Ashur Rafiev +https://github.com/ashurrafiev/JParsedown +MIT Licence: https://github.com/ashurrafiev/JParsedown/blob/master/LICENSE + +This work is derived from Parsedown version 1.8.0-beta-5: +Copyright (c) 2013-2018 Emanuil Rusev +http://parsedown.org +*/ +@SuppressWarnings("RegExpRedundantEscape") +public class JParseDown { + public static final String version = "1.0.4"; + + public static class ReferenceData { + public String url; + public String title; + + public ReferenceData(String url, String title) { + this.url = url; + this.title = title; + } + } + + public static class Line { + public String body; + public String text; + public int indent; + + public Line(String line) { + body = line; + text = line.replaceFirst("^\\s+", ""); + indent = line.length() - text.length(); + } + } + + public abstract static class Component { + public String markup = null; + public boolean hidden = false; + public HashSet<Class<?>> nonNestables = new HashSet<>(); + } + + public interface BlockType<B extends Block> { + Block startBlock(JParseDown parseDown, Line line, Block block); + } + + public interface InlineType<L extends Inline> { + Inline inline(JParseDown parseDown, String text, String context); + } + + public abstract static class Block extends Component { + public boolean identified = false; + public int interrupted = 0; + public List<Inline> inlines; + public Boolean autoBreak = null; + + public boolean isContinuable() { + return false; + } + + public boolean isCompletable() { + return false; + } + + public Block continueBlock(Line line) { + return null; + } + + public Block completeBlock() { + return null; + } + + public abstract Collection<Inline> inline(JParseDown parseDown); + } + + public static class BlockParagraph extends Block { + public String text; + + public BlockParagraph(String text) { + this.text = text; + } + + public static Block startBlock(JParseDown parseDown, Line line, Block block) { + return new BlockParagraph(line.text); + } + + @Override + public boolean isContinuable() { + return false; + } + + @Override + public Block continueBlock(Line line) { + if (interrupted > 0) + return null; + text += "\n" + line.text; + return this; + } + + @Override + public Collection<Inline> inline(JParseDown parseDown) { + return parseDown.lineElements(text, nonNestables); + } + + @Override + public String toString() { + return "BlockParagraph{" + + "text='" + text + '\'' + + '}'; + } + } + + public static class BlockCode extends Block { + public String text; + + public BlockCode(String text) { + this.text = text; + } + + public static Block startBlock(JParseDown parseDown, Line line, Block block) { + if (block != null && block instanceof BlockParagraph && block.interrupted == 0) + return null; + if (line.indent >= 4) { + return new BlockCode(line.body.substring(4)); + } else + return null; + } + + @Override + public boolean isContinuable() { + return true; + } + + @Override + public Block continueBlock(Line line) { + if (line.indent >= 4) { + while (interrupted > 0) { + text += "\n"; + interrupted--; + } + text += "\n"; + text += line.body.substring(4); + return this; + } else + return null; + } + + @Override + public boolean isCompletable() { + return true; + } + + @Override + public Block completeBlock() { + return this; + } + + @Override + public Collection<Inline> inline(JParseDown parseDown) { + return parseDown.lineElements(text, nonNestables); + } + + @Override + public String toString() { + return "BlockCode{" + + "text='" + text + '\'' + + '}'; + } + } + + public static class BlockComment extends Block { + public String text; + public boolean closed = false; + + public BlockComment(String text) { + this.text = text; + } + + public static Block startBlock(JParseDown parseDown, Line line, Block block) { + if (parseDown.markupEscaped || parseDown.safeMode) + return null; + if (line.text.indexOf("<!--") == 0) { + BlockComment b = new BlockComment(line.body); + if (line.text.contains("-->")) + b.closed = true; + return b; + } else + return null; + } + + @Override + public boolean isContinuable() { + return true; + } + + @Override + public Block continueBlock(Line line) { + if (closed) + return null; + text += "\n" + line.body; + if (line.text.contains("-->")) + closed = true; + return this; + } + + @Override + public Collection<Inline> inline(JParseDown parseDown) { + return parseDown.lineElements(text, nonNestables); + } + + @Override + public String toString() { + return "BlockComment{" + + "text='" + text + '\'' + + '}'; + } + } + + public static class BlockFencedCode extends Block { + public final char marker; + public final int openerLength; + public final String infoString; + public String text = ""; + public boolean complete = false; + + public BlockFencedCode(char marker, int openerLength, String infoString) { + this.marker = marker; + this.openerLength = openerLength; + this.infoString = infoString; + } + + public static Block startBlock(JParseDown parseDown, Line line, Block block) { + char marker = line.text.charAt(0); + int openerLength = startSpan(line.text, marker); + if (openerLength < 3) + return null; + String infoString = line.text.substring(openerLength).trim(); + if (infoString.contains("`")) + return null; + return new BlockFencedCode(marker, openerLength, infoString); + } + + @Override + public boolean isContinuable() { + return true; + } + + @Override + public Block continueBlock(Line line) { + if (complete) + return null; + while (interrupted > 0) { + text += "\n"; + interrupted--; + } + int len = startSpan(line.text, marker); + if (len >= openerLength && line.text.substring(len).trim().isEmpty()) { + if (!text.isEmpty()) + text = text.substring(1); + complete = true; + return this; + } + text += "\n" + line.body; + return this; + } + + @Override + public boolean isCompletable() { + return true; + } + + @Override + public Block completeBlock() { + return this; + } + + @Override + public Collection<Inline> inline(JParseDown parseDown) { + return parseDown.lineElements(text, nonNestables); + } + + @Override + public String toString() { + return "BlockFencedCode{" + + "infoString='" + infoString + '\'' + + ", text='" + text + '\'' + + '}'; + } + } + + public static class BlockHeader extends Block { + public final int level; + public final String line; + + public BlockHeader(int level, String line) { + this.level = level; + this.line = line; + this.autoBreak = true; + } + + public static Block startBlock(JParseDown parseDown, Line line, Block block) { + int level = startSpan(line.text, '#'); + if (level > 6) + return null; + + String text = line.text.substring(level); + if (parseDown.strictMode && !text.isEmpty() && text.charAt(0) != ' ') + return null; + text = text.trim(); + + return new BlockHeader(level, text); + } + + @Override + public Collection<Inline> inline(JParseDown parseDown) { + return parseDown.lineElements(line, nonNestables); + } + + @Override + public String toString() { + return "BlockHeader{" + + "level=" + level + + ", line='" + line + '\'' + + '}'; + } + } + + public class BlockList extends Block { + public int indent; + public String pattern; + public boolean loose = false; + + public boolean ordered; + public String marker; + public String markerType; + public String markerTypeRegex; + + public LinkedList<String> lines = new LinkedList<>(); + + public BlockList() { + autoBreak = true; + } + + public static Block startBlock(JParseDown parseDown, Line line, Block block) { + boolean ordered; + String pattern; + if (Character.isDigit(line.text.charAt(0))) { + ordered = true; // ol + pattern = "[0-9]{1,9}+[.\\)]"; + } else { + ordered = false; // ul + pattern = "[*+-]"; + } + Matcher m = Pattern.compile("^(" + pattern + "([ ]++|$))(.*+)").matcher(line.text); + if (m.find()) { + String marker = m.group(1); + String body = m.group(3); + + int contentIndent = m.group(2).length(); + if (contentIndent >= 5) { + contentIndent--; + marker = marker.substring(0, -contentIndent); + while (contentIndent > 0) { + body = " " + body; + contentIndent--; + } + } else if (contentIndent == 0) { + marker += " "; + } + String markerWithoutWhitespace = marker.substring(0, marker.indexOf(' ')); + + BlockList b = parseDown.new BlockList(); + b.indent = line.indent; + b.pattern = pattern; + b.ordered = ordered; + b.marker = marker; + b.markerType = !ordered ? + markerWithoutWhitespace : + markerWithoutWhitespace.substring(markerWithoutWhitespace.length() - 1, markerWithoutWhitespace.length()); + b.markerTypeRegex = Pattern.quote(b.markerType); + + b.lines.add(body); + + return b; + } else + return null; + } + + @Override + public boolean isContinuable() { + return true; + } + + @Override + public Block continueBlock(Line line) { + if (interrupted > 0 && lines.isEmpty()) + return null; + + int requiredIndent = indent + marker.length(); + Matcher m; + if (line.indent < requiredIndent && ( + (ordered && (m = Pattern.compile("^[0-9]++" + markerTypeRegex + "(?:[ ]++(.*)|$)").matcher(line.text)).find()) || + (!ordered && (m = Pattern.compile("^" + markerTypeRegex + "(?:[ ]++(.*)|$)").matcher(line.text)).find()) + )) { + if (interrupted > 0) { + lines.add(""); + loose = true; + interrupted = 0; + } + String text = m.group(1) != null ? m.group(1) : ""; + indent = line.indent; + lines.add(text); + return this; + } else if (line.indent < requiredIndent && BlockList.startBlock(JParseDown.this, line, null) != null) { + return null; + } + + if (line.text.charAt(0) == '[' && BlockReference.startBlock(JParseDown.this, line, null) != null) { + return this; + } + + if (line.indent >= requiredIndent) { + if (interrupted > 0) { + lines.add(""); + loose = true; + interrupted = 0; + } + String text = line.body.substring(requiredIndent); + lines.add(text); + return this; + } + + if (interrupted == 0) { + String text = line.body.replaceAll("^[ ]{0," + requiredIndent + "}+", ""); + lines.add(text); + return this; + } + + return null; + } + + @Override + public boolean isCompletable() { + return true; + } + + @Override + public Block completeBlock() { + if (loose) { + if (!lines.getLast().isEmpty()) + lines.add(""); + } + return this; + } + + @Override + public Collection<Inline> inline(JParseDown parseDown) { + LinkedList<Inline> inlines = new LinkedList<>(); + for (String line : lines) { + if (!inlines.isEmpty()) + inlines.add(new InlineLineBreak()); + inlines.addAll(parseDown.lineElements(line, nonNestables)); + } + return inlines; + } + + @Override + public String toString() { + return "BlockList{" + + "lines=[" + String.join(", ", lines) + + "]}"; + } + } + + public static class BlockQuote extends Block { + public final LinkedList<String> lines = new LinkedList<>(); + + public BlockQuote(String text) { + if (text != null) + lines.add(text); + } + + public static Block startBlock(JParseDown parseDown, Line line, Block block) { + Matcher m; + if ((m = Pattern.compile("^>[ ]?+(.*+)").matcher(line.text)).find()) { + return new BlockQuote(m.group(1)); + } else + return null; + } + + @Override + public boolean isContinuable() { + return true; + } + + @Override + public Block continueBlock(Line line) { + if (interrupted > 0) + return null; + Matcher m; + if (line.text.charAt(0) == '>' && (m = Pattern.compile("^>[ ]?+(.*+)").matcher(line.text)).find()) { + lines.add(m.group(1)); + return this; + } + if (interrupted == 0) { + lines.add(line.text); + return this; + } + return null; + } + + @Override + public Collection<Inline> inline(JParseDown parseDown) { + LinkedList<Inline> inlines = new LinkedList<>(); + for (String line : lines) { + if (!inlines.isEmpty()) + inlines.add(new InlineLineBreak()); + inlines.addAll(parseDown.lineElements(line, nonNestables)); + } + return inlines; + } + + @Override + public String toString() { + return "BlockQuote{" + + "lines=[" + String.join(", ", lines) + + "]}"; + } + } + + public static class BlockRule { + public static Block startBlock(JParseDown parseDown, Line line, Block block) { + char marker = line.text.charAt(0); + int count = startSpan(line.text, marker); + if (count >= 3 && line.text.trim().length() == count) { + return new BlockHorizontalRule(); + } else + return null; + } + } + + public static class BlockHorizontalRule extends Block { + public BlockHorizontalRule() { + autoBreak = true; + } + + @Override + public Collection<Inline> inline(JParseDown parseDown) { + return Collections.singletonList(new InlineHorizontalRule()); + } + + @Override + public String toString() { + return "BlockHorizontalRule{}"; + } + } + + public static class InlineLineBreak extends Inline { + @Override + public String toString() { + return "InlineLineBreak{}"; + } + } + + public static class InlineHorizontalRule extends Inline { + @Override + public String toString() { + return "InlineHorizontalRule{}"; + } + } + + public static String regexHtmlAttribute = "[a-zA-Z_:][\\w:.-]*+(?:\\s*+=\\s*+(?:[^\"\\'=<>`\\s]+|\"[^\"]*+\"|\\'[^\\']*+\\'))?+"; + + public static class BlockReference extends Block { + public final String id; + public final ReferenceData data; + + public BlockReference(String id, ReferenceData data) { + this.id = id; + this.data = data; + } + + public static Block startBlock(JParseDown parseDown, Line line, Block block) { + Matcher m; + if (line.text.indexOf(']') >= 0 && (m = Pattern.compile("^\\[(.+?)\\]:[ ]*+<?(\\S+?)>?(?:[ ]+[\"\\'(](.+)[\"\\')])?[ ]*+$").matcher(line.text)).find()) { + String id = m.group(1).toLowerCase(); + ReferenceData data = new ReferenceData(parseDown.convertUrl(m.group(2)), m.group(3)); + parseDown.referenceDefinitio |
