Bladeren bron

Basic markdown support added

master
Rocketsoup 3 jaren geleden
bovenliggende
commit
076477343b
1 gewijzigde bestanden met toevoegingen van 200 en 1 verwijderingen
  1. 200
    1
      htdocs/index.php

+ 200
- 1
htdocs/index.php Bestand weergeven

890
 	}
890
 	}
891
 
891
 
892
 	public static function render_post(Post $post): void {
892
 	public static function render_post(Post $post): void {
893
-		$body_html = self::post_body_html($post->body);
893
+		$body_html = Markdown::markdown_to_html($post->body);
894
 		$date = localized_date_string($post->created);
894
 		$date = localized_date_string($post->created);
895
 		print(<<<HTML
895
 		print(<<<HTML
896
 			<div class="post">
896
 			<div class="post">
1000
 	}
1000
 	}
1001
 }
1001
 }
1002
 
1002
 
1003
+class HTMLBlock {
1004
+	public HTMLBlockType $type;
1005
+	public int $indent;
1006
+	public string $content_markdown;
1007
+
1008
+	function __construct(HTMLBlockType $type, int $indent, string $content_markdown) {
1009
+		$this->type = $type;
1010
+		$this->indent = $indent;
1011
+		$this->content_markdown = $content_markdown;
1012
+	}
1013
+}
1014
+
1015
+enum HTMLBlockType {
1016
+	case Plain;
1017
+	case ListItem;
1018
+	case BlockQuote;
1019
+	case Preformatted;
1020
+	case H1;
1021
+	case H2;
1022
+	case H3;
1023
+	case H4;
1024
+	case H5;
1025
+	case H6;
1026
+}
1027
+
1028
+class Markdown {
1029
+	/// Converts one line of markdown to HTML.
1030
+	/// @param string $markdown  Markdown string
1031
+	/// @return string  HTML
1032
+	public static function line_markdown_to_html(string $markdown): string {
1033
+		$html = htmlentities($markdown);
1034
+		// Explicit URL [label](URL)
1035
+		$html = preg_replace('|\[(.*?)\]\((.*?)\)|',
1036
+			'<a referrerpolicy="no-referrer" target="_new" href="$2">$1</a>', $html);
1037
+		// Implicit URL
1038
+		$html = preg_replace('|(?<!href=")(http(?:s)?://)(\S+[^\s\.,\?!:;"\'\)])|',
1039
+			'<a referrerpolicy="no-referrer" target="_new" href="$1$2">$2</a>', $html);
1040
+		// Italic
1041
+		$html = preg_replace('/__(\S|\S.*?)__/', '<em>$1</em>', $html);
1042
+		// Bold
1043
+		$html = preg_replace('/\*\*(\S|\S.*?\S)\*\*/', '<strong>$1</strong>', $html);
1044
+		// Strikethrough
1045
+		$html = preg_replace('/~~(\S|\S.*?\S)~~/', '<strike>$1</strike>', $html);
1046
+		// Code
1047
+		$html = preg_replace('/`(\S|\S.*?\S)`/', '<code>$1</code>', $html);
1048
+		return $html;
1049
+	}
1050
+
1051
+	/// Converts markdown into an array of HTMLBlocks.
1052
+	/// @param string $markdown  Markdown string
1053
+	/// @return array  Array of HTMLBlocks
1054
+	private static function markdown_to_blocks(string $markdown): array {
1055
+		$prefix_to_linetype = array(
1056
+			'*' => HTMLBlockType::ListItem,
1057
+			'-' => HTMLBlockType::ListItem,
1058
+			'+' => HTMLBlockType::ListItem,
1059
+			'>' => HTMLBlockType::BlockQuote,
1060
+			'######' => HTMLBlockType::H6,
1061
+			'#####' => HTMLBlockType::H5,
1062
+			'####' => HTMLBlockType::H4,
1063
+			'###' => HTMLBlockType::H3,
1064
+			'##' => HTMLBlockType::H2,
1065
+			'#' => HTMLBlockType::H1,
1066
+		);
1067
+		$blocks = array();
1068
+		foreach (explode("\n", $markdown) as $line) {
1069
+			$trimmed_line = trim($line);
1070
+			$indent = intval(round((strlen($line) - strlen($trimmed_line)) / 4));
1071
+			$block_type = HTMLBlockType::Plain;
1072
+			$block_content = $trimmed_line;
1073
+			foreach ($prefix_to_linetype as $prefix => $type) {
1074
+				if ($trimmed_line == $prefix ||
1075
+					str_starts_with($trimmed_line, $prefix . ' ')) {
1076
+					$block_content = substr($trimmed_line, strlen($prefix));
1077
+					$block_type = $type;
1078
+					break;
1079
+				}
1080
+			}
1081
+			$blocks[] = new HTMLBlock($block_type, $indent, $block_content);
1082
+		}
1083
+		return $blocks;
1084
+	}
1085
+
1086
+	/// Converts markdown to HTML
1087
+	public static function markdown_to_html(string $markdown): string {
1088
+		return self::blocks_to_html(self::markdown_to_blocks($markdown));
1089
+	}
1090
+
1091
+	/// Converts an array of HTMLBlocks to HTML.
1092
+	private static function blocks_to_html(array $blocks): string {
1093
+		$html = '';
1094
+		$last_block = null;
1095
+		$tag_stack = array(); // stack of end tag strings for current open blocks
1096
+		foreach ($blocks as $block) {
1097
+			$is_empty = strlen($block->content_markdown) == 0;
1098
+			$is_last_empty = strlen($last_block?->content_markdown ?? '') == 0;
1099
+			$is_same_block = $block->type == $last_block?->type;
1100
+			if (!$is_same_block && sizeof($tag_stack) > 0) {
1101
+				foreach (array_reverse($tag_stack) as $tag) {
1102
+					$html .= $tag;
1103
+				}
1104
+				$tag_stack = array();
1105
+			}
1106
+			switch ($block->type) {
1107
+				case HTMLBlockType::Plain:
1108
+					if ($is_empty) {
1109
+						if ($is_last_empty) {
1110
+							// ignore two consecutive empty lines
1111
+						} else {
1112
+							$html .= array_pop($tag_stack);
1113
+						}
1114
+					} elseif ($is_last_empty) {
1115
+						$html .= "<p>" . self::line_markdown_to_html($block->content_markdown);
1116
+						$tag_stack[] = "</p>\n\n";
1117
+					} else {
1118
+						$html .= "<br/>\n" . self::line_markdown_to_html($block->content_markdown);
1119
+					}
1120
+					break;
1121
+				case HTMLBlockType::ListItem:
1122
+					if (!$is_same_block) {
1123
+						foreach (array_reverse($tag_stack) as $tag) {
1124
+							$html .= $tag;
1125
+						}
1126
+						$tag_stack = array();
1127
+						for ($i = 0; $i <= $block->indent; $i++) {
1128
+							$html .= "<ul>\n";
1129
+							$html .= "<li>";
1130
+							$tag_stack[] = "</ul>\n";
1131
+							$tag_stack[] = "</li>\n";
1132
+						}
1133
+						$html .= self::line_markdown_to_html($block->content_markdown);
1134
+					} elseif ($block->indent == $last_block->indent) {
1135
+						$html .= "</li>\n";
1136
+						$html .= "<li>" . self::line_markdown_to_html($block->content_markdown);
1137
+					} elseif ($block->indent > $last_block->indent) {
1138
+						// Deeper indent level
1139
+						for ($i = $last_block->indent; $i < $block->indent; $i++) {
1140
+							$html .= "<ul>\n<li>" . self::line_markdown_to_html($block->content_markdown);
1141
+							$tag_stack[] = "</ul>\n";
1142
+							$tag_stack[] = "</li>\n";
1143
+						}
1144
+					} elseif ($block->indent < $last_block->indent) {
1145
+						// Shallower indent level
1146
+						for ($i = $block->indent; $i < $last_block->indent; $i++) {
1147
+							$html .= array_pop($tag_stack);
1148
+							$html .= array_pop($tag_stack);
1149
+						}
1150
+						$html .= "</li>\n";
1151
+						$html .= "<li>" . self::line_markdown_to_html($block->content_markdown);
1152
+					}
1153
+					break;
1154
+				case HTMLBlockType::BlockQuote:
1155
+					if ($is_same_block) {
1156
+						$html .= "<br/>\n";
1157
+					} else {
1158
+						$html .= "<blockquote>";
1159
+						$tag_stack[] = "</blockquote>\n\n";
1160
+					}
1161
+					$html .= self::line_markdown_to_html($block->content_markdown);
1162
+					break;
1163
+				case HTMLBlockType::Preformatted:
1164
+					if ($is_same_block) {
1165
+						$html .= "\n";
1166
+					} else {
1167
+						$html .= "<pre>";
1168
+						$tag_stack[] = "</pre>\n\n";
1169
+					}
1170
+					$html .= htmlentities($block->content_markdown);
1171
+					break;
1172
+				case HTMLBlockType::H1:
1173
+					$html .= '<h1>' . self::line_markdown_to_html($block->content_markdown) . "</h1>\n\n";
1174
+					break;
1175
+				case HTMLBlockType::H2:
1176
+					$html .= '<h2>' . self::line_markdown_to_html($block->content_markdown) . "</h2>\n\n";
1177
+					break;
1178
+				case HTMLBlockType::H3:
1179
+					$html .= '<h3>' . self::line_markdown_to_html($block->content_markdown) . "</h3>\n\n";
1180
+					break;
1181
+				case HTMLBlockType::H4:
1182
+					$html .= '<h4>' . self::line_markdown_to_html($block->content_markdown) . "</h4>\n\n";
1183
+					break;
1184
+				case HTMLBlockType::H5:
1185
+					$html .= '<h5>' . self::line_markdown_to_html($block->content_markdown) . "</h5>\n\n";
1186
+					break;
1187
+				case HTMLBlockType::H6:
1188
+					$html .= '<h6>' . self::line_markdown_to_html($block->content_markdown) . "</h6>\n\n";
1189
+					break;
1190
+			}
1191
+			$last_block = $block;
1192
+		}
1193
+		if (sizeof($tag_stack) > 0) {
1194
+			foreach (array_reverse($tag_stack) as $tag) {
1195
+				$html .= $tag;
1196
+			}
1197
+		}
1198
+		return $html;
1199
+	}
1200
+}
1201
+
1003
 // -- Main logic ------------------------------------------
1202
 // -- Main logic ------------------------------------------
1004
 
1203
 
1005
 check_setup();
1204
 check_setup();

Laden…
Annuleren
Opslaan