Warning, /office/klevernotes/src/contents/ui/textEditor/TextToolBar.qml is written in an unsupported language. File is not indexed.
0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 // SPDX-FileCopyrightText: 2022 Louis Schul <schul9louis@gmail.com>
0003
0004 import QtQuick 2.15
0005 import QtQuick.Controls 2.15
0006 import QtQuick.Dialogs
0007
0008 import org.kde.kirigami 2.19 as Kirigami
0009
0010 import org.kde.Klever 1.0
0011
0012 import "qrc:/contents/ui/dialogs"
0013 import "qrc:/contents/ui/dialogs/emojiDialog"
0014 import "qrc:/contents/ui/dialogs/imagePickerDialog"
0015 import "qrc:/contents/ui/dialogs/tableMakerDialog"
0016
0017 Kirigami.ActionToolBar {
0018 id: toolbar
0019
0020 required property TextArea editorTextArea
0021 required property string notePath
0022
0023 readonly property QtObject imagePickerDialog: imagePickerDialog
0024 // This 'replicate' the DefaultCardBackground and just change the background color
0025 //(https://api.kde.org/frameworks/kirigami/html/DefaultCardBackground_8qml_source.html)
0026 background: Kirigami.ShadowedRectangle{
0027 Kirigami.Theme.colorSet: Kirigami.Theme.Header
0028 Kirigami.Theme.inherit: false
0029 color: Kirigami.Theme.backgroundColor
0030 radius: Kirigami.Units.smallSpacing
0031 }
0032
0033 actions: [
0034 Kirigami.Action {
0035 text: "𝐇"
0036 tooltip: i18nc("@tooltip, text format, will be followed by the shortcut", "Headers") + " (Ctrl+" + i18nc("@tooltip, short form of 'number'", "num") + ")"
0037
0038 Kirigami.Action {
0039 text: "𝐇𝟏"
0040 shortcut: "Ctrl+1"
0041 tooltip: i18nc("@tooltip, text format header level, will be followed by the shortcut", "Header 1") + " (" + shortcut + ")"
0042
0043 onTriggered: {
0044 const [selectionStart, selectionEnd] = getLinesBlock(editorTextArea.selectionStart,
0045 editorTextArea.selectionEnd);
0046
0047 handleAction(selectionStart, selectionEnd, ["# "], false, false, false)
0048 }
0049 }
0050 Kirigami.Action {
0051 text: "𝐇𝟐"
0052 shortcut: "Ctrl+2"
0053 tooltip: i18nc("@tooltip, text format header level, will be followed by the shortcut", "Header 2") + " (" + shortcut + ")"
0054
0055 onTriggered: {
0056 const [selectionStart, selectionEnd] = getLinesBlock(editorTextArea.selectionStart,
0057 editorTextArea.selectionEnd);
0058
0059 handleAction(selectionStart, selectionEnd, ["## "], false, false, false)
0060 }
0061 }
0062 Kirigami.Action {
0063 text: "𝐇𝟑"
0064 shortcut: "Ctrl+3"
0065 tooltip: i18nc("@tooltip, text format header level, will be followed by the shortcut", "Header 3") + " (" + shortcut + ")"
0066
0067 onTriggered: {
0068 const [selectionStart, selectionEnd] = getLinesBlock(editorTextArea.selectionStart,
0069 editorTextArea.selectionEnd);
0070
0071 handleAction(selectionStart, selectionEnd, ["### "], false, false, false)
0072 }
0073 }
0074 Kirigami.Action {
0075 text: "𝐇𝟒"
0076 shortcut: "Ctrl+4"
0077 tooltip: i18nc("@tooltip, text format header level, will be followed by the shortcut", "Header 4") + " (" + shortcut + ")"
0078
0079 onTriggered: {
0080 const [selectionStart, selectionEnd] = getLinesBlock(editorTextArea.selectionStart,
0081 editorTextArea.selectionEnd);
0082
0083 handleAction(selectionStart, selectionEnd, ["#### "], false, false, false)
0084 }
0085 }
0086 Kirigami.Action {
0087 text: "𝐇𝟓"
0088 shortcut: "Ctrl+5"
0089 tooltip: i18nc("@tooltip, text format header level, will be followed by the shortcut", "Header 5") + " (" + shortcut + ")"
0090
0091 onTriggered: {
0092 const [selectionStart, selectionEnd] = getLinesBlock(editorTextArea.selectionStart,
0093 editorTextArea.selectionEnd);
0094
0095 handleAction(selectionStart, selectionEnd, ["###### "], false, false, false)
0096 }
0097 }
0098 Kirigami.Action {
0099 text: "𝐇𝟔"
0100 shortcut: "Ctrl+6"
0101 tooltip: i18nc("@tooltip, text format header level, will be followed by the shortcut", "Header 6") + " (" + shortcut + ")"
0102
0103 onTriggered: {
0104 const [selectionStart, selectionEnd] = getLinesBlock(editorTextArea.selectionStart,
0105 editorTextArea.selectionEnd);
0106
0107 handleAction(selectionStart, selectionEnd, ["####### "], false, false, false)
0108 }
0109 }
0110
0111 },
0112 Kirigami.Action {
0113 id: boldAction
0114 shortcut: "Ctrl+B"
0115 tooltip: i18nc("@tooltip, text format, will be followed by the shortcut", "Bold") + " (" + shortcut + ")"
0116 icon.name: "format-text-bold"
0117 onTriggered: handleAction(editorTextArea.selectionStart,
0118 editorTextArea.selectionEnd, ["**","__"],
0119 true, false, false)
0120 },
0121 Kirigami.Action {
0122 id: italicAction
0123 shortcut: "Ctrl+I"
0124 tooltip: i18nc("@tooltip, text format, will be followed by the shortcut", "Italic") + " (" + shortcut + ")"
0125 icon.name: "format-text-italic"
0126 onTriggered: handleAction(editorTextArea.selectionStart,
0127 editorTextArea.selectionEnd, ["_","*"],
0128 true, false, false)
0129 },
0130 Kirigami.Action {
0131 id: strikethroughAction
0132 shortcut: "Alt+Shift+S"
0133 tooltip: i18nc("@tooltip, text format, will be followed by the shortcut", "Strikethrough") + " (" + shortcut + ")"
0134 icon.name: "format-text-strikethrough"
0135 onTriggered: handleAction(editorTextArea.selectionStart,
0136 editorTextArea.selectionEnd, ["~~"],
0137 true, false, false)
0138 },
0139 Kirigami.Action {
0140 id: codeBlockAction
0141 shortcut: "Ctrl+Shift+K"
0142 tooltip: i18nc("@tooltip, text format, will be followed by the shortcut", "Code") + " (" + shortcut + ")"
0143 icon.name: "format-text-code"
0144 onTriggered: handleAction(editorTextArea.selectionStart,
0145 editorTextArea.selectionEnd, ["\n```\n"],
0146 true, false, true)
0147 },
0148 Kirigami.Action {
0149 id: quoteAction
0150 shortcut: "Ctrl+Shift+Q"
0151 tooltip: i18nc("@tooltip, text format, will be followed by the shortcut", "Quote") + " (" + shortcut + ")"
0152 icon.name: "format-text-blockquote"
0153 onTriggered: handleAction(editorTextArea.selectionStart,
0154 editorTextArea.selectionEnd, ["> "],
0155 false, false, false)
0156 },
0157 Kirigami.Action {
0158 id: imageAction
0159 shortcut: "Ctrl+Shift+I"
0160 tooltip: i18nc("@tooltip, text format, will be followed by the shortcut", "Image") + " (" + shortcut + ")"
0161 icon.name: "insert-image"
0162 onTriggered: imagePickerDialog.open()
0163 },
0164 Kirigami.Action {
0165 id: linkAction
0166 shortcut: "Ctrl+K"
0167 tooltip: i18nc("@tooltip, text format, will be followed by the shortcut", "Link") + " (" + shortcut + ")"
0168 icon.name: "insert-link-symbolic"
0169 onTriggered: linkDialog.open()
0170 },
0171 Kirigami.Action {
0172 id: tableAction
0173 shortcut: "Ctrl+T"
0174 tooltip: i18nc("@tooltip, text format, will be followed by the shortcut", "Table") + " (" + shortcut + ")"
0175 icon.name: "insert-table"
0176 onTriggered: tableMakerDialog.open()
0177 },
0178 Kirigami.Action {
0179 id: orderedListAction
0180 shortcut: "Ctrl+Shift+O"
0181 tooltip: i18nc("@tooltip, text format, will be followed by the shortcut", "Ordered list") + " (" + shortcut + ")"
0182 icon.name: "format-list-ordered"
0183 onTriggered: {
0184 const [selectionStart, selectionEnd] = getLinesBlock(editorTextArea.selectionStart,
0185 editorTextArea.selectionEnd);
0186
0187 handleAction(selectionStart, selectionEnd, [". "], false, true, false)
0188 }
0189 },
0190 Kirigami.Action {
0191 id: unorderedListAction
0192 shortcut: "Ctrl+Shift+U"
0193 tooltip: i18nc("@tooltip, text format, will be followed by the shortcut", "Unordered list") + " (" + shortcut + ")"
0194 icon.name: "format-list-unordered"
0195 onTriggered: {
0196 const [selectionStart, selectionEnd] = getLinesBlock(editorTextArea.selectionStart,
0197 editorTextArea.selectionEnd);
0198
0199 handleAction(selectionStart, selectionEnd, ["- "], false, false, false)
0200 }
0201 },
0202 Kirigami.Action {
0203 id: highlightAction
0204 shortcut: "Ctrl+Alt+H"
0205 tooltip: i18nc("@tooltip, text format, will be followed by the shortcut", "Text highlight") + " (" + shortcut + ")"
0206 icon.name: "draw-highlight"
0207 onTriggered: {
0208 handleAction(editorTextArea.selectionStart, editorTextArea.selectionEnd, ["=="], true, false, false)
0209 }
0210 },
0211 Kirigami.Action {
0212 id: emojiAction
0213 shortcut: "Ctrl+Shift+E"
0214 tooltip: i18nc("@tooltip, text format, will be followed by the shortcut", "Unordered list") + " (" + shortcut + ")"
0215 icon.name: "smiley"
0216 onTriggered: {
0217 emojiDialog.open()
0218 }
0219 },
0220 Kirigami.Action {
0221 id: linkNoteAction
0222 visible: Config.noteMapEnabled
0223 shortcut: "Ctrl+Alt+K"
0224 tooltip: i18nc("@tooltip, text format, will be followed by the shortcut", "Link note") + " (" + shortcut + ")"
0225 icon.name: "edit-link"
0226 onTriggered: {
0227 linkNoteDialog.open()
0228 }
0229 }
0230 ]
0231
0232 EmojiDialog {
0233 id: emojiDialog
0234
0235 onChosen: function (emoji) {
0236 editorTextArea.insert(editorTextArea.selectionStart, Config.quickEmojiEnabled && Config.quickEmojiDialogEnabled ? (":" + emoji + ":") : emoji)
0237 }
0238 }
0239
0240 ImagePickerDialog {
0241 id: imagePickerDialog
0242
0243 noteImagesStoringPath: toolbar.notePath.replace("note.md","") + "Images/"
0244
0245 onRejected: {
0246 storedImageChoosen = false
0247 }
0248 onAccepted: if (imageLoaded) {
0249 let modifiedPath = path
0250
0251 let useLocalImage = storedImageChoosen
0252 if (storeImage && !storedImageChoosen) {
0253 let wantedImageName = imageName
0254
0255 if (wantedImageName.length === 0 && paintedImageChoosen) {
0256 wantedImageName = "painting"
0257 } else if (!paintedImageChoosen) {
0258 const fileName = KleverUtility.getName(path)
0259 wantedImageName = fileName.substring(0,fileName.lastIndexOf("."))
0260 }
0261
0262 // We can't asign the result to modifiedPath and use it to saveToFile or it won't work !
0263 const validPath = KleverUtility.getImageStoragingPath(noteImagesStoringPath, wantedImageName)
0264 modifiedPath = validPath
0265
0266 imageObject.grabToImage(function(result) {
0267 result.saveToFile(validPath)
0268 },Qt.size(imageObject.idealWidth,imageObject.idealHeight));
0269
0270 useLocalImage = true
0271
0272 storedImagesExist = true
0273 }
0274
0275 if (modifiedPath.startsWith("file://")) modifiedPath = modifiedPath.replace("file://","")
0276
0277 if (useLocalImage) modifiedPath = "./Images/"+modifiedPath.replace(noteImagesStoringPath,"")
0278
0279 if (modifiedPath.startsWith("/home/")) {
0280 // Get the first "/" after the /home/username
0281 modifiedPath = modifiedPath.replace("/home/","")
0282 const idx = modifiedPath.indexOf("/")
0283 modifiedPath = "~" + modifiedPath.substring(idx)
0284 }
0285
0286 let imageString = ' '
0287
0288 toolbar.editorTextArea.insert(toolbar.editorTextArea.cursorPosition, imageString)
0289
0290 storedImageChoosen = false
0291 }
0292 }
0293
0294 TableMakerDialog {
0295 id: tableMakerDialog
0296
0297 onAccepted: {
0298 const alignPattern = {
0299 "left": ":" + "-".repeat(i18n("Header").length - 1),
0300 "center": ":" + "-".repeat(i18n("Header").length - 2) + ":",
0301 "right": "-".repeat(i18n("Header").length - 1) + ":"
0302 }
0303 const cells = "|" + (" ".repeat(i18n("Header").length) + "|").repeat(tableMakerDialog.columnCount) + "\n"
0304 const headers = "|" + (i18n("Header") + "|").repeat(tableMakerDialog.columnCount) + "\n"
0305
0306 let columnsAlignments = "|"
0307
0308 for(var childIdx = 0; childIdx < tableMakerDialog.columnCount; childIdx++) {
0309 columnsAlignments = columnsAlignments.concat(alignPattern[tableMakerDialog.alignment],"|")
0310 }
0311 columnsAlignments += "\n"
0312
0313 const result = "\n" + headers + columnsAlignments + cells.repeat(tableMakerDialog.rowCount-1)
0314
0315 toolbar.editorTextArea.insert(toolbar.editorTextArea.cursorPosition, result)
0316 }
0317 }
0318
0319 LinkDialog {
0320 id: linkDialog
0321
0322 onAccepted: {
0323 let linkString = '[' + linkText + '](' + urlText + ') '
0324 toolbar.editorTextArea.insert(toolbar.editorTextArea.cursorPosition, linkString)
0325 }
0326 }
0327
0328 LinkNoteDialog {
0329 id: linkNoteDialog
0330
0331 listModel: applicationWindow().globalDrawer.treeModel
0332
0333 onAccepted: {
0334 const text = linkText.trim()
0335 const notePath = path.substring(Config.storagePath.length)
0336 const headerPart = headerString.length > 0
0337 ? " : " + headerString
0338 : ""
0339 const textPart = text.length > 0
0340 ? " | " + linkText
0341 : ""
0342 const linkString = '[[' + notePath + headerPart + textPart + ']]'
0343 toolbar.editorTextArea.insert(toolbar.editorTextArea.cursorPosition, linkString)
0344 }
0345 }
0346
0347 function applyInstructions(selectionStart, selectionEnd, info, givenSpecialChars,
0348 multiPlaceApply, applyIncrement, checkByBlock) {
0349 let applied = false
0350 let specialChars = givenSpecialChars
0351 if (checkByBlock) {
0352 const instruction = info.instructions
0353
0354 if (instruction === "apply") {
0355 toolbar.editorTextArea.insert(selectionEnd, specialChars)
0356 toolbar.editorTextArea.insert(selectionStart, specialChars)
0357 applied = true
0358
0359 } else {
0360 toolbar.editorTextArea.remove(selectionEnd - specialChars.length + 1, selectionEnd + 1)
0361 toolbar.editorTextArea.remove(selectionStart, selectionStart + specialChars.length)
0362 }
0363 } else {
0364 const instructions = info.instructions
0365 const lines = info.lines
0366
0367 let end = selectionEnd
0368
0369 // Currently only used for ordered list
0370 const nonEmptyStrNumber = lines.filter((line) => line.trim().length > 0).length
0371 const hasNonEmptyStrings = nonEmptyStrNumber > 0
0372 let counter = hasNonEmptyStrings ? nonEmptyStrNumber : 1 // Pressing ordered list on an single empty line will return 1. and not 0.
0373
0374 for (var i = lines.length-1 ; i >= 0; i--) {
0375 const line = lines[i]
0376 const instruction = instructions[i]
0377
0378 end = (line.length > 0 || lines.length == 1) ? end : end - 1
0379 const start = end - line.length
0380
0381 // Currently only used for ordered list
0382 if (line.trim().length === 0 && hasNonEmptyStrings) continue
0383 if (applyIncrement) {
0384 specialChars = counter.toString() + givenSpecialChars
0385 counter--
0386 }
0387
0388 switch(instruction) {
0389 case "apply":
0390 if (multiPlaceApply) toolbar.editorTextArea.insert(end, specialChars)
0391 toolbar.editorTextArea.insert(start, specialChars)
0392
0393 applied = true
0394 break;
0395 case "remove":
0396 if (multiPlaceApply) toolbar.editorTextArea.remove(end - specialChars.length, end)
0397 toolbar.editorTextArea.remove(start, start + specialChars.length)
0398 break;
0399 default:
0400 break
0401 }
0402 end = start - 1
0403 }
0404 }
0405 if (applied) {
0406 let end = toolbar.editorTextArea.selectionEnd
0407
0408 let endingNewLineCounter = 0
0409 while (specialChars.endsWith('\n')) {
0410 endingNewLineCounter++
0411 specialChars = specialChars.substring(0, specialChars.length - 2)
0412 }
0413
0414 end = (checkByBlock) ? end - endingNewLineCounter : end
0415 toolbar.editorTextArea.select(selectionStart, end)
0416 }
0417 }
0418
0419 function handleAction(selectionStart, selectionEnd, specialChars,
0420 multiPlaceApply, applyIncrement, checkByBlock) {
0421
0422 const selectedText = editorTextArea.getText(selectionStart, selectionEnd)
0423 const info = MDHandler.getInstructions(selectedText, specialChars,
0424 multiPlaceApply, applyIncrement,
0425 checkByBlock)
0426
0427 const appliedSpecialChars = specialChars[0]
0428 applyInstructions(selectionStart, selectionEnd, info,
0429 appliedSpecialChars, multiPlaceApply,
0430 applyIncrement, checkByBlock)
0431 }
0432
0433 function getLinesBlock(selectionStart,selectionEnd) {
0434 const startingText = editorTextArea.getText(0, editorTextArea.selectionStart)
0435 const endingText = editorTextArea.getText(editorTextArea.selectionEnd,
0436 editorTextArea.text.length)
0437
0438
0439 const startBlockIndex = startingText.lastIndexOf('\n')+1
0440
0441 return [startBlockIndex, editorTextArea.selectionEnd]
0442 }
0443 }