Ver código fonte

Export: support outline panel in exported HTML file

Le Tan 7 anos atrás
pai
commit
77dd8d0f32

+ 14 - 6
src/dialog/vexportdialog.cpp

@@ -372,11 +372,16 @@ QWidget *VExportDialog::setupHTMLAdvancedSettings()
                 m_completeHTMLCB->setEnabled(!checked);
             });
 
-    QFormLayout *advLayout = new QFormLayout();
-    advLayout->addRow(m_embedStyleCB);
-    advLayout->addRow(m_completeHTMLCB);
-    advLayout->addRow(m_embedImagesCB);
-    advLayout->addRow(m_mimeHTMLCB);
+    // Outline panel.
+    m_outlinePanelCB = new QCheckBox(tr("Enable outline panel"), this);
+    m_outlinePanelCB->setToolTip(tr("Add an outline panel in HTML file"));
+
+    QGridLayout *advLayout = new QGridLayout();
+    advLayout->addWidget(m_embedStyleCB, 0, 1, 1, 2);
+    advLayout->addWidget(m_completeHTMLCB, 0, 4, 1, 2);
+    advLayout->addWidget(m_embedImagesCB, 1, 1, 1, 2);
+    advLayout->addWidget(m_mimeHTMLCB, 1, 4, 1, 2);
+    advLayout->addWidget(m_outlinePanelCB, 2, 1, 1, 2);
 
     advLayout->setContentsMargins(0, 0, 0, 0);
 
@@ -483,6 +488,8 @@ void VExportDialog::initUIFields(MarkdownConverterType p_renderer)
 
     m_mimeHTMLCB->setChecked(s_opt.m_htmlOpt.m_mimeHTML);
 
+    m_outlinePanelCB->setChecked(s_opt.m_htmlOpt.m_outlinePanel);
+
     m_tableOfContentsCB->setChecked(s_opt.m_pdfOpt.m_enableTableOfContents);
 
     m_wkhtmltopdfCB->setChecked(s_opt.m_pdfOpt.m_wkhtmltopdf);
@@ -587,7 +594,8 @@ void VExportDialog::startExport()
                          ExportHTMLOption(m_embedStyleCB->isChecked(),
                                           m_completeHTMLCB->isChecked(),
                                           m_embedImagesCB->isChecked(),
-                                          m_mimeHTMLCB->isChecked()),
+                                          m_mimeHTMLCB->isChecked(),
+                                          m_outlinePanelCB->isChecked()),
                          ExportCustomOption((ExportCustomOption::SourceFormat)
                                             m_customSrcFormatCB->currentData().toInt(),
                                             m_customSuffixEdit->text(),

+ 9 - 3
src/dialog/vexportdialog.h

@@ -58,18 +58,21 @@ struct ExportHTMLOption
         : m_embedCssStyle(true),
           m_completeHTML(true),
           m_embedImages(true),
-          m_mimeHTML(false)
+          m_mimeHTML(false),
+          m_outlinePanel(true)
     {
     }
 
     ExportHTMLOption(bool p_embedCssStyle,
                      bool p_completeHTML,
                      bool p_embedImages,
-                     bool p_mimeHTML)
+                     bool p_mimeHTML,
+                     bool p_outlinePanel)
         : m_embedCssStyle(p_embedCssStyle),
           m_completeHTML(p_completeHTML),
           m_embedImages(p_embedImages),
-          m_mimeHTML(p_mimeHTML)
+          m_mimeHTML(p_mimeHTML),
+          m_outlinePanel(p_outlinePanel)
     {
     }
 
@@ -77,6 +80,7 @@ struct ExportHTMLOption
     bool m_completeHTML;
     bool m_embedImages;
     bool m_mimeHTML;
+    bool m_outlinePanel;
 };
 
 
@@ -452,6 +456,8 @@ private:
 
     QCheckBox *m_mimeHTMLCB;
 
+    QCheckBox *m_outlinePanelCB;
+
     QCheckBox *m_subfolderCB;
 
     QComboBox *m_customSrcFormatCB;

+ 36 - 0
src/resources/export/export_template.html

@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<head>
+    <style type="text/css">
+    /* STYLE_GLOBAL_PLACE_HOLDER */
+    </style>
+
+    <style type="text/css">
+    /* STYLE_OUTLINE_PLACE_HOLDER */
+    /* STYLE_PLACE_HOLDER */
+    </style>
+
+    <!-- EXTRA_PLACE_HOLDER -->
+
+<!-- HEAD_PLACE_HOLDER -->
+</head>
+<body>
+<div class="container-fluid">
+<div class="row flex-xl-nowrap">
+    <div id="outline-panel" style="display:none;" class="d-none d-md-block d-xl-block col-md-3 col-xl-2 bd-toc">
+        <div id="outline-content" class="section-nav"></div>
+    </div>
+    <div id="post-content" class="col-12 col-md-9 col-xl-10 py-md-3 pl-md-5 bd-content">
+    <!-- BODY_PLACE_HOLDER -->
+    </div>
+</div>
+</div>
+
+<div id="container-floating" style="display:none;" class="d-none d-md-block d-xl-block">
+    <div id="floating-button" onclick="toggleMore()">
+        <p id="floating-more" class="more">&gt;</p>
+    </div>
+</div>
+</body>
+</html>

+ 199 - 0
src/resources/export/outline.css

@@ -0,0 +1,199 @@
+*,
+*::before,
+*::after {
+  box-sizing: border-box;
+}
+
+.container-fluid {
+    width: 100%;
+    padding-right: 15px;
+    padding-left: 15px;
+    margin-right: auto;
+    margin-left: auto;
+}
+
+.col, .col-1, .col-10, .col-11, .col-12, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-auto, .col-lg, .col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-auto, .col-md, .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-auto, .col-sm, .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-auto, .col-xl, .col-xl-1, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-auto {
+    position: relative;
+    width: 100%;
+    min-height: 1px;
+    padding-right: 15px;
+    padding-left: 15px;
+}
+
+.col-12 {
+    -webkit-box-flex: 0;
+    -ms-flex: 0 0 100%;
+    flex: 0 0 100%;
+    max-width: 100%;
+}
+
+@media (min-width: 768px) {
+    .col-md-3 {
+        -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+        flex: 0 0 25%;
+        max-width: 25%;
+    }
+}
+
+@media (min-width: 768px) {
+    .col-md-9 {
+        -webkit-box-flex: 0;
+        -ms-flex: 0 0 75%;
+        flex: 0 0 75%;
+        max-width: 75%;
+    }
+}
+
+@media (min-width: 1200px) {
+    .col-xl-2 {
+        -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.666667%;
+        flex: 0 0 16.666667%;
+        max-width: 16.666667%;
+    }
+}
+
+@media (min-width: 1200px) {
+    .col-xl-10 {
+        -webkit-box-flex: 0;
+        -ms-flex: 0 0 83.333333%;
+        flex: 0 0 83.333333%;
+        max-width: 83.333333%;
+    }
+}
+
+@media (min-width: 768px) {
+    .pt-md-3, .py-md-3 {
+        padding-top: 1rem!important;
+    }
+}
+
+@media (min-width: 768px) {
+    .pb-md-3, .py-md-3 {
+        padding-bottom: 1rem!important;
+    }
+}
+
+@media (min-width: 768px) {
+    .pl-md-5, .px-md-5 {
+        padding-left: 3rem!important;
+    }
+}
+
+.d-none {
+    display: none!important;
+}
+
+@media (min-width: 1200px) {
+    .d-xl-block {
+        display: block!important;
+    }
+}
+
+@media (min-width: 768px) {
+    .d-md-block {
+        display: block!important;
+    }
+}
+
+.bd-content {
+    -webkit-box-ordinal-group: 1;
+    -ms-flex-order: 0;
+    order: 0;
+}
+
+.bd-toc {
+    position: -webkit-sticky;
+    position: sticky;
+    top: 4rem;
+    height: calc(100vh - 10rem);
+    overflow-y: auto;
+}
+
+.bd-toc {
+    -webkit-box-ordinal-group: 2;
+    -ms-flex-order: 1;
+    order: 1;
+    padding-top: 1.5rem;
+    padding-bottom: 1.5rem;
+    font-size: .875rem;
+}
+
+.section-nav {
+    padding-left: 0;
+}
+
+.section-nav ul {
+    font-size: .875rem;
+    list-style-type: none;
+}
+
+.section-nav li {
+    font-size: .875rem;
+}
+
+.section-nav a {
+    color: inherit !important;
+}
+
+.row {
+    display: -webkit-box;
+    display: -ms-flexbox;
+    display: flex;
+    -ms-flex-wrap: wrap;
+    flex-wrap: wrap;
+    margin-right: -15px;
+    margin-left: -15px;
+}
+
+@media (min-width: 1200px) {
+    .flex-xl-nowrap {
+        flex-wrap: nowrap !important;
+    }
+}
+
+#floating-button {
+    width: 2.5rem;
+    height: 2.5rem;
+    border-radius: 50%;
+    background: #00897B;
+    position: fixed;
+    top: .5rem;
+    right: .5rem;
+    cursor: pointer;
+    box-shadow: 0px 2px 5px #666;
+}
+
+#floating-button .more {
+    color: #F5F5F5;
+    position: absolute;
+    top: 0;
+    display: block;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    text-align: center;
+    padding: 0;
+    margin: 0;
+    line-height: 2.5rem;
+    font-size: 2rem;
+    font-family: 'monospace';
+    font-weight: 300;
+}
+
+.hide-none {
+    display: none !important;
+}
+
+.col-expand {
+    -webkit-box-flex: 0;
+    -ms-flex: 0 0 100% !important;
+    flex: 0 0 100% !important;
+    max-width: 100% !important;
+    padding-right: 3rem !important;
+}
+
+.outline-bold {
+    font-weight: bolder !important;
+}

+ 228 - 0
src/resources/export/outline.js

@@ -0,0 +1,228 @@
+var toc = [];
+
+var setVisible = function(node, visible) {
+    var cl = 'hide-none';
+    if (visible) {
+        node.classList.remove(cl);
+    } else {
+        node.classList.add(cl);
+    }
+};
+
+var isVisible = function(node) {
+    var cl = 'hide-none';
+    return !node.classList.contains(cl);
+};
+
+var setPostContentExpanded = function(node, expanded) {
+    var cl = 'col-expand';
+    if (expanded) {
+        node.classList.add(cl);
+    } else {
+        node.classList.remove(cl);
+    }
+};
+
+var setOutlinePanelVisible = function(visible) {
+    var outlinePanel = document.getElementById('outline-panel');
+    var postContent = document.getElementById('post-content');
+
+    setVisible(outlinePanel, visible);
+    setPostContentExpanded(postContent, !visible);
+};
+
+var isOutlinePanelVisible = function() {
+    var outlinePanel = document.getElementById('outline-panel');
+    return isVisible(outlinePanel);
+};
+
+window.addEventListener('load', function() {
+    var outlinePanel = document.getElementById('outline-panel');
+    outlinePanel.style.display = 'initial';
+
+    var floatingContainer = document.getElementById('container-floating');
+    floatingContainer.style.display = 'initial';
+
+    var outlineContent = document.getElementById('outline-content');
+    var postContent = document.getElementById('post-content');
+
+    // Fetch the outline.
+    var headers = postContent.querySelectorAll("h1, h2, h3, h4, h5, h6");
+    toc = [];
+    for (var i = 0; i < headers.length; ++i) {
+        var header = headers[i];
+
+        toc.push({
+            level: parseInt(header.tagName.substr(1)),
+            anchor: header.id,
+            title: header.textContent
+        });
+    }
+
+    if (toc.length == 0) {
+        setOutlinePanelVisible(false);
+        setVisible(floatingContainer, false);
+        return;
+    }
+
+    var baseLevel = baseLevelOfToc(toc);
+    var tocTree = tocToTree(toPerfectToc(toc, baseLevel), baseLevel);
+
+    outlineContent.innerHTML = tocTree;
+    setOutlinePanelVisible(true);
+    setVisible(floatingContainer, true);
+});
+
+// Return the topest level of @toc, starting from 1.
+var baseLevelOfToc = function(p_toc) {
+    var level = -1;
+    for (i in p_toc) {
+        if (level == -1) {
+            level = p_toc[i].level;
+        } else if (level > p_toc[i].level) {
+            level = p_toc[i].level;
+        }
+    }
+
+    if (level == -1) {
+        level = 1;
+    }
+
+    return level;
+};
+
+// Handle wrong title levels, such as '#' followed by '###'
+var toPerfectToc = function(p_toc, p_baseLevel) {
+    var i;
+    var curLevel = p_baseLevel - 1;
+    var perfToc = [];
+    for (i in p_toc) {
+        var item = p_toc[i];
+
+        // Insert empty header.
+        while (item.level > curLevel + 1) {
+            curLevel += 1;
+            var tmp = { level: curLevel,
+                        anchor: '',
+                        title: '[EMPTY]'
+                      };
+            perfToc.push(tmp);
+        }
+
+        perfToc.push(item);
+        curLevel = item.level;
+    }
+
+    return perfToc;
+};
+
+var itemToHtml = function(item) {
+    return '<a href="#' + item.anchor + '" data="' + item.anchor + '">' + item.title + '</a>';
+};
+
+// Turn a perfect toc to a tree using <ul>
+var tocToTree = function(p_toc, p_baseLevel) {
+    var i;
+    var front = '<li>';
+    var ending = ['</li>'];
+    var curLevel = p_baseLevel;
+    for (i in p_toc) {
+        var item = p_toc[i];
+        if (item.level == curLevel) {
+            front += '</li>';
+            front += '<li>';
+            front += itemToHtml(item);
+        } else if (item.level > curLevel) {
+            // assert(item.level - curLevel == 1)
+            front += '<ul>';
+            ending.push('</ul>');
+            front += '<li>';
+            front += itemToHtml(item);
+            ending.push('</li>');
+            curLevel = item.level;
+        } else {
+            while (item.level < curLevel) {
+                var ele = ending.pop();
+                front += ele;
+                if (ele == '</ul>') {
+                    curLevel--;
+                }
+            }
+            front += '</li>';
+            front += '<li>';
+            front += itemToHtml(item);
+        }
+    }
+    while (ending.length > 0) {
+        front += ending.pop();
+    }
+    front = front.replace("<li></li>", "");
+    front = '<ul>' + front + '</ul>';
+    return front;
+};
+
+var toggleMore = function() {
+    if (toc.length == 0) {
+        return;
+    }
+
+    var p = document.getElementById('floating-more');
+    if (isOutlinePanelVisible()) {
+        p.textContent = '<';
+        setOutlinePanelVisible(false);
+    } else {
+        p.textContent = '>';
+        setOutlinePanelVisible(true);
+    }
+};
+
+window.addEventListener('scroll', function() {
+    if (toc.length == 0 || !isOutlinePanelVisible()) {
+        return;
+    }
+
+    var postContent = document.getElementById('post-content');
+    var scrollTop = document.documentElement.scrollTop
+                    || document.body.scrollTop
+                    || window.pageYOffset;
+    var eles = postContent.querySelectorAll("h1, h2, h3, h4, h5, h6");
+
+    if (eles.length == 0) {
+        return;
+    }
+
+    var idx = -1;
+    var biaScrollTop = scrollTop + 50;
+    for (var i = 0; i < eles.length; ++i) {
+        if (biaScrollTop >= eles[i].offsetTop) {
+            idx = i;
+        } else {
+            break;
+        }
+    }
+
+    var header = '';
+    if (idx != -1) {
+        header = eles[idx].id;
+    }
+
+    highlightItemOnlyInOutline(header);
+});
+
+var highlightItemOnlyInOutline = function(id) {
+    var cl = 'outline-bold';
+    var outlineContent = document.getElementById('outline-content');
+    var eles = outlineContent.querySelectorAll("a");
+    var target = null;
+    for (var i = 0; i < eles.length; ++i) {
+        var ele = eles[i];
+        if (ele.getAttribute('data') == id) {
+            target = ele;
+            ele.classList.add(cl);
+        } else {
+            ele.classList.remove(cl);
+        }
+    }
+
+    // TODO: scroll target into view within the outline panel scroll area.
+};

+ 0 - 20
src/resources/export_template.html

@@ -1,20 +0,0 @@
-<!DOCTYPE html>
-<html>
-<meta charset="utf-8">
-<head>
-    <style type="text/css">
-    /* STYLE_GLOBAL_PLACE_HOLDER */
-    </style>
-
-    <style type="text/css">
-    /* STYLE_PLACE_HOLDER */
-    </style>
-
-    <!-- EXTRA_PLACE_HOLDER -->
-
-<!-- HEAD_PLACE_HOLDER -->
-</head>
-<body>
-<!-- BODY_PLACE_HOLDER -->
-</body>
-</html>

+ 0 - 56
src/resources/outline.css

@@ -1,56 +0,0 @@
-.post-content {
-    width: 960px;
-    min-height: 200px;
-    margin-left: auto;
-    margin-right: auto;
-
-}
-.toc {
-    overflow: hidden;
-    color: #555;
-    border: 1px solid #d2d2d2;
-    border-radius: 3px;
-    min-width: 150px;
-    opacity: 1;
-    font-size: inherit;
-    z-index: 19941112;
-}
-.toc {
-    display: none;
-    margin-bottom: 2em;
-    line-height: 1.5em;
-}
-.toc a {
-    color: #333;
-}
-.toc a:hover{
-    color: #555;
-    background-color: #fff;
-}
-.toc .catalog-title {
-    cursor: move;
-    padding-left: 12px;
-    width: 100%;
-    height: 35px;
-    line-height: 36px;
-    border-bottom: 1px solid #eee;
-    font-size: 14px;
-    color: #555;
-    overflow: hidden;
-}
-.toc .catalog-close {
-    position: absolute;
-    right: 15px;
-    top: 6px;
-    cursor: pointer;
-    text-decoration: none;
-}
-.fixed {
-    position: fixed;
-    top: 20px;
-    right: 250px;
-    width: auto;
-}
-.blodtoc {
-    font-weight: bold;
-}

+ 0 - 27
src/resources/outline.html

@@ -1,27 +0,0 @@
-<!DOCTYPE html>
-<html>
-<meta charset="utf-8">
-<head>
-    <style type="text/css">
-    /* STYLE_GLOBAL_PLACE_HOLDER */
-    </style>
-
-    <style type="text/css">
-    /* STYLE_PLACE_HOLDER */
-    </style>
-
-    <!-- EXTRA_PLACE_HOLDER -->
-
-<!-- HEAD_PLACE_HOLDER -->
-<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
-</head>
-<body>
-<div class="toc fixed">
-    <div class="catalog-title"><span>目录</span></div>
-    <a class="catalog-close"><span>X</span></a>
-</div>
-<div class="post-content">
-<!-- BODY_PLACE_HOLDER -->
-</div>
-</body>
-</html>

+ 0 - 100
src/resources/outline.js

@@ -1,100 +0,0 @@
-    /**
-     * Convert item of header array to html in li 
-     * @param item An element of header array
-     */
-    var itemToHtml = function(item) {
-        return '<a href="#' + item.id + '" id="' + 'menu-'+ item.id + '" >' + item.innerText + '</a>';
-    };
-    /**
-     * Generate tree from header array 
-     * @param toc_list An array containing header elements
-     * @param p_baseLevel The base level number of the toc you want to display
-     */
-    var tocToTree = function(toc_list, p_baseLevel) {
-        let i;
-        let p_toc = [];
-        for (i in toc_list) {
-            let itemLevel = parseInt(toc_list[i].tagName.substring(1));
-            if (itemLevel >= p_baseLevel) {
-                p_toc.push(toc_list[i]);
-            }
-        }
-        let front = '<li>';
-        let ending = ['</li>'];
-        let curLevel = p_baseLevel;
-        let toclen = p_toc.length;
-        for (i in p_toc) {
-            let item = p_toc[i];
-            console.log(item.tagName);
-            let itemLevel = parseInt(item.tagName.substring(1));
-            if (item.tagName == curLevel) {
-                front += '</li>';
-                front += '<li>';
-                front += itemToHtml(item);
-            } else if (itemLevel > curLevel) {
-                // assert(item.level - curLevel == 1)
-                front += '<ul>';
-                ending.push('</ul>');
-                front += '<li>';
-                front += itemToHtml(item);
-                ending.push('</li>');
-                curLevel = itemLevel;
-            } else {
-                while (itemLevel < curLevel) {
-                    let ele = ending.pop();
-                    front += ele;
-                    if (ele == '</ul>') {
-                        curLevel--;
-                    }
-                }
-                front += '</li>';
-                front += '<li>';
-                front += itemToHtml(item);
-            }
-        }
-        while (ending.length > 0) {
-            front += ending.pop();
-        }
-        front = front.replace("<li></li>", "");
-        front = '<ul>' + front + '</ul>';
-        return front;
-    };
-
-    let headerObjList = $(":header").toArray();
-    $('.toc').append(tocToTree( headerObjList, 2 ));
-
-
-    // scroll to display side outline
-    $(window).bind('scroll', function(){
-        if ($(document).scrollTop() >= 100) {
-            $('.toc').css("display", "block");
-            highToc();
-        } else {
-            $('.toc').css("display", "none");
-        }
-    });
-
-
-    // make the corresponding outline text blod
-    let highToc = function(){
-        $(":header").each(function(index, element) {
-            var wst = $(window).scrollTop(); 
-            let tag_id = $(this).attr("id");
-            if($("#"+tag_id).offset().top <= wst){
-                $('.toc a').removeClass("blodtoc");
-                $('#menu-'+tag_id).addClass("blodtoc");
-            }
-        });
-    }
-
-    // click to make outline text blod
-    $('.toc a').click(function(){
-        $('.toc a').removeClass("blodtoc");
-        $(this).addClass("blodtoc");
-    });
-
-    // button to close the outline
-    $('.toc .catalog-close').click(function(){
-        $('.toc').hide();
-        $(window).unbind('scroll');
-    });

+ 15 - 1
src/utils/vutils.cpp

@@ -868,7 +868,9 @@ QString VUtils::generateHtmlTemplate(const QString &p_template,
     return htmlTemplate;
 }
 
-QString VUtils::generateExportHtmlTemplate(const QString &p_renderBg, bool p_includeMathJax)
+QString VUtils::generateExportHtmlTemplate(const QString &p_renderBg,
+                                           bool p_includeMathJax,
+                                           bool p_outlinePanel)
 {
     QString templ = VNote::generateExportHtmlTemplate(g_config->getRenderBackgroundColor(p_renderBg));
     QString extra;
@@ -902,6 +904,18 @@ with 2em, if there are Chinese characters in it, the font will be a mess.
         extra += "<script type=\"text/javascript\" async src=\"" + mj + "\"></script>\n";
     }
 
+    if (p_outlinePanel) {
+        const QString outlineCss(":/resources/export/outline.css");
+        QString css = VUtils::readFileFromDisk(outlineCss);
+        if (!css.isEmpty()) {
+            templ.replace(HtmlHolder::c_outlineStyleHolder, css);
+        }
+
+        const QString outlineJs(":/resources/export/outline.js");
+        QString js = VUtils::readFileFromDisk(outlineJs);
+        extra += QString("<script type=\"text/javascript\">\n%1\n</script>\n").arg(js);
+    }
+
     if (!extra.isEmpty()) {
         templ.replace(HtmlHolder::c_extraHolder, extra);
     }

+ 3 - 1
src/utils/vutils.h

@@ -195,7 +195,9 @@ public:
                                         bool p_addToc = false);
 
     // @p_renderBg is the background name.
-    static QString generateExportHtmlTemplate(const QString &p_renderBg, bool p_includeMathJax);
+    static QString generateExportHtmlTemplate(const QString &p_renderBg,
+                                              bool p_includeMathJax,
+                                              bool p_outlinePanel);
 
     static QString generateSimpleHtmlTemplate(const QString &p_body);
 

+ 1 - 0
src/vconstants.h

@@ -48,6 +48,7 @@ namespace HtmlHolder
     static const QString c_bodyHolder = "<!-- BODY_PLACE_HOLDER -->";
     static const QString c_headHolder = "<!-- HEAD_PLACE_HOLDER -->";
     static const QString c_styleHolder = "/* STYLE_PLACE_HOLDER */";
+    static const QString c_outlineStyleHolder = "/* STYLE_OUTLINE_PLACE_HOLDER */";
 }
 
 // Directory Config file items.

+ 6 - 1
src/vexporter.cpp

@@ -55,8 +55,13 @@ void VExporter::prepareExport(const ExportOption &p_opt)
                                                   isPdf && p_opt.m_pdfOpt.m_wkhtmltopdf,
                                                   extraToc);
 
+    bool outline = p_opt.m_htmlOpt.m_outlinePanel
+                   && !isPdf
+                   && (p_opt.m_format == ExportFormat::HTML
+                       || p_opt.m_format == ExportFormat::Custom);
     m_exportHtmlTemplate = VUtils::generateExportHtmlTemplate(p_opt.m_renderBg,
-                                                              isPdf && p_opt.m_pdfOpt.m_wkhtmltopdf);
+                                                              isPdf && p_opt.m_pdfOpt.m_wkhtmltopdf,
+                                                              outline);
 
     m_pageLayout = *(p_opt.m_pdfOpt.m_layout);
 

+ 1 - 1
src/vnote.cpp

@@ -152,7 +152,7 @@ QString VNote::generateHtmlTemplate(const QString &p_renderBg,
 
 QString VNote::generateExportHtmlTemplate(const QString &p_renderBg)
 {
-    const QString c_exportTemplatePath(":/resources/export_template.html");
+    const QString c_exportTemplatePath(":/resources/export/export_template.html");
 
     QString cssStyle;
     if (!p_renderBg.isEmpty()) {

+ 3 - 1
src/vnote.qrc

@@ -177,7 +177,6 @@
         <file>resources/icons/delete_cart_item.svg</file>
         <file>resources/icons/fullscreen.svg</file>
         <file>resources/icons/menubar.svg</file>
-        <file>resources/export_template.html</file>
         <file>resources/themes/v_pure/v_pure_mermaid.css</file>
         <file>resources/themes/v_moonlight/v_moonlight_mermaid.css</file>
         <file>resources/themes/v_moonlight/arrow_dropdown_disabled.svg</file>
@@ -272,5 +271,8 @@
         <file>resources/common.css</file>
         <file>resources/icons/quick_access.svg</file>
         <file>resources/common.js</file>
+        <file>resources/export/export_template.html</file>
+        <file>resources/export/outline.css</file>
+        <file>resources/export/outline.js</file>
     </qresource>
 </RCC>