Commit 4403d1ca authored by jan.koester's avatar jan.koester
Browse files

test

parent 4ac09db3
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ ColumnLayout {
    property var treeData: null

    function reload() {
        selectedUuid = ""
        var json = localApi.getDocTree()
        var resp = JSON.parse(json)
        if (resp.status === "success")
+264 −31
Original line number Diff line number Diff line
@@ -8,26 +8,234 @@ ColumnLayout {
    signal textChangedByUser(string newText)

    property bool rawMode: false
    property string _storedHtml: ""
    property bool _switching: false

    onRawModeChanged: {
        _switching = true
        if (rawMode) {
            /* RichText → Raw: extract and clean Qt artifacts */
            _storedHtml = stripQtArtifacts(editorArea.text)
            editorArea.textFormat = TextEdit.PlainText
            editorArea.text = _storedHtml
        } else {
            /* Raw → RichText: take raw text as HTML source */
            _storedHtml = editorArea.text
            editorArea.textFormat = TextEdit.RichText
            editorArea.text = _storedHtml
        }
        _switching = false
    }

    function stripQtArtifacts(html) {
        var s = html
        /* Remove DOCTYPE, html/head/body wrappers */
        s = s.replace(/<!DOCTYPE[^>]*>/gi, "")
        s = s.replace(/<\/?html[^>]*>/gi, "")
        s = s.replace(/<head[^>]*>[\s\S]*?<\/head>/gi, "")
        s = s.replace(/<\/?body[^>]*>/gi, "")

        /* Remove Qt paragraph style artifacts */
        s = s.replace(/\s*style\s*=\s*"\s*-qt-[^"]*"/gi, "")

        /* Remove empty style attributes left over */
        s = s.replace(/\s+style\s*=\s*"[\s;]*"/gi, "")

        /* Remove font-family / font-size from inline styles (Qt injects system font) */
        s = s.replace(/\s*font-family\s*:\s*'[^']*'\s*;?/gi, "")
        s = s.replace(/\s*font-size\s*:\s*[^;'"]+;?/gi, "")
        s = s.replace(/\s*font-weight\s*:\s*[^;'"]+;?/gi, "")
        s = s.replace(/\s*font-style\s*:\s*[^;'"]+;?/gi, "")

        /* Clean up remaining empty style attributes after font removal */
        s = s.replace(/\s+style\s*=\s*"[\s;]*"/gi, "")

        /* Remove <span> tags with no remaining attributes */
        s = s.replace(/<span\s*>([\s\S]*?)<\/span>/gi, "$1")

        /* Remove <font> wrapper tags */
        s = s.replace(/<\/?font[^>]*>/gi, "")

        /* Collapse multiple blank lines */
        s = s.replace(/\n{3,}/g, "\n\n")

        return s.trim()
    }

    RowLayout {
        Layout.fillWidth: true
        spacing: 2

        ToolButton { text: "<b>B</b>"; onClicked: insertText("<b>", "</b>"); font.bold: true; visible: !rawMode }
        ToolButton { text: "<i>I</i>"; onClicked: insertText("<i>", "</i>"); font.italic: true; visible: !rawMode }
        ToolButton { text: "<u>U</u>"; onClicked: insertText("<u>", "</u>"); font.underline: true; visible: !rawMode }
        ToolButton { text: "<b>B</b>"; onClicked: wrapSelection("<b>", "</b>"); font.bold: true; visible: !rawMode }
        ToolButton { text: "<i>I</i>"; onClicked: wrapSelection("<i>", "</i>"); font.italic: true; visible: !rawMode }
        ToolButton { text: "<u>U</u>"; onClicked: wrapSelection("<u>", "</u>"); font.underline: true; visible: !rawMode }

        ToolSeparator { visible: !rawMode }

        ToolButton { text: "H1"; onClicked: insertText("<h1>", "</h1>"); visible: !rawMode }
        ToolButton { text: "H2"; onClicked: insertText("<h2>", "</h2>"); visible: !rawMode }
        ToolButton { text: "H3"; onClicked: insertText("<h3>", "</h3>"); visible: !rawMode }
        ToolButton { text: "H1"; onClicked: wrapSelection("<h1>", "</h1>"); visible: !rawMode }
        ToolButton { text: "H2"; onClicked: wrapSelection("<h2>", "</h2>"); visible: !rawMode }
        ToolButton { text: "H3"; onClicked: wrapSelection("<h3>", "</h3>"); visible: !rawMode }

        ToolSeparator { visible: !rawMode }

        ToolButton { text: "p"; onClicked: insertText("<p>", "</p>"); visible: !rawMode }
        ToolButton { text: "br"; onClicked: insertText("<br/>", ""); visible: !rawMode }
        ToolButton { text: "Link"; onClicked: insertText("<a href=\"#\">", "</a>"); visible: !rawMode }
        ToolButton { text: "p"; onClicked: wrapSelection("<p>", "</p>"); visible: !rawMode }
        ToolButton { text: "br"; onClicked: wrapSelection("<br/>", ""); visible: !rawMode }
        ToolButton { text: "Link"; onClicked: wrapSelection("<a href=\"#\">", "</a>"); visible: !rawMode }

        ToolSeparator { visible: !rawMode }

        /* Text color */
        ToolButton {
            id: fgColorBtn
            visible: !rawMode
            contentItem: Row {
                spacing: 2
                Text { text: "A"; font.pixelSize: 13; font.bold: true; color: Theme.textPrimary }
                Rectangle { width: 14; height: 4; anchors.bottom: parent.bottom; color: fgColorSwatch.selectedColor; radius: 1 }
            }
            ToolTip.text: qsTr("Text Color")
            ToolTip.visible: hovered
            onClicked: fgColorPopup.open()

            property string selectedColor: ""

            Popup {
                id: fgColorPopup
                width: colorGrid.width + 16
                height: colorGrid.height + clearFgBtn.height + 24
                padding: 8

                GridLayout {
                    id: colorGrid
                    columns: 6
                    columnSpacing: 4
                    rowSpacing: 4

                    Repeater {
                        model: [
                            "#000000", "#ffffff", "#e63946", "#f4a261", "#2a9d8f", "#264653",
                            "#e76f51", "#f1c453", "#06d6a0", "#118ab2", "#073b4c", "#6c757d",
                            "#d62828", "#f77f00", "#3ad43a", "#4361ee", "#7209b7", "#ff006e",
                            "var(--blogi-text)", "var(--blogi-accent)", "var(--blogi-text-muted)",
                            "var(--blogi-error)", "var(--blogi-success-text)", "var(--blogi-nav-active)"
                        ]

                        Rectangle {
                            width: 24; height: 24; radius: 3
                            color: modelData.startsWith("var(") ? "transparent" : modelData
                            border.color: Theme.border; border.width: 1

                            /* Show label for CSS variables */
                            Text {
                                visible: modelData.startsWith("var(")
                                anchors.centerIn: parent
                                text: modelData.replace("var(--blogi-", "").replace(")", "")
                                font.pixelSize: 5
                                color: Theme.textPrimary
                            }

                            MouseArea {
                                anchors.fill: parent
                                cursorShape: Qt.PointingHandCursor
                                onClicked: {
                                    fgColorBtn.selectedColor = modelData
                                    wrapSelection("<span style=\"color:" + modelData + "\">", "</span>")
                                    fgColorPopup.close()
                                }
                            }
                        }
                    }
                }

                Button {
                    id: clearFgBtn
                    anchors.top: colorGrid.bottom
                    anchors.topMargin: 4
                    text: qsTr("Custom...")
                    font.pixelSize: 11
                    onClicked: {
                        fgColorPopup.close()
                        fgCustomPicker.initialColor = fgColorBtn.selectedColor || "#000000"
                        fgCustomPicker.open()
                    }
                }
            }
        }

        /* Background color */
        ToolButton {
            id: bgColorBtn
            visible: !rawMode
            contentItem: Row {
                spacing: 2
                Rectangle { width: 14; height: 14; color: bgColorSwatch.selectedColor || Theme.bgTertiary; radius: 2; border.color: Theme.border }
            }
            ToolTip.text: qsTr("Background Color")
            ToolTip.visible: hovered
            onClicked: bgColorPopup.open()

            property string selectedColor: ""

            Popup {
                id: bgColorPopup
                width: bgColorGrid.width + 16
                height: bgColorGrid.height + clearBgBtn.height + 24
                padding: 8

                GridLayout {
                    id: bgColorGrid
                    columns: 6
                    columnSpacing: 4
                    rowSpacing: 4

                    Repeater {
                        model: [
                            "#000000", "#ffffff", "#fce4ec", "#fff3e0", "#e8f5e9", "#e3f2fd",
                            "#f3e5f5", "#fff8e1", "#e0f7fa", "#fbe9e7", "#f1f8e9", "#ede7f6",
                            "var(--blogi-bg)", "var(--blogi-surface)", "var(--blogi-bg-alt)",
                            "var(--blogi-success-bg)", "var(--blogi-error-bg)", "var(--blogi-accent)"
                        ]

                        Rectangle {
                            width: 24; height: 24; radius: 3
                            color: modelData.startsWith("var(") ? "transparent" : modelData
                            border.color: Theme.border; border.width: 1

                            Text {
                                visible: modelData.startsWith("var(")
                                anchors.centerIn: parent
                                text: modelData.replace("var(--blogi-", "").replace(")", "")
                                font.pixelSize: 5
                                color: Theme.textPrimary
                            }

                            MouseArea {
                                anchors.fill: parent
                                cursorShape: Qt.PointingHandCursor
                                onClicked: {
                                    bgColorBtn.selectedColor = modelData
                                    wrapSelection("<span style=\"background:" + modelData + "\">", "</span>")
                                    bgColorPopup.close()
                                }
                            }
                        }
                    }
                }

                Button {
                    id: clearBgBtn
                    anchors.top: bgColorGrid.bottom
                    anchors.topMargin: 4
                    text: qsTr("Custom...")
                    font.pixelSize: 11
                    onClicked: {
                        bgColorPopup.close()
                        bgCustomPicker.initialColor = bgColorBtn.selectedColor || "#ffffff"
                        bgCustomPicker.open()
                    }
                }
            }
        }

        Item { Layout.fillWidth: true }

@@ -39,6 +247,26 @@ ColumnLayout {
        }
    }

    /* Hidden color swatch references for the buttons */
    QtObject { id: fgColorSwatch; property string selectedColor: fgColorBtn.selectedColor || Theme.textPrimary }
    QtObject { id: bgColorSwatch; property string selectedColor: bgColorBtn.selectedColor }

    /* Custom RGBA pickers */
    RgbaColorPicker {
        id: fgCustomPicker
        onColorSelected: function(colorStr) {
            fgColorBtn.selectedColor = colorStr
            wrapSelection("<span style=\"color:" + colorStr + "\">", "</span>")
        }
    }
    RgbaColorPicker {
        id: bgCustomPicker
        onColorSelected: function(colorStr) {
            bgColorBtn.selectedColor = colorStr
            wrapSelection("<span style=\"background:" + colorStr + "\">", "</span>")
        }
    }

    ScrollView {
        Layout.fillWidth: true
        Layout.minimumHeight: 150
@@ -47,30 +275,35 @@ ColumnLayout {
        TextArea {
            id: editorArea
            text: root.text
            textFormat: root.rawMode ? TextEdit.PlainText : TextEdit.RichText
            textFormat: TextEdit.RichText
            font.pixelSize: 13
            color: Theme.textPrimary
            wrapMode: TextEdit.Wrap
            background: Rectangle { color: Theme.bgTertiary; border.color: Theme.border; radius: Theme.radius }

            onTextChanged: {
                if (editorArea.activeFocus) {
                if (editorArea.activeFocus && !root._switching) {
                    if (root.rawMode)
                        root.textChangedByUser(editorArea.text)
                    else
                        root.textChangedByUser(stripQtArtifacts(editorArea.text))
                }
            }
        }
    }

    function insertText(prefix, suffix) {
        var start = editorArea.selectionStart;
        var end = editorArea.selectionEnd;
        var currentText = editorArea.getText(0, editorArea.length);
        var selectedText = currentText.substring(start, end);
        var newText = currentText.substring(0, start) + prefix + selectedText + suffix + currentText.substring(end);
    function wrapSelection(prefix, suffix) {
        var start = editorArea.selectionStart
        var end = editorArea.selectionEnd
        var currentText = editorArea.getText(0, editorArea.length)
        var selectedText = currentText.substring(start, end)
        var newText = currentText.substring(0, start) + prefix + selectedText + suffix + currentText.substring(end)

        editorArea.text = newText;
        editorArea.cursorPosition = start + prefix.length + selectedText.length;
        _switching = true
        editorArea.text = newText
        editorArea.cursorPosition = start + prefix.length + selectedText.length
        _switching = false

        root.textChangedByUser(editorArea.text);
        root.textChangedByUser(stripQtArtifacts(editorArea.text))
    }
}
+11 −4
Original line number Diff line number Diff line
@@ -52,6 +52,12 @@ ApplicationWindow {
            Item { Layout.fillWidth: true }
            Row {
                spacing: 4
                ComboBox {
                    id: themeSelect
                    model: ["Dark", "Light"]
                    implicitWidth: 90
                    onCurrentIndexChanged: previewFrame.darkPreview = (currentIndex === 0)
                }
                Button {
                    text: "Desktop"
                    highlighted: !previewFrame.mobileMode
@@ -72,13 +78,14 @@ ApplicationWindow {

        /* ---- Sidebar ---- */
        ScrollView {
            SplitView.preferredWidth: 280
            SplitView.minimumWidth: 200
            SplitView.maximumWidth: 500
            id: leftSidebar
            SplitView.preferredWidth: 350
            SplitView.minimumWidth: 250
            SplitView.maximumWidth: 1024
            clip: true

            ColumnLayout {
                width: parent.width
                width: leftSidebar.availableWidth
                spacing: 0

                /* Widgets (collapsed by default) */
+50 −20
Original line number Diff line number Diff line
@@ -6,11 +6,13 @@ Item {
    id: previewPane

    property bool mobileMode: false
    property bool darkPreview: true
    property bool useServerPreview: false
    property string serverHtml: ""
    property bool engineReady: false

    onMobileModeChanged: reload()
    onDarkPreviewChanged: reload()

    function reload() {
        if (!engineReady) return
@@ -20,26 +22,54 @@ Item {
        } else {
            var html = localApi.renderPreview()
            var baseUrl = surl.length > 0 ? surl : "about:blank"
            var cssVars
            if (darkPreview) {
                cssVars = ":root {"
                    + "  --blogi-bg: #232627;"
                    + "  --blogi-surface: #31363b;"
                    + "  --blogi-surface-a: rgba(49,54,59,0.7);"
                    + "  --blogi-text: #eeeeee;"
                    + "  --blogi-text-muted: #aaaaaa;"
                    + "  --blogi-accent: #3ad43a;"
                    + "  --blogi-accent-dark: #2ea82e;"
                    + "  --blogi-border: #3a3f44;"
                    + "  --blogi-bg-alt: #2a2e32;"
                    + "  --blogi-error: #F36947;"
                    + "  --blogi-error-bg: #3d1f1a;"
                    + "  --blogi-error-text: #ff9a8a;"
                    + "  --blogi-success-bg: #1a3d1f;"
                    + "  --blogi-success-text: #8aff9a;"
                    + "  --blogi-nav-active: #3daee9;"
                    + "  --blogi-nav-hover: #232629;"
                    + "  --blogi-nav-border: #222529;"
                    + "  color-scheme: dark;"
                    + "}"
            } else {
                cssVars = ":root {"
                    + "  --blogi-bg: rgba(235,237,240,1);"
                    + "  --blogi-surface: rgba(245,246,248,1);"
                    + "  --blogi-surface-a: rgba(245,246,248,0.88);"
                    + "  --blogi-text: rgba(44,62,80,1);"
                    + "  --blogi-text-muted: rgba(127,140,141,1);"
                    + "  --blogi-accent: rgba(39,148,94,1);"
                    + "  --blogi-accent-dark: rgba(30,122,75,1);"
                    + "  --blogi-border: rgba(220,225,230,1);"
                    + "  --blogi-bg-alt: rgba(232,236,240,1);"
                    + "  --blogi-error: rgba(231,76,60,1);"
                    + "  --blogi-error-bg: rgba(253,236,234,1);"
                    + "  --blogi-error-text: rgba(192,57,43,1);"
                    + "  --blogi-success-bg: rgba(234,250,241,1);"
                    + "  --blogi-success-text: rgba(39,174,96,1);"
                    + "  --blogi-nav-active: rgba(41,128,185,1);"
                    + "  --blogi-nav-hover: rgba(232,236,240,1);"
                    + "  --blogi-nav-border: rgba(189,195,199,1);"
                    + "  color-scheme: light;"
                    + "}"
            }
            var bgColor = darkPreview ? "#232627" : "rgba(235,237,240,1)"
            webView.loadHtml(
                "<!DOCTYPE html><html><head><meta charset='utf-8'>"
                + "<style>"
                + ":root {"
                + "  --blogi-bg: #ffffff;"
                + "  --blogi-surface: #f8f9fa;"
                + "  --blogi-surface-a: #f0f1f3;"
                + "  --blogi-text: #1a1a2e;"
                + "  --blogi-text-muted: #6c757d;"
                + "  --blogi-accent: #4361ee;"
                + "  --blogi-accent-dark: #3a56d4;"
                + "  --blogi-border: #dee2e6;"
                + "  --blogi-bg-alt: #eef0f2;"
                + "  --blogi-error: #e63946;"
                + "  --blogi-success-bg: #d4edda;"
                + "  --blogi-success-text: #155724;"
                + "  --blogi-nav-active: #4361ee;"
                + "  --blogi-nav-hover: #e9ecef;"
                + "  --blogi-nav-border: #dee2e6;"
                + "}"
                + "<style>" + cssVars
                + "html,body{margin:0;padding:8px;background:var(--blogi-bg);color:var(--blogi-text);font-family:sans-serif;}"
                + "</style>"
                + "</head><body>" + html + "</body></html>",
@@ -50,7 +80,7 @@ Item {

    Rectangle {
        anchors.fill: parent
        color: "#ffffff"
        color: darkPreview ? "#232627" : "#ffffff"
    }

    WebEngineView {
@@ -62,7 +92,7 @@ Item {
        settings.localContentCanAccessRemoteUrls: true
        settings.javascriptEnabled: true

        backgroundColor: "#ffffff"
        backgroundColor: darkPreview ? "#232627" : "#ffffff"

        onLoadingChanged: function(loadRequest) {
            if (!previewPane.engineReady && loadRequest.status !== WebEngineView.LoadStartedStatus) {