Warning, /office/klevernotes/src/contents/ui/textEditor/TextDisplay.qml is written in an unsupported language. File is not indexed.

0001 // SPDX-FileCopyrightText: 2023 Louis Schul <schul9louis@gmail.com>
0002 // SPDX-License-Identifier: GPL-3.0-or-later
0003 
0004 // ORIGINALLY BASED ON : https://github.com/CrazyCxl/markdown-editor
0005 // SPDX-FileCopyrightText: 2019 CrazyCxl <chenxiaolong0001@gmail.com>
0006 
0007 import QtQuick 2.2
0008 import QtQuick.Controls 2.2
0009 import QtWebChannel 1.0
0010 import QtWebEngine 1.10
0011 import QtQuick.Layouts 1.15
0012 import Qt.labs.platform 1.1
0013 
0014 import org.kde.kirigami 2.19 as Kirigami
0015 
0016 import org.kde.Klever 1.0
0017 
0018 RowLayout {
0019     id: root
0020 
0021     required property string path
0022     required property string text
0023     
0024     // Syntax highlight
0025     readonly property bool highlightEnabled: Config.codeSynthaxHighlightEnabled // give us acces to a "Changed" signal
0026     readonly property string highlighterStyle: Config.codeSynthaxHighlighterStyle // This will also be triggered when the highlighter itself is changed
0027     // NoteMapper
0028     readonly property bool noteMapEnabled: Config.noteMapEnabled // give us acces to a "Changed" signal
0029     readonly property NoteMapper noteMapper: applicationWindow().noteMapper
0030     // Emoji
0031     readonly property bool emojiEnabled: Config.quickEmojiEnabled
0032     readonly property string emojiTone: Config.emojiTone
0033     // PlantUML
0034     readonly property bool pumlEnabled: Config.pumlEnabled
0035     readonly property bool pumlDark: Config.pumlDark
0036 
0037     readonly property Parser parser: parser
0038     readonly property string stylePath: Config.stylePath
0039     readonly property var codeFontInfo: KleverUtility.fontInfo(Config.codeFont)
0040     readonly property var viewFontInfo: KleverUtility.fontInfo(Config.viewFont)
0041     readonly property string previewLocation: StandardPaths.writableLocation(StandardPaths.TempLocation)+"/pdf-preview.pdf"
0042     readonly property string emptyPreview: (StandardPaths.writableLocation(StandardPaths.TempLocation)+"/empty.pdf").substring(7)
0043     readonly property var defaultCSS: {
0044         '--bodyColor': Config.viewBodyColor !== "None" ? Config.viewBodyColor : Kirigami.Theme.backgroundColor,
0045         '--font': viewFontInfo.family,
0046         '--fontSize': viewFontInfo.pointSize + "px",
0047         '--textColor': Config.viewTextColor !== "None" ? Config.viewTextColor : Kirigami.Theme.textColor,
0048         '--titleColor': Config.viewTitleColor !== "None" ? Config.viewTitleColor : Kirigami.Theme.disabledTextColor,
0049         '--linkColor': Config.viewLinkColor !== "None" ? Config.viewLinkColor : Kirigami.Theme.linkColor,
0050         '--visitedLinkColor': Config.viewVisitedLinkColor !== "None" ? Config.viewVisitedLinkColor : Kirigami.Theme.visitedLinkColor,
0051         '--codeColor': Config.viewCodeColor !== "None" ? Config.viewCodeColor : Kirigami.Theme.alternateBackgroundColor,
0052         '--highlightColor': Config.viewHighlightColor !== "None" ? Config.viewHighlightColor : Kirigami.Theme.highlightColor,
0053         '--codeFont': codeFontInfo.family,
0054         '--codeFontSize': codeFontInfo.pointSize + "px",
0055     }
0056 
0057     property string defaultHtml
0058     property string parsedHtml
0059     property string cssStyle
0060     property string completCss
0061     property bool printBackground: true
0062     property bool isInit: false
0063 
0064     spacing: 0
0065 
0066     Kirigami.Theme.colorSet: Kirigami.Theme.View
0067     Kirigami.Theme.inherit: false
0068 
0069     onPathChanged: {
0070         parser.notePath = path
0071     }
0072     onTextChanged: {
0073         root.parseText()
0074     }
0075     onHighlightEnabledChanged: {
0076         root.parseText()
0077     }
0078     onHighlighterStyleChanged: {
0079         parser.newHighlightStyle()
0080         root.parseText()
0081     }
0082     onNoteMapEnabledChanged: {
0083         root.parseText()
0084     }
0085     onEmojiEnabledChanged: {
0086         root.parseText()
0087     }
0088     onEmojiToneChanged: {
0089         root.parseText()
0090     }
0091     onPumlEnabledChanged: {
0092         root.parseText()
0093     }
0094     onPumlDarkChanged: {
0095         parser.pumlDarkChanged()
0096         root.parseText()
0097     }
0098     onDefaultCSSChanged: if (web_view.loadProgress === 100) {
0099         changeStyle({})
0100     }
0101     onStylePathChanged: if (web_view.loadProgress === 100) {
0102         loadStyle()
0103     }
0104 
0105     Kirigami.Card {
0106         id: background
0107 
0108         Layout.fillWidth: true
0109         Layout.fillHeight: true
0110 
0111         WebEngineView {
0112             id: web_view
0113 
0114             x: 2
0115             y: 2
0116             width: background.width - 4
0117             height: background.height - 4
0118 
0119             settings {
0120                 showScrollBars: false
0121                 localContentCanAccessFileUrls: true
0122                 localContentCanAccessRemoteUrls: true
0123             }
0124             focus: true
0125             backgroundColor: "transparent"
0126 
0127             onJavaScriptConsoleMessage: function (level, message, lineNumber, sourceID) {
0128                 console.error('WEB:', message, lineNumber, sourceID)
0129             }
0130             onPdfPrintingFinished: {
0131                 const printingPage = applicationWindow().pageStack.currentItem
0132 
0133                 printingPage.displayPdf()
0134             }
0135             onLoadProgressChanged: if (loadProgress === 100) {
0136                 if (!root.isInit) {
0137                     loadStyle()
0138                     root.isInit = true
0139                     updateHtml()
0140                 }
0141 
0142                 scrollToHeader()
0143             }
0144             onScrollPositionChanged: if (!vbar.active) {
0145                 vbar.position = scrollPosition.y / contentsSize.height
0146             }
0147             onNavigationRequested: function(request) {
0148                 const url = request.url.toString()
0149                 if (url.startsWith("http")) {
0150                     Qt.openUrlExternally(request.url)
0151                     request.action = WebEngineNavigationRequest.IgnoreRequest
0152                     return
0153                 }
0154                 if (url.startsWith("file:///")) {
0155                     let notePath = url.substring(7)
0156                     const delimiterIndex = notePath.lastIndexOf("@HEADER@")
0157                     const header = notePath.substring(delimiterIndex + 8)
0158                     
0159                     notePath = notePath.substring(0, delimiterIndex)
0160 
0161                     const headerInfo = applicationWindow().noteMapper.getCleanedHeaderAndLevel(header)
0162                     const sidebar = applicationWindow().globalDrawer
0163                     const noteModelIndex = sidebar.treeModel.getNoteModelIndex(notePath)
0164 
0165                     if (noteModelIndex.row !== -1) {
0166                         if (header[1] !== 0) parser.headerInfo = headerInfo
0167 
0168                         sidebar.askForFocus(noteModelIndex)
0169                     } 
0170                     else {
0171                         notePath = notePath.replace(".BaseCategory", Config.categoryDisplayName).replace(".BaseGroup/", "")
0172                         showPassiveNotification(i18nc("@notification, error message %1 is a path", "%1 doesn't exists", notePath))
0173                     }
0174                     request.action = WebEngineNavigationRequest.IgnoreRequest
0175                     return
0176                 }
0177             }
0178         }
0179     }
0180 
0181     ScrollBar {
0182         id: vbar
0183 
0184         size: background.height / web_view.contentsSize.height
0185         active: hovered || pressed
0186         snapMode: ScrollBar.SnapAlways
0187         orientation: Qt.Vertical
0188         hoverEnabled: true
0189 
0190         Layout.row :0
0191         Layout.column: 1
0192         Layout.fillHeight: true
0193 
0194         onPositionChanged: {
0195             if (active) {
0196                 let scrollY = web_view.contentsSize.height * vbar.position
0197                 web_view.runJavaScript("window.scrollTo(0," + scrollY + ")")
0198             }
0199         }
0200     }
0201 
0202     function updateHtml() {
0203         if (!root.defaultHtml) root.defaultHtml = DocumentHandler.readFile(":/index.html")
0204 
0205         let customHtml = '<style>\n'
0206             + root.completCss
0207             + '</style>\n'
0208             + parsedHtml
0209         
0210         let defaultHtml = root.defaultHtml
0211         const finishedHtml = defaultHtml.replace("INSERT HTML HERE", customHtml)
0212 
0213         web_view.loadHtml(finishedHtml, "file:/")
0214     }
0215 
0216     Parser { 
0217         id: parser
0218 
0219         onNewLinkedNotesInfos: function(linkedNotesInfos) {
0220             noteMapper.addLinkedNotesInfos(linkedNotesInfos)
0221         }
0222         onNoteHeadersSent: function(notePath, noteHeaders) {
0223             noteMapper.updatePathInfo(notePath, noteHeaders)
0224         }
0225     }
0226 
0227     function parseText() {
0228         parsedHtml = parser.parse(text)
0229         updateHtml()
0230     }
0231 
0232     function changeStyle(styleDict) {
0233         if (!styleDict) return
0234         const emptyDict = Object.keys(styleDict).length === 0;
0235         styleDict = emptyDict ? defaultCSS : styleDict
0236 
0237         let varStartIndex
0238         let varEndIndex
0239         let newCssVar = ""
0240         let noBg
0241         let style = root.cssStyle
0242 
0243         for (const [cssVar, value] of Object.entries(styleDict)) {
0244             noBg = cssVar === "--bodyColor" && !root.printBackground
0245 
0246             varStartIndex = style.indexOf(cssVar)
0247             if (-1 < varStartIndex) {
0248                 varEndIndex = style.indexOf(";", varStartIndex)
0249 
0250                 newCssVar = noBg
0251                     ? cssVar + ": " + "undefined" + ";\n"
0252                     : cssVar + ": " + value + ";\n"
0253 
0254                 style = style.substring(0, varStartIndex) + newCssVar + style.substring(varEndIndex)
0255             }
0256         }
0257 
0258         root.completCss = style
0259         updateHtml()
0260     }
0261 
0262     function loadStyle() {
0263         root.cssStyle = DocumentHandler.getCssStyle(root.stylePath)
0264         changeStyle({})
0265     }
0266 
0267     function makePdf() {
0268         web_view.printToPdf(root.previewLocation.replace("file://",""))
0269     }
0270 
0271     function scrollToHeader() {
0272         if (parser.headerLevel !== "0") {
0273             web_view.runJavaScript("document.getElementById('noteMapperScrollTo')",function(result) { 
0274                 if (result) { // Seems redundant but it's mandatory due to the way the wayview handle loadProgress
0275                     web_view.runJavaScript("document.getElementById('noteMapperScrollTo').scrollIntoView()")
0276                     parser.headerInfo = ["", "0"]
0277                 }
0278             })
0279         }
0280     }
0281 }