");
+ in_code_block = true;
+ }
+ continue;
+ }
+
+ if (in_code_block) {
+ html.append (GLib.Markup.escape_text (line));
+ html.append ("\n");
+ continue;
+ }
+
+ // Headers
+ if (trimmed.has_prefix ("#")) {
+ if (list_type != "") {
+ html.append ("" + list_type + ">\n");
+ list_type = "";
+ }
+
+ if (trimmed.has_prefix ("######")) {
+ html.append ("").append (process_inline (trimmed.substring (6).strip ())).append ("
\n");
+ } else if (trimmed.has_prefix ("#####")) {
+ html.append ("").append (process_inline (trimmed.substring (5).strip ())).append ("
\n");
+ } else if (trimmed.has_prefix ("####")) {
+ html.append ("").append (process_inline (trimmed.substring (4).strip ())).append ("
\n");
+ } else if (trimmed.has_prefix ("###")) {
+ html.append ("").append (process_inline (trimmed.substring (3).strip ())).append ("
\n");
+ } else if (trimmed.has_prefix ("##")) {
+ html.append ("").append (process_inline (trimmed.substring (2).strip ())).append ("
\n");
+ } else if (trimmed.has_prefix ("#")) {
+ html.append ("").append (process_inline (trimmed.substring (1).strip ())).append ("
\n");
+ }
+ }
+ // Horizontal rule
+ else if (trimmed == "---" || trimmed == "***" || trimmed == "___") {
+ if (list_type != "") {
+ html.append ("" + list_type + ">\n");
+ list_type = "";
+ }
+ html.append ("
\n");
+ }
+ // Blockquote
+ else if (trimmed.has_prefix (">")) {
+ if (list_type != "") {
+ html.append ("" + list_type + ">\n");
+ list_type = "";
+ }
+ html.append ("").append (process_inline (trimmed.substring (1).strip ())).append ("
\n");
+ }
+ // Unordered list
+ else if (trimmed.has_prefix ("* ") || trimmed.has_prefix ("- ") || trimmed.has_prefix ("+ ")) {
+ if (list_type == "ol") {
+ html.append ("\n");
+ list_type = "";
+ }
+ if (list_type == "") {
+ html.append ("\n");
+ list_type = "ul";
+ }
+ html.append ("- ").append (process_inline (trimmed.substring (2))).append ("
\n");
+ }
+ // Ordered list
+ else if (trimmed.length > 2 && trimmed[0].isdigit () && trimmed[1] == '.' && trimmed[2] == ' ') {
+ if (list_type == "ul") {
+ html.append ("
\n");
+ list_type = "";
+ }
+ if (list_type == "") {
+ html.append ("\n");
+ list_type = "ol";
+ }
+ html.append ("- ").append (process_inline (trimmed.substring (3))).append ("
\n");
+ }
+ // Empty line
+ else if (trimmed == "") {
+ if (list_type != "") {
+ html.append ("" + list_type + ">\n");
+ list_type = "";
+ }
+ html.append ("
\n");
+ }
+ // Regular paragraph
+ else {
+ if (list_type != "") {
+ html.append ("" + list_type + ">\n");
+ list_type = "";
+ }
+ html.append ("").append (process_inline (line)).append ("
\n");
+ }
+ }
+
+ if (list_type != "") {
+ html.append ("" + list_type + ">\n");
+ }
+
+ html.append ("");
+ return html.str;
+ }
+
+ private string process_inline (string text) {
+ var result = GLib.Markup.escape_text (text);
+
+ // Bold **text** or __text__
+ try {
+ var bold_regex = new Regex ("\\*\\*(.+?)\\*\\*");
+ result = bold_regex.replace (result, -1, 0, "\\1");
+
+ var bold_regex2 = new Regex ("__(.+?)__");
+ result = bold_regex2.replace (result, -1, 0, "\\1");
+
+ // Italic *text* or _text_
+ var italic_regex = new Regex ("\\*(.+?)\\*");
+ result = italic_regex.replace (result, -1, 0, "\\1");
+
+ var italic_regex2 = new Regex ("_(.+?)_");
+ result = italic_regex2.replace (result, -1, 0, "\\1");
+
+ // Inline code `code`
+ var code_regex = new Regex ("`(.+?)`");
+ result = code_regex.replace (result, -1, 0, "\\1");
+
+ // Images  - MUST BE BEFORE LINKS
+ var img_regex = new Regex ("!\\[(.+?)\\]\\((.+?)\\)");
+ result = img_regex.replace (result, -1, 0, "
");
+
+ // Links [text](url)
+ var link_regex = new Regex ("\\[(.+?)\\]\\((.+?)\\)");
+ result = link_regex.replace (result, -1, 0, "\\1");
+ } catch (RegexError e) {
+ warning ("Regex error: %s", e.message);
+ }
+
+ return result;
+ }
+
+ public void deactivate () {
+ // Clean up all preview states
+ foreach (var doc in preview_states.keys) {
+ if (preview_states.get (doc).visible) {
+ hide_preview (doc);
+ }
+ }
+ preview_states.clear ();
+
+ // Disconnect main signal handlers
+ if (plugins != null) {
+ if (hook_toolbar_id > 0) {
+ plugins.disconnect (hook_toolbar_id);
+ hook_toolbar_id = 0;
+ }
+ if (hook_document_id > 0) {
+ plugins.disconnect (hook_document_id);
+ hook_document_id = 0;
+ }
+ }
+
+ // Disconnect from current source
+ if (current_source != null) {
+ current_source.notify["language"].disconnect (on_language_changed);
+ current_source = null;
+ }
+
+ if (toolbar != null && preview_button != null && preview_button.parent != null) {
+ toolbar.remove (preview_button);
+ }
+
+ if (update_timeout_id > 0) {
+ Source.remove (update_timeout_id);
+ update_timeout_id = 0;
+ }
+ }
+}
+
+[ModuleInit]
+public void peas_register_types (TypeModule module) {
+ var objmodule = module as Peas.ObjectModule;
+ objmodule.register_extension_type (typeof (Scratch.Services.ActivatablePlugin),
+ typeof (Code.Plugins.MarkdownPreview));
+}
diff --git a/plugins/markdown-preview/meson.build b/plugins/markdown-preview/meson.build
new file mode 100644
index 000000000..442104077
--- /dev/null
+++ b/plugins/markdown-preview/meson.build
@@ -0,0 +1,34 @@
+module_name = 'markdown-preview'
+
+module_files = [
+ 'markdown-preview.vala',
+]
+
+module_deps = [
+ codecore_dep,
+ dependency('webkit2gtk-4.1'),
+ dependency('gee-0.8'),
+]
+
+shared_module(
+ module_name,
+ module_files,
+ dependencies: module_deps,
+ install: true,
+ install_dir: pluginsdir / module_name,
+)
+
+custom_target(module_name + '.plugin_merge',
+ input: module_name + '.plugin',
+ output: module_name + '.plugin',
+ command : [msgfmt,
+ '--desktop',
+ '--keyword=Description',
+ '--keyword=Name',
+ '-d' + meson.project_source_root () / 'po' / 'plugins',
+ '--template=@INPUT@',
+ '-o@OUTPUT@',
+ ],
+ install : true,
+ install_dir: pluginsdir / module_name,
+)
diff --git a/plugins/meson.build b/plugins/meson.build
index 906dcd078..38111d213 100644
--- a/plugins/meson.build
+++ b/plugins/meson.build
@@ -11,3 +11,4 @@ subdir('spell')
subdir('vim-emulation')
subdir('word-completion')
subdir('fuzzy-search')
+subdir('markdown-preview')
diff --git a/po/extra/zh_Hant.po b/po/extra/zh_Hant (Case Conflict).po
similarity index 100%
rename from po/extra/zh_Hant.po
rename to po/extra/zh_Hant (Case Conflict).po
diff --git a/po/plugins/zh_Hant.po b/po/plugins/zh_Hant (Case Conflict).po
similarity index 100%
rename from po/plugins/zh_Hant.po
rename to po/plugins/zh_Hant (Case Conflict).po
diff --git a/po/zh_Hant.po b/po/zh_Hant (Case Conflict).po
similarity index 100%
rename from po/zh_Hant.po
rename to po/zh_Hant (Case Conflict).po