Merge pull request #234 from vkbo/view_details

View Details, and Link Navigation
This commit is contained in:
Veronica K. Berglyd Olsen
2020-05-28 21:08:25 +02:00
committed by GitHub
12 changed files with 275 additions and 137 deletions
+5
View File
@@ -2,6 +2,11 @@
## Version 0.7 RC1 [2020-xx-xx]
**User Interface**
* The back-references list now shows references to any tag in the open document, not just the first tag. Issue #227, PR #234.
* Clicking a tag now tries to scroll to the header where the tag is set. The index needed a couple of minor changes for this feature, so this will invalidate the old index for a project, and require a new to be built. This is done automatically. PR #234.
**Project Structure**
* The project folder structure has been simplified and cleaned up. We also now freeze the main entry values in the main XML file. The XML file is now given version 1.1, and no further core changes to its structure will be made without bumping this version. We're also locking it to only be opened by version 0.7 or later. An old project file is converted on first open. PR #253.
+17 -17
View File
@@ -199,7 +199,7 @@ class NWIndex():
try:
for tTag in self.tagIndex:
if len(self.tagIndex[tTag]) != 3:
if len(self.tagIndex[tTag]) != 4:
self.indexBroken = True
for tHandle in self.refIndex:
@@ -228,7 +228,7 @@ class NWIndex():
if self.indexBroken:
self.clearIndex()
self.theParent.makeAlert(
"The index loaded from project cache contains errors. Rebuilding index.",
"The project index is outdated or broken. Rebuilding index.",
nwAlert.WARN
)
@@ -260,9 +260,9 @@ class NWIndex():
logger.debug("Indexing item with handle %s" % tHandle)
# Check file type, and reset its old index
# Also add a dummy entry for T0 in case the file has no title
# Also add a dummy entry T000000 in case the file has no title
self.refIndex[tHandle] = {}
self.refIndex[tHandle]["T0"] = {
self.refIndex[tHandle]["T000000"] = {
"tags" : [],
"updated" : time(),
}
@@ -301,7 +301,7 @@ class NWIndex():
elif aLine.startswith(r"@"):
self._indexNoteRef(tHandle, aLine, nLine, nTitle)
self._indexTag(tHandle, aLine, nLine, itemClass)
self._indexTag(tHandle, aLine, nLine, nTitle, itemClass)
elif aLine.startswith(r"%"):
if nTitle > 0:
@@ -436,7 +436,7 @@ class NWIndex():
return True
def _indexTag(self, tHandle, aLine, nLine, itemClass):
def _indexTag(self, tHandle, aLine, nLine, nTitle, itemClass):
"""Validate and save the information from a tag.
"""
isValid, theBits, thePos = self.scanThis(aLine)
@@ -444,7 +444,8 @@ class NWIndex():
return False
if theBits[0] == nwKeyWords.TAG_KEY:
self.tagIndex[theBits[1]] = [nLine, tHandle, itemClass.name]
sTitle = "T%06d" % nTitle
self.tagIndex[theBits[1]] = [nLine, tHandle, itemClass.name, sTitle]
return True
@@ -606,18 +607,17 @@ class NWIndex():
if tHandle is None:
return theRefs
theTag = None
theTags = set()
for tTag in self.tagIndex:
if tHandle == self.tagIndex[tTag][1]:
theTag = tTag
break
theTags.add(tTag)
if theTag is not None:
if theTags:
for tHandle in self.refIndex:
for sTitle in self.refIndex[tHandle]:
for nLine, tKey, tTag in self.refIndex[tHandle][sTitle]["tags"]:
if tTag == theTag:
theRefs[tHandle] = nLine
for _, _, tTag in self.refIndex[tHandle][sTitle]["tags"]:
if tTag in theTags and tHandle not in theRefs:
theRefs[tHandle] = sTitle
return theRefs
@@ -626,8 +626,8 @@ class NWIndex():
"""
if theTag in self.tagIndex:
theRef = self.tagIndex[theTag]
if len(theRef) == 3:
return theRef[1], theRef[0]
return None, 0
if len(theRef) == 4:
return theRef[1], theRef[0], theRef[3]
return None, 0, "T000000"
# END Class NWIndex
+21 -16
View File
@@ -143,7 +143,7 @@ class ToHtml(Tokenizer):
parStyle = None
tmpResult = []
hasHardBreak = False
for tType, tText, tFormat, tStyle in self.theTokens:
for tType, tLine, tText, tFormat, tStyle in self.theTokens:
# Styles
aStyle = []
@@ -174,6 +174,11 @@ class ToHtml(Tokenizer):
else:
hStyle = ""
if self.linkHeaders:
aNm = "<a name='head_%s:T%06d'></a>" % (self.theHandle, tLine)
else:
aNm = ""
# Process TextType
if tType == self.T_EMPTY:
if parStyle is None:
@@ -191,23 +196,23 @@ class ToHtml(Tokenizer):
elif tType == self.T_TITLE:
tHead = tText.replace(r"\\", "<br/>")
tmpResult.append("<h1 class='title'%s>%s</h1>\n" % (hStyle, tHead))
tmpResult.append("<h1 class='title'%s>%s%s</h1>\n" % (hStyle, aNm, tHead))
elif tType == self.T_HEAD1:
tHead = tText.replace(r"\\", "<br/>")
tmpResult.append("<%s%s>%s</%s>\n" % (h1, hStyle, tHead, h1))
tmpResult.append("<%s%s>%s%s</%s>\n" % (h1, hStyle, aNm, tHead, h1))
elif tType == self.T_HEAD2:
tHead = tText.replace(r"\\", "<br/>")
tmpResult.append("<%s%s>%s</%s>\n" % (h2, hStyle, tHead, h2))
tmpResult.append("<%s%s>%s%s</%s>\n" % (h2, hStyle, aNm, tHead, h2))
elif tType == self.T_HEAD3:
tHead = tText.replace(r"\\", "<br/>")
tmpResult.append("<%s%s>%s</%s>\n" % (h3, hStyle, tHead, h3))
tmpResult.append("<%s%s>%s%s</%s>\n" % (h3, hStyle, aNm, tHead, h3))
elif tType == self.T_HEAD4:
tHead = tText.replace(r"\\", "<br/>")
tmpResult.append("<%s%s>%s</%s>\n" % (h4, hStyle, tHead, h4))
tmpResult.append("<%s%s>%s%s</%s>\n" % (h4, hStyle, aNm, tHead, h4))
elif tType == self.T_SEP:
tmpResult.append("<p class='sep'>%s</p>\n" % tText)
@@ -296,17 +301,17 @@ class ToHtml(Tokenizer):
refTags = []
if theBits[0] in nwLabels.KEY_NAME:
retText += "<span class='tags'>%s:</span>&nbsp;" % nwLabels.KEY_NAME[theBits[0]]
if self.genMode == self.M_PREVIEW:
for tTag in theBits[1:]:
refTags.append("<a href='#%s=%s'>%s</a>" % (
theBits[0][1:], tTag, tTag
))
retText += ", ".join(refTags)
if theBits[0] == nwKeyWords.TAG_KEY:
retText += "<a name='tag_%s'>%s</a>" % (
theBits[1], theBits[1]
)
else:
if theBits[0] == nwKeyWords.TAG_KEY:
retText += "<a name='tag_%s'/>%s" % (
theBits[1], theBits[1]
)
if self.genMode == self.M_PREVIEW:
for tTag in theBits[1:]:
refTags.append("<a href='#%s=%s'>%s</a>" % (
theBits[0][1:], tTag, tTag
))
retText += ", ".join(refTags)
else:
for tTag in theBits[1:]:
refTags.append("<a href='#tag_%s'>%s</a>" % (
+144 -51
View File
@@ -102,6 +102,8 @@ class Tokenizer():
self.hideScene = False # Do not include scene headers
self.hideSection = False # Do not include section headers
self.linkHeaders = False # Add an anchor before headers
# Instance Variables
self.numChapter = 0 # Counter for chapter numbers
self.numChScene = 0 # Counter for scene number within chapter
@@ -174,6 +176,10 @@ class Tokenizer():
self.hideSection = hideSection
return
def setLinkHeaders(self, linkHeaders):
self.linkHeaders = linkHeaders
return
def setBodyText(self, doBodyText):
self.doBodyText = doBodyText
return
@@ -307,12 +313,16 @@ class Tokenizer():
self.theTokens = []
self.theMarkdown = ""
tmpMarkdown = []
nLine = 0
for aLine in self.theText.splitlines():
nLine += 1
# Tag lines starting with specific characters
if len(aLine.strip()) == 0:
self.theTokens.append((
self.T_EMPTY, "", None, self.A_NONE
self.T_EMPTY, nLine,
"", None,
self.A_NONE
))
tmpMarkdown.append("\n")
@@ -320,45 +330,59 @@ class Tokenizer():
cLine = aLine[1:].strip()
if cLine.lower().startswith("synopsis:"):
self.theTokens.append((
self.T_SYNOPSIS, cLine[9:].strip(), None, self.A_NONE
self.T_SYNOPSIS, nLine,
cLine[9:].strip(), None,
self.A_NONE
))
if self.doSynopsis:
tmpMarkdown.append("%s\n" % aLine)
else:
self.theTokens.append((
self.T_COMMENT, aLine[1:].strip(), None, self.A_NONE
self.T_COMMENT, nLine,
aLine[1:].strip(), None,
self.A_NONE
))
if self.doComments:
tmpMarkdown.append("%s\n" % aLine)
elif aLine[0] == "@":
self.theTokens.append((
self.T_KEYWORD, aLine[1:].strip(), None, self.A_NONE
self.T_KEYWORD, nLine,
aLine[1:].strip(), None,
self.A_NONE
))
if self.doKeywords:
tmpMarkdown.append("%s\n" % aLine)
elif aLine[:2] == "# ":
self.theTokens.append((
self.T_HEAD1, aLine[2:].strip(), None, self.A_NONE
self.T_HEAD1, nLine,
aLine[2:].strip(), None,
self.A_NONE
))
tmpMarkdown.append("%s\n" % aLine)
elif aLine[:3] == "## ":
self.theTokens.append((
self.T_HEAD2, aLine[3:].strip(), None, self.A_NONE
self.T_HEAD2, nLine,
aLine[3:].strip(), None,
self.A_NONE
))
tmpMarkdown.append("%s\n" % aLine)
elif aLine[:4] == "### ":
self.theTokens.append((
self.T_HEAD3, aLine[4:].strip(), None, self.A_NONE
self.T_HEAD3, nLine,
aLine[4:].strip(), None,
self.A_NONE
))
tmpMarkdown.append("%s\n" % aLine)
elif aLine[:5] == "#### ":
self.theTokens.append((
self.T_HEAD4, aLine[5:].strip(), None, self.A_NONE
self.T_HEAD4, nLine,
aLine[5:].strip(), None,
self.A_NONE
))
tmpMarkdown.append("%s\n" % aLine)
@@ -383,13 +407,17 @@ class Tokenizer():
# sorted by position
fmtPos = sorted(fmtPos, key=itemgetter(0))
self.theTokens.append((
self.T_TEXT, aLine, fmtPos, self.A_NONE
self.T_TEXT, nLine,
aLine, fmtPos,
self.A_NONE
))
tmpMarkdown.append("%s\n" % aLine)
# Always add an empty line at the end
self.theTokens.append((
self.T_EMPTY, "", None, self.A_NONE
self.T_EMPTY, nLine,
"", None,
self.A_NONE
))
tmpMarkdown.append("\n")
@@ -413,100 +441,146 @@ class Tokenizer():
for n in range(len(self.theTokens)):
tToken = self.theTokens[n]
tType = tToken[0]
tText = tToken[1]
# In case we see text before a scene, we reset the flag
if tType == self.T_TEXT:
if tToken[0] == self.T_TEXT:
self.firstScene = False
elif tType == self.T_HEAD1:
elif tToken[0] == self.T_HEAD1:
# Main Title
# ==========
tText = self._formatHeading(self.fmtTitle, tText)
tTemp = self._formatHeading(self.fmtTitle, tToken[2])
self.theTokens[n] = (
tType, tText, None, self.A_NONE
tToken[0],
tToken[1],
tTemp,
None,
self.A_NONE
)
elif tType == self.T_HEAD2:
elif tToken[0] == self.T_HEAD2:
# Novel Chapter
# =============
# Numbered or Unnumbered
if self.isUnNum:
tText = self._formatHeading(self.fmtUnNum, tText)
tTemp = self._formatHeading(self.fmtUnNum, tToken[2])
else:
self.numChapter += 1
tText = self._formatHeading(self.fmtChapter, tText)
tTemp = self._formatHeading(self.fmtChapter, tToken[2])
# Format the chapter header
self.theTokens[n] = (
tType, tText, None, self.A_PBB
tToken[0],
tToken[1],
tTemp,
None,
self.A_PBB
)
# Set scene variables
self.firstScene = True
self.numChScene = 0
elif tType == self.T_HEAD3:
elif tToken[0] == self.T_HEAD3:
# Novel Scene
# ===========
self.numChScene += 1
self.numAbsScene += 1
tTemp = self._formatHeading(self.fmtScene, tText)
tTemp = self._formatHeading(self.fmtScene, tToken[2])
if tTemp == "" and self.hideScene:
self.theTokens[n] = (
self.T_EMPTY, "", None, self.A_NONE
self.T_EMPTY,
tToken[1],
"",
None,
self.A_NONE
)
elif tTemp == "" and not self.hideScene:
if self.firstScene:
self.theTokens[n] = (
self.T_EMPTY, "", None, self.A_NONE
self.T_EMPTY,
tToken[1],
"",
None,
self.A_NONE
)
else:
self.theTokens[n] = (
self.T_SKIP, "", None, self.A_NONE
self.T_SKIP,
tToken[1],
"",
None,
self.A_NONE
)
elif tTemp == self.fmtScene:
if self.firstScene:
self.theTokens[n] = (
self.T_EMPTY, "", None, self.A_NONE
self.T_EMPTY,
tToken[1],
"",
None,
self.A_NONE
)
else:
self.theTokens[n] = (
self.T_SEP, tTemp, None, self.A_CENTRE
self.T_SEP,
tToken[1],
tTemp,
None,
self.A_CENTRE
)
else:
self.theTokens[n] = (
tType, tTemp, None, self.A_NONE
tToken[0],
tToken[1],
tTemp,
None,
self.A_NONE
)
# Definitely no longer the first scene
self.firstScene = False
elif tType == self.T_HEAD4:
elif tToken[0] == self.T_HEAD4:
# Novel Section
# =============
tTemp = self._formatHeading(self.fmtSection, tText)
tTemp = self._formatHeading(self.fmtSection, tToken[2])
if tTemp == "" and self.hideSection:
self.theTokens[n] = (
self.T_EMPTY, "", None, self.A_NONE
self.T_EMPTY,
tToken[1],
"",
None,
self.A_NONE
)
elif tTemp == "" and not self.hideSection:
self.theTokens[n] = (
self.T_SKIP, "", None, self.A_NONE
self.T_SKIP,
tToken[1],
"",
None,
self.A_NONE
)
elif tTemp == self.fmtSection:
self.theTokens[n] = (
self.T_SEP, tTemp, None, self.A_CENTRE
self.T_SEP,
tToken[1],
tTemp,
None,
self.A_CENTRE
)
else:
self.theTokens[n] = (
tType, tTemp, None, self.A_NONE
tToken[0],
tToken[1],
tTemp,
None,
self.A_NONE
)
# For title page and partitions, we need to centre all text.
@@ -515,21 +589,30 @@ class Tokenizer():
# We also swap header level 1 with a title type instead.
if self.isTitle or self.isPart:
for n, tToken in enumerate(self.theTokens):
tType = tToken[0]
tText = tToken[1]
tFormat = tToken[2]
if tType == self.T_HEAD1:
if tToken[0] == self.T_HEAD1:
if self.isTitle:
self.theTokens[n] = (
self.T_TITLE, tText, tFormat, self.A_PBB_NO | self.A_CENTRE
self.T_TITLE,
tToken[1],
tToken[2],
tToken[3],
self.A_PBB_NO | self.A_CENTRE
)
else:
self.theTokens[n] = (
tType, tText, tFormat, self.A_PBB | self.A_CENTRE
tToken[0],
tToken[1],
tToken[2],
tToken[3],
self.A_PBB | self.A_CENTRE
)
else:
self.theTokens[n] = (
tType, tText, tFormat, self.A_CENTRE
tToken[0],
tToken[1],
tToken[2],
tToken[3],
self.A_CENTRE
)
# Add a page break after the last entry
@@ -537,23 +620,32 @@ class Tokenizer():
if n >= 0:
tToken = self.theTokens[n]
self.theTokens[n] = (
tToken[0], tToken[1], tToken[2], tToken[3] | self.A_PBA
tToken[0],
tToken[1],
tToken[2],
tToken[3],
tToken[4] | self.A_PBA
)
# A single page is always left-aligned and starts on a fresh
# page, unless it's empty.
if self.isPage:
for n, tToken in enumerate(self.theTokens):
tType = tToken[0]
tText = tToken[1]
tFormat = tToken[2]
if n == 0:
self.theTokens[n] = (
tType, tText, tFormat, self.A_LEFT | self.A_PBB
tToken[0],
tToken[1],
tToken[2],
tToken[3],
self.A_LEFT | self.A_PBB
)
else:
self.theTokens[n] = (
tType, tText, tFormat, self.A_LEFT
tToken[0],
tToken[1],
tToken[2],
tToken[3],
self.A_LEFT
)
return
@@ -566,10 +658,11 @@ class Tokenizer():
"""Replaces the %keyword% strings.
"""
theTitle = theTitle.replace(r"%title%", theText)
theTitle = theTitle.replace(r"%ch%", str(self.numChapter))
theTitle = theTitle.replace(r"%sc%", str(self.numChScene))
theTitle = theTitle.replace(r"%sca%", str(self.numAbsScene))
theTitle = theTitle.replace(r"%chw%", numberToWord(self.numChapter,"en"))
theTitle = theTitle.replace(r"%ch%", str(self.numChapter))
theTitle = theTitle.replace(r"%sc%", str(self.numChScene))
theTitle = theTitle.replace(r"%sca%", str(self.numAbsScene))
if r"%chw%" in theTitle:
theTitle = theTitle.replace(r"%chw%", numberToWord(self.numChapter,"en"))
return theTitle
# END Class Tokenizer
+4 -1
View File
@@ -401,6 +401,8 @@ class GuiDocEditor(QTextEdit):
def setCursorPosition(self, thePosition):
"""Move the cursor to a given position in the document.
"""
if not isinstance(thePosition, int):
return False
if thePosition >= 0:
theCursor = self.textCursor()
theCursor.setPosition(thePosition)
@@ -416,12 +418,13 @@ class GuiDocEditor(QTextEdit):
def setCursorLine(self, theLine):
"""Move the cursor to a given line in the document.
"""
if theLine is None:
if not isinstance(theLine, int):
return False
if theLine >= 0:
theBlock = self.qDocument.findBlockByLineNumber(theLine)
if theBlock:
self.setCursorPosition(theBlock.position())
logger.verbose("Cursor moved to line %d" % theLine)
return True
##
+50 -29
View File
@@ -28,7 +28,7 @@
import logging
import nw
from PyQt5.QtCore import Qt
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtWidgets import QTextBrowser
from PyQt5.QtGui import QTextOption, QFont, QPalette, QColor, QTextCursor
@@ -134,6 +134,7 @@ class GuiDocViewer(QTextBrowser):
sPos = self.verticalScrollBar().value()
aDoc = ToHtml(self.theProject, self.theParent)
aDoc.setPreview(True, self.mainConf.viewComments)
aDoc.setLinkHeaders(True)
aDoc.setText(tHandle)
aDoc.doAutoReplace()
aDoc.tokenizeText()
@@ -164,19 +165,17 @@ class GuiDocViewer(QTextBrowser):
index being up to date.
"""
logger.debug("Loading document from tag '%s'" % theTag)
if theTag in self.theParent.theIndex.tagIndex.keys():
theTarget = self.theParent.theIndex.tagIndex[theTag]
tHandle, onLine, sTitle = self.theParent.theIndex.getTagSource(theTag)
if tHandle is None:
self.theParent.makeAlert((
"Could not find the reference for tag '%s'. It either doesn't "
"exist, or the index is out of date. The index can be updated "
"from the Tools menu, or by pressing F9."
) % theTag, nwAlert.ERROR)
return
else:
logger.debug("The tag was not found in the index")
return False
if len(theTarget) != 3:
# Just to make sure the index is not messed up
return False
self.loadText(theTarget[1])
self.loadText(tHandle)
self.navigateTo("#head_%s:%s" % (tHandle, sTitle))
return True
def docAction(self, theAction):
@@ -200,6 +199,15 @@ class GuiDocViewer(QTextBrowser):
return False
return True
def navigateTo(self, navLink):
"""Go to a specific #link in the document.
"""
if not isinstance(navLink, str):
return False
if navLink.startswith("#"):
self.setSource(QUrl(navLink))
return True
def updateDocMargins(self):
"""Automatically adjust the margins so the text is centred if
Config.textFixedW is enabled or we're in Zen mode. Otherwise,
@@ -233,6 +241,33 @@ class GuiDocViewer(QTextBrowser):
self.updateDocMargins()
return
##
# Setters
##
def setCursorPosition(self, thePosition):
"""Move the cursor to a given position in the document.
"""
if not isinstance(thePosition, int):
return False
if thePosition >= 0:
theCursor = self.textCursor()
theCursor.setPosition(thePosition)
self.setTextCursor(theCursor)
return True
def setCursorLine(self, theLine):
"""Move the cursor to a given line in the document.
"""
if not isinstance(theLine, int):
return False
if theLine >= 0:
theBlock = self.qDocument.findBlockByLineNumber(theLine)
if theBlock:
self.setCursorPosition(theBlock.position())
logger.verbose("Cursor moved to line %d" % theLine)
return True
##
# Events
##
@@ -262,25 +297,11 @@ class GuiDocViewer(QTextBrowser):
"""Slot for a link in the document being clicked.
"""
theLink = theURL.url()
tHandle = None
onLine = 0
theTag = ""
logger.verbose("Clicked link: '%s'" % theLink)
if len(theLink) > 0:
theBits = theLink.split("=")
if len(theBits) == 2:
theTag = theBits[1]
tHandle, onLine = self.theParent.theIndex.getTagSource(theBits[1])
if tHandle is None:
self.theParent.makeAlert((
"Could not find the reference for tag '%s'. It either doesn't exist, or the index "
"is out of date. The index can be updated from the Tools menu.") % theTag,
nwAlert.ERROR
)
return
else:
self.loadText(tHandle)
self.loadFromTag(theBits[1])
return
def _makeStyleSheet(self):
+7 -4
View File
@@ -107,7 +107,9 @@ class GuiDocViewDetails(QWidget):
for tHandle in theRefs:
tItem = self.theProject.projTree[tHandle]
if tItem is not None:
theList.append("<a href='#tag=%s'>%s</a>" % (tHandle,tItem.itemName))
theList.append("<a href='#head_%s:%s'>%s</a>" % (
tHandle, theRefs[tHandle], tItem.itemName
))
self.refList.setText(", ".join(theList))
self.refList.adjustSize()
@@ -122,9 +124,10 @@ class GuiDocViewDetails(QWidget):
"""Capture the link-click and forward it to the document viewer
class for handling.
"""
if len(theLink) == 18:
tHandle = theLink[-13:]
self.theParent.viewDocument(tHandle)
logger.verbose("Clicked link: '%s'" % theLink)
if len(theLink) == 27:
tHandle = theLink[6:19]
self.theParent.viewDocument(tHandle, theLink)
return
def _doShowHide(self, chState):
+10 -8
View File
@@ -485,7 +485,7 @@ class GuiMain(QMainWindow):
self.docEditor.saveText()
return True
def viewDocument(self, tHandle=None):
def viewDocument(self, tHandle=None, navLink=None):
"""Load a document for viewing in the view panel.
"""
if tHandle is None:
@@ -503,13 +503,15 @@ class GuiMain(QMainWindow):
# Make sure main tab is in Editor view
self.tabWidget.setCurrentWidget(self.splitView)
if self.docViewer.loadText(tHandle) and not self.viewPane.isVisible():
bPos = self.splitMain.sizes()
self.viewPane.setVisible(True)
vPos = [0,0]
vPos[0] = int(bPos[1]/2)
vPos[1] = bPos[1]-vPos[0]
self.splitView.setSizes(vPos)
if self.docViewer.loadText(tHandle):
if not self.viewPane.isVisible():
bPos = self.splitMain.sizes()
self.viewPane.setVisible(True)
vPos = [0,0]
vPos[0] = int(bPos[1]/2)
vPos[1] = bPos[1]-vPos[0]
self.splitView.setSizes(vPos)
self.docViewer.navigateTo(navLink)
return True
+1 -1
View File
@@ -2,6 +2,6 @@
### We Found John!
@pov: John
@location: Mars
@location: Mars, OuterSpace
Jane has been searching for a while, and she finally found John on Mars. He was indeed in space! What was he doing on Mars anyway? Well, it turns out, he was farming potatoes.
+6
View File
@@ -5,4 +5,10 @@
Space … its an awful lot of nothing, with bits in it here and there. Some of which, people like to call home.
## Outer Space
@tag: OuterSpace
Now even further into space!
You can have more than one tag in a file, as long as there is only one tag per heading.
+7 -7
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.6.1" hexVersion="0x000601f0" fileVersion="1.1" saveCount="189" autoCount="28" timeStamp="2020-05-28 19:12:56">
<novelWriterXML appVersion="0.6.2" hexVersion="0x000602f0" fileVersion="1.1" saveCount="189" autoCount="30" timeStamp="2020-05-28 20:19:28">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
@@ -11,8 +11,8 @@
<spellCheck>True</spellCheck>
<autoOutline>True</autoOutline>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>ba8a28a246524</lastViewed>
<lastWordCount>914</lastWordCount>
<lastViewed>bc0cbd2a407f3</lastViewed>
<lastWordCount>941</lastWordCount>
<autoReplace>
<A>B</A>
<B>E</B>
@@ -122,7 +122,7 @@
<charCount>1199</charCount>
<wordCount>216</wordCount>
<paraCount>7</paraCount>
<cursorPos>825</cursorPos>
<cursorPos>1066</cursorPos>
</item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>Another Scene</name>
@@ -257,9 +257,9 @@
<expanded>False</expanded>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>115</charCount>
<wordCount>24</wordCount>
<paraCount>1</paraCount>
<charCount>241</charCount>
<wordCount>51</wordCount>
<paraCount>3</paraCount>
<cursorPos>135</cursorPos>
</item>
<item handle="5eaea4e8cdee8" order="2" parent="15c4492bd5107">
+3 -3
View File
@@ -155,7 +155,7 @@ def testIndexCheckThese(nwTempProj):
"# Hello World!\n"
"@pov: Jane"
))
assert str(theIndex.tagIndex) == "{'Jane': [2, '2858dcd1057d3', 'CHARACTER']}"
assert str(theIndex.tagIndex) == "{'Jane': [2, '2858dcd1057d3', 'CHARACTER', 'T000001']}"
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!"
assert str(theIndex.checkThese(["@tag", "Jane"], cItem)) == "[True, True]"
@@ -195,7 +195,7 @@ def testIndexMeta(nwTempProj):
"\n"
"Well, not really.\n"
))
assert str(theIndex.tagIndex) == "{'Jane': [2, '2858dcd1057d3', 'CHARACTER']}"
assert str(theIndex.tagIndex) == "{'Jane': [2, '2858dcd1057d3', 'CHARACTER', 'T000001']}"
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!"
# The novel structure should contain the pointer to the novel file header
@@ -214,6 +214,6 @@ def testIndexMeta(nwTempProj):
# The character file should have a record of the reference from the novel file
theRefs = theIndex.getBackReferenceList(cHandle)
assert str(theRefs) == "{'41cfc0d1f2d12': 3}"
assert str(theRefs) == "{'41cfc0d1f2d12': 'T000001'}"
assert theProject.closeProject()