Add highlight text shortcode (#1715)

This commit is contained in:
Veronica Berglyd Olsen
2024-02-26 22:43:52 +01:00
committed by GitHub
38 changed files with 313 additions and 148 deletions
+3 -1
View File
@@ -58,9 +58,11 @@ Some of the assets bundled with novelWriter were adapted from the following sour
## Fonts ## Fonts
The font used for the main novelWriter logo, mimetype and text banners is: The font used for the main novelWriter logo, mimetype and text banners is Pridi. Other fonts are
used on buttons and icons.
* Pridi by Cadson Demak (Open Font License, Version 1.1) * Pridi by Cadson Demak (Open Font License, Version 1.1)
* Source Sans Pro by Paul D. Hunt (SIL Open Font License)
## Special Mentions ## Special Mentions
+11 -6
View File
@@ -179,15 +179,20 @@ A syntax theme ``.conf`` file consists of the following settings:
errorline = 0, 0, 0 errorline = 0, 0, 0
replacetag = 0, 0, 0 replacetag = 0, 0, 0
modifier = 0, 0, 0 modifier = 0, 0, 0
texthighlight = 255, 255, 255, 128
In the Main section, you must define at least the ``name`` setting. The Syntax colour values are In the Main section, you must define at least the ``name`` setting. The Syntax colour values are
RGB numbers of the format ``r, g, b`` where each is an integer from to 255. Omitted values default RGB(A) numbers of the format ``r, g, b, a`` where each is an integer from to 255. The fourth value
to black, except ``background`` which defaults to white, is the alpha channel, which can be omitted.
Omitted syntax colours default to black, except ``background`` which defaults to white, and
``texthighlight`` which defaults to grey.
.. versionadded:: 2.2 .. versionadded:: 2.2
The `shortcode` syntax colour entry was added, so you need to update your custom themes if you The `shortcode` syntax colour entry was added.
made any before version 2.2.
.. versionadded:: 2.3 .. versionadded:: 2.3
The `optional` syntax colour entry was added, so you need to update your custom themes if you The `optional` syntax colour entry was added.
made any before version 2.3.
.. versionadded:: 2.4
The `texthighlight` syntax colour entry was added.
@@ -53,6 +53,7 @@ fmt_bold = nw_tb-bold.svg
fmt_bold-md = nw_tb-bold-md.svg fmt_bold-md = nw_tb-bold-md.svg
fmt_italic = nw_tb-italic.svg fmt_italic = nw_tb-italic.svg
fmt_italic-md = nw_tb-italic-md.svg fmt_italic-md = nw_tb-italic-md.svg
fmt_mark = nw_tb-mark.svg
fmt_strike = nw_tb-strike.svg fmt_strike = nw_tb-strike.svg
fmt_strike-md = nw_tb-strike-md.svg fmt_strike-md = nw_tb-strike-md.svg
fmt_subscript = nw_tb-subscript.svg fmt_subscript = nw_tb-subscript.svg
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<rect x=".26458" y=".79375" width="5.8208" height="4.7625" fill="#ffff84" fill-opacity=".8" stroke-linecap="round" stroke-width=".1354"/>
<path d="m1.3229 5.2917v-4.2333h0.98087l0.6397 1.8829q0.060924 0.18829 0.11576 0.38957 0.054831 0.19479 0.11576 0.38957h0.024369q0.060924-0.19479 0.10966-0.38957 0.054831-0.20128 0.11576-0.38957l0.62752-1.8829h0.97478v4.2333h-0.81638v-1.5518q0-0.15583 0.012185-0.34412 0.012185-0.18829 0.030462-0.37658 0.018277-0.19479 0.036554-0.37658 0.018277-0.18829 0.036554-0.34412h-0.02437l-0.3229 1.0064-0.56659 1.6167h-0.49348l-0.56659-1.6167-0.3168-1.0064h-0.024369q0.018277 0.15583 0.036554 0.34412 0.018277 0.1818 0.036554 0.37658 0.018277 0.18829 0.030462 0.37658 0.012185 0.18829 0.012185 0.34412v1.5518z" fill="#808080" stroke-linecap="round" stroke-width=".11793"/>
<path d="m4.9555 5.5562v-0.43195h0.54619v-3.8986h-0.54619v-0.43195h1.1299v4.7624z" fill="#69c" stroke-width="1.1186"/>
<path d="m0.26458 5.5562v-4.7624h1.1299v0.43195h-0.54083v3.8986h0.54083v0.43195z" fill="#69c" stroke-width="1.1186"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -53,6 +53,7 @@ fmt_bold = nw_tb-bold.svg
fmt_bold-md = nw_tb-bold-md.svg fmt_bold-md = nw_tb-bold-md.svg
fmt_italic = nw_tb-italic.svg fmt_italic = nw_tb-italic.svg
fmt_italic-md = nw_tb-italic-md.svg fmt_italic-md = nw_tb-italic-md.svg
fmt_mark = nw_tb-mark.svg
fmt_strike = nw_tb-strike.svg fmt_strike = nw_tb-strike.svg
fmt_strike-md = nw_tb-strike-md.svg fmt_strike-md = nw_tb-strike-md.svg
fmt_subscript = nw_tb-subscript.svg fmt_subscript = nw_tb-subscript.svg
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<rect x=".26458" y=".79375" width="5.8208" height="4.7625" fill="#ad8700" fill-opacity=".8" stroke-linecap="round" stroke-width=".1354"/>
<path d="m1.3229 5.2917v-4.2333h0.98087l0.6397 1.8829q0.060924 0.18829 0.11576 0.38957 0.054831 0.19479 0.11576 0.38957h0.024369q0.060924-0.19479 0.10966-0.38957 0.054831-0.20128 0.11576-0.38957l0.62752-1.8829h0.97478v4.2333h-0.81638v-1.5518q0-0.15583 0.012185-0.34412 0.012185-0.18829 0.030462-0.37658 0.018277-0.19479 0.036554-0.37658 0.018277-0.18829 0.036554-0.34412h-0.02437l-0.3229 1.0064-0.56659 1.6167h-0.49348l-0.56659-1.6167-0.3168-1.0064h-0.024369q0.018277 0.15583 0.036554 0.34412 0.018277 0.1818 0.036554 0.37658 0.018277 0.18829 0.030462 0.37658 0.012185 0.18829 0.012185 0.34412v1.5518z" fill-opacity=".78039" stroke-linecap="round" stroke-width=".11793"/>
<path d="m4.9555 5.5562v-0.43195h0.54619v-3.8986h-0.54619v-0.43195h1.1299v4.7624z" fill="#4271ae" stroke-width="1.1186"/>
<path d="m0.26458 5.5562v-4.7624h1.1299v0.43195h-0.54083v3.8986h0.54083v0.43195z" fill="#4271ae" stroke-width="1.1186"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -25,3 +25,4 @@ spellcheckline = 200, 46, 0
errorline = 46, 200, 0 errorline = 46, 200, 0
replacetag = 0, 184, 46 replacetag = 0, 184, 46
modifier = 200, 120, 0 modifier = 200, 120, 0
texthighlight = 136, 200, 0, 96
@@ -25,3 +25,4 @@ spellcheckline = 200, 0, 0
errorline = 0, 150, 0 errorline = 0, 150, 0
replacetag = 0, 150, 0 replacetag = 0, 150, 0
modifier = 150, 110, 30 modifier = 150, 110, 30
texthighlight = 150, 150, 0, 96
+1
View File
@@ -25,3 +25,4 @@ spellcheckline = 200, 46, 0
errorline = 46, 200, 0 errorline = 46, 200, 0
replacetag = 225, 225, 225 replacetag = 225, 225, 225
modifier = 225, 225, 225 modifier = 225, 225, 225
texthighlight = 255, 255, 255, 64
@@ -25,3 +25,4 @@ spellcheckline = 200, 0, 0
errorline = 0, 150, 0 errorline = 0, 150, 0
replacetag = 0, 0, 0 replacetag = 0, 0, 0
modifier = 0, 0, 0 modifier = 0, 0, 0
texthighlight = 0, 0, 0, 64
+1
View File
@@ -45,3 +45,4 @@ spellcheckline = 222, 61, 58
errorline = 8, 145, 106 errorline = 8, 145, 106
replacetag = 42, 162, 152 replacetag = 42, 162, 152
modifier = 224, 175, 5 modifier = 224, 175, 5
texthighlight = 218, 170, 1, 96
+1
View File
@@ -45,3 +45,4 @@ spellcheckline = 247, 140, 108
errorline = 173, 219, 103 errorline = 173, 219, 103
replacetag = 127, 219, 202 replacetag = 127, 219, 202
modifier = 236, 196, 141 modifier = 236, 196, 141
texthighlight = 255, 235, 149, 96
@@ -25,3 +25,4 @@ spellcheckline = 203, 75, 22
errorline = 220, 50, 47 errorline = 220, 50, 47
replacetag = 133, 153, 0 replacetag = 133, 153, 0
modifier = 181, 137, 0 modifier = 181, 137, 0
texthighlight = 181, 137, 0, 96
@@ -25,3 +25,4 @@ spellcheckline = 203, 75, 22
errorline = 220, 50, 47 errorline = 220, 50, 47
replacetag = 133, 153, 0 replacetag = 133, 153, 0
modifier = 181, 137, 0 modifier = 181, 137, 0
texthighlight = 181, 137, 0, 96
+1
View File
@@ -45,3 +45,4 @@ spellcheckline = 240, 40, 41
errorline = 113, 140, 0 errorline = 113, 140, 0
replacetag = 62, 153, 159 replacetag = 62, 153, 159
modifier = 245, 135, 31 modifier = 245, 135, 31
texthighlight = 234, 183, 0, 96
@@ -45,3 +45,4 @@ spellcheckline = 204, 102, 102
errorline = 181, 189, 104 errorline = 181, 189, 104
replacetag = 138, 190, 183 replacetag = 138, 190, 183
modifier = 222, 147, 95 modifier = 222, 147, 95
texthighlight = 240, 198, 116, 96
@@ -45,3 +45,4 @@ spellcheckline = 255, 157, 164
errorline = 209, 241, 169 errorline = 209, 241, 169
replacetag = 153, 255, 255 replacetag = 153, 255, 255
modifier = 255, 197, 143 modifier = 255, 197, 143
texthighlight = 255, 238, 173, 96
@@ -45,3 +45,4 @@ spellcheckline = 213, 78, 83
errorline = 185, 202, 74 errorline = 185, 202, 74
replacetag = 112, 192, 177 replacetag = 112, 192, 177
modifier = 231, 140, 69 modifier = 231, 140, 69
texthighlight = 231, 197, 71, 128
@@ -45,3 +45,4 @@ spellcheckline = 242, 119, 122
errorline = 153, 204, 153 errorline = 153, 204, 153
replacetag = 102, 204, 204 replacetag = 102, 204, 204
modifier = 249, 145, 57 modifier = 249, 145, 57
texthighlight = 255, 204, 102, 96
+25 -23
View File
@@ -8,9 +8,9 @@
<h3>Contributors</h3> <h3>Contributors</h3>
<p> <p>
<b>Concept:</b> Marian Lückhof (<a href="https://github.com/Number042">@Number042</a>)<br> &nbsp;&bull;&nbsp;<b>Concept:</b> Marian Lückhof (<a href="https://github.com/Number042">@Number042</a>)<br>
<b>Internationalisation:</b> Bruno Meneguello (<a href="https://github.com/bkmeneguello">@bkmeneguello</a>)<br> &nbsp;&bull;&nbsp;<b>Internationalisation:</b> Bruno Meneguello (<a href="https://github.com/bkmeneguello">@bkmeneguello</a>)<br>
<b>Setup and Packaging:</b> Rachel Powers (<a href="https://github.com/Ryex">@Ryex</a>) &nbsp;&bull;&nbsp;<b>Setup and Packaging:</b> Rachel Powers (<a href="https://github.com/Ryex">@Ryex</a>)
</p> </p>
<p>For other contributions, see the project's <p>For other contributions, see the project's
<a href="https://github.com/vkbo/novelWriter/graphs/contributors">Contributors</a> page.</p> <a href="https://github.com/vkbo/novelWriter/graphs/contributors">Contributors</a> page.</p>
@@ -24,20 +24,20 @@
<p>The default language is English (UK) with English (US) as an option. These are the original <p>The default language is English (UK) with English (US) as an option. These are the original
translators for the languages currently available:</p> translators for the languages currently available:</p>
<p> <p>
<b>Dutch:</b> Martijn van der Kleijn (<a href="https://github.com/mvdkleijn">@mvdkleijn</a>)<br> &nbsp;&bull;&nbsp;<b>Dutch:</b> Martijn van der Kleijn (<a href="https://github.com/mvdkleijn">@mvdkleijn</a>)<br>
<b>French:</b> Jan Lüdke (<a href="https://github.com/jyhelle">@jyhelle</a>)<br> &nbsp;&bull;&nbsp;<b>French:</b> Jan Lüdke (<a href="https://github.com/jyhelle">@jyhelle</a>)<br>
<b>German:</b> Myian (<a href="https://github.com/heymyian">@heymyian</a>)<br> &nbsp;&bull;&nbsp;<b>German:</b> Myian (<a href="https://github.com/heymyian">@heymyian</a>)<br>
<b>Italian:</b> Riccardo Mangili<br> &nbsp;&bull;&nbsp;<b>Italian:</b> Riccardo Mangili<br>
<b>Japanese:</b> hebekeg (<a href="https://github.com/hebekeg">@hebekeg</a>)<br> &nbsp;&bull;&nbsp;<b>Japanese:</b> hebekeg (<a href="https://github.com/hebekeg">@hebekeg</a>)<br>
<b>Latin American Spanish:</b> Tommy Marplatt (<a href="https://github.com/tmarplatt">@tmarplatt</a>)<br> &nbsp;&bull;&nbsp;<b>Latin American Spanish:</b> Tommy Marplatt (<a href="https://github.com/tmarplatt">@tmarplatt</a>)<br>
<b>Norwegian:</b> Veronica Berglyd Olsen (<a href="https://github.com/vkbo">@vkbo</a>)<br> &nbsp;&bull;&nbsp;<b>Norwegian:</b> Veronica Berglyd Olsen (<a href="https://github.com/vkbo">@vkbo</a>)<br>
<b>Portuguese:</b> Bruno Meneguello (<a href="https://github.com/bkmeneguello">@bkmeneguello</a>)<br> &nbsp;&bull;&nbsp;<b>Portuguese:</b> Bruno Meneguello (<a href="https://github.com/bkmeneguello">@bkmeneguello</a>)<br>
<b>Simplified Chinese:</b> Qianzhi Long (<a href="https://github.com/longqzh">@longqzh</a>) &nbsp;&bull;&nbsp;<b>Simplified Chinese:</b> Qianzhi Long (<a href="https://github.com/longqzh">@longqzh</a>)
</p> </p>
<p>Additional larger translation contributions:</p> <p>Additional larger translation contributions:</p>
<p> <p>
<b>French:</b> Albert Aribaud (<a href="https://github.com/aaribaud">@aaribaud</a>) &nbsp;&bull;&nbsp;<b>French:</b> Albert Aribaud (<a href="https://github.com/aaribaud">@aaribaud</a>)
</p> </p>
<p>Translations are managed on <a href="https://crowdin.com/project/novelwriter">Crowdin</a>, and <p>Translations are managed on <a href="https://crowdin.com/project/novelwriter">Crowdin</a>, and
@@ -47,27 +47,29 @@ more contributions are listed on the project's <a href="https://crowdin.com/proj
<p>The following libraries are dependencies of novelWriter:</p> <p>The following libraries are dependencies of novelWriter:</p>
<p> <p>
<a href="https://www.qt.io">Qt5</a> by Qt Company<br> &nbsp;&bull;&nbsp;<a href="https://www.qt.io">Qt5</a> by Qt Company<br>
<a href="https://www.riverbankcomputing.com/software/pyqt">PyQt5</a> by Riverbank Computing<br> &nbsp;&bull;&nbsp;<a href="https://www.riverbankcomputing.com/software/pyqt">PyQt5</a> by Riverbank Computing<br>
<a href="https://abiword.github.io/enchant">Enchant</a> by Dom Lachowicz<br> &nbsp;&bull;&nbsp;<a href="https://abiword.github.io/enchant">Enchant</a> by Dom Lachowicz<br>
<a href="https://pyenchant.github.io/pyenchant">PyEnchant</a> by Dimitri Merejkowsky &nbsp;&bull;&nbsp;<a href="https://pyenchant.github.io/pyenchant">PyEnchant</a> by Dimitri Merejkowsky
</p> </p>
<h3>Assets</h3> <h3>Assets</h3>
<p>Some of the assets bundled with novelWriter were adapted from the following sources:</p> <p>Some of the assets bundled with novelWriter were adapted from the following sources:</p>
<p> <p>
<a href="https://github.com/stephenhutchings/typicons.font">Typicons</a> icons by Stephen Hutchings (CC BY-SA 4.0)<br> &nbsp;&bull;&nbsp;<a href="https://github.com/stephenhutchings/typicons.font">Typicons</a> icons by Stephen Hutchings (CC BY-SA 4.0)<br>
<a href="https://github.com/chriskempson/base16">Tomorrow</a> syntax themes by Chris Kempson (MIT License)<br> &nbsp;&bull;&nbsp;<a href="https://github.com/chriskempson/base16">Tomorrow</a> syntax themes by Chris Kempson (MIT License)<br>
<a href="https://github.com/sdras/night-owl-vscode-theme">Owl</a> syntax themes by Sarah Drasner (MIT License)<br> &nbsp;&bull;&nbsp;<a href="https://github.com/sdras/night-owl-vscode-theme">Owl</a> syntax themes by Sarah Drasner (MIT License)<br>
<a href="https://github.com/altercation/solarized">Solarized</a> themes by Ethan Schoonover (MIT License) &nbsp;&bull;&nbsp;<a href="https://github.com/altercation/solarized">Solarized</a> themes by Ethan Schoonover (MIT License)
</p> </p>
<h3>Fonts</h3> <h3>Fonts</h3>
<p>The font used for the main novelWriter logo, mimetype and text banners is:</p> <p>The font used for the main novelWriter logo, mimetype and text banners is Pridi. Other fonts are
used on buttons and icons.</p>
<p> <p>
Pridi by Cadson Demak (Open Font License, Version 1.1) &nbsp;&bull;&nbsp;Pridi by Cadson Demak (Open Font License, Version 1.1)<br>
&nbsp;&bull;&nbsp;Source Sans Pro by Paul D. Hunt (SIL Open Font License)
</p> </p>
<h3>Special Mentions</h3> <h3>Special Mentions</h3>
+3 -1
View File
@@ -67,7 +67,7 @@ class nwRegEx:
FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)" FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
FMT_EB = r"(?<![\w\\])([\*]{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)" FMT_EB = r"(?<![\w\\])([\*]{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
FMT_ST = r"(?<![\w\\])([~]{2})(?![\s~])(.+?)(?<![\s\\])(\1)(?!\w)" FMT_ST = r"(?<![\w\\])([~]{2})(?![\s~])(.+?)(?<![\s\\])(\1)(?!\w)"
FMT_SC = r"(?i)(?<!\\)(\[[\/\!]?(?:i|b|s|u|sup|sub)\])" FMT_SC = r"(?i)(?<!\\)(\[[\/\!]?(?:i|b|s|u|m|sup|sub)\])"
FMT_SV = r"(?<!\\)(\[(?i)(?:fn|footnote):)(.+?)(?<!\\)(\])" FMT_SV = r"(?<!\\)(\[(?i)(?:fn|footnote):)(.+?)(?<!\\)(\])"
# Pre-Compiled RegEx # Pre-Compiled RegEx
@@ -86,6 +86,8 @@ class nwShortcode:
STRIKE_C = "[/s]" STRIKE_C = "[/s]"
ULINE_O = "[u]" ULINE_O = "[u]"
ULINE_C = "[/u]" ULINE_C = "[/u]"
MARK_O = "[m]"
MARK_C = "[/m]"
SUP_O = "[sup]" SUP_O = "[sup]"
SUP_C = "[/sup]" SUP_C = "[/sup]"
SUB_O = "[sub]" SUB_O = "[sub]"
+4
View File
@@ -133,6 +133,8 @@ class ToHtml(Tokenizer):
self.FMT_D_E: "</span>", self.FMT_D_E: "</span>",
self.FMT_U_B: "<u>", self.FMT_U_B: "<u>",
self.FMT_U_E: "</u>", self.FMT_U_E: "</u>",
self.FMT_M_B: "<mark>",
self.FMT_M_E: "</mark>",
} }
else: else:
htmlTags = { # HTML5 (for export) htmlTags = { # HTML5 (for export)
@@ -144,6 +146,8 @@ class ToHtml(Tokenizer):
self.FMT_D_E: "</del>", self.FMT_D_E: "</del>",
self.FMT_U_B: "<span style='text-decoration: underline;'>", self.FMT_U_B: "<span style='text-decoration: underline;'>",
self.FMT_U_E: "</span>", self.FMT_U_E: "</span>",
self.FMT_M_B: "<mark>",
self.FMT_M_E: "</mark>",
} }
htmlTags[self.FMT_SUP_B] = "<sup>" htmlTags[self.FMT_SUP_B] = "<sup>"
+7 -4
View File
@@ -74,10 +74,12 @@ class Tokenizer(ABC):
FMT_D_E = 6 # End strikeout FMT_D_E = 6 # End strikeout
FMT_U_B = 7 # Begin underline FMT_U_B = 7 # Begin underline
FMT_U_E = 8 # End underline FMT_U_E = 8 # End underline
FMT_SUP_B = 9 # Begin superscript FMT_M_B = 9 # Begin mark
FMT_SUP_E = 10 # End superscript FMT_M_E = 10 # End mark
FMT_SUB_B = 11 # Begin subscript FMT_SUP_B = 11 # Begin superscript
FMT_SUB_E = 12 # End subscript FMT_SUP_E = 12 # End superscript
FMT_SUB_B = 13 # Begin subscript
FMT_SUB_E = 14 # End subscript
# Block Type # Block Type
T_EMPTY = 1 # Empty line (new paragraph) T_EMPTY = 1 # Empty line (new paragraph)
@@ -187,6 +189,7 @@ class Tokenizer(ABC):
nwShortcode.BOLD_O: self.FMT_B_B, nwShortcode.BOLD_C: self.FMT_B_E, nwShortcode.BOLD_O: self.FMT_B_B, nwShortcode.BOLD_C: self.FMT_B_E,
nwShortcode.STRIKE_O: self.FMT_D_B, nwShortcode.STRIKE_C: self.FMT_D_E, nwShortcode.STRIKE_O: self.FMT_D_B, nwShortcode.STRIKE_C: self.FMT_D_E,
nwShortcode.ULINE_O: self.FMT_U_B, nwShortcode.ULINE_C: self.FMT_U_E, nwShortcode.ULINE_O: self.FMT_U_B, nwShortcode.ULINE_C: self.FMT_U_E,
nwShortcode.MARK_O: self.FMT_M_B, nwShortcode.MARK_C: self.FMT_M_E,
nwShortcode.SUP_O: self.FMT_SUP_B, nwShortcode.SUP_C: self.FMT_SUP_E, nwShortcode.SUP_O: self.FMT_SUP_B, nwShortcode.SUP_C: self.FMT_SUP_E,
nwShortcode.SUB_O: self.FMT_SUB_B, nwShortcode.SUB_C: self.FMT_SUB_E, nwShortcode.SUB_O: self.FMT_SUB_B, nwShortcode.SUB_C: self.FMT_SUB_E,
} }
+4
View File
@@ -95,6 +95,8 @@ class ToMarkdown(Tokenizer):
self.FMT_D_E: "", self.FMT_D_E: "",
self.FMT_U_B: "", self.FMT_U_B: "",
self.FMT_U_E: "", self.FMT_U_E: "",
self.FMT_M_B: "",
self.FMT_M_E: "",
self.FMT_SUP_B: "", self.FMT_SUP_B: "",
self.FMT_SUP_E: "", self.FMT_SUP_E: "",
self.FMT_SUB_B: "", self.FMT_SUB_B: "",
@@ -111,6 +113,8 @@ class ToMarkdown(Tokenizer):
self.FMT_D_E: "~~", self.FMT_D_E: "~~",
self.FMT_U_B: "", self.FMT_U_B: "",
self.FMT_U_E: "", self.FMT_U_E: "",
self.FMT_M_B: "==",
self.FMT_M_E: "==",
self.FMT_SUP_B: "^", self.FMT_SUP_B: "^",
self.FMT_SUP_E: "^", self.FMT_SUP_E: "^",
self.FMT_SUB_B: "~", self.FMT_SUB_B: "~",
+19 -2
View File
@@ -84,14 +84,16 @@ X_BLD = 0x01 # Bold format
X_ITA = 0x02 # Italic format X_ITA = 0x02 # Italic format
X_DEL = 0x04 # Strikethrough format X_DEL = 0x04 # Strikethrough format
X_UND = 0x08 # Underline format X_UND = 0x08 # Underline format
X_SUP = 0x10 # Superscript X_MRK = 0x10 # Marked format
X_SUB = 0x20 # Subscript X_SUP = 0x20 # Superscript
X_SUB = 0x40 # Subscript
# Formatting Masks # Formatting Masks
M_BLD = ~X_BLD M_BLD = ~X_BLD
M_ITA = ~X_ITA M_ITA = ~X_ITA
M_DEL = ~X_DEL M_DEL = ~X_DEL
M_UND = ~X_UND M_UND = ~X_UND
M_MRK = ~X_MRK
M_SUP = ~X_SUP M_SUP = ~X_SUP
M_SUB = ~X_SUB M_SUB = ~X_SUB
@@ -189,6 +191,7 @@ class ToOdt(Tokenizer):
self._opaHead34 = None self._opaHead34 = None
self._colMetaTx = None self._colMetaTx = None
self._opaMetaTx = None self._opaMetaTx = None
self._markText = "#ffffa6"
return return
@@ -643,6 +646,10 @@ class ToOdt(Tokenizer):
xFmt |= X_UND xFmt |= X_UND
elif fFmt == self.FMT_U_E: elif fFmt == self.FMT_U_E:
xFmt &= M_UND xFmt &= M_UND
elif fFmt == self.FMT_M_B:
xFmt |= X_MRK
elif fFmt == self.FMT_M_E:
xFmt &= M_MRK
elif fFmt == self.FMT_SUP_B: elif fFmt == self.FMT_SUP_B:
xFmt |= X_SUP xFmt |= X_SUP
elif fFmt == self.FMT_SUP_E: elif fFmt == self.FMT_SUP_E:
@@ -710,6 +717,8 @@ class ToOdt(Tokenizer):
newStyle.setUnderlineStyle("solid") newStyle.setUnderlineStyle("solid")
newStyle.setUnderlineWidth("auto") newStyle.setUnderlineWidth("auto")
newStyle.setUnderlineColour("font-color") newStyle.setUnderlineColour("font-color")
if hFmt & X_MRK:
newStyle.setBackgroundColor(self._markText)
if hFmt & X_SUP: if hFmt & X_SUP:
newStyle.setTextPosition("super") newStyle.setTextPosition("super")
if hFmt & X_SUB: if hFmt & X_SUB:
@@ -1279,6 +1288,7 @@ class ODTTextStyle:
self._tAttr = { self._tAttr = {
"font-weight": ["fo", None], "font-weight": ["fo", None],
"font-style": ["fo", None], "font-style": ["fo", None],
"background-color": ["fo", None],
"text-position": ["style", None], "text-position": ["style", None],
"text-line-through-style": ["style", None], "text-line-through-style": ["style", None],
"text-line-through-type": ["style", None], "text-line-through-type": ["style", None],
@@ -1306,6 +1316,13 @@ class ODTTextStyle:
self._tAttr["font-style"][1] = None self._tAttr["font-style"][1] = None
return return
def setBackgroundColor(self, value: str | None) -> None:
if value and len(value) == 7 and value[0] == "#":
self._tAttr["background-color"][1] = value
else:
self._tAttr["background-color"][1] = None
return
def setTextPosition(self, value: str | None) -> None: def setTextPosition(self, value: str | None) -> None:
if value in self.VALID_POS: if value in self.VALID_POS:
self._tAttr["text-position"][1] = f"{value} 58%" self._tAttr["text-position"][1] = f"{value} 58%"
+3 -2
View File
@@ -124,8 +124,9 @@ class nwDocAction(Enum):
SC_BOLD = 31 SC_BOLD = 31
SC_STRIKE = 32 SC_STRIKE = 32
SC_ULINE = 33 SC_ULINE = 33
SC_SUP = 34 SC_MARK = 34
SC_SUB = 35 SC_SUP = 35
SC_SUB = 36
# END Enum nwDocAction # END Enum nwDocAction
+11
View File
@@ -769,6 +769,8 @@ class GuiDocEditor(QPlainTextEdit):
self._wrapSelection(nwShortcode.STRIKE_O, nwShortcode.STRIKE_C) self._wrapSelection(nwShortcode.STRIKE_O, nwShortcode.STRIKE_C)
elif action == nwDocAction.SC_ULINE: elif action == nwDocAction.SC_ULINE:
self._wrapSelection(nwShortcode.ULINE_O, nwShortcode.ULINE_C) self._wrapSelection(nwShortcode.ULINE_O, nwShortcode.ULINE_C)
elif action == nwDocAction.SC_MARK:
self._wrapSelection(nwShortcode.MARK_O, nwShortcode.MARK_C)
elif action == nwDocAction.SC_SUP: elif action == nwDocAction.SC_SUP:
self._wrapSelection(nwShortcode.SUP_O, nwShortcode.SUP_C) self._wrapSelection(nwShortcode.SUP_O, nwShortcode.SUP_C)
elif action == nwDocAction.SC_SUB: elif action == nwDocAction.SC_SUB:
@@ -2313,6 +2315,13 @@ class GuiDocToolBar(QWidget):
lambda: self.requestDocAction.emit(nwDocAction.SC_ULINE) lambda: self.requestDocAction.emit(nwDocAction.SC_ULINE)
) )
self.tbMark = QToolButton(self)
self.tbMark.setIconSize(iconSize)
self.tbMark.setToolTip(self.tr("Shortcode Highlight"))
self.tbMark.clicked.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_MARK)
)
self.tbSuperscript = QToolButton(self) self.tbSuperscript = QToolButton(self)
self.tbSuperscript.setIconSize(iconSize) self.tbSuperscript.setIconSize(iconSize)
self.tbSuperscript.setToolTip(self.tr("Shortcode Superscript")) self.tbSuperscript.setToolTip(self.tr("Shortcode Superscript"))
@@ -2339,6 +2348,7 @@ class GuiDocToolBar(QWidget):
self.outerBox.addWidget(self.tbItalic) self.outerBox.addWidget(self.tbItalic)
self.outerBox.addWidget(self.tbStrike) self.outerBox.addWidget(self.tbStrike)
self.outerBox.addWidget(self.tbUnderline) self.outerBox.addWidget(self.tbUnderline)
self.outerBox.addWidget(self.tbMark)
self.outerBox.addWidget(self.tbSuperscript) self.outerBox.addWidget(self.tbSuperscript)
self.outerBox.addWidget(self.tbSubscript) self.outerBox.addWidget(self.tbSubscript)
self.outerBox.setContentsMargins(cM, cM, cM, cM) self.outerBox.setContentsMargins(cM, cM, cM, cM)
@@ -2369,6 +2379,7 @@ class GuiDocToolBar(QWidget):
self.tbItalic.setIcon(SHARED.theme.getIcon("fmt_italic")) self.tbItalic.setIcon(SHARED.theme.getIcon("fmt_italic"))
self.tbStrike.setIcon(SHARED.theme.getIcon("fmt_strike")) self.tbStrike.setIcon(SHARED.theme.getIcon("fmt_strike"))
self.tbUnderline.setIcon(SHARED.theme.getIcon("fmt_underline")) self.tbUnderline.setIcon(SHARED.theme.getIcon("fmt_underline"))
self.tbMark.setIcon(SHARED.theme.getIcon("fmt_mark"))
self.tbSuperscript.setIcon(SHARED.theme.getIcon("fmt_superscript")) self.tbSuperscript.setIcon(SHARED.theme.getIcon("fmt_superscript"))
self.tbSubscript.setIcon(SHARED.theme.getIcon("fmt_subscript")) self.tbSubscript.setIcon(SHARED.theme.getIcon("fmt_subscript"))
+18 -36
View File
@@ -466,48 +466,30 @@ class GuiDocViewer(QTextBrowser):
colText = SHARED.theme.colText colText = SHARED.theme.colText
colHead = SHARED.theme.colHead colHead = SHARED.theme.colHead
colVals = SHARED.theme.colVal colVals = SHARED.theme.colVal
colEmph = SHARED.theme.colEmph colMark = SHARED.theme.colMark
colKeys = SHARED.theme.colKey colKeys = SHARED.theme.colKey
colHide = SHARED.theme.colHidden colHide = SHARED.theme.colHidden
colMods = SHARED.theme.colMod colMods = SHARED.theme.colMod
colOpts = SHARED.theme.colOpt colOpts = SHARED.theme.colOpt
styleSheet = ( styleSheet = (
"body {{" "body {{color: rgb({rT}, {gT}, {bT});}}\n"
" color: rgb({tColR}, {tColG}, {tColB});" "h1, h2, h3, h4 {{color: rgb({rH}, {gH}, {bH});}}\n"
"}}\n" "a {{color: rgb({rA}, {gA}, {bA});}}\n"
"h1, h2, h3, h4 {{" "mark {{background-color: rgba({rE}, {gE}, {bE}, {aE});}}\n"
" color: rgb({hColR}, {hColG}, {hColB});" ".tags {{color: rgb({rK}, {gK}, {bK});}}\n"
"}}\n" ".optional {{color: rgb({rO}, {gO}, {bO});}}\n"
"a {{" ".comment {{color: rgb({rC}, {gC}, {bC});}}\n"
" color: rgb({aColR}, {aColG}, {aColB});" ".synopsis {{color: rgb({rM}, {gM}, {bM});}}\n"
"}}\n" ".title {{text-align: center;}}\n"
"mark {{"
" color: rgb({eColR}, {eColG}, {eColB});"
"}}\n"
".tags {{"
" color: rgb({kColR}, {kColG}, {kColB});"
"}}\n"
".optional {{"
" color: rgb({oColR}, {oColG}, {oColB});"
"}}\n"
".comment {{"
" color: rgb({cColR}, {cColG}, {cColB});"
"}}\n"
".synopsis {{"
" color: rgb({mColR}, {mColG}, {mColB});"
"}}\n"
".title {{"
" text-align: center;"
"}}\n"
).format( ).format(
tColR=colText.red(), tColG=colText.green(), tColB=colText.blue(), rT=colText.red(), gT=colText.green(), bT=colText.blue(),
hColR=colHead.red(), hColG=colHead.green(), hColB=colHead.blue(), rH=colHead.red(), gH=colHead.green(), bH=colHead.blue(),
aColR=colVals.red(), aColG=colVals.green(), aColB=colVals.blue(), rA=colVals.red(), gA=colVals.green(), bA=colVals.blue(),
eColR=colEmph.red(), eColG=colEmph.green(), eColB=colEmph.blue(), rE=colMark.red(), gE=colMark.green(), bE=colMark.blue(), aE=colMark.alpha(),
kColR=colKeys.red(), kColG=colKeys.green(), kColB=colKeys.blue(), rK=colKeys.red(), gK=colKeys.green(), bK=colKeys.blue(),
cColR=colHide.red(), cColG=colHide.green(), cColB=colHide.blue(), rC=colHide.red(), gC=colHide.green(), bC=colHide.blue(),
mColR=colMods.red(), mColG=colMods.green(), mColB=colMods.blue(), rM=colMods.red(), gM=colMods.green(), bM=colMods.blue(),
oColR=colOpts.red(), oColG=colOpts.green(), oColB=colOpts.blue(), rO=colOpts.red(), gO=colOpts.green(), bO=colOpts.blue(),
) )
self.document().setDefaultStyleSheet(styleSheet) self.document().setDefaultStyleSheet(styleSheet)
+6
View File
@@ -672,6 +672,12 @@ class GuiMainMenu(QMenuBar):
lambda: self.requestDocAction.emit(nwDocAction.SC_ULINE) lambda: self.requestDocAction.emit(nwDocAction.SC_ULINE)
) )
# Shortcode Mark
self.aScMark = self.mShortcodes.addAction(self.tr("Highlight"))
self.aScMark.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_MARK)
)
# Shortcode Superscript # Shortcode Superscript
self.aScSuper = self.mShortcodes.addAction(self.tr("Superscript")) self.aScSuper = self.mShortcodes.addAction(self.tr("Superscript"))
self.aScSuper.triggered.connect( self.aScSuper.triggered.connect(
+4 -2
View File
@@ -106,6 +106,7 @@ class GuiTheme:
self.colError = QColor(0, 0, 0) self.colError = QColor(0, 0, 0)
self.colRepTag = QColor(0, 0, 0) self.colRepTag = QColor(0, 0, 0)
self.colMod = QColor(0, 0, 0) self.colMod = QColor(0, 0, 0)
self.colMark = QColor(255, 255, 255, 128)
# Class Setup # Class Setup
# =========== # ===========
@@ -329,6 +330,7 @@ class GuiTheme:
self.colError = self._parseColour(confParser, cnfSec, "errorline") self.colError = self._parseColour(confParser, cnfSec, "errorline")
self.colRepTag = self._parseColour(confParser, cnfSec, "replacetag") self.colRepTag = self._parseColour(confParser, cnfSec, "replacetag")
self.colMod = self._parseColour(confParser, cnfSec, "modifier") self.colMod = self._parseColour(confParser, cnfSec, "modifier")
self.colMark = self._parseColour(confParser, cnfSec, "texthighlight")
return True return True
@@ -448,8 +450,8 @@ class GuiIcons:
"search_regex", "search_word", "search_regex", "search_word",
# Format Icons # Format Icons
"fmt_bold", "fmt_bold-md", "fmt_italic", "fmt_italic-md", "fmt_strike", "fmt_strike-md", "fmt_bold", "fmt_bold-md", "fmt_italic", "fmt_italic-md", "fmt_mark", "fmt_strike",
"fmt_subscript", "fmt_superscript", "fmt_underline", "fmt_strike-md", "fmt_subscript", "fmt_superscript", "fmt_underline",
# General Button Icons # General Button Icons
"add", "add_document", "backward", "bookmark", "browse", "checked", "close", "cross", "add", "add_document", "backward", "bookmark", "browse", "checked", "close", "cross",
+2
View File
@@ -768,6 +768,8 @@ class _PreviewWidget(QTextBrowser):
html = html.replace("\t", "!!tab!!") html = html.replace("\t", "!!tab!!")
html = html.replace("<del>", "<span style='text-decoration: line-through;'>") html = html.replace("<del>", "<span style='text-decoration: line-through;'>")
html = html.replace("</del>", "</span>") html = html.replace("</del>", "</span>")
html = html.replace("<mark>", "<span style='background-color: #ffffa6;'>")
html = html.replace("</mark>", "</span>")
self.setHtml(html) self.setHtml(html)
qApp.processEvents() qApp.processEvents()
while self.find("!!tab!!"): while self.find("!!tab!!"):
+3 -3
View File
@@ -1,8 +1,8 @@
%%~name: Making a Scene %%~name: Making a Scene
%%~path: 6a2d6d5f4f401/636b6aa9b697b %%~path: 6a2d6d5f4f401/636b6aa9b697b
%%~kind: NOVEL/DOCUMENT %%~kind: NOVEL/DOCUMENT
%%~hash: d7920fe95fb48140278ae160fe7c3c1ce530072c %%~hash: e1c58699b05b512306a534da70c2952aafc60e8b
%%~date: Unknown/2023-10-20 22:57:56 %%~date: Unknown/2024-02-25 16:33:40
### Making a Scene ### Making a Scene
@pov: Jane @pov: Jane
@@ -13,7 +13,7 @@ A scene is defined by a level three heading, like the one at the top of this pag
Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, _italic_ and **_bold italic_**. You can also ~~strike through~~ text. There is **some support for _nested_ emphasis**, but there are some known limitations. If the syntax highlighter doesnt show it correctly, the export tool will not either. Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, _italic_ and **_bold italic_**. You can also ~~strike through~~ text. There is **some support for _nested_ emphasis**, but there are some known limitations. If the syntax highlighter doesnt show it correctly, the export tool will not either.
For special formatting aside from standard emphasis, you can use “shortcodes”. Here we have super[sup]script[/sup] and sub[sub]script[/sub], and [b]part[/b]ial bold too, which ends in the middle of a word. The shortcodes also support [u]underline[/u]. For special formatting aside from standard emphasis, you can use “shortcodes”. Here we have super[sup]script[/sup] and sub[sub]script[/sub], and [b]part[/b]ial bold too, which ends in the middle of a word. The shortcodes also support [u]underline[/u], and you can [m]highlight text as well[/m].
In addition, the editor supports automatic formatting of “quotes”, both double and single. Depending on the syntax highlighter settings and colour theme, these can be in different colours. “You can of course use **bold** and _italic_ text inside of quotes too.” In addition, the editor supports automatic formatting of “quotes”, both double and single. Depending on the syntax highlighter settings and colour theme, these can be in different colours. “You can of course use **bold** and _italic_ text inside of quotes too.”
+4 -4
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.3b1" hexVersion="0x020300b1" fileVersion="1.5" fileRevision="2" timeStamp="2024-02-15 22:37:50"> <novelWriterXML appVersion="2.4a0" hexVersion="0x020400a0" fileVersion="1.5" fileRevision="2" timeStamp="2024-02-25 16:33:49">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1681" autoCount="263" editTime="82755"> <project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1690" autoCount="264" editTime="83277">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jane Smith</author> <author>Jane Smith</author>
</project> </project>
@@ -35,7 +35,7 @@
<entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry> <entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="31" novelWords="991" notesWords="416"> <content items="31" novelWords="998" notesWords="416">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL"> <item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes" /> <meta expanded="yes" />
<name status="sc24b8f" import="ia857f0">Novel</name> <name status="sc24b8f" import="ia857f0">Novel</name>
@@ -57,7 +57,7 @@
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name> <name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
</item> </item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="2901" wordCount="513" paraCount="15" cursorPos="66" /> <meta expanded="no" heading="H3" charCount="2937" wordCount="520" paraCount="15" cursorPos="1465" />
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name> <name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
</item> </item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
+36 -14
View File
@@ -29,8 +29,8 @@ from novelwriter.core.project import NWProject
@pytest.mark.core @pytest.mark.core
def testCoreToHtml_ConvertFormat(mockGUI): def testCoreToHtml_ConvertHeaders(mockGUI):
"""Test the tokenizer and converter chain using the ToHtml class.""" """Test header formats in the ToHtml class."""
project = NWProject() project = NWProject()
html = ToHtml(project) html = ToHtml(project)
@@ -129,6 +129,19 @@ def testCoreToHtml_ConvertFormat(mockGUI):
html.doConvert() html.doConvert()
assert html.result == "<h2><a name='T0001'></a>Heading Two</h2>\n" assert html.result == "<h2><a name='T0001'></a>Heading Two</h2>\n"
# END Test testCoreToHtml_ConvertHeaders
@pytest.mark.core
def testCoreToHtml_ConvertParagraphs(mockGUI):
"""Test paragraph formats in the ToHtml class."""
project = NWProject()
html = ToHtml(project)
html._isNovel = True
html._isNote = False
html._isFirst = True
# Paragraphs # Paragraphs
# ========== # ==========
@@ -141,6 +154,19 @@ def testCoreToHtml_ConvertFormat(mockGUI):
"<del>strikethrough</del> text</strong> here</p>\n" "<del>strikethrough</del> text</strong> here</p>\n"
) )
# Shortcodes
html._text = (
"Some [b]bold[/b], [i]italic[/i], [s]strike[/s], [u]underline[/u], [m]mark[/m], "
"super[sup]script[/sup], sub[sub]script[/sub] here\n"
)
html.tokenizeText()
html.doConvert()
assert html.result == (
"<p>Some <strong>bold</strong>, <em>italic</em>, <del>strike</del>, "
"<span style='text-decoration: underline;'>underline</span>, <mark>mark</mark>, "
"super<sup>script</sup>, sub<sub>script</sub> here</p>\n"
)
# Text w/Hard Break # Text w/Hard Break
html._text = "Line one \nLine two \nLine three\n" html._text = "Line one \nLine two \nLine three\n"
html.tokenizeText() html.tokenizeText()
@@ -206,17 +232,13 @@ def testCoreToHtml_ConvertFormat(mockGUI):
html.tokenizeText() html.tokenizeText()
html.doConvert() html.doConvert()
assert html.result == ( assert html.result == (
"<h2>" "<h1 style='page-break-before: always;'>Chapter</h1>\n"
"<a name='T0001'></a>Chapter</h2>\n" "<p style='margin-bottom: 0;'><span class='tags'>Point of View:</span> "
"<p style='margin-bottom: 0;'>" "<a href='#tag_Bod'>Bod</a></p>\n"
"<span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a>" "<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> "
"</p>\n" "<a href='#tag_Main'>Main</a></p>\n"
"<p style='margin-bottom: 0; margin-top: 0;'>" "<p style='margin-top: 0;'><span class='tags'>Locations:</span> "
"<span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a>" "<a href='#tag_Europe'>Europe</a></p>\n"
"</p>\n"
"<p style='margin-top: 0;'>"
"<span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a>"
"</p>\n"
) )
# Preview Mode # Preview Mode
@@ -234,7 +256,7 @@ def testCoreToHtml_ConvertFormat(mockGUI):
"text</b> here</p>\n" "text</b> here</p>\n"
) )
# END Test testCoreToHtml_ConvertFormat # END Test testCoreToHtml_ConvertParagraphs
@pytest.mark.core @pytest.mark.core
+5
View File
@@ -638,6 +638,11 @@ def testCoreToken_ExtractFormats(mockGUI):
assert text == "Text with underline in it." assert text == "Text with underline in it."
assert fmt == [(10, tokens.FMT_U_B), (19, tokens.FMT_U_E)] assert fmt == [(10, tokens.FMT_U_B), (19, tokens.FMT_U_E)]
# Plain mark
text, fmt = tokens._extractFormats("Text with [m]highlight[/m] in it.")
assert text == "Text with highlight in it."
assert fmt == [(10, tokens.FMT_M_B), (19, tokens.FMT_M_E)]
# Plain superscript # Plain superscript
text, fmt = tokens._extractFormats("Text with super[sup]script[/sup] in it.") text, fmt = tokens._extractFormats("Text with super[sup]script[/sup] in it.")
assert text == "Text with superscript in it." assert text == "Text with superscript in it."
+40 -10
View File
@@ -29,16 +29,11 @@ from novelwriter.core.project import NWProject
@pytest.mark.core @pytest.mark.core
def testCoreToMarkdown_ConvertFormat(mockGUI): def testCoreToMarkdown_ConvertHeaders(mockGUI):
"""Test the tokenizer and converter chain using the ToMarkdown """Test header formats in the ToMarkdown class."""
class.
"""
project = NWProject() project = NWProject()
toMD = ToMarkdown(project) toMD = ToMarkdown(project)
# Headers
# =======
toMD._isNovel = True toMD._isNovel = True
toMD._isNote = False toMD._isNote = False
toMD._isFirst = True toMD._isFirst = True
@@ -79,8 +74,18 @@ def testCoreToMarkdown_ConvertFormat(mockGUI):
toMD.doConvert() toMD.doConvert()
assert toMD.result == "## Prologue\n\n" assert toMD.result == "## Prologue\n\n"
# Paragraphs # END Test testCoreToMarkdown_ConvertHeaders
# ==========
@pytest.mark.core
def testCoreToMarkdown_ConvertParagraphs(mockGUI):
"""Test paragraph formats in the ToMarkdown class."""
project = NWProject()
toMD = ToMarkdown(project)
toMD._isNovel = True
toMD._isNote = False
toMD._isFirst = True
# Text for Extended Markdown # Text for Extended Markdown
toMD.setExtendedMarkdown() toMD.setExtendedMarkdown()
@@ -100,6 +105,31 @@ def testCoreToMarkdown_ConvertFormat(mockGUI):
"Some **nested bold and _italic_ and strikethrough text** here\n\n" "Some **nested bold and _italic_ and strikethrough text** here\n\n"
) )
# Shortcodes for Extended Markdown
toMD.setExtendedMarkdown()
toMD._text = (
"Some [b]bold[/b], [i]italic[/i], [s]strike[/s], [u]underline[/u], [m]mark[/m], "
"super[sup]script[/sup], sub[sub]script[/sub] here\n"
)
toMD.tokenizeText()
toMD.doConvert()
assert toMD.result == (
"Some **bold**, _italic_, ~~strike~~, underline, ==mark==, "
"super^script^, sub~script~ here\n\n"
)
# Shortcodes for Standard Markdown
toMD.setStandardMarkdown()
toMD._text = (
"Some [b]bold[/b], [i]italic[/i], [s]strike[/s], [u]underline[/u], [m]mark[/m], "
"super[sup]script[/sup], sub[sub]script[/sub] here\n"
)
toMD.tokenizeText()
toMD.doConvert()
assert toMD.result == (
"Some **bold**, _italic_, strike, underline, mark, superscript, subscript here\n\n"
)
# Text w/Hard Break # Text w/Hard Break
toMD._text = "Line one \nLine two \nLine three\n" toMD._text = "Line one \nLine two \nLine three\n"
toMD.tokenizeText() toMD.tokenizeText()
@@ -160,7 +190,7 @@ def testCoreToMarkdown_ConvertFormat(mockGUI):
"**Locations:** Europe\n\n" "**Locations:** Europe\n\n"
) )
# END Test testCoreToMarkdown_ConvertFormat # END Test testCoreToMarkdown_ConvertParagraphs
@pytest.mark.core @pytest.mark.core
+69 -40
View File
@@ -237,22 +237,12 @@ def testCoreToOdt_TextFormatting(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_Convert(mockGUI): def testCoreToOdt_ConvertHeaders(mockGUI):
"""Test the converter of the ToOdt class.""" """Test the converter of the ToOdt class."""
project = NWProject() project = NWProject()
odt = ToOdt(project, isFlat=True) odt = ToOdt(project, isFlat=True)
odt._isNovel = True odt._isNovel = True
def getStyle(styleName):
for aSet in odt._autoPara.values():
if aSet[0] == styleName:
return aSet[1]
return None
# Headers
# =======
# Header 1 # Header 1
odt._text = "# Title\n" odt._text = "# Title\n"
odt.tokenizeText() odt.tokenizeText()
@@ -331,8 +321,21 @@ def testCoreToOdt_Convert(mockGUI):
'</office:text>' '</office:text>'
) )
# Paragraphs # END Test testCoreToOdt_ConvertHeaders
# ==========
@pytest.mark.core
def testCoreToOdt_ConvertParagraphs(mockGUI):
"""Test the converter of the ToOdt class."""
project = NWProject()
odt = ToOdt(project, isFlat=True)
odt._isNovel = True
def getStyle(styleName):
for aSet in odt._autoPara.values():
if aSet[0] == styleName:
return aSet[1]
return None
# Nested Markdown Text # Nested Markdown Text
odt._text = "Some ~~nested **bold** and _italics_ text~~ text." odt._text = "Some ~~nested **bold** and _italics_ text~~ text."
@@ -372,7 +375,7 @@ def testCoreToOdt_Convert(mockGUI):
'</office:text>' '</office:text>'
) )
# Nested Shortcode Text, Super/Subscript # Shortcode Text, Super/Subscript
odt._text = "Some super[sup]script[/sup] and sub[sub]script[/sub] text." odt._text = "Some super[sup]script[/sup] and sub[sub]script[/sub] text."
odt.tokenizeText() odt.tokenizeText()
odt.initDocument() odt.initDocument()
@@ -387,6 +390,21 @@ def testCoreToOdt_Convert(mockGUI):
'</office:text>' '</office:text>'
) )
# Shortcode Text, Underline/Highlight
odt._text = "Some [u]underlined and [m]highlighted[/m][/u] text."
odt.tokenizeText()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
assert odt.errData == []
assert xmlToText(odt._xText) == (
'<office:text>'
'<text:p text:style-name="Text_20_body">Some '
'<text:span text:style-name="T7">underlined and </text:span>'
'<text:span text:style-name="T8">highlighted</text:span> text.</text:p>'
'</office:text>'
)
# Hard Break # Hard Break
odt._text = "Some text.\nNext line\n" odt._text = "Some text.\nNext line\n"
odt.tokenizeText() odt.tokenizeText()
@@ -422,7 +440,7 @@ def testCoreToOdt_Convert(mockGUI):
assert odt.errData == [] assert odt.errData == []
assert xmlToText(odt._xText) == ( assert xmlToText(odt._xText) == (
'<office:text>' '<office:text>'
'<text:p text:style-name="Text_20_body">Some <text:span text:style-name="T7">' '<text:p text:style-name="Text_20_body">Some <text:span text:style-name="T9">'
'bold<text:tab />text</text:span></text:p>' 'bold<text:tab />text</text:span></text:p>'
'</office:text>' '</office:text>'
) )
@@ -467,13 +485,13 @@ def testCoreToOdt_Convert(mockGUI):
assert xmlToText(odt._xText) == ( assert xmlToText(odt._xText) == (
'<office:text>' '<office:text>'
'<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>' '<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>'
'<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T7">' '<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T9">'
'Point of View:</text:span> Jane</text:p>' 'Point of View:</text:span> Jane</text:p>'
'<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T7">' '<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T9">'
'Synopsis:</text:span> So it begins</text:p>' 'Synopsis:</text:span> So it begins</text:p>'
'<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T7">' '<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T9">'
'Short Description:</text:span> Then what</text:p>' 'Short Description:</text:span> Then what</text:p>'
'<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T7">' '<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T9">'
'Comment:</text:span> A plain comment</text:p>' 'Comment:</text:span> A plain comment</text:p>'
'</office:text>' '</office:text>'
) )
@@ -535,26 +553,26 @@ def testCoreToOdt_Convert(mockGUI):
assert xmlToText(odt._xText) == ( assert xmlToText(odt._xText) == (
'<office:text>' '<office:text>'
'<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>' '<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>'
'<text:p text:style-name="P3"><text:span text:style-name="T7">' '<text:p text:style-name="P1"><text:span text:style-name="T9">'
'Point of View:</text:span> Jane</text:p>' 'Point of View:</text:span> Jane</text:p>'
'<text:p text:style-name="P4"><text:span text:style-name="T7">' '<text:p text:style-name="P2"><text:span text:style-name="T9">'
'Characters:</text:span> John</text:p>' 'Characters:</text:span> John</text:p>'
'<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T7">' '<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T9">'
'Plot:</text:span> Main</text:p>' 'Plot:</text:span> Main</text:p>'
'<text:p text:style-name="P5">Right align</text:p>' '<text:p text:style-name="P3">Right align</text:p>'
'<text:p text:style-name="Text_20_body">Left Align</text:p>' '<text:p text:style-name="Text_20_body">Left Align</text:p>'
'<text:p text:style-name="P6">Centered</text:p>' '<text:p text:style-name="P4">Centered</text:p>'
'<text:p text:style-name="P7">Left indent</text:p>' '<text:p text:style-name="P5">Left indent</text:p>'
'<text:p text:style-name="P8">Right indent</text:p>' '<text:p text:style-name="P6">Right indent</text:p>'
'</office:text>' '</office:text>'
) )
assert getStyle("P3")._pAttr["margin-bottom"] == ["fo", "0.000cm"] # type: ignore assert getStyle("P1")._pAttr["margin-bottom"] == ["fo", "0.000cm"] # type: ignore
assert getStyle("P4")._pAttr["margin-bottom"] == ["fo", "0.000cm"] # type: ignore assert getStyle("P2")._pAttr["margin-bottom"] == ["fo", "0.000cm"] # type: ignore
assert getStyle("P4")._pAttr["margin-top"] == ["fo", "0.000cm"] # type: ignore assert getStyle("P2")._pAttr["margin-top"] == ["fo", "0.000cm"] # type: ignore
assert getStyle("P5")._pAttr["text-align"] == ["fo", "right"] # type: ignore assert getStyle("P3")._pAttr["text-align"] == ["fo", "right"] # type: ignore
assert getStyle("P6")._pAttr["text-align"] == ["fo", "center"] # type: ignore assert getStyle("P4")._pAttr["text-align"] == ["fo", "center"] # type: ignore
assert getStyle("P7")._pAttr["margin-left"] == ["fo", "1.693cm"] # type: ignore assert getStyle("P5")._pAttr["margin-left"] == ["fo", "1.693cm"] # type: ignore
assert getStyle("P8")._pAttr["margin-right"] == ["fo", "1.693cm"] # type: ignore assert getStyle("P6")._pAttr["margin-right"] == ["fo", "1.693cm"] # type: ignore
# Justified # Justified
odt._text = ( odt._text = (
@@ -573,11 +591,11 @@ def testCoreToOdt_Convert(mockGUI):
'<office:text>' '<office:text>'
'<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>' '<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>'
'<text:p text:style-name="Text_20_body">Regular paragraph</text:p>' '<text:p text:style-name="Text_20_body">Regular paragraph</text:p>'
'<text:p text:style-name="P9">with<text:line-break />break</text:p>' '<text:p text:style-name="P7">with<text:line-break />break</text:p>'
'<text:p text:style-name="P9">Left Align</text:p>' '<text:p text:style-name="P7">Left Align</text:p>'
'</office:text>' '</office:text>'
) )
assert getStyle("P9")._pAttr["text-align"] == ["fo", "left"] # type: ignore assert getStyle("P7")._pAttr["text-align"] == ["fo", "left"] # type: ignore
# Page Breaks # Page Breaks
odt._text = ( odt._text = (
@@ -593,9 +611,9 @@ def testCoreToOdt_Convert(mockGUI):
assert odt.errData == [] assert odt.errData == []
assert xmlToText(odt._xText) == ( assert xmlToText(odt._xText) == (
'<office:text>' '<office:text>'
'<text:h text:style-name="P2" text:outline-level="2">Chapter One</text:h>' '<text:h text:style-name="P8" text:outline-level="2">Chapter One</text:h>'
'<text:p text:style-name="Text_20_body">Text</text:p>' '<text:p text:style-name="Text_20_body">Text</text:p>'
'<text:h text:style-name="P2" text:outline-level="2">Chapter Two</text:h>' '<text:h text:style-name="P8" text:outline-level="2">Chapter Two</text:h>'
'<text:p text:style-name="Text_20_body">Text</text:p>' '<text:p text:style-name="Text_20_body">Text</text:p>'
'</office:text>' '</office:text>'
) )
@@ -612,12 +630,12 @@ def testCoreToOdt_Convert(mockGUI):
assert odt.errData == [] assert odt.errData == []
assert xmlToText(odt._xText) == ( assert xmlToText(odt._xText) == (
'<office:text>' '<office:text>'
'<text:p text:style-name="Text_20_body">Test text **<text:span text:style-name="T8">' '<text:p text:style-name="Text_20_body">Test text **<text:span text:style-name="T10">'
'bold</text:span>** and more.</text:p>' 'bold</text:span>** and more.</text:p>'
'</office:text>' '</office:text>'
) )
# END Test testCoreToOdt_Convert # END Test testCoreToOdt_ConvertParagraphs
@pytest.mark.core @pytest.mark.core
@@ -1081,6 +1099,17 @@ def testCoreToOdt_ODTTextStyle():
txtStyle.setFontStyle("stuff") txtStyle.setFontStyle("stuff")
assert txtStyle._tAttr["font-style"] == ["fo", None] assert txtStyle._tAttr["font-style"] == ["fo", None]
# Background Color
assert txtStyle._tAttr["background-color"] == ["fo", None]
txtStyle.setBackgroundColor("stuff")
assert txtStyle._tAttr["background-color"] == ["fo", None]
txtStyle.setBackgroundColor("012345")
assert txtStyle._tAttr["background-color"] == ["fo", None]
txtStyle.setBackgroundColor("#012345")
assert txtStyle._tAttr["background-color"] == ["fo", "#012345"]
txtStyle.setBackgroundColor("stuff")
assert txtStyle._tAttr["background-color"] == ["fo", None]
# Text Position # Text Position
assert txtStyle._tAttr["text-position"] == ["style", None] assert txtStyle._tAttr["text-position"] == ["style", None]
txtStyle.setTextPosition("stuff") txtStyle.setTextPosition("stuff")
+7
View File
@@ -491,6 +491,13 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
assert nwGUI.docEditor.getText() == text assert nwGUI.docEditor.getText() == text
# Mark
nwGUI.docEditor.setCursorPosition(46)
assert nwGUI.docEditor.docAction(nwDocAction.SC_MARK) is True
assert nwGUI.docEditor.getText() == text.replace("consectetur", "[m]consectetur[/m]")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
assert nwGUI.docEditor.getText() == text
# Superscript # Superscript
nwGUI.docEditor.setCursorPosition(46) nwGUI.docEditor.setCursorPosition(46)
assert nwGUI.docEditor.docAction(nwDocAction.SC_SUP) is True assert nwGUI.docEditor.docAction(nwDocAction.SC_SUP) is True