Remove HTML preview mode and manuscript cache

This commit is contained in:
Veronica Berglyd Olsen
2024-05-24 20:12:13 +02:00
parent 6cc046c65c
commit e74c1057a4
4 changed files with 41 additions and 176 deletions
+5 -15
View File
@@ -55,7 +55,7 @@ class NWBuildDocument:
__slots__ = ( __slots__ = (
"_project", "_build", "_queue", "_error", "_cache", "_count", "_project", "_build", "_queue", "_error", "_cache", "_count",
"_outline", "_preview" "_outline",
) )
def __init__(self, project: NWProject, build: BuildSettings) -> None: def __init__(self, project: NWProject, build: BuildSettings) -> None:
@@ -66,7 +66,6 @@ class NWBuildDocument:
self._cache = None self._cache = None
self._count = False self._count = False
self._outline = False self._outline = False
self._preview = False
return return
## ##
@@ -100,15 +99,6 @@ class NWBuildDocument:
self._outline = state self._outline = state
return return
def setPreviewMode(self, state: bool) -> None:
"""Set the preview mode of the build. This also enables stats
count and outline mode.
"""
self._preview = state
self._outline = state
self._count = state
return
## ##
# Special Methods # Special Methods
## ##
@@ -140,10 +130,12 @@ class NWBuildDocument:
makeObj = ToQTextDocument(self._project) makeObj = ToQTextDocument(self._project)
filtered = self._setupBuild(makeObj) filtered = self._setupBuild(makeObj)
self._outline = True
self._count = True
font = QFont() font = QFont()
font.fromString(self._build.getStr("format.textFont")) font.fromString(self._build.getStr("format.textFont"))
makeObj.setLinkHeadings(self._preview)
makeObj.initDocument(font, theme) makeObj.initDocument(font, theme)
for i, tHandle in enumerate(self._queue): for i, tHandle in enumerate(self._queue):
self._error = None self._error = None
@@ -207,8 +199,6 @@ class NWBuildDocument:
makeObj = ToHtml(self._project) makeObj = ToHtml(self._project)
filtered = self._setupBuild(makeObj) filtered = self._setupBuild(makeObj)
makeObj.setPreview(self._preview)
makeObj.setLinkHeadings(self._preview)
for i, tHandle in enumerate(self._queue): for i, tHandle in enumerate(self._queue):
self._error = None self._error = None
if filtered.get(tHandle, (False, 0))[0]: if filtered.get(tHandle, (False, 0))[0]:
@@ -218,7 +208,7 @@ class NWBuildDocument:
makeObj.appendFootnotes() makeObj.appendFootnotes()
if not (self._build.getBool("html.preserveTabs") or self._preview): if not self._build.getBool("html.preserveTabs"):
makeObj.replaceTabs() makeObj.replaceTabs()
self._error = None self._error = None
+14 -55
View File
@@ -38,24 +38,6 @@ from novelwriter.types import FONT_STYLE, FONT_WEIGHTS
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
HTML4_TAGS = {
Tokenizer.FMT_B_B: "<b>",
Tokenizer.FMT_B_E: "</b>",
Tokenizer.FMT_I_B: "<i>",
Tokenizer.FMT_I_E: "</i>",
Tokenizer.FMT_D_B: "<span style='text-decoration: line-through;'>",
Tokenizer.FMT_D_E: "</span>",
Tokenizer.FMT_U_B: "<u>",
Tokenizer.FMT_U_E: "</u>",
Tokenizer.FMT_M_B: "<mark>",
Tokenizer.FMT_M_E: "</mark>",
Tokenizer.FMT_SUP_B: "<sup>",
Tokenizer.FMT_SUP_E: "</sup>",
Tokenizer.FMT_SUB_B: "<sub>",
Tokenizer.FMT_SUB_E: "</sub>",
Tokenizer.FMT_STRIP: "",
}
HTML5_TAGS = { HTML5_TAGS = {
Tokenizer.FMT_B_B: "<strong>", Tokenizer.FMT_B_B: "<strong>",
Tokenizer.FMT_B_E: "</strong>", Tokenizer.FMT_B_E: "</strong>",
@@ -82,14 +64,9 @@ class ToHtml(Tokenizer):
also used by the Document Viewer, and Manuscript Build Preview. also used by the Document Viewer, and Manuscript Build Preview.
""" """
M_PREVIEW = 0 # Tweak output for the DocViewer
M_EXPORT = 1 # Tweak output for saving to HTML or printing
M_EBOOK = 2 # Tweak output for converting to epub
def __init__(self, project: NWProject) -> None: def __init__(self, project: NWProject) -> None:
super().__init__(project) super().__init__(project)
self._genMode = self.M_EXPORT
self._cssStyles = True self._cssStyles = True
self._fullHTML: list[str] = [] self._fullHTML: list[str] = []
@@ -112,11 +89,6 @@ class ToHtml(Tokenizer):
# Setters # Setters
## ##
def setPreview(self, state: bool) -> None:
"""Set to preview generator mode."""
self._genMode = self.M_PREVIEW if state else self.M_EXPORT
return
def setStyles(self, cssStyles: bool) -> None: def setStyles(self, cssStyles: bool) -> None:
"""Enable or disable CSS styling. Some elements may still have """Enable or disable CSS styling. Some elements may still have
class tags. class tags.
@@ -157,8 +129,7 @@ class ToHtml(Tokenizer):
"""Convert the list of text tokens into an HTML document.""" """Convert the list of text tokens into an HTML document."""
self._result = "" self._result = ""
hTags = HTML4_TAGS if self._genMode == self.M_PREVIEW else HTML5_TAGS if self._isNovel:
if self._isNovel and self._genMode != self.M_PREVIEW:
# For story files, we bump the titles one level up # For story files, we bump the titles one level up
h1Cl = " class='title'" h1Cl = " class='title'"
h1 = "h1" h1 = "h1"
@@ -240,7 +211,7 @@ class ToHtml(Tokenizer):
# Process Text Type # Process Text Type
if tType == self.T_TEXT: if tType == self.T_TEXT:
lines.append(f"<p{hStyle}>{self._formatText(tText, tFormat, hTags)}</p>\n") lines.append(f"<p{hStyle}>{self._formatText(tText, tFormat)}</p>\n")
elif tType == self.T_TITLE: elif tType == self.T_TITLE:
tHead = tText.replace(nwHeadFmt.BR, "<br>") tHead = tText.replace(nwHeadFmt.BR, "<br>")
@@ -269,13 +240,13 @@ class ToHtml(Tokenizer):
lines.append(f"<p class='skip'{hStyle}>&nbsp;</p>\n") lines.append(f"<p class='skip'{hStyle}>&nbsp;</p>\n")
elif tType == self.T_SYNOPSIS and self._doSynopsis: elif tType == self.T_SYNOPSIS and self._doSynopsis:
lines.append(self._formatSynopsis(self._formatText(tText, tFormat, hTags), True)) lines.append(self._formatSynopsis(self._formatText(tText, tFormat), True))
elif tType == self.T_SHORT and self._doSynopsis: elif tType == self.T_SHORT and self._doSynopsis:
lines.append(self._formatSynopsis(self._formatText(tText, tFormat, hTags), False)) lines.append(self._formatSynopsis(self._formatText(tText, tFormat), False))
elif tType == self.T_COMMENT and self._doComments: elif tType == self.T_COMMENT and self._doComments:
lines.append(self._formatComments(self._formatText(tText, tFormat, hTags))) lines.append(self._formatComments(self._formatText(tText, tFormat)))
elif tType == self.T_KEYWORD and self._doKeywords: elif tType == self.T_KEYWORD and self._doKeywords:
tag, text = self._formatKeywords(tText) tag, text = self._formatKeywords(tText)
@@ -291,7 +262,6 @@ class ToHtml(Tokenizer):
def appendFootnotes(self) -> None: def appendFootnotes(self) -> None:
"""Append the footnotes in the buffer.""" """Append the footnotes in the buffer."""
if self._usedNotes: if self._usedNotes:
tags = HTML4_TAGS if self._genMode == self.M_PREVIEW else HTML5_TAGS
footnotes = self._localLookup("Footnotes") footnotes = self._localLookup("Footnotes")
lines = [] lines = []
@@ -299,7 +269,7 @@ class ToHtml(Tokenizer):
lines.append("<ol>\n") lines.append("<ol>\n")
for key, index in self._usedNotes.items(): for key, index in self._usedNotes.items():
if content := self._footnotes.get(key): if content := self._footnotes.get(key):
text = self._formatText(*content, tags) text = self._formatText(*content)
lines.append(f"<li id='footnote_{index}'><p>{text}</p></li>\n") lines.append(f"<li id='footnote_{index}'><p>{text}</p></li>\n")
lines.append("</ol>\n") lines.append("</ol>\n")
@@ -468,7 +438,7 @@ class ToHtml(Tokenizer):
# Internal Functions # Internal Functions
## ##
def _formatText(self, text: str, tFmt: T_Formats, tags: dict[int, str]) -> str: def _formatText(self, text: str, tFmt: T_Formats) -> str:
"""Apply formatting tags to text.""" """Apply formatting tags to text."""
temp = text temp = text
for pos, fmt, data in reversed(tFmt): for pos, fmt, data in reversed(tFmt):
@@ -481,7 +451,7 @@ class ToHtml(Tokenizer):
else: else:
html = "<sup>ERR</sup>" html = "<sup>ERR</sup>"
else: else:
html = tags.get(fmt, "ERR") html = HTML5_TAGS.get(fmt, "ERR")
temp = f"{temp[:pos]}{html}{temp[pos:]}" temp = f"{temp[:pos]}{html}{temp[pos:]}"
temp = temp.replace("\n", "<br>") temp = temp.replace("\n", "<br>")
return stripEscape(temp) return stripEscape(temp)
@@ -492,18 +462,12 @@ class ToHtml(Tokenizer):
sSynop = self._localLookup("Synopsis") sSynop = self._localLookup("Synopsis")
else: else:
sSynop = self._localLookup("Short Description") sSynop = self._localLookup("Short Description")
if self._genMode == self.M_PREVIEW: return f"<p class='synopsis'><strong>{sSynop}:</strong> {text}</p>\n"
return f"<p class='note'><span class='modifier'>{sSynop}:</span> {text}</p>\n"
else:
return f"<p class='synopsis'><strong>{sSynop}:</strong> {text}</p>\n"
def _formatComments(self, text: str) -> str: def _formatComments(self, text: str) -> str:
"""Apply HTML formatting to comments.""" """Apply HTML formatting to comments."""
if self._genMode == self.M_PREVIEW: sComm = self._localLookup("Comment")
return f"<p class='comment'>{text}</p>\n" return f"<p class='comment'><strong>{sComm}:</strong> {text}</p>\n"
else:
sComm = self._localLookup("Comment")
return f"<p class='comment'><strong>{sComm}:</strong> {text}</p>\n"
def _formatKeywords(self, text: str) -> tuple[str, str]: def _formatKeywords(self, text: str) -> tuple[str, str]:
"""Apply HTML formatting to keywords.""" """Apply HTML formatting to keywords."""
@@ -519,13 +483,8 @@ class ToHtml(Tokenizer):
if two: if two:
result += f" | <span class='optional'>{two}</a>" result += f" | <span class='optional'>{two}</a>"
else: else:
if self._genMode == self.M_PREVIEW: result += ", ".join(
result += ", ".join( f"<a class='tag' href='#tag_{t}'>{t}</a>" for t in bits[1:]
f"<a class='tag' href='#{bits[0][1:]}={t}'>{t}</a>" for t in bits[1:] )
)
else:
result += ", ".join(
f"<a class='tag' href='#tag_{t}'>{t}</a>" for t in bits[1:]
)
return bits[0][1:], result return bits[0][1:], result
+22 -65
View File
@@ -23,10 +23,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
import json
import logging import logging
from datetime import datetime
from time import time from time import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@@ -41,12 +39,11 @@ from PyQt5.QtWidgets import (
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import checkInt, fuzzyTime from novelwriter.common import fuzzyTime
from novelwriter.core.buildsettings import BuildCollection, BuildSettings from novelwriter.core.buildsettings import BuildCollection, BuildSettings
from novelwriter.core.docbuild import NWBuildDocument from novelwriter.core.docbuild import NWBuildDocument
from novelwriter.core.tokenizer import HeadingFormatter from novelwriter.core.tokenizer import HeadingFormatter
from novelwriter.core.toqdoc import TextDocumentTheme, ToQTextDocument from novelwriter.core.toqdoc import TextDocumentTheme, ToQTextDocument
from novelwriter.error import logException
from novelwriter.extensions.circularprogress import NProgressCircle from novelwriter.extensions.circularprogress import NProgressCircle
from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton, NToolDialog from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton, NToolDialog
from novelwriter.gui.theme import STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON from novelwriter.gui.theme import STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON
@@ -250,20 +247,6 @@ class GuiManuscript(NToolDialog):
if selected in self._buildMap: if selected in self._buildMap:
self.buildList.setCurrentItem(self._buildMap[selected]) self.buildList.setCurrentItem(self._buildMap[selected])
# logger.debug("Loading build cache")
# cache = CONFIG.dataPath("cache") / f"build_{SHARED.project.data.uuid}.json"
# if cache.is_file():
# try:
# with open(cache, mode="r", encoding="utf-8") as fObj:
# data = json.load(fObj)
# build = self._builds.getBuild(data.get("uuid", ""))
# if isinstance(build, BuildSettings):
# self._updatePreview(data, build)
# except Exception:
# logger.error("Failed to load build cache")
# logException()
# return
return return
## ##
@@ -342,7 +325,6 @@ class GuiManuscript(NToolDialog):
SHARED.saveDocument() SHARED.saveDocument()
docBuild = NWBuildDocument(SHARED.project, build) docBuild = NWBuildDocument(SHARED.project, build)
docBuild.setPreviewMode(True)
docBuild.queueAll() docBuild.queueAll()
theme = TextDocumentTheme() theme = TextDocumentTheme()
@@ -364,23 +346,17 @@ class GuiManuscript(NToolDialog):
buildObj = docBuild.lastBuild buildObj = docBuild.lastBuild
assert isinstance(buildObj, ToQTextDocument) assert isinstance(buildObj, ToQTextDocument)
data = {
"uuid": build.buildID,
"time": int(time()),
"stats": buildObj.textStats,
"outline": buildObj.textOutline,
}
self._updatePreview(data, build, buildObj.document)
logger.debug("Saving build cache") font = QFont()
cache = CONFIG.dataPath("cache") / f"build_{SHARED.project.data.uuid}.json" font.fromString(build.getStr("format.textFont"))
try:
with open(cache, mode="w+", encoding="utf-8") as outFile: self.docPreview.setTextFont(font)
outFile.write(json.dumps(data, indent=2)) self.docPreview.setContent(buildObj.document)
except Exception: self.docPreview.setBuildName(build.name)
logger.error("Failed to save build cache") self.docPreview.setJustify(build.getBool("format.justifyText"))
logException()
return self.docStats.updateStats(buildObj.textStats)
self.buildOutline.updateOutline(buildObj.textOutline)
return return
@@ -409,18 +385,6 @@ class GuiManuscript(NToolDialog):
# Internal Functions # Internal Functions
## ##
def _updatePreview(self, data: dict, build: BuildSettings, document: QTextDocument) -> None:
"""Update the preview widget and set relevant values."""
font = QFont()
font.fromString(build.getStr("format.textFont"))
self.docPreview.setTextFont(font)
self.docPreview.setContent(data, document)
self.docPreview.setBuildName(build.name)
self.docPreview.setJustify(build.getBool("format.justifyText"))
self.docStats.updateStats(data.get("stats", {}))
self.buildOutline.updateOutline(data.get("outline", {}))
return
def _getSelectedBuild(self) -> BuildSettings | None: def _getSelectedBuild(self) -> BuildSettings | None:
"""Get the currently selected build. If none are selected, """Get the currently selected build. If none are selected,
automatically select the first one. automatically select the first one.
@@ -854,23 +818,19 @@ class _PreviewWidget(QTextBrowser):
QApplication.processEvents() QApplication.processEvents()
return return
def setContent(self, data: dict, doc: QTextDocument) -> None: def setContent(self, document: QTextDocument) -> None:
"""Set the content of the preview widget.""" """Set the content of the preview widget."""
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor)) QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
self.buildProgress.setCentreText(self.tr("Processing ...")) self.buildProgress.setCentreText(self.tr("Processing ..."))
QApplication.processEvents() QApplication.processEvents()
doc.setDocumentMargin(CONFIG.getTextMargin()) document.setDocumentMargin(CONFIG.getTextMargin())
self.setDocument(doc) self.setDocument(document)
self._docTime = checkInt(data.get("time"), 0) self._docTime = int(time())
self._updateBuildAge() self._updateBuildAge()
# Since we change the content while it may still be rendering, we mark
# the document as dirty again to make sure it's re-rendered properly.
# self.document().markContentsDirty(0, self.document().characterCount())
self.buildProgress.setCentreText(self.tr("Done")) self.buildProgress.setCentreText(self.tr("Done"))
QApplication.restoreOverrideCursor() QApplication.restoreOverrideCursor()
QApplication.processEvents() QApplication.processEvents()
@@ -915,17 +875,14 @@ class _PreviewWidget(QTextBrowser):
@pyqtSlot() @pyqtSlot()
def _updateBuildAge(self) -> None: def _updateBuildAge(self) -> None:
"""Update the build time and the fuzzy age.""" """Update the build time and the fuzzy age."""
if self._docTime > 0: if self._buildName and self._docTime > 0:
strBuildTime = "%s (%s)" % ( self.ageLabel.setText("<b>{0}</b><br>{1}: {2}".format(
CONFIG.localDateTime(datetime.fromtimestamp(self._docTime)), self._buildName,
fuzzyTime(int(time()) - self._docTime) self.tr("Built"),
) fuzzyTime(int(time()) - self._docTime),
))
else: else:
strBuildTime = self.tr("Unknown") self.ageLabel.setText("<b>{0}</b>".format(self.tr("No Preview")))
text = "{0}: {1}".format(self.tr("Built"), strBuildTime)
if self._buildName:
text = "<b>{0}</b><br>{1}".format(self._buildName, text)
self.ageLabel.setText(text)
return return
@pyqtSlot() @pyqtSlot()
-41
View File
@@ -284,21 +284,6 @@ def testCoreToHtml_ConvertParagraphs(mockGUI):
"</ol>\n" "</ol>\n"
) )
# Preview Mode
# ============
html.setPreview(True)
# Text (HTML4)
html._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
html.tokenizeText()
html.doConvert()
assert html.result == (
"<p>Some <b>nested bold and <i>italic</i> and "
"<span style='text-decoration: line-through;'>strikethrough</span> "
"text</b> here</p>\n"
)
@pytest.mark.core @pytest.mark.core
def testCoreToHtml_ConvertDirect(mockGUI): def testCoreToHtml_ConvertDirect(mockGUI):
@@ -682,29 +667,3 @@ def testCoreToHtml_Format(mockGUI):
"<a class='tag' href='#tag_Bod'>Bod</a>, " "<a class='tag' href='#tag_Bod'>Bod</a>, "
"<a class='tag' href='#tag_Jane'>Jane</a>" "<a class='tag' href='#tag_Jane'>Jane</a>"
) )
# Preview Mode
# ============
html.setPreview(True)
assert html._formatSynopsis("synopsis text", True) == (
"<p class='note'><span class='modifier'>Synopsis:</span> synopsis text</p>\n"
)
assert html._formatSynopsis("short text", False) == (
"<p class='note'><span class='modifier'>Short Description:</span> short text</p>\n"
)
assert html._formatComments("comment text") == (
"<p class='comment'>comment text</p>\n"
)
assert html._formatKeywords("") == ("", "")
assert html._formatKeywords("tag: Jane") == (
"tag", "<span class='keyword'>Tag:</span> <a class='tag' name='tag_Jane'>Jane</a>"
)
assert html._formatKeywords("char: Bod, Jane") == (
"char",
"<span class='keyword'>Characters:</span> "
"<a class='tag' href='#char=Bod'>Bod</a>, "
"<a class='tag' href='#char=Jane'>Jane</a>"
)