Merge branch 'main' into release_1.0b4

This commit is contained in:
Veronica K. B. Olsen
2020-10-08 00:14:23 +02:00
45 changed files with 1373 additions and 582 deletions
+202 -183
View File
@@ -16,7 +16,7 @@
novelWriter is a Markdown-like text editor designed for writing novels and larger projects of many novelWriter is a Markdown-like text editor designed for writing novels and larger projects of many
smaller plain text documents. It uses its own flavour of Markdown that supports a meta data syntax smaller plain text documents. It uses its own flavour of Markdown that supports a meta data syntax
for comments, synopsis and cross-referencing between files. It's designed to be a simple text editor for comments, synopsis, and cross-referencing between files. It's designed to be a simple text editor
that allows for easy organisation of text files and notes, built on plain text files for that allows for easy organisation of text files and notes, built on plain text files for
robustness. robustness.
@@ -29,19 +29,6 @@ The full documentation is available at [novelwriter.readthedocs.io](https://nove
The contributing guide is available in [CONTRIBUTING](CONTRIBUTING.md). The contributing guide is available in [CONTRIBUTING](CONTRIBUTING.md).
### Note on the Default Branch
The default branch on this repository switched to `main` on 6. August 2020. If you are running
novelWriter from a git clone, you need to clone the repository again.
Alternatively, you can run the following to get back on the new default branch:
```bash
git remote update
git checkout -t origin/main
```
### Development Status ### Development Status
The application is still under initial development, but all core features have now been added. The The application is still under initial development, but all core features have now been added. The
@@ -49,15 +36,213 @@ core functionality has been in place for a while, and novelWriter is being used
by the author and collaborators. by the author and collaborators.
No new major features will be added at this time, until the application is stable. Until then, No new major features will be added at this time, until the application is stable. Until then,
novelWriter is in a _beta_ state. Please report any issues you may encounter in the repository issue novelWriter is in a _beta_ state. Please report any issues you encounter via the repository's issue
tracker. tracker.
You should be able to use novelWriter for real projects, but as with all software, please make You should be able to use novelWriter for real projects, but as with all software, please make
regular backups of your data. There is a built in backup feature that can pack the entire project regular backups of your data. There is a built in backup feature that can pack the entire project
into a zip file on close. Please check the documentation for further details. into a zip file each time the main window or the project is closed. Please check the documentation
for further details.
## License ## Implementation
The application is written in Python 3 using Qt5 via PyQt5. It is developed on Linux, but it should
in principle work fine on other operating systems as well as long as dependencies are met. The unit
tests are run on the latest versions of Ubuntu Linux, Windows Server and macOS.
## Installing and Running
You can runt novelWriter either from a downloaded copy of the source code, or by running:
```bash
pip install novelwriter
```
**Note:** On some systems you must use `pip3` instead for the Python 3 version.
You can update novelWriter to the latest version by running:
```bash
pip install --upgrade novelwriter
```
The application can then be started with one of the commands, depending on your Python configuration:
```bash
./novelWriter.py
python novelWriter.py
python3 novelWriter.py
```
It also takes a few parameters for debugging and such, which can be listed with the switch `--help`.
The `--info`, `--debug` or `--verbose` flags are particularly useful for increasing logging output
for debugging.
You can also provide a path to a folder containing a novelWriter project as the last parameter.
### Launcher and Icons
In the root assets folder there are icons and scripts and a template for setting up a launcher on
Gnome desktops. You may need to modify those scripts slightly, but as they are, they work on Debian
and Ubuntu. For other operating systems, please consult your operating system documentation for how
to make those. Feel free to submit more if you are able to make them.
## Package Dependencies
It is recommended that novelWriter runs with Qt 5.10 or later, and Python 3.6 or later. Running with
Qt as low as 5.2.1 and Python 3.4.3 has been tested, and worked in the past, but there are no
guarantees that this will keep working as these are not a part of the test builds.
### Linux
Generally, dependencies can be installed via `pip` with:
```bash
pip3 install -r requirements.txt
```
You can also install the packages from the distro's own package manager.
For the apt package manager on Debian/Ubuntu systems, the following Python3 packages are needed:
* `python3-pyqt5` for the GUI
* `python3-lxml` for writing project files
* `python3-enchant` for better spell checking (optional)
### macOS
These instructions assume you're using brew, and have Python and pip set up.
If not, see the [brew docs](https://docs.brew.sh/Homebrew-and-Python) for help.
Main requirements are installed via the requirements file.
You also need to install the `pyobjc` package on macOS, so you must run:
```bash
pip3 install --user -r requirements.txt
pip3 install --user pyobjc
```
For spell checking you may also need to install the enchant package.
It comes with a lot of default dictionaries.
```bash
brew install enchant
```
### Windows
On Windows, the `pip install` command is generally sufficient to install everything you need.
That should also install the Qt libraries and the spell check dictionary dependencies.
**Note:** On Windows, make sure Python3 is in your PATH if you want to launch novelWriter from
command line. You can also right click the `novelWriter.py` file, create a shortcut, then right
click again, select "Properties" and change the target to your python executable and
`novelWriter.py`.
It should look something like this:
```
C:\...\AppData\Local\Programs\Python\Python38\python.exe novelWriter.py
```
### Package Versions
PyQt/Qt should be at least 5.3, but ideally 5.10 or higher for nearly all features to work.
Exporting to Markdown requires PyQt/Qt 5.14. There are no known minimum for `lxml`, but the code
was originally written with 4.2. The optional spell check library must be at least 3.0.0 to work
with Windows 64 bit systems. On Linux, 2.0.0 also works fine.
If no external spell checking tool is installed, novelWriter will use a basic spell checker based on
standard Python package `difflib`. Currently, only English dictionaries are available for this spell
checker, but more can be added to the `nw/assets/dict` folder. See the [README](nw/assets/dict/README.md)
file in that folder for how to generate more dictionaries. Note that the difflib-based option is
both slow and limited.
## Key Features
Some features of novelWriter are listed below. Consult the documentation for more information.
### Markdown Flavour
novelWriter is _not_ a full-feature Markdown editor. It allows for a minimal set of formatting
needed for writing text documents for novels. These are currently limited to:
* Headings level 1 to 4 using the `#` syntax only.
* Emphasised and strong text. These are rendered as italicised and bold.
* Strikethrough text.
* Hard line breaks using two or more spaces at the end of a line.
That is it. Features not supported in the editor are also not exported when using the export tool.
In addition, novelWriter adds the following, which is otherwise not supported by Markdown:
* A line starting with `%` is treated as a comment and not rendered on exports unless requested.
Comments do not count towards the word count. If the first word of the comment is `synopsis:`, the
comment is indexed and treated as the synopsis for the section of text under the same header.
These synopsis comments can be used to build an outline and exported to external documents.
* A set of meta data keyword/values starting with the character `@`. This is used for tagging
and inter-linking documents, and can be used to generate a project outline.
* Non-breaking spaces are supported as long as your system is using at least Qt 5.9. For earlier
version, non-breaking spaces are converted to normal spaces when saving the document. This is done
by the Qt library.
* Thin spaces are also supported, as well as non-breaking thin spaces, with the same library version
restriction as above.
* Tabs can be used in the text, and should be properly aligned. The width of a tab in pixels can be
changed in Preferences. Note that for the HTML format, most browsers will treat a tab as a space,
so it may not show up like expected. If you import the HTML file to Libre Office, for instance,
they should appear as expected.
The core export format of novelWriter is HTML5. You can also export the entire project as a single
novelWriter Markdown-flavour document. In addition, other exports to Open Document, PDF, and plain
text is offered through the Qt library, although with limitations to formatting.
The HTML format is well suited for file conversion tools and import into other text editors.
### Colour Themes
The editor has syntax highlighting for the features it supports, and includes a set of different
syntax highlighting themes. The GUI also has an optional dark theme in addition to the default
system theme.
New themes can easily be added to the `nw/assets/themes` folder. Have a look in the existing folders
for examples of how to define the colours.
### Easy Organising of Project Files
The structure of the project is shown on the left hand side of the main GUI. Project files are
organised into root folders, indicating what class of file they are. The most important root folder
is the Novel folder, which contains all of the files that makes up the finished novel. Each root
folder can have subfolders. Folders have no impact on the final project structure, they are purely
tools for organising the files in whatever way the user needs.
The editor supports four levels of headings, which determines what level the following text belongs
to. Headings of level one signify a book or partition title. Headings of level two signify the start
of a new chapter. Headings of level three signify the start of a new scene. Headings of level four
can be used internally in each scene to separate sections.
Each novel file can be assigned a layout format, which shows up as a flag next to the item in the
project tree. These are mostly to help the user track what they contain, but they also have some
impact on the format of the exported document. See the documentation for further details.
#### Project Notes
Supporting note files can be added for the story plot, characters, locations, story timeline, etc.
These have their separate root folders. These are optional files.
### Visualisation of Story Elements
The different notes can be assigned tags, which other files can refer back to using the `@` meta
keywords. This information can be used to display an outline of the story, showing where each scene
connects to the plot, and which characters, etc. occur in them. In addition, the tags themselves are
clickable in the document view pane, and control-clickable in the editor. They make it possible to
quickly navigate between the documents while editing.
## Licenses
This is Open Source software, and novelWriter is licensed under GPLv3. See the This is Open Source software, and novelWriter is licensed under GPLv3. See the
[GNU General Public License website](https://www.gnu.org/licenses/gpl-3.0.en.html) for more details, [GNU General Public License website](https://www.gnu.org/licenses/gpl-3.0.en.html) for more details,
@@ -79,172 +264,6 @@ Bundled assets have the following licenses:
main repo is available at [sdras/night-owl-vscode-theme](https://github.com/sdras/night-owl-vscode-theme). main repo is available at [sdras/night-owl-vscode-theme](https://github.com/sdras/night-owl-vscode-theme).
## Markdown Flavour
novelWriter is _not_ a full-feature Markdown editor. It allows for a minimal set of formatting
needed for writing text documents for novels. These are currently limited to:
* Headings level 1 to 4 using the `#` syntax only.
* Emphasised and strong text. These are rendered as italicised and bold.
* Strikethrough text.
* Hard line breaks using two or more spaces at the end of a line.
That is it. Features not supported in the editor are also not exported when using the export tool.
In addition, novelWriter adds the following, which is otherwise not supported by Markdown:
* A line starting with `%` is treated as a comment and not rendered on exports unless requested.
Comments do not count towards the word count. If the first word of the comment is `synopsis:`, the
comment is indexed and treated as the synopsis for the following section of text. These synopsis
comments can be used to build an outline and exported to external documents.
* A set of meta data keyword/value sets starting with the character `@`. This is used for tagging
and inter-linking documents.
* Non-breaking spaces are supported as long as your system is using at least Qt 5.9. For earlier
version, non-breaking spaces are converted to normal spaces when saving the document. This is done
by the Qt library.
* Thin spaces are also supported, as well as non-breaking thin spaces.
* Tabs can be used in the text, and should be properly aligned. The width of a tab in pixels can be
changed in Preferences. Note that tabs are exported as-is, also to HTML format. However, most
browsers will treat a tab as a space, so it may not show up like expected if you view the exported
HTML file.
The core export format of novelWriter is HTML5. You can also export the entire project as a single
novelWriter Markdown-flavour document. In addition, other exports to Open Document, PDF, and plain
text is offered through the Qt library, although with limitations to formatting.
## Implementation
The application is written in Python3 using Qt5 via PyQt5. It is developed on Linux, but it should
in principle work fine on other operating systems as well, as long as dependencies are met. It is
regularly tested on Windows 10.
The application can be started from the source folder with one of the commands, depending on your
Python configuration:
```
./novelWriter.py
python novelWriter.py
python3 novelWriter.py
```
It also takes a few parameters for debugging and such, which can be listed with the switch `--help`.
In the root assets folder there are icons and scripts and a template for setting up a launcher on
Gnome desktops. You may need to modify those scripts slightly, but as they are, they work on Debian
and Ubuntu. For other operating systems, please consult your operating system documentation for how
to make those. Feel free to submit more if you are able to make them.
## Package Dependencies
It is recommended that novelWriter runs with Qt 5.10 or later, and Python 3.6 or later. Running with
Qt as low as 5.2.1 and Python 3.4.3 has been tested, and worked in the past, but there are no
guarantees that this will keep working as these are not a part of the test builds.
For the apt package manager on Debian systems, the following Python3 packages are needed:
* `python3-pyqt5` for the GUI
* `python3-pyqt5.qtsvg` may need to be installed separately
* `python3-lxml` for writing project files
These are optional, but recommended:
* `python3-enchant` for better spell checking
Alternatively, the packages can be installed with `pip` by running
```bash
pip install -r requirements.txt
```
in the application folder.
You can also do them one at a time, skipping the ones you don't need:
```bash
pip install pyqt5
pip install lxml
pip install pyenchant
```
PyQt/Qt should be at least 5.3, but ideally 5.10 or higher for nearly all features to work.
Exporting to Markdown requires PyQt/Qt 5.14. There are no known minimum for `lxml`, but the code
was originally written with 4.2. The optional spell check library must be at least 3.0.0 to work
with Windows 64 bit systems. On Linux, 2.0.0 also works fine.
If no external spell checking tool is installed, novelWriter will use a basic spell checker based on
standard Python package `difflib`. Currently, only English dictionaries are available for this spell
checker, but more can be added to the `nw/assets/dict` folder. See the [README](nw/assets/dict/README.md)
file in that folder for how to generate more dictionaries. Note that the difflib-based option is
both slow and limited.
Note: On Windows, make sure Python3 is in your PATH if you want to launch novelWriter from command
line. You can also right click the `novelWriter.py` file, create a shortcut, then right click again,
select "Properties" and change the target to your python executable and `novelWriter.py`.
It should look something like this:
```
C:\...\AppData\Local\Programs\Python\Python38\python.exe novelWriter.py
```
## Key Features
Some features of novelWriter are listed below. Consult the documentation for more information.
### Colour Themes
The editor has syntax highlighting for the features it supports, and includes a set of different
syntax highlighting themes. The GUI also has an optional dark theme in addition to the default
system theme.
New themes can easily be added to the `nw/assets/themes` folder. Have a look in the existing folders
for examples of how to define the colours.
### Auto-Saving and Document Stats
Open documents and the project file itself is saved regularly on a timer. The status of this is
indicated by two indicators on the right hand side of the status bar. Latest project word count is
shown next to these indicators in the status bar. The counts are updated regularly, but not
as-you-type.
The word count for documents is presented in a footer in the document editor itself. Both project
and document word counters will also show how many words you've added in the current writing
session.
### Easy Organising of Project Files
The structure of the project is shown on the left hand side of the main GUI. Project files are
organised into root folders, indicating what class of file they are. The most important root folder
is the Novel folder, which contains all of the files that makes up the finished novel. Each root
folder can have subfolders. Folders have no impact on the project structure, they are purely tools
for organising the files in whatever way the user needs.
The editor supports four levels of headings, which determines what level the following text belongs
to. Headings of level one signify a book or partition title. Headings of level two signify the start
of a new chapter. Headings of level three signify the start of a new scene. Headings of level four
can be used internally in each scene to separate sections.
Each novel file can be assigned a layout format, which shows up as a flag next to the item in the
project tree. These are mostly to help the user see what they contain, but they also have some
impact on the format of the exported document. See the documentation for further details.
#### Project Notes
Supporting note files can be added for the story plot, characters, locations, story timeline, etc.
These have their separate root folders. These are optional files.
### Visualisation of Story Elements
The different notes can be assigned tags, which other files can refer back to using special meta
keywords. This information can be used to display an outline of the story, showing where each scene
connects to the plot, and which characters, etc. occur in them. In addition, the tags themselves are
clickable in the document view pane, and control-clickable in the editor. They make it possible to
quickly navigate between the documents while editing.
## Screenshot ## Screenshot
**novelWriter with default system theme:** **novelWriter with default system theme:**
-4
View File
@@ -66,10 +66,6 @@ The following Python packages are required to run novelWriter:
You can of course also install these packages from your operating system's package repository. You can of course also install these packages from your operating system's package repository.
.. note::
Sometimes the SVG graphics package for PyQt5 must be installed separately. It is usually called
something like ``python3-pyqt5.qtsvg``.
PyQt/Qt should be at least 5.2.1, but ideally 5.10 or higher for nearly all features to work. PyQt/Qt should be at least 5.2.1, but ideally 5.10 or higher for nearly all features to work.
Exporting to standard Markdown, for instance, requires PyQt/Qt 5.14. Searching using regular Exporting to standard Markdown, for instance, requires PyQt/Qt 5.14. Searching using regular
expressions requires 5.3, and for full Unicode support, 5.13. expressions requires 5.3, and for full Unicode support, 5.13.
+4 -5
View File
@@ -27,13 +27,13 @@ helpMsg = (
) )
try: try:
inOpts, inArgs = getopt.getopt(sys.argv[1:],shortOpt,longOpt) inOpts, inArgs = getopt.getopt(sys.argv[1:], shortOpt, longOpt)
except getopt.GetoptError: except getopt.GetoptError:
print(helpMsg) print(helpMsg)
sys.exit(2) sys.exit(2)
for inOpt, inArg in inOpts: for inOpt, inArg in inOpts:
if inOpt in ("-h","--help"): if inOpt in ("-h", "--help"):
print(helpMsg) print(helpMsg)
sys.exit() sys.exit()
elif inOpt in ("-d", "--debug"): elif inOpt in ("-d", "--debug"):
@@ -41,7 +41,7 @@ for inOpt, inArg in inOpts:
# Run pip # Run pip
packList = ["pyinstaller"] packList = ["pyinstaller"]
with open("requirements.txt",mode="r") as reqFile: with open("requirements.txt", mode="r") as reqFile:
for reqPack in reqFile: for reqPack in reqFile:
if len(reqPack.strip()) > 0: if len(reqPack.strip()) > 0:
packList.append(reqPack) packList.append(reqPack)
@@ -72,7 +72,7 @@ if buildWindowed:
instOpt.append("novelWriter.py") instOpt.append("novelWriter.py")
import PyInstaller.__main__ import PyInstaller.__main__ # noqa: F401
PyInstaller.__main__.run(instOpt) PyInstaller.__main__.run(instOpt)
print("") print("")
@@ -82,4 +82,3 @@ print("##################")
print("") print("")
print("If everything went well, the novelWriter executable should be in the folder named 'dist'") print("If everything went well, the novelWriter executable should be in the folder named 'dist'")
print("") print("")
+4 -4
View File
@@ -4,10 +4,10 @@
import sys import sys
try: try:
import PyQt5.QtWidgets import PyQt5.QtWidgets # noqa: F401
import PyQt5.QtGui import PyQt5.QtGui # noqa: F401
import PyQt5.QtCore import PyQt5.QtCore # noqa: F401
except: except Exception:
print("ERROR: Failed to load dependency python3-pyqt5") print("ERROR: Failed to load dependency python3-pyqt5")
sys.exit(1) sys.exit(1)
+16 -6
View File
@@ -199,28 +199,28 @@ def main(sysArgs=None):
# Check Packages and Versions # Check Packages and Versions
errorData = [] errorData = []
errorCode = 0
if sys.hexversion < 0x030403f0: if sys.hexversion < 0x030403f0:
errorData.append( errorData.append(
"At least Python 3.4.3 is required, but 3.6 is highly recommended." "At least Python 3.4.3 is required, but 3.6 is highly recommended."
) )
errorCode |= 4
if CONFIG.verQtValue < 50200: if CONFIG.verQtValue < 50200:
errorData.append( errorData.append(
"At least Qt5 version 5.2 is required, found %s." % CONFIG.verQtString "At least Qt5 version 5.2 is required, found %s." % CONFIG.verQtString
) )
errorCode |= 8
if CONFIG.verPyQtValue < 50200: if CONFIG.verPyQtValue < 50200:
errorData.append( errorData.append(
"At least PyQt5 version 5.2 is required, found %s." % CONFIG.verPyQtString "At least PyQt5 version 5.2 is required, found %s." % CONFIG.verPyQtString
) )
errorCode |= 16
try:
import PyQt5.QtSvg # noqa: F401
except ImportError:
errorData.append("Python module 'PyQt5.QtSvg' is missing.")
try: try:
import lxml # noqa: F401 import lxml # noqa: F401
except ImportError: except ImportError:
errorData.append("Python module 'lxml' is missing.") errorData.append("Python module 'lxml' is missing.")
errorCode |= 32
if errorData: if errorData:
if not testMode: if not testMode:
@@ -236,11 +236,21 @@ def main(sysArgs=None):
"<br>&nbsp;-&nbsp;".join(errorData) "<br>&nbsp;-&nbsp;".join(errorData)
)) ))
errApp.exec_() errApp.exec_()
sys.exit(10 + len(errorData)) sys.exit(errorCode)
# Finish initialising config # Finish initialising config
CONFIG.initConfig(confPath, dataPath) CONFIG.initConfig(confPath, dataPath)
if CONFIG.osDarwin:
try:
from Foundation import NSBundle
bundle = NSBundle.mainBundle()
info = bundle.localizedInfoDictionary() or bundle.infoDictionary()
info["CFBundleName"] = "novelWriter"
except ImportError as e:
logger.error("Failed to set application name")
logger.error(str(e))
# Import GUI (after dependency checks), and launch # Import GUI (after dependency checks), and launch
from nw.guimain import GuiMain from nw.guimain import GuiMain
if testMode: if testMode:
+14 -4
View File
@@ -29,6 +29,8 @@ import logging
from datetime import datetime from datetime import datetime
from PyQt5.QtWidgets import qApp
from nw.constants import nwConst, nwUnicode from nw.constants import nwConst, nwUnicode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -201,12 +203,12 @@ def transferCase(theSource, theTarget):
if len(theTarget) < 1 or len(theSource) < 1: if len(theTarget) < 1 or len(theSource) < 1:
return theResult return theResult
if theSource[0] == theSource[0].upper(): if theSource.istitle():
theResult = theTarget[0].upper() + theTarget[1:] theResult = theTarget.title()
if theSource == theSource.upper(): if theSource.isupper():
theResult = theTarget.upper() theResult = theTarget.upper()
elif theSource == theSource.lower(): elif theSource.islower():
theResult = theTarget.lower() theResult = theTarget.lower()
return theResult return theResult
@@ -251,3 +253,11 @@ def makeFileNameSafe(theText):
if c.isalpha() or c.isdigit() or c == " ": if c.isalpha() or c.isdigit() or c == " ":
cleanName += c cleanName += c
return cleanName return cleanName
def getGuiItem(theName):
"""Returns a QtWidget based on its objectName.
"""
for qWidget in qApp.topLevelWidgets():
if qWidget.objectName() == theName:
return qWidget
return None
+2 -4
View File
@@ -68,7 +68,6 @@ class Config:
self.confPath = None # Folder where the config is saved self.confPath = None # Folder where the config is saved
self.confFile = None # The config file name self.confFile = None # The config file name
self.dataPath = None # Folder where app data is stored self.dataPath = None # Folder where app data is stored
self.homePath = None # The user's home folder
self.lastPath = None # The last user-selected folder (browse dialogs) self.lastPath = None # The last user-selected folder (browse dialogs)
self.appPath = None # The full path to the novelwriter package folder self.appPath = None # The full path to the novelwriter package folder
self.appRoot = None # The full path to the novelwriter root folder self.appRoot = None # The full path to the novelwriter root folder
@@ -257,8 +256,7 @@ class Config:
logger.verbose("Data path: %s" % self.dataPath) logger.verbose("Data path: %s" % self.dataPath)
self.confFile = self.appHandle+".conf" self.confFile = self.appHandle+".conf"
self.homePath = os.path.expanduser("~") self.lastPath = os.path.expanduser("~")
self.lastPath = self.homePath
self.appPath = getattr(sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__))) self.appPath = getattr(sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__)))
self.appRoot = os.path.join(self.appPath, os.path.pardir) self.appRoot = os.path.join(self.appPath, os.path.pardir)
self.assetPath = os.path.join(self.appPath, "assets") self.assetPath = os.path.join(self.appPath, "assets")
@@ -268,7 +266,7 @@ class Config:
self.appIcon = os.path.join(self.iconPath, "novelwriter.svg") self.appIcon = os.path.join(self.iconPath, "novelwriter.svg")
logger.verbose("App path: %s" % self.appPath) logger.verbose("App path: %s" % self.appPath)
logger.verbose("Home path: %s" % self.homePath) logger.verbose("Last path: %s" % self.lastPath)
# If config folder does not exist, make it. # If config folder does not exist, make it.
# This assumes that the os config folder itself exists. # This assumes that the os config folder itself exists.
+3 -1
View File
@@ -33,7 +33,9 @@ class nwConst():
fStampFmt = "%Y-%m-%d %H.%M.%S" # FileName safe format fStampFmt = "%Y-%m-%d %H.%M.%S" # FileName safe format
dStampFmt = "%Y-%m-%d" # Date only format dStampFmt = "%Y-%m-%d" # Date only format
maxDepth = 30 # Maximum folder depth of a project maxDepth = 30 # Maximum folder depth of a project
maxDocSize = 5000000 # Maxium size of a single document
maxBuildSize = 10000000 # Maxium size of a project build
# END Class nwConst # END Class nwConst
+6 -6
View File
@@ -271,16 +271,16 @@ class NWIndex():
theRoot = self.theProject.projTree.getRootItem(tHandle) theRoot = self.theProject.projTree.getRootItem(tHandle)
if theItem is None: if theItem is None:
logger.error("Not indexing unknown item %s" % tHandle) logger.info("Not indexing unknown item %s" % tHandle)
return False return False
if theItem.itemType != nwItemType.FILE: if theItem.itemType != nwItemType.FILE:
logger.error("Not indexing non-file item %s" % tHandle) logger.info("Not indexing non-file item %s" % tHandle)
return False return False
if theItem.itemLayout == nwItemLayout.NO_LAYOUT: if theItem.itemLayout == nwItemLayout.NO_LAYOUT:
logger.error("Not indexing no-layout item %s" % tHandle) logger.info("Not indexing no-layout item %s" % tHandle)
return False return False
if theItem.parHandle is None: if theItem.parHandle is None:
logger.error("Not indexing orphaned item %s" % tHandle) logger.info("Not indexing orphaned item %s" % tHandle)
return False return False
# Run word counter for the whole text # Run word counter for the whole text
@@ -289,10 +289,10 @@ class NWIndex():
# If the file is archived or trashed, we don't index the file itself # If the file is archived or trashed, we don't index the file itself
if self.theProject.projTree.isTrashRoot(theItem.parHandle): if self.theProject.projTree.isTrashRoot(theItem.parHandle):
logger.error("Not indexing trash item %s" % tHandle) logger.info("Not indexing trash item %s" % tHandle)
return False return False
if theRoot.itemClass == nwItemClass.ARCHIVE: if theRoot.itemClass == nwItemClass.ARCHIVE:
logger.error("Not indexing archived item %s" % tHandle) logger.info("Not indexing archived item %s" % tHandle)
return False return False
itemClass = theItem.itemClass itemClass = theItem.itemClass
+3
View File
@@ -62,6 +62,8 @@ class OptionState():
"GuiBuildNovel": { "GuiBuildNovel": {
"winWidth", "winWidth",
"winHeight", "winHeight",
"boxWidth",
"docWidth",
"addNovel", "addNovel",
"addNotes", "addNotes",
"ignoreFlag", "ignoreFlag",
@@ -74,6 +76,7 @@ class OptionState():
"incComments", "incComments",
"incKeywords", "incKeywords",
"incBodyText", "incBodyText",
"replaceTabs",
}, },
"GuiOutline": { "GuiOutline": {
"headerOrder", "headerOrder",
+4
View File
@@ -757,6 +757,10 @@ class NWProject():
if self.projPath is None or self.projPath == "": if self.projPath is None or self.projPath == "":
return False return False
if self.projPath == os.path.expanduser("~"):
# Don't make a mess in the user's home folder
return False
self.projMeta = os.path.join(self.projPath, "meta") self.projMeta = os.path.join(self.projPath, "meta")
self.projCache = os.path.join(self.projPath, "cache") self.projCache = os.path.join(self.projPath, "cache")
self.projContent = os.path.join(self.projPath, "content") self.projContent = os.path.join(self.projPath, "content")
+31 -6
View File
@@ -87,6 +87,11 @@ class NWSpellCheck():
""" """
return [] return []
def describeDict(self):
"""Dummy function.
"""
return "", ""
@staticmethod @staticmethod
def expandLanguage(spTag): def expandLanguage(spTag):
"""Translate a language tag to something more user friendly. """Translate a language tag to something more user friendly.
@@ -187,6 +192,21 @@ class NWSpellEnchant(NWSpellCheck):
logger.error("Failed to list languages for enchant spell checking") logger.error("Failed to list languages for enchant spell checking")
return retList return retList
def describeDict(self):
"""Return the tag and provider of the currently loaded
dictionary.
"""
try:
spTag = self.theDict.tag
spName = self.theDict.provider.name
except Exception as e:
logger.error("Failed to extract information about the dictionary")
logger.error(str(e))
spTag = ""
spName = ""
return spTag, spName
# END Class NWSpellEnchant # END Class NWSpellEnchant
class NWSpellEnchantDummy: class NWSpellEnchantDummy:
@@ -221,12 +241,14 @@ class NWSpellSimple(NWSpellCheck):
def __init__(self): def __init__(self):
NWSpellCheck.__init__(self) NWSpellCheck.__init__(self)
self.theLang = ""
logger.debug("Simple spell checking activated") logger.debug("Simple spell checking activated")
return return
def setLanguage(self, theLang, projectDict=None): def setLanguage(self, theLang, projectDict=None):
"""Load a dictionary as a list from the app assets folder. """Load a dictionary as a list from the app assets folder.
""" """
self.theLang = theLang
self.WORDS = [] self.WORDS = []
dictFile = os.path.join(self.mainConf.dictPath, theLang+".dict") dictFile = os.path.join(self.mainConf.dictPath, theLang+".dict")
try: try:
@@ -269,15 +291,12 @@ class NWSpellSimple(NWSpellCheck):
if len(theWord) == 0: if len(theWord) == 0:
return [] return []
firstUp = theWord[0] == theWord[0].upper() theMatches = get_close_matches(theWord.lower(), self.WORDS, n=10, cutoff=0.75)
theWord = theWord.lower()
theMatches = get_close_matches(theWord, self.WORDS, n=10, cutoff=0.75)
theOptions = [] theOptions = []
for aWord in theMatches: for aWord in theMatches:
if len(aWord) == 0: if len(aWord) == 0:
continue continue
if firstUp: if theWord[0].isupper():
aWord = aWord[0].upper() + aWord[1:] aWord = aWord[0].upper() + aWord[1:]
aWord = aWord.replace("'", self.mainConf.fmtApostrophe) aWord = aWord.replace("'", self.mainConf.fmtApostrophe)
theOptions.append(aWord) theOptions.append(aWord)
@@ -305,9 +324,15 @@ class NWSpellSimple(NWSpellCheck):
if theBits[1] != ".dict": if theBits[1] != ".dict":
continue continue
spName = "%s [Internal]" % self.expandLanguage(theBits[0]) spName = "%s [internal]" % self.expandLanguage(theBits[0])
retList.append((theBits[0], spName)) retList.append((theBits[0], spName))
return retList return retList
def describeDict(self):
"""Return the tag and provider of the currently loaded
dictionary.
"""
return self.theLang, "internal"
# END Class NWSpellSimple # END Class NWSpellSimple
+1 -2
View File
@@ -77,7 +77,6 @@ class ToHtml(Tokenizer):
self.doKeywords = True self.doKeywords = True
self.doComments = doComments self.doComments = doComments
self.doSynopsis = doSynopsis self.doSynopsis = doSynopsis
self._buildRegEx()
return return
def setStyles(self, cssStyles): def setStyles(self, cssStyles):
@@ -316,7 +315,7 @@ class ToHtml(Tokenizer):
retText = "" retText = ""
refTags = [] refTags = []
if theBits[0] in nwLabels.KEY_NAME: if theBits[0] in nwLabels.KEY_NAME:
retText += "<span class='tags'>%s:</span>&nbsp;" % nwLabels.KEY_NAME[theBits[0]] retText += "<span class='tags'>%s:</span> " % nwLabels.KEY_NAME[theBits[0]]
if len(theBits) > 1: if len(theBits) > 1:
if theBits[0] == nwKeyWords.TAG_KEY: if theBits[0] == nwKeyWords.TAG_KEY:
retText += "<a name='tag_%s'>%s</a>" % ( retText += "<a name='tag_%s'>%s</a>" % (
+17 -1
View File
@@ -33,7 +33,7 @@ from PyQt5.QtCore import QRegularExpression
from nw.core.document import NWDoc from nw.core.document import NWDoc
from nw.core.tools import numberToWord, numberToRoman from nw.core.tools import numberToWord, numberToRoman
from nw.constants import nwItemLayout, nwItemType, nwRegEx from nw.constants import nwConst, nwItemLayout, nwItemType, nwRegEx
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -120,6 +120,9 @@ class Tokenizer():
self.isNote = False self.isNote = False
self.isNovel = False self.isNovel = False
# Error Handling
self.errData = []
return return
## ##
@@ -212,6 +215,14 @@ class Tokenizer():
theDocument = NWDoc(self.theProject, self.theParent) theDocument = NWDoc(self.theProject, self.theParent)
self.theText = theDocument.openDocument(theHandle) self.theText = theDocument.openDocument(theHandle)
docSize = len(self.theText)
if docSize > nwConst.maxDocSize:
errVal = "Document '%s' is too big (%.2f MB). Skipping." % (
self.theItem.itemName, docSize/1.0e6
)
self.theText = "# ERROR\n\n%s\n\n" % errVal
self.errData.append(errVal)
self.isNone = self.theItem.itemLayout == nwItemLayout.NO_LAYOUT self.isNone = self.theItem.itemLayout == nwItemLayout.NO_LAYOUT
self.isTitle = self.theItem.itemLayout == nwItemLayout.TITLE self.isTitle = self.theItem.itemLayout == nwItemLayout.TITLE
self.isBook = self.theItem.itemLayout == nwItemLayout.BOOK self.isBook = self.theItem.itemLayout == nwItemLayout.BOOK
@@ -230,6 +241,11 @@ class Tokenizer():
""" """
return self.theResult return self.theResult
def getResultSize(self):
"""Return the size of the result from the conversion.
"""
return len(self.theResult)
def getFilteredMarkdown(self): def getFilteredMarkdown(self):
"""Return the novelWriter markdown after the filters have been applied. """Return the novelWriter markdown after the filters have been applied.
""" """
+12 -2
View File
@@ -29,6 +29,8 @@
import logging import logging
from nw.constants import nwUnicode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# =============================================================================================== # # =============================================================================================== #
@@ -44,6 +46,15 @@ def countWords(theText):
paraCount = 0 paraCount = 0
prevEmpty = True prevEmpty = True
# We need to treat dashes as word separators for counting words.
# The check+replace apprach is much faster that direct replace for
# large texts, and a bit slower for small texts, but in the latter
# case it doesn't matter.
if nwUnicode.U_ENDASH in theText:
theText = theText.replace(nwUnicode.U_ENDASH, " ")
if nwUnicode.U_EMDASH in theText:
theText = theText.replace(nwUnicode.U_EMDASH, " ")
for aLine in theText.splitlines(): for aLine in theText.splitlines():
countPara = True countPara = True
@@ -72,8 +83,7 @@ def countWords(theText):
charCount -= 2 charCount -= 2
countPara = False countPara = False
theBuff = aLine.replace("", " ").replace("", " ") wordCount += len(aLine.split())
wordCount += len(theBuff.split())
charCount += theLen charCount += theLen
if countPara and prevEmpty: if countPara and prevEmpty:
paraCount += 1 paraCount += 1
+223 -66
View File
@@ -36,19 +36,20 @@ from datetime import datetime
from PyQt5.QtCore import Qt, QByteArray, QTimer from PyQt5.QtCore import Qt, QByteArray, QTimer
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QPalette, QColor, QTextDocumentWriter, QFont QPalette, QColor, QTextDocumentWriter, QFont, QCursor
) )
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel, qApp, QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel,
QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction, QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction,
QFileDialog, QFontDialog, QSpinBox QFileDialog, QFontDialog, QSpinBox, QScrollArea, QSplitter, QWidget,
QSizePolicy
) )
from nw.common import fuzzyTime, makeFileNameSafe from nw.common import fuzzyTime, makeFileNameSafe
from nw.gui.custom import QSwitch from nw.gui.custom import QSwitch
from nw.core import ToHtml from nw.core import ToHtml
from nw.constants import ( from nw.constants import (
nwAlert, nwFiles, nwItemType, nwItemLayout, nwItemClass nwConst, nwAlert, nwFiles, nwItemType, nwItemLayout, nwItemClass
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -76,27 +77,25 @@ class GuiBuildNovel(QDialog):
self.theTheme = theParent.theTheme self.theTheme = theParent.theTheme
self.optState = self.theProject.optState self.optState = self.theProject.optState
self.htmlText = [] # List of html document self.htmlText = [] # List of html documents
self.htmlStyle = [] # List of html styles self.htmlStyle = [] # List of html styles
self.nwdText = [] # List of markdown documents self.nwdText = [] # List of markdown documents
self.buildTime = 0 # The timestamp of the last build self.buildTime = 0 # The timestamp of the last build
self.setWindowTitle("Build Novel Project") self.setWindowTitle("Build Novel Project")
self.setMinimumWidth(self.mainConf.pxInt(900)) self.setMinimumWidth(self.mainConf.pxInt(700))
self.setMinimumHeight(self.mainConf.pxInt(800)) self.setMinimumHeight(self.mainConf.pxInt(600))
self.resize( self.resize(
self.mainConf.pxInt(self.optState.getInt("GuiBuildNovel", "winWidth", 900)), self.mainConf.pxInt(self.optState.getInt("GuiBuildNovel", "winWidth", 900)),
self.mainConf.pxInt(self.optState.getInt("GuiBuildNovel", "winHeight", 800)) self.mainConf.pxInt(self.optState.getInt("GuiBuildNovel", "winHeight", 800))
) )
self.outerBox = QHBoxLayout()
self.toolsBox = QVBoxLayout()
self.docView = GuiBuildNovelDocView(self, self.theProject) self.docView = GuiBuildNovelDocView(self, self.theProject)
# Title Formats # Title Formats
# ============= # =============
self.titleGroup = QGroupBox("Title Formats for Novel Files", self) self.titleGroup = QGroupBox("Title Formats for Novel Files", self)
self.titleForm = QGridLayout(self) self.titleForm = QGridLayout(self)
self.titleGroup.setLayout(self.titleForm) self.titleGroup.setLayout(self.titleForm)
@@ -118,11 +117,11 @@ class GuiBuildNovel(QDialog):
r"be centred automatically and only appear between sections of " r"be centred automatically and only appear between sections of "
r"the same type." r"the same type."
) )
xFmt = self.mainConf.pxInt(220) xFmt = self.mainConf.pxInt(100)
self.fmtTitle = QLineEdit() self.fmtTitle = QLineEdit()
self.fmtTitle.setMaxLength(200) self.fmtTitle.setMaxLength(200)
self.fmtTitle.setFixedWidth(xFmt) self.fmtTitle.setMinimumWidth(xFmt)
self.fmtTitle.setToolTip(fmtHelp) self.fmtTitle.setToolTip(fmtHelp)
self.fmtTitle.setText( self.fmtTitle.setText(
self._reFmtCodes(self.theProject.titleFormat["title"]) self._reFmtCodes(self.theProject.titleFormat["title"])
@@ -130,7 +129,7 @@ class GuiBuildNovel(QDialog):
self.fmtChapter = QLineEdit() self.fmtChapter = QLineEdit()
self.fmtChapter.setMaxLength(200) self.fmtChapter.setMaxLength(200)
self.fmtChapter.setFixedWidth(xFmt) self.fmtChapter.setMinimumWidth(xFmt)
self.fmtChapter.setToolTip(fmtHelp) self.fmtChapter.setToolTip(fmtHelp)
self.fmtChapter.setText( self.fmtChapter.setText(
self._reFmtCodes(self.theProject.titleFormat["chapter"]) self._reFmtCodes(self.theProject.titleFormat["chapter"])
@@ -138,7 +137,7 @@ class GuiBuildNovel(QDialog):
self.fmtUnnumbered = QLineEdit() self.fmtUnnumbered = QLineEdit()
self.fmtUnnumbered.setMaxLength(200) self.fmtUnnumbered.setMaxLength(200)
self.fmtUnnumbered.setFixedWidth(xFmt) self.fmtUnnumbered.setMinimumWidth(xFmt)
self.fmtUnnumbered.setToolTip(fmtHelp) self.fmtUnnumbered.setToolTip(fmtHelp)
self.fmtUnnumbered.setText( self.fmtUnnumbered.setText(
self._reFmtCodes(self.theProject.titleFormat["unnumbered"]) self._reFmtCodes(self.theProject.titleFormat["unnumbered"])
@@ -146,7 +145,7 @@ class GuiBuildNovel(QDialog):
self.fmtScene = QLineEdit() self.fmtScene = QLineEdit()
self.fmtScene.setMaxLength(200) self.fmtScene.setMaxLength(200)
self.fmtScene.setFixedWidth(xFmt) self.fmtScene.setMinimumWidth(xFmt)
self.fmtScene.setToolTip(fmtHelp + fmtScHelp) self.fmtScene.setToolTip(fmtHelp + fmtScHelp)
self.fmtScene.setText( self.fmtScene.setText(
self._reFmtCodes(self.theProject.titleFormat["scene"]) self._reFmtCodes(self.theProject.titleFormat["scene"])
@@ -154,28 +153,41 @@ class GuiBuildNovel(QDialog):
self.fmtSection = QLineEdit() self.fmtSection = QLineEdit()
self.fmtSection.setMaxLength(200) self.fmtSection.setMaxLength(200)
self.fmtSection.setFixedWidth(xFmt) self.fmtSection.setMinimumWidth(xFmt)
self.fmtSection.setToolTip(fmtHelp + fmtScHelp) self.fmtSection.setToolTip(fmtHelp + fmtScHelp)
self.fmtSection.setText( self.fmtSection.setText(
self._reFmtCodes(self.theProject.titleFormat["section"]) self._reFmtCodes(self.theProject.titleFormat["section"])
) )
self.titleForm.addWidget(QLabel("Title"), 0, 0, 1, 1, Qt.AlignLeft) # Dummy boxes due to QGridView and QLineEdit expand bug
self.titleForm.addWidget(self.fmtTitle, 0, 1, 1, 1, Qt.AlignRight) self.boxTitle = QHBoxLayout()
self.titleForm.addWidget(QLabel("Chapter"), 1, 0, 1, 1, Qt.AlignLeft) self.boxTitle.addWidget(self.fmtTitle)
self.titleForm.addWidget(self.fmtChapter, 1, 1, 1, 1, Qt.AlignRight) self.boxChapter = QHBoxLayout()
self.titleForm.addWidget(QLabel("Unnumbered"), 2, 0, 1, 1, Qt.AlignLeft) self.boxChapter.addWidget(self.fmtChapter)
self.titleForm.addWidget(self.fmtUnnumbered, 2, 1, 1, 1, Qt.AlignRight) self.boxUnnumbered = QHBoxLayout()
self.titleForm.addWidget(QLabel("Scene"), 3, 0, 1, 1, Qt.AlignLeft) self.boxUnnumbered.addWidget(self.fmtUnnumbered)
self.titleForm.addWidget(self.fmtScene, 3, 1, 1, 1, Qt.AlignRight) self.boxScene = QHBoxLayout()
self.titleForm.addWidget(QLabel("Section"), 4, 0, 1, 1, Qt.AlignLeft) self.boxScene.addWidget(self.fmtScene)
self.titleForm.addWidget(self.fmtSection, 4, 1, 1, 1, Qt.AlignRight) self.boxSection = QHBoxLayout()
self.boxSection.addWidget(self.fmtSection)
self.titleForm.setColumnStretch(0, 1) self.titleForm.addWidget(QLabel("Title"), 0, 0, 1, 1, Qt.AlignLeft)
self.titleForm.setColumnStretch(1, 0) self.titleForm.addLayout(self.boxTitle, 0, 1, 1, 1, Qt.AlignRight)
self.titleForm.addWidget(QLabel("Chapter"), 1, 0, 1, 1, Qt.AlignLeft)
self.titleForm.addLayout(self.boxChapter, 1, 1, 1, 1, Qt.AlignRight)
self.titleForm.addWidget(QLabel("Unnumbered"), 2, 0, 1, 1, Qt.AlignLeft)
self.titleForm.addLayout(self.boxUnnumbered, 2, 1, 1, 1, Qt.AlignRight)
self.titleForm.addWidget(QLabel("Scene"), 3, 0, 1, 1, Qt.AlignLeft)
self.titleForm.addLayout(self.boxScene, 3, 1, 1, 1, Qt.AlignRight)
self.titleForm.addWidget(QLabel("Section"), 4, 0, 1, 1, Qt.AlignLeft)
self.titleForm.addLayout(self.boxSection, 4, 1, 1, 1, Qt.AlignRight)
self.titleForm.setColumnStretch(0, 0)
self.titleForm.setColumnStretch(1, 1)
# Text Options # Text Options
# ============= # =============
self.formatGroup = QGroupBox("Formatting Options", self) self.formatGroup = QGroupBox("Formatting Options", self)
self.formatForm = QGridLayout(self) self.formatForm = QGridLayout(self)
self.formatGroup.setLayout(self.formatForm) self.formatGroup.setLayout(self.formatForm)
@@ -183,7 +195,7 @@ class GuiBuildNovel(QDialog):
## Font Family ## Font Family
self.textFont = QLineEdit() self.textFont = QLineEdit()
self.textFont.setReadOnly(True) self.textFont.setReadOnly(True)
self.textFont.setFixedWidth(self.mainConf.pxInt(182)) self.textFont.setMinimumWidth(xFmt)
self.textFont.setText( self.textFont.setText(
self.optState.getString("GuiBuildNovel", "textFont", self.mainConf.textFont) self.optState.getString("GuiBuildNovel", "textFont", self.mainConf.textFont)
) )
@@ -219,8 +231,12 @@ class GuiBuildNovel(QDialog):
self.optState.getBool("GuiBuildNovel", "noStyling", False) self.optState.getBool("GuiBuildNovel", "noStyling", False)
) )
# Dummy box due to QGridView and QLineEdit expand bug
self.boxFont = QHBoxLayout()
self.boxFont.addWidget(self.textFont)
self.formatForm.addWidget(QLabel("Font family"), 0, 0, 1, 1, Qt.AlignLeft) self.formatForm.addWidget(QLabel("Font family"), 0, 0, 1, 1, Qt.AlignLeft)
self.formatForm.addWidget(self.textFont, 0, 1, 1, 1, Qt.AlignRight) self.formatForm.addLayout(self.boxFont, 0, 1, 1, 1, Qt.AlignRight)
self.formatForm.addWidget(self.fontButton, 0, 2, 1, 1, Qt.AlignRight) self.formatForm.addWidget(self.fontButton, 0, 2, 1, 1, Qt.AlignRight)
self.formatForm.addWidget(QLabel("Font size"), 1, 0, 1, 1, Qt.AlignLeft) self.formatForm.addWidget(QLabel("Font size"), 1, 0, 1, 1, Qt.AlignLeft)
self.formatForm.addWidget(self.textSize, 1, 1, 1, 2, Qt.AlignRight) self.formatForm.addWidget(self.textSize, 1, 1, 1, 2, Qt.AlignRight)
@@ -229,12 +245,13 @@ class GuiBuildNovel(QDialog):
self.formatForm.addWidget(QLabel("Disable styling"), 3, 0, 1, 1, Qt.AlignLeft) self.formatForm.addWidget(QLabel("Disable styling"), 3, 0, 1, 1, Qt.AlignLeft)
self.formatForm.addWidget(self.noStyling, 3, 1, 1, 2, Qt.AlignRight) self.formatForm.addWidget(self.noStyling, 3, 1, 1, 2, Qt.AlignRight)
self.formatForm.setColumnStretch(0, 1) self.formatForm.setColumnStretch(0, 0)
self.formatForm.setColumnStretch(1, 0) self.formatForm.setColumnStretch(1, 1)
self.formatForm.setColumnStretch(2, 0) self.formatForm.setColumnStretch(2, 0)
# Include Switches # Include Switches
# ================ # ================
self.textGroup = QGroupBox("Text Options", self) self.textGroup = QGroupBox("Text Options", self)
self.textForm = QGridLayout(self) self.textForm = QGridLayout(self)
self.textGroup.setLayout(self.textForm) self.textGroup.setLayout(self.textForm)
@@ -283,9 +300,10 @@ class GuiBuildNovel(QDialog):
self.textForm.setColumnStretch(0, 1) self.textForm.setColumnStretch(0, 1)
self.textForm.setColumnStretch(1, 0) self.textForm.setColumnStretch(1, 0)
# Additional Options # File Filter Options
# ================== # ===================
self.fileGroup = QGroupBox("File Options", self)
self.fileGroup = QGroupBox("File Filter Options", self)
self.fileForm = QGridLayout(self) self.fileForm = QGridLayout(self)
self.fileGroup.setLayout(self.fileForm) self.fileGroup.setLayout(self.fileForm)
@@ -323,8 +341,31 @@ class GuiBuildNovel(QDialog):
self.fileForm.setColumnStretch(0, 1) self.fileForm.setColumnStretch(0, 1)
self.fileForm.setColumnStretch(1, 0) self.fileForm.setColumnStretch(1, 0)
# Export Options
# ==============
self.exportGroup = QGroupBox("Export Options", self)
self.exportForm = QGridLayout(self)
self.exportGroup.setLayout(self.exportForm)
self.replaceTabs = QSwitch()
self.replaceTabs.setToolTip(
"Replace all tabs with eight spaces."
)
self.replaceTabs.setChecked(
self.optState.getBool("GuiBuildNovel", "replaceTabs", False)
)
self.exportForm.addWidget(QLabel("Replace tabs with spaces"), 0, 0, 1, 1, Qt.AlignLeft)
self.exportForm.addWidget(self.replaceTabs, 0, 1, 1, 1, Qt.AlignRight)
self.exportForm.setColumnStretch(0, 1)
self.exportForm.setColumnStretch(1, 0)
# Build Button # Build Button
# ============ # ============
self.buildProgress = QProgressBar()
self.buildProgress = QProgressBar() self.buildProgress = QProgressBar()
self.buildNovel = QPushButton("Build Project") self.buildNovel = QPushButton("Build Project")
@@ -332,6 +373,7 @@ class GuiBuildNovel(QDialog):
# Action Buttons # Action Buttons
# ============== # ==============
self.buttonBox = QHBoxLayout() self.buttonBox = QHBoxLayout()
self.btnPrint = QPushButton("Print") self.btnPrint = QPushButton("Print")
@@ -349,28 +391,28 @@ class GuiBuildNovel(QDialog):
self.savePDF.triggered.connect(lambda: self._saveDocument(self.FMT_PDF)) self.savePDF.triggered.connect(lambda: self._saveDocument(self.FMT_PDF))
self.saveMenu.addAction(self.savePDF) self.saveMenu.addAction(self.savePDF)
self.saveHTM = QAction("%s HTML (.htm)" % nw.__package__, self) self.saveHTM = QAction("%s HTML (.htm)" % self.mainConf.appName, self)
self.saveHTM.triggered.connect(lambda: self._saveDocument(self.FMT_HTM)) self.saveHTM.triggered.connect(lambda: self._saveDocument(self.FMT_HTM))
self.saveMenu.addAction(self.saveHTM) self.saveMenu.addAction(self.saveHTM)
self.saveNWD = QAction("%s Markdown (.nwd)" % self.mainConf.appName, self)
self.saveNWD.triggered.connect(lambda: self._saveDocument(self.FMT_NWD))
self.saveMenu.addAction(self.saveNWD)
if self.mainConf.verQtValue >= 51400: if self.mainConf.verQtValue >= 51400:
self.saveMD = QAction("Markdown (.md)", self) self.saveMD = QAction("Markdown (.md)", self)
self.saveMD.triggered.connect(lambda: self._saveDocument(self.FMT_MD)) self.saveMD.triggered.connect(lambda: self._saveDocument(self.FMT_MD))
self.saveMenu.addAction(self.saveMD) self.saveMenu.addAction(self.saveMD)
self.saveNWD = QAction("%s Markdown (.nwd)" % nw.__package__, self)
self.saveNWD.triggered.connect(lambda: self._saveDocument(self.FMT_NWD))
self.saveMenu.addAction(self.saveNWD)
self.saveTXT = QAction("Plain Text (.txt)", self) self.saveTXT = QAction("Plain Text (.txt)", self)
self.saveTXT.triggered.connect(lambda: self._saveDocument(self.FMT_TXT)) self.saveTXT.triggered.connect(lambda: self._saveDocument(self.FMT_TXT))
self.saveMenu.addAction(self.saveTXT) self.saveMenu.addAction(self.saveTXT)
self.saveJsonH = QAction("JSON + %s HTML (.json)" % nw.__package__, self) self.saveJsonH = QAction("JSON + %s HTML (.json)" % self.mainConf.appName, self)
self.saveJsonH.triggered.connect(lambda: self._saveDocument(self.FMT_JSON_H)) self.saveJsonH.triggered.connect(lambda: self._saveDocument(self.FMT_JSON_H))
self.saveMenu.addAction(self.saveJsonH) self.saveMenu.addAction(self.saveJsonH)
self.saveJsonM = QAction("JSON + %s Markdown (.json)" % nw.__package__, self) self.saveJsonM = QAction("JSON + %s Markdown (.json)" % self.mainConf.appName, self)
self.saveJsonM.triggered.connect(lambda: self._saveDocument(self.FMT_JSON_M)) self.saveJsonM.triggered.connect(lambda: self._saveDocument(self.FMT_JSON_M))
self.saveMenu.addAction(self.saveJsonM) self.saveMenu.addAction(self.saveJsonM)
@@ -384,27 +426,68 @@ class GuiBuildNovel(QDialog):
# Assemble GUI # Assemble GUI
# ============ # ============
# Splitter Position
boxWidth = self.mainConf.pxInt(350)
boxWidth = self.optState.getInt("GuiBuildNovel", "boxWidth", boxWidth)
docWidth = max(self.width() - boxWidth, 100)
docWidth = self.optState.getInt("GuiBuildNovel", "docWidth", docWidth)
# The Tool Box
self.toolsBox = QVBoxLayout()
self.toolsBox.addWidget(self.titleGroup) self.toolsBox.addWidget(self.titleGroup)
self.toolsBox.addWidget(self.formatGroup) self.toolsBox.addWidget(self.formatGroup)
self.toolsBox.addWidget(self.textGroup) self.toolsBox.addWidget(self.textGroup)
self.toolsBox.addWidget(self.fileGroup) self.toolsBox.addWidget(self.fileGroup)
self.toolsBox.addWidget(self.exportGroup)
self.toolsBox.addStretch(1) self.toolsBox.addStretch(1)
self.toolsBox.addWidget(self.buildProgress)
self.toolsBox.addWidget(self.buildNovel)
self.toolsBox.addSpacing(8)
self.toolsBox.addLayout(self.buttonBox)
self.outerBox.addLayout(self.toolsBox) # Tool Box Wrapper Widget
self.outerBox.addWidget(self.docView) self.toolsWidget = QWidget()
self.outerBox.setStretch(0, 0) self.toolsWidget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
self.outerBox.setStretch(1, 1) self.toolsWidget.setLayout(self.toolsBox)
# Tool Box Scroll Area
self.toolsArea = QScrollArea()
self.toolsArea.setMinimumWidth(self.mainConf.pxInt(250))
self.toolsArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.toolsArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.toolsArea.setWidgetResizable(True)
self.toolsArea.setWidget(self.toolsWidget)
# Tools and Buttons Layout
self.innerBox = QVBoxLayout()
self.innerBox.addWidget(self.toolsArea)
self.innerBox.addSpacing(8)
self.innerBox.addWidget(self.buildProgress)
self.innerBox.addWidget(self.buildNovel)
self.innerBox.addSpacing(8)
self.innerBox.addLayout(self.buttonBox)
# Tools and Buttons Wrapper Widget
self.innerWidget = QWidget()
self.innerWidget.setLayout(self.innerBox)
# Main Dialog Splitter
self.mainSplit = QSplitter(Qt.Horizontal)
self.mainSplit.addWidget(self.innerWidget)
self.mainSplit.addWidget(self.docView)
self.mainSplit.setSizes([boxWidth, docWidth])
# Outer Layout
self.outerBox = QHBoxLayout()
self.outerBox.addWidget(self.mainSplit)
self.setLayout(self.outerBox) self.setLayout(self.outerBox)
self.buildNovel.setFocus() self.buildNovel.setFocus()
logger.debug("GuiBuildNovel initialisation complete") logger.debug("GuiBuildNovel initialisation complete")
# Load from Cache return
def viewCachedDoc(self):
"""Load the previously generated document from cache.
"""
if self._loadCache(): if self._loadCache():
textFont = self.textFont.text() textFont = self.textFont.text()
textSize = self.textSize.value() textSize = self.textSize.value()
@@ -415,14 +498,25 @@ class GuiBuildNovel(QDialog):
self.docView.clearStyleSheet() self.docView.clearStyleSheet()
else: else:
self.docView.setStyleSheet(self.htmlStyle) self.docView.setStyleSheet(self.htmlStyle)
self.docView.setContent(self.htmlText, self.buildTime)
htmlSize = sum([len(x) for x in self.htmlText])
if htmlSize < nwConst.maxBuildSize:
qApp.processEvents()
self.docView.setContent(self.htmlText, self.buildTime)
else:
self.docView.setText(
"Failed to generate preview. The result is too big."
)
self._enableQtSave(False)
else: else:
self.htmlText = [] self.htmlText = []
self.htmlStyle = [] self.htmlStyle = []
self.nwdText = [] self.nwdText = []
self.buildTime = 0 self.buildTime = 0
return False
return return True
## ##
# Slots # Slots
@@ -448,6 +542,7 @@ class GuiBuildNovel(QDialog):
noteFiles = self.noteFiles.isChecked() noteFiles = self.noteFiles.isChecked()
ignoreFlag = self.ignoreFlag.isChecked() ignoreFlag = self.ignoreFlag.isChecked()
includeBody = self.includeBody.isChecked() includeBody = self.includeBody.isChecked()
replaceTabs = self.replaceTabs.isChecked()
makeHtml = ToHtml(self.theProject, self.theParent) makeHtml = ToHtml(self.theProject, self.theParent)
makeHtml.setTitleFormat(fmtTitle) makeHtml.setTitleFormat(fmtTitle)
@@ -474,6 +569,8 @@ class GuiBuildNovel(QDialog):
self.htmlStyle = [] self.htmlStyle = []
self.nwdText = [] self.nwdText = []
htmlSize = 0
for nItt, tItem in enumerate(self.theProject.projTree): for nItt, tItem in enumerate(self.theProject.projTree):
noteRoot = noteFiles noteRoot = noteFiles
@@ -488,6 +585,7 @@ class GuiBuildNovel(QDialog):
makeHtml.doConvert() makeHtml.doConvert()
self.htmlText.append(makeHtml.getResult()) self.htmlText.append(makeHtml.getResult())
self.nwdText.append(makeHtml.getFilteredMarkdown()) self.nwdText.append(makeHtml.getFilteredMarkdown())
htmlSize += makeHtml.getResultSize()
elif self._checkInclude(tItem, noteFiles, novelFiles, ignoreFlag): elif self._checkInclude(tItem, noteFiles, novelFiles, ignoreFlag):
makeHtml.setText(tItem.itemHandle) makeHtml.setText(tItem.itemHandle)
@@ -498,6 +596,7 @@ class GuiBuildNovel(QDialog):
makeHtml.doPostProcessing() makeHtml.doPostProcessing()
self.htmlText.append(makeHtml.getResult()) self.htmlText.append(makeHtml.getResult())
self.nwdText.append(makeHtml.getFilteredMarkdown()) self.nwdText.append(makeHtml.getFilteredMarkdown())
htmlSize += makeHtml.getResultSize()
except Exception as e: except Exception as e:
logger.error("Failed to generate html of document '%s'" % tItem.itemHandle) logger.error("Failed to generate html of document '%s'" % tItem.itemHandle)
@@ -511,6 +610,24 @@ class GuiBuildNovel(QDialog):
# Update progress bar, also for skipped items # Update progress bar, also for skipped items
self.buildProgress.setValue(nItt+1) self.buildProgress.setValue(nItt+1)
if makeHtml.errData:
self.theParent.makeAlert((
"There were problems when building the project:"
"<br>-&nbsp;%s"
) % "<br>-&nbsp;".join(makeHtml.errData), nwAlert.ERROR)
if replaceTabs:
htmlText = []
eightSpace = "&nbsp;"*8
for aLine in self.htmlText:
htmlText.append(aLine.replace("\t", eightSpace))
self.htmlText = htmlText
nwdText = []
for aLine in self.nwdText:
nwdText.append(aLine.replace("\t", " "))
self.nwdText = nwdText
tEnd = int(time()) tEnd = int(time())
logger.debug("Built project in %.3f ms" % (1000*(tEnd-tStart))) logger.debug("Built project in %.3f ms" % (1000*(tEnd-tStart)))
self.htmlStyle = makeHtml.getStyleSheet() self.htmlStyle = makeHtml.getStyleSheet()
@@ -523,7 +640,15 @@ class GuiBuildNovel(QDialog):
self.docView.clearStyleSheet() self.docView.clearStyleSheet()
else: else:
self.docView.setStyleSheet(self.htmlStyle) self.docView.setStyleSheet(self.htmlStyle)
self.docView.setContent(self.htmlText, self.buildTime)
if htmlSize < nwConst.maxBuildSize:
self.docView.setContent(self.htmlText, self.buildTime)
self._enableQtSave(True)
else:
self.docView.setText(
"Failed to generate preview. The result is too big."
)
self._enableQtSave(False)
self._saveCache() self._saveCache()
@@ -637,12 +762,10 @@ class GuiBuildNovel(QDialog):
if self.mainConf.showGUI: if self.mainConf.showGUI:
dlgOpt = QFileDialog.Options() dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.DontUseNativeDialog dlgOpt |= QFileDialog.DontUseNativeDialog
saveTo = QFileDialog.getSaveFileName( savePath, _ = QFileDialog.getSaveFileName(
self, "Save Document As", savePath, options=dlgOpt self, "Save Document As", savePath, options=dlgOpt
) )
if saveTo[0]: if not savePath:
savePath = saveTo[0]
else:
return False return False
self.mainConf.setLastPath(savePath) self.mainConf.setLastPath(savePath)
@@ -666,6 +789,9 @@ class GuiBuildNovel(QDialog):
# Write novelWriter HTML data # Write novelWriter HTML data
theStyle = self.htmlStyle.copy() theStyle = self.htmlStyle.copy()
theStyle.append(r"article {width: 800px; margin: 40px auto;}") theStyle.append(r"article {width: 800px; margin: 40px auto;}")
bodyText = "".join(self.htmlText)
bodyText = bodyText.replace("\t", "&#09;")
theHtml = ( theHtml = (
"<!DOCTYPE html>\n" "<!DOCTYPE html>\n"
"<html>\n" "<html>\n"
@@ -685,7 +811,7 @@ class GuiBuildNovel(QDialog):
).format( ).format(
projTitle = self.theProject.projName, projTitle = self.theProject.projName,
htmlStyle = "\n".join(theStyle), htmlStyle = "\n".join(theStyle),
bodyText = "".join(self.htmlText), bodyText = bodyText,
) )
outFile.write(theHtml) outFile.write(theHtml)
@@ -773,8 +899,10 @@ class GuiBuildNovel(QDialog):
def _doPrintPreview(self, thePrinter): def _doPrintPreview(self, thePrinter):
"""Connect the print preview painter to the document viewer. """Connect the print preview painter to the document viewer.
""" """
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
thePrinter.setOrientation(QPrinter.Portrait) thePrinter.setOrientation(QPrinter.Portrait)
self.docView.qDocument.print(thePrinter) self.docView.qDocument.print(thePrinter)
qApp.restoreOverrideCursor()
return return
def _selectFont(self): def _selectFont(self):
@@ -787,6 +915,9 @@ class GuiBuildNovel(QDialog):
if theStatus: if theStatus:
self.textFont.setText(theFont.family()) self.textFont.setText(theFont.family())
self.textSize.setValue(theFont.pointSize()) self.textSize.setValue(theFont.pointSize())
self.raise_() # Move the dialog to front (fixes a bug on macOS)
return return
def _loadCache(self): def _loadCache(self):
@@ -860,13 +991,24 @@ class GuiBuildNovel(QDialog):
"""Capture the user closing the window so we can save settings. """Capture the user closing the window so we can save settings.
""" """
self._saveSettings() self._saveSettings()
QDialog.closeEvent(self, theEvent) self.docView.clear()
theEvent.accept()
return return
## ##
# Internal Functions # Internal Functions
## ##
def _enableQtSave(self, theState):
"""Set the enabled status of Save menu entries that depend on
the QTextDocument.
"""
self.saveODT.setEnabled(theState)
self.savePDF.setEnabled(theState)
self.saveMD.setEnabled(theState)
self.saveTXT.setEnabled(theState)
return
def _saveSettings(self): def _saveSettings(self):
"""Save the various user settings. """Save the various user settings.
""" """
@@ -894,10 +1036,21 @@ class GuiBuildNovel(QDialog):
incComments = self.includeComments.isChecked() incComments = self.includeComments.isChecked()
incKeywords = self.includeKeywords.isChecked() incKeywords = self.includeKeywords.isChecked()
incBodyText = self.includeBody.isChecked() incBodyText = self.includeBody.isChecked()
replaceTabs = self.replaceTabs.isChecked()
mainSplit = self.mainSplit.sizes()
if len(mainSplit) == 2:
boxWidth = self.mainConf.rpxInt(mainSplit[0])
docWidth = self.mainConf.rpxInt(mainSplit[1])
else:
boxWidth = 100
docWidth = 100
# GUI Settings # GUI Settings
self.optState.setValue("GuiBuildNovel", "winWidth", winWidth) self.optState.setValue("GuiBuildNovel", "winWidth", winWidth)
self.optState.setValue("GuiBuildNovel", "winHeight", winHeight) self.optState.setValue("GuiBuildNovel", "winHeight", winHeight)
self.optState.setValue("GuiBuildNovel", "boxWidth", boxWidth)
self.optState.setValue("GuiBuildNovel", "docWidth", docWidth)
self.optState.setValue("GuiBuildNovel", "justifyText", justifyText) self.optState.setValue("GuiBuildNovel", "justifyText", justifyText)
self.optState.setValue("GuiBuildNovel", "noStyling", noStyling) self.optState.setValue("GuiBuildNovel", "noStyling", noStyling)
self.optState.setValue("GuiBuildNovel", "textFont", textFont) self.optState.setValue("GuiBuildNovel", "textFont", textFont)
@@ -909,6 +1062,7 @@ class GuiBuildNovel(QDialog):
self.optState.setValue("GuiBuildNovel", "incComments", incComments) self.optState.setValue("GuiBuildNovel", "incComments", incComments)
self.optState.setValue("GuiBuildNovel", "incKeywords", incKeywords) self.optState.setValue("GuiBuildNovel", "incKeywords", incKeywords)
self.optState.setValue("GuiBuildNovel", "incBodyText", incBodyText) self.optState.setValue("GuiBuildNovel", "incBodyText", incBodyText)
self.optState.setValue("GuiBuildNovel", "replaceTabs", replaceTabs)
self.optState.saveSettings() self.optState.saveSettings()
return return
@@ -956,6 +1110,12 @@ class GuiBuildNovelDocView(QTextBrowser):
theFont.setPointSize(self.mainConf.textSize) theFont.setPointSize(self.mainConf.textSize)
self.setFont(theFont) self.setFont(theFont)
# Set the tab stops
if self.mainConf.verQtValue >= 51000:
self.setTabStopDistance(self.mainConf.getTabWidth())
else:
self.setTabStopWidth(self.mainConf.getTabWidth())
docPalette = self.palette() docPalette = self.palette()
docPalette.setColor(QPalette.Base, QColor(255, 255, 255)) docPalette.setColor(QPalette.Base, QColor(255, 255, 255))
docPalette.setColor(QPalette.Text, QColor(0, 0, 0)) docPalette.setColor(QPalette.Text, QColor(0, 0, 0))
@@ -1019,17 +1179,13 @@ class GuiBuildNovelDocView(QTextBrowser):
self.buildTime = timeStamp self.buildTime = timeStamp
sPos = self.verticalScrollBar().value() sPos = self.verticalScrollBar().value()
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
# Refresh the tab stops
if self.mainConf.verQtValue >= 51000:
self.setTabStopDistance(self.mainConf.getTabWidth())
else:
self.setTabStopWidth(self.mainConf.getTabWidth())
theText = theText.replace("\t", "!!tab!!") theText = theText.replace("\t", "!!tab!!")
theText = theText.replace("<del>", "<span style='text-decoration: line-through;'>") theText = theText.replace("<del>", "<span style='text-decoration: line-through;'>")
theText = theText.replace("</del>", "</span>") theText = theText.replace("</del>", "</span>")
self.setHtml(theText) self.setHtml(theText)
qApp.processEvents()
while self.find("!!tab!!"): while self.find("!!tab!!"):
theCursor = self.textCursor() theCursor = self.textCursor()
@@ -1041,6 +1197,7 @@ class GuiBuildNovelDocView(QTextBrowser):
# Since we change the content while it may still be rendering, we mark # Since we change the content while it may still be rendering, we mark
# the document dirty again to make sure it's re-rendered properly. # the document dirty again to make sure it's re-rendered properly.
self.qDocument.markContentsDirty(0, self.qDocument.characterCount()) self.qDocument.markContentsDirty(0, self.qDocument.characterCount())
qApp.restoreOverrideCursor()
return return
+190 -90
View File
@@ -12,6 +12,7 @@
Created: 2020-04-25 [0.4.5] GuiDocEditHeader Created: 2020-04-25 [0.4.5] GuiDocEditHeader
Rewritten: 2020-06-15 [0.9.0] GuiDocEditSearch Rewritten: 2020-06-15 [0.9.0] GuiDocEditSearch
Created: 2020-06-27 [0.10.0] GuiDocEditFooter Created: 2020-06-27 [0.10.0] GuiDocEditFooter
Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter
This file is a part of novelWriter This file is a part of novelWriter
Copyright 20182020, Veronica Berglyd Olsen Copyright 20182020, Veronica Berglyd Olsen
@@ -36,7 +37,8 @@ import logging
from time import time from time import time
from PyQt5.QtCore import ( from PyQt5.QtCore import (
Qt, QSize, QThread, QTimer, pyqtSlot, QRegExp, QRegularExpression Qt, QSize, QTimer, pyqtSlot, pyqtSignal, QRegExp, QRegularExpression,
QPointF, QObject, QRunnable
) )
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette, QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette,
@@ -52,7 +54,7 @@ from nw.core import NWDoc, NWSpellCheck, NWSpellSimple, countWords
from nw.gui.dochighlight import GuiDocHighlighter from nw.gui.dochighlight import GuiDocHighlighter
from nw.common import transferCase from nw.common import transferCase
from nw.constants import ( from nw.constants import (
nwAlert, nwUnicode, nwDocAction, nwDocInsert, nwItemClass nwConst, nwAlert, nwUnicode, nwDocAction, nwDocInsert, nwItemClass
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -69,21 +71,23 @@ class GuiDocEditor(QTextEdit):
self.theParent = theParent self.theParent = theParent
self.theTheme = theParent.theTheme self.theTheme = theParent.theTheme
self.theProject = theParent.theProject self.theProject = theParent.theProject
self.docChanged = False
self.spellCheck = False
self.nwDocument = NWDoc(self.theProject, self.theParent) self.nwDocument = NWDoc(self.theProject, self.theParent)
self.theHandle = None
self.theDict = None self.docChanged = False # Flag for changed status of document
self.spellCheck = False # Flag for spell checking enabled
self.theHandle = None # The handle of the open file
self.theDict = None # The current spell check dictionary
self.nonWord = "\"'" # Characters to not include in spell checking
# Document Variables # Document Variables
self.charCount = 0 self.charCount = 0 # Character count
self.wordCount = 0 self.wordCount = 0 # Word count
self.paraCount = 0 self.paraCount = 0 # Paragraph count
self.lastEdit = 0 self.lastEdit = 0 # Time stamp of last edit
self.lastFind = None self.lastFind = None # Position of the last found search word
self.bigDoc = False self.bigDoc = False # Flag for very large document size
self.doReplace = False self.doReplace = False # Switch to temporarily disable auto-replace
self.nonWord = "\"'" self.queuePos = None # Used for delayed change of cursor position
# Typography # Typography
self.typDQOpen = self.mainConf.fmtDoubleQuotes[0] self.typDQOpen = self.mainConf.fmtDoubleQuotes[0]
@@ -94,6 +98,7 @@ class GuiDocEditor(QTextEdit):
# Core Elements and Signals # Core Elements and Signals
self.qDocument = self.document() self.qDocument = self.document()
self.qDocument.contentsChange.connect(self._docChange) self.qDocument.contentsChange.connect(self._docChange)
self.qDocument.documentLayout().documentSizeChanged.connect(self._docSizeChanged)
# Document Title # Document Title
self.docHeader = GuiDocEditHeader(self) self.docHeader = GuiDocEditHeader(self)
@@ -132,14 +137,15 @@ class GuiDocEditor(QTextEdit):
activated=self._followTag activated=self._followTag
) )
# Set Up Word Count Thread and Timer # Set Up Word Counter
self.wcInterval = self.mainConf.wordCountTimer self.wcInterval = self.mainConf.wordCountTimer
self.wcTimer = QTimer() self.wcTimer = QTimer()
self.wcTimer.setInterval(int(self.wcInterval*1000)) self.wcTimer.setInterval(int(self.wcInterval*1000))
self.wcTimer.timeout.connect(self._runCounter) self.wcTimer.timeout.connect(self._runCounter)
self.wCounter = BackgroundWordCounter(self) self.wCounter = BackgroundWordCounter(self)
self.wCounter.finished.connect(self._updateCounts) self.wCounter.setAutoDelete(False)
self.wCounter.signals.countsReady.connect(self._updateCounts)
self.initEditor() self.initEditor()
@@ -161,8 +167,10 @@ class GuiDocEditor(QTextEdit):
self.wordCount = 0 self.wordCount = 0
self.paraCount = 0 self.paraCount = 0
self.lastEdit = 0 self.lastEdit = 0
self.lastFind = None
self.bigDoc = False self.bigDoc = False
self.doReplace = False self.doReplace = False
self.queuePos = None
self.setDocumentChanged(False) self.setDocumentChanged(False)
self.docHeader.setTitleFromHandle(self.theHandle) self.docHeader.setTitleFromHandle(self.theHandle)
@@ -216,6 +224,12 @@ class GuiDocEditor(QTextEdit):
self.qDocument.setDefaultTextOption(theOpt) self.qDocument.setDefaultTextOption(theOpt)
# Refresh the tab stops
if self.mainConf.verQtValue >= 51000:
self.setTabStopDistance(self.mainConf.getTabWidth())
else:
self.setTabStopWidth(self.mainConf.getTabWidth())
# Initialise the syntax highlighter # Initialise the syntax highlighter
self.hLight.initHighlighter() self.hLight.initHighlighter()
@@ -223,21 +237,12 @@ class GuiDocEditor(QTextEdit):
# font changed, otherwise we just clear the editor entirely, # font changed, otherwise we just clear the editor entirely,
# which makes it read only. # which makes it read only.
if self.theHandle is not None: if self.theHandle is not None:
self.reloadText() self.redrawText()
else: else:
self.clearEditor() self.clearEditor()
return True return True
def reloadText(self):
"""Reloads the document currently being edited.
"""
if self.theHandle is not None:
tHandle = self.theHandle
self.clearEditor()
self.loadText(tHandle, showStatus=False)
return
def loadText(self, tHandle, tLine=None, showStatus=True): def loadText(self, tHandle, tLine=None, showStatus=True):
"""Load text from a document into the editor. If we have an io """Load text from a document into the editor. If we have an io
error, we must handle this and clear the editor so that we don't error, we must handle this and clear the editor so that we don't
@@ -253,12 +258,22 @@ class GuiDocEditor(QTextEdit):
self.clearEditor() self.clearEditor()
return False return False
docSize = len(theDoc)
if docSize > nwConst.maxDocSize:
self.theParent.makeAlert((
"The document you are trying to open is too big. "
"The document size is %.2f\u202fMB. "
"The maximum size allowed is %.2f\u202fMB."
) % (docSize/1.0e6, nwConst.maxDocSize/1.0e6), nwAlert.ERROR)
self.clearEditor()
return False
qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
self.hLight.setHandle(tHandle) self.hLight.setHandle(tHandle)
# Check that the document is not too big for full, initial spell # Check that the document is not too big for full, initial spell
# checking. If it is too big, we switch to only check as we type # checking. If it is too big, we switch to only check as we type
self._checkDocSize(len(theDoc)) self._checkDocSize(docSize)
spTemp = self.hLight.spellCheck spTemp = self.hLight.spellCheck
if self.bigDoc: if self.bigDoc:
self.hLight.spellCheck = False self.hLight.spellCheck = False
@@ -266,15 +281,11 @@ class GuiDocEditor(QTextEdit):
bfTime = time() bfTime = time()
self._allowAutoReplace(False) self._allowAutoReplace(False)
self.setPlainText(theDoc) self.setPlainText(theDoc)
qApp.processEvents()
self._allowAutoReplace(True) self._allowAutoReplace(True)
afTime = time() afTime = time()
logger.debug("Document highlighted in %.3f milliseconds" % (1000*(afTime-bfTime))) logger.debug("Document highlighted in %.3f ms" % (1000*(afTime-bfTime)))
theItem = self.nwDocument.getCurrentItem()
if tLine is None and theItem is not None:
self.setCursorPosition(theItem.cursorPos)
else:
self.setCursorLine(tLine)
self.lastEdit = time() self.lastEdit = time()
self._runCounter() self._runCounter()
@@ -287,24 +298,56 @@ class GuiDocEditor(QTextEdit):
self.docFooter.setHandle(self.theHandle) self.docFooter.setHandle(self.theHandle)
self.updateDocMargins() self.updateDocMargins()
self.hLight.spellCheck = spTemp self.hLight.spellCheck = spTemp
theItem = self.nwDocument.getCurrentItem()
if tLine is None and theItem is not None:
# For large documents we queue the repositioning until the
# document layout has grown past the point we want to move
# the cursor to. This makes the loading significantly
# faster.
if docSize > 50000:
self.queuePos = theItem.cursorPos
else:
self.setCursorPosition(theItem.cursorPos)
else:
self.setCursorLine(tLine)
qApp.restoreOverrideCursor() qApp.restoreOverrideCursor()
# Refresh the tab stops
if self.mainConf.verQtValue >= 51000:
self.setTabStopDistance(self.mainConf.getTabWidth())
else:
self.setTabStopWidth(self.mainConf.getTabWidth())
return True return True
def updateTagHighLighting(self, forceBigDoc=False):
"""Rerun the syntax highlighter on all meta data lines.
"""
self.hLight.rehighlightByType(GuiDocHighlighter.BLOCK_META)
return
def redrawText(self):
"""Redraw the text by marking the document content as "dirty".
"""
self.qDocument.markContentsDirty(0, self.qDocument.characterCount())
return
def replaceText(self, theText): def replaceText(self, theText):
"""Replaces the text of the current document with the provided """Replaces the text of the current document with the provided
text. This also clears undo history. text. This also clears undo history.
""" """
docSize = len(theText)
if docSize > nwConst.maxDocSize:
self.theParent.makeAlert((
"The text you are trying to add is too big. "
"The text size is %.2f\u202fMB. "
"The maximum size allowed is %.2f\u202fMB."
) % (docSize/1.0e6, nwConst.maxDocSize/1.0e6), nwAlert.ERROR)
return False
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
self.setPlainText(theText) self.setPlainText(theText)
self.setDocumentChanged(True) self.setDocumentChanged(True)
self.updateDocMargins() self.updateDocMargins()
return qApp.restoreOverrideCursor()
return True
def saveText(self): def saveText(self):
"""Save the text currently in the editor to the NWDoc object, """Save the text currently in the editor to the NWDoc object,
@@ -315,11 +358,10 @@ class GuiDocEditor(QTextEdit):
return False return False
docText = self.getText() docText = self.getText()
cursPos = self.getCursorPosition()
theItem.setCharCount(self.charCount) theItem.setCharCount(self.charCount)
theItem.setWordCount(self.wordCount) theItem.setWordCount(self.wordCount)
theItem.setParaCount(self.paraCount) theItem.setParaCount(self.paraCount)
theItem.setCursorPos(cursPos) self.saveCursorPosition()
self.nwDocument.saveDocument(docText) self.nwDocument.saveDocument(docText)
self.setDocumentChanged(False) self.setDocumentChanged(False)
@@ -428,6 +470,15 @@ class GuiDocEditor(QTextEdit):
""" """
return self.textCursor().selectionEnd() return self.textCursor().selectionEnd()
def saveCursorPosition(self):
"""Save the cursor position to the current project item object.
"""
theItem = self.nwDocument.getCurrentItem()
if theItem is not None:
cursPos = self.getCursorPosition()
theItem.setCursorPos(cursPos)
return
def setCursorLine(self, theLine): def setCursorLine(self, theLine):
"""Move the cursor to a given line in the document. """Move the cursor to a given line in the document.
""" """
@@ -455,7 +506,11 @@ class GuiDocEditor(QTextEdit):
theLang = self.theProject.projLang theLang = self.theProject.projLang
self.theDict.setLanguage(theLang, self.theProject.projDict) self.theDict.setLanguage(theLang, self.theProject.projDict)
self.theParent.statusBar.setLanguage(self.theDict.spellLanguage)
aLang, aName = self.theDict.describeDict()
self.theParent.statusBar.setLanguage(
aLang, "%s [%s]" % (self.mainConf.spellTool.title(), aName.title())
)
if not self.bigDoc: if not self.bigDoc:
self.spellCheckDocument() self.spellCheckDocument()
@@ -502,9 +557,8 @@ class GuiDocEditor(QTextEdit):
qApp.restoreOverrideCursor() qApp.restoreOverrideCursor()
afTime = time() afTime = time()
logger.debug( logger.debug(
"Document re-highlighted in %.3f milliseconds" % (1000*(afTime-bfTime)) "Document highlighted in %.3f ms" % (1000*(afTime-bfTime))
) )
self.theParent.statusBar.showMessage("Spell check complete") self.theParent.statusBar.showMessage("Spell check complete")
return True return True
@@ -727,6 +781,13 @@ class GuiDocEditor(QTextEdit):
""" """
self.lastEdit = time() self.lastEdit = time()
self.lastFind = None self.lastFind = None
if self.qDocument.characterCount() > nwConst.maxDocSize:
self.theParent.makeAlert((
"The document has grown too big and you cannot add more text to it. "
"The maximum size of a single novelWriter document is %.2f\u202fMB."
) % (nwConst.maxDocSize/1.0e6), nwAlert.ERROR)
self.undo()
return
if not self.docChanged: if not self.docChanged:
self.setDocumentChanged(True) self.setDocumentChanged(True)
if not self.wcTimer.isActive(): if not self.wcTimer.isActive():
@@ -802,7 +863,7 @@ class GuiDocEditor(QTextEdit):
mnuHead = QAction("Spelling Suggestion(s)", mnuContext) mnuHead = QAction("Spelling Suggestion(s)", mnuContext)
mnuContext.addAction(mnuHead) mnuContext.addAction(mnuHead)
theSuggest = self.theDict.suggestWords(theWord) theSuggest = self.theDict.suggestWords(theWord)[:15]
if len(theSuggest) > 0: if len(theSuggest) > 0:
for aWord in theSuggest: for aWord in theSuggest:
mnuWord = QAction("%s %s" % (nwUnicode.U_ENDASH, aWord), mnuContext) mnuWord = QAction("%s %s" % (nwUnicode.U_ENDASH, aWord), mnuContext)
@@ -855,21 +916,18 @@ class GuiDocEditor(QTextEdit):
"""Decide whether to run the word counter, or stop the timer due """Decide whether to run the word counter, or stop the timer due
to inactivity. to inactivity.
""" """
sinceActive = time()-self.lastEdit if self.wCounter.isRunning():
if sinceActive > 5*self.wcInterval: logger.verbose("Word counter is busy")
logger.debug( return
"Stopping word count timer: no activity last %.1f seconds" % sinceActive
) if time() - self.lastEdit < 5*self.wcInterval:
self.wcTimer.stop() logger.verbose("Running word counter")
elif self.wCounter.isRunning(): self.theParent.threadPool.start(self.wCounter)
logger.verbose("Word counter thread is busy")
else:
logger.verbose("Starting word counter")
self.wCounter.start()
return return
@pyqtSlot() @pyqtSlot(int, int, int)
def _updateCounts(self): def _updateCounts(self, cCount, wCount, pCount):
"""Slot for the word counter's finished signal """Slot for the word counter's finished signal
""" """
theItem = self.nwDocument.getCurrentItem() theItem = self.nwDocument.getCurrentItem()
@@ -878,23 +936,44 @@ class GuiDocEditor(QTextEdit):
logger.verbose("Updating word count") logger.verbose("Updating word count")
self.charCount = self.wCounter.charCount self.charCount = cCount
self.wordCount = self.wCounter.wordCount self.wordCount = wCount
self.paraCount = self.wCounter.paraCount self.paraCount = pCount
theItem.setCharCount(self.charCount) theItem.setCharCount(cCount)
theItem.setWordCount(self.wordCount) theItem.setWordCount(wCount)
theItem.setParaCount(self.paraCount) theItem.setParaCount(pCount)
self.theParent.treeView.propagateCount(self.theHandle, self.wordCount) self.theParent.treeView.propagateCount(self.theHandle, wCount)
self.theParent.treeView.projectWordCount() self.theParent.treeView.projectWordCount()
self.theParent.treeMeta.updateCounts( self.theParent.treeMeta.updateCounts(self.theHandle, cCount, wCount, pCount)
self.theHandle, self.charCount, self.wordCount, self.paraCount self._checkDocSize(self.qDocument.characterCount())
)
self._checkDocSize(self.charCount)
self.docFooter.updateCounts() self.docFooter.updateCounts()
return return
@pyqtSlot("QSizeF")
def _docSizeChanged(self, theSize):
"""Called whenever the underlying document layout size changes.
This is used to queue the repositioning of the cursor for very
large documents to ensure the region where the cursor is being
moved to has been drawn before the move is made.
"""
if self.queuePos is not None:
thePos = self.qDocument.documentLayout().hitTest(
QPointF(theSize.width(), theSize.height()), Qt.FuzzyHit
)
if self.queuePos <= thePos:
logger.verbose(
"Allowed cursor move to %d <= %d" % (self.queuePos, thePos)
)
self.setCursorPosition(self.queuePos)
self.queuePos = None
else:
logger.verbose(
"Denied cursor move to %d > %d" % (self.queuePos, thePos)
)
return
## ##
# Internal Functions # Internal Functions
## ##
@@ -1050,15 +1129,24 @@ class GuiDocEditor(QTextEdit):
"""Check if document size crosses the big document limit set in """Check if document size crosses the big document limit set in
config. If so, we will set the big document flag to True. config. If so, we will set the big document flag to True.
""" """
if theSize > self.mainConf.bigDocLimit*1000: newState = theSize > self.mainConf.bigDocLimit*1000
logger.info(
"The document size is %d > %d, big doc mode is enabled" % ( if newState != self.bigDoc:
theSize, self.mainConf.bigDocLimit*1000 if newState:
logger.info(
"The document size is {:n} > {:n}, big doc mode has been enabled".format(
theSize, self.mainConf.bigDocLimit*1000
)
) )
) else:
self.bigDoc = True logger.info(
else: "The document size is {:n} <= {:n}, big doc mode has been disabled".format(
self.bigDoc = False theSize, self.mainConf.bigDocLimit*1000
)
)
self.bigDoc = newState
return return
def _wrapSelection(self, tBefore, tAfter=None): def _wrapSelection(self, tBefore, tAfter=None):
@@ -1432,33 +1520,42 @@ class GuiDocEditor(QTextEdit):
# END Class GuiDocEditor # END Class GuiDocEditor
# =============================================================================================== # # =============================================================================================== #
# The Off GUI Thread Word Counter # The Off-GUI Thread Word Counter
# Runs the word counter in the background for the DocEditor # A runnable for the word counter to be run in the thread pool off the main GUI thread.
# =============================================================================================== # # =============================================================================================== #
class BackgroundWordCounter(QThread): class BackgroundWordCounter(QRunnable):
def __init__(self, docEditor): def __init__(self, docEditor):
QThread.__init__(self, docEditor) QRunnable.__init__(self)
self.docEditor = docEditor self.docEditor = docEditor
self.charCount = 0 self.signals = BackgroundWordCounterSignals()
self.wordCount = 0 self._isRunning = False
self.paraCount = 0
return return
def isRunning(self):
return self._isRunning
@pyqtSlot()
def run(self): def run(self):
"""Overloaded run function for the word counter, forwarding the """Overloaded run function for the word counter, forwarding the
call to the function that does the actual counting. call to the function that does the actual counting.
""" """
self._isRunning = True
theText = self.docEditor.getText() theText = self.docEditor.getText()
cC, wC, pC = countWords(theText) cC, wC, pC = countWords(theText)
self.charCount = cC self.signals.countsReady.emit(cC, wC, pC)
self.wordCount = wC self._isRunning = False
self.paraCount = pC
return return
## END Class BackgroundWordCounter ## END Class BackgroundWordCounter
class BackgroundWordCounterSignals(QObject):
countsReady = pyqtSignal(int, int, int)
# END Class BackgroundWordCounterSignals
# =============================================================================================== # # =============================================================================================== #
# The Embedded Document Search/Replace Feature # The Embedded Document Search/Replace Feature
# Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport # Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport
@@ -2176,7 +2273,10 @@ class GuiDocEditFooter(QWidget):
wCount = self.theItem.wordCount wCount = self.theItem.wordCount
wDiff = wCount - self.theItem.initCount wDiff = wCount - self.theItem.initCount
self.wordsText.setText("Words: {:n} ({:+n})".format(wCount, wDiff)) self.wordsText.setText(f"Words: {wCount:n} ({wDiff:+n})")
byteSize = self.docEditor.qDocument.characterCount()
self.wordsText.setToolTip(f"Document size is {byteSize:n} bytes")
return return
+38 -2
View File
@@ -28,6 +28,8 @@
import nw import nw
import logging import logging
from time import time
from PyQt5.QtCore import Qt, QRegularExpression from PyQt5.QtCore import Qt, QRegularExpression
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QColor, QTextCharFormat, QFont, QSyntaxHighlighter, QBrush QColor, QTextCharFormat, QFont, QSyntaxHighlighter, QBrush
@@ -39,6 +41,11 @@ logger = logging.getLogger(__name__)
class GuiDocHighlighter(QSyntaxHighlighter): class GuiDocHighlighter(QSyntaxHighlighter):
BLOCK_NONE = 0
BLOCK_TEXT = 1
BLOCK_META = 2
BLOCK_TITLE = 4
def __init__(self, theDoc, theParent): def __init__(self, theDoc, theParent):
QSyntaxHighlighter.__init__(self, theDoc) QSyntaxHighlighter.__init__(self, theDoc)
@@ -198,7 +205,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
# Build a QRegExp for spell checker # Build a QRegExp for spell checker
# Include additional characters that the highlighter should # Include additional characters that the highlighter should
# consider to be word separators # consider to be word separators
wordSep = r"_\+/" wordSep = r"\-_\+/"
wordSep += nwUnicode.U_ENDASH wordSep += nwUnicode.U_ENDASH
wordSep += nwUnicode.U_EMDASH wordSep += nwUnicode.U_EMDASH
self.spellRx = QRegularExpression(r"\b[^\s"+wordSep+r"]+\b") self.spellRx = QRegularExpression(r"\b[^\s"+wordSep+r"]+\b")
@@ -229,6 +236,27 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.theHandle = theHandle self.theHandle = theHandle
return True return True
##
# Methods
##
def rehighlightByType(self, theType):
"""Loop through all blocks and rehighlight those of a given
content type.
"""
qDocument = self.document()
nBlocks = qDocument.blockCount()
bfTime = time()
for i in range(nBlocks):
theBlock = qDocument.findBlockByNumber(i)
if theBlock.userState() & theType > 0:
self.rehighlightBlock(theBlock)
afTime = time()
logger.debug(
"Document highlighted in %.3f ms" % (1000*(afTime-bfTime))
)
return
## ##
# Highlight Block # Highlight Block
## ##
@@ -239,10 +267,12 @@ class GuiDocHighlighter(QSyntaxHighlighter):
is significantly faster than running the regex checks used for is significantly faster than running the regex checks used for
text paragraphs. text paragraphs.
""" """
self.setCurrentBlockState(self.BLOCK_NONE)
if self.theHandle is None or not theText: if self.theHandle is None or not theText:
return return
if theText.startswith("@"): # Keywords and commands if theText.startswith("@"): # Keywords and commands
self.setCurrentBlockState(self.BLOCK_META)
tItem = self.theParent.theProject.projTree[self.theHandle] tItem = self.theParent.theProject.projTree[self.theHandle]
isValid, theBits, thePos = self.theIndex.scanThis(theText) isValid, theBits, thePos = self.theIndex.scanThis(theText)
isGood = self.theIndex.checkThese(theBits, tItem) isGood = self.theIndex.checkThese(theBits, tItem)
@@ -266,22 +296,27 @@ class GuiDocHighlighter(QSyntaxHighlighter):
return return
elif theText.startswith("# "): # Header 1 elif theText.startswith("# "): # Header 1
self.setCurrentBlockState(self.BLOCK_TITLE)
self.setFormat(0, 1, self.hStyles["header1h"]) self.setFormat(0, 1, self.hStyles["header1h"])
self.setFormat(1, len(theText), self.hStyles["header1"]) self.setFormat(1, len(theText), self.hStyles["header1"])
elif theText.startswith("## "): # Header 2 elif theText.startswith("## "): # Header 2
self.setCurrentBlockState(self.BLOCK_TITLE)
self.setFormat(0, 2, self.hStyles["header2h"]) self.setFormat(0, 2, self.hStyles["header2h"])
self.setFormat(2, len(theText), self.hStyles["header2"]) self.setFormat(2, len(theText), self.hStyles["header2"])
elif theText.startswith("### "): # Header 3 elif theText.startswith("### "): # Header 3
self.setCurrentBlockState(self.BLOCK_TITLE)
self.setFormat(0, 3, self.hStyles["header3h"]) self.setFormat(0, 3, self.hStyles["header3h"])
self.setFormat(3, len(theText), self.hStyles["header3"]) self.setFormat(3, len(theText), self.hStyles["header3"])
elif theText.startswith("#### "): # Header 4 elif theText.startswith("#### "): # Header 4
self.setCurrentBlockState(self.BLOCK_TITLE)
self.setFormat(0, 4, self.hStyles["header4h"]) self.setFormat(0, 4, self.hStyles["header4h"])
self.setFormat(4, len(theText), self.hStyles["header4"]) self.setFormat(4, len(theText), self.hStyles["header4"])
elif theText.startswith("%"): # Comments elif theText.startswith("%"): # Comments
self.setCurrentBlockState(self.BLOCK_TEXT)
toCheck = theText[1:].lstrip() toCheck = theText[1:].lstrip()
synTag = toCheck[:9].lower() synTag = toCheck[:9].lower()
tLen = len(theText) tLen = len(theText)
@@ -294,6 +329,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.setFormat(0, tLen, self.hStyles["hidden"]) self.setFormat(0, tLen, self.hStyles["hidden"])
else: # Text Paragraph else: # Text Paragraph
self.setCurrentBlockState(self.BLOCK_TEXT)
for rX, xFmt in self.rxRules: for rX, xFmt in self.rxRules:
rxItt = rX.globalMatch(theText, 0) rxItt = rX.globalMatch(theText, 0)
while rxItt.hasNext(): while rxItt.hasNext():
@@ -314,7 +350,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
while rxSpell.hasNext(): while rxSpell.hasNext():
rxMatch = rxSpell.next() rxMatch = rxSpell.next()
if not self.theDict.checkWord(rxMatch.captured(0)): if not self.theDict.checkWord(rxMatch.captured(0)):
if rxMatch.captured(0) == rxMatch.captured(0).upper(): if rxMatch.captured(0).isupper() or rxMatch.captured(0).isnumeric():
continue continue
xPos = rxMatch.capturedStart(0) xPos = rxMatch.capturedStart(0)
xLen = rxMatch.capturedLength(0) xLen = rxMatch.capturedLength(0)
+19 -12
View File
@@ -34,10 +34,10 @@ import logging
from PyQt5.QtCore import Qt, QUrl, QSize, pyqtSlot from PyQt5.QtCore import Qt, QUrl, QSize, pyqtSlot
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QTextOption, QFont, QPalette, QColor, QTextCursor, QIcon QTextOption, QFont, QPalette, QColor, QTextCursor, QIcon, QCursor
) )
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QTextBrowser, QWidget, QScrollArea, QLabel, QHBoxLayout, QToolButton, qApp, QTextBrowser, QWidget, QScrollArea, QLabel, QHBoxLayout, QToolButton,
QAction, QMenu QAction, QMenu
) )
@@ -124,11 +124,15 @@ class GuiDocViewer(QTextBrowser):
theOpt.setAlignment(Qt.AlignJustify) theOpt.setAlignment(Qt.AlignJustify)
self.qDocument.setDefaultTextOption(theOpt) self.qDocument.setDefaultTextOption(theOpt)
# Refresh the tab stops
if self.mainConf.verQtValue >= 51000:
self.setTabStopDistance(self.mainConf.getTabWidth())
else:
self.setTabStopWidth(self.mainConf.getTabWidth())
# If we have a document open, we should reload it in case the font changed # If we have a document open, we should reload it in case the font changed
if self.theHandle is not None: if self.theHandle is not None:
tHandle = self.theHandle self.redrawText()
self.clearViewer()
self.loadText(tHandle)
return True return True
@@ -144,6 +148,8 @@ class GuiDocViewer(QTextBrowser):
return False return False
logger.debug("Generating preview for item %s" % tHandle) logger.debug("Generating preview for item %s" % tHandle)
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
sPos = self.verticalScrollBar().value() sPos = self.verticalScrollBar().value()
aDoc = ToHtml(self.theProject, self.theParent) aDoc = ToHtml(self.theProject, self.theParent)
aDoc.setPreview(True, self.mainConf.viewComments, self.mainConf.viewSynopsis) aDoc.setPreview(True, self.mainConf.viewComments, self.mainConf.viewSynopsis)
@@ -195,7 +201,8 @@ class GuiDocViewer(QTextBrowser):
# Since we change the content while it may still be rendering, we mark # Since we change the content while it may still be rendering, we mark
# the document dirty again to make sure it's re-rendered properly. # the document dirty again to make sure it's re-rendered properly.
self.qDocument.markContentsDirty(0, self.qDocument.characterCount()) self.redrawText()
qApp.restoreOverrideCursor()
return True return True
@@ -205,6 +212,12 @@ class GuiDocViewer(QTextBrowser):
self.loadText(self.theHandle, updateHistory=False) self.loadText(self.theHandle, updateHistory=False)
return return
def redrawText(self):
"""Redraw the text by marking the document content as "dirty".
"""
self.qDocument.markContentsDirty(0, self.qDocument.characterCount())
return
def loadFromTag(self, theTag): def loadFromTag(self, theTag):
"""Load text in the document from a reference given by a meta """Load text in the document from a reference given by a meta
tag rather than a known handle. This function depends on the tag rather than a known handle. This function depends on the
@@ -489,12 +502,6 @@ class GuiDocViewer(QTextBrowser):
"mark {{" "mark {{"
" color: rgb({eColR},{eColG},{eColB});" " color: rgb({eColR},{eColG},{eColB});"
"}}\n" "}}\n"
"table {{"
" margin: 10px 0px;"
"}}\n"
"td {{"
" padding: 0px 4px;"
"}}\n"
".tags {{" ".tags {{"
" color: rgb({kColR},{kColG},{kColB});" " color: rgb({kColR},{kColG},{kColB});"
" font-wright: bold;" " font-wright: bold;"
+6 -6
View File
@@ -195,9 +195,9 @@ class GuiItemDetails(QWidget):
we're already showing. we're already showing.
""" """
if tHandle == self.theHandle: if tHandle == self.theHandle:
self.cCountData.setText("{:n}".format(cC)) self.cCountData.setText(f"{cC:n}")
self.wCountData.setText("{:n}".format(wC)) self.wCountData.setText(f"{wC:n}")
self.pCountData.setText("{:n}".format(pC)) self.pCountData.setText(f"{pC:n}")
return return
def updateViewBox(self, tHandle): def updateViewBox(self, tHandle):
@@ -252,9 +252,9 @@ class GuiItemDetails(QWidget):
self.layoutData.setText(nwLabels.LAYOUT_NAME[nwItem.itemLayout]) self.layoutData.setText(nwLabels.LAYOUT_NAME[nwItem.itemLayout])
if nwItem.itemType == nwItemType.FILE: if nwItem.itemType == nwItemType.FILE:
self.cCountData.setText("{:n}".format(nwItem.charCount)) self.cCountData.setText(f"{nwItem.charCount:n}")
self.wCountData.setText("{:n}".format(nwItem.wordCount)) self.wCountData.setText(f"{nwItem.wordCount:n}")
self.pCountData.setText("{:n}".format(nwItem.paraCount)) self.pCountData.setText(f"{nwItem.paraCount:n}")
else: else:
self.cCountData.setText("") self.cCountData.setText("")
self.wCountData.setText("") self.wCountData.setText("")
+4
View File
@@ -276,6 +276,7 @@ class GuiMainMenu(QMenuBar):
self.aExitNW = QAction("Exit", self) self.aExitNW = QAction("Exit", self)
self.aExitNW.setStatusTip("Exit %s" % self.mainConf.appName) self.aExitNW.setStatusTip("Exit %s" % self.mainConf.appName)
self.aExitNW.setShortcut("Ctrl+Q") self.aExitNW.setShortcut("Ctrl+Q")
self.aExitNW.setMenuRole(QAction.QuitRole)
self.aExitNW.triggered.connect(lambda: self.theParent.closeMain()) self.aExitNW.triggered.connect(lambda: self.theParent.closeMain())
self.projMenu.addAction(self.aExitNW) self.projMenu.addAction(self.aExitNW)
@@ -843,6 +844,7 @@ class GuiMainMenu(QMenuBar):
self.aPreferences = QAction("Preferences", self) self.aPreferences = QAction("Preferences", self)
self.aPreferences.setStatusTip("Preferences") self.aPreferences.setStatusTip("Preferences")
self.aPreferences.setShortcut("Ctrl+,") self.aPreferences.setShortcut("Ctrl+,")
self.aPreferences.setMenuRole(QAction.PreferencesRole)
self.aPreferences.triggered.connect(lambda: self.theParent.showPreferencesDialog()) self.aPreferences.triggered.connect(lambda: self.theParent.showPreferencesDialog())
self.toolsMenu.addAction(self.aPreferences) self.toolsMenu.addAction(self.aPreferences)
@@ -857,12 +859,14 @@ class GuiMainMenu(QMenuBar):
# Help > About # Help > About
self.aAboutNW = QAction("About %s" % self.mainConf.appName, self) self.aAboutNW = QAction("About %s" % self.mainConf.appName, self)
self.aAboutNW.setStatusTip("About %s" % self.mainConf.appName) self.aAboutNW.setStatusTip("About %s" % self.mainConf.appName)
self.aAboutNW.setMenuRole(QAction.AboutRole)
self.aAboutNW.triggered.connect(lambda: self.theParent.showAboutNWDialog()) self.aAboutNW.triggered.connect(lambda: self.theParent.showAboutNWDialog())
self.helpMenu.addAction(self.aAboutNW) self.helpMenu.addAction(self.aAboutNW)
# Help > About Qt5 # Help > About Qt5
self.aAboutQt = QAction("About Qt5", self) self.aAboutQt = QAction("About Qt5", self)
self.aAboutQt.setStatusTip("About Qt5") self.aAboutQt.setStatusTip("About Qt5")
self.aAboutQt.setMenuRole(QAction.AboutQtRole)
self.aAboutQt.triggered.connect(lambda: self.theParent.showAboutQtDialog()) self.aAboutQt.triggered.connect(lambda: self.theParent.showAboutQtDialog())
self.helpMenu.addAction(self.aAboutQt) self.helpMenu.addAction(self.aAboutQt)
+3 -3
View File
@@ -431,9 +431,9 @@ class GuiOutline(QTreeWidget):
newItem.setText(self.colIndex[nwOutline.LINE], sTitle[1:].lstrip("0")) newItem.setText(self.colIndex[nwOutline.LINE], sTitle[1:].lstrip("0"))
newItem.setData(self.colIndex[nwOutline.LINE], Qt.UserRole, sTitle) newItem.setData(self.colIndex[nwOutline.LINE], Qt.UserRole, sTitle)
newItem.setText(self.colIndex[nwOutline.SYNOP], novIdx["synopsis"]) newItem.setText(self.colIndex[nwOutline.SYNOP], novIdx["synopsis"])
newItem.setText(self.colIndex[nwOutline.CCOUNT], str(novIdx["cCount"])) newItem.setText(self.colIndex[nwOutline.CCOUNT], "{:n}".format(novIdx["cCount"]))
newItem.setText(self.colIndex[nwOutline.WCOUNT], str(novIdx["wCount"])) newItem.setText(self.colIndex[nwOutline.WCOUNT], "{:n}".format(novIdx["wCount"]))
newItem.setText(self.colIndex[nwOutline.PCOUNT], str(novIdx["pCount"])) newItem.setText(self.colIndex[nwOutline.PCOUNT], "{:n}".format(novIdx["pCount"]))
newItem.setTextAlignment(self.colIndex[nwOutline.CCOUNT], Qt.AlignRight) newItem.setTextAlignment(self.colIndex[nwOutline.CCOUNT], Qt.AlignRight)
newItem.setTextAlignment(self.colIndex[nwOutline.WCOUNT], Qt.AlignRight) newItem.setTextAlignment(self.colIndex[nwOutline.WCOUNT], Qt.AlignRight)
newItem.setTextAlignment(self.colIndex[nwOutline.PCOUNT], Qt.AlignRight) newItem.setTextAlignment(self.colIndex[nwOutline.PCOUNT], Qt.AlignRight)
+1 -1
View File
@@ -340,7 +340,7 @@ class GuiConfigEditGeneralTab(QWidget):
dlgOpt = QFileDialog.Options() dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.ShowDirsOnly dlgOpt |= QFileDialog.ShowDirsOnly
dlgOpt |= QFileDialog.DontUseNativeDialog dlgOpt |= QFileDialog.DontUseNativeDialog
newDir = QFileDialog.getExistingDirectory( newDir = QFileDialog.getExistingDirectory(
self, "Backup Directory", currDir, options=dlgOpt self, "Backup Directory", currDir, options=dlgOpt
) )
if newDir: if newDir:
+18 -6
View File
@@ -125,7 +125,7 @@ class GuiProjectLoad(QDialog):
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Open | QDialogButtonBox.Cancel) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Open | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doOpenRecent) self.buttonBox.accepted.connect(self._doOpenRecent)
self.buttonBox.rejected.connect(self._doClose) self.buttonBox.rejected.connect(self._doCancel)
self.newButton = self.buttonBox.addButton("New", QDialogButtonBox.ActionRole) self.newButton = self.buttonBox.addButton("New", QDialogButtonBox.ActionRole)
self.newButton.clicked.connect(self._doNewProject) self.newButton.clicked.connect(self._doNewProject)
@@ -153,7 +153,7 @@ class GuiProjectLoad(QDialog):
"""Close the dialog window with a recent project selected. """Close the dialog window with a recent project selected.
""" """
logger.verbose("GuiProjectLoad open button clicked") logger.verbose("GuiProjectLoad open button clicked")
self._saveDialogState() self._saveSettings()
selItems = self.listBox.selectedItems() selItems = self.listBox.selectedItems()
if selItems: if selItems:
@@ -194,11 +194,12 @@ class GuiProjectLoad(QDialog):
return return
def _doClose(self): def _doCancel(self):
"""Close the dialog window without doing anything. """Close the dialog window without doing anything.
""" """
logger.verbose("GuiProjectLoad close button clicked") logger.verbose("GuiProjectLoad close button clicked")
self._saveDialogState() self.openPath = None
self.openState = self.NONE_STATE
self.close() self.close()
return return
@@ -206,7 +207,7 @@ class GuiProjectLoad(QDialog):
"""Create a new project. """Create a new project.
""" """
logger.verbose("GuiProjectLoad new project button clicked") logger.verbose("GuiProjectLoad new project button clicked")
self._saveDialogState() self._saveSettings()
self.openPath = None self.openPath = None
self.openState = self.NEW_STATE self.openState = self.NEW_STATE
self.accept() self.accept()
@@ -230,11 +231,22 @@ class GuiProjectLoad(QDialog):
return return
##
# Events
##
def closeEvent(self, theEvent):
"""Capture the user closing the dialog so we can save settings.
"""
self._saveSettings()
theEvent.accept()
return
## ##
# Internal Functions # Internal Functions
## ##
def _saveDialogState(self): def _saveSettings(self):
"""Save the changes made to the dialog. """Save the changes made to the dialog.
""" """
colWidths = [0, 0, 0] colWidths = [0, 0, 0]
+3 -3
View File
@@ -282,15 +282,15 @@ class GuiProjectEditMeta(QWidget):
self.nRootLabel = QLabel("Root folders:") self.nRootLabel = QLabel("Root folders:")
self.nRootLabel.setIndent(xInd) self.nRootLabel.setIndent(xInd)
self.nRootValue = QLabel("{:n}".format(nR)) self.nRootValue = QLabel(f"{nR:n}")
self.nDirLabel = QLabel("Folders:") self.nDirLabel = QLabel("Folders:")
self.nDirLabel.setIndent(xInd) self.nDirLabel.setIndent(xInd)
self.nDirValue = QLabel("{:n}".format(nD)) self.nDirValue = QLabel(f"{nD:n}")
self.nFileLabel = QLabel("Documents:") self.nFileLabel = QLabel("Documents:")
self.nFileLabel.setIndent(xInd) self.nFileLabel.setIndent(xInd)
self.nFileValue = QLabel("{:n}".format(nF)) self.nFileValue = QLabel(f"{nF:n}")
self.wordsLabel = QLabel("Word count:") self.wordsLabel = QLabel("Word count:")
self.wordsLabel.setIndent(xInd) self.wordsLabel.setIndent(xInd)
+8 -5
View File
@@ -401,7 +401,7 @@ class GuiProjectTree(QTreeWidget):
if nwItemS is None: if nwItemS is None:
return False return False
wCount = int(trItemS.text(self.C_COUNT)) wCount = int(trItemS.data(self.C_COUNT, Qt.UserRole))
if nwItemS.itemType == nwItemType.FILE: if nwItemS.itemType == nwItemType.FILE:
logger.debug("User requested file %s moved to trash" % tHandle) logger.debug("User requested file %s moved to trash" % tHandle)
trItemP = trItemS.parent() trItemP = trItemS.parent()
@@ -550,12 +550,13 @@ class GuiProjectTree(QTreeWidget):
""" """
tItem = self._getTreeItem(tHandle) tItem = self._getTreeItem(tHandle)
if tItem is not None: if tItem is not None:
tItem.setText(self.C_COUNT, str(theCount)) tItem.setText(self.C_COUNT, f"{theCount:n}")
tItem.setData(self.C_COUNT, Qt.UserRole, int(theCount))
pItem = tItem.parent() pItem = tItem.parent()
if pItem is not None: if pItem is not None:
pCount = 0 pCount = 0
for i in range(pItem.childCount()): for i in range(pItem.childCount()):
pCount += int(pItem.child(i).text(self.C_COUNT)) pCount += int(pItem.child(i).data(self.C_COUNT, Qt.UserRole))
pHandle = pItem.data(self.C_NAME, Qt.UserRole) pHandle = pItem.data(self.C_NAME, Qt.UserRole)
if not nDepth > nwConst.maxDepth + 1 and pHandle != "": if not nDepth > nwConst.maxDepth + 1 and pHandle != "":
@@ -575,7 +576,7 @@ class GuiProjectTree(QTreeWidget):
tItem = self.topLevelItem(n) tItem = self.topLevelItem(n)
if tItem == self.orphRoot: if tItem == self.orphRoot:
continue continue
nWords += int(tItem.text(self.C_COUNT)) nWords += int(tItem.data(self.C_COUNT, Qt.UserRole))
self.theProject.setProjectWordCount(nWords) self.theProject.setProjectWordCount(nWords)
sWords = self.theProject.getSessionWordCount() sWords = self.theProject.getSessionWordCount()
@@ -715,7 +716,7 @@ class GuiProjectTree(QTreeWidget):
self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR) self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR)
return return
wCount = int(sItem.text(self.C_COUNT)) wCount = int(sItem.data(self.C_COUNT, Qt.UserRole))
isSame = snItem.itemClass == dnItem.itemClass isSame = snItem.itemClass == dnItem.itemClass
isNone = snItem.itemClass == nwItemClass.NO_CLASS isNone = snItem.itemClass == nwItemClass.NO_CLASS
isNote = snItem.itemLayout == nwItemLayout.NOTE isNote = snItem.itemLayout == nwItemLayout.NOTE
@@ -809,6 +810,7 @@ class GuiProjectTree(QTreeWidget):
newItem.setTextAlignment(self.C_FLAGS, Qt.AlignLeft | Qt.AlignVCenter) newItem.setTextAlignment(self.C_FLAGS, Qt.AlignLeft | Qt.AlignVCenter)
newItem.setData(self.C_NAME, Qt.UserRole, tHandle) newItem.setData(self.C_NAME, Qt.UserRole, tHandle)
newItem.setData(self.C_COUNT, Qt.UserRole, 0)
self.theMap[tHandle] = newItem self.theMap[tHandle] = newItem
if pHandle is None: if pHandle is None:
@@ -881,6 +883,7 @@ class GuiProjectTree(QTreeWidget):
self.orphRoot = newItem self.orphRoot = newItem
newItem.setExpanded(True) newItem.setExpanded(True)
newItem.setData(self.C_NAME, Qt.UserRole, "") newItem.setData(self.C_NAME, Qt.UserRole, "")
newItem.setData(self.C_COUNT, Qt.UserRole, 0)
newItem.setIcon(self.C_NAME, self.theTheme.getIcon("proj_orphan")) newItem.setIcon(self.C_NAME, self.theTheme.getIcon("proj_orphan"))
return return
+5 -1
View File
@@ -152,13 +152,17 @@ class GuiMainStatus(QStatusBar):
qApp.processEvents() qApp.processEvents()
return return
def setLanguage(self, theLanguage): def setLanguage(self, theLanguage, theProvider=""):
"""Set the language code for the spell checker. """Set the language code for the spell checker.
""" """
if theLanguage is None: if theLanguage is None:
self.langText.setText("None") self.langText.setText("None")
self.langText.setToolTip("")
else: else:
self.langText.setText(NWSpellCheck.expandLanguage(theLanguage)) self.langText.setText(NWSpellCheck.expandLanguage(theLanguage))
self.langText.setToolTip(
"Provider: %s" % (theProvider if theProvider else "unknown")
)
return return
def setProjectStatus(self, isChanged): def setProjectStatus(self, isChanged):
+21 -15
View File
@@ -33,7 +33,7 @@ import os
from datetime import datetime from datetime import datetime
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap from PyQt5.QtGui import QPixmap, QCursor
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
qApp, QDialog, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QGridLayout, qApp, QDialog, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QGridLayout,
QLabel, QGroupBox, QMenu, QAction, QFileDialog, QSpinBox, QHBoxLayout QLabel, QGroupBox, QMenu, QAction, QFileDialog, QSpinBox, QHBoxLayout
@@ -253,10 +253,15 @@ class GuiWritingStats(QDialog):
logger.debug("GuiWritingStats initialisation complete") logger.debug("GuiWritingStats initialisation complete")
qApp.processEvents() return
def populateGUI(self):
"""Populate list box with data from the log file.
"""
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
self._loadLogFile() self._loadLogFile()
self._updateListBox() self._updateListBox()
qApp.restoreOverrideCursor()
return return
## ##
@@ -320,20 +325,20 @@ class GuiWritingStats(QDialog):
# Generate the file name # Generate the file name
if fileExt: if fileExt:
fileName = "sessionStats.%s" % fileExt saveDir = self.mainConf.lastPath
saveDir = self.mainConf.lastPath
savePath = os.path.join(saveDir, fileName)
if not os.path.isdir(saveDir): if not os.path.isdir(saveDir):
saveDir = self.mainConf.homePath saveDir = os.path.expanduser("~")
fileName = "sessionStats.%s" % fileExt
savePath = os.path.join(saveDir, fileName)
dlgOpt = QFileDialog.Options() dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.DontUseNativeDialog dlgOpt |= QFileDialog.DontUseNativeDialog
saveTo = QFileDialog.getSaveFileName( savePath, _ = QFileDialog.getSaveFileName(
self, "Save Document As", savePath, options=dlgOpt self, "Save Document As", savePath, options=dlgOpt
) )
if saveTo:
savePath = saveTo[0] if not savePath:
else:
return False return False
self.mainConf.setLastPath(savePath) self.mainConf.setLastPath(savePath)
@@ -449,10 +454,11 @@ class GuiWritingStats(QDialog):
) )
return False return False
ttWords = ttNovel + ttNotes
self.labelTotal.setText(self._formatTime(ttTime)) self.labelTotal.setText(self._formatTime(ttTime))
self.novelWords.setText("{:n}".format(ttNovel)) self.novelWords.setText(f"{ttNovel:n}")
self.notesWords.setText("{:n}".format(ttNotes)) self.notesWords.setText(f"{ttNotes:n}")
self.totalWords.setText("{:n}".format(ttNovel + ttNotes)) self.totalWords.setText(f"{ttWords:n}")
return True return True
@@ -539,7 +545,7 @@ class GuiWritingStats(QDialog):
newItem = QTreeWidgetItem() newItem = QTreeWidgetItem()
newItem.setText(self.C_TIME, sStart) newItem.setText(self.C_TIME, sStart)
newItem.setText(self.C_LENGTH, self._formatTime(sDiff)) newItem.setText(self.C_LENGTH, self._formatTime(sDiff))
newItem.setText(self.C_COUNT, "{:n}".format(nWords)) newItem.setText(self.C_COUNT, f"{nWords:n}")
if nWords > 0 and listMax > 0: if nWords > 0 and listMax > 0:
theBar = self.barImage.scaled( theBar = self.barImage.scaled(
+22 -9
View File
@@ -32,7 +32,7 @@ import os
from datetime import datetime from datetime import datetime
from time import time from time import time
from PyQt5.QtCore import Qt, QTimer from PyQt5.QtCore import Qt, QTimer, QThreadPool
from PyQt5.QtGui import QIcon, QPixmap, QColor, QKeySequence, QCursor from PyQt5.QtGui import QIcon, QPixmap, QColor, QKeySequence, QCursor
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
qApp, QMainWindow, QVBoxLayout, QWidget, QSplitter, QFileDialog, QShortcut, qApp, QMainWindow, QVBoxLayout, QWidget, QSplitter, QFileDialog, QShortcut,
@@ -48,6 +48,7 @@ from nw.gui import (
) )
from nw.core import NWProject, NWDoc, NWIndex from nw.core import NWProject, NWDoc, NWIndex
from nw.constants import nwItemType, nwItemClass, nwAlert from nw.constants import nwItemType, nwItemClass, nwAlert
from nw.common import getGuiItem
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -59,6 +60,7 @@ class GuiMain(QMainWindow):
logger.debug("Initialising GUI ...") logger.debug("Initialising GUI ...")
self.setObjectName("GuiMain") self.setObjectName("GuiMain")
self.mainConf = nw.CONFIG self.mainConf = nw.CONFIG
self.threadPool = QThreadPool()
# Some runtime info useful for debugging # Some runtime info useful for debugging
logger.info("OS: %s" % self.mainConf.osType) logger.info("OS: %s" % self.mainConf.osType)
@@ -448,6 +450,7 @@ class GuiMain(QMainWindow):
"""Close the document and clear the editor and title field. """Close the document and clear the editor and title field.
""" """
if self.hasProject: if self.hasProject:
self.docEditor.saveCursorPosition()
if self.docEditor.docChanged: if self.docEditor.docChanged:
self.saveDocument() self.saveDocument()
self.docEditor.clearEditor() self.docEditor.clearEditor()
@@ -556,16 +559,15 @@ class GuiMain(QMainWindow):
extFilter = [ extFilter = [
"Text files (*.txt)", "Text files (*.txt)",
"Markdown files (*.md)", "Markdown files (*.md)",
"novelWriter files (*.nwd)",
"All files (*.*)", "All files (*.*)",
] ]
dlgOpt = QFileDialog.Options() dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.DontUseNativeDialog dlgOpt |= QFileDialog.DontUseNativeDialog
inPath = QFileDialog.getOpenFileName( loadFile, _ = QFileDialog.getOpenFileName(
self, "Import File", lastPath, options=dlgOpt, filter=";;".join(extFilter) self, "Import File", lastPath, options=dlgOpt, filter=";;".join(extFilter)
) )
if inPath: if not loadFile:
loadFile = inPath[0]
else:
return False return False
if loadFile.strip() == "": if loadFile.strip() == "":
@@ -716,8 +718,7 @@ class GuiMain(QMainWindow):
tEnd = time() tEnd = time()
self.statusBar.setStatus("Indexing completed in %.1f ms" % ((tEnd - tStart)*1000.0)) self.statusBar.setStatus("Indexing completed in %.1f ms" % ((tEnd - tStart)*1000.0))
self.docEditor.reloadText() self.docEditor.updateTagHighLighting()
qApp.restoreOverrideCursor() qApp.restoreOverrideCursor()
if not beQuiet: if not beQuiet:
@@ -816,9 +817,15 @@ class GuiMain(QMainWindow):
logger.error("No project open") logger.error("No project open")
return return
dlgBuild = GuiBuildNovel(self, self.theProject) dlgBuild = getGuiItem("GuiBuildNovel")
if dlgBuild is None:
dlgBuild = GuiBuildNovel(self, self.theProject)
dlgBuild.setModal(False) dlgBuild.setModal(False)
dlgBuild.show() dlgBuild.show()
qApp.processEvents()
dlgBuild.viewCachedDoc()
return return
def showWritingStatsDialog(self): def showWritingStatsDialog(self):
@@ -828,9 +835,15 @@ class GuiMain(QMainWindow):
logger.error("No project open") logger.error("No project open")
return return
dlgStats = GuiWritingStats(self, self.theProject) dlgStats = getGuiItem("GuiWritingStats")
if dlgStats is None:
dlgStats = GuiWritingStats(self, self.theProject)
dlgStats.setModal(False) dlgStats.setModal(False)
dlgStats.show() dlgStats.show()
qApp.processEvents()
dlgStats.populateGUI()
return return
def showAboutNWDialog(self): def showAboutNWDialog(self):
+1 -1
View File
@@ -135,7 +135,7 @@ setuptools.setup(
"console_scripts" : ["novelWriter-cli=nw:main"], "console_scripts" : ["novelWriter-cli=nw:main"],
"gui_scripts" : ["novelWriter=nw:main"], "gui_scripts" : ["novelWriter=nw:main"],
}, },
packages = setuptools.find_packages(exclude=["docs","tests","sample"]), packages = setuptools.find_packages(exclude=["docs", "tests", "sample"]),
include_package_data = True, include_package_data = True,
package_data = {"": ["*.conf"]}, package_data = {"": ["*.conf"]},
project_urls = { project_urls = {
+5 -1
View File
@@ -3,4 +3,8 @@
% Exctracted from the lipsum.com website. % Exctracted from the lipsum.com website.
It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like). It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.
The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.
Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).
+10 -9
View File
@@ -1,18 +1,19 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.12.1" hexVersion="0x001201f0" fileVersion="1.2" timeStamp="2020-08-28 15:49:43"> <novelWriterXML appVersion="1.0b3" hexVersion="0x010000b3" fileVersion="1.2" timeStamp="2020-10-04 19:00:34">
<project> <project>
<name>Lorem Ipsum</name> <name>Lorem Ipsum</name>
<title>Lorem Ipsum</title> <title>Lorem Ipsum</title>
<author>lipsum.com</author> <author>lipsum.com</author>
<saveCount>8</saveCount> <saveCount>9</saveCount>
<autoCount>21</autoCount> <autoCount>22</autoCount>
<editTime>1472</editTime> <editTime>1552</editTime>
</project> </project>
<settings> <settings>
<doBackup>False</doBackup> <doBackup>False</doBackup>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
<lastEdited>fb609cd8319dc</lastEdited> <lastEdited>846352075de7d</lastEdited>
<lastViewed>None</lastViewed> <lastViewed>None</lastViewed>
<lastWordCount>3847</lastWordCount> <lastWordCount>3847</lastWordCount>
<novelWordCount>3109</novelWordCount> <novelWordCount>3109</novelWordCount>
@@ -147,10 +148,10 @@
<status>New</status> <status>New</status>
<exported>False</exported> <exported>False</exported>
<layout>BOOK</layout> <layout>BOOK</layout>
<charCount>630</charCount> <charCount>631</charCount>
<wordCount>109</wordCount> <wordCount>109</wordCount>
<paraCount>1</paraCount> <paraCount>3</paraCount>
<cursorPos>64</cursorPos> <cursorPos>376</cursorPos>
</item> </item>
<item handle="6bd935d2490cd" order="6" parent="b3643d0f92e32"> <item handle="6bd935d2490cd" order="6" parent="b3643d0f92e32">
<name>Chapter Two</name> <name>Chapter Two</name>
@@ -205,7 +206,7 @@
<charCount>3644</charCount> <charCount>3644</charCount>
<wordCount>543</wordCount> <wordCount>543</wordCount>
<paraCount>5</paraCount> <paraCount>5</paraCount>
<cursorPos>56</cursorPos> <cursorPos>351</cursorPos>
</item> </item>
<item handle="67a8707f2f249" order="1" parent="None"> <item handle="67a8707f2f249" order="1" parent="None">
<name>Characters</name> <name>Characters</name>
+13 -11
View File
@@ -34,17 +34,17 @@ article {width: 800px; margin: 40px auto;}
<h1 style='text-align: center; page-break-before: always;'>Act One</h1> <h1 style='text-align: center; page-break-before: always;'>Act One</h1>
<p style='text-align: center;'>“Fusce maximus felis libero”</p> <p style='text-align: center;'>“Fusce maximus felis libero”</p>
<h1 style='page-break-before: always;'>Chapter One: Chapter One</h1> <h1 style='page-break-before: always;'>Chapter One: Chapter One</h1>
<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.</p> <div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam. Praesent magna nunc, lacinia sit amet quam eget, aliquet ultrices justo. Morbi ornare enim et lorem rutrum finibus ut eu dolor. Aliquam a orci odio. Ut ultrices sem quis massa placerat, eget mollis nisl cursus. Cras vel sagittis justo. Ut non ultricies leo. Maecenas rutrum velit in est varius, et egestas massa pulvinar.</p> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam. Praesent magna nunc, lacinia sit amet quam eget, aliquet ultrices justo. Morbi ornare enim et lorem rutrum finibus ut eu dolor. Aliquam a orci odio. Ut ultrices sem quis massa placerat, eget mollis nisl cursus. Cras vel sagittis justo. Ut non ultricies leo. Maecenas rutrum velit in est varius, et egestas massa pulvinar.</p>
<h2>Scene 1.1: Scene One</h2> <h2>Scene 1.1: Scene One</h2>
<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.</p> <div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.</p>
<p>Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur. Donec quis ante nunc. Mauris ut leo ipsum. Vestibulum est neque, hendrerit nec neque a, ullamcorper lobortis tellus. Fusce sollicitudin purus quis congue bibendum. Aliquam condimentum ipsum tristique blandit tristique. Donec pulvinar neque ac suscipit malesuada.</p> <p>Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur. Donec quis ante nunc. Mauris ut leo ipsum. Vestibulum est neque, hendrerit nec neque a, ullamcorper lobortis tellus. Fusce sollicitudin purus quis congue bibendum. Aliquam condimentum ipsum tristique blandit tristique. Donec pulvinar neque ac suscipit malesuada.</p>
<p>Aliquam ut nisl arcu. Ut ultricies, lorem dignissim rutrum convallis, risus orci tempus lectus, congue feugiat sem lectus vitae odio. Duis sit amet justo finibus, hendrerit nulla at, ullamcorper enim. Praesent vel tellus sit amet tellus vulputate bibendum. Morbi eleifend sagittis sem, ac volutpat ante congue non. In hac habitasse platea dictumst. Morbi lobortis fermentum elit, dignissim sagittis ligula volutpat lacinia. Vestibulum eu interdum odio. Integer ac purus commodo metus congue tempor non at urna. Sed eget tortor vel quam viverra egestas. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec non convallis mauris, ac feugiat ex.</p> <p>Aliquam ut nisl arcu. Ut ultricies, lorem dignissim rutrum convallis, risus orci tempus lectus, congue feugiat sem lectus vitae odio. Duis sit amet justo finibus, hendrerit nulla at, ullamcorper enim. Praesent vel tellus sit amet tellus vulputate bibendum. Morbi eleifend sagittis sem, ac volutpat ante congue non. In hac habitasse platea dictumst. Morbi lobortis fermentum elit, dignissim sagittis ligula volutpat lacinia. Vestibulum eu interdum odio. Integer ac purus commodo metus congue tempor non at urna. Sed eget tortor vel quam viverra egestas. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec non convallis mauris, ac feugiat ex.</p>
<h3>1.1.1: Scene One, Section Two</h3> <h3>1.1.1: Scene One, Section Two</h3>
<p>Integer vel libero ipsum. Donec varius aliquam libero, sit amet commodo urna hendrerit non. Nullam quis erat mollis nunc viverra volutpat tincidunt in odio. Nam vitae quam sem. Aliquam suscipit nulla non lorem pharetra semper. Ut suscipit erat eu ligula accumsan ultrices. Phasellus nisl tellus, placerat sed laoreet id, consectetur nec dolor. Sed fringilla ipsum id dapibus posuere. Aenean finibus pharetra tincidunt. Ut molestie malesuada nulla, id posuere lorem tincidunt eu. Aliquam tempor eros a est vulputate, scelerisque pulvinar ipsum fermentum. In hac habitasse platea dictumst.</p> <p>Integer vel libero ipsum. Donec varius aliquam libero, sit amet commodo urna hendrerit non. Nullam quis erat mollis nunc viverra volutpat tincidunt in odio. Nam vitae quam sem. Aliquam suscipit nulla non lorem pharetra semper. Ut suscipit erat eu ligula accumsan ultrices. Phasellus nisl tellus, placerat sed laoreet id, consectetur nec dolor. Sed fringilla ipsum id dapibus posuere. Aenean finibus pharetra tincidunt. Ut molestie malesuada nulla, id posuere lorem tincidunt eu. Aliquam tempor eros a est vulputate, scelerisque pulvinar ipsum fermentum. In hac habitasse platea dictumst.</p>
<p>Curabitur congue, justo quis interdum fermentum, tellus nulla imperdiet sapien, eu interdum enim tellus condimentum metus. Vivamus nunc velit, dignissim ut ultrices sit amet, ultricies quis enim. Donec ut vestibulum neque. Vivamus semper neque id ex ullamcorper varius. Fusce mattis nibh viverra lorem sagittis, et tempor arcu congue. Suspendisse sit amet felis sed urna facilisis mattis eget vitae arcu. Proin eu magna hendrerit, tristique sem maximus, placerat diam. Nulla tristique sed velit sit amet varius. Etiam vel ornare magna, in vulputate arcu. Cras velit orci, tincidunt sed volutpat cursus, bibendum vel sem. Nunc vulputate pharetra tortor, ac consectetur neque tincidunt sit amet. Nulla ornare mi sed mi dignissim ultricies. Ut tincidunt bibendum mauris, sed elementum ex vulputate vel. Mauris fermentum, felis nec vehicula congue, felis lorem facilisis erat, a dictum dolor augue vitae quam. Maecenas rutrum tortor nec consequat eleifend.</p> <p>Curabitur congue, justo quis interdum fermentum, tellus nulla imperdiet sapien, eu interdum enim tellus condimentum metus. Vivamus nunc velit, dignissim ut ultrices sit amet, ultricies quis enim. Donec ut vestibulum neque. Vivamus semper neque id ex ullamcorper varius. Fusce mattis nibh viverra lorem sagittis, et tempor arcu congue. Suspendisse sit amet felis sed urna facilisis mattis eget vitae arcu. Proin eu magna hendrerit, tristique sem maximus, placerat diam. Nulla tristique sed velit sit amet varius. Etiam vel ornare magna, in vulputate arcu. Cras velit orci, tincidunt sed volutpat cursus, bibendum vel sem. Nunc vulputate pharetra tortor, ac consectetur neque tincidunt sit amet. Nulla ornare mi sed mi dignissim ultricies. Ut tincidunt bibendum mauris, sed elementum ex vulputate vel. Mauris fermentum, felis nec vehicula congue, felis lorem facilisis erat, a dictum dolor augue vitae quam. Maecenas rutrum tortor nec consequat eleifend.</p>
<h2>Scene 1.2: Scene Two</h2> <h2>Scene 1.2: Scene Two</h2>
<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.</p> <div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.</p>
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci. Vestibulum facilisis bibendum aliquam. Aliquam posuere, turpis ac bibendum varius, sem tellus venenatis risus, in elementum massa enim ac lorem. Integer in sem ac diam blandit ultricies ut in nulla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit amet erat est. Curabitur vitae cursus justo, sit amet placerat dolor. Vivamus eu felis hendrerit, tincidunt massa rutrum, maximus arcu. Pellentesque commodo justo odio, vel rutrum nulla tincidunt eu. Integer non neque condimentum, convallis diam non, varius ligula. Aliquam eget sapien mauris. Aenean pharetra nunc nisi, vel maximus ante tristique sit amet. Aliquam risus metus, interdum non odio eu, consectetur lacinia sapien.</p> <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci. Vestibulum facilisis bibendum aliquam. Aliquam posuere, turpis ac bibendum varius, sem tellus venenatis risus, in elementum massa enim ac lorem. Integer in sem ac diam blandit ultricies ut in nulla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit amet erat est. Curabitur vitae cursus justo, sit amet placerat dolor. Vivamus eu felis hendrerit, tincidunt massa rutrum, maximus arcu. Pellentesque commodo justo odio, vel rutrum nulla tincidunt eu. Integer non neque condimentum, convallis diam non, varius ligula. Aliquam eget sapien mauris. Aenean pharetra nunc nisi, vel maximus ante tristique sit amet. Aliquam risus metus, interdum non odio eu, consectetur lacinia sapien.</p>
<p>Proin vitae gravida nisl. Integer viverra orci turpis, sit amet pretium ligula facilisis consequat. Nulla interdum commodo metus, mollis consequat dui tincidunt et. Proin consequat bibendum justo id commodo. Fusce fermentum nunc turpis, eu vestibulum risus feugiat ut. Sed scelerisque vel ligula ut interdum. Suspendisse ac blandit ligula, sagittis fringilla dolor. In tincidunt convallis diam et ornare. Aenean id dignissim est, ut rhoncus quam. Donec vitae nisl velit. In convallis nibh ut augue dignissim, eu elementum quam cursus. Phasellus in lectus lorem. Curabitur in pellentesque nisi, at gravida sapien. Sed cursus justo volutpat lacus placerat, sit amet dignissim turpis commodo. Aliquam vitae orci eget nulla posuere condimentum in ut felis.</p> <p>Proin vitae gravida nisl. Integer viverra orci turpis, sit amet pretium ligula facilisis consequat. Nulla interdum commodo metus, mollis consequat dui tincidunt et. Proin consequat bibendum justo id commodo. Fusce fermentum nunc turpis, eu vestibulum risus feugiat ut. Sed scelerisque vel ligula ut interdum. Suspendisse ac blandit ligula, sagittis fringilla dolor. In tincidunt convallis diam et ornare. Aenean id dignissim est, ut rhoncus quam. Donec vitae nisl velit. In convallis nibh ut augue dignissim, eu elementum quam cursus. Phasellus in lectus lorem. Curabitur in pellentesque nisi, at gravida sapien. Sed cursus justo volutpat lacus placerat, sit amet dignissim turpis commodo. Aliquam vitae orci eget nulla posuere condimentum in ut felis.</p>
<p>Nulla accumsan ante in pulvinar efficitur. Nulla non velit quis urna hendrerit bibendum. Suspendisse ultrices ante eu justo malesuada, sed fermentum enim rutrum. Nunc fermentum pharetra felis, vitae sollicitudin quam rutrum porta. Aliquam fringilla velit a mi laoreet, et luctus est rutrum. In gravida non ipsum sit amet tempus. Curabitur et eleifend purus. Nulla facilisi.</p> <p>Nulla accumsan ante in pulvinar efficitur. Nulla non velit quis urna hendrerit bibendum. Suspendisse ultrices ante eu justo malesuada, sed fermentum enim rutrum. Nunc fermentum pharetra felis, vitae sollicitudin quam rutrum porta. Aliquam fringilla velit a mi laoreet, et luctus est rutrum. In gravida non ipsum sit amet tempus. Curabitur et eleifend purus. Nulla facilisi.</p>
@@ -54,18 +54,20 @@ article {width: 800px; margin: 40px auto;}
<p>Ut et consequat enim, quis ornare nibh. In lectus neque, mollis et suscipit et, vestibulum vitae augue. Praesent id ante sit amet odio venenatis placerat a at erat. Sed sed metus sed nisi dictum varius. Integer tincidunt fermentum purus ac porta. Fusce porttitor non risus eget tristique. Donec augue nunc, maximus at fermentum vel, varius et neque. Ut sed consectetur mauris. Quisque ipsum enim, porttitor vitae imperdiet sit amet, tempor et mauris. Aliquam malesuada tincidunt lectus quis blandit. Sed commodo orci felis, quis ultrices tellus facilisis sed. Nunc vel varius est. Duis ullamcorper eu metus in pulvinar. Morbi at sapien dictum, rutrum mauris eget, interdum tellus.</p> <p>Ut et consequat enim, quis ornare nibh. In lectus neque, mollis et suscipit et, vestibulum vitae augue. Praesent id ante sit amet odio venenatis placerat a at erat. Sed sed metus sed nisi dictum varius. Integer tincidunt fermentum purus ac porta. Fusce porttitor non risus eget tristique. Donec augue nunc, maximus at fermentum vel, varius et neque. Ut sed consectetur mauris. Quisque ipsum enim, porttitor vitae imperdiet sit amet, tempor et mauris. Aliquam malesuada tincidunt lectus quis blandit. Sed commodo orci felis, quis ultrices tellus facilisis sed. Nunc vel varius est. Duis ullamcorper eu metus in pulvinar. Morbi at sapien dictum, rutrum mauris eget, interdum tellus.</p>
<h1 style='page-break-before: always;'>Chapter Two: Why do we use it?</h1> <h1 style='page-break-before: always;'>Chapter Two: Why do we use it?</h1>
<p class='comment'><strong>Comment: </strong>Exctracted from the lipsum.com website.</p> <p class='comment'><strong>Comment: </strong>Exctracted from the lipsum.com website.</p>
<p>It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).</p> <p>&#09;It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.</p>
<p>&#09;The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.</p>
<p>&#09;Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).</p>
<h1 style='page-break-before: always;'>Chapter Three: Chapter Two</h1> <h1 style='page-break-before: always;'>Chapter Three: Chapter Two</h1>
<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.</p> <div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.</p>
<p>Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue. Vestibulum pharetra fermentum leo, sed faucibus eros placerat quis. In hac habitasse platea dictumst. Donec metus massa, rutrum quis consequat et, tincidunt ac felis. Duis mollis metus ac nunc tincidunt blandit. Ut aliquet velit eu odio pharetra condimentum. Integer rutrum lacus orci, id venenatis libero accumsan at.</p> <p>Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue. Vestibulum pharetra fermentum leo, sed faucibus eros placerat quis. In hac habitasse platea dictumst. Donec metus massa, rutrum quis consequat et, tincidunt ac felis. Duis mollis metus ac nunc tincidunt blandit. Ut aliquet velit eu odio pharetra condimentum. Integer rutrum lacus orci, id venenatis libero accumsan at.</p>
<h2>Scene 3.1: Scene Three</h2> <h2>Scene 3.1: Scene Three</h2>
<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</p> <div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</p>
<p>Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean tincidunt lacus vitae nibh elementum eleifend. Sed rutrum condimentum sem quis blandit. Duis imperdiet libero metus, quis convallis quam faucibus a. Nulla ligula est, semper quis sollicitudin et, pretium id justo. Curabitur pharetra risus eget consectetur commodo. Duis mattis arcu non est condimentum, id venenatis risus volutpat. Pellentesque aliquet mauris non mauris porttitor ultrices. Phasellus ut vestibulum mi. Suspendisse malesuada metus lorem, a malesuada orci rhoncus a. Praesent euismod convallis ante, lacinia tincidunt ex egestas id. Praesent sit amet efficitur sapien. Morbi tincidunt volutpat nunc sed dictum. Aliquam ultrices metus id fermentum lobortis.</p> <p>Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean tincidunt lacus vitae nibh elementum eleifend. Sed rutrum condimentum sem quis blandit. Duis imperdiet libero metus, quis convallis quam faucibus a. Nulla ligula est, semper quis sollicitudin et, pretium id justo. Curabitur pharetra risus eget consectetur commodo. Duis mattis arcu non est condimentum, id venenatis risus volutpat. Pellentesque aliquet mauris non mauris porttitor ultrices. Phasellus ut vestibulum mi. Suspendisse malesuada metus lorem, a malesuada orci rhoncus a. Praesent euismod convallis ante, lacinia tincidunt ex egestas id. Praesent sit amet efficitur sapien. Morbi tincidunt volutpat nunc sed dictum. Aliquam ultrices metus id fermentum lobortis.</p>
<p>Pellentesque id sagittis dui. Praesent ut nisi sit amet libero euismod ornare. Vestibulum vehicula, lorem eget aliquet imperdiet, eros nulla iaculis mi, vel bibendum est dui sed orci. Nullam vitae lorem rutrum, euismod lacus id, ullamcorper lectus. Duis nec commodo mi, a fringilla diam. Vestibulum molestie nibh tristique, viverra augue non, aliquet metus. Phasellus a tellus ac nisl tempor aliquet. Nulla vitae sapien rutrum augue ornare ultrices a quis nisi. Sed pulvinar tincidunt ex. Fusce vel sem vitae ante pellentesque lobortis.</p> <p>Pellentesque id sagittis dui. Praesent ut nisi sit amet libero euismod ornare. Vestibulum vehicula, lorem eget aliquet imperdiet, eros nulla iaculis mi, vel bibendum est dui sed orci. Nullam vitae lorem rutrum, euismod lacus id, ullamcorper lectus. Duis nec commodo mi, a fringilla diam. Vestibulum molestie nibh tristique, viverra augue non, aliquet metus. Phasellus a tellus ac nisl tempor aliquet. Nulla vitae sapien rutrum augue ornare ultrices a quis nisi. Sed pulvinar tincidunt ex. Fusce vel sem vitae ante pellentesque lobortis.</p>
<p>Maecenas ullamcorper lacus nec turpis finibus aliquet eget rutrum augue. Integer lorem erat, faucibus non lacus lacinia, pulvinar egestas felis. Proin rutrum nunc eget nulla varius, id blandit mauris tincidunt. Donec sit amet ullamcorper nisi, ut efficitur mi. Aliquam aliquet, nulla eget rhoncus tristique, justo lorem consectetur dui, id ornare leo odio sed tellus. Curabitur interdum velit a turpis condimentum venenatis. Nunc rhoncus sem ac augue auctor, nec malesuada ex fringilla. Vestibulum egestas diam sed leo consectetur vulputate quis eget enim. Nam tincidunt metus sit amet maximus ullamcorper. Sed placerat velit vitae massa efficitur viverra. Etiam eleifend dignissim ante, sed luctus nisl tristique a. In vestibulum pharetra dolor in molestie. Vivamus auctor massa ac magna imperdiet, sit amet iaculis turpis finibus.</p> <p>Maecenas ullamcorper lacus nec turpis finibus aliquet eget rutrum augue. Integer lorem erat, faucibus non lacus lacinia, pulvinar egestas felis. Proin rutrum nunc eget nulla varius, id blandit mauris tincidunt. Donec sit amet ullamcorper nisi, ut efficitur mi. Aliquam aliquet, nulla eget rhoncus tristique, justo lorem consectetur dui, id ornare leo odio sed tellus. Curabitur interdum velit a turpis condimentum venenatis. Nunc rhoncus sem ac augue auctor, nec malesuada ex fringilla. Vestibulum egestas diam sed leo consectetur vulputate quis eget enim. Nam tincidunt metus sit amet maximus ullamcorper. Sed placerat velit vitae massa efficitur viverra. Etiam eleifend dignissim ante, sed luctus nisl tristique a. In vestibulum pharetra dolor in molestie. Vivamus auctor massa ac magna imperdiet, sit amet iaculis turpis finibus.</p>
<p>Aenean dapibus vulputate purus, sit amet tempor nunc suscipit consequat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris auctor congue eros, non pellentesque neque dapibus ac. Vestibulum non leo nec urna lacinia eleifend quis et diam. Praesent eu nisi magna. Nulla at magna massa. Suspendisse porta varius scelerisque. Duis at auctor dolor, non dapibus urna. Nunc venenatis feugiat magna non molestie. Aliquam non ornare ex. Quisque eu ultrices velit, quis pellentesque eros. Phasellus eleifend, elit id imperdiet aliquam, nulla quam molestie turpis, at egestas odio ante et tortor. Suspendisse fringilla condimentum justo, at aliquet odio aliquam ac.</p> <p>Aenean dapibus vulputate purus, sit amet tempor nunc suscipit consequat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris auctor congue eros, non pellentesque neque dapibus ac. Vestibulum non leo nec urna lacinia eleifend quis et diam. Praesent eu nisi magna. Nulla at magna massa. Suspendisse porta varius scelerisque. Duis at auctor dolor, non dapibus urna. Nunc venenatis feugiat magna non molestie. Aliquam non ornare ex. Quisque eu ultrices velit, quis pellentesque eros. Phasellus eleifend, elit id imperdiet aliquam, nulla quam molestie turpis, at egestas odio ante et tortor. Suspendisse fringilla condimentum justo, at aliquet odio aliquam ac.</p>
<h2>Scene 3.2: Scene Four</h2> <h2>Scene 3.2: Scene Four</h2>
<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.</p> <div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.</p>
<p>Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo. Nullam viverra dui et auctor pretium. Ut ullamcorper velit urna, sed imperdiet massa convallis a. Suspendisse efficitur, ipsum nec cursus pulvinar, eros urna posuere diam, nec elementum mi felis vitae sapien.</p> <p>Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo. Nullam viverra dui et auctor pretium. Ut ullamcorper velit urna, sed imperdiet massa convallis a. Suspendisse efficitur, ipsum nec cursus pulvinar, eros urna posuere diam, nec elementum mi felis vitae sapien.</p>
<p>Duis efficitur metus pulvinar, molestie magna eget, feugiat dui. Fusce convallis vehicula ipsum convallis blandit. Duis eros risus, malesuada eu imperdiet in, hendrerit ac metus. Vestibulum id justo gravida, dignissim nibh non, iaculis diam. Fusce accumsan est ut massa porta ultricies. Nulla vitae justo in tortor laoreet mollis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin eu libero justo. Vivamus aliquet placerat est, et auctor eros posuere venenatis. Nunc quam diam, tincidunt ac aliquet in, fermentum sit amet lectus. Proin commodo tincidunt blandit. Quisque erat arcu, semper nec dui non, consectetur gravida ipsum. Nullam pretium consectetur elit at condimentum.</p> <p>Duis efficitur metus pulvinar, molestie magna eget, feugiat dui. Fusce convallis vehicula ipsum convallis blandit. Duis eros risus, malesuada eu imperdiet in, hendrerit ac metus. Vestibulum id justo gravida, dignissim nibh non, iaculis diam. Fusce accumsan est ut massa porta ultricies. Nulla vitae justo in tortor laoreet mollis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin eu libero justo. Vivamus aliquet placerat est, et auctor eros posuere venenatis. Nunc quam diam, tincidunt ac aliquet in, fermentum sit amet lectus. Proin commodo tincidunt blandit. Quisque erat arcu, semper nec dui non, consectetur gravida ipsum. Nullam pretium consectetur elit at condimentum.</p>
<p>Etiam sagittis, erat vitae accumsan tempor, neque augue scelerisque nulla, ut ultrices justo urna sit amet augue. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aenean at pulvinar tortor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras vel porta quam. Nullam eu mauris mollis, vehicula justo vel, placerat sapien. Phasellus viverra elit et vestibulum pharetra. Vestibulum commodo fermentum leo, eu porta nisi aliquam eget. Nulla tempus porttitor nisi nec mollis. Nam non mollis turpis. Nam finibus leo a bibendum tincidunt. Donec commodo velit magna, ac semper sapien mattis id. Proin sem velit, lobortis quis ultricies id, pharetra et lectus. Vestibulum condimentum neque vitae mi dapibus mollis. Mauris luctus vel sapien vitae hendrerit.</p> <p>Etiam sagittis, erat vitae accumsan tempor, neque augue scelerisque nulla, ut ultrices justo urna sit amet augue. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aenean at pulvinar tortor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras vel porta quam. Nullam eu mauris mollis, vehicula justo vel, placerat sapien. Phasellus viverra elit et vestibulum pharetra. Vestibulum commodo fermentum leo, eu porta nisi aliquam eget. Nulla tempus porttitor nisi nec mollis. Nam non mollis turpis. Nam finibus leo a bibendum tincidunt. Donec commodo velit magna, ac semper sapien mattis id. Proin sem velit, lobortis quis ultricies id, pharetra et lectus. Vestibulum condimentum neque vitae mi dapibus mollis. Mauris luctus vel sapien vitae hendrerit.</p>
@@ -73,7 +75,7 @@ article {width: 800px; margin: 40px auto;}
<p>Donec ipsum eros, vestibulum sit amet cursus eget, iaculis quis dolor. Pellentesque magna augue, tristique dapibus mi vitae, molestie venenatis enim. Nam malesuada, turpis volutpat rhoncus ullamcorper, justo est eleifend orci, ut luctus risus ex rutrum arcu. Sed mi elit, feugiat rhoncus ornare sed, porta id leo. Pellentesque feugiat nulla tincidunt erat suscipit, eu congue lacus hendrerit. Morbi pulvinar enim sed consequat auctor. Ut eleifend enim sem, vitae euismod ex ultricies sit amet. Curabitur eu efficitur nisi, suscipit finibus sapien. In sodales blandit erat, vestibulum pulvinar ante volutpat nec. Vivamus dictum non libero at molestie. Donec sit amet neque in ante convallis pretium. Nunc vel iaculis dui.</p> <p>Donec ipsum eros, vestibulum sit amet cursus eget, iaculis quis dolor. Pellentesque magna augue, tristique dapibus mi vitae, molestie venenatis enim. Nam malesuada, turpis volutpat rhoncus ullamcorper, justo est eleifend orci, ut luctus risus ex rutrum arcu. Sed mi elit, feugiat rhoncus ornare sed, porta id leo. Pellentesque feugiat nulla tincidunt erat suscipit, eu congue lacus hendrerit. Morbi pulvinar enim sed consequat auctor. Ut eleifend enim sem, vitae euismod ex ultricies sit amet. Curabitur eu efficitur nisi, suscipit finibus sapien. In sodales blandit erat, vestibulum pulvinar ante volutpat nec. Vivamus dictum non libero at molestie. Donec sit amet neque in ante convallis pretium. Nunc vel iaculis dui.</p>
<p>Phasellus eu nunc ut nunc faucibus laoreet. Aliquam at magna risus. Praesent lobortis, risus finibus semper varius, magna purus vestibulum eros, at pulvinar sapien enim a ex. In scelerisque malesuada ex, sit amet egestas neque condimentum sed. Praesent vulputate efficitur massa. Cras at accumsan ligula. In elementum lectus eget blandit dictum. Nam vitae libero ut justo eleifend rutrum ac nec arcu. Aliquam sodales in quam congue vestibulum. Aliquam in accumsan sapien. Quisque lobortis nisl nisi, vitae bibendum turpis efficitur sed. Vestibulum tempor nulla eget nisi convallis, blandit sagittis ipsum convallis. Donec odio nibh, ultrices quis odio in, mollis euismod libero.</p> <p>Phasellus eu nunc ut nunc faucibus laoreet. Aliquam at magna risus. Praesent lobortis, risus finibus semper varius, magna purus vestibulum eros, at pulvinar sapien enim a ex. In scelerisque malesuada ex, sit amet egestas neque condimentum sed. Praesent vulputate efficitur massa. Cras at accumsan ligula. In elementum lectus eget blandit dictum. Nam vitae libero ut justo eleifend rutrum ac nec arcu. Aliquam sodales in quam congue vestibulum. Aliquam in accumsan sapien. Quisque lobortis nisl nisi, vitae bibendum turpis efficitur sed. Vestibulum tempor nulla eget nisi convallis, blandit sagittis ipsum convallis. Donec odio nibh, ultrices quis odio in, mollis euismod libero.</p>
<h2>Scene 3.3: Scene Five</h2> <h2>Scene 3.3: Scene Five</h2>
<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p> <div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
<p>Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In sed felis auctor, rhoncus dui ac, consequat dolor. Integer volutpat libero sed nisl aliquet varius. Suspendisse et lorem sapien. Proin id ultrices nibh, ac suscipit diam. Suspendisse placerat varius porttitor. Curabitur elementum sed enim ultrices imperdiet.</p> <p>Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In sed felis auctor, rhoncus dui ac, consequat dolor. Integer volutpat libero sed nisl aliquet varius. Suspendisse et lorem sapien. Proin id ultrices nibh, ac suscipit diam. Suspendisse placerat varius porttitor. Curabitur elementum sed enim ultrices imperdiet.</p>
<p>In ut lobortis lacus, nec luctus arcu. Vivamus condimentum sapien a ipsum malesuada sodales. Donec et vestibulum risus. Integer dictum euismod eros id tincidunt. Aliquam sagittis leo vitae consequat fermentum. Donec maximus ex eu ex iaculis porta. Praesent pharetra lacinia risus, et eleifend diam commodo non. Sed feugiat ipsum ut orci sagittis, quis faucibus lectus blandit. Sed tellus quam, gravida vitae laoreet quis, tempus lobortis dui. Vivamus semper accumsan ullamcorper. Praesent tempus pretium eros, non elementum risus. Pellentesque odio quam, auctor quis ex non, vulputate egestas dolor. Nunc luctus enim ut justo sodales consectetur. Sed aliquet a mauris vel posuere.</p> <p>In ut lobortis lacus, nec luctus arcu. Vivamus condimentum sapien a ipsum malesuada sodales. Donec et vestibulum risus. Integer dictum euismod eros id tincidunt. Aliquam sagittis leo vitae consequat fermentum. Donec maximus ex eu ex iaculis porta. Praesent pharetra lacinia risus, et eleifend diam commodo non. Sed feugiat ipsum ut orci sagittis, quis faucibus lectus blandit. Sed tellus quam, gravida vitae laoreet quis, tempus lobortis dui. Vivamus semper accumsan ullamcorper. Praesent tempus pretium eros, non elementum risus. Pellentesque odio quam, auctor quis ex non, vulputate egestas dolor. Nunc luctus enim ut justo sodales consectetur. Sed aliquet a mauris vel posuere.</p>
<p>Donec luctus lectus efficitur, blandit nisi vitae, dignissim tellus. Pellentesque euismod pharetra augue gravida hendrerit. Quisque nisi mi, mattis ac nisi non, maximus malesuada ante. Nulla lobortis, diam eu ornare ornare, tellus enim feugiat arcu, non vestibulum tortor nunc eu justo. Integer blandit felis justo, eu semper est scelerisque vel. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nam ultricies, nisi vel elementum commodo, nisl dolor tincidunt magna, sed varius est nunc at lectus. Aliquam dolor tortor, sodales placerat ultricies quis, sodales quis sapien. Duis ullamcorper sollicitudin risus at mattis. Integer consequat et nunc at condimentum. Pellentesque cursus congue augue, non suscipit lectus sodales ut. Nam a mi bibendum, blandit nisl eu, accumsan nunc. Aliquam a ex mauris. Sed nec sem quis arcu dignissim tempus eget et turpis. Ut sed ex nec ipsum ultrices lobortis.</p> <p>Donec luctus lectus efficitur, blandit nisi vitae, dignissim tellus. Pellentesque euismod pharetra augue gravida hendrerit. Quisque nisi mi, mattis ac nisi non, maximus malesuada ante. Nulla lobortis, diam eu ornare ornare, tellus enim feugiat arcu, non vestibulum tortor nunc eu justo. Integer blandit felis justo, eu semper est scelerisque vel. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nam ultricies, nisi vel elementum commodo, nisl dolor tincidunt magna, sed varius est nunc at lectus. Aliquam dolor tortor, sodales placerat ultricies quis, sodales quis sapien. Duis ullamcorper sollicitudin risus at mattis. Integer consequat et nunc at condimentum. Pellentesque cursus congue augue, non suscipit lectus sodales ut. Nam a mi bibendum, blandit nisl eu, accumsan nunc. Aliquam a ex mauris. Sed nec sem quis arcu dignissim tempus eget et turpis. Ut sed ex nec ipsum ultrices lobortis.</p>
@@ -81,16 +83,16 @@ article {width: 800px; margin: 40px auto;}
<p>Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacinia. Praesent lacinia urna porttitor aliquam condimentum. Nulla eu eros dictum, dictum nunc vitae, sagittis nibh. Integer ante neque, consequat nec sollicitudin id, consectetur vitae dolor. Nullam volutpat sem orci, quis viverra magna auctor a. Suspendisse potenti. Maecenas commodo sed neque pellentesque vehicula. Sed luctus nisl risus, elementum semper purus interdum vel. Ut pulvinar, massa sit amet venenatis placerat, nunc lacus hendrerit odio, non aliquet nunc risus eu lectus. Maecenas feugiat semper ligula, id lobortis sem porta eu. Integer posuere elit magna, at mollis eros bibendum et. Ut imperdiet purus vel nulla aliquam maximus. Morbi sodales purus tellus, a rhoncus sem rutrum sit amet. Quisque risus sem, laoreet nec convallis nec, rutrum vitae justo.</p> <p>Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacinia. Praesent lacinia urna porttitor aliquam condimentum. Nulla eu eros dictum, dictum nunc vitae, sagittis nibh. Integer ante neque, consequat nec sollicitudin id, consectetur vitae dolor. Nullam volutpat sem orci, quis viverra magna auctor a. Suspendisse potenti. Maecenas commodo sed neque pellentesque vehicula. Sed luctus nisl risus, elementum semper purus interdum vel. Ut pulvinar, massa sit amet venenatis placerat, nunc lacus hendrerit odio, non aliquet nunc risus eu lectus. Maecenas feugiat semper ligula, id lobortis sem porta eu. Integer posuere elit magna, at mollis eros bibendum et. Ut imperdiet purus vel nulla aliquam maximus. Morbi sodales purus tellus, a rhoncus sem rutrum sit amet. Quisque risus sem, laoreet nec convallis nec, rutrum vitae justo.</p>
<h1 class='title' style='text-align: center; page-break-before: always;'>Notes: Characters</h1> <h1 class='title' style='text-align: center; page-break-before: always;'>Notes: Characters</h1>
<h1>Nobody Owens</h1> <h1>Nobody Owens</h1>
<div><span class='tags'>Tag:</span>&nbsp;<a name='tag_Bod'>Bod</a></div><p>Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.</p> <div><span class='tags'>Tag:</span> <a name='tag_Bod'>Bod</a></div><p>Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.</p>
<p>Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.</p> <p>Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.</p>
<p>Nullam scelerisque velit et tortor congue vestibulum a at nisi. Vivamus sodales ut turpis a convallis. In dignissim nibh at luctus sodales. Etiam sit amet rhoncus massa. Phasellus ligula magna, sollicitudin non imperdiet sit amet, volutpat vel magna. Nunc vestibulum tempor lectus, sit amet porta nunc hendrerit in. Curabitur non odio sit amet massa tincidunt facilisis. Integer et luctus nunc, eget euismod leo. Praesent faucibus metus sed purus convallis scelerisque. Fusce viverra lorem et placerat malesuada. In at elit malesuada, ullamcorper risus vitae, sodales dolor. Donec quis elementum lectus. Quisque eu eros at dui imperdiet euismod ut id neque.</p> <p>Nullam scelerisque velit et tortor congue vestibulum a at nisi. Vivamus sodales ut turpis a convallis. In dignissim nibh at luctus sodales. Etiam sit amet rhoncus massa. Phasellus ligula magna, sollicitudin non imperdiet sit amet, volutpat vel magna. Nunc vestibulum tempor lectus, sit amet porta nunc hendrerit in. Curabitur non odio sit amet massa tincidunt facilisis. Integer et luctus nunc, eget euismod leo. Praesent faucibus metus sed purus convallis scelerisque. Fusce viverra lorem et placerat malesuada. In at elit malesuada, ullamcorper risus vitae, sodales dolor. Donec quis elementum lectus. Quisque eu eros at dui imperdiet euismod ut id neque.</p>
<h1 class='title' style='text-align: center; page-break-before: always;'>Notes: Plot</h1> <h1 class='title' style='text-align: center; page-break-before: always;'>Notes: Plot</h1>
<h1>Main Plot</h1> <h1>Main Plot</h1>
<div><span class='tags'>Tag:</span>&nbsp;<a name='tag_Main'>Main</a></div><p>Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vitae ultricies felis ullamcorper eu. Duis luctus risus mi, in accumsan velit cursus ut. Vestibulum eleifend leo in magna eleifend fermentum. Proin nec ornare elit. Phasellus nec interdum risus. In a volutpat augue, quis egestas justo. Morbi porta mauris mattis bibendum imperdiet.</p> <div><span class='tags'>Tag:</span> <a name='tag_Main'>Main</a></div><p>Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vitae ultricies felis ullamcorper eu. Duis luctus risus mi, in accumsan velit cursus ut. Vestibulum eleifend leo in magna eleifend fermentum. Proin nec ornare elit. Phasellus nec interdum risus. In a volutpat augue, quis egestas justo. Morbi porta mauris mattis bibendum imperdiet.</p>
<p>Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.</p> <p>Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.</p>
<h1 class='title' style='text-align: center; page-break-before: always;'>Notes: World</h1> <h1 class='title' style='text-align: center; page-break-before: always;'>Notes: World</h1>
<h1>Ancient Europe</h1> <h1>Ancient Europe</h1>
<div><span class='tags'>Tag:</span>&nbsp;<a name='tag_Europe'>Europe</a></div><p>Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.</p> <div><span class='tags'>Tag:</span> <a name='tag_Europe'>Europe</a></div><p>Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.</p>
<p>Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.</p> <p>Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.</p>
<p>Nunc ullamcorper magna quis elit condimentum rhoncus. Aenean dictum pulvinar dolor suscipit interdum. Aliquam elit massa, elementum nec cursus eu, maximus nec ipsum. Donec ullamcorper iaculis dolor eu commodo. Nunc eget tortor quis turpis consectetur varius. Vestibulum nec justo vel tellus venenatis condimentum. Duis auctor iaculis massa. Nunc risus magna, rutrum vitae eros non, tristique mollis enim.</p> <p>Nunc ullamcorper magna quis elit condimentum rhoncus. Aenean dictum pulvinar dolor suscipit interdum. Aliquam elit massa, elementum nec cursus eu, maximus nec ipsum. Donec ullamcorper iaculis dolor eu commodo. Nunc eget tortor quis turpis consectetur varius. Vestibulum nec justo vel tellus venenatis condimentum. Duis auctor iaculis massa. Nunc risus magna, rutrum vitae eros non, tristique mollis enim.</p>
+5 -1
View File
@@ -76,7 +76,11 @@ Ut et consequat enim, quis ornare nibh. In lectus neque, mollis et suscipit et,
% Exctracted from the lipsum.com website. % Exctracted from the lipsum.com website.
It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like). It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.
The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.
Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).
## Chapter Two ## Chapter Two
-71
View File
@@ -1,71 +0,0 @@
{
"meta": {
"workingTitle": "Lorem Ipsum",
"novelTitle": "Lorem Ipsum",
"authors": [
"lipsum.com"
],
"buildTime": 1601120680
},
"text": {
"css": [
"p {text-align: justify;}",
"h1, h2 {color: rgb(66, 113, 174);}",
"h3, h4 {color: rgb(50, 50, 50);}",
"h1, h2, h3, h4 {page-break-after: avoid;}",
"a {color: rgb(66, 113, 174);}",
".title {font-size: 2.5em;}",
".tags {color: rgb(245, 135, 31); font-weight: bold;}",
".break {text-align: left;}",
".sep {text-align: center; margin-top: 1em; margin-bottom: 1em;}",
".skip {margin-top: 1em; margin-bottom: 1em;}",
".synopsis {font-style: italic;}",
".comment {font-style: italic; color: rgb(100, 100, 100);}"
],
"html": [
[
"<h1 class='title' style='text-align: center; page-break-before: never;'>Lorem Ipsum</h1>"
],
[
""
],
[
"<h1 style='page-break-before: always;'>Prologue</h1>",
"<p class='synopsis'><strong>Synopsis: </strong>Explanation from the lipsum.com website.</p>"
],
[
"<h1 style='text-align: center; page-break-before: always;'>Act One</h1>"
],
[
"<h1 style='page-break-before: always;'>Chapter One: Chapter One</h1>",
"<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.</p>"
],
[
"<h2>Scene 1: Scene One</h2>",
"<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.</p>",
"<h3>Section: Scene One, Section Two</h3>"
],
[
"<h2>Scene 2: Scene Two</h2>",
"<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.</p>",
"<h3>Section: Scene Two, Section Two</h3>"
],
[
"<h1 style='page-break-before: always;'>Chapter Two: Chapter Two</h1>",
"<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.</p>"
],
[
"<h2>Scene 3: Scene Three</h2>",
"<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</p>"
],
[
"<h2>Scene 4: Scene Four</h2>",
"<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.</p>"
],
[
"<h2>Scene 5: Scene Five</h2>",
"<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>"
]
]
}
}
+69 -15
View File
@@ -22,25 +22,79 @@ article {width: 800px; margin: 40px auto;}
<body> <body>
<article> <article>
<h1 class='title' style='text-align: center; page-break-before: never;'>Lorem Ipsum</h1> <h1 class='title' style='text-align: center; page-break-before: never;'>Lorem Ipsum</h1>
<p style='text-align: center;'><strong>By lipsum.com</strong></p>
<p style='text-align: center;'>“Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit&hellip;</p>
<p style='text-align: center;'>“There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain&hellip;</p>
<p class='comment'><strong>Comment: </strong>Exctracted from the lipsum.com website.</p>
<p style='text-align: left;'>Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of “de Finibus Bonorum et Malorum” (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, “Lorem ipsum dolor sit amet..”, comes from a line in section 1.10.32.</p>
<p style='text-align: left;'>The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from “de Finibus Bonorum et Malorum” by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.</p>
<h1 style='page-break-before: always;'>Prologue</h1> <h1 style='page-break-before: always;'>Prologue</h1>
<p class='synopsis'><strong>Synopsis: </strong>Explanation from the lipsum.com website.</p> <p class='synopsis'><strong>Synopsis: </strong>Explanation from the lipsum.com website.</p>
<p><em>Lorem Ipsum</em> is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
<h1 style='text-align: center; page-break-before: always;'>Act One</h1> <h1 style='text-align: center; page-break-before: always;'>Act One</h1>
<p style='text-align: center;'>“Fusce maximus felis libero”</p>
<h1 style='page-break-before: always;'>Chapter One: Chapter One</h1> <h1 style='page-break-before: always;'>Chapter One: Chapter One</h1>
<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.</p> <div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.</p>
<h2>Scene 1: Scene One</h2> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam. Praesent magna nunc, lacinia sit amet quam eget, aliquet ultrices justo. Morbi ornare enim et lorem rutrum finibus ut eu dolor. Aliquam a orci odio. Ut ultrices sem quis massa placerat, eget mollis nisl cursus. Cras vel sagittis justo. Ut non ultricies leo. Maecenas rutrum velit in est varius, et egestas massa pulvinar.</p>
<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.</p> <h2>Scene 1.1: Scene One</h2>
<h3>Section: Scene One, Section Two</h3> <div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.</p>
<h2>Scene 2: Scene Two</h2> <p>Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur. Donec quis ante nunc. Mauris ut leo ipsum. Vestibulum est neque, hendrerit nec neque a, ullamcorper lobortis tellus. Fusce sollicitudin purus quis congue bibendum. Aliquam condimentum ipsum tristique blandit tristique. Donec pulvinar neque ac suscipit malesuada.</p>
<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.</p> <p>Aliquam ut nisl arcu. Ut ultricies, lorem dignissim rutrum convallis, risus orci tempus lectus, congue feugiat sem lectus vitae odio. Duis sit amet justo finibus, hendrerit nulla at, ullamcorper enim. Praesent vel tellus sit amet tellus vulputate bibendum. Morbi eleifend sagittis sem, ac volutpat ante congue non. In hac habitasse platea dictumst. Morbi lobortis fermentum elit, dignissim sagittis ligula volutpat lacinia. Vestibulum eu interdum odio. Integer ac purus commodo metus congue tempor non at urna. Sed eget tortor vel quam viverra egestas. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec non convallis mauris, ac feugiat ex.</p>
<h3>Section: Scene Two, Section Two</h3> <h3>1.1.1: Scene One, Section Two</h3>
<h1 style='page-break-before: always;'>Chapter Two: Chapter Two</h1> <p>Integer vel libero ipsum. Donec varius aliquam libero, sit amet commodo urna hendrerit non. Nullam quis erat mollis nunc viverra volutpat tincidunt in odio. Nam vitae quam sem. Aliquam suscipit nulla non lorem pharetra semper. Ut suscipit erat eu ligula accumsan ultrices. Phasellus nisl tellus, placerat sed laoreet id, consectetur nec dolor. Sed fringilla ipsum id dapibus posuere. Aenean finibus pharetra tincidunt. Ut molestie malesuada nulla, id posuere lorem tincidunt eu. Aliquam tempor eros a est vulputate, scelerisque pulvinar ipsum fermentum. In hac habitasse platea dictumst.</p>
<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.</p> <p>Curabitur congue, justo quis interdum fermentum, tellus nulla imperdiet sapien, eu interdum enim tellus condimentum metus. Vivamus nunc velit, dignissim ut ultrices sit amet, ultricies quis enim. Donec ut vestibulum neque. Vivamus semper neque id ex ullamcorper varius. Fusce mattis nibh viverra lorem sagittis, et tempor arcu congue. Suspendisse sit amet felis sed urna facilisis mattis eget vitae arcu. Proin eu magna hendrerit, tristique sem maximus, placerat diam. Nulla tristique sed velit sit amet varius. Etiam vel ornare magna, in vulputate arcu. Cras velit orci, tincidunt sed volutpat cursus, bibendum vel sem. Nunc vulputate pharetra tortor, ac consectetur neque tincidunt sit amet. Nulla ornare mi sed mi dignissim ultricies. Ut tincidunt bibendum mauris, sed elementum ex vulputate vel. Mauris fermentum, felis nec vehicula congue, felis lorem facilisis erat, a dictum dolor augue vitae quam. Maecenas rutrum tortor nec consequat eleifend.</p>
<h2>Scene 3: Scene Three</h2> <h2>Scene 1.2: Scene Two</h2>
<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</p> <div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.</p>
<h2>Scene 4: Scene Four</h2> <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci. Vestibulum facilisis bibendum aliquam. Aliquam posuere, turpis ac bibendum varius, sem tellus venenatis risus, in elementum massa enim ac lorem. Integer in sem ac diam blandit ultricies ut in nulla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit amet erat est. Curabitur vitae cursus justo, sit amet placerat dolor. Vivamus eu felis hendrerit, tincidunt massa rutrum, maximus arcu. Pellentesque commodo justo odio, vel rutrum nulla tincidunt eu. Integer non neque condimentum, convallis diam non, varius ligula. Aliquam eget sapien mauris. Aenean pharetra nunc nisi, vel maximus ante tristique sit amet. Aliquam risus metus, interdum non odio eu, consectetur lacinia sapien.</p>
<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.</p> <p>Proin vitae gravida nisl. Integer viverra orci turpis, sit amet pretium ligula facilisis consequat. Nulla interdum commodo metus, mollis consequat dui tincidunt et. Proin consequat bibendum justo id commodo. Fusce fermentum nunc turpis, eu vestibulum risus feugiat ut. Sed scelerisque vel ligula ut interdum. Suspendisse ac blandit ligula, sagittis fringilla dolor. In tincidunt convallis diam et ornare. Aenean id dignissim est, ut rhoncus quam. Donec vitae nisl velit. In convallis nibh ut augue dignissim, eu elementum quam cursus. Phasellus in lectus lorem. Curabitur in pellentesque nisi, at gravida sapien. Sed cursus justo volutpat lacus placerat, sit amet dignissim turpis commodo. Aliquam vitae orci eget nulla posuere condimentum in ut felis.</p>
<h2>Scene 5: Scene Five</h2> <p>Nulla accumsan ante in pulvinar efficitur. Nulla non velit quis urna hendrerit bibendum. Suspendisse ultrices ante eu justo malesuada, sed fermentum enim rutrum. Nunc fermentum pharetra felis, vitae sollicitudin quam rutrum porta. Aliquam fringilla velit a mi laoreet, et luctus est rutrum. In gravida non ipsum sit amet tempus. Curabitur et eleifend purus. Nulla facilisi.</p>
<div><span class='tags'>Point of View:</span>&nbsp;<a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span>&nbsp;<a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span>&nbsp;<a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p> <h3>1.2.1: Scene Two, Section Two</h3>
<p>Suspendisse potenti. Fusce tempus lorem nec laoreet suscipit. Fusce vulputate nisl ac diam tincidunt, nec malesuada quam pellentesque. Maecenas congue, tellus quis commodo rutrum, magna leo egestas arcu, quis suscipit ex risus id ligula. Suspendisse potenti. Morbi blandit lacus vitae laoreet vulputate. Donec vitae tellus eleifend, lobortis eros eu, tincidunt enim. Nullam et ullamcorper nisi. Vivamus tellus ex, lobortis quis rutrum ut, dapibus sit amet turpis. Phasellus pellentesque metus diam, commodo tristique ante commodo ac. Ut mollis ipsum nec diam blandit sollicitudin. Duis bibendum lacus nec commodo dapibus. Sed condimentum luctus ante, id ultricies urna varius nec. Nam convallis magna nec bibendum ultrices. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed auctor pharetra quam, vitae porta ex bibendum eu.</p>
<p>Vivamus ut venenatis lectus. Phasellus nec elit id sem dictum ornare. Quisque feugiat, diam eget sagittis ultricies, orci turpis efficitur nisi, et fringilla justo odio nec nibh. In hac habitasse platea dictumst. Sed tempus bibendum feugiat. Etiam luctus mauris arcu, non interdum ipsum ultrices id. Vivamus blandit urna sit amet scelerisque vulputate. Quisque in metus eget massa rutrum dictum sit amet sed nulla. Vivamus vel efficitur dolor.</p>
<p>Ut et consequat enim, quis ornare nibh. In lectus neque, mollis et suscipit et, vestibulum vitae augue. Praesent id ante sit amet odio venenatis placerat a at erat. Sed sed metus sed nisi dictum varius. Integer tincidunt fermentum purus ac porta. Fusce porttitor non risus eget tristique. Donec augue nunc, maximus at fermentum vel, varius et neque. Ut sed consectetur mauris. Quisque ipsum enim, porttitor vitae imperdiet sit amet, tempor et mauris. Aliquam malesuada tincidunt lectus quis blandit. Sed commodo orci felis, quis ultrices tellus facilisis sed. Nunc vel varius est. Duis ullamcorper eu metus in pulvinar. Morbi at sapien dictum, rutrum mauris eget, interdum tellus.</p>
<h1 style='page-break-before: always;'>Chapter Two: Why do we use it?</h1>
<p class='comment'><strong>Comment: </strong>Exctracted from the lipsum.com website.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).</p>
<h1 style='page-break-before: always;'>Chapter Three: Chapter Two</h1>
<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.</p>
<p>Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue. Vestibulum pharetra fermentum leo, sed faucibus eros placerat quis. In hac habitasse platea dictumst. Donec metus massa, rutrum quis consequat et, tincidunt ac felis. Duis mollis metus ac nunc tincidunt blandit. Ut aliquet velit eu odio pharetra condimentum. Integer rutrum lacus orci, id venenatis libero accumsan at.</p>
<h2>Scene 3.1: Scene Three</h2>
<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</p>
<p>Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean tincidunt lacus vitae nibh elementum eleifend. Sed rutrum condimentum sem quis blandit. Duis imperdiet libero metus, quis convallis quam faucibus a. Nulla ligula est, semper quis sollicitudin et, pretium id justo. Curabitur pharetra risus eget consectetur commodo. Duis mattis arcu non est condimentum, id venenatis risus volutpat. Pellentesque aliquet mauris non mauris porttitor ultrices. Phasellus ut vestibulum mi. Suspendisse malesuada metus lorem, a malesuada orci rhoncus a. Praesent euismod convallis ante, lacinia tincidunt ex egestas id. Praesent sit amet efficitur sapien. Morbi tincidunt volutpat nunc sed dictum. Aliquam ultrices metus id fermentum lobortis.</p>
<p>Pellentesque id sagittis dui. Praesent ut nisi sit amet libero euismod ornare. Vestibulum vehicula, lorem eget aliquet imperdiet, eros nulla iaculis mi, vel bibendum est dui sed orci. Nullam vitae lorem rutrum, euismod lacus id, ullamcorper lectus. Duis nec commodo mi, a fringilla diam. Vestibulum molestie nibh tristique, viverra augue non, aliquet metus. Phasellus a tellus ac nisl tempor aliquet. Nulla vitae sapien rutrum augue ornare ultrices a quis nisi. Sed pulvinar tincidunt ex. Fusce vel sem vitae ante pellentesque lobortis.</p>
<p>Maecenas ullamcorper lacus nec turpis finibus aliquet eget rutrum augue. Integer lorem erat, faucibus non lacus lacinia, pulvinar egestas felis. Proin rutrum nunc eget nulla varius, id blandit mauris tincidunt. Donec sit amet ullamcorper nisi, ut efficitur mi. Aliquam aliquet, nulla eget rhoncus tristique, justo lorem consectetur dui, id ornare leo odio sed tellus. Curabitur interdum velit a turpis condimentum venenatis. Nunc rhoncus sem ac augue auctor, nec malesuada ex fringilla. Vestibulum egestas diam sed leo consectetur vulputate quis eget enim. Nam tincidunt metus sit amet maximus ullamcorper. Sed placerat velit vitae massa efficitur viverra. Etiam eleifend dignissim ante, sed luctus nisl tristique a. In vestibulum pharetra dolor in molestie. Vivamus auctor massa ac magna imperdiet, sit amet iaculis turpis finibus.</p>
<p>Aenean dapibus vulputate purus, sit amet tempor nunc suscipit consequat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris auctor congue eros, non pellentesque neque dapibus ac. Vestibulum non leo nec urna lacinia eleifend quis et diam. Praesent eu nisi magna. Nulla at magna massa. Suspendisse porta varius scelerisque. Duis at auctor dolor, non dapibus urna. Nunc venenatis feugiat magna non molestie. Aliquam non ornare ex. Quisque eu ultrices velit, quis pellentesque eros. Phasellus eleifend, elit id imperdiet aliquam, nulla quam molestie turpis, at egestas odio ante et tortor. Suspendisse fringilla condimentum justo, at aliquet odio aliquam ac.</p>
<h2>Scene 3.2: Scene Four</h2>
<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.</p>
<p>Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo. Nullam viverra dui et auctor pretium. Ut ullamcorper velit urna, sed imperdiet massa convallis a. Suspendisse efficitur, ipsum nec cursus pulvinar, eros urna posuere diam, nec elementum mi felis vitae sapien.</p>
<p>Duis efficitur metus pulvinar, molestie magna eget, feugiat dui. Fusce convallis vehicula ipsum convallis blandit. Duis eros risus, malesuada eu imperdiet in, hendrerit ac metus. Vestibulum id justo gravida, dignissim nibh non, iaculis diam. Fusce accumsan est ut massa porta ultricies. Nulla vitae justo in tortor laoreet mollis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin eu libero justo. Vivamus aliquet placerat est, et auctor eros posuere venenatis. Nunc quam diam, tincidunt ac aliquet in, fermentum sit amet lectus. Proin commodo tincidunt blandit. Quisque erat arcu, semper nec dui non, consectetur gravida ipsum. Nullam pretium consectetur elit at condimentum.</p>
<p>Etiam sagittis, erat vitae accumsan tempor, neque augue scelerisque nulla, ut ultrices justo urna sit amet augue. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aenean at pulvinar tortor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras vel porta quam. Nullam eu mauris mollis, vehicula justo vel, placerat sapien. Phasellus viverra elit et vestibulum pharetra. Vestibulum commodo fermentum leo, eu porta nisi aliquam eget. Nulla tempus porttitor nisi nec mollis. Nam non mollis turpis. Nam finibus leo a bibendum tincidunt. Donec commodo velit magna, ac semper sapien mattis id. Proin sem velit, lobortis quis ultricies id, pharetra et lectus. Vestibulum condimentum neque vitae mi dapibus mollis. Mauris luctus vel sapien vitae hendrerit.</p>
<p>Aenean vestibulum magna placerat fermentum tempus. Nam auctor condimentum nunc, in elementum quam ornare a. Etiam in ipsum elit. Proin pharetra, dolor sollicitudin pellentesque congue, lorem dolor ultricies magna, non iaculis risus nisl dictum diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Vivamus vel euismod nibh, et lobortis dolor. Maecenas dui odio, gravida nec molestie ut, feugiat ut arcu. Pellentesque risus sapien, gravida a convallis quis, ullamcorper porttitor sapien.</p>
<p>Donec ipsum eros, vestibulum sit amet cursus eget, iaculis quis dolor. Pellentesque magna augue, tristique dapibus mi vitae, molestie venenatis enim. Nam malesuada, turpis volutpat rhoncus ullamcorper, justo est eleifend orci, ut luctus risus ex rutrum arcu. Sed mi elit, feugiat rhoncus ornare sed, porta id leo. Pellentesque feugiat nulla tincidunt erat suscipit, eu congue lacus hendrerit. Morbi pulvinar enim sed consequat auctor. Ut eleifend enim sem, vitae euismod ex ultricies sit amet. Curabitur eu efficitur nisi, suscipit finibus sapien. In sodales blandit erat, vestibulum pulvinar ante volutpat nec. Vivamus dictum non libero at molestie. Donec sit amet neque in ante convallis pretium. Nunc vel iaculis dui.</p>
<p>Phasellus eu nunc ut nunc faucibus laoreet. Aliquam at magna risus. Praesent lobortis, risus finibus semper varius, magna purus vestibulum eros, at pulvinar sapien enim a ex. In scelerisque malesuada ex, sit amet egestas neque condimentum sed. Praesent vulputate efficitur massa. Cras at accumsan ligula. In elementum lectus eget blandit dictum. Nam vitae libero ut justo eleifend rutrum ac nec arcu. Aliquam sodales in quam congue vestibulum. Aliquam in accumsan sapien. Quisque lobortis nisl nisi, vitae bibendum turpis efficitur sed. Vestibulum tempor nulla eget nisi convallis, blandit sagittis ipsum convallis. Donec odio nibh, ultrices quis odio in, mollis euismod libero.</p>
<h2>Scene 3.3: Scene Five</h2>
<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
<p>Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In sed felis auctor, rhoncus dui ac, consequat dolor. Integer volutpat libero sed nisl aliquet varius. Suspendisse et lorem sapien. Proin id ultrices nibh, ac suscipit diam. Suspendisse placerat varius porttitor. Curabitur elementum sed enim ultrices imperdiet.</p>
<p>In ut lobortis lacus, nec luctus arcu. Vivamus condimentum sapien a ipsum malesuada sodales. Donec et vestibulum risus. Integer dictum euismod eros id tincidunt. Aliquam sagittis leo vitae consequat fermentum. Donec maximus ex eu ex iaculis porta. Praesent pharetra lacinia risus, et eleifend diam commodo non. Sed feugiat ipsum ut orci sagittis, quis faucibus lectus blandit. Sed tellus quam, gravida vitae laoreet quis, tempus lobortis dui. Vivamus semper accumsan ullamcorper. Praesent tempus pretium eros, non elementum risus. Pellentesque odio quam, auctor quis ex non, vulputate egestas dolor. Nunc luctus enim ut justo sodales consectetur. Sed aliquet a mauris vel posuere.</p>
<p>Donec luctus lectus efficitur, blandit nisi vitae, dignissim tellus. Pellentesque euismod pharetra augue gravida hendrerit. Quisque nisi mi, mattis ac nisi non, maximus malesuada ante. Nulla lobortis, diam eu ornare ornare, tellus enim feugiat arcu, non vestibulum tortor nunc eu justo. Integer blandit felis justo, eu semper est scelerisque vel. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nam ultricies, nisi vel elementum commodo, nisl dolor tincidunt magna, sed varius est nunc at lectus. Aliquam dolor tortor, sodales placerat ultricies quis, sodales quis sapien. Duis ullamcorper sollicitudin risus at mattis. Integer consequat et nunc at condimentum. Pellentesque cursus congue augue, non suscipit lectus sodales ut. Nam a mi bibendum, blandit nisl eu, accumsan nunc. Aliquam a ex mauris. Sed nec sem quis arcu dignissim tempus eget et turpis. Ut sed ex nec ipsum ultrices lobortis.</p>
<p>Pellentesque rhoncus pharetra eros, non mollis nisi pretium non. Mauris accumsan quis odio quis euismod. Maecenas ultrices, augue et aliquam tincidunt, erat tellus ornare ligula, quis ultrices turpis nibh vel justo. Fusce gravida odio tellus. In a congue diam. Mauris consequat ex id leo lacinia dictum. Fusce id sem sodales, ultrices sapien ac, convallis orci. Donec gravida nunc sit amet nisi hendrerit, sed porta enim aliquam. In hac habitasse platea dictumst. Cras a orci felis. Curabitur non felis nec urna maximus auctor ut ut nisi. Curabitur at turpis eleifend, blandit eros at, molestie odio. Phasellus euismod neque augue.</p>
<p>Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacinia. Praesent lacinia urna porttitor aliquam condimentum. Nulla eu eros dictum, dictum nunc vitae, sagittis nibh. Integer ante neque, consequat nec sollicitudin id, consectetur vitae dolor. Nullam volutpat sem orci, quis viverra magna auctor a. Suspendisse potenti. Maecenas commodo sed neque pellentesque vehicula. Sed luctus nisl risus, elementum semper purus interdum vel. Ut pulvinar, massa sit amet venenatis placerat, nunc lacus hendrerit odio, non aliquet nunc risus eu lectus. Maecenas feugiat semper ligula, id lobortis sem porta eu. Integer posuere elit magna, at mollis eros bibendum et. Ut imperdiet purus vel nulla aliquam maximus. Morbi sodales purus tellus, a rhoncus sem rutrum sit amet. Quisque risus sem, laoreet nec convallis nec, rutrum vitae justo.</p>
<h1 class='title' style='text-align: center; page-break-before: always;'>Notes: Characters</h1>
<h1>Nobody Owens</h1>
<div><span class='tags'>Tag:</span> <a name='tag_Bod'>Bod</a></div><p>Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.</p>
<p>Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.</p>
<p>Nullam scelerisque velit et tortor congue vestibulum a at nisi. Vivamus sodales ut turpis a convallis. In dignissim nibh at luctus sodales. Etiam sit amet rhoncus massa. Phasellus ligula magna, sollicitudin non imperdiet sit amet, volutpat vel magna. Nunc vestibulum tempor lectus, sit amet porta nunc hendrerit in. Curabitur non odio sit amet massa tincidunt facilisis. Integer et luctus nunc, eget euismod leo. Praesent faucibus metus sed purus convallis scelerisque. Fusce viverra lorem et placerat malesuada. In at elit malesuada, ullamcorper risus vitae, sodales dolor. Donec quis elementum lectus. Quisque eu eros at dui imperdiet euismod ut id neque.</p>
<h1 class='title' style='text-align: center; page-break-before: always;'>Notes: Plot</h1>
<h1>Main Plot</h1>
<div><span class='tags'>Tag:</span> <a name='tag_Main'>Main</a></div><p>Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vitae ultricies felis ullamcorper eu. Duis luctus risus mi, in accumsan velit cursus ut. Vestibulum eleifend leo in magna eleifend fermentum. Proin nec ornare elit. Phasellus nec interdum risus. In a volutpat augue, quis egestas justo. Morbi porta mauris mattis bibendum imperdiet.</p>
<p>Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.</p>
<h1 class='title' style='text-align: center; page-break-before: always;'>Notes: World</h1>
<h1>Ancient Europe</h1>
<div><span class='tags'>Tag:</span> <a name='tag_Europe'>Europe</a></div><p>Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.</p>
<p>Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.</p>
<p>Nunc ullamcorper magna quis elit condimentum rhoncus. Aenean dictum pulvinar dolor suscipit interdum. Aliquam elit massa, elementum nec cursus eu, maximus nec ipsum. Donec ullamcorper iaculis dolor eu commodo. Nunc eget tortor quis turpis consectetur varius. Vestibulum nec justo vel tellus venenatis condimentum. Duis auctor iaculis massa. Nunc risus magna, rutrum vitae eros non, tristique mollis enim.</p>
</article> </article>
</body> </body>
+79
View File
@@ -1,18 +1,26 @@
# Lorem Ipsum # Lorem Ipsum
**By lipsum.com**
“Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit…”
“There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain…”
% Exctracted from the lipsum.com website.
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of “de Finibus Bonorum et Malorum” (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, “Lorem ipsum dolor sit amet..”, comes from a line in section 1.10.32.
The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from “de Finibus Bonorum et Malorum” by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.
## Prologue ## Prologue
% Synopsis:Explanation from the lipsum.com website. % Synopsis:Explanation from the lipsum.com website.
_Lorem Ipsum_ is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
# Act One # Act One
“Fusce maximus felis libero”
## Chapter One ## Chapter One
@@ -22,6 +30,7 @@
% Synopsis: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam. % Synopsis: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam. Praesent magna nunc, lacinia sit amet quam eget, aliquet ultrices justo. Morbi ornare enim et lorem rutrum finibus ut eu dolor. Aliquam a orci odio. Ut ultrices sem quis massa placerat, eget mollis nisl cursus. Cras vel sagittis justo. Ut non ultricies leo. Maecenas rutrum velit in est varius, et egestas massa pulvinar.
### Scene One ### Scene One
@@ -31,11 +40,15 @@
% Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur. % Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.
Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur. Donec quis ante nunc. Mauris ut leo ipsum. Vestibulum est neque, hendrerit nec neque a, ullamcorper lobortis tellus. Fusce sollicitudin purus quis congue bibendum. Aliquam condimentum ipsum tristique blandit tristique. Donec pulvinar neque ac suscipit malesuada.
Aliquam ut nisl arcu. Ut ultricies, lorem dignissim rutrum convallis, risus orci tempus lectus, congue feugiat sem lectus vitae odio. Duis sit amet justo finibus, hendrerit nulla at, ullamcorper enim. Praesent vel tellus sit amet tellus vulputate bibendum. Morbi eleifend sagittis sem, ac volutpat ante congue non. In hac habitasse platea dictumst. Morbi lobortis fermentum elit, dignissim sagittis ligula volutpat lacinia. Vestibulum eu interdum odio. Integer ac purus commodo metus congue tempor non at urna. Sed eget tortor vel quam viverra egestas. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec non convallis mauris, ac feugiat ex.
#### Scene One, Section Two #### Scene One, Section Two
Integer vel libero ipsum. Donec varius aliquam libero, sit amet commodo urna hendrerit non. Nullam quis erat mollis nunc viverra volutpat tincidunt in odio. Nam vitae quam sem. Aliquam suscipit nulla non lorem pharetra semper. Ut suscipit erat eu ligula accumsan ultrices. Phasellus nisl tellus, placerat sed laoreet id, consectetur nec dolor. Sed fringilla ipsum id dapibus posuere. Aenean finibus pharetra tincidunt. Ut molestie malesuada nulla, id posuere lorem tincidunt eu. Aliquam tempor eros a est vulputate, scelerisque pulvinar ipsum fermentum. In hac habitasse platea dictumst.
Curabitur congue, justo quis interdum fermentum, tellus nulla imperdiet sapien, eu interdum enim tellus condimentum metus. Vivamus nunc velit, dignissim ut ultrices sit amet, ultricies quis enim. Donec ut vestibulum neque. Vivamus semper neque id ex ullamcorper varius. Fusce mattis nibh viverra lorem sagittis, et tempor arcu congue. Suspendisse sit amet felis sed urna facilisis mattis eget vitae arcu. Proin eu magna hendrerit, tristique sem maximus, placerat diam. Nulla tristique sed velit sit amet varius. Etiam vel ornare magna, in vulputate arcu. Cras velit orci, tincidunt sed volutpat cursus, bibendum vel sem. Nunc vulputate pharetra tortor, ac consectetur neque tincidunt sit amet. Nulla ornare mi sed mi dignissim ultricies. Ut tincidunt bibendum mauris, sed elementum ex vulputate vel. Mauris fermentum, felis nec vehicula congue, felis lorem facilisis erat, a dictum dolor augue vitae quam. Maecenas rutrum tortor nec consequat eleifend.
### Scene Two ### Scene Two
@@ -45,13 +58,29 @@
% Synopsis: Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci. % Synopsis: Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci. Vestibulum facilisis bibendum aliquam. Aliquam posuere, turpis ac bibendum varius, sem tellus venenatis risus, in elementum massa enim ac lorem. Integer in sem ac diam blandit ultricies ut in nulla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit amet erat est. Curabitur vitae cursus justo, sit amet placerat dolor. Vivamus eu felis hendrerit, tincidunt massa rutrum, maximus arcu. Pellentesque commodo justo odio, vel rutrum nulla tincidunt eu. Integer non neque condimentum, convallis diam non, varius ligula. Aliquam eget sapien mauris. Aenean pharetra nunc nisi, vel maximus ante tristique sit amet. Aliquam risus metus, interdum non odio eu, consectetur lacinia sapien.
Proin vitae gravida nisl. Integer viverra orci turpis, sit amet pretium ligula facilisis consequat. Nulla interdum commodo metus, mollis consequat dui tincidunt et. Proin consequat bibendum justo id commodo. Fusce fermentum nunc turpis, eu vestibulum risus feugiat ut. Sed scelerisque vel ligula ut interdum. Suspendisse ac blandit ligula, sagittis fringilla dolor. In tincidunt convallis diam et ornare. Aenean id dignissim est, ut rhoncus quam. Donec vitae nisl velit. In convallis nibh ut augue dignissim, eu elementum quam cursus. Phasellus in lectus lorem. Curabitur in pellentesque nisi, at gravida sapien. Sed cursus justo volutpat lacus placerat, sit amet dignissim turpis commodo. Aliquam vitae orci eget nulla posuere condimentum in ut felis.
Nulla accumsan ante in pulvinar efficitur. Nulla non velit quis urna hendrerit bibendum. Suspendisse ultrices ante eu justo malesuada, sed fermentum enim rutrum. Nunc fermentum pharetra felis, vitae sollicitudin quam rutrum porta. Aliquam fringilla velit a mi laoreet, et luctus est rutrum. In gravida non ipsum sit amet tempus. Curabitur et eleifend purus. Nulla facilisi.
#### Scene Two, Section Two #### Scene Two, Section Two
Suspendisse potenti. Fusce tempus lorem nec laoreet suscipit. Fusce vulputate nisl ac diam tincidunt, nec malesuada quam pellentesque. Maecenas congue, tellus quis commodo rutrum, magna leo egestas arcu, quis suscipit ex risus id ligula. Suspendisse potenti. Morbi blandit lacus vitae laoreet vulputate. Donec vitae tellus eleifend, lobortis eros eu, tincidunt enim. Nullam et ullamcorper nisi. Vivamus tellus ex, lobortis quis rutrum ut, dapibus sit amet turpis. Phasellus pellentesque metus diam, commodo tristique ante commodo ac. Ut mollis ipsum nec diam blandit sollicitudin. Duis bibendum lacus nec commodo dapibus. Sed condimentum luctus ante, id ultricies urna varius nec. Nam convallis magna nec bibendum ultrices. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed auctor pharetra quam, vitae porta ex bibendum eu.
Vivamus ut venenatis lectus. Phasellus nec elit id sem dictum ornare. Quisque feugiat, diam eget sagittis ultricies, orci turpis efficitur nisi, et fringilla justo odio nec nibh. In hac habitasse platea dictumst. Sed tempus bibendum feugiat. Etiam luctus mauris arcu, non interdum ipsum ultrices id. Vivamus blandit urna sit amet scelerisque vulputate. Quisque in metus eget massa rutrum dictum sit amet sed nulla. Vivamus vel efficitur dolor.
Ut et consequat enim, quis ornare nibh. In lectus neque, mollis et suscipit et, vestibulum vitae augue. Praesent id ante sit amet odio venenatis placerat a at erat. Sed sed metus sed nisi dictum varius. Integer tincidunt fermentum purus ac porta. Fusce porttitor non risus eget tristique. Donec augue nunc, maximus at fermentum vel, varius et neque. Ut sed consectetur mauris. Quisque ipsum enim, porttitor vitae imperdiet sit amet, tempor et mauris. Aliquam malesuada tincidunt lectus quis blandit. Sed commodo orci felis, quis ultrices tellus facilisis sed. Nunc vel varius est. Duis ullamcorper eu metus in pulvinar. Morbi at sapien dictum, rutrum mauris eget, interdum tellus.
## Why do we use it?
% Exctracted from the lipsum.com website.
It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.
The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.
Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).
## Chapter Two ## Chapter Two
@@ -61,6 +90,7 @@
% Synopsis: Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue. % Synopsis: Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.
Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue. Vestibulum pharetra fermentum leo, sed faucibus eros placerat quis. In hac habitasse platea dictumst. Donec metus massa, rutrum quis consequat et, tincidunt ac felis. Duis mollis metus ac nunc tincidunt blandit. Ut aliquet velit eu odio pharetra condimentum. Integer rutrum lacus orci, id venenatis libero accumsan at.
### Scene Three ### Scene Three
@@ -70,9 +100,13 @@
% Synopsis: Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. % Synopsis: Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean tincidunt lacus vitae nibh elementum eleifend. Sed rutrum condimentum sem quis blandit. Duis imperdiet libero metus, quis convallis quam faucibus a. Nulla ligula est, semper quis sollicitudin et, pretium id justo. Curabitur pharetra risus eget consectetur commodo. Duis mattis arcu non est condimentum, id venenatis risus volutpat. Pellentesque aliquet mauris non mauris porttitor ultrices. Phasellus ut vestibulum mi. Suspendisse malesuada metus lorem, a malesuada orci rhoncus a. Praesent euismod convallis ante, lacinia tincidunt ex egestas id. Praesent sit amet efficitur sapien. Morbi tincidunt volutpat nunc sed dictum. Aliquam ultrices metus id fermentum lobortis.
Pellentesque id sagittis dui. Praesent ut nisi sit amet libero euismod ornare. Vestibulum vehicula, lorem eget aliquet imperdiet, eros nulla iaculis mi, vel bibendum est dui sed orci. Nullam vitae lorem rutrum, euismod lacus id, ullamcorper lectus. Duis nec commodo mi, a fringilla diam. Vestibulum molestie nibh tristique, viverra augue non, aliquet metus. Phasellus a tellus ac nisl tempor aliquet. Nulla vitae sapien rutrum augue ornare ultrices a quis nisi. Sed pulvinar tincidunt ex. Fusce vel sem vitae ante pellentesque lobortis.
Maecenas ullamcorper lacus nec turpis finibus aliquet eget rutrum augue. Integer lorem erat, faucibus non lacus lacinia, pulvinar egestas felis. Proin rutrum nunc eget nulla varius, id blandit mauris tincidunt. Donec sit amet ullamcorper nisi, ut efficitur mi. Aliquam aliquet, nulla eget rhoncus tristique, justo lorem consectetur dui, id ornare leo odio sed tellus. Curabitur interdum velit a turpis condimentum venenatis. Nunc rhoncus sem ac augue auctor, nec malesuada ex fringilla. Vestibulum egestas diam sed leo consectetur vulputate quis eget enim. Nam tincidunt metus sit amet maximus ullamcorper. Sed placerat velit vitae massa efficitur viverra. Etiam eleifend dignissim ante, sed luctus nisl tristique a. In vestibulum pharetra dolor in molestie. Vivamus auctor massa ac magna imperdiet, sit amet iaculis turpis finibus.
Aenean dapibus vulputate purus, sit amet tempor nunc suscipit consequat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris auctor congue eros, non pellentesque neque dapibus ac. Vestibulum non leo nec urna lacinia eleifend quis et diam. Praesent eu nisi magna. Nulla at magna massa. Suspendisse porta varius scelerisque. Duis at auctor dolor, non dapibus urna. Nunc venenatis feugiat magna non molestie. Aliquam non ornare ex. Quisque eu ultrices velit, quis pellentesque eros. Phasellus eleifend, elit id imperdiet aliquam, nulla quam molestie turpis, at egestas odio ante et tortor. Suspendisse fringilla condimentum justo, at aliquet odio aliquam ac.
### Scene Four ### Scene Four
@@ -82,11 +116,17 @@
% Synopsis: Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo. % Synopsis: Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.
Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo. Nullam viverra dui et auctor pretium. Ut ullamcorper velit urna, sed imperdiet massa convallis a. Suspendisse efficitur, ipsum nec cursus pulvinar, eros urna posuere diam, nec elementum mi felis vitae sapien.
Duis efficitur metus pulvinar, molestie magna eget, feugiat dui. Fusce convallis vehicula ipsum convallis blandit. Duis eros risus, malesuada eu imperdiet in, hendrerit ac metus. Vestibulum id justo gravida, dignissim nibh non, iaculis diam. Fusce accumsan est ut massa porta ultricies. Nulla vitae justo in tortor laoreet mollis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin eu libero justo. Vivamus aliquet placerat est, et auctor eros posuere venenatis. Nunc quam diam, tincidunt ac aliquet in, fermentum sit amet lectus. Proin commodo tincidunt blandit. Quisque erat arcu, semper nec dui non, consectetur gravida ipsum. Nullam pretium consectetur elit at condimentum.
Etiam sagittis, erat vitae accumsan tempor, neque augue scelerisque nulla, ut ultrices justo urna sit amet augue. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aenean at pulvinar tortor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras vel porta quam. Nullam eu mauris mollis, vehicula justo vel, placerat sapien. Phasellus viverra elit et vestibulum pharetra. Vestibulum commodo fermentum leo, eu porta nisi aliquam eget. Nulla tempus porttitor nisi nec mollis. Nam non mollis turpis. Nam finibus leo a bibendum tincidunt. Donec commodo velit magna, ac semper sapien mattis id. Proin sem velit, lobortis quis ultricies id, pharetra et lectus. Vestibulum condimentum neque vitae mi dapibus mollis. Mauris luctus vel sapien vitae hendrerit.
Aenean vestibulum magna placerat fermentum tempus. Nam auctor condimentum nunc, in elementum quam ornare a. Etiam in ipsum elit. Proin pharetra, dolor sollicitudin pellentesque congue, lorem dolor ultricies magna, non iaculis risus nisl dictum diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Vivamus vel euismod nibh, et lobortis dolor. Maecenas dui odio, gravida nec molestie ut, feugiat ut arcu. Pellentesque risus sapien, gravida a convallis quis, ullamcorper porttitor sapien.
Donec ipsum eros, vestibulum sit amet cursus eget, iaculis quis dolor. Pellentesque magna augue, tristique dapibus mi vitae, molestie venenatis enim. Nam malesuada, turpis volutpat rhoncus ullamcorper, justo est eleifend orci, ut luctus risus ex rutrum arcu. Sed mi elit, feugiat rhoncus ornare sed, porta id leo. Pellentesque feugiat nulla tincidunt erat suscipit, eu congue lacus hendrerit. Morbi pulvinar enim sed consequat auctor. Ut eleifend enim sem, vitae euismod ex ultricies sit amet. Curabitur eu efficitur nisi, suscipit finibus sapien. In sodales blandit erat, vestibulum pulvinar ante volutpat nec. Vivamus dictum non libero at molestie. Donec sit amet neque in ante convallis pretium. Nunc vel iaculis dui.
Phasellus eu nunc ut nunc faucibus laoreet. Aliquam at magna risus. Praesent lobortis, risus finibus semper varius, magna purus vestibulum eros, at pulvinar sapien enim a ex. In scelerisque malesuada ex, sit amet egestas neque condimentum sed. Praesent vulputate efficitur massa. Cras at accumsan ligula. In elementum lectus eget blandit dictum. Nam vitae libero ut justo eleifend rutrum ac nec arcu. Aliquam sodales in quam congue vestibulum. Aliquam in accumsan sapien. Quisque lobortis nisl nisi, vitae bibendum turpis efficitur sed. Vestibulum tempor nulla eget nisi convallis, blandit sagittis ipsum convallis. Donec odio nibh, ultrices quis odio in, mollis euismod libero.
### Scene Five ### Scene Five
@@ -96,8 +136,47 @@
% Synopsis: Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. % Synopsis: Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In sed felis auctor, rhoncus dui ac, consequat dolor. Integer volutpat libero sed nisl aliquet varius. Suspendisse et lorem sapien. Proin id ultrices nibh, ac suscipit diam. Suspendisse placerat varius porttitor. Curabitur elementum sed enim ultrices imperdiet.
In ut lobortis lacus, nec luctus arcu. Vivamus condimentum sapien a ipsum malesuada sodales. Donec et vestibulum risus. Integer dictum euismod eros id tincidunt. Aliquam sagittis leo vitae consequat fermentum. Donec maximus ex eu ex iaculis porta. Praesent pharetra lacinia risus, et eleifend diam commodo non. Sed feugiat ipsum ut orci sagittis, quis faucibus lectus blandit. Sed tellus quam, gravida vitae laoreet quis, tempus lobortis dui. Vivamus semper accumsan ullamcorper. Praesent tempus pretium eros, non elementum risus. Pellentesque odio quam, auctor quis ex non, vulputate egestas dolor. Nunc luctus enim ut justo sodales consectetur. Sed aliquet a mauris vel posuere.
Donec luctus lectus efficitur, blandit nisi vitae, dignissim tellus. Pellentesque euismod pharetra augue gravida hendrerit. Quisque nisi mi, mattis ac nisi non, maximus malesuada ante. Nulla lobortis, diam eu ornare ornare, tellus enim feugiat arcu, non vestibulum tortor nunc eu justo. Integer blandit felis justo, eu semper est scelerisque vel. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nam ultricies, nisi vel elementum commodo, nisl dolor tincidunt magna, sed varius est nunc at lectus. Aliquam dolor tortor, sodales placerat ultricies quis, sodales quis sapien. Duis ullamcorper sollicitudin risus at mattis. Integer consequat et nunc at condimentum. Pellentesque cursus congue augue, non suscipit lectus sodales ut. Nam a mi bibendum, blandit nisl eu, accumsan nunc. Aliquam a ex mauris. Sed nec sem quis arcu dignissim tempus eget et turpis. Ut sed ex nec ipsum ultrices lobortis.
Pellentesque rhoncus pharetra eros, non mollis nisi pretium non. Mauris accumsan quis odio quis euismod. Maecenas ultrices, augue et aliquam tincidunt, erat tellus ornare ligula, quis ultrices turpis nibh vel justo. Fusce gravida odio tellus. In a congue diam. Mauris consequat ex id leo lacinia dictum. Fusce id sem sodales, ultrices sapien ac, convallis orci. Donec gravida nunc sit amet nisi hendrerit, sed porta enim aliquam. In hac habitasse platea dictumst. Cras a orci felis. Curabitur non felis nec urna maximus auctor ut ut nisi. Curabitur at turpis eleifend, blandit eros at, molestie odio. Phasellus euismod neque augue.
Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacinia. Praesent lacinia urna porttitor aliquam condimentum. Nulla eu eros dictum, dictum nunc vitae, sagittis nibh. Integer ante neque, consequat nec sollicitudin id, consectetur vitae dolor. Nullam volutpat sem orci, quis viverra magna auctor a. Suspendisse potenti. Maecenas commodo sed neque pellentesque vehicula. Sed luctus nisl risus, elementum semper purus interdum vel. Ut pulvinar, massa sit amet venenatis placerat, nunc lacus hendrerit odio, non aliquet nunc risus eu lectus. Maecenas feugiat semper ligula, id lobortis sem porta eu. Integer posuere elit magna, at mollis eros bibendum et. Ut imperdiet purus vel nulla aliquam maximus. Morbi sodales purus tellus, a rhoncus sem rutrum sit amet. Quisque risus sem, laoreet nec convallis nec, rutrum vitae justo.
# Notes: Characters
# Nobody Owens
@tag: Bod
Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.
Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.
Nullam scelerisque velit et tortor congue vestibulum a at nisi. Vivamus sodales ut turpis a convallis. In dignissim nibh at luctus sodales. Etiam sit amet rhoncus massa. Phasellus ligula magna, sollicitudin non imperdiet sit amet, volutpat vel magna. Nunc vestibulum tempor lectus, sit amet porta nunc hendrerit in. Curabitur non odio sit amet massa tincidunt facilisis. Integer et luctus nunc, eget euismod leo. Praesent faucibus metus sed purus convallis scelerisque. Fusce viverra lorem et placerat malesuada. In at elit malesuada, ullamcorper risus vitae, sodales dolor. Donec quis elementum lectus. Quisque eu eros at dui imperdiet euismod ut id neque.
# Notes: Plot
# Main Plot
@tag: Main
Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vitae ultricies felis ullamcorper eu. Duis luctus risus mi, in accumsan velit cursus ut. Vestibulum eleifend leo in magna eleifend fermentum. Proin nec ornare elit. Phasellus nec interdum risus. In a volutpat augue, quis egestas justo. Morbi porta mauris mattis bibendum imperdiet.
Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.
# Notes: World
# Ancient Europe
@tag: Europe
Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.
Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.
Nunc ullamcorper magna quis elit condimentum rhoncus. Aenean dictum pulvinar dolor suscipit interdum. Aliquam elit massa, elementum nec cursus eu, maximus nec ipsum. Donec ullamcorper iaculis dolor eu commodo. Nunc eget tortor quis turpis consectetur varius. Vestibulum nec justo vel tellus venenatis condimentum. Duis auctor iaculis massa. Nunc risus magna, rutrum vitae eros non, tristique mollis enim.
+71
View File
@@ -0,0 +1,71 @@
{
"meta": {
"workingTitle": "Lorem Ipsum",
"novelTitle": "Lorem Ipsum",
"authors": [
"lipsum.com"
],
"buildTime": 1601829988
},
"text": {
"css": [
"p {text-align: justify;}",
"h1, h2 {color: rgb(66, 113, 174);}",
"h3, h4 {color: rgb(50, 50, 50);}",
"h1, h2, h3, h4 {page-break-after: avoid;}",
"a {color: rgb(66, 113, 174);}",
".title {font-size: 2.5em;}",
".tags {color: rgb(245, 135, 31); font-weight: bold;}",
".break {text-align: left;}",
".sep {text-align: center; margin-top: 1em; margin-bottom: 1em;}",
".skip {margin-top: 1em; margin-bottom: 1em;}",
".synopsis {font-style: italic;}",
".comment {font-style: italic; color: rgb(100, 100, 100);}"
],
"html": [
[
"<h1 class='title' style='text-align: center; page-break-before: never;'>Lorem Ipsum</h1>"
],
[
""
],
[
"<h1 style='page-break-before: always;'>Prologue</h1>",
"<p class='synopsis'><strong>Synopsis: </strong>Explanation from the lipsum.com website.</p>"
],
[
"<h1 style='text-align: center; page-break-before: always;'>Act One</h1>"
],
[
"<h1 style='page-break-before: always;'>Chapter One: Chapter One</h1>",
"<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.</p>"
],
[
"<h2>Scene 1: Scene One</h2>",
"<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.</p>",
"<h3>Section: Scene One, Section Two</h3>"
],
[
"<h2>Scene 2: Scene Two</h2>",
"<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.</p>",
"<h3>Section: Scene Two, Section Two</h3>"
],
[
"<h1 style='page-break-before: always;'>Chapter Two: Chapter Two</h1>",
"<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.</p>"
],
[
"<h2>Scene 3: Scene Three</h2>",
"<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</p>"
],
[
"<h2>Scene 4: Scene Four</h2>",
"<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.</p>"
],
[
"<h2>Scene 5: Scene Five</h2>",
"<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>"
]
]
}
}
+47
View File
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Lorem Ipsum</title>
</head>
<style>
p {text-align: justify;}
h1, h2 {color: rgb(66, 113, 174);}
h3, h4 {color: rgb(50, 50, 50);}
h1, h2, h3, h4 {page-break-after: avoid;}
a {color: rgb(66, 113, 174);}
.title {font-size: 2.5em;}
.tags {color: rgb(245, 135, 31); font-weight: bold;}
.break {text-align: left;}
.sep {text-align: center; margin-top: 1em; margin-bottom: 1em;}
.skip {margin-top: 1em; margin-bottom: 1em;}
.synopsis {font-style: italic;}
.comment {font-style: italic; color: rgb(100, 100, 100);}
article {width: 800px; margin: 40px auto;}
</style>
<body>
<article>
<h1 class='title' style='text-align: center; page-break-before: never;'>Lorem Ipsum</h1>
<h1 style='page-break-before: always;'>Prologue</h1>
<p class='synopsis'><strong>Synopsis: </strong>Explanation from the lipsum.com website.</p>
<h1 style='text-align: center; page-break-before: always;'>Act One</h1>
<h1 style='page-break-before: always;'>Chapter One: Chapter One</h1>
<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.</p>
<h2>Scene 1: Scene One</h2>
<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.</p>
<h3>Section: Scene One, Section Two</h3>
<h2>Scene 2: Scene Two</h2>
<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.</p>
<h3>Section: Scene Two, Section Two</h3>
<h1 style='page-break-before: always;'>Chapter Two: Chapter Two</h1>
<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.</p>
<h2>Scene 3: Scene Three</h2>
<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</p>
<h2>Scene 4: Scene Four</h2>
<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.</p>
<h2>Scene 5: Scene Five</h2>
<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
</article>
</body>
</html>
+103
View File
@@ -0,0 +1,103 @@
# Lorem Ipsum
## Prologue
% Synopsis:Explanation from the lipsum.com website.
# Act One
## Chapter One
@pov: Bod
@plot: Main
@location: Europe
% Synopsis: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.
### Scene One
@pov: Bod
@plot: Main
@location: Europe
% Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.
#### Scene One, Section Two
### Scene Two
@pov: Bod
@plot: Main
@location: Europe
% Synopsis: Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.
#### Scene Two, Section Two
## Chapter Two
@pov: Bod
@plot: Main
@location: Europe
% Synopsis: Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.
### Scene Three
@pov: Bod
@plot: Main
@location: Europe
% Synopsis: Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
### Scene Four
@pov: Bod
@plot: Main
@location: Europe
% Synopsis: Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.
### Scene Five
@pov: Bod
@plot: Main
@location: Europe
% Synopsis: Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
+4 -4
View File
@@ -387,9 +387,9 @@
"title": "Why do we use it?", "title": "Why do we use it?",
"layout": "BOOK", "layout": "BOOK",
"synopsis": "", "synopsis": "",
"cCount": 630, "cCount": 631,
"wCount": 109, "wCount": 109,
"pCount": 1, "pCount": 3,
"updated": 123 "updated": 123
} }
}, },
@@ -517,9 +517,9 @@
6 6
], ],
"846352075de7d": [ "846352075de7d": [
630, 631,
109, 109,
1 3
], ],
"441420a886d82": [ "441420a886d82": [
477, 477,
+69 -15
View File
@@ -114,7 +114,12 @@ def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, nwR
projEdit._doSave() projEdit._doSave()
# Open again, and check project settings # Open again, and check project settings
projEdit = GuiProjectSettings(nwGUI, nwGUI.theProject) nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000)
projEdit = getGuiItem("GuiProjectSettings")
assert isinstance(projEdit, GuiProjectSettings)
qtbot.addWidget(projEdit) qtbot.addWidget(projEdit)
assert projEdit.tabMain.editName.text() == "Project Name" assert projEdit.tabMain.editName.text() == "Project Name"
assert projEdit.tabMain.editTitle.text() == "Project Title" assert projEdit.tabMain.editTitle.text() == "Project Title"
@@ -278,10 +283,10 @@ def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTemp):
assert isinstance(sessLog, GuiWritingStats) assert isinstance(sessLog, GuiWritingStats)
qtbot.wait(stepDelay) qtbot.wait(stepDelay)
monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda *args, **kwargs: []) monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda *args, **kwargs: ("", ""))
assert not sessLog._saveData(sessLog.FMT_CSV) assert not sessLog._saveData(sessLog.FMT_CSV)
monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda ss, tt, pp, options: [pp]) monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda ss, tt, pp, options: (pp, ""))
assert sessLog._saveData(sessLog.FMT_CSV) assert sessLog._saveData(sessLog.FMT_CSV)
qtbot.wait(stepDelay) qtbot.wait(stepDelay)
assert sessLog._saveData(sessLog.FMT_JSON) assert sessLog._saveData(sessLog.FMT_JSON)
@@ -488,6 +493,27 @@ def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, nwRef, nwTemp):
copyfile(projFile, testFile) copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile) assert cmpFiles(testFile, refFile)
# Replace Tabs with Spaces
qtbot.mouseClick(nwBuild.replaceTabs, Qt.LeftButton)
qtbot.wait(stepDelay)
qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
# Save files that can be compared
assert nwBuild._saveDocument(nwBuild.FMT_NWD)
projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
testFile = os.path.join(nwTempBuild, "3_LoremIpsum.nwd")
refFile = os.path.join(nwRef, "build", "3_LoremIpsum.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
assert nwBuild._saveDocument(nwBuild.FMT_HTM)
projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
testFile = os.path.join(nwTempBuild, "3_LoremIpsum.htm")
refFile = os.path.join(nwRef, "build", "3_LoremIpsum.htm")
copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile)
# Putline Mode # Putline Mode
nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%") nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%")
qtbot.wait(stepDelay) qtbot.wait(stepDelay)
@@ -510,30 +536,30 @@ def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, nwRef, nwTemp):
# Save files that can be compared # Save files that can be compared
assert nwBuild._saveDocument(nwBuild.FMT_NWD) assert nwBuild._saveDocument(nwBuild.FMT_NWD)
projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd") projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
testFile = os.path.join(nwTempBuild, "3_LoremIpsum.nwd") testFile = os.path.join(nwTempBuild, "4_LoremIpsum.nwd")
refFile = os.path.join(nwRef, "build", "3_LoremIpsum.nwd") refFile = os.path.join(nwRef, "build", "4_LoremIpsum.nwd")
copyfile(projFile, testFile) copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile) assert cmpFiles(testFile, refFile)
assert nwBuild._saveDocument(nwBuild.FMT_HTM) assert nwBuild._saveDocument(nwBuild.FMT_HTM)
projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm") projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
testFile = os.path.join(nwTempBuild, "3_LoremIpsum.htm") testFile = os.path.join(nwTempBuild, "4_LoremIpsum.htm")
refFile = os.path.join(nwRef, "build", "3_LoremIpsum.htm") refFile = os.path.join(nwRef, "build", "4_LoremIpsum.htm")
copyfile(projFile, testFile) copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile) assert cmpFiles(testFile, refFile)
# Check the JSON files too at this stage # Check the JSON files too at this stage
assert nwBuild._saveDocument(nwBuild.FMT_JSON_H) assert nwBuild._saveDocument(nwBuild.FMT_JSON_H)
projFile = os.path.join(nwLipsum, "Lorem Ipsum.json") projFile = os.path.join(nwLipsum, "Lorem Ipsum.json")
testFile = os.path.join(nwTempBuild, "3H_LoremIpsum.json") testFile = os.path.join(nwTempBuild, "4H_LoremIpsum.json")
refFile = os.path.join(nwRef, "build", "3H_LoremIpsum.json") refFile = os.path.join(nwRef, "build", "4H_LoremIpsum.json")
copyfile(projFile, testFile) copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile, [8]) assert cmpFiles(testFile, refFile, [8])
assert nwBuild._saveDocument(nwBuild.FMT_JSON_M) assert nwBuild._saveDocument(nwBuild.FMT_JSON_M)
projFile = os.path.join(nwLipsum, "Lorem Ipsum.json") projFile = os.path.join(nwLipsum, "Lorem Ipsum.json")
testFile = os.path.join(nwTempBuild, "3M_LoremIpsum.json") testFile = os.path.join(nwTempBuild, "4M_LoremIpsum.json")
refFile = os.path.join(nwRef, "build", "3M_LoremIpsum.json") refFile = os.path.join(nwRef, "build", "4M_LoremIpsum.json")
copyfile(projFile, testFile) copyfile(projFile, testFile)
assert cmpFiles(testFile, refFile, [8]) assert cmpFiles(testFile, refFile, [8])
@@ -561,8 +587,13 @@ def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, nwRef, nwTemp):
nwBuild._doClose() nwBuild._doClose()
# Re-open build dialog from cahce # Re-open build dialog from cahce
nwBuild = GuiBuildNovel(nwGUI, nwGUI.theProject) nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiBuildNovel") is not None, timeout=1000)
nwBuild = getGuiItem("GuiBuildNovel")
assert isinstance(nwBuild, GuiBuildNovel)
assert nwBuild.viewCachedDoc()
assert nwBuild.htmlText == htmlText assert nwBuild.htmlText == htmlText
assert nwBuild.htmlStyle == htmlStyle assert nwBuild.htmlStyle == htmlStyle
assert nwBuild.nwdText == nwdText assert nwBuild.nwdText == nwdText
@@ -638,7 +669,11 @@ def testMergeSplitTools(qtbot, monkeypatch, yesToAll, nwTempGUI, nwLipsum, nwRef
# Split By Scene # Split By Scene
assert nwGUI.treeView.setSelectedHandle("73475cb40a568") assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
qtbot.wait(stepDelay) qtbot.wait(stepDelay)
nwSplit = GuiDocSplit(nwGUI, nwGUI.theProject) nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiDocSplit") is not None, timeout=1000)
nwSplit = getGuiItem("GuiDocSplit")
assert isinstance(nwSplit, GuiDocSplit)
qtbot.wait(stepDelay) qtbot.wait(stepDelay)
nwSplit.splitLevel.setCurrentIndex(2) nwSplit.splitLevel.setCurrentIndex(2)
qtbot.wait(stepDelay) qtbot.wait(stepDelay)
@@ -670,7 +705,11 @@ def testMergeSplitTools(qtbot, monkeypatch, yesToAll, nwTempGUI, nwLipsum, nwRef
# Split By Section # Split By Section
assert nwGUI.treeView.setSelectedHandle("73475cb40a568") assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
qtbot.wait(stepDelay) qtbot.wait(stepDelay)
nwSplit = GuiDocSplit(nwGUI, nwGUI.theProject) nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiDocSplit") is not None, timeout=1000)
nwSplit = getGuiItem("GuiDocSplit")
assert isinstance(nwSplit, GuiDocSplit)
qtbot.wait(stepDelay) qtbot.wait(stepDelay)
nwSplit.splitLevel.setCurrentIndex(3) nwSplit.splitLevel.setCurrentIndex(3)
qtbot.wait(stepDelay) qtbot.wait(stepDelay)
@@ -924,6 +963,7 @@ def testLoadProject(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp):
assert nwGUI.openProject(nwMinimal) assert nwGUI.openProject(nwMinimal)
assert nwGUI.closeProject() assert nwGUI.closeProject()
qtbot.wait(stepDelay)
monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *args: None) monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *args: None)
monkeypatch.setattr(GuiProjectLoad, "result", lambda *args: QDialog.Accepted) monkeypatch.setattr(GuiProjectLoad, "result", lambda *args: QDialog.Accepted)
nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger) nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger)
@@ -933,36 +973,50 @@ def testLoadProject(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp):
assert isinstance(nwLoad, GuiProjectLoad) assert isinstance(nwLoad, GuiProjectLoad)
nwLoad.show() nwLoad.show()
qtbot.wait(stepDelay)
recentCount = nwLoad.listBox.topLevelItemCount() recentCount = nwLoad.listBox.topLevelItemCount()
assert recentCount > 0 assert recentCount > 0
qtbot.wait(stepDelay)
selItem = nwLoad.listBox.topLevelItem(0) selItem = nwLoad.listBox.topLevelItem(0)
selPath = selItem.data(nwLoad.C_NAME, Qt.UserRole) selPath = selItem.data(nwLoad.C_NAME, Qt.UserRole)
assert isinstance(selItem, QTreeWidgetItem) assert isinstance(selItem, QTreeWidgetItem)
qtbot.wait(stepDelay)
nwLoad.selPath.setText("") nwLoad.selPath.setText("")
nwLoad.listBox.setCurrentItem(selItem) nwLoad.listBox.setCurrentItem(selItem)
nwLoad._doSelectRecent() nwLoad._doSelectRecent()
assert nwLoad.selPath.text() == selPath assert nwLoad.selPath.text() == selPath
qtbot.wait(stepDelay)
qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Open), Qt.LeftButton) qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Open), Qt.LeftButton)
assert nwLoad.openPath == selPath assert nwLoad.openPath == selPath
assert nwLoad.openState == nwLoad.OPEN_STATE assert nwLoad.openState == nwLoad.OPEN_STATE
# Just create a new project load from scratch for the rest of the test # Just create a new project load from scratch for the rest of the test
del nwLoad del nwLoad
nwLoad = GuiProjectLoad(nwGUI)
qtbot.wait(stepDelay)
nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiProjectLoad") is not None, timeout=1000)
qtbot.wait(stepDelay)
nwLoad = getGuiItem("GuiProjectLoad")
assert isinstance(nwLoad, GuiProjectLoad)
nwLoad.show() nwLoad.show()
qtbot.wait(stepDelay)
qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Cancel), Qt.LeftButton) qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Cancel), Qt.LeftButton)
assert nwLoad.openPath is None assert nwLoad.openPath is None
assert nwLoad.openState == nwLoad.NONE_STATE assert nwLoad.openState == nwLoad.NONE_STATE
qtbot.wait(stepDelay)
nwLoad.show() nwLoad.show()
qtbot.mouseClick(nwLoad.newButton, Qt.LeftButton) qtbot.mouseClick(nwLoad.newButton, Qt.LeftButton)
assert nwLoad.openPath is None assert nwLoad.openPath is None
assert nwLoad.openState == nwLoad.NEW_STATE assert nwLoad.openState == nwLoad.NEW_STATE
qtbot.wait(stepDelay)
nwLoad.show() nwLoad.show()
nwLoad._keyPressDelete() nwLoad._keyPressDelete()
assert nwLoad.listBox.topLevelItemCount() == recentCount - 1 assert nwLoad.listBox.topLevelItemCount() == recentCount - 1
+9 -7
View File
@@ -85,7 +85,6 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, nwTemp):
assert ex.value.code == 2 assert ex.value.code == 2
# Simulate import error # Simulate import error
monkeypatch.setitem(sys.modules, "PyQt5.QtSvg", None)
monkeypatch.setitem(sys.modules, "lxml", None) monkeypatch.setitem(sys.modules, "lxml", None)
monkeypatch.setattr("sys.hexversion", 0x0) monkeypatch.setattr("sys.hexversion", 0x0)
monkeypatch.setattr("nw.CONFIG.verQtValue", 50000) monkeypatch.setattr("nw.CONFIG.verQtValue", 50000)
@@ -96,7 +95,10 @@ def testLaunch(qtbot, monkeypatch, nwFuncTemp, nwTemp):
) )
nwGUI.closeMain() nwGUI.closeMain()
nwGUI.close() nwGUI.close()
assert ex.value.code == 15 assert ex.value.code & 4 == 4 # Python version not satisfied
assert ex.value.code & 8 == 8 # Qt version not satisfied
assert ex.value.code & 16 == 16 # PyQt version not satisfied
assert ex.value.code & 32 == 32 # lxml package missing
monkeypatch.undo() monkeypatch.undo()
@pytest.mark.gui @pytest.mark.gui
@@ -547,9 +549,9 @@ def testDocViewer(qtbot, yesToAll, nwLipsum, nwTemp):
# Document footer show/hide comments # Document footer show/hide comments
assert nwGUI.viewDocument("846352075de7d") assert nwGUI.viewDocument("846352075de7d")
assert len(nwGUI.docViewer.toPlainText()) == 672 assert len(nwGUI.docViewer.toPlainText()) == 675
nwGUI.docViewer.docFooter._doToggleComments(False) nwGUI.docViewer.docFooter._doToggleComments(False)
assert len(nwGUI.docViewer.toPlainText()) == 632 assert len(nwGUI.docViewer.toPlainText()) == 635
# qtbot.stopForInteraction() # qtbot.stopForInteraction()
nwGUI.closeMain() nwGUI.closeMain()
@@ -1105,16 +1107,16 @@ def testInsertMenu(qtbot, monkeypatch, nwFuncTemp, nwTemp):
nwGUI.closeDocument() nwGUI.closeDocument()
# First, with no path # First, with no path
monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwards: []) monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwards: ("", ""))
assert not nwGUI.importDocument() assert not nwGUI.importDocument()
# Then with a path, but an invalid one # Then with a path, but an invalid one
monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwards: [" "]) monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwards: (" ", ""))
assert not nwGUI.importDocument() assert not nwGUI.importDocument()
# Then a valid path, but bot a file that exists # Then a valid path, but bot a file that exists
theFile = os.path.join(nwTemp, "import.txt") theFile = os.path.join(nwTemp, "import.txt")
monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwards: [theFile]) monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwards: (theFile, ""))
assert not nwGUI.importDocument() assert not nwGUI.importDocument()
# Create the file and try again, but with no target document open # Create the file and try again, but with no target document open
+8
View File
@@ -377,6 +377,10 @@ def testSpellEnchant(nwTemp, nwConf):
dList = spChk.listDictionaries() dList = spChk.listDictionaries()
assert len(dList) > 0 assert len(dList) > 0
aTag, aName = spChk.describeDict()
assert aTag == "en"
assert aName != ""
@pytest.mark.project @pytest.mark.project
def testSpellSimple(nwTemp, nwConf): def testSpellSimple(nwTemp, nwConf):
wList = os.path.join(nwTemp, "wordlist.txt") wList = os.path.join(nwTemp, "wordlist.txt")
@@ -402,6 +406,10 @@ def testSpellSimple(nwTemp, nwConf):
dList = spChk.listDictionaries() dList = spChk.listDictionaries()
assert len(dList) > 0 assert len(dList) > 0
aTag, aName = spChk.describeDict()
assert aTag == "en"
assert aName == "internal"
@pytest.mark.project @pytest.mark.project
def testProjectOptions(nwDummy, nwLipsum): def testProjectOptions(nwDummy, nwLipsum):
"""Test the class that holds all the GUI state user options that are """Test the class that holds all the GUI state user options that are