novelWriter 2.4.2__py3-none-any.whl → 2.5b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {novelWriter-2.4.2.dist-info → novelWriter-2.5b1.dist-info}/METADATA +4 -5
- {novelWriter-2.4.2.dist-info → novelWriter-2.5b1.dist-info}/RECORD +109 -101
- novelwriter/__init__.py +33 -39
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +2 -0
- novelwriter/assets/icons/typicons_dark/nw_font.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_quote.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +2 -0
- novelwriter/assets/icons/typicons_light/nw_font.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_quote.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/cyberpunk_night.conf +5 -3
- novelwriter/assets/syntax/default_dark.conf +32 -18
- novelwriter/assets/syntax/default_light.conf +24 -10
- novelwriter/assets/syntax/dracula.conf +44 -0
- novelwriter/assets/syntax/grey_dark.conf +5 -4
- novelwriter/assets/syntax/grey_light.conf +5 -4
- novelwriter/assets/syntax/light_owl.conf +7 -6
- novelwriter/assets/syntax/night_owl.conf +7 -6
- novelwriter/assets/syntax/snazzy.conf +42 -0
- novelwriter/assets/syntax/solarized_dark.conf +4 -3
- novelwriter/assets/syntax/solarized_light.conf +4 -3
- novelwriter/assets/syntax/tango.conf +27 -11
- novelwriter/assets/syntax/tomorrow.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night.conf +7 -6
- novelwriter/assets/syntax/tomorrow_night_blue.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night_bright.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +6 -5
- novelwriter/assets/text/credits_en.htm +4 -1
- novelwriter/assets/themes/cyberpunk_night.conf +2 -0
- novelwriter/assets/themes/default_dark.conf +1 -0
- novelwriter/assets/themes/default_light.conf +1 -0
- novelwriter/assets/themes/dracula.conf +47 -0
- novelwriter/assets/themes/solarized_dark.conf +1 -0
- novelwriter/assets/themes/solarized_light.conf +1 -0
- novelwriter/common.py +31 -9
- novelwriter/config.py +118 -84
- novelwriter/constants.py +40 -26
- novelwriter/core/buildsettings.py +63 -66
- novelwriter/core/coretools.py +2 -22
- novelwriter/core/docbuild.py +51 -40
- novelwriter/core/document.py +3 -5
- novelwriter/core/index.py +115 -45
- novelwriter/core/item.py +8 -19
- novelwriter/core/options.py +2 -4
- novelwriter/core/project.py +23 -57
- novelwriter/core/projectdata.py +1 -3
- novelwriter/core/projectxml.py +12 -15
- novelwriter/core/sessions.py +3 -5
- novelwriter/core/spellcheck.py +4 -9
- novelwriter/core/status.py +211 -164
- novelwriter/core/storage.py +0 -8
- novelwriter/core/tohtml.py +94 -100
- novelwriter/core/tokenizer.py +199 -112
- novelwriter/core/{tomd.py → tomarkdown.py} +97 -78
- novelwriter/core/toodt.py +212 -148
- novelwriter/core/toqdoc.py +403 -0
- novelwriter/core/tree.py +5 -7
- novelwriter/dialogs/about.py +3 -5
- novelwriter/dialogs/docmerge.py +1 -3
- novelwriter/dialogs/docsplit.py +1 -3
- novelwriter/dialogs/editlabel.py +0 -2
- novelwriter/dialogs/preferences.py +111 -88
- novelwriter/dialogs/projectsettings.py +216 -180
- novelwriter/dialogs/quotes.py +3 -4
- novelwriter/dialogs/wordlist.py +3 -9
- novelwriter/enum.py +31 -25
- novelwriter/error.py +8 -15
- novelwriter/extensions/circularprogress.py +5 -6
- novelwriter/extensions/configlayout.py +18 -18
- novelwriter/extensions/eventfilters.py +1 -5
- novelwriter/extensions/modified.py +50 -13
- novelwriter/extensions/novelselector.py +1 -3
- novelwriter/extensions/pagedsidebar.py +9 -12
- novelwriter/extensions/simpleprogress.py +1 -3
- novelwriter/extensions/statusled.py +1 -3
- novelwriter/extensions/switch.py +4 -6
- novelwriter/extensions/switchbox.py +7 -6
- novelwriter/extensions/versioninfo.py +3 -9
- novelwriter/gui/doceditor.py +132 -133
- novelwriter/gui/dochighlight.py +237 -183
- novelwriter/gui/docviewer.py +61 -97
- novelwriter/gui/docviewerpanel.py +3 -10
- novelwriter/gui/editordocument.py +1 -3
- novelwriter/gui/itemdetails.py +7 -11
- novelwriter/gui/mainmenu.py +11 -7
- novelwriter/gui/noveltree.py +11 -24
- novelwriter/gui/outline.py +11 -23
- novelwriter/gui/projtree.py +26 -43
- novelwriter/gui/search.py +1 -3
- novelwriter/gui/sidebar.py +2 -6
- novelwriter/gui/statusbar.py +6 -10
- novelwriter/gui/theme.py +26 -51
- novelwriter/guimain.py +50 -71
- novelwriter/shared.py +30 -15
- novelwriter/tools/dictionaries.py +12 -15
- novelwriter/tools/lipsum.py +2 -4
- novelwriter/tools/manusbuild.py +1 -3
- novelwriter/tools/manuscript.py +71 -144
- novelwriter/tools/manussettings.py +67 -73
- novelwriter/tools/noveldetails.py +6 -11
- novelwriter/tools/welcome.py +2 -16
- novelwriter/tools/writingstats.py +6 -9
- novelwriter/types.py +45 -3
- {novelWriter-2.4.2.dist-info → novelWriter-2.5b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.4.2.dist-info → novelWriter-2.5b1.dist-info}/WHEEL +0 -0
- {novelWriter-2.4.2.dist-info → novelWriter-2.5b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.4.2.dist-info → novelWriter-2.5b1.dist-info}/top_level.txt +0 -0
novelwriter/core/tohtml.py
CHANGED
@@ -26,17 +26,36 @@ from __future__ import annotations
|
|
26
26
|
import json
|
27
27
|
import logging
|
28
28
|
|
29
|
-
from time import time
|
30
29
|
from pathlib import Path
|
30
|
+
from time import time
|
31
31
|
|
32
32
|
from novelwriter import CONFIG
|
33
33
|
from novelwriter.common import formatTimeStamp
|
34
|
-
from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels
|
34
|
+
from novelwriter.constants import nwHeadFmt, nwHtmlUnicode, nwKeyWords, nwLabels
|
35
35
|
from novelwriter.core.project import NWProject
|
36
|
-
from novelwriter.core.tokenizer import Tokenizer, stripEscape
|
36
|
+
from novelwriter.core.tokenizer import T_Formats, Tokenizer, stripEscape
|
37
|
+
from novelwriter.types import FONT_STYLE, FONT_WEIGHTS
|
37
38
|
|
38
39
|
logger = logging.getLogger(__name__)
|
39
40
|
|
41
|
+
HTML5_TAGS = {
|
42
|
+
Tokenizer.FMT_B_B: "<strong>",
|
43
|
+
Tokenizer.FMT_B_E: "</strong>",
|
44
|
+
Tokenizer.FMT_I_B: "<em>",
|
45
|
+
Tokenizer.FMT_I_E: "</em>",
|
46
|
+
Tokenizer.FMT_D_B: "<del>",
|
47
|
+
Tokenizer.FMT_D_E: "</del>",
|
48
|
+
Tokenizer.FMT_U_B: "<span style='text-decoration: underline;'>",
|
49
|
+
Tokenizer.FMT_U_E: "</span>",
|
50
|
+
Tokenizer.FMT_M_B: "<mark>",
|
51
|
+
Tokenizer.FMT_M_E: "</mark>",
|
52
|
+
Tokenizer.FMT_SUP_B: "<sup>",
|
53
|
+
Tokenizer.FMT_SUP_E: "</sup>",
|
54
|
+
Tokenizer.FMT_SUB_B: "<sub>",
|
55
|
+
Tokenizer.FMT_SUB_E: "</sub>",
|
56
|
+
Tokenizer.FMT_STRIP: "",
|
57
|
+
}
|
58
|
+
|
40
59
|
|
41
60
|
class ToHtml(Tokenizer):
|
42
61
|
"""Core: HTML Document Writer
|
@@ -45,19 +64,15 @@ class ToHtml(Tokenizer):
|
|
45
64
|
also used by the Document Viewer, and Manuscript Build Preview.
|
46
65
|
"""
|
47
66
|
|
48
|
-
M_PREVIEW = 0 # Tweak output for the DocViewer
|
49
|
-
M_EXPORT = 1 # Tweak output for saving to HTML or printing
|
50
|
-
M_EBOOK = 2 # Tweak output for converting to epub
|
51
|
-
|
52
67
|
def __init__(self, project: NWProject) -> None:
|
53
68
|
super().__init__(project)
|
54
69
|
|
55
|
-
self._genMode = self.M_EXPORT
|
56
70
|
self._cssStyles = True
|
57
71
|
self._fullHTML: list[str] = []
|
58
72
|
|
59
73
|
# Internals
|
60
74
|
self._trMap = {}
|
75
|
+
self._usedNotes: dict[str, int] = {}
|
61
76
|
self.setReplaceUnicode(False)
|
62
77
|
|
63
78
|
return
|
@@ -74,11 +89,6 @@ class ToHtml(Tokenizer):
|
|
74
89
|
# Setters
|
75
90
|
##
|
76
91
|
|
77
|
-
def setPreview(self, state: bool) -> None:
|
78
|
-
"""Set to preview generator mode."""
|
79
|
-
self._genMode = self.M_PREVIEW if state else self.M_EXPORT
|
80
|
-
return
|
81
|
-
|
82
92
|
def setStyles(self, cssStyles: bool) -> None:
|
83
93
|
"""Enable or disable CSS styling. Some elements may still have
|
84
94
|
class tags.
|
@@ -117,39 +127,9 @@ class ToHtml(Tokenizer):
|
|
117
127
|
|
118
128
|
def doConvert(self) -> None:
|
119
129
|
"""Convert the list of text tokens into an HTML document."""
|
120
|
-
|
121
|
-
htmlTags = { # HTML4 + CSS2 (for Qt)
|
122
|
-
self.FMT_B_B: "<b>",
|
123
|
-
self.FMT_B_E: "</b>",
|
124
|
-
self.FMT_I_B: "<i>",
|
125
|
-
self.FMT_I_E: "</i>",
|
126
|
-
self.FMT_D_B: "<span style='text-decoration: line-through;'>",
|
127
|
-
self.FMT_D_E: "</span>",
|
128
|
-
self.FMT_U_B: "<u>",
|
129
|
-
self.FMT_U_E: "</u>",
|
130
|
-
self.FMT_M_B: "<mark>",
|
131
|
-
self.FMT_M_E: "</mark>",
|
132
|
-
}
|
133
|
-
else:
|
134
|
-
htmlTags = { # HTML5 (for export)
|
135
|
-
self.FMT_B_B: "<strong>",
|
136
|
-
self.FMT_B_E: "</strong>",
|
137
|
-
self.FMT_I_B: "<em>",
|
138
|
-
self.FMT_I_E: "</em>",
|
139
|
-
self.FMT_D_B: "<del>",
|
140
|
-
self.FMT_D_E: "</del>",
|
141
|
-
self.FMT_U_B: "<span style='text-decoration: underline;'>",
|
142
|
-
self.FMT_U_E: "</span>",
|
143
|
-
self.FMT_M_B: "<mark>",
|
144
|
-
self.FMT_M_E: "</mark>",
|
145
|
-
}
|
146
|
-
|
147
|
-
htmlTags[self.FMT_SUP_B] = "<sup>"
|
148
|
-
htmlTags[self.FMT_SUP_E] = "</sup>"
|
149
|
-
htmlTags[self.FMT_SUB_B] = "<sub>"
|
150
|
-
htmlTags[self.FMT_SUB_E] = "</sub>"
|
130
|
+
self._result = ""
|
151
131
|
|
152
|
-
if self._isNovel
|
132
|
+
if self._isNovel:
|
153
133
|
# For story files, we bump the titles one level up
|
154
134
|
h1Cl = " class='title'"
|
155
135
|
h1 = "h1"
|
@@ -163,12 +143,7 @@ class ToHtml(Tokenizer):
|
|
163
143
|
h3 = "h3"
|
164
144
|
h4 = "h4"
|
165
145
|
|
166
|
-
self._result = ""
|
167
|
-
|
168
|
-
para = []
|
169
|
-
pStyle = None
|
170
146
|
lines = []
|
171
|
-
|
172
147
|
tHandle = self._handle
|
173
148
|
|
174
149
|
for tType, nHead, tText, tFormat, tStyle in self._tokens:
|
@@ -181,11 +156,11 @@ class ToHtml(Tokenizer):
|
|
181
156
|
for c in tText:
|
182
157
|
if c == "<":
|
183
158
|
cText.append("<")
|
184
|
-
tFormat = [
|
159
|
+
tFormat = [(p + 3 if p > i else p, f, k) for p, f, k in tFormat]
|
185
160
|
i += 4
|
186
161
|
elif c == ">":
|
187
162
|
cText.append(">")
|
188
|
-
tFormat = [
|
163
|
+
tFormat = [(p + 3 if p > i else p, f, k) for p, f, k in tFormat]
|
189
164
|
i += 4
|
190
165
|
else:
|
191
166
|
cText.append(c)
|
@@ -235,37 +210,27 @@ class ToHtml(Tokenizer):
|
|
235
210
|
aNm = ""
|
236
211
|
|
237
212
|
# Process Text Type
|
238
|
-
if tType == self.
|
239
|
-
|
240
|
-
pStyle = ""
|
241
|
-
if len(para) > 1 and self._cssStyles:
|
242
|
-
pClass = " class='break'"
|
243
|
-
else:
|
244
|
-
pClass = ""
|
245
|
-
if len(para) > 0:
|
246
|
-
tTemp = "<br/>".join(para)
|
247
|
-
lines.append(f"<p{pClass+pStyle}>{tTemp.rstrip()}</p>\n")
|
248
|
-
para = []
|
249
|
-
pStyle = None
|
213
|
+
if tType == self.T_TEXT:
|
214
|
+
lines.append(f"<p{hStyle}>{self._formatText(tText, tFormat)}</p>\n")
|
250
215
|
|
251
216
|
elif tType == self.T_TITLE:
|
252
|
-
tHead = tText.replace(nwHeadFmt.BR, "<br
|
217
|
+
tHead = tText.replace(nwHeadFmt.BR, "<br>")
|
253
218
|
lines.append(f"<h1 class='title'{hStyle}>{aNm}{tHead}</h1>\n")
|
254
219
|
|
255
220
|
elif tType == self.T_HEAD1:
|
256
|
-
tHead = tText.replace(nwHeadFmt.BR, "<br
|
221
|
+
tHead = tText.replace(nwHeadFmt.BR, "<br>")
|
257
222
|
lines.append(f"<{h1}{h1Cl}{hStyle}>{aNm}{tHead}</{h1}>\n")
|
258
223
|
|
259
224
|
elif tType == self.T_HEAD2:
|
260
|
-
tHead = tText.replace(nwHeadFmt.BR, "<br
|
225
|
+
tHead = tText.replace(nwHeadFmt.BR, "<br>")
|
261
226
|
lines.append(f"<{h2}{hStyle}>{aNm}{tHead}</{h2}>\n")
|
262
227
|
|
263
228
|
elif tType == self.T_HEAD3:
|
264
|
-
tHead = tText.replace(nwHeadFmt.BR, "<br
|
229
|
+
tHead = tText.replace(nwHeadFmt.BR, "<br>")
|
265
230
|
lines.append(f"<{h3}{hStyle}>{aNm}{tHead}</{h3}>\n")
|
266
231
|
|
267
232
|
elif tType == self.T_HEAD4:
|
268
|
-
tHead = tText.replace(nwHeadFmt.BR, "<br
|
233
|
+
tHead = tText.replace(nwHeadFmt.BR, "<br>")
|
269
234
|
lines.append(f"<{h4}{hStyle}>{aNm}{tHead}</{h4}>\n")
|
270
235
|
|
271
236
|
elif tType == self.T_SEP:
|
@@ -274,22 +239,14 @@ class ToHtml(Tokenizer):
|
|
274
239
|
elif tType == self.T_SKIP:
|
275
240
|
lines.append(f"<p class='skip'{hStyle}> </p>\n")
|
276
241
|
|
277
|
-
elif tType == self.T_TEXT:
|
278
|
-
tTemp = tText
|
279
|
-
if pStyle is None:
|
280
|
-
pStyle = hStyle
|
281
|
-
for pos, fmt in reversed(tFormat):
|
282
|
-
tTemp = f"{tTemp[:pos]}{htmlTags[fmt]}{tTemp[pos:]}"
|
283
|
-
para.append(stripEscape(tTemp.rstrip()))
|
284
|
-
|
285
242
|
elif tType == self.T_SYNOPSIS and self._doSynopsis:
|
286
|
-
lines.append(self._formatSynopsis(tText, True))
|
243
|
+
lines.append(self._formatSynopsis(self._formatText(tText, tFormat), True))
|
287
244
|
|
288
245
|
elif tType == self.T_SHORT and self._doSynopsis:
|
289
|
-
lines.append(self._formatSynopsis(tText, False))
|
246
|
+
lines.append(self._formatSynopsis(self._formatText(tText, tFormat), False))
|
290
247
|
|
291
248
|
elif tType == self.T_COMMENT and self._doComments:
|
292
|
-
lines.append(self._formatComments(tText))
|
249
|
+
lines.append(self._formatComments(self._formatText(tText, tFormat)))
|
293
250
|
|
294
251
|
elif tType == self.T_KEYWORD and self._doKeywords:
|
295
252
|
tag, text = self._formatKeywords(tText)
|
@@ -302,6 +259,26 @@ class ToHtml(Tokenizer):
|
|
302
259
|
|
303
260
|
return
|
304
261
|
|
262
|
+
def appendFootnotes(self) -> None:
|
263
|
+
"""Append the footnotes in the buffer."""
|
264
|
+
if self._usedNotes:
|
265
|
+
footnotes = self._localLookup("Footnotes")
|
266
|
+
|
267
|
+
lines = []
|
268
|
+
lines.append(f"<h3>{footnotes}</h3>\n")
|
269
|
+
lines.append("<ol>\n")
|
270
|
+
for key, index in self._usedNotes.items():
|
271
|
+
if content := self._footnotes.get(key):
|
272
|
+
text = self._formatText(*content)
|
273
|
+
lines.append(f"<li id='footnote_{index}'><p>{text}</p></li>\n")
|
274
|
+
lines.append("</ol>\n")
|
275
|
+
|
276
|
+
result = "".join(lines)
|
277
|
+
self._result += result
|
278
|
+
self._fullHTML.append(result)
|
279
|
+
|
280
|
+
return
|
281
|
+
|
305
282
|
def saveHtml5(self, path: str | Path) -> None:
|
306
283
|
"""Save the data to an HTML file."""
|
307
284
|
with open(path, mode="w", encoding="utf-8") as fObj:
|
@@ -367,8 +344,16 @@ class ToHtml(Tokenizer):
|
|
367
344
|
mScale = self._lineHeight/1.15
|
368
345
|
|
369
346
|
styles = []
|
370
|
-
|
371
|
-
|
347
|
+
font = self._textFont
|
348
|
+
styles.append((
|
349
|
+
"body {{"
|
350
|
+
"font-family: '{0:s}'; font-size: {1:d}pt; "
|
351
|
+
"font-weight: {2:d}; font-style: {3:s};"
|
352
|
+
"}}"
|
353
|
+
).format(
|
354
|
+
font.family(), font.pointSize(),
|
355
|
+
FONT_WEIGHTS.get(font.weight(), 400),
|
356
|
+
FONT_STYLE.get(font.style(), "normal"),
|
372
357
|
))
|
373
358
|
styles.append((
|
374
359
|
"p {{"
|
@@ -442,7 +427,7 @@ class ToHtml(Tokenizer):
|
|
442
427
|
|
443
428
|
styles.append("a {color: rgb(66, 113, 174);}")
|
444
429
|
styles.append("mark {background: rgb(255, 255, 166);}")
|
445
|
-
styles.append(".
|
430
|
+
styles.append(".keyword {color: rgb(245, 135, 31); font-weight: bold;}")
|
446
431
|
styles.append(".break {text-align: left;}")
|
447
432
|
styles.append(".synopsis {font-style: italic;}")
|
448
433
|
styles.append(".comment {font-style: italic; color: rgb(100, 100, 100);}")
|
@@ -453,24 +438,36 @@ class ToHtml(Tokenizer):
|
|
453
438
|
# Internal Functions
|
454
439
|
##
|
455
440
|
|
441
|
+
def _formatText(self, text: str, tFmt: T_Formats) -> str:
|
442
|
+
"""Apply formatting tags to text."""
|
443
|
+
temp = text
|
444
|
+
for pos, fmt, data in reversed(tFmt):
|
445
|
+
html = ""
|
446
|
+
if fmt == self.FMT_FNOTE:
|
447
|
+
if data in self._footnotes:
|
448
|
+
index = len(self._usedNotes) + 1
|
449
|
+
self._usedNotes[data] = index
|
450
|
+
html = f"<sup><a href='#footnote_{index}'>{index}</a></sup>"
|
451
|
+
else:
|
452
|
+
html = "<sup>ERR</sup>"
|
453
|
+
else:
|
454
|
+
html = HTML5_TAGS.get(fmt, "ERR")
|
455
|
+
temp = f"{temp[:pos]}{html}{temp[pos:]}"
|
456
|
+
temp = temp.replace("\n", "<br>")
|
457
|
+
return stripEscape(temp)
|
458
|
+
|
456
459
|
def _formatSynopsis(self, text: str, synopsis: bool) -> str:
|
457
460
|
"""Apply HTML formatting to synopsis."""
|
458
461
|
if synopsis:
|
459
462
|
sSynop = self._localLookup("Synopsis")
|
460
463
|
else:
|
461
464
|
sSynop = self._localLookup("Short Description")
|
462
|
-
|
463
|
-
return f"<p class='comment'><span class='synopsis'>{sSynop}:</span> {text}</p>\n"
|
464
|
-
else:
|
465
|
-
return f"<p class='synopsis'><strong>{sSynop}:</strong> {text}</p>\n"
|
465
|
+
return f"<p class='synopsis'><strong>{sSynop}:</strong> {text}</p>\n"
|
466
466
|
|
467
467
|
def _formatComments(self, text: str) -> str:
|
468
468
|
"""Apply HTML formatting to comments."""
|
469
|
-
|
470
|
-
|
471
|
-
else:
|
472
|
-
sComm = self._localLookup("Comment")
|
473
|
-
return f"<p class='comment'><strong>{sComm}:</strong> {text}</p>\n"
|
469
|
+
sComm = self._localLookup("Comment")
|
470
|
+
return f"<p class='comment'><strong>{sComm}:</strong> {text}</p>\n"
|
474
471
|
|
475
472
|
def _formatKeywords(self, text: str) -> tuple[str, str]:
|
476
473
|
"""Apply HTML formatting to keywords."""
|
@@ -478,19 +475,16 @@ class ToHtml(Tokenizer):
|
|
478
475
|
if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
|
479
476
|
return "", ""
|
480
477
|
|
481
|
-
result = f"<span class='
|
478
|
+
result = f"<span class='keyword'>{self._localLookup(nwLabels.KEY_NAME[bits[0]])}:</span> "
|
482
479
|
if len(bits) > 1:
|
483
480
|
if bits[0] == nwKeyWords.TAG_KEY:
|
484
481
|
one, two = self._project.index.parseValue(bits[1])
|
485
|
-
result += f"<a name='tag_{one}'>{one}</a>"
|
482
|
+
result += f"<a class='tag' name='tag_{one}'>{one}</a>"
|
486
483
|
if two:
|
487
484
|
result += f" | <span class='optional'>{two}</a>"
|
488
485
|
else:
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
result += ", ".join(f"<a href='#tag_{t}'>{t}</a>" for t in bits[1:])
|
486
|
+
result += ", ".join(
|
487
|
+
f"<a class='tag' href='#tag_{t}'>{t}</a>" for t in bits[1:]
|
488
|
+
)
|
493
489
|
|
494
490
|
return bits[0][1:], result
|
495
|
-
|
496
|
-
# END Class ToHtml
|