Add a display name filed to index, and improve handling of it
This commit is contained in:
+62
-31
@@ -32,7 +32,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
from typing import TYPE_CHECKING, ItemsView, Iterable, Iterator
|
from typing import TYPE_CHECKING, ItemsView, Iterable, Iterator, Literal
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from novelwriter import SHARED
|
from novelwriter import SHARED
|
||||||
@@ -420,10 +420,11 @@ class NWIndex:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if tBits[0] == nwKeyWords.TAG_KEY:
|
if tBits[0] == nwKeyWords.TAG_KEY:
|
||||||
tagName = tBits[1]
|
tagKey = tBits[1]
|
||||||
self._tagsIndex.add(tagName, tHandle, sTitle, itemClass)
|
displayName = tBits[2] if len(tBits) > 2 else tagKey
|
||||||
self._itemIndex.setHeadingTag(tHandle, sTitle, tagName)
|
self._tagsIndex.add(tagKey, displayName, tHandle, sTitle, itemClass)
|
||||||
tags[tagName.lower()] = True
|
self._itemIndex.setHeadingTag(tHandle, sTitle, tagKey)
|
||||||
|
tags[tagKey.lower()] = True
|
||||||
else:
|
else:
|
||||||
self._itemIndex.addHeadingRef(tHandle, sTitle, tBits[1:], tBits[0])
|
self._itemIndex.addHeadingRef(tHandle, sTitle, tBits[1:], tBits[0])
|
||||||
|
|
||||||
@@ -471,33 +472,41 @@ class NWIndex:
|
|||||||
|
|
||||||
return True, tBits, tPos
|
return True, tBits, tPos
|
||||||
|
|
||||||
def checkThese(self, tBits: list[str], nwItem: NWItem) -> list[bool]:
|
def checkThese(self, tBits: list[str], nwItem: NWItem) -> list[Literal[0, 1, 2, 3]]:
|
||||||
"""Check the tags against the index to see if they are valid
|
"""Check the tags against the index to see if they are valid
|
||||||
tags. This is needed for syntax highlighting.
|
tags. This is needed for syntax highlighting. The return values
|
||||||
|
for each item are:
|
||||||
|
0: Invalid
|
||||||
|
1: Valid and a keyword
|
||||||
|
2: Valid and a value
|
||||||
|
3: Valid and an optional value
|
||||||
"""
|
"""
|
||||||
nBits = len(tBits)
|
nBits = len(tBits)
|
||||||
isGood = [False]*nBits
|
|
||||||
if nBits == 0:
|
if nBits == 0:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Check that the key is valid
|
# Check that the key is valid
|
||||||
isGood[0] = tBits[0] in nwKeyWords.VALID_KEYS
|
isGood: list[Literal[0, 1, 2, 3]] = [0]*nBits
|
||||||
if not isGood[0] or nBits == 1:
|
isGood[0] = 1 if tBits[0] in nwKeyWords.VALID_KEYS else 0
|
||||||
|
if isGood[0] == 0 or nBits == 1:
|
||||||
return isGood
|
return isGood
|
||||||
|
|
||||||
# For a tag, only the first value is accepted, the rest are ignored
|
# For a tag, the first value is the tag, and the second is
|
||||||
|
# optional and is the display name
|
||||||
if tBits[0] == nwKeyWords.TAG_KEY and nBits > 1:
|
if tBits[0] == nwKeyWords.TAG_KEY and nBits > 1:
|
||||||
if tBits[1] in self._tagsIndex:
|
if tBits[1] in self._tagsIndex:
|
||||||
isGood[1] = self._tagsIndex.tagHandle(tBits[1]) == nwItem.itemHandle
|
isGood[1] = 2 if self._tagsIndex.tagHandle(tBits[1]) == nwItem.itemHandle else 0
|
||||||
else:
|
else:
|
||||||
isGood[1] = True
|
isGood[1] = 2
|
||||||
|
if nBits > 2:
|
||||||
|
isGood[2] = 3
|
||||||
return isGood
|
return isGood
|
||||||
|
|
||||||
# If we're still here, we check that the references exist
|
# If we're still here, we check that the references exist
|
||||||
refKey = nwKeyWords.KEY_CLASS[tBits[0]].name
|
refKey = nwKeyWords.KEY_CLASS[tBits[0]].name
|
||||||
for n in range(1, nBits):
|
for n in range(1, nBits):
|
||||||
if tBits[n] in self._tagsIndex:
|
if tBits[n] in self._tagsIndex:
|
||||||
isGood[n] = self._tagsIndex.tagClass(tBits[n]) == refKey
|
isGood[n] = 2 if self._tagsIndex.tagClass(tBits[n]) == refKey else 0
|
||||||
|
|
||||||
return isGood
|
return isGood
|
||||||
|
|
||||||
@@ -615,9 +624,18 @@ class NWIndex:
|
|||||||
for refType in refTypes:
|
for refType in refTypes:
|
||||||
if refType in tRefs:
|
if refType in tRefs:
|
||||||
tRefs[refType].append(self._tagsIndex.tagName(aTag))
|
tRefs[refType].append(self._tagsIndex.tagName(aTag))
|
||||||
|
|
||||||
return tRefs
|
return tRefs
|
||||||
|
|
||||||
|
def getReferenceForHeader(self, tHandle: str, nHead: int, keyClass: str) -> list[str]:
|
||||||
|
"""Get the display names for a tags class for insertion into a
|
||||||
|
heading by one of the build classes.
|
||||||
|
"""
|
||||||
|
if iItem := self._itemIndex[tHandle]:
|
||||||
|
if hItem := iItem[f"T{nHead:04d}"]:
|
||||||
|
hRefs = [k for k, v in hItem.references.items() if keyClass in v]
|
||||||
|
return [self._tagsIndex.tagDisplay(k) for k in hRefs]
|
||||||
|
return []
|
||||||
|
|
||||||
def getBackReferenceList(self, tHandle: str) -> dict[str, tuple[str, IndexHeading]]:
|
def getBackReferenceList(self, tHandle: str) -> dict[str, tuple[str, IndexHeading]]:
|
||||||
"""Build a dict of files referring back to our file."""
|
"""Build a dict of files referring back to our file."""
|
||||||
if tHandle is None or tHandle not in self._itemIndex:
|
if tHandle is None or tHandle not in self._itemIndex:
|
||||||
@@ -715,17 +733,26 @@ class TagsIndex:
|
|||||||
"""Return a dictionary view of all tags."""
|
"""Return a dictionary view of all tags."""
|
||||||
return self._tags.items()
|
return self._tags.items()
|
||||||
|
|
||||||
def add(self, tagKey: str, tHandle: str, sTitle: str, itemClass: nwItemClass) -> None:
|
def add(self, tagKey: str, displayName: str, tHandle: str, sTitle: str,
|
||||||
|
itemClass: nwItemClass) -> None:
|
||||||
"""Add a key to the index and set all values."""
|
"""Add a key to the index and set all values."""
|
||||||
self._tags[tagKey.lower()] = {
|
self._tags[tagKey.lower()] = {
|
||||||
"name": tagKey, "handle": tHandle, "heading": sTitle, "class": itemClass.name
|
"name": tagKey,
|
||||||
|
"display": displayName,
|
||||||
|
"handle": tHandle,
|
||||||
|
"heading": sTitle,
|
||||||
|
"class": itemClass.name,
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
def tagName(self, tagKey: str) -> str:
|
def tagName(self, tagKey: str) -> str:
|
||||||
"""Get the display name of a given tag."""
|
"""Get the name of a given tag."""
|
||||||
return self._tags.get(tagKey.lower(), {}).get("name", "")
|
return self._tags.get(tagKey.lower(), {}).get("name", "")
|
||||||
|
|
||||||
|
def tagDisplay(self, tagKey: str) -> str:
|
||||||
|
"""Get the display name of a given tag."""
|
||||||
|
return self._tags.get(tagKey.lower(), {}).get("display", "")
|
||||||
|
|
||||||
def tagHandle(self, tagKey: str) -> str | None:
|
def tagHandle(self, tagKey: str) -> str | None:
|
||||||
"""Get the handle of a given tag."""
|
"""Get the handle of a given tag."""
|
||||||
return self._tags.get(tagKey.lower(), {}).get("handle", None)
|
return self._tags.get(tagKey.lower(), {}).get("handle", None)
|
||||||
@@ -760,24 +787,28 @@ class TagsIndex:
|
|||||||
if not isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
raise ValueError("tagsIndex is not a dict")
|
raise ValueError("tagsIndex is not a dict")
|
||||||
|
|
||||||
for tagKey, tagData in data.items():
|
for key, entry in data.items():
|
||||||
if not isinstance(tagKey, str):
|
if not isinstance(key, str):
|
||||||
raise ValueError("tagsIndex keys must be a strings")
|
raise ValueError("tagsIndex keys must be a string")
|
||||||
if "name" not in tagData:
|
if "name" not in entry:
|
||||||
raise KeyError("A tagIndex item is missing a name entry")
|
raise KeyError("A tagIndex item is missing a name entry")
|
||||||
if "handle" not in tagData:
|
if "display" not in entry:
|
||||||
|
raise KeyError("A tagIndex item is missing a display entry")
|
||||||
|
if "handle" not in entry:
|
||||||
raise KeyError("A tagIndex item is missing a handle entry")
|
raise KeyError("A tagIndex item is missing a handle entry")
|
||||||
if "heading" not in tagData:
|
if "heading" not in entry:
|
||||||
raise KeyError("A tagIndex item is missing a heading entry")
|
raise KeyError("A tagIndex item is missing a heading entry")
|
||||||
if "class" not in tagData:
|
if "class" not in entry:
|
||||||
raise KeyError("A tagIndex item is missing a class entry")
|
raise KeyError("A tagIndex item is missing a class entry")
|
||||||
if tagData["name"].lower() != tagKey:
|
if not isinstance(entry["name"], str):
|
||||||
raise ValueError("tagsIndex name must match key")
|
raise ValueError("tagsIndex name must be a string")
|
||||||
if not isHandle(tagData["handle"]):
|
if not isinstance(entry["display"], str):
|
||||||
|
raise ValueError("tagsIndex display must be a string")
|
||||||
|
if not isHandle(entry["handle"]):
|
||||||
raise ValueError("tagsIndex handle must be a handle")
|
raise ValueError("tagsIndex handle must be a handle")
|
||||||
if not isTitleTag(tagData["heading"]):
|
if not isTitleTag(entry["heading"]):
|
||||||
raise ValueError("tagsIndex heading must be a title tag")
|
raise ValueError("tagsIndex heading must be a title tag")
|
||||||
if not isItemClass(tagData["class"]):
|
if not isItemClass(entry["class"]):
|
||||||
raise ValueError("tagsIndex handle must be an nwItemClass")
|
raise ValueError("tagsIndex handle must be an nwItemClass")
|
||||||
|
|
||||||
self._tags = data
|
self._tags = data
|
||||||
@@ -1165,7 +1196,7 @@ class IndexHeading:
|
|||||||
return self._tag
|
return self._tag
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def references(self) -> dict:
|
def references(self) -> dict[str, set[str]]:
|
||||||
return self._refs
|
return self._refs
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|||||||
@@ -868,16 +868,16 @@ class HeadingFormatter:
|
|||||||
|
|
||||||
if nwHeadFmt.CHAR_POV in hFormat or nwHeadFmt.CHAR_FOCUS in hFormat:
|
if nwHeadFmt.CHAR_POV in hFormat or nwHeadFmt.CHAR_FOCUS in hFormat:
|
||||||
if self._handle and nHead > 0:
|
if self._handle and nHead > 0:
|
||||||
refs = self._project.index.getReferences(self._handle, f"T{nHead:04d}")
|
index = self._project.index
|
||||||
povData = refs[nwKeyWords.POV_KEY]
|
pList = index.getReferenceForHeader(self._handle, nHead, nwKeyWords.POV_KEY)
|
||||||
focData = refs[nwKeyWords.FOCUS_KEY]
|
fList = index.getReferenceForHeader(self._handle, nHead, nwKeyWords.FOCUS_KEY)
|
||||||
povText = povData[0] if povData else nwUnicode.U_ENDASH
|
pText = pList[0] if pList else nwUnicode.U_ENDASH
|
||||||
focText = focData[0] if focData else nwUnicode.U_ENDASH
|
fText = fList[0] if fList else nwUnicode.U_ENDASH
|
||||||
else:
|
else:
|
||||||
povText = trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY])
|
pText = trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY])
|
||||||
focText = trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY])
|
fText = trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY])
|
||||||
hFormat = hFormat.replace(nwHeadFmt.CHAR_POV, povText)
|
hFormat = hFormat.replace(nwHeadFmt.CHAR_POV, pText)
|
||||||
hFormat = hFormat.replace(nwHeadFmt.CHAR_FOCUS, focText)
|
hFormat = hFormat.replace(nwHeadFmt.CHAR_FOCUS, fText)
|
||||||
|
|
||||||
return hFormat
|
return hFormat
|
||||||
|
|
||||||
|
|||||||
@@ -1842,7 +1842,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
return nwTrinary.NEUTRAL
|
return nwTrinary.NEUTRAL
|
||||||
|
|
||||||
tag = ""
|
tag = ""
|
||||||
exist = False
|
exist = 0
|
||||||
cPos = cursor.selectionStart() - block.position()
|
cPos = cursor.selectionStart() - block.position()
|
||||||
tExist = SHARED.project.index.checkThese(tBits, self._nwItem)
|
tExist = SHARED.project.index.checkThese(tBits, self._nwItem)
|
||||||
for sTag, sPos, sExist in zip(reversed(tBits), reversed(tPos), reversed(tExist)):
|
for sTag, sPos, sExist in zip(reversed(tBits), reversed(tPos), reversed(tExist)):
|
||||||
@@ -1854,14 +1854,14 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
exist = sExist
|
exist = sExist
|
||||||
break
|
break
|
||||||
|
|
||||||
if not tag or tag.startswith("@"):
|
if exist in (1, 3) or not tag:
|
||||||
# The keyword cannot be looked up, so we ignore that
|
# Ignore keywords, optionals and empty tags
|
||||||
return nwTrinary.NEUTRAL
|
return nwTrinary.NEUTRAL
|
||||||
|
|
||||||
if follow and exist:
|
if follow and exist == 2:
|
||||||
logger.debug("Attempting to follow tag '%s'", tag)
|
logger.debug("Attempting to follow tag '%s'", tag)
|
||||||
self.loadDocumentTagRequest.emit(tag, nwDocMode.VIEW)
|
self.loadDocumentTagRequest.emit(tag, nwDocMode.VIEW)
|
||||||
elif create and not exist:
|
elif create and exist == 0:
|
||||||
if SHARED.question(self.tr(
|
if SHARED.question(self.tr(
|
||||||
"Do you want to create a new project note for the tag '{0}'?"
|
"Do you want to create a new project note for the tag '{0}'?"
|
||||||
).format(tag)):
|
).format(tag)):
|
||||||
@@ -1874,7 +1874,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
"If one doesn't exist, you must create one first."
|
"If one doesn't exist, you must create one first."
|
||||||
).format(trConst(nwLabels.CLASS_NAME[itemClass])))
|
).format(trConst(nwLabels.CLASS_NAME[itemClass])))
|
||||||
|
|
||||||
return nwTrinary.POSITIVE if exist else nwTrinary.NEGATIVE
|
return nwTrinary.POSITIVE if exist == 2 else nwTrinary.NEGATIVE
|
||||||
|
|
||||||
return nwTrinary.NEUTRAL
|
return nwTrinary.NEUTRAL
|
||||||
|
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ from PyQt5.QtGui import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
|
from novelwriter.enum import nwComment
|
||||||
from novelwriter.common import checkInt
|
from novelwriter.common import checkInt
|
||||||
from novelwriter.constants import nwRegEx, nwUnicode
|
from novelwriter.constants import nwRegEx, nwUnicode
|
||||||
from novelwriter.core.index import processComment
|
from novelwriter.core.index import processComment
|
||||||
from novelwriter.enum import nwComment
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -107,7 +107,8 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
"code": self._makeFormat(SHARED.theme.colCode),
|
"code": self._makeFormat(SHARED.theme.colCode),
|
||||||
"keyword": self._makeFormat(SHARED.theme.colKey),
|
"keyword": self._makeFormat(SHARED.theme.colKey),
|
||||||
"modifier": self._makeFormat(SHARED.theme.colMod),
|
"modifier": self._makeFormat(SHARED.theme.colMod),
|
||||||
"value": self._makeFormat(SHARED.theme.colVal, "underline"),
|
"value": self._makeFormat(SHARED.theme.colVal),
|
||||||
|
"optional": self._makeFormat(SHARED.theme.colOpt),
|
||||||
"codevalue": self._makeFormat(SHARED.theme.colVal),
|
"codevalue": self._makeFormat(SHARED.theme.colVal),
|
||||||
"codeinval": self._makeFormat(None, "errline"),
|
"codeinval": self._makeFormat(None, "errline"),
|
||||||
}
|
}
|
||||||
@@ -286,15 +287,14 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
for n, bit in enumerate(bits):
|
for n, bit in enumerate(bits):
|
||||||
xPos = pos[n]
|
xPos = pos[n]
|
||||||
xLen = len(bit)
|
xLen = len(bit)
|
||||||
if isGood[n]:
|
if isGood[n] == 1:
|
||||||
if n == 0:
|
self.setFormat(xPos, xLen, self._hStyles["keyword"])
|
||||||
self.setFormat(xPos, xLen, self._hStyles["keyword"])
|
elif isGood[n] == 2:
|
||||||
else:
|
self.setFormat(xPos, xLen, self._hStyles["value"])
|
||||||
self.setFormat(xPos, xLen, self._hStyles["value"])
|
elif isGood[n] == 3:
|
||||||
|
self.setFormat(xPos, xLen, self._hStyles["optional"])
|
||||||
else:
|
else:
|
||||||
kwFmt = self.format(xPos)
|
self.setFormat(xPos, xLen, self._hStyles["codeinval"])
|
||||||
kwFmt.merge(self._hStyles["codeinval"])
|
|
||||||
self.setFormat(xPos, xLen, kwFmt)
|
|
||||||
|
|
||||||
# We never want to run the spell checker on keyword/values,
|
# We never want to run the spell checker on keyword/values,
|
||||||
# so we force a return here
|
# so we force a return here
|
||||||
@@ -406,8 +406,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
if "errline" in styles:
|
if "errline" in styles:
|
||||||
charFormat.setUnderlineColor(SHARED.theme.colError)
|
charFormat.setUnderlineColor(SHARED.theme.colError)
|
||||||
charFormat.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
|
charFormat.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
|
||||||
if "underline" in styles:
|
|
||||||
charFormat.setFontUnderline(True)
|
|
||||||
if "background" in styles and color is not None:
|
if "background" in styles and color is not None:
|
||||||
charFormat.setBackground(QBrush(color, Qt.SolidPattern))
|
charFormat.setBackground(QBrush(color, Qt.SolidPattern))
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
%%~name: John Smith
|
%%~name: John Smith
|
||||||
%%~path: f7e2d9f330615/14298de4d9524
|
%%~path: f7e2d9f330615/14298de4d9524
|
||||||
%%~kind: CHARACTER/NOTE
|
%%~kind: CHARACTER/NOTE
|
||||||
%%~hash: fda91c416d874aa41a47fceaedb3d62f040c7e32
|
%%~hash: 0f40182de0bb7935bb40fc4c7fd0d75d55421299
|
||||||
%%~date: Unknown/2023-11-25 18:16:13
|
%%~date: Unknown/2024-01-29 12:19:42
|
||||||
# John Smith
|
# John Smith
|
||||||
|
|
||||||
@tag: John
|
@tag: John, John Smith
|
||||||
|
|
||||||
% Short: The sidekick
|
% Short: The sidekick
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
%%~name: Jane Smith
|
%%~name: Jane Smith
|
||||||
%%~path: f7e2d9f330615/bb2c23b3c42cc
|
%%~path: f7e2d9f330615/bb2c23b3c42cc
|
||||||
%%~kind: CHARACTER/NOTE
|
%%~kind: CHARACTER/NOTE
|
||||||
%%~hash: b7291713899bd0356617a36ae606b08e5fee1b65
|
%%~hash: 0faba71c86841552090a9e82dd9b7acd5b0bf56f
|
||||||
%%~date: Unknown/2023-11-25 18:16:07
|
%%~date: Unknown/2024-01-29 12:31:34
|
||||||
# Jane Smith
|
# Jane Smith
|
||||||
|
|
||||||
@tag: Jane
|
@tag: Jane, Jane Smith
|
||||||
|
|
||||||
% Short: The heroine
|
% Short: The heroine
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.3a3" hexVersion="0x020300a3" fileVersion="1.5" fileRevision="2" timeStamp="2024-01-27 18:20:02">
|
<novelWriterXML appVersion="2.3a3" hexVersion="0x020300a3" fileVersion="1.5" fileRevision="2" timeStamp="2024-01-29 17:42:31">
|
||||||
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1621" autoCount="255" editTime="81261">
|
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1660" autoCount="256" editTime="81969">
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<author>Jane Smith</author>
|
<author>Jane Smith</author>
|
||||||
</project>
|
</project>
|
||||||
@@ -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="392" />
|
<meta expanded="no" heading="H3" charCount="2901" wordCount="513" paraCount="15" cursorPos="66" />
|
||||||
<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">
|
||||||
@@ -101,11 +101,11 @@
|
|||||||
<name status="sf12341" import="ia857f0">Main Characters</name>
|
<name status="sf12341" import="ia857f0">Main Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
<item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||||
<meta expanded="no" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="48" />
|
<meta expanded="no" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="15" />
|
||||||
<name status="sf12341" import="icfb3a5" active="yes">John Smith</name>
|
<name status="sf12341" import="icfb3a5" active="yes">John Smith</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
|
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
|
||||||
<meta expanded="no" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="47" />
|
<meta expanded="no" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="31" />
|
||||||
<name status="sf12341" import="i2d7a54" active="yes">Jane Smith</name>
|
<name status="sf12341" import="i2d7a54" active="yes">Jane Smith</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD">
|
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD">
|
||||||
|
|||||||
Reference in New Issue
Block a user