<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          基于Java和Bytemd用120行代碼實現(xiàn)一個桌面版Markdown編輯器

          共 14240字,需瀏覽 29分鐘

           ·

          2021-08-18 02:51

          前提

          某一天點開掘金的寫作界面的時候,發(fā)現(xiàn)了內(nèi)置Markdown編輯器有一個Github的圖標,點進去就是一個開源的Markdown編輯器項目bytemdhttps://github.com/bytedance/bytemd):

          這是一個NodeJs項目,由字節(jié)跳動提供。聯(lián)想到之前業(yè)余的時候做過一些Swing或者JavaFxDemo,記得JavaFx中有一個組件WebView已經(jīng)支持Html5、CSS3ES5,這個組件作為一個嵌入式瀏覽器,可以輕松地渲染一個URL里面的文本內(nèi)容或者直接渲染一個原始的Html字符串。另外,由于原生的JavaFx的視覺效果比較丑,可以考慮引入Swing配合IntelliJ IDEA的主題提供更好的視覺效果。本文的代碼基于JDK11開發(fā)。

          引入依賴

          很多人吐槽過Swing組件的視覺效果比較差,原因有幾個:

          • 技術(shù)小眾,現(xiàn)在有更好的組件進行混合開發(fā)和跨平臺開發(fā)
          • 基于上一點原因,導致很少人會去開發(fā)Swing組件的UI,其實Swing的每個組件都可以重新實現(xiàn)UI的表現(xiàn)效果
          • compose-jbJetBrains)組件很晚才發(fā)布出來,剛好碰上Swing官方停止維護,后面應該更加少人會使用SwingGUI開發(fā)

          使用Swing并且成功了的方案最知名的就是JetBrains全家桶。目前來看,為了解決這個"丑"的問題,現(xiàn)在有比較簡單的處理方案:

          • 方案一:使用compose-jb(名字有點不好聽,官方倉庫為https://github.com/JetBrains/compose-jb)開發(fā),這個是JetBrains系列的通用組件,基于Swing做二次封裝,「不過必須使用語言Kotlin,有點強買強賣的嫌疑」,這列貼兩個官方的圖參考一下:
          • 方案二:FormDev(之前推出過Swing布局器的開發(fā)商,官網(wǎng)https://www.formdev.com/flatlaf)提供的FlatLafFlat Look and Feel),提供了Light Dark IntelliJ and Darcula themes,而且依賴少,使用起來十分簡單,個人認為當前這個是Swing UI組件視覺效果首選

          引入FlatLafOpenFx的依賴:

          <dependency>
              <groupId>com.formdev</groupId>
              <artifactId>flatlaf</artifactId>
              <version>1.5</version>
          </dependency>
          <dependency>
              <groupId>com.formdev</groupId>
              <artifactId>flatlaf-intellij-themes</artifactId>
              <version>1.5</version>
          </dependency>
          <dependency>
              <groupId>org.openjfx</groupId>
              <artifactId>javafx-media</artifactId>
              <version>11.0.2</version>
          </dependency>
          <dependency>
              <groupId>org.openjfx</groupId>
              <artifactId>javafx-swing</artifactId>
              <version>11.0.2</version>
          </dependency>
          <dependency>
              <groupId>org.openjfx</groupId>
              <artifactId>javafx-web</artifactId>
              <version>11.0.2</version>
          </dependency>
          <dependency>
              <groupId>org.openjfx</groupId>
              <artifactId>javafx-base</artifactId>
              <version>11.0.2</version>
          </dependency>
          <dependency>
              <groupId>org.openjfx</groupId>
              <artifactId>javafx-graphics</artifactId>
              <version>11.0.2</version>
          </dependency>
          <dependency>
              <groupId>org.openjfx</groupId>
              <artifactId>javafx-controls</artifactId>
              <version>11.0.2</version>
          </dependency>

          布局和實現(xiàn)

          布局的實現(xiàn)比較簡單:

          最終的H5文本渲染在WebView組件中(JFXPanelJavaFx => Swing的適配器,WebViewJavaFx的組件,但是這里使用的外層容器都是Swing組件),具體的編碼實現(xiàn)如下:

          public class MarkdownEditor {

              private static final int W = 1200;
              private static final int H = 1000;
              private static final String TITLE = "markdown editor";

              public static String CONTENT = "<!DOCTYPE html>\n" +
                      "<html lang=\"en\">\n" +
                      "<head>\n" +
                      "    <meta charset=\"UTF-8\"/>\n" +
                      "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"/>\n" +
                      "    <title>ByteMD example</title>\n" +
                      "    <link rel=\"stylesheet\" href=\"https://unpkg.com/bytemd/dist/index.min.css\"/>\n" +
                      "    <link rel=\"stylesheet\" href=\"https://unpkg.com/github-markdown-css\"/>\n" +
                      "    <script src=\"https://unpkg.com/bytemd\"></script>\n" +
                      "    <script src=\"https://unpkg.com/@bytemd/plugin-gfm\"></script>\n" +
                      "    <script src=\"https://unpkg.com/@bytemd/plugin-highlight\"></script>\n" +
                      "    <style>\n" +
                      "        .bytemd {\n" +
                      "            height: calc(100vh - 50px);\n" +
                      "        }\n" +
                      "\n" +
                      "        .footer {\n" +
                      "            width: 100%;\n" +
                      "            height: 30px;\n" +
                      "            left: 0;\n" +
                      "            position: absolute;\n" +
                      "            bottom: 0;\n" +
                      "            text-align: center;\n" +
                      "        }\n" +
                      "    </style>\n" +
                      "</head>\n" +
                      "<body>\n" +
                      "<div class=\"footer\">\n" +
                      "    <a href=\"https://github.com/bytedance/bytemd\">bytemd</a>\n" +
                      "</div>\n" +
                      "<script>\n" +
                      "    const plugins = [bytemdPluginGfm(), bytemdPluginHighlight()];\n" +
                      "    const editor = new bytemd.Editor({\n" +
                      "        target: document.body,\n" +
                      "        props: {\n" +
                      "            value: '# heading\\n\\nparagraph\\n\\n> blockquote',\n" +
                      "            plugins,\n" +
                      "        },\n" +
                      "    });\n" +
                      "    editor.$on('change', (e) => {\n" +
                      "        editor.$set({value: e.detail.value});\n" +
                      "    });\n" +
                      "</script>\n" +
                      "</body>\n" +
                      "</html>";

              static {
                  // 初始化主題
                  try {
                      UIManager.setLookAndFeel(FlatIntelliJLaf.class.getName());
                  } catch (Exception e) {
                      throw new IllegalStateException("theme init error", e);
                  }
              }

              private static JFrame buildFrame(int w, int h, LayoutManager layoutManager) {
                  JFrame frame = new JFrame();
                  frame.setLayout(layoutManager);
                  frame.setTitle(TITLE);
                  frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
                  frame.setSize(w, h);
                  Toolkit toolkit = Toolkit.getDefaultToolkit();
                  int x = (int) (toolkit.getScreenSize().getWidth() - frame.getWidth()) / 2;
                  int y = (int) (toolkit.getScreenSize().getHeight() - frame.getHeight()) / 2;
                  frame.setLocation(x, y);
                  return frame;
              }

              private static void initAndDisplay() {
                  // 構(gòu)建窗體
                  JFrame frame = buildFrame(W, H, new BorderLayout());
                  JFXPanel panel = new JFXPanel();
                  Platform.runLater(() -> {
                      panel.setSize(W, H);
                      initWebView(panel, CONTENT);
                      frame.getContentPane().add(panel);
                  });
                  frame.setVisible(true);
              }

              public static void main(String[] args) {
                  SwingUtilities.invokeLater(MarkdownEditor::initAndDisplay);
              }

              private static void initWebView(JFXPanel fxPanel, String content) {
                  StackPane root = new StackPane();
                  Scene scene = new Scene(root);
                  WebView webView = new WebView();
                  WebEngine webEngine = webView.getEngine();
                  webEngine.setJavaScriptEnabled(true);
                  webEngine.loadContent(content);
                  root.getChildren().add(webView);
                  fxPanel.setScene(scene);
              }
          }

          H5文本來源于bytemd的原生JS實現(xiàn)例子:

          所有代碼加上注釋大概120多行。使用JDK11運行,結(jié)果如下:

          目前有2個沒有解決的問題(也有可能是):

          • JS的動作觸發(fā)有輕微延遲
          • WebView組件初始化比較慢

          小結(jié)

          Oracle JDK官方已經(jīng)宣布不再維護Swing項目,按照一般的尿性后面有可能從JDK中移除,也許是因為它體現(xiàn)不了自身的價值(低情商:不賺錢)。Swing的開發(fā)中布局是比較反人類的,一般可能一個Swing項目布局會耗費90%以上的時間,原生組件的UI設計看上去比較"丑",沒有豐富的擴展組件和活躍的社區(qū),加上現(xiàn)在有其他更好的跨平臺開發(fā)方案如Qt、React NativeFlutter等等,Swing被遺忘是一個既定的結(jié)局。往后除了一枝獨秀的JetBrains,Swing的結(jié)局就是成為少數(shù)人業(yè)務的愛好,成為JDK GUI編程愛好者的收藏品。

          Demo源碼:

          • local-markdown-editor(https://gitee.com/throwableDoge/local-markdown-editor)

          (本文完 e-a-20210815 c-1-d)


          瀏覽 39
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久成人影音先锋 | 波霸一区二区 | av五月天在线 | 成人视频日本 | 亚洲最大性爱网站 |