@@ -23,6 +23,7 @@ __pycache__
|
||||
/sample/meta
|
||||
*.bak
|
||||
*.lock
|
||||
ToC.txt
|
||||
|
||||
# PyTest
|
||||
/prof/
|
||||
|
||||
+174
-1
@@ -1,5 +1,112 @@
|
||||
# novelWriter Changelog
|
||||
|
||||
## Version 1.2 RC 1 [2021-03-02]
|
||||
|
||||
### Release Notes
|
||||
|
||||
This is the release candidate of 1.2. The release is intended for testing of new features and to
|
||||
sort out potential bugs before the full release. Please take this into account when working on your
|
||||
live projects.
|
||||
|
||||
#### The Build Novel Project Tool
|
||||
|
||||
The main changes for version 1.2 are to the Build Novel Project tool. The Open Document export, as
|
||||
well as the Markdown export, is now handled entirely by code written for novelWriter. Previously,
|
||||
these export features depended on the underlying Qt library's save routines connected to the
|
||||
preview document shown in the build dialog. Using this method of export both meant that the content
|
||||
of the document was dependent on the preview being generated first, and it also meant that the
|
||||
exported document had limited support for novelWriter-specific features and custom formatting. The
|
||||
new export class should generate a much better result, especially for the Open Document formats.
|
||||
The Open Document standard is supported by Open Office, Libre Office, Google Docs, Microsoft Word,
|
||||
and probably a number of other applications too. The Markdown export hasn't changed a lot, but
|
||||
should be a slight improvement on the previous export feature.
|
||||
|
||||
These changes to the build tool also imply that the saving process is now independent of the
|
||||
content of the preview window, meaning you don't have to rebuild the preview before saving, which
|
||||
was previously the case. To make this more consistent, the PDF export option has been moved to the
|
||||
print button as it is actually a print-to-file feature under the hood, not technically a proper PDF
|
||||
export format. It is exactly the same as printing to file from the print preview dialog.
|
||||
|
||||
In addition to the changes to the export features, the Build Novel Project tool now also has
|
||||
controls for line height, which applies to all rich text export formats, and the option to replace
|
||||
unicode characters with appropriate html entities for html export.
|
||||
|
||||
#### Document Layout Automation
|
||||
|
||||
Among other changes in this release are a few improvements to the process of creating and changing
|
||||
documents. When a new document is first created, the header is generated from the assigned label
|
||||
and layout.
|
||||
|
||||
In addition, for some document layouts, when the user changes the header level of the first header
|
||||
of the document, the document layout setting is updated accordingly. This should reduce the need
|
||||
for the user to maintain two ways of assigning the role of a given document. This automation only
|
||||
applies to combinations of header level and current document layouts where there is no ambiguity.
|
||||
For instance, changing the header level in a "Scene" document from level 3 to 2 changes the
|
||||
document layout automatically to "Chapter". But changing the first header of a "Book" layout
|
||||
document from 1 to 2 does not change the document's layout as the "Book" layout is a generic
|
||||
document layout and it's perfectly reasonable for its first header to be a chapter header.
|
||||
|
||||
Keep in mind that novelWriter treats documents with layout "Book", "Chapter" and "Scene" exactly
|
||||
the same during exports. The distinction is only meant as a way to indicate the purpose of a
|
||||
document in the project tree. This new automation is meant to assist in keeping this information up
|
||||
to date. The other layouts do have an effect on formatting during export, and are generally left
|
||||
alone.
|
||||
|
||||
#### The Session Timer and Idle Time
|
||||
|
||||
Another change that has been requested by a couple of users is to have the session timer in the
|
||||
status bar stop counting when the user is inactive (idle). This feature is optional, and can be
|
||||
controlled from Preferences. The definition of "idle" in this context is either that the user is
|
||||
active in a different application than novelWriter (loss of focus) or that the user has not made
|
||||
any changes to the current document for a given amount of time. The time threshold is by default
|
||||
five minutes, but can be altered in Preferences.
|
||||
|
||||
In addition, the idle time is also recorded in the session log, and can be viewed in the Writing
|
||||
Statistics dialog and exported with the rest of the information. The idle time is recorder in the
|
||||
logs regardless of whether the status bar clock takes idle time into consideration or not. So even
|
||||
if you turn off the idle time switch in Preferences, the other idle time setting still affects the
|
||||
writing stats log entries.
|
||||
|
||||
#### Other Changes
|
||||
|
||||
The user dictionary of the project, where words added to the dictionary from the document editor
|
||||
go, can now be viewed and edited with a new "Project Word List" tool in the "Tools" menu.
|
||||
|
||||
A small additional feature added is also the ability to undo the last move of an item in the
|
||||
project tree. The keyboard shortcut for this is `Ctrl+Shift+Z`, or it can be accessed from the
|
||||
menu. The feature can only undo the last move, but it includes both documents moved to trash, moves
|
||||
by up/down keypress or menu entries, and drag and drop moves.
|
||||
|
||||
Lastly, a new keyword has been added to mark characters in the story. The new keyword is intended
|
||||
to tag a character as the focus character for a chapter or scene. This is useful for stories where
|
||||
the point-of-view character and the focus character are different.
|
||||
|
||||
_These Release Notes also include the changes from 1.2 Beta 1._
|
||||
|
||||
### Detailed Changelog
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
* If a tag or reference keyword was mistyped, it would still be indexed and put into the index.
|
||||
This caused the index to be deemed invalid on the next loading of the project, triggering a
|
||||
rebuild. A check has been added to the code parsing the lines starting with `@` to ensure only
|
||||
valid keywords are written into the index. Issue #688. PR #690.
|
||||
|
||||
**New Features**
|
||||
|
||||
* Added a tool to edit the project's user dictionary. This is the dictionary where the "Add Word to
|
||||
Dictionary" actions from the document editor go. The dialog tool allows for listing, removing and
|
||||
adding words to this dictionary. Issue #665. PR #669.
|
||||
|
||||
**User Interface**
|
||||
|
||||
* Pressing the `F2` key when in the document editor will open the Item Editor for the open document
|
||||
instead of the one selected in the project tree. PR #664.
|
||||
* The shortcut for deleting an item in the project tree has been changed from `Ctrl+Del` to
|
||||
`Ctrl+Shift+Del`. This change was also backported to version 1.1.1. Resolves #629. PR #664.
|
||||
|
||||
----
|
||||
|
||||
## Version 1.1.1 [2021-02-21]
|
||||
|
||||
### Release Notes
|
||||
@@ -53,6 +160,72 @@ but is still lacking in functionality compared to Enchant.
|
||||
|
||||
----
|
||||
|
||||
## Version 1.2 Beta 1 [2021-02-11]
|
||||
|
||||
### Detailed Changelog
|
||||
|
||||
**New Features**
|
||||
|
||||
* A full Open Document exporter has been written and added for the Build Novel Project tool. This
|
||||
replaces the previously used Qt Save to ODT which just saved the content of the preview window to
|
||||
an ODT file. The Qt pathway was very limited and didn't generate proper formatting classes. The
|
||||
new export class can generate both full `.odt` and flat XML `.fodt` files. The formatting support
|
||||
of this exporter is equivalent or better than the HTML5 exporter, which was previously the best
|
||||
supported option. Solves issue #611. PRs #607, #652, #660, and #654.
|
||||
* A full Markdown exporter has been written and added for the Build Novel Project tool. This too
|
||||
replaces a Qt feature that was used to save the content of the preview window into a markdown
|
||||
file. The exporter allows for both standard markdown and GitHub flavour markdown. The only
|
||||
relevant difference being that the latter allows strikethrough text. Issue #617. PR #650.
|
||||
* The Build Novel Project tool now has a "Line height" property that is applied to the preview, and
|
||||
to the HTML5 and Open Document export formats. Discussion #653. Issue #654. PR #660.
|
||||
* The Build Novel Project tool now has an option to convert Unicode characters to HTML entities on
|
||||
export to HTML5. Previously, some symbols were converted while others were not. This option
|
||||
provides a more consistent "all or nothing" option. PR #660.
|
||||
* The last file or folder move in the project tree can now be undone from the Project menu or by
|
||||
pressing `Ctrl+Shift+Z`. PR #632.
|
||||
* When a document header level is altered in a novel file of layout type Scene, Chapter,
|
||||
Unnumbered, or Partition, and that document is saved, the document's layout setting as seen in
|
||||
the project tree is updated to reflect the new level of that heading. This helps reduce the
|
||||
duplication of effort by the user to keep this information in sync. See discussion #613. This is
|
||||
an acceptable solution to #614. Issue #618. PR #620.
|
||||
* The session timer now records the amount of time the user is idle. Idle is defined to be when the
|
||||
application window does not have focus, and when the user hasn't made any changes to the document
|
||||
open in the editor for a specified amount of time. The default is 5 minutes. The idle time is
|
||||
recorded to the session log and can be shown in Writing Statistics. Optionally, the status bar
|
||||
session timer can also be set to pause when the user is considered idle. Issues #606 and #651.
|
||||
PRs #656 and #661.
|
||||
* It is now possible to tag a character as the focus character for a give section of text. This is
|
||||
useful for cases where the point-of-view character differs from the main character of the story,
|
||||
or the part of the story. Issue #605. PR #662.
|
||||
|
||||
**User Interface**
|
||||
|
||||
* A document's class and layout is now displayed next to its status or importance in the document
|
||||
editor footer bar. PR #628.
|
||||
* When a new document is created, the header of the file is automatically generated based on the
|
||||
document's tree label, and the level determined by the selected layout. Issue #530. PR #628.
|
||||
* On the Build Novel Project GUI, the PDF option has been moved from the "Save As" button to the
|
||||
"Print" button. This more accurately reflects what it actually does: print the content of the
|
||||
preview to a PDF using the printer pathway. This also means that all remaining items on the "Save
|
||||
As" dropdown list now can be executed regardless of the content of the preview window. They all
|
||||
run their own separate build process. This resolves #611. PR #650.
|
||||
* Export to plain text has been dropped. PR #617.
|
||||
|
||||
**Code Improvements**
|
||||
|
||||
* The index class now scans every element of the loaded index cache before accepting it. This means
|
||||
that any unrecognised content will trigger a full re-indexing. This is particularly useful when
|
||||
opening an index that was saved by a later release. Every entry that requires a lookup is checked
|
||||
to avoid potential key errors for instance. All values are also checked for correct data type.
|
||||
The new check is extensive, but still fast enough that it only adds a few milliseconds to the
|
||||
startup time. PR #619.
|
||||
|
||||
**Code Maintenance**
|
||||
|
||||
* Cleaned up some redundant code after PR #637. PR #638.
|
||||
|
||||
----
|
||||
|
||||
## Version 1.1 [2021-02-07]
|
||||
|
||||
### Release Notes
|
||||
@@ -87,7 +260,7 @@ _These Release Notes also include the changes from 1.1 RC 1._
|
||||
**Bugfixes**
|
||||
|
||||
* A `None` check in the details panel below the project tree was missing, resulting in an
|
||||
occasional error message being printed to the logging output. The error was otherwise handles, so
|
||||
occasional error message being printed to the logging output. The error was otherwise handled, so
|
||||
this is mainly a fix to prevent the error message. PR #639.
|
||||
|
||||
**User Interface**
|
||||
|
||||
+2
-2
@@ -25,9 +25,9 @@ copyright = "2018–2021, Veronica Berglyd Olsen"
|
||||
author = "Veronica Berglyd Olsen"
|
||||
|
||||
# The short X.Y version
|
||||
version = "1.1.1"
|
||||
version = "1.2"
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = "1.1.1"
|
||||
release = "1.2-rc1"
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
@@ -93,13 +93,11 @@ Project Export
|
||||
==============
|
||||
|
||||
The project can at any time be exported to a range of different formats through the
|
||||
:guilabel:`Build Novel Project` tool. Natively, novelWriter supports export to plain text file,
|
||||
HTML document, novelWriter flavoured markdown, standard markdown (requires Qt 5.14), and to a basic
|
||||
Open Document format.
|
||||
:guilabel:`Build Novel Project` tool. Natively, novelWriter supports export to Open Document,
|
||||
HTML5, and various flavours of Markdown.
|
||||
|
||||
In addition, printing and printing to PDF is also possible. The best supported export format is
|
||||
HTML, which can be imported or converted by a number of other tools like Pandoc, or simply imported
|
||||
into Libre Office Writer and similar word processors.
|
||||
The HTML5 export format is suitable for convertion by a number of other tools like Pandoc, or for
|
||||
importing into word processors. In addition, printing and printing to PDF is also possible.
|
||||
|
||||
It is also possible to export the content of the project to a JSON file. This is useful if you want
|
||||
to write your own processing script in for instance Python as the entire novel can be read into a
|
||||
|
||||
@@ -44,10 +44,9 @@ The following Python packages are needed to run novelWriter:
|
||||
* ``pyenchant`` – needed for efficient spell checking (optional).
|
||||
|
||||
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
|
||||
expressions requires 5.3, and for full Unicode support, 5.13. There is no known minimum version
|
||||
requirement for package ``lxml``, but the code was originally written with 4.2, which is therefore
|
||||
set as the minimum. It may work on lower versions. You have to test it.
|
||||
Searching using regular expressions requires 5.3, and for full Unicode support, 5.13. There is no
|
||||
known minimum version requirement for package ``lxml``, but the code was originally written with
|
||||
4.2, which is therefore set as the minimum. It may work on lower versions. You have to test it.
|
||||
|
||||
Optionally, a package can be installed to interface with the Enchant spell checking libaries, but
|
||||
this isn't strictly required. If no external spell checking library is available, novelWriter falls
|
||||
|
||||
@@ -102,6 +102,16 @@ document has a checkmark after the status icon in the :guilabel:`Flags` column.
|
||||
override these settings.
|
||||
|
||||
|
||||
.. _a_export_print:
|
||||
|
||||
Printing
|
||||
========
|
||||
|
||||
The print button allows you to print the content in the preview window. You can either print to one
|
||||
of your system's printers, or print directly to file. You can also print to file from the regular
|
||||
print dialog. The direct to file option is just a shortcut.
|
||||
|
||||
|
||||
.. _a_export_formats:
|
||||
|
||||
Export Formats
|
||||
@@ -110,21 +120,13 @@ Export Formats
|
||||
Currently, six formats are supported for exporting.
|
||||
|
||||
OpenDocument Format
|
||||
This produces an open document ``.odt`` file. The document produced has very little formatting,
|
||||
and may require further editing afterwards. For a better formatted office document, you may get
|
||||
a better result by exporting to HTML and then importing that HTML document into your office word
|
||||
processor. They are generally good at importing HTML documents.
|
||||
|
||||
PDF Format
|
||||
The PDF export is just a shortcut for print-to-file. For a better PDF result, you may instead
|
||||
want to export to HTML and use a word processor to convert the HTML document to PDF.
|
||||
The Build tool can produce either an ``.odt`` file, of a ``.fodt`` file. The latter is just a
|
||||
flat version of the document format as a single XML file.
|
||||
|
||||
novelWriter HTML
|
||||
The HTML export format writes a single ``.htm`` file with minimal style formatting. The exported
|
||||
HTML document is suitable for further processing by document conversion tools like Pandoc, for
|
||||
importing in word processors, or for printing from browser. It is generally the best formatted
|
||||
export option and supports all features of novelWriter since it is entirely generated by the
|
||||
application and doesn't depend on Qt library features.
|
||||
importing in word processors, or for printing from browser.
|
||||
|
||||
novelWriter Markdown
|
||||
This is simply a concatenation of the project documents selected by the filters. The documents
|
||||
@@ -132,12 +134,10 @@ novelWriter Markdown
|
||||
included if they are selected. This is a useful format for exporting the project for later
|
||||
import back into novelWriter.
|
||||
|
||||
Standard Markdown
|
||||
If you have Qt 5.14 or higher, the option to export to plain markdown is available. This feature
|
||||
uses Qt's own markdown export feature.
|
||||
|
||||
Plain Text
|
||||
The plain text export format writes a simple ``.txt`` file without any formatting at all.
|
||||
Standard/GitHub Markdown
|
||||
The Markdown export format comes in both Standard and GitHub flavour. The *only* difference in
|
||||
terms of novelWriter functionality is the support of strikethrough text, which is not supported
|
||||
by the Standard flavour, but *is* supported by the GitHub flavour.
|
||||
|
||||
|
||||
.. _a_export_options:
|
||||
|
||||
@@ -377,6 +377,10 @@ Most features are available as keyboard shortcuts. These are as follows:
|
||||
":kbd:`Ctrl`:kbd:`Z`", "Undo latest changes."
|
||||
":kbd:`Ctrl`:kbd:`F7`", "Toggle spell checking."
|
||||
":kbd:`Ctrl`:kbd:`F10`", "Toggle automatic updating of project outline."
|
||||
":kbd:`Ctrl`:kbd:`Up`", "Move item one step up in the project tree."
|
||||
":kbd:`Ctrl`:kbd:`Down`", "Move item one step down in the project tree."
|
||||
":kbd:`Ctrl`:kbd:`Del`", "Delete next word in editor."
|
||||
":kbd:`Ctrl`:kbd:`Backspace`", "Delete previous word in editor."
|
||||
":kbd:`Ctrl`:kbd:`'`", "Wrap selected text, or word under cursor, in single quotes."
|
||||
":kbd:`Ctrl`:kbd:`""`", "Wrap selected text, or word under cursor, in double quotes."
|
||||
":kbd:`Ctrl`:kbd:`Enter`", "Open the tag or reference under the cursor in the Viewer."
|
||||
@@ -391,9 +395,7 @@ Most features are available as keyboard shortcuts. These are as follows:
|
||||
":kbd:`Ctrl`:kbd:`Shift`:kbd:`R`", "Close the document viewer."
|
||||
":kbd:`Ctrl`:kbd:`Shift`:kbd:`S`", "Save the current project."
|
||||
":kbd:`Ctrl`:kbd:`Shift`:kbd:`W`", "Close the current project."
|
||||
":kbd:`Ctrl`:kbd:`Shift`:kbd:`Z`", "Alternative sequence for redo last undo."
|
||||
":kbd:`Ctrl`:kbd:`Shift`:kbd:`Up`", "Move item one step up in the project tree."
|
||||
":kbd:`Ctrl`:kbd:`Shift`:kbd:`Down`", "Move item one step down in the project tree."
|
||||
":kbd:`Ctrl`:kbd:`Shift`:kbd:`Z`", "Undo move of project tree item."
|
||||
":kbd:`Ctrl`:kbd:`Shift`:kbd:`Del`", "If in the project tree, move a document to trash, or delete a folder."
|
||||
":kbd:`F1`", "Open the documentation. This will either open the Qt Assistant, if available, or send you to the documentation website."
|
||||
":kbd:`F2`", "If in the project tree, edit a document or folder settings. (Same as :kbd:`Ctrl`:kbd:`E`)"
|
||||
@@ -453,6 +455,7 @@ a key or key combination for the inserted content.
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`Ctrl`:kbd:`D`", "Insert a division sign."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`G`", "Insert a ``@tag`` keyword."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`V`", "Insert a ``@pov`` keyword."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`F`", "Insert a ``@focus`` keyword."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`C`", "Insert a ``@char`` keyword."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`P`", "Insert a ``@plot`` keyword."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`T`", "Insert a ``@time`` keyword."
|
||||
|
||||
@@ -51,8 +51,9 @@ no restrictions are enforced by the application. You can use them however you wa
|
||||
:guilabel:`Characters`
|
||||
Character notes go in this root folder. These are especially important if one wants to use the
|
||||
Outline view to see which character appears where, and which part of the story is told from a
|
||||
specific character's point-of-view. Tags in this folder can be references using the ``@pov``
|
||||
keyword for point-of-view characters, or the ``@char`` keyword for other characters.
|
||||
specific character's point-of-view or focusing on a particular character's storyline. Tags in
|
||||
this folder can be referenced using the ``@pov`` keyword for point-of-view characters,
|
||||
``@focus`` for a focus character, or the ``@char`` keyword for any other characters.
|
||||
|
||||
:guilabel:`Locations`
|
||||
The locations folder is for various scene locations that you want to track. Tags in this folder
|
||||
|
||||
@@ -103,28 +103,33 @@ allow multiple values.
|
||||
The point-of-view character for the current section. The target must be a note tag in the
|
||||
:guilabel:`Character` type root folder.
|
||||
|
||||
``@focus``
|
||||
The character that has the focus for the current section. This can be used in cases where the
|
||||
focus is not a point-of-view character. The target must be a note tag in the
|
||||
:guilabel:`Character` type root folder.
|
||||
|
||||
``@char``
|
||||
Other characters in the current section. The target must be a note tag in a
|
||||
Other characters in the current section. The target must be a note tag in the
|
||||
:guilabel:`Character` type root folder. This should not include the point-of-view character.
|
||||
|
||||
``@plot``
|
||||
The plot or subplot advanced in the current section. The target must be a note tag in a
|
||||
The plot or subplot advanced in the current section. The target must be a note tag in the
|
||||
:guilabel:`Plot` type root folder.
|
||||
|
||||
``@time``
|
||||
The timelines touched by the current section. The target must be a note tag in a
|
||||
The timelines touched by the current section. The target must be a note tag in the
|
||||
:guilabel:`Timeline` type root folder.
|
||||
|
||||
``@location``
|
||||
The location the current section takes place in. The target must be a note tag in a
|
||||
The location the current section takes place in. The target must be a note tag in the
|
||||
:guilabel:`Locations` type root folder.
|
||||
|
||||
``@object``
|
||||
Objects present in the current section. The target must be a note tag in an :guilabel:`Object`
|
||||
Objects present in the current section. The target must be a note tag in the :guilabel:`Object`
|
||||
type root folder.
|
||||
|
||||
``@entity``
|
||||
Entities present in the current section. The target must be a note tag in an
|
||||
Entities present in the current section. The target must be a note tag in the
|
||||
:guilabel:`Entities` type root folder.
|
||||
|
||||
``@custom``
|
||||
|
||||
+6
-6
@@ -31,7 +31,7 @@ import logging
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QApplication, QErrorMessage
|
||||
|
||||
from nw.error import exceptionHandler
|
||||
from nw.error import exceptionHandler, logException
|
||||
from nw.config import Config
|
||||
|
||||
##
|
||||
@@ -62,9 +62,9 @@ __license__ = "GPLv3"
|
||||
__author__ = "Veronica Berglyd Olsen"
|
||||
__maintainer__ = "Veronica Berglyd Olsen"
|
||||
__email__ = "code@vkbo.net"
|
||||
__version__ = "1.1.1"
|
||||
__hexversion__ = "0x010101f0"
|
||||
__date__ = "2021-02-21"
|
||||
__version__ = "1.2rc1"
|
||||
__hexversion__ = "0x010200c1"
|
||||
__date__ = "2021-03-02"
|
||||
__status__ = "Stable"
|
||||
__domain__ = "novelwriter.io"
|
||||
__url__ = "https://novelwriter.io"
|
||||
@@ -264,9 +264,9 @@ def main(sysArgs=None):
|
||||
bundle = NSBundle.mainBundle()
|
||||
info = bundle.localizedInfoDictionary() or bundle.infoDictionary()
|
||||
info["CFBundleName"] = "novelWriter"
|
||||
except ImportError as e:
|
||||
except ImportError:
|
||||
logger.error("Failed to set application name")
|
||||
logger.error(str(e))
|
||||
logException()
|
||||
|
||||
# Import GUI (after dependency checks), and launch
|
||||
from nw.guimain import GuiMain
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg5582">
|
||||
<metadata
|
||||
id="metadata5588">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs5586" />
|
||||
<path
|
||||
d="m 6.7499997,3 c -1.656,0 -3,1.3440001 -3,3.0000001 V 17.999999 c 0,1.656 1.344,3.000001 3,3.000001 1.6560002,0 2.9999998,-1.344001 2.9999998,-3.000001 V 6.0000001 C 9.7499995,4.3440001 8.4059999,3 6.7499997,3 Z M 17.249999,3 c -1.656,0 -3.000001,1.3440001 -3.000001,3.0000001 V 17.999999 c 0,1.656 1.344001,3.000001 3.000001,3.000001 1.656,0 3.000001,-1.344001 3.000001,-3.000001 V 6.0000001 C 20.25,4.3440001 18.905999,3 17.249999,3 Z"
|
||||
id="path5580"
|
||||
style="fill:#aeaeae;fill-opacity:1;stroke-width:1.49999" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg5582">
|
||||
<metadata
|
||||
id="metadata5588">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs5586" />
|
||||
<path
|
||||
d="m 6.7499997,3 c -1.656,0 -3,1.3440001 -3,3.0000001 V 17.999999 c 0,1.656 1.344,3.000001 3,3.000001 1.6560002,0 2.9999998,-1.344001 2.9999998,-3.000001 V 6.0000001 C 9.7499995,4.3440001 8.4059999,3 6.7499997,3 Z M 17.249999,3 c -1.656,0 -3.000001,1.3440001 -3.000001,3.0000001 V 17.999999 c 0,1.656 1.344001,3.000001 3.000001,3.000001 1.656,0 3.000001,-1.344001 3.000001,-3.000001 V 6.0000001 C 20.25,4.3440001 18.905999,3 17.249999,3 Z"
|
||||
id="path5580"
|
||||
style="fill:#000000;fill-opacity:0.780392;stroke-width:1.49999" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -2,48 +2,75 @@
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<h2>Release Notes for 1.1.1</h2>
|
||||
<p><i>Released on 21 February 2021</i></p>
|
||||
<p>This patch makes a couple of minor improvements to the GUI. The keyboard shortcut for deleting
|
||||
entries in the project tree has been changed from "Ctrl+Del" to "Ctrl+Shift+Del" to free up that
|
||||
shortcut for the document editor. It is now possible to use the shortcut for deleting the word in
|
||||
front of the cursor, a common and useful feature of many text editors.</p>
|
||||
<p>The way the negative word count filter option works on the Writing Statistics tool has been
|
||||
changed to be more intuitive. Enabling this filter now appears to remove all negative entries
|
||||
without altering the other entries. Previously, the removed negative counts would be included in
|
||||
the following entries to make it consistent with the total word count. In addition, writing
|
||||
sessions shorter than five minutes, and with no change in the word count, are no longer recorded in
|
||||
the session log file.</p>
|
||||
<p>Other changes include improving the speed of the internal spell checker, used when the Enchant
|
||||
spell check library isn't available. The internal spell checker is no longer significantly slower,
|
||||
but is still lacking in functionality compared to Enchant.</p>
|
||||
<h2>Release Notes for 1.2 RC 1</h2>
|
||||
<p><i>Released on 2 March 2021</i></p>
|
||||
<p>This is the release candidate of 1.2. The release is intended for testing of new features and to
|
||||
sort out potential bugs before the full release. Please take this into account when working on your
|
||||
live projects.</p>
|
||||
|
||||
<h3>The Build Novel Project Tool</h3>
|
||||
<p>The main changes for version 1.2 are to the Build Novel Project tool. The Open Document export,
|
||||
as well as the Markdown export, is now handled entirely by code written for novelWriter.
|
||||
Previously, these export features depended on the underlying Qt library's save routines connected
|
||||
to the preview document shown in the build dialog. Using this method of export both meant that the
|
||||
content of the document was dependent on the preview being generated first, and it also meant that
|
||||
the exported document had limited support for novelWriter-specific features and custom formatting.
|
||||
The new export class should generate a much better result, especially for the Open Document
|
||||
formats. The Open Document standard is supported by Open Office, Libre Office, Google Docs,
|
||||
Microsoft Word, and probably a number of other applications too. The Markdown export hasn't changed
|
||||
a lot, but should be a slight improvement on the previous export feature.</p>
|
||||
<p>These changes to the build tool also imply that the saving process is now independent of the
|
||||
content of the preview window, meaning you don't have to rebuild the preview before saving, which
|
||||
was previously the case. To make this more consistent, the PDF export option has been moved to the
|
||||
print button as it is actually a print-to-file feature under the hood, not technically a proper PDF
|
||||
export format. It is exactly the same as printing to file from the print preview dialog.</p>
|
||||
<p>In addition to the changes to the export features, the Build Novel Project tool now also has
|
||||
controls for line height, which applies to all rich text export formats, and the option to replace
|
||||
unicode characters with appropriate html entities for html export.</p>
|
||||
|
||||
<h3>Document Layout Automation</h3>
|
||||
<p>Among other changes in this release are a few improvements to the process of creating and
|
||||
changing documents. When a new document is first created, the header is generated from the assigned
|
||||
label and layout.</p>
|
||||
<p>In addition, for some document layouts, when the user changes the header level of the first
|
||||
header of the document, the document layout setting is updated accordingly. This should reduce the
|
||||
need for the user to maintain two ways of assigning the role of a given document. This automation
|
||||
only applies to combinations of header level and current document layouts where there is no
|
||||
ambiguity. For instance, changing the header level in a "Scene" document from level 3 to 2 changes
|
||||
the document layout automatically to "Chapter". But changing the first header of a "Book" layout
|
||||
document from 1 to 2 does not change the document's layout as the "Book" layout is a generic
|
||||
document layout and it's perfectly reasonable for its first header to be a chapter header.</p>
|
||||
<p>Keep in mind that novelWriter treats documents with layout "Book", "Chapter" and "Scene" exactly
|
||||
the same during exports. The distinction is only meant as a way to indicate the purpose of a
|
||||
document in the project tree. This new automation is meant to assist in keeping this information up
|
||||
to date. The other layouts do have an effect on formatting during export, and are generally left
|
||||
alone.</p>
|
||||
|
||||
<h3>The Session Timer and Idle Time</h3>
|
||||
<p>Another change that has been requested by a couple of users is to have the session timer in the
|
||||
status bar stop counting when the user is inactive (idle). This feature is optional, and can be
|
||||
controlled from Preferences. The definition of "idle" in this context is either that the user is
|
||||
active in a different application than novelWriter (loss of focus) or that the user has not made
|
||||
any changes to the current document for a given amount of time. The time threshold is by default
|
||||
five minutes, but can be altered in Preferences.</p>
|
||||
<p>In addition, the idle time is also recorded in the session log, and can be viewed in the Writing
|
||||
Statistics dialog and exported with the rest of the information. The idle time is recorder in the
|
||||
logs regardless of whether the status bar clock takes idle time into consideration or not. So even
|
||||
if you turn off the idle time switch in Preferences, the other idle time setting still affects the
|
||||
writing stats log entries.</p>
|
||||
|
||||
<h3>Other Changes</h3>
|
||||
<p>The user dictionary of the project, where words added to the dictionary from the document editor
|
||||
go, can now be viewed and edited with a new "Project Word List" tool in the "Tools" menu.</p>
|
||||
<p>A small additional feature added is also the ability to undo the last move of an item in the
|
||||
project tree. The keyboard shortcut for this is `Ctrl+Shift+Z`, or it can be accessed from the
|
||||
menu. The feature can only undo the last move, but it includes both documents moved to trash, moves
|
||||
by up/down keypress or menu entries, and drag and drop moves.</p>
|
||||
<p>Lastly, a new keyword has been added to mark characters in the story. The new keyword is
|
||||
intended to tag a character as the focus character for a chapter or scene. This is useful for
|
||||
stories where the point-of-view character and the focus character are different.</p>
|
||||
|
||||
<p><i>See also the <a href="https://github.com/vkbo/novelWriter/releases">Releases</a> page.</i></p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>Release Notes for 1.1</h2>
|
||||
<p><i>Released on 7 February 2021</i></p>
|
||||
<p>The main change in this release is the addition of a new tab to the project tree on the left
|
||||
side of the main window. The regular project tree is now on a tab named "Project", while a new tab
|
||||
named "Novel" displays a simpler version of the information on the main "Outline" page. It lists
|
||||
all the headers of the novel part of the project, as well as the word count and point-of-view
|
||||
character of each section. This is an alternative way to navigate the novel part of the project.
|
||||
The various tree views are now also kept better in sync when the user selects various documents and
|
||||
headers.</p>
|
||||
<p>In addition, a new information dialog named "Project Details" has been added. It replaces the
|
||||
"Details" tab in "Project Settings", and adds more information about the novel part of the project.
|
||||
In particular, a "Table of Contents" in the "Contents" tab displays a summary of the main parts
|
||||
and chapters of the project, their total word counts, and an estimated page count. This was made in
|
||||
response to users asking for ways to estimate the total page count of the project. The page count
|
||||
is estimated based on a words per page setting, which can be changed on the dialog window.</p>
|
||||
<p>Since the tabs below the project tree now add some extra room on the GUI, some convenient
|
||||
buttons have been added in the same area, with direct access to "Project Details", "Writing
|
||||
Statistics" and "Project Setting".</p>
|
||||
<p>A few other minor changes have been made as well. The Preferences dialog has been improved with
|
||||
clearer categories and hopefully better help text. Some new options have been added too. They allow
|
||||
syntax highlighting of multi-paragraph quotes. The highlighter can now optionally accept quotes to
|
||||
be left "hanging", that is, no closing quote in the same paragraph.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html public "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<h2>Release Notes for 1.1.1</h2>
|
||||
<p><i>Released on 21 February 2021</i></p>
|
||||
<p>This patch makes a couple of minor improvements to the GUI. The keyboard shortcut for deleting
|
||||
entries in the project tree has been changed from "Ctrl+Del" to "Ctrl+Shift+Del" to free up that
|
||||
shortcut for the document editor. It is now possible to use the shortcut for deleting the word in
|
||||
front of the cursor, a common and useful feature of many text editors.</p>
|
||||
<p>The way the negative word count filter option works on the Writing Statistics tool has been
|
||||
changed to be more intuitive. Enabling this filter now appears to remove all negative entries
|
||||
without altering the other entries. Previously, the removed negative counts would be included in
|
||||
the following entries to make it consistent with the total word count. In addition, writing
|
||||
sessions shorter than five minutes, and with no change in the word count, are no longer recorded in
|
||||
the session log file.</p>
|
||||
<p>Other changes include improving the speed of the internal spell checker, used when the Enchant
|
||||
spell check library isn't available. The internal spell checker is no longer significantly slower,
|
||||
but is still lacking in functionality compared to Enchant.</p>
|
||||
|
||||
<p><i>See also the <a href="https://github.com/vkbo/novelWriter/releases">Releases</a> page.</i></p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>Release Notes for 1.1</h2>
|
||||
<p><i>Released on 7 February 2021</i></p>
|
||||
<p>The main change in this release is the addition of a new tab to the project tree on the left
|
||||
side of the main window. The regular project tree is now on a tab named "Project", while a new tab
|
||||
named "Novel" displays a simpler version of the information on the main "Outline" page. It lists
|
||||
all the headers of the novel part of the project, as well as the word count and point-of-view
|
||||
character of each section. This is an alternative way to navigate the novel part of the project.
|
||||
The various tree views are now also kept better in sync when the user selects various documents and
|
||||
headers.</p>
|
||||
<p>In addition, a new information dialog named "Project Details" has been added. It replaces the
|
||||
"Details" tab in "Project Settings", and adds more information about the novel part of the project.
|
||||
In particular, a "Table of Contents" in the "Contents" tab displays a summary of the main parts
|
||||
and chapters of the project, their total word counts, and an estimated page count. This was made in
|
||||
response to users asking for ways to estimate the total page count of the project. The page count
|
||||
is estimated based on a words per page setting, which can be changed on the dialog window.</p>
|
||||
<p>Since the tabs below the project tree now add some extra room on the GUI, some convenient
|
||||
buttons have been added in the same area, with direct access to "Project Details", "Writing
|
||||
Statistics" and "Project Setting".</p>
|
||||
<p>A few other minor changes have been made as well. The Preferences dialog has been improved with
|
||||
clearer categories and hopefully better help text. Some new options have been added too. They allow
|
||||
syntax highlighting of multi-paragraph quotes. The highlighter can now optionally accept quotes to
|
||||
be left "hanging", that is, no closing quote in the same paragraph.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
+34
-4
@@ -30,7 +30,9 @@ from datetime import datetime
|
||||
|
||||
from PyQt5.QtWidgets import qApp
|
||||
|
||||
from nw.constants import nwConst, nwUnicode
|
||||
from nw.constants import (
|
||||
nwConst, nwUnicode, nwItemClass, nwItemType, nwItemLayout
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -103,11 +105,39 @@ def isHandle(theString):
|
||||
return False
|
||||
if len(theString) != 13:
|
||||
return False
|
||||
invalidChar = False
|
||||
for c in theString:
|
||||
if c not in "0123456789abcdef":
|
||||
invalidChar = True
|
||||
return not invalidChar
|
||||
return False
|
||||
return True
|
||||
|
||||
def isTitleTag(theString):
|
||||
"""Check if a string is a valid title string.
|
||||
"""
|
||||
if not isinstance(theString, str):
|
||||
return False
|
||||
if len(theString) != 7:
|
||||
return False
|
||||
if not theString.startswith("T"):
|
||||
return False
|
||||
for c in theString[1:]:
|
||||
if c not in "0123456789":
|
||||
return False
|
||||
return True
|
||||
|
||||
def isItemClass(theString):
|
||||
"""Check if an item is a calid nwItemClass identifier.
|
||||
"""
|
||||
return theString in nwItemClass.__members__
|
||||
|
||||
def isItemType(theString):
|
||||
"""Check if an item is a calid nwItemType identifier.
|
||||
"""
|
||||
return theString in nwItemType.__members__
|
||||
|
||||
def isItemLayout(theString):
|
||||
"""Check if an item is a calid nwItemLayout identifier.
|
||||
"""
|
||||
return theString in nwItemLayout.__members__
|
||||
|
||||
def hexToInt(value, default=0):
|
||||
"""Convert a hex string to an integer.
|
||||
|
||||
+18
-6
@@ -38,6 +38,7 @@ from PyQt5.QtCore import QT_VERSION_STR, QStandardPaths, QSysInfo
|
||||
|
||||
from nw.constants import nwConst, nwFiles, nwUnicode
|
||||
from nw.common import splitVersionNumber, formatTimeStamp
|
||||
from nw.error import logException
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -143,6 +144,9 @@ class Config:
|
||||
self.allowOpenDQuote = True # Allow open-ended double quotes
|
||||
self.highlightEmph = True # Add colour to text emphasis
|
||||
|
||||
self.stopWhenIdle = True # Stop the status bar clock when the user is idle
|
||||
self.userIdleTime = 300 # Time of inactivity to consider user idle
|
||||
|
||||
## User-Selected Symbols
|
||||
self.fmtApostrophe = nwUnicode.U_RSQUO
|
||||
self.fmtSingleQuotes = [nwUnicode.U_LSQUO, nwUnicode.U_RSQUO]
|
||||
@@ -296,7 +300,7 @@ class Config:
|
||||
os.mkdir(self.confPath)
|
||||
except Exception as e:
|
||||
logger.error("Could not create folder: %s" % self.confPath)
|
||||
logger.error(str(e))
|
||||
logException()
|
||||
self.hasError = True
|
||||
self.errData.append("Could not create folder: %s" % self.confPath)
|
||||
self.errData.append(str(e))
|
||||
@@ -319,7 +323,7 @@ class Config:
|
||||
os.mkdir(self.dataPath)
|
||||
except Exception as e:
|
||||
logger.error("Could not create folder: %s" % self.dataPath)
|
||||
logger.error(str(e))
|
||||
logException()
|
||||
self.hasError = True
|
||||
self.errData.append("Could not create folder: %s" % self.dataPath)
|
||||
self.errData.append(str(e))
|
||||
@@ -364,7 +368,7 @@ class Config:
|
||||
cnfParse.read_file(inFile)
|
||||
except Exception as e:
|
||||
logger.error("Could not load config file")
|
||||
logger.error(str(e))
|
||||
logException()
|
||||
self.hasError = True
|
||||
self.errData.append("Could not load config file")
|
||||
self.errData.append(str(e))
|
||||
@@ -531,6 +535,12 @@ class Config:
|
||||
self.highlightEmph = self._parseLine(
|
||||
cnfParse, cnfSec, "highlightemph", self.CNF_BOOL, self.highlightEmph
|
||||
)
|
||||
self.stopWhenIdle = self._parseLine(
|
||||
cnfParse, cnfSec, "stopwhenidle", self.CNF_BOOL, self.stopWhenIdle
|
||||
)
|
||||
self.userIdleTime = self._parseLine(
|
||||
cnfParse, cnfSec, "useridletime", self.CNF_INT, self.userIdleTime
|
||||
)
|
||||
|
||||
## Backup
|
||||
cnfSec = "Backup"
|
||||
@@ -671,6 +681,8 @@ class Config:
|
||||
cnfParse.set(cnfSec, "allowopensquote", str(self.allowOpenSQuote))
|
||||
cnfParse.set(cnfSec, "allowopendquote", str(self.allowOpenDQuote))
|
||||
cnfParse.set(cnfSec, "highlightemph", str(self.highlightEmph))
|
||||
cnfParse.set(cnfSec, "stopwhenidle", str(self.stopWhenIdle))
|
||||
cnfParse.set(cnfSec, "useridletime", str(self.userIdleTime))
|
||||
|
||||
## Backup
|
||||
cnfSec = "Backup"
|
||||
@@ -705,7 +717,7 @@ class Config:
|
||||
self.confChanged = False
|
||||
except Exception as e:
|
||||
logger.error("Could not save config file")
|
||||
logger.error(str(e))
|
||||
logException()
|
||||
self.hasError = True
|
||||
self.errData.append("Could not save config file")
|
||||
self.errData.append(str(e))
|
||||
@@ -981,9 +993,9 @@ class Config:
|
||||
return self._unpackList(
|
||||
cnfParse.get(cnfSec, cnfName), cnfDefault, self.CNF_S_LST
|
||||
)
|
||||
except ValueError as e:
|
||||
except ValueError:
|
||||
logger.error("Failed to load value from config file.")
|
||||
logger.error(str(e))
|
||||
logException()
|
||||
return cnfDefault
|
||||
|
||||
return cnfDefault
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from nw.constants.iso import isoLanguage, isoCountry
|
||||
from nw.constants.constants import (
|
||||
nwConst, nwLists, nwRegEx, nwFiles, nwKeyWords, nwLabels, nwQuotes, nwUnicode
|
||||
nwConst, nwLists, nwRegEx, nwFiles, nwKeyWords, nwLabels, nwQuotes,
|
||||
nwUnicode, nwHtmlUnicode
|
||||
)
|
||||
from nw.constants.enum import (
|
||||
nwAlert, nwDocAction, nwItemClass, nwItemLayout, nwItemType, nwOutline,
|
||||
@@ -19,6 +20,7 @@ __all__ = [
|
||||
"nwLabels",
|
||||
"nwQuotes",
|
||||
"nwUnicode",
|
||||
"nwHtmlUnicode",
|
||||
"nwAlert",
|
||||
"nwDocAction",
|
||||
"nwItemClass",
|
||||
|
||||
@@ -55,6 +55,9 @@ class nwLists():
|
||||
# Item classes where the full list of novel layouts are allowed
|
||||
CLS_NOVEL = {nwItemClass.NOVEL, nwItemClass.ARCHIVE}
|
||||
|
||||
# Item classes which do not require items to have same class
|
||||
FREE_CLASS = {nwItemClass.ARCHIVE, nwItemClass.TRASH}
|
||||
|
||||
# END Class nwLists
|
||||
|
||||
class nwRegEx():
|
||||
@@ -83,6 +86,7 @@ class nwKeyWords:
|
||||
|
||||
TAG_KEY = "@tag"
|
||||
POV_KEY = "@pov"
|
||||
FOCUS_KEY = "@focus"
|
||||
CHAR_KEY = "@char"
|
||||
PLOT_KEY = "@plot"
|
||||
TIME_KEY = "@time"
|
||||
@@ -93,14 +97,15 @@ class nwKeyWords:
|
||||
|
||||
# Set of Valid Keys
|
||||
VALID_KEYS = {
|
||||
TAG_KEY, POV_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY,
|
||||
TAG_KEY, POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY,
|
||||
WORLD_KEY, OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY
|
||||
}
|
||||
|
||||
# Map from Keys to Item Class
|
||||
KEY_CLASS = {
|
||||
CHAR_KEY : nwItemClass.CHARACTER,
|
||||
POV_KEY : nwItemClass.CHARACTER,
|
||||
FOCUS_KEY : nwItemClass.CHARACTER,
|
||||
CHAR_KEY : nwItemClass.CHARACTER,
|
||||
PLOT_KEY : nwItemClass.PLOT,
|
||||
TIME_KEY : nwItemClass.TIMELINE,
|
||||
WORLD_KEY : nwItemClass.WORLD,
|
||||
@@ -177,6 +182,7 @@ class nwLabels():
|
||||
KEY_NAME = {
|
||||
nwKeyWords.TAG_KEY : "Tag",
|
||||
nwKeyWords.POV_KEY : "Point of View",
|
||||
nwKeyWords.FOCUS_KEY : "Focus",
|
||||
nwKeyWords.CHAR_KEY : "Characters",
|
||||
nwKeyWords.PLOT_KEY : "Plot",
|
||||
nwKeyWords.TIME_KEY : "Timeline",
|
||||
@@ -194,6 +200,7 @@ class nwLabels():
|
||||
nwOutline.WCOUNT : "Words",
|
||||
nwOutline.PCOUNT : "Pars",
|
||||
nwOutline.POV : "POV",
|
||||
nwOutline.FOCUS : "Focus",
|
||||
nwOutline.CHAR : KEY_NAME[nwKeyWords.CHAR_KEY],
|
||||
nwOutline.PLOT : KEY_NAME[nwKeyWords.PLOT_KEY],
|
||||
nwOutline.TIME : KEY_NAME[nwKeyWords.TIME_KEY],
|
||||
@@ -262,7 +269,7 @@ class nwUnicode:
|
||||
U_LCQUO = "\u300c" # Left corner bracket
|
||||
U_RCQUO = "\u300d" # Right corner bracket
|
||||
U_LWCQUO = "\u300e" # Left white corner bracket
|
||||
U_RECQUO = "\u300f" # Right white corner bracket
|
||||
U_RWCQUO = "\u300f" # Right white corner bracket
|
||||
|
||||
## Punctuation
|
||||
U_FGDASH = "\u2012" # Figure dash
|
||||
@@ -328,7 +335,7 @@ class nwUnicode:
|
||||
H_LCQUO = "「"
|
||||
H_RCQUO = "」"
|
||||
H_LWCQUO = "『"
|
||||
H_LWCQUO = "『"
|
||||
H_RWCQUO = "』"
|
||||
|
||||
## Punctuation
|
||||
H_FGDASH = "‒"
|
||||
@@ -371,3 +378,60 @@ class nwUnicode:
|
||||
H_LTRIS = "◂"
|
||||
|
||||
# END Class nwUnicode
|
||||
|
||||
class nwHtmlUnicode():
|
||||
|
||||
U_TO_H = {
|
||||
## Quotes
|
||||
nwUnicode.U_QUOT : nwUnicode.H_QUOT,
|
||||
nwUnicode.U_APOS : nwUnicode.H_APOS,
|
||||
nwUnicode.U_LAQUO : nwUnicode.H_LAQUO,
|
||||
nwUnicode.U_RAQUO : nwUnicode.H_RAQUO,
|
||||
nwUnicode.U_LSQUO : nwUnicode.H_LSQUO,
|
||||
nwUnicode.U_RSQUO : nwUnicode.H_RSQUO,
|
||||
nwUnicode.U_SBQUO : nwUnicode.H_SBQUO,
|
||||
nwUnicode.U_SUQUO : nwUnicode.H_SUQUO,
|
||||
nwUnicode.U_LDQUO : nwUnicode.H_LDQUO,
|
||||
nwUnicode.U_RDQUO : nwUnicode.H_RDQUO,
|
||||
nwUnicode.U_BDQUO : nwUnicode.H_BDQUO,
|
||||
nwUnicode.U_UDQUO : nwUnicode.H_UDQUO,
|
||||
nwUnicode.U_LSAQUO : nwUnicode.H_LSAQUO,
|
||||
nwUnicode.U_RSAQUO : nwUnicode.H_RSAQUO,
|
||||
nwUnicode.U_BDRQUO : nwUnicode.H_BDRQUO,
|
||||
nwUnicode.U_LCQUO : nwUnicode.H_LCQUO,
|
||||
nwUnicode.U_RCQUO : nwUnicode.H_RCQUO,
|
||||
nwUnicode.U_LWCQUO : nwUnicode.H_LWCQUO,
|
||||
nwUnicode.U_RWCQUO : nwUnicode.H_RWCQUO,
|
||||
|
||||
## Punctuation
|
||||
nwUnicode.U_FGDASH : nwUnicode.H_FGDASH,
|
||||
nwUnicode.U_ENDASH : nwUnicode.H_ENDASH,
|
||||
nwUnicode.U_EMDASH : nwUnicode.H_EMDASH,
|
||||
nwUnicode.U_HBAR : nwUnicode.H_HBAR,
|
||||
nwUnicode.U_HELLIP : nwUnicode.H_HELLIP,
|
||||
nwUnicode.U_MAPOSS : nwUnicode.H_MAPOSS,
|
||||
nwUnicode.U_PRIME : nwUnicode.H_PRIME,
|
||||
nwUnicode.U_DPRIME : nwUnicode.H_DPRIME,
|
||||
|
||||
## Spaces
|
||||
nwUnicode.U_NBSP : nwUnicode.H_NBSP,
|
||||
nwUnicode.U_THSP : nwUnicode.H_THSP,
|
||||
nwUnicode.U_THNBSP : nwUnicode.H_THNBSP,
|
||||
nwUnicode.U_ENSP : nwUnicode.H_ENSP,
|
||||
nwUnicode.U_EMSP : nwUnicode.H_EMSP,
|
||||
|
||||
## Symbols
|
||||
nwUnicode.U_CHECK : nwUnicode.H_CHECK,
|
||||
nwUnicode.U_CROSS : nwUnicode.H_CROSS,
|
||||
nwUnicode.U_BULL : nwUnicode.H_BULL,
|
||||
nwUnicode.U_TRBULL : nwUnicode.H_TRBULL,
|
||||
nwUnicode.U_HYBULL : nwUnicode.H_HYBULL,
|
||||
nwUnicode.U_FLOWER : nwUnicode.H_FLOWER,
|
||||
nwUnicode.U_PERMIL : nwUnicode.H_PERMIL,
|
||||
nwUnicode.U_DEGREE : nwUnicode.H_DEGREE,
|
||||
nwUnicode.U_MINUS : nwUnicode.H_MINUS,
|
||||
nwUnicode.U_TIMES : nwUnicode.H_TIMES,
|
||||
nwUnicode.U_DIVIDE : nwUnicode.H_DIVIDE,
|
||||
}
|
||||
|
||||
# END Class nwHtmlUnicode
|
||||
|
||||
@@ -127,13 +127,14 @@ class nwOutline(Enum):
|
||||
WCOUNT = 5
|
||||
PCOUNT = 6
|
||||
POV = 7
|
||||
CHAR = 8
|
||||
PLOT = 9
|
||||
TIME = 10
|
||||
WORLD = 11
|
||||
OBJECT = 12
|
||||
ENTITY = 13
|
||||
CUSTOM = 14
|
||||
SYNOP = 15
|
||||
FOCUS = 8
|
||||
CHAR = 9
|
||||
PLOT = 10
|
||||
TIME = 11
|
||||
WORLD = 12
|
||||
OBJECT = 13
|
||||
ENTITY = 14
|
||||
CUSTOM = 15
|
||||
SYNOP = 16
|
||||
|
||||
# END Enum nwOutline
|
||||
|
||||
@@ -5,6 +5,8 @@ from nw.core.index import NWIndex
|
||||
from nw.core.project import NWProject
|
||||
from nw.core.spellcheck import NWSpellCheck, NWSpellEnchant, NWSpellSimple
|
||||
from nw.core.tohtml import ToHtml
|
||||
from nw.core.toodt import ToOdt
|
||||
from nw.core.tomd import ToMarkdown
|
||||
from nw.core.tools import countWords, numberToRoman, numberToWord
|
||||
|
||||
__all__ = [
|
||||
@@ -18,4 +20,6 @@ __all__ = [
|
||||
"NWSpellEnchant",
|
||||
"NWSpellSimple",
|
||||
"ToHtml",
|
||||
"ToOdt",
|
||||
"ToMarkdown",
|
||||
]
|
||||
|
||||
+179
-59
@@ -36,11 +36,13 @@ from nw.constants import (
|
||||
)
|
||||
from nw.core.document import NWDoc
|
||||
from nw.core.tools import countWords
|
||||
from nw.common import isHandle, isTitleTag, isItemClass, isItemLayout
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class NWIndex():
|
||||
|
||||
H_VALID = ("H0", "H1", "H2", "H3", "H4")
|
||||
H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
|
||||
|
||||
def __init__(self, theProject, theParent):
|
||||
@@ -152,9 +154,14 @@ class NWIndex():
|
||||
try:
|
||||
with open(indexFile, mode="r", encoding="utf8") as inFile:
|
||||
theData = json.load(inFile)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error("Failed to load index file")
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
self.indexBroken = True
|
||||
self.theParent.makeAlert(
|
||||
"Could not load cached index file. Rebuilding index.",
|
||||
nwAlert.WARN
|
||||
)
|
||||
return False
|
||||
|
||||
self._tagIndex = theData.get("tagIndex", {})
|
||||
@@ -188,9 +195,9 @@ class NWIndex():
|
||||
"noteIndex" : self._noteIndex,
|
||||
"textCounts" : self._textCounts,
|
||||
}, outFile, indent=2)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error("Failed to save index file")
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -200,37 +207,25 @@ class NWIndex():
|
||||
elements it should.
|
||||
"""
|
||||
logger.debug("Checking index")
|
||||
self.indexBroken = False
|
||||
tStart = time()
|
||||
|
||||
try:
|
||||
for tTag in self._tagIndex:
|
||||
if len(self._tagIndex[tTag]) != 4:
|
||||
self.indexBroken = True
|
||||
|
||||
for tHandle in self._refIndex:
|
||||
for sTitle in self._refIndex[tHandle]:
|
||||
for tEntry in self._refIndex[tHandle][sTitle]["tags"]:
|
||||
if len(tEntry) != 3:
|
||||
self.indexBroken = True
|
||||
|
||||
for tHandle in self._novelIndex:
|
||||
for sLine in self._novelIndex[tHandle]:
|
||||
if len(self._novelIndex[tHandle][sLine].keys()) != 8:
|
||||
self.indexBroken = True
|
||||
|
||||
for tHandle in self._noteIndex:
|
||||
for sLine in self._noteIndex[tHandle]:
|
||||
if len(self._noteIndex[tHandle][sLine].keys()) != 8:
|
||||
self.indexBroken = True
|
||||
|
||||
for tHandle in self._textCounts:
|
||||
if len(self._textCounts[tHandle]) != 3:
|
||||
self.indexBroken = True
|
||||
self._checkTagIndex()
|
||||
self._checkRefIndex()
|
||||
self._checkNovelNoteIndex("novelIndex")
|
||||
self._checkNovelNoteIndex("noteIndex")
|
||||
self._checkTextCounts()
|
||||
self.indexBroken = False
|
||||
|
||||
except Exception:
|
||||
logger.error("Error while checking index")
|
||||
nw.logException()
|
||||
self.indexBroken = True
|
||||
|
||||
tEnd = time()
|
||||
logger.debug("Index check took %.3f ms" % ((tEnd - tStart)*1000))
|
||||
logger.debug("Index check complete")
|
||||
|
||||
if self.indexBroken:
|
||||
self.clearIndex()
|
||||
self.theParent.makeAlert(
|
||||
@@ -317,7 +312,7 @@ class NWIndex():
|
||||
if nChar == 0:
|
||||
continue
|
||||
|
||||
if aLine.startswith(r"#"):
|
||||
if aLine.startswith("#"):
|
||||
isTitle = self._indexTitle(tHandle, isNovel, aLine, nLine, itemLayout)
|
||||
if isTitle and nLine > 0:
|
||||
if nTitle > 0:
|
||||
@@ -325,11 +320,10 @@ class NWIndex():
|
||||
self._indexWordCounts(tHandle, isNovel, lastText, nTitle)
|
||||
nTitle = nLine
|
||||
|
||||
elif aLine.startswith(r"@"):
|
||||
self._indexNoteRef(tHandle, aLine, nLine, nTitle)
|
||||
self._indexTag(tHandle, aLine, nLine, nTitle, itemClass)
|
||||
elif aLine.startswith("@"):
|
||||
self._indexKeyword(tHandle, aLine, nLine, nTitle, itemClass)
|
||||
|
||||
elif aLine.startswith(r"%"):
|
||||
elif aLine.startswith("%"):
|
||||
if nTitle > 0:
|
||||
toCheck = aLine[1:].lstrip()
|
||||
synTag = toCheck[:9].lower()
|
||||
@@ -468,41 +462,28 @@ class NWIndex():
|
||||
self._noteIndex[tHandle][sTitle]["updated"] = round(time())
|
||||
return
|
||||
|
||||
def _indexNoteRef(self, tHandle, aLine, nLine, nTitle):
|
||||
def _indexKeyword(self, tHandle, aLine, nLine, nTitle, itemClass):
|
||||
"""Validate and save the information about a reference to a tag
|
||||
in another file.
|
||||
"""
|
||||
isValid, theBits, _ = self.scanThis(aLine)
|
||||
if not isValid or len(theBits) == 0:
|
||||
return False
|
||||
|
||||
sTitle = "T%06d" % nTitle
|
||||
if sTitle not in self._refIndex[tHandle]:
|
||||
return False
|
||||
|
||||
if theBits[0] == nwKeyWords.TAG_KEY:
|
||||
return False
|
||||
if not isValid or len(theBits) < 2:
|
||||
logger.warning("Skipping keyword with %d value(s) in %s" % (len(theBits), tHandle))
|
||||
return
|
||||
|
||||
if theBits[0] not in nwKeyWords.VALID_KEYS:
|
||||
return False
|
||||
|
||||
for aVal in theBits[1:]:
|
||||
self._refIndex[tHandle][sTitle]["tags"].append([nLine, theBits[0], aVal])
|
||||
|
||||
return True
|
||||
|
||||
def _indexTag(self, tHandle, aLine, nLine, nTitle, itemClass):
|
||||
"""Validate and save the information from a tag.
|
||||
"""
|
||||
isValid, theBits, thePos = self.scanThis(aLine)
|
||||
if not isValid or len(theBits) != 2:
|
||||
return False
|
||||
logger.warning("Skipping invalid keyword '%s' in %s" % (theBits[0], tHandle))
|
||||
return
|
||||
|
||||
sTitle = "T%06d" % nTitle
|
||||
if theBits[0] == nwKeyWords.TAG_KEY:
|
||||
sTitle = "T%06d" % nTitle
|
||||
self._tagIndex[theBits[1]] = [nLine, tHandle, itemClass.name, sTitle]
|
||||
|
||||
return True
|
||||
elif sTitle in self._refIndex[tHandle]:
|
||||
for aVal in theBits[1:]:
|
||||
self._refIndex[tHandle][sTitle]["tags"].append([nLine, theBits[0], aVal])
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Check @ Lines
|
||||
@@ -722,7 +703,7 @@ class NWIndex():
|
||||
for refTitle in self._refIndex[tHandle]:
|
||||
for aTag in self._refIndex[tHandle][refTitle].get("tags", []):
|
||||
if len(aTag) == 3 and (sTitle is None or sTitle == refTitle):
|
||||
if aTag[1] in theRefs: # Future-compatible. Check can be removed in 1.2.
|
||||
if aTag[1] in theRefs:
|
||||
theRefs[aTag[1]].append(aTag[2])
|
||||
|
||||
return theRefs
|
||||
@@ -784,4 +765,143 @@ class NWIndex():
|
||||
|
||||
return theHandles
|
||||
|
||||
##
|
||||
# Index Checkers
|
||||
##
|
||||
|
||||
def _checkTagIndex(self):
|
||||
"""Scan the tag index for errors.
|
||||
Waring: This function raises exceptions.
|
||||
"""
|
||||
for tTag in self._tagIndex:
|
||||
if not isinstance(tTag, str):
|
||||
raise KeyError("tagIndex key is not a string")
|
||||
|
||||
tEntry = self._tagIndex[tTag]
|
||||
if len(tEntry) != 4:
|
||||
raise IndexError("tagIndex[a] expected 4 values")
|
||||
if not isinstance(tEntry[0], int):
|
||||
raise ValueError("tagIndex[a][0] is not an integer")
|
||||
if not isHandle(tEntry[1]):
|
||||
raise ValueError("tagIndex[a][1] is not a handle")
|
||||
if not isItemClass(tEntry[2]):
|
||||
raise ValueError("tagIndex[a][2] is not an nwItemClass")
|
||||
if not isTitleTag(tEntry[3]):
|
||||
raise ValueError("tagIndex[a][3] is not a title tag")
|
||||
|
||||
return
|
||||
|
||||
def _checkRefIndex(self):
|
||||
"""Scan the reference index for errors.
|
||||
Waring: This function raises exceptions.
|
||||
"""
|
||||
for tHandle in self._refIndex:
|
||||
if not isHandle(tHandle):
|
||||
raise KeyError("refIndex key is not a handle")
|
||||
|
||||
hEntry = self._refIndex[tHandle]
|
||||
for sTitle in hEntry:
|
||||
if not isTitleTag(sTitle):
|
||||
raise KeyError("refIndex[a] key is not a title tag")
|
||||
|
||||
sEntry = hEntry[sTitle]
|
||||
if "tags" not in sEntry:
|
||||
raise KeyError("refIndex[a][b] has no 'tag' key")
|
||||
for tEntry in sEntry["tags"]:
|
||||
if len(tEntry) != 3:
|
||||
raise IndexError("refIndex[a][b][tags][i] expected 3 values")
|
||||
if not isinstance(tEntry[0], int):
|
||||
raise ValueError("refIndex[a][b][tags][i][0] is not an integer")
|
||||
if not tEntry[1] in nwKeyWords.VALID_KEYS:
|
||||
raise ValueError("refIndex[a][b][tags][i][1] is not a keyword")
|
||||
if not isinstance(tEntry[2], str):
|
||||
raise ValueError("refIndex[a][b][tags][i][2] is not a string")
|
||||
|
||||
if "updated" not in sEntry:
|
||||
raise KeyError("refIndex[a][b] has no 'updated' key")
|
||||
if not isinstance(sEntry["updated"], int):
|
||||
raise ValueError("%refIndex[a][b][updated] is not an integer")
|
||||
|
||||
return
|
||||
|
||||
def _checkNovelNoteIndex(self, idxName):
|
||||
"""Scan the novel or note index for errors.
|
||||
Waring: This function raises exceptions.
|
||||
"""
|
||||
if idxName == "novelIndex":
|
||||
theIndex = self._novelIndex
|
||||
elif idxName == "noteIndex":
|
||||
theIndex = self._noteIndex
|
||||
else:
|
||||
raise IndexError("Unknown index %s" % idxName)
|
||||
|
||||
for tHandle in theIndex:
|
||||
if not isHandle(tHandle):
|
||||
raise KeyError("%s key is not a handle" % idxName)
|
||||
|
||||
hEntry = theIndex[tHandle]
|
||||
for sTitle in theIndex[tHandle]:
|
||||
if not isTitleTag(sTitle):
|
||||
raise KeyError("%s[a] key is not a title tag" % idxName)
|
||||
|
||||
sEntry = hEntry[sTitle]
|
||||
if len(sEntry) != 8:
|
||||
raise IndexError("%s[a][b] expected 8 values" % idxName)
|
||||
|
||||
if "level" not in sEntry:
|
||||
raise KeyError("%s[a][b] has no 'level' key" % idxName)
|
||||
if "title" not in sEntry:
|
||||
raise KeyError("%s[a][b] has no 'title' key" % idxName)
|
||||
if "layout" not in sEntry:
|
||||
raise KeyError("%s[a][b] has no 'layout' key" % idxName)
|
||||
if "synopsis" not in sEntry:
|
||||
raise KeyError("%s[a][b] has no 'synopsis' key" % idxName)
|
||||
if "cCount" not in sEntry:
|
||||
raise KeyError("%s[a][b] has no 'cCount' key" % idxName)
|
||||
if "wCount" not in sEntry:
|
||||
raise KeyError("%s[a][b] has no 'wCount' key" % idxName)
|
||||
if "pCount" not in sEntry:
|
||||
raise KeyError("%s[a][b] has no 'pCount' key" % idxName)
|
||||
if "updated" not in sEntry:
|
||||
raise KeyError("%s[a][b] has no 'updated' key" % idxName)
|
||||
|
||||
if not sEntry["level"] in self.H_VALID:
|
||||
raise ValueError("%s[a][b][level] is not a header level" % idxName)
|
||||
if not isinstance(sEntry["title"], str):
|
||||
raise ValueError("%s[a][b][title] is not a string" % idxName)
|
||||
if not isItemLayout(sEntry["layout"]):
|
||||
raise ValueError("%s[a][b][layout] is not an nwItemLayout" % idxName)
|
||||
if not isinstance(sEntry["synopsis"], str):
|
||||
raise ValueError("%s[a][b][synopsis] is not a string" % idxName)
|
||||
if not isinstance(sEntry["cCount"], int):
|
||||
raise ValueError("%s[a][b][cCount] is not an integer" % idxName)
|
||||
if not isinstance(sEntry["wCount"], int):
|
||||
raise ValueError("%s[a][b][wCount] is not an integer" % idxName)
|
||||
if not isinstance(sEntry["pCount"], int):
|
||||
raise ValueError("%s[a][b][pCount] is not an integer" % idxName)
|
||||
if not isinstance(sEntry["updated"], int):
|
||||
raise ValueError("%s[a][b][updated] is not an integer" % idxName)
|
||||
|
||||
return
|
||||
|
||||
def _checkTextCounts(self):
|
||||
"""Scan the text counts index for errors.
|
||||
Waring: This function raises exceptions.
|
||||
"""
|
||||
for tHandle in self._textCounts:
|
||||
if not isHandle(tHandle):
|
||||
raise KeyError("textCounts key is not a handle")
|
||||
|
||||
tEntry = self._textCounts[tHandle]
|
||||
if len(tEntry) != 3:
|
||||
raise IndexError("textCounts[a] expected 3 values")
|
||||
if not isinstance(tEntry[0], int):
|
||||
raise ValueError("textCounts[a][0] is not an integer")
|
||||
if not isinstance(tEntry[1], int):
|
||||
raise ValueError("textCounts[a][1] is not an integer")
|
||||
if not isinstance(tEntry[2], int):
|
||||
raise ValueError("textCounts[a][2] is not an integer")
|
||||
|
||||
return
|
||||
|
||||
# END Class NWIndex
|
||||
|
||||
+6
-4
@@ -28,8 +28,10 @@ import logging
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from nw.common import checkInt, isHandle
|
||||
from nw.constants import nwItemType, nwItemClass, nwItemLayout
|
||||
from nw.common import (
|
||||
checkInt, isHandle, isItemClass, isItemLayout, isItemType
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -201,7 +203,7 @@ class NWItem():
|
||||
"""
|
||||
if isinstance(theType, nwItemType):
|
||||
self.itemType = theType
|
||||
elif theType in nwItemType.__members__:
|
||||
elif isItemType(theType):
|
||||
self.itemType = nwItemType[theType]
|
||||
else:
|
||||
logger.error("Unrecognised item type '%s'" % theType)
|
||||
@@ -214,7 +216,7 @@ class NWItem():
|
||||
"""
|
||||
if isinstance(theClass, nwItemClass):
|
||||
self.itemClass = theClass
|
||||
elif theClass in nwItemClass.__members__:
|
||||
elif isItemClass(theClass):
|
||||
self.itemClass = nwItemClass[theClass]
|
||||
else:
|
||||
logger.error("Unrecognised item class '%s'" % theClass)
|
||||
@@ -227,7 +229,7 @@ class NWItem():
|
||||
"""
|
||||
if isinstance(theLayout, nwItemLayout):
|
||||
self.itemLayout = theLayout
|
||||
elif theLayout in nwItemLayout.__members__:
|
||||
elif isItemLayout(theLayout):
|
||||
self.itemLayout = nwItemLayout[theLayout]
|
||||
else:
|
||||
logger.error("Unrecognised item layout '%s'" % theLayout)
|
||||
|
||||
+13
-4
@@ -25,6 +25,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import nw
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
@@ -46,6 +47,7 @@ class OptionState():
|
||||
"widthCol0",
|
||||
"widthCol1",
|
||||
"widthCol2",
|
||||
"widthCol3",
|
||||
"sortCol",
|
||||
"sortOrder",
|
||||
"incNovel",
|
||||
@@ -53,6 +55,7 @@ class OptionState():
|
||||
"hideZeros",
|
||||
"hideNegative",
|
||||
"groupByDay",
|
||||
"showIdleTime",
|
||||
"histMax",
|
||||
},
|
||||
"GuiDocSplit": {
|
||||
@@ -70,12 +73,14 @@ class OptionState():
|
||||
"excludeBody",
|
||||
"textFont",
|
||||
"textSize",
|
||||
"lineHeight",
|
||||
"noStyling",
|
||||
"incSynopsis",
|
||||
"incComments",
|
||||
"incKeywords",
|
||||
"incBodyText",
|
||||
"replaceTabs",
|
||||
"replaceUCode",
|
||||
},
|
||||
"GuiOutline": {
|
||||
"headerOrder",
|
||||
@@ -99,6 +104,10 @@ class OptionState():
|
||||
"countFrom",
|
||||
"clearDouble",
|
||||
},
|
||||
"GuiWordList": {
|
||||
"winWidth",
|
||||
"winHeight",
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
@@ -121,9 +130,9 @@ class OptionState():
|
||||
try:
|
||||
with open(stateFile, mode="r", encoding="utf8") as inFile:
|
||||
theState = json.load(inFile)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error("Failed to load GUI options file")
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
return False
|
||||
|
||||
# Filter out unused variables
|
||||
@@ -148,9 +157,9 @@ class OptionState():
|
||||
try:
|
||||
with open(stateFile, mode="w+", encoding="utf8") as outFile:
|
||||
json.dump(self.theState, outFile, indent=2)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error("Failed to save GUI options file")
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
+30
-24
@@ -726,12 +726,12 @@ class NWProject():
|
||||
|
||||
return True
|
||||
|
||||
def closeProject(self):
|
||||
def closeProject(self, idleTime=0):
|
||||
"""Close the current project and clear all meta data.
|
||||
"""
|
||||
self.optState.saveSettings()
|
||||
self.projTree.writeToCFile()
|
||||
self._appendSessionStats()
|
||||
self._appendSessionStats(idleTime)
|
||||
self._clearLockFile()
|
||||
self.clearProject()
|
||||
self.lockedBy = None
|
||||
@@ -1219,9 +1219,9 @@ class NWProject():
|
||||
if len(theLines) != 4:
|
||||
return ["ERROR"]
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error("Failed to read project lockfile")
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
return ["ERROR"]
|
||||
|
||||
return theLines
|
||||
@@ -1240,9 +1240,9 @@ class NWProject():
|
||||
outFile.write("%s\n" % self.mainConf.kernelVer)
|
||||
outFile.write("%d\n" % time())
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error("Failed to write project lockfile")
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -1257,9 +1257,9 @@ class NWProject():
|
||||
if os.path.isfile(lockFile):
|
||||
try:
|
||||
os.unlink(lockFile)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error("Failed to remove project lockfile")
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -1389,7 +1389,7 @@ class NWProject():
|
||||
|
||||
return True
|
||||
|
||||
def _appendSessionStats(self):
|
||||
def _appendSessionStats(self, idleTime):
|
||||
"""Append session statistics to the sessions log file.
|
||||
"""
|
||||
if not self.ensureFolderStructure():
|
||||
@@ -1413,20 +1413,21 @@ class NWProject():
|
||||
# It's a new file, so add a header
|
||||
if self.lastWCount > 0:
|
||||
outFile.write("# Offset %d\n" % self.lastWCount)
|
||||
outFile.write("# %-17s %-19s %8s %8s\n" % (
|
||||
"Start Time", "End Time", "Novel", "Notes"
|
||||
outFile.write("# %-17s %-19s %8s %8s %8s\n" % (
|
||||
"Start Time", "End Time", "Novel", "Notes", "Idle"
|
||||
))
|
||||
|
||||
outFile.write("%-19s %-19s %8d %8d\n" % (
|
||||
outFile.write("%-19s %-19s %8d %8d %8d\n" % (
|
||||
formatTimeStamp(self.projOpened),
|
||||
formatTimeStamp(nowTime),
|
||||
self.novelWCount,
|
||||
self.notesWCount,
|
||||
int(idleTime),
|
||||
))
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error("Failed to write session stats file")
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -1462,17 +1463,19 @@ class NWProject():
|
||||
os.rename(theFile, newPath)
|
||||
logger.info("Moved file: %s" % theFile)
|
||||
logger.info("New location: %s" % newPath)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
except Exception:
|
||||
errList.append("Could not move: %s" % theFile)
|
||||
logger.error("Could not move: %s" % theFile)
|
||||
nw.logException()
|
||||
|
||||
elif len(dataItem) == 21 and dataItem.endswith("_main.bak"):
|
||||
try:
|
||||
os.unlink(theFile)
|
||||
logger.info("Deleted file: %s" % theFile)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
except Exception:
|
||||
errList.append("Could not delete: %s" % theFile)
|
||||
logger.error("Could not delete: %s" % theFile)
|
||||
nw.logException()
|
||||
|
||||
else:
|
||||
theErr = self._moveUnknownItem(theData, dataItem)
|
||||
@@ -1484,9 +1487,10 @@ class NWProject():
|
||||
try:
|
||||
os.rmdir(theData)
|
||||
logger.info("Removed folder: %s" % theFolder)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
except Exception:
|
||||
errList.append("Failed to remove: %s" % theFolder)
|
||||
logger.error("Failed to remove: %s" % theFolder)
|
||||
nw.logException()
|
||||
|
||||
return errList
|
||||
|
||||
@@ -1504,8 +1508,9 @@ class NWProject():
|
||||
try:
|
||||
os.rename(theSrc, theDst)
|
||||
logger.info("Moved to junk: %s" % theSrc)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
except Exception:
|
||||
logger.error("Could not move item %s to junk." % theSrc)
|
||||
nw.logException()
|
||||
return "Could not move item %s to junk." % theSrc
|
||||
|
||||
return ""
|
||||
@@ -1538,8 +1543,9 @@ class NWProject():
|
||||
logger.info("Deleting: %s" % rmFile)
|
||||
try:
|
||||
os.unlink(rmFile)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
except Exception:
|
||||
logger.error("Could not delete: %s" % rmFile)
|
||||
nw.logException()
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
+17
-9
@@ -72,9 +72,9 @@ class NWSpellCheck():
|
||||
with open(self.projectDict, mode="a+", encoding="utf-8") as outFile:
|
||||
outFile.write("%s\n" % newWord)
|
||||
self.projDict.append(newWord)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error("Failed to add word to project word list %s" % str(self.projectDict))
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
@@ -123,9 +123,10 @@ class NWSpellCheck():
|
||||
if len(theLine) > 0 and theLine not in self.projDict:
|
||||
self.projDict.append(theLine)
|
||||
logger.debug("Project word list contains %d words" % len(self.projDict))
|
||||
except Exception as e:
|
||||
|
||||
except Exception:
|
||||
logger.error("Failed to load project word list")
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -141,6 +142,7 @@ class NWSpellEnchant(NWSpellCheck):
|
||||
def __init__(self):
|
||||
NWSpellCheck.__init__(self)
|
||||
logger.debug("Enchant spell checking activated")
|
||||
self.theBroker = None
|
||||
return
|
||||
|
||||
def setLanguage(self, theLang, projectDict=None):
|
||||
@@ -150,9 +152,15 @@ class NWSpellEnchant(NWSpellCheck):
|
||||
"""
|
||||
try:
|
||||
import enchant
|
||||
self.theDict = enchant.Dict(theLang)
|
||||
if self.theBroker is not None:
|
||||
logger.verbose("Deleting old pyenchant broker")
|
||||
del self.theBroker
|
||||
|
||||
self.theBroker = enchant.Broker()
|
||||
self.theDict = self.theBroker.request_dict(theLang)
|
||||
self.spellLanguage = theLang
|
||||
logger.debug("Enchant spell checking for language %s loaded" % theLang)
|
||||
|
||||
except Exception:
|
||||
logger.error("Failed to load enchant spell checking for language %s" % theLang)
|
||||
self.theDict = NWSpellEnchantDummy()
|
||||
@@ -201,9 +209,9 @@ class NWSpellEnchant(NWSpellCheck):
|
||||
try:
|
||||
spTag = self.theDict.tag
|
||||
spName = self.theDict.provider.name
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error("Failed to extract information about the dictionary")
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
spTag = ""
|
||||
spName = ""
|
||||
|
||||
@@ -263,9 +271,9 @@ class NWSpellSimple(NWSpellCheck):
|
||||
logger.debug("Dictionary contains %d words" % len(self.theWords))
|
||||
self.spellLanguage = theLang
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error("Failed to load spell check word list for language %s" % theLang)
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
self.spellLanguage = None
|
||||
|
||||
self._readProjectDictionary(projectDict)
|
||||
|
||||
+179
-80
@@ -25,10 +25,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from nw.core.tokenizer import Tokenizer
|
||||
from nw.constants import nwUnicode, nwLabels, nwKeyWords
|
||||
from nw.constants import nwLabels, nwKeyWords, nwHtmlUnicode
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -41,25 +40,13 @@ class ToHtml(Tokenizer):
|
||||
def __init__(self, theProject, theParent):
|
||||
Tokenizer.__init__(self, theProject, theParent)
|
||||
|
||||
self.genMode = self.M_EXPORT
|
||||
self.genMode = self.M_EXPORT
|
||||
self.cssStyles = True
|
||||
self.fullHTML = []
|
||||
|
||||
self.repDict = {
|
||||
"<" : "<",
|
||||
">" : ">",
|
||||
"&" : "&",
|
||||
nwUnicode.U_ENDASH : nwUnicode.H_ENDASH,
|
||||
nwUnicode.U_EMDASH : nwUnicode.H_EMDASH,
|
||||
nwUnicode.U_HELLIP : nwUnicode.H_HELLIP,
|
||||
nwUnicode.U_NBSP : nwUnicode.H_NBSP,
|
||||
nwUnicode.U_THSP : nwUnicode.H_THSP,
|
||||
nwUnicode.U_THNBSP : nwUnicode.H_THNBSP,
|
||||
nwUnicode.U_MAPOSS : nwUnicode.H_RSQUO,
|
||||
}
|
||||
self.revDict = {}
|
||||
self.reReplace = []
|
||||
self.reReverse = []
|
||||
self._buildRegEx()
|
||||
# Internals
|
||||
self._trMap = {}
|
||||
self.setReplaceUnicode(False)
|
||||
|
||||
return
|
||||
|
||||
@@ -86,31 +73,38 @@ class ToHtml(Tokenizer):
|
||||
self.cssStyles = cssStyles
|
||||
return
|
||||
|
||||
def setReplaceUnicode(self, doReplace):
|
||||
"""Set the translation map to either minimal or full unicode to
|
||||
html entities replacement.
|
||||
"""
|
||||
# Control characters must always be replaced
|
||||
self._trMap = str.maketrans({
|
||||
"<" : "<",
|
||||
">" : ">",
|
||||
"&" : "&",
|
||||
})
|
||||
|
||||
if doReplace:
|
||||
# Extend to all relevant Unicode characters
|
||||
self._trMap.update(str.maketrans(nwHtmlUnicode.U_TO_H))
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Class Methods
|
||||
##
|
||||
|
||||
def doAutoReplace(self):
|
||||
def getFullResultSize(self):
|
||||
"""Return the size of the full HTML result.
|
||||
"""
|
||||
return sum([len(x) for x in self.fullHTML])
|
||||
|
||||
def doPreProcessing(self):
|
||||
"""Extend the auto-replace to also properly encode some unicode
|
||||
characters into their respective HTML entities.
|
||||
"""
|
||||
Tokenizer.doAutoReplace(self)
|
||||
self.theText = self.reReplace.sub(
|
||||
lambda x: self.repDict[x.group(0)], self.theText
|
||||
)
|
||||
return
|
||||
|
||||
def doPostProcessing(self):
|
||||
"""Reverse the html entities replacement on the markdown text.
|
||||
Otherwise, all the &something; bits will also be in there.
|
||||
"""
|
||||
Tokenizer.doPostProcessing(self)
|
||||
if self.genMode == self.M_PREVIEW:
|
||||
# Doesn't matter for preview as we don't use the markdown
|
||||
return
|
||||
self.theMarkdown = self.reReverse.sub(
|
||||
lambda x: self.revDict[x.group(0)], self.theMarkdown
|
||||
)
|
||||
Tokenizer.doPreProcessing(self)
|
||||
self.theText = self.theText.translate(self._trMap)
|
||||
return
|
||||
|
||||
def doConvert(self):
|
||||
@@ -158,6 +152,7 @@ class ToHtml(Tokenizer):
|
||||
parStyle = None
|
||||
tmpResult = []
|
||||
hasHardBreak = False
|
||||
|
||||
for tType, tLine, tText, tFormat, tStyle in self.theTokens:
|
||||
|
||||
# Styles
|
||||
@@ -165,24 +160,27 @@ class ToHtml(Tokenizer):
|
||||
if tStyle is not None and self.cssStyles:
|
||||
if tStyle & self.A_LEFT:
|
||||
aStyle.append("text-align: left;")
|
||||
if tStyle & self.A_RIGHT:
|
||||
elif tStyle & self.A_RIGHT:
|
||||
aStyle.append("text-align: right;")
|
||||
if tStyle & self.A_CENTRE:
|
||||
elif tStyle & self.A_CENTRE:
|
||||
aStyle.append("text-align: center;")
|
||||
if tStyle & self.A_JUSTIFY:
|
||||
elif tStyle & self.A_JUSTIFY:
|
||||
aStyle.append("text-align: justify;")
|
||||
|
||||
if tStyle & self.A_PBB:
|
||||
aStyle.append("page-break-before: always;")
|
||||
if tStyle & self.A_PBB_AV:
|
||||
aStyle.append("page-break-before: avoid;")
|
||||
if tStyle & self.A_PBB_NO:
|
||||
aStyle.append("page-break-before: never;")
|
||||
elif tStyle & self.A_PBB_AUT:
|
||||
aStyle.append("page-break-before: auto;")
|
||||
|
||||
if tStyle & self.A_PBA:
|
||||
aStyle.append("page-break-after: always;")
|
||||
if tStyle & self.A_PBA_AV:
|
||||
aStyle.append("page-break-after: avoid;")
|
||||
if tStyle & self.A_PBA_NO:
|
||||
aStyle.append("page-break-after: never;")
|
||||
elif tStyle & self.A_PBA_AUT:
|
||||
aStyle.append("page-break-after: auto;")
|
||||
|
||||
if tStyle & self.A_Z_BTMMRG:
|
||||
aStyle.append("margin-bottom: 0;")
|
||||
if tStyle & self.A_Z_TOPMRG:
|
||||
aStyle.append("margin-top: 0;")
|
||||
|
||||
if len(aStyle) > 0:
|
||||
hStyle = " style='%s'" % (" ".join(aStyle))
|
||||
@@ -240,12 +238,12 @@ class ToHtml(Tokenizer):
|
||||
if parStyle is None:
|
||||
parStyle = hStyle
|
||||
for xPos, xLen, xFmt in reversed(tFormat):
|
||||
tTemp = tTemp[:xPos]+htmlTags[xFmt]+tTemp[xPos+xLen:]
|
||||
tTemp = tTemp[:xPos] + htmlTags[xFmt] + tTemp[xPos+xLen:]
|
||||
if tText.endswith(" "):
|
||||
thisPar.append(tTemp.rstrip()+"<br/>")
|
||||
thisPar.append(tTemp.rstrip() + "<br/>")
|
||||
hasHardBreak = True
|
||||
else:
|
||||
thisPar.append(tTemp.rstrip()+" ")
|
||||
thisPar.append(tTemp.rstrip() + " ")
|
||||
|
||||
elif tType == self.T_SYNOPSIS and self.doSynopsis:
|
||||
tmpResult.append(self._formatSynopsis(tText))
|
||||
@@ -254,11 +252,60 @@ class ToHtml(Tokenizer):
|
||||
tmpResult.append(self._formatComments(tText))
|
||||
|
||||
elif tType == self.T_KEYWORD and self.doKeywords:
|
||||
tmpResult.append(self._formatKeywords(tText))
|
||||
tTemp = "<p%s>%s</p>\n" % (hStyle, self._formatKeywords(tText))
|
||||
tmpResult.append(tTemp)
|
||||
|
||||
self.theResult = "".join(tmpResult)
|
||||
tmpResult = []
|
||||
|
||||
if self.genMode != self.M_PREVIEW:
|
||||
self.fullHTML.append(self.theResult)
|
||||
|
||||
return
|
||||
|
||||
def saveHTML5(self, savePath):
|
||||
"""Save the data to an .html file.
|
||||
"""
|
||||
with open(savePath, mode="w", encoding="utf8") as outFile:
|
||||
theStyle = self.getStyleSheet()
|
||||
theStyle.append("article {width: 800px; margin: 40px auto;}")
|
||||
bodyText = "".join(self.fullHTML)
|
||||
bodyText = bodyText.replace("\t", "	").rstrip()
|
||||
|
||||
theHtml = (
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html>\n"
|
||||
"<head>\n"
|
||||
"<meta charset='utf-8'>\n"
|
||||
"<title>{projTitle:s}</title>\n"
|
||||
"</head>\n"
|
||||
"<style>\n"
|
||||
"{htmlStyle:s}\n"
|
||||
"</style>\n"
|
||||
"<body>\n"
|
||||
"<article>\n"
|
||||
"{bodyText:s}\n"
|
||||
"</article>\n"
|
||||
"</body>\n"
|
||||
"</html>\n"
|
||||
).format(
|
||||
projTitle = self.theProject.projName,
|
||||
htmlStyle = "\n".join(theStyle),
|
||||
bodyText = bodyText,
|
||||
)
|
||||
outFile.write(theHtml)
|
||||
|
||||
return
|
||||
|
||||
def replaceTabs(self, nSpaces=8, spaceChar=" "):
|
||||
"""Replace tabs with spaces in the html.
|
||||
"""
|
||||
htmlText = []
|
||||
eightSpace = spaceChar*nSpaces
|
||||
for aLine in self.fullHTML:
|
||||
htmlText.append(aLine.replace("\t", eightSpace))
|
||||
|
||||
self.fullHTML = htmlText
|
||||
return
|
||||
|
||||
def getStyleSheet(self):
|
||||
@@ -268,22 +315,86 @@ class ToHtml(Tokenizer):
|
||||
if not self.cssStyles:
|
||||
return theStyles
|
||||
|
||||
if self.doJustify:
|
||||
theStyles.append(r"p {text-align: justify;}")
|
||||
else:
|
||||
theStyles.append(r"p {text-align: left;}")
|
||||
mScale = self.lineHeight/1.15
|
||||
textAlign = "justify" if self.doJustify else "left"
|
||||
|
||||
theStyles.append(r"h1, h2 {color: rgb(66, 113, 174);}")
|
||||
theStyles.append(r"h3, h4 {color: rgb(50, 50, 50);}")
|
||||
theStyles.append(r"h1, h2, h3, h4 {page-break-after: avoid;}")
|
||||
theStyles.append(r"a {color: rgb(66, 113, 174);}")
|
||||
theStyles.append(r".title {font-size: 2.5em;}")
|
||||
theStyles.append(r".tags {color: rgb(245, 135, 31); font-weight: bold;}")
|
||||
theStyles.append(r".break {text-align: left;}")
|
||||
theStyles.append(r".sep {text-align: center; margin-top: 1em; margin-bottom: 1em;}")
|
||||
theStyles.append(r".skip {margin-top: 1em; margin-bottom: 1em;}")
|
||||
theStyles.append(r".synopsis {font-style: italic;}")
|
||||
theStyles.append(r".comment {font-style: italic; color: rgb(100, 100, 100);}")
|
||||
theStyles.append("body {font-family: '%s'; font-size: %dpt;}" % (
|
||||
self.textFont, self.textSize
|
||||
))
|
||||
theStyles.append((
|
||||
"p {"
|
||||
"text-align: %s; line-height: %d%%; "
|
||||
"margin-top: %.2fem; margin-bottom: %.2fem;"
|
||||
"}"
|
||||
) % (
|
||||
textAlign,
|
||||
round(100 * self.lineHeight),
|
||||
mScale * self.marginText[0],
|
||||
mScale * self.marginText[1],
|
||||
))
|
||||
theStyles.append((
|
||||
"h1 {"
|
||||
"color: rgb(66, 113, 174); "
|
||||
"page-break-after: avoid; "
|
||||
"margin-top: %.2fem; "
|
||||
"margin-bottom: %.2fem;"
|
||||
"}"
|
||||
) % (
|
||||
mScale * self.marginHead1[0], mScale * self.marginHead1[1]
|
||||
))
|
||||
theStyles.append((
|
||||
"h2 {"
|
||||
"color: rgb(66, 113, 174); "
|
||||
"page-break-after: avoid; "
|
||||
"margin-top: %.2fem; "
|
||||
"margin-bottom: %.2fem;"
|
||||
"}"
|
||||
) % (
|
||||
mScale * self.marginHead2[0], mScale * self.marginHead2[1]
|
||||
))
|
||||
theStyles.append((
|
||||
"h3 {"
|
||||
"color: rgb(50, 50, 50); "
|
||||
"page-break-after: avoid; "
|
||||
"margin-top: %.2fem; "
|
||||
"margin-bottom: %.2fem;"
|
||||
"}"
|
||||
) % (
|
||||
mScale * self.marginHead3[0], mScale * self.marginHead3[1]
|
||||
))
|
||||
theStyles.append((
|
||||
"h4 {"
|
||||
"color: rgb(50, 50, 50); "
|
||||
"page-break-after: avoid; "
|
||||
"margin-top: %.2fem; "
|
||||
"margin-bottom: %.2fem;"
|
||||
"}"
|
||||
) % (
|
||||
mScale * self.marginHead4[0], mScale * self.marginHead4[1]
|
||||
))
|
||||
theStyles.append((
|
||||
".title {"
|
||||
"font-size: 2.5em; "
|
||||
"margin-top: %.2fem; "
|
||||
"margin-bottom: %.2fem;"
|
||||
"}"
|
||||
) % (
|
||||
mScale * self.marginTitle[0], mScale * self.marginTitle[1]
|
||||
))
|
||||
theStyles.append((
|
||||
".sep, .skip {"
|
||||
"text-align: center; "
|
||||
"margin-top: %.2fem; "
|
||||
"margin-bottom: %.2fem;}"
|
||||
) % (
|
||||
mScale, mScale
|
||||
))
|
||||
|
||||
theStyles.append("a {color: rgb(66, 113, 174);}")
|
||||
theStyles.append(".tags {color: rgb(245, 135, 31); font-weight: bold;}")
|
||||
theStyles.append(".break {text-align: left;}")
|
||||
theStyles.append(".synopsis {font-style: italic;}")
|
||||
theStyles.append(".comment {font-style: italic; color: rgb(100, 100, 100);}")
|
||||
|
||||
return theStyles
|
||||
|
||||
@@ -337,18 +448,6 @@ class ToHtml(Tokenizer):
|
||||
))
|
||||
retText += ", ".join(refTags)
|
||||
|
||||
return "<div>%s</div>\n" % retText
|
||||
|
||||
def _buildRegEx(self):
|
||||
"""Build the regular expressions
|
||||
"""
|
||||
self.revDict = dict(map(reversed, self.repDict.items()))
|
||||
self.reReplace = re.compile(
|
||||
"|".join([re.escape(k) for k in self.repDict.keys()]), flags=re.DOTALL
|
||||
)
|
||||
self.reReverse = re.compile(
|
||||
"|".join([re.escape(k) for k in self.revDict.keys()]), flags=re.DOTALL
|
||||
)
|
||||
return
|
||||
return retText
|
||||
|
||||
# END Class ToHtml
|
||||
|
||||
+166
-193
@@ -32,7 +32,7 @@ from PyQt5.QtCore import QRegularExpression
|
||||
|
||||
from nw.core.document import NWDoc
|
||||
from nw.core.tools import numberToWord, numberToRoman
|
||||
from nw.constants import nwConst, nwItemLayout, nwItemType, nwRegEx
|
||||
from nw.constants import nwConst, nwUnicode, nwItemLayout, nwItemType, nwRegEx
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -67,11 +67,11 @@ class Tokenizer():
|
||||
A_CENTRE = 0x0004 # Centred
|
||||
A_JUSTIFY = 0x0008 # Justified
|
||||
A_PBB = 0x0010 # Page break before always
|
||||
A_PBB_AV = 0x0020 # Page break before avoid
|
||||
A_PBB_NO = 0x0040 # Page break before never
|
||||
A_PBA = 0x0080 # Page break after always
|
||||
A_PBA_AV = 0x0100 # Page break after avoid
|
||||
A_PBA_NO = 0x0200 # Page break after avoid
|
||||
A_PBB_AUT = 0x0020 # Page break before auto
|
||||
A_PBA = 0x0040 # Page break after always
|
||||
A_PBA_AUT = 0x0080 # Page break after auto
|
||||
A_Z_TOPMRG = 0x0100 # Zero top margin
|
||||
A_Z_BTMMRG = 0x0200 # Zero bottom margin
|
||||
|
||||
def __init__(self, theProject, theParent):
|
||||
|
||||
@@ -79,20 +79,36 @@ class Tokenizer():
|
||||
self.theParent = theParent
|
||||
|
||||
# Data Variables
|
||||
self.theText = None # The raw text to be tokenized
|
||||
self.theText = "" # The raw text to be tokenized
|
||||
self.theHandle = None # The handle associated with the text
|
||||
self.theItem = None # The NWItem associated with the handle
|
||||
self.theTokens = None # The list of the processed tokens
|
||||
self.theResult = None # The result text after conversion
|
||||
self.theMarkdown = None # The result text in novelWriter markdown
|
||||
self.theTokens = [] # The list of the processed tokens
|
||||
self.theResult = "" # The result of the last document
|
||||
|
||||
self.keepMarkdown = False # Whether to keep the markdown text
|
||||
self.theMarkdown = [] # The result novelWriter markdown of all documents
|
||||
|
||||
# User Settings
|
||||
self.doBodyText = True # Include body text
|
||||
self.doSynopsis = False # Also process synopsis comments
|
||||
self.doComments = False # Also process comments
|
||||
self.doKeywords = False # Also process keywords like tags and references
|
||||
self.doJustify = False # Justify text
|
||||
self.textFont = "Serif" # Output text font
|
||||
self.textSize = 11 # Output text size
|
||||
self.textFixed = False # Fixed width text
|
||||
self.lineHeight = 1.15 # Line height
|
||||
self.doJustify = False # Justify text
|
||||
self.doBodyText = True # Include body text
|
||||
self.doSynopsis = False # Also process synopsis comments
|
||||
self.doComments = False # Also process comments
|
||||
self.doKeywords = False # Also process keywords like tags and references
|
||||
|
||||
## Title Margins
|
||||
self.marginTitle = (1.000, 0.500)
|
||||
self.marginHead1 = (1.000, 0.500)
|
||||
self.marginHead2 = (0.834, 0.500)
|
||||
self.marginHead3 = (0.584, 0.500)
|
||||
self.marginHead4 = (0.584, 0.500)
|
||||
self.marginText = (0.000, 0.584)
|
||||
self.marginMeta = (0.000, 0.584)
|
||||
|
||||
## Title Formats
|
||||
self.fmtTitle = "%title%" # Formatting for titles
|
||||
self.fmtChapter = "%title%" # Formatting for numbered chapters
|
||||
self.fmtUnNum = "%title%" # Formatting for unnumbered chapters
|
||||
@@ -153,6 +169,48 @@ class Tokenizer():
|
||||
self.hideSection = hideSection
|
||||
return
|
||||
|
||||
def setFont(self, textFont, textSize, textFixed=False):
|
||||
self.textFont = textFont
|
||||
self.textSize = round(int(textSize))
|
||||
self.textFixed = textFixed
|
||||
return
|
||||
|
||||
def setLineHeight(self, lineHeight):
|
||||
self.lineHeight = float(lineHeight)
|
||||
return
|
||||
|
||||
def setJustify(self, doJustify):
|
||||
self.doJustify = doJustify
|
||||
return
|
||||
|
||||
def setTitleMargins(self, mUpper, mLower):
|
||||
self.marginTitle = (float(mUpper), float(mLower))
|
||||
return
|
||||
|
||||
def setHead1Margins(self, mUpper, mLower):
|
||||
self.marginHead1 = (float(mUpper), float(mLower))
|
||||
return
|
||||
|
||||
def setHead2Margins(self, mUpper, mLower):
|
||||
self.marginHead2 = (float(mUpper), float(mLower))
|
||||
return
|
||||
|
||||
def setHead3Margins(self, mUpper, mLower):
|
||||
self.marginHead3 = (float(mUpper), float(mLower))
|
||||
return
|
||||
|
||||
def setHead4Margins(self, mUpper, mLower):
|
||||
self.marginHead4 = (float(mUpper), float(mLower))
|
||||
return
|
||||
|
||||
def setTextMargins(self, mUpper, mLower):
|
||||
self.marginText = (float(mUpper), float(mLower))
|
||||
return
|
||||
|
||||
def setMetaMargins(self, mUpper, mLower):
|
||||
self.marginMeta = (float(mUpper), float(mLower))
|
||||
return
|
||||
|
||||
def setLinkHeaders(self, linkHeaders):
|
||||
self.linkHeaders = linkHeaders
|
||||
return
|
||||
@@ -173,8 +231,8 @@ class Tokenizer():
|
||||
self.doKeywords = doKeywords
|
||||
return
|
||||
|
||||
def setJustify(self, doJustify):
|
||||
self.doJustify = doJustify
|
||||
def setKeepMarkdown(self, keepMarkdown):
|
||||
self.keepMarkdown = keepMarkdown
|
||||
return
|
||||
|
||||
##
|
||||
@@ -196,7 +254,8 @@ class Tokenizer():
|
||||
self.theTokens.append((
|
||||
self.T_TITLE, 0, theTitle, None, self.A_PBB | self.A_CENTRE
|
||||
))
|
||||
self.theMarkdown = "# %s\n\n" % theTitle
|
||||
if self.keepMarkdown:
|
||||
self.theMarkdown.append("# %s\n\n" % theTitle)
|
||||
|
||||
return True
|
||||
|
||||
@@ -238,26 +297,10 @@ class Tokenizer():
|
||||
|
||||
return True
|
||||
|
||||
def getResult(self):
|
||||
"""Return the result from the conversion.
|
||||
"""
|
||||
return self.theResult
|
||||
|
||||
def getResultSize(self):
|
||||
"""Return the size of the result from the conversion.
|
||||
"""
|
||||
if self.theResult is None:
|
||||
return 0
|
||||
return len(self.theResult)
|
||||
|
||||
def getFilteredMarkdown(self):
|
||||
"""Return the novelWriter markdown after the filters have been applied.
|
||||
"""
|
||||
return self.theMarkdown
|
||||
|
||||
def doAutoReplace(self):
|
||||
"""Run through the user's auto-replace dictionary.
|
||||
def doPreProcessing(self):
|
||||
"""Reun trough the various replace doctionaries.
|
||||
"""
|
||||
# Process the user's auto-replace dictionary
|
||||
if len(self.theProject.autoReplace) > 0:
|
||||
repDict = {}
|
||||
for aKey, aVal in self.theProject.autoReplace.items():
|
||||
@@ -265,6 +308,10 @@ class Tokenizer():
|
||||
xRep = re.compile("|".join([re.escape(k) for k in repDict.keys()]), flags=re.DOTALL)
|
||||
self.theText = xRep.sub(lambda x: repDict[x.group(0)], self.theText)
|
||||
|
||||
# Process the character translation map
|
||||
trDict = {nwUnicode.U_MAPOSS: nwUnicode.U_RSQUO}
|
||||
self.theText = self.theText.translate(str.maketrans(trDict))
|
||||
|
||||
return
|
||||
|
||||
def doPostProcessing(self):
|
||||
@@ -307,7 +354,6 @@ class Tokenizer():
|
||||
]
|
||||
|
||||
self.theTokens = []
|
||||
self.theMarkdown = ""
|
||||
tmpMarkdown = []
|
||||
nLine = 0
|
||||
for aLine in self.theText.splitlines():
|
||||
@@ -316,88 +362,61 @@ class Tokenizer():
|
||||
# Tag lines starting with specific characters
|
||||
if len(aLine.strip()) == 0:
|
||||
self.theTokens.append((
|
||||
self.T_EMPTY,
|
||||
nLine,
|
||||
"",
|
||||
None,
|
||||
self.A_NONE
|
||||
self.T_EMPTY, nLine, "", None, self.A_NONE
|
||||
))
|
||||
tmpMarkdown.append("\n")
|
||||
if self.keepMarkdown:
|
||||
tmpMarkdown.append("\n")
|
||||
|
||||
elif aLine[0] == "%":
|
||||
cLine = aLine[1:].lstrip()
|
||||
synTag = cLine[:9].lower()
|
||||
if synTag == "synopsis:":
|
||||
self.theTokens.append((
|
||||
self.T_SYNOPSIS,
|
||||
nLine,
|
||||
cLine[9:].strip(),
|
||||
None,
|
||||
self.A_NONE
|
||||
self.T_SYNOPSIS, nLine, cLine[9:].strip(), None, self.A_NONE
|
||||
))
|
||||
if self.doSynopsis:
|
||||
if self.doSynopsis and self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
else:
|
||||
self.theTokens.append((
|
||||
self.T_COMMENT,
|
||||
nLine,
|
||||
aLine[1:].strip(),
|
||||
None,
|
||||
self.A_NONE
|
||||
self.T_COMMENT, nLine, aLine[1:].strip(), None, self.A_NONE
|
||||
))
|
||||
if self.doComments:
|
||||
if self.doComments and self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[0] == "@":
|
||||
self.theTokens.append((
|
||||
self.T_KEYWORD,
|
||||
nLine,
|
||||
aLine[1:].strip(),
|
||||
None,
|
||||
self.A_NONE
|
||||
self.T_KEYWORD, nLine, aLine[1:].strip(), None, self.A_NONE
|
||||
))
|
||||
if self.doKeywords:
|
||||
if self.doKeywords and self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[:2] == "# ":
|
||||
self.theTokens.append((
|
||||
self.T_HEAD1,
|
||||
nLine,
|
||||
aLine[2:].strip(),
|
||||
None,
|
||||
self.A_NONE
|
||||
self.T_HEAD1, nLine, aLine[2:].strip(), None, self.A_NONE
|
||||
))
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
if self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[:3] == "## ":
|
||||
self.theTokens.append((
|
||||
self.T_HEAD2,
|
||||
nLine,
|
||||
aLine[3:].strip(),
|
||||
None,
|
||||
self.A_NONE
|
||||
self.T_HEAD2, nLine, aLine[3:].strip(), None, self.A_NONE
|
||||
))
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
if self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[:4] == "### ":
|
||||
self.theTokens.append((
|
||||
self.T_HEAD3,
|
||||
nLine,
|
||||
aLine[4:].strip(),
|
||||
None,
|
||||
self.A_NONE
|
||||
self.T_HEAD3, nLine, aLine[4:].strip(), None, self.A_NONE
|
||||
))
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
if self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[:5] == "#### ":
|
||||
self.theTokens.append((
|
||||
self.T_HEAD4,
|
||||
nLine,
|
||||
aLine[5:].strip(),
|
||||
None,
|
||||
self.A_NONE
|
||||
self.T_HEAD4, nLine, aLine[5:].strip(), None, self.A_NONE
|
||||
))
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
if self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
else:
|
||||
if not self.doBodyText:
|
||||
@@ -420,26 +439,44 @@ class Tokenizer():
|
||||
# sorted by position
|
||||
fmtPos = sorted(fmtPos, key=itemgetter(0))
|
||||
self.theTokens.append((
|
||||
self.T_TEXT,
|
||||
nLine,
|
||||
aLine,
|
||||
fmtPos,
|
||||
self.A_NONE
|
||||
self.T_TEXT, nLine, aLine, fmtPos, self.A_NONE
|
||||
))
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
if self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
# Always add an empty line at the end
|
||||
self.theTokens.append((
|
||||
self.T_EMPTY,
|
||||
nLine,
|
||||
"",
|
||||
None,
|
||||
self.A_NONE
|
||||
self.T_EMPTY, nLine, "", None, self.A_NONE
|
||||
))
|
||||
tmpMarkdown.append("\n")
|
||||
if self.keepMarkdown:
|
||||
tmpMarkdown.append("\n")
|
||||
|
||||
self.theMarkdown = "".join(tmpMarkdown)
|
||||
tmpMarkdown = []
|
||||
if self.keepMarkdown:
|
||||
self.theMarkdown.append("".join(tmpMarkdown))
|
||||
|
||||
# Second Pass
|
||||
# ===========
|
||||
# Some items need a second pass
|
||||
|
||||
pToken = (self.T_EMPTY, 0, "", None, self.A_NONE)
|
||||
nToken = (self.T_EMPTY, 0, "", None, self.A_NONE)
|
||||
tCount = len(self.theTokens)
|
||||
for n, tToken in enumerate(self.theTokens):
|
||||
|
||||
if n > 0:
|
||||
pToken = self.theTokens[n-1]
|
||||
if n < tCount - 1:
|
||||
nToken = self.theTokens[n+1]
|
||||
|
||||
if tToken[0] == self.T_KEYWORD:
|
||||
aStyle = tToken[4]
|
||||
if pToken[0] == self.T_KEYWORD:
|
||||
aStyle |= self.A_Z_TOPMRG
|
||||
if nToken[0] == self.T_KEYWORD:
|
||||
aStyle |= self.A_Z_BTMMRG
|
||||
self.theTokens[n] = (
|
||||
tToken[0], tToken[1], tToken[2], tToken[3], aStyle
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
@@ -454,9 +491,7 @@ class Tokenizer():
|
||||
# For novel files, we need to handle chapter numbering, scene
|
||||
# numbering, and scene breaks
|
||||
if self.isNovel:
|
||||
for n in range(len(self.theTokens)):
|
||||
|
||||
tToken = self.theTokens[n]
|
||||
for n, tToken in enumerate(self.theTokens):
|
||||
|
||||
# In case we see text before a scene, we reset the flag
|
||||
if tToken[0] == self.T_TEXT:
|
||||
@@ -468,11 +503,7 @@ class Tokenizer():
|
||||
|
||||
tTemp = self._formatHeading(self.fmtTitle, tToken[2])
|
||||
self.theTokens[n] = (
|
||||
tToken[0],
|
||||
tToken[1],
|
||||
tTemp,
|
||||
None,
|
||||
self.A_NONE
|
||||
tToken[0], tToken[1], tTemp, None, self.A_NONE
|
||||
)
|
||||
|
||||
elif tToken[0] == self.T_HEAD2:
|
||||
@@ -490,11 +521,7 @@ class Tokenizer():
|
||||
|
||||
# Format the chapter header
|
||||
self.theTokens[n] = (
|
||||
tToken[0],
|
||||
tToken[1],
|
||||
tTemp,
|
||||
None,
|
||||
self.A_PBB
|
||||
tToken[0], tToken[1], tTemp, None, self.A_PBB
|
||||
)
|
||||
|
||||
# Set scene variables
|
||||
@@ -511,53 +538,29 @@ class Tokenizer():
|
||||
tTemp = self._formatHeading(self.fmtScene, tToken[2])
|
||||
if tTemp == "" and self.hideScene:
|
||||
self.theTokens[n] = (
|
||||
self.T_EMPTY,
|
||||
tToken[1],
|
||||
"",
|
||||
None,
|
||||
self.A_NONE
|
||||
self.T_EMPTY, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
elif tTemp == "" and not self.hideScene:
|
||||
if self.firstScene:
|
||||
self.theTokens[n] = (
|
||||
self.T_EMPTY,
|
||||
tToken[1],
|
||||
"",
|
||||
None,
|
||||
self.A_NONE
|
||||
self.T_EMPTY, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
self.T_SKIP,
|
||||
tToken[1],
|
||||
"",
|
||||
None,
|
||||
self.A_NONE
|
||||
self.T_SKIP, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
elif tTemp == self.fmtScene:
|
||||
if self.firstScene:
|
||||
self.theTokens[n] = (
|
||||
self.T_EMPTY,
|
||||
tToken[1],
|
||||
"",
|
||||
None,
|
||||
self.A_NONE
|
||||
self.T_EMPTY, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
self.T_SEP,
|
||||
tToken[1],
|
||||
tTemp,
|
||||
None,
|
||||
self.A_CENTRE
|
||||
self.T_SEP, tToken[1], tTemp, None, self.A_CENTRE
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
tToken[0],
|
||||
tToken[1],
|
||||
tTemp,
|
||||
None,
|
||||
self.A_NONE
|
||||
tToken[0], tToken[1], tTemp, None, self.A_NONE
|
||||
)
|
||||
|
||||
# Definitely no longer the first scene
|
||||
@@ -570,35 +573,19 @@ class Tokenizer():
|
||||
tTemp = self._formatHeading(self.fmtSection, tToken[2])
|
||||
if tTemp == "" and self.hideSection:
|
||||
self.theTokens[n] = (
|
||||
self.T_EMPTY,
|
||||
tToken[1],
|
||||
"",
|
||||
None,
|
||||
self.A_NONE
|
||||
self.T_EMPTY, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
elif tTemp == "" and not self.hideSection:
|
||||
self.theTokens[n] = (
|
||||
self.T_SKIP,
|
||||
tToken[1],
|
||||
"",
|
||||
None,
|
||||
self.A_NONE
|
||||
self.T_SKIP, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
elif tTemp == self.fmtSection:
|
||||
self.theTokens[n] = (
|
||||
self.T_SEP,
|
||||
tToken[1],
|
||||
tTemp,
|
||||
None,
|
||||
self.A_CENTRE
|
||||
self.T_SEP, tToken[1], tTemp, None, self.A_CENTRE
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
tToken[0],
|
||||
tToken[1],
|
||||
tTemp,
|
||||
None,
|
||||
self.A_NONE
|
||||
tToken[0], tToken[1], tTemp, None, self.A_NONE
|
||||
)
|
||||
|
||||
# For title page and partitions, we need to centre all text.
|
||||
@@ -609,28 +596,18 @@ class Tokenizer():
|
||||
for n, tToken in enumerate(self.theTokens):
|
||||
if tToken[0] == self.T_HEAD1:
|
||||
if self.isTitle:
|
||||
aStyle = self.A_PBB_AUT | self.A_CENTRE
|
||||
self.theTokens[n] = (
|
||||
self.T_TITLE,
|
||||
tToken[1],
|
||||
tToken[2],
|
||||
tToken[3],
|
||||
self.A_PBB_NO | self.A_CENTRE
|
||||
self.T_TITLE, tToken[1], tToken[2], tToken[3], aStyle
|
||||
)
|
||||
else:
|
||||
aStyle = self.A_PBB | self.A_CENTRE
|
||||
self.theTokens[n] = (
|
||||
tToken[0],
|
||||
tToken[1],
|
||||
tToken[2],
|
||||
tToken[3],
|
||||
self.A_PBB | self.A_CENTRE
|
||||
tToken[0], tToken[1], tToken[2], tToken[3], aStyle
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
tToken[0],
|
||||
tToken[1],
|
||||
tToken[2],
|
||||
tToken[3],
|
||||
self.A_CENTRE
|
||||
tToken[0], tToken[1], tToken[2], tToken[3], self.A_CENTRE
|
||||
)
|
||||
|
||||
# Add a page break after the last entry
|
||||
@@ -638,11 +615,7 @@ class Tokenizer():
|
||||
if n >= 0:
|
||||
tToken = self.theTokens[n]
|
||||
self.theTokens[n] = (
|
||||
tToken[0],
|
||||
tToken[1],
|
||||
tToken[2],
|
||||
tToken[3],
|
||||
tToken[4] | self.A_PBA
|
||||
tToken[0], tToken[1], tToken[2], tToken[3], tToken[4] | self.A_PBA
|
||||
)
|
||||
|
||||
# A single page is always left-aligned and starts on a fresh
|
||||
@@ -651,23 +624,23 @@ class Tokenizer():
|
||||
for n, tToken in enumerate(self.theTokens):
|
||||
if n == 0:
|
||||
self.theTokens[n] = (
|
||||
tToken[0],
|
||||
tToken[1],
|
||||
tToken[2],
|
||||
tToken[3],
|
||||
self.A_LEFT | self.A_PBB
|
||||
tToken[0], tToken[1], tToken[2], tToken[3], self.A_LEFT | self.A_PBB
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
tToken[0],
|
||||
tToken[1],
|
||||
tToken[2],
|
||||
tToken[3],
|
||||
self.A_LEFT
|
||||
tToken[0], tToken[1], tToken[2], tToken[3], self.A_LEFT
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def saveRawMarkdown(self, savePath):
|
||||
"""Save the data to a plain text file.
|
||||
"""
|
||||
with open(savePath, mode="w", encoding="utf8") as outFile:
|
||||
for nwdPage in self.theMarkdown:
|
||||
outFile.write(nwdPage)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
novelWriter – Markdown Text Converter
|
||||
=====================================
|
||||
Extends the Tokenizer class to generate Makrdown output
|
||||
|
||||
File History:
|
||||
Created: 2021-02-06 [1.2a0]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2021, Veronica Berglyd Olsen
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from nw.core.tokenizer import Tokenizer
|
||||
from nw.constants import nwLabels
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ToMarkdown(Tokenizer):
|
||||
|
||||
M_STD = 0 # Standard Markdown
|
||||
M_GH = 1 # GitHub Markdown
|
||||
|
||||
def __init__(self, theProject, theParent):
|
||||
Tokenizer.__init__(self, theProject, theParent)
|
||||
|
||||
self.genMode = self.M_STD
|
||||
self.fullMD = []
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setStandardMarkdown(self):
|
||||
self.genMode = self.M_STD
|
||||
return
|
||||
|
||||
def setGitHubMarkdown(self):
|
||||
self.genMode = self.M_GH
|
||||
return
|
||||
|
||||
##
|
||||
# Class Methods
|
||||
##
|
||||
|
||||
def getFullResultSize(self):
|
||||
"""Return the size of the full Markdown result.
|
||||
"""
|
||||
return sum([len(x) for x in self.fullMD])
|
||||
|
||||
def doConvert(self):
|
||||
"""Convert the list of text tokens into a HTML document saved
|
||||
to theResult.
|
||||
"""
|
||||
if self.genMode == self.M_STD:
|
||||
# Standard
|
||||
mdTags = {
|
||||
self.FMT_B_B : "**", self.FMT_B_E : "**",
|
||||
self.FMT_I_B : "_", self.FMT_I_E : "_",
|
||||
self.FMT_D_B : "", self.FMT_D_E : "",
|
||||
}
|
||||
else:
|
||||
# GitHub and novelWriter
|
||||
mdTags = {
|
||||
self.FMT_B_B : "**", self.FMT_B_E : "**",
|
||||
self.FMT_I_B : "_", self.FMT_I_E : "_",
|
||||
self.FMT_D_B : "~~", self.FMT_D_E : "~~",
|
||||
}
|
||||
|
||||
self.theResult = ""
|
||||
|
||||
thisPar = []
|
||||
tmpResult = []
|
||||
|
||||
for tType, tLine, tText, tFormat, tStyle in self.theTokens:
|
||||
|
||||
# Process Text Type
|
||||
if tType == self.T_EMPTY:
|
||||
if len(thisPar) > 0:
|
||||
tTemp = "".join(thisPar)
|
||||
tmpResult.append("%s\n\n" % tTemp.rstrip(" "))
|
||||
thisPar = []
|
||||
|
||||
elif tType == self.T_TITLE:
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
tmpResult.append("# %s\n\n" % tHead)
|
||||
|
||||
elif tType == self.T_HEAD1:
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
tmpResult.append("# %s\n\n" % tHead)
|
||||
|
||||
elif tType == self.T_HEAD2:
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
tmpResult.append("## %s\n\n" % tHead)
|
||||
|
||||
elif tType == self.T_HEAD3:
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
tmpResult.append("### %s\n\n" % tHead)
|
||||
|
||||
elif tType == self.T_HEAD4:
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
tmpResult.append("#### %s\n\n" % tHead)
|
||||
|
||||
elif tType == self.T_SEP:
|
||||
tmpResult.append("%s\n\n" % tText)
|
||||
|
||||
elif tType == self.T_SKIP:
|
||||
tmpResult.append("\n\n\n")
|
||||
|
||||
elif tType == self.T_TEXT:
|
||||
tTemp = tText
|
||||
for xPos, xLen, xFmt in reversed(tFormat):
|
||||
tTemp = tTemp[:xPos] + mdTags[xFmt] + tTemp[xPos+xLen:]
|
||||
if tText.endswith(" "):
|
||||
thisPar.append(tTemp.rstrip() + " \n")
|
||||
else:
|
||||
thisPar.append(tTemp.rstrip() + " ")
|
||||
|
||||
elif tType == self.T_SYNOPSIS and self.doSynopsis:
|
||||
tmpResult.append("**Synopsis:** %s\n\n" % tText)
|
||||
|
||||
elif tType == self.T_COMMENT and self.doComments:
|
||||
tmpResult.append("**Comment:** %s\n\n" % tText)
|
||||
|
||||
elif tType == self.T_KEYWORD and self.doKeywords:
|
||||
tmpResult.append(self._formatKeywords(tText, tStyle))
|
||||
|
||||
self.theResult = "".join(tmpResult)
|
||||
tmpResult = []
|
||||
|
||||
self.fullMD.append(self.theResult)
|
||||
|
||||
return
|
||||
|
||||
def saveMarkdown(self, savePath):
|
||||
"""Save the data to a plain text file.
|
||||
"""
|
||||
with open(savePath, mode="w", encoding="utf8") as outFile:
|
||||
theText = "".join(self.fullMD)
|
||||
outFile.write(theText)
|
||||
|
||||
return
|
||||
|
||||
def replaceTabs(self, nSpaces=8, spaceChar=" "):
|
||||
"""Replace tabs with spaces.
|
||||
"""
|
||||
fullMD = []
|
||||
eightSpace = spaceChar*nSpaces
|
||||
for aPage in self.fullMD:
|
||||
fullMD.append(aPage.replace("\t", eightSpace))
|
||||
|
||||
self.fullMD = fullMD
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _formatKeywords(self, tText, tStyle):
|
||||
"""Apply Markdown formatting to keywords.
|
||||
"""
|
||||
isValid, theBits, thePos = self.theParent.theIndex.scanThis("@"+tText)
|
||||
if not isValid or not theBits:
|
||||
return ""
|
||||
|
||||
retText = ""
|
||||
if theBits[0] in nwLabels.KEY_NAME:
|
||||
retText += "**%s:** " % nwLabels.KEY_NAME[theBits[0]]
|
||||
|
||||
if len(theBits) > 1:
|
||||
retText += ", ".join(theBits[1:])
|
||||
|
||||
if tStyle & self.A_Z_BTMMRG:
|
||||
retText += " \n"
|
||||
else:
|
||||
retText += "\n\n"
|
||||
|
||||
return retText
|
||||
|
||||
# END Class ToMarkdown
|
||||
+1285
File diff suppressed because it is too large
Load Diff
+53
-3
@@ -24,6 +24,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import nw
|
||||
import logging
|
||||
import os
|
||||
|
||||
@@ -33,10 +34,35 @@ from time import time
|
||||
|
||||
from nw.core.item import NWItem
|
||||
from nw.common import checkHandle
|
||||
from nw.constants import nwFiles, nwItemType, nwItemClass, nwItemLayout, nwConst
|
||||
from nw.constants import (
|
||||
nwFiles, nwItemType, nwItemClass, nwItemLayout, nwConst, nwLists
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Layout Translation Map
|
||||
LAYOUT_MAP = {
|
||||
nwItemLayout.SCENE: {
|
||||
"H1": nwItemLayout.BOOK,
|
||||
"H2": nwItemLayout.CHAPTER,
|
||||
},
|
||||
nwItemLayout.CHAPTER: {
|
||||
"H1": nwItemLayout.BOOK,
|
||||
"H3": nwItemLayout.SCENE,
|
||||
"H4": nwItemLayout.SCENE,
|
||||
},
|
||||
nwItemLayout.UNNUMBERED: {
|
||||
"H1": nwItemLayout.BOOK,
|
||||
"H3": nwItemLayout.SCENE,
|
||||
"H4": nwItemLayout.SCENE,
|
||||
},
|
||||
nwItemLayout.PARTITION: {
|
||||
"H2": nwItemLayout.CHAPTER,
|
||||
"H3": nwItemLayout.SCENE,
|
||||
"H4": nwItemLayout.SCENE,
|
||||
},
|
||||
}
|
||||
|
||||
class NWTree():
|
||||
|
||||
def __init__(self, theProject):
|
||||
@@ -179,8 +205,9 @@ class NWTree():
|
||||
outFile.write("\n".join(tocList))
|
||||
outFile.write("\n")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
except Exception:
|
||||
logger.error("Could not write ToC file")
|
||||
nw.logException()
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -202,6 +229,29 @@ class NWTree():
|
||||
novelWords += tItem.wordCount
|
||||
return novelWords, noteWords
|
||||
|
||||
def updateItemLayout(self, tHandle, hLevel):
|
||||
"""Check if the item layout needs updating based on the header
|
||||
given level.
|
||||
"""
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
return False
|
||||
if tItem.itemClass not in nwLists.CLS_NOVEL:
|
||||
return False
|
||||
if hLevel not in ("H1", "H2", "H3", "H4"):
|
||||
return False
|
||||
|
||||
iLayout = tItem.itemLayout
|
||||
if iLayout in LAYOUT_MAP:
|
||||
if hLevel in LAYOUT_MAP[iLayout]:
|
||||
tItem.itemLayout = LAYOUT_MAP[iLayout][hLevel]
|
||||
logger.debug("Changed layout for %s from %s to %s" % (
|
||||
tHandle, iLayout.name, tItem.itemLayout.name
|
||||
))
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
##
|
||||
# Tree Structure Methods
|
||||
##
|
||||
|
||||
+19
-4
@@ -24,12 +24,31 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import (
|
||||
qApp, QDialog, QGridLayout, QStyle, QPlainTextEdit, QLabel,
|
||||
QDialogButtonBox
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# =============================================================================================== #
|
||||
# Utility Functions
|
||||
# =============================================================================================== #
|
||||
|
||||
def logException():
|
||||
"""Log the content of an exception message.
|
||||
"""
|
||||
exType, exValue, _ = sys.exc_info()
|
||||
logger.error("%s: %s" % (exType.__name__, str(exValue).strip("'")))
|
||||
|
||||
# =============================================================================================== #
|
||||
# Error Handler
|
||||
# =============================================================================================== #
|
||||
|
||||
class NWErrorMessage(QDialog):
|
||||
|
||||
def __init__(self, parent):
|
||||
@@ -72,7 +91,6 @@ class NWErrorMessage(QDialog):
|
||||
"""Generate a message and append session data, error info and
|
||||
error traceback.
|
||||
"""
|
||||
import sys
|
||||
from traceback import format_tb
|
||||
from nw import __issuesurl__, __version__
|
||||
from PyQt5.Qt import PYQT_VERSION_STR
|
||||
@@ -133,15 +151,12 @@ class NWErrorMessage(QDialog):
|
||||
|
||||
# END Class NWErrorMessage
|
||||
|
||||
|
||||
def exceptionHandler(exType, exValue, exTrace):
|
||||
"""Function to catch unhandled global exceptions.
|
||||
"""
|
||||
import logging
|
||||
from traceback import print_tb
|
||||
from PyQt5.QtWidgets import qApp
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.critical("%s: %s" % (exType.__name__, str(exValue)))
|
||||
print_tb(exTrace)
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ from nw.gui.projtree import GuiProjectTree
|
||||
from nw.gui.projwizard import GuiProjectWizard
|
||||
from nw.gui.statusbar import GuiMainStatus
|
||||
from nw.gui.theme import GuiTheme
|
||||
from nw.gui.wordlist import GuiWordList
|
||||
from nw.gui.writingstats import GuiWritingStats
|
||||
|
||||
__all__ = [
|
||||
@@ -44,5 +45,6 @@ __all__ = [
|
||||
"GuiProjectTree",
|
||||
"GuiProjectWizard",
|
||||
"GuiTheme",
|
||||
"GuiWordList",
|
||||
"GuiWritingStats",
|
||||
]
|
||||
|
||||
+462
-374
File diff suppressed because it is too large
Load Diff
+34
-12
@@ -54,7 +54,7 @@ from nw.gui.dochighlight import GuiDocHighlighter
|
||||
from nw.common import transferCase
|
||||
from nw.constants import (
|
||||
nwConst, nwAlert, nwUnicode, nwDocAction, nwDocInsert, nwItemClass,
|
||||
nwKeyWords
|
||||
nwKeyWords, nwLabels
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -91,6 +91,7 @@ class GuiDocEditor(QTextEdit):
|
||||
self.wordCount = 0 # Word count
|
||||
self.paraCount = 0 # Paragraph count
|
||||
self.lastEdit = 0 # Time stamp of last edit
|
||||
self.lastActive = 0 # Time stamp of last activity
|
||||
self.lastFind = None # Position of the last found search word
|
||||
self.bigDoc = False # Flag for very large document size
|
||||
self.doReplace = False # Switch to temporarily disable auto-replace
|
||||
@@ -169,15 +170,16 @@ class GuiDocEditor(QTextEdit):
|
||||
self.clear()
|
||||
self.wcTimer.stop()
|
||||
|
||||
self.theHandle = None
|
||||
self.charCount = 0
|
||||
self.wordCount = 0
|
||||
self.paraCount = 0
|
||||
self.lastEdit = 0
|
||||
self.lastFind = None
|
||||
self.bigDoc = False
|
||||
self.doReplace = False
|
||||
self.queuePos = None
|
||||
self.theHandle = None
|
||||
self.charCount = 0
|
||||
self.wordCount = 0
|
||||
self.paraCount = 0
|
||||
self.lastEdit = 0
|
||||
self.lastActive = 0
|
||||
self.lastFind = None
|
||||
self.bigDoc = False
|
||||
self.doReplace = False
|
||||
self.queuePos = None
|
||||
|
||||
self.setDocumentChanged(False)
|
||||
self.docHeader.setTitleFromHandle(self.theHandle)
|
||||
@@ -319,6 +321,7 @@ class GuiDocEditor(QTextEdit):
|
||||
logger.debug("Document highlighted in %.3f ms" % (1000*(afTime-bfTime)))
|
||||
|
||||
self.lastEdit = time()
|
||||
self.lastActive = time()
|
||||
self._runCounter()
|
||||
self.wcTimer.start()
|
||||
self.theHandle = tHandle
|
||||
@@ -415,11 +418,21 @@ class GuiDocEditor(QTextEdit):
|
||||
self.setDocumentChanged(False)
|
||||
|
||||
self.theIndex.scanText(tHandle, docText)
|
||||
|
||||
if self._updateHeaders(checkLevel=True):
|
||||
self.theParent.requestNovelTreeRefresh()
|
||||
else:
|
||||
self.theParent.novelView.updateWordCounts(tHandle)
|
||||
|
||||
hLevel = "H0"
|
||||
if self.theHeaders:
|
||||
hLevel = self.theHeaders[0][1]
|
||||
|
||||
if self.theProject.projTree.updateItemLayout(tHandle, hLevel):
|
||||
self.theParent.treeView.setTreeItemValues(tHandle)
|
||||
self.nwDocument.saveDocument(docText)
|
||||
self.docFooter.updateInfo()
|
||||
|
||||
return True
|
||||
|
||||
def updateDocMargins(self):
|
||||
@@ -647,6 +660,10 @@ class GuiDocEditor(QTextEdit):
|
||||
this class when calling these actions from other classes.
|
||||
"""
|
||||
logger.verbose("Requesting action: %s" % theAction.name)
|
||||
if not self.hasFocus():
|
||||
logger.verbose("Editor does not have focus")
|
||||
return False
|
||||
|
||||
if self.theHandle is None:
|
||||
logger.error("No document open")
|
||||
return False
|
||||
@@ -708,6 +725,7 @@ class GuiDocEditor(QTextEdit):
|
||||
return False
|
||||
|
||||
self._allowAutoReplace(True)
|
||||
self.lastActive = time()
|
||||
|
||||
return True
|
||||
|
||||
@@ -825,6 +843,7 @@ class GuiDocEditor(QTextEdit):
|
||||
* The undo/redo/select all sequences bypasses the docAction
|
||||
pathway from the menu, so we redirect them back from here.
|
||||
"""
|
||||
self.lastActive = time()
|
||||
isReturn = keyEvent.key() == Qt.Key_Return
|
||||
isReturn |= keyEvent.key() == Qt.Key_Enter
|
||||
if isReturn and self.docSearch.anyFocus():
|
||||
@@ -1069,7 +1088,7 @@ class GuiDocEditor(QTextEdit):
|
||||
logger.verbose("Word counter is busy")
|
||||
return
|
||||
|
||||
if time() - self.lastEdit < 5*self.wcInterval:
|
||||
if time() - self.lastEdit < 5 * self.wcInterval:
|
||||
logger.verbose("Running word counter")
|
||||
self.theParent.threadPool.start(self.wCounter)
|
||||
|
||||
@@ -2511,8 +2530,11 @@ class GuiDocEditFooter(QWidget):
|
||||
else:
|
||||
iStatus = self.theProject.importItems.checkEntry(iStatus)
|
||||
theIcon = self.theParent.importIcons[iStatus]
|
||||
|
||||
sIcon = theIcon.pixmap(self.sPx, self.sPx)
|
||||
sText = self.theItem.itemStatus
|
||||
sClass = nwLabels.CLASS_NAME[self.theItem.itemClass]
|
||||
sLayout = nwLabels.LAYOUT_NAME[self.theItem.itemLayout]
|
||||
sText = f"{self.theItem.itemStatus} / {sClass} / {sLayout}"
|
||||
|
||||
self.statusIcon.setPixmap(sIcon)
|
||||
self.statusText.setText(sText)
|
||||
|
||||
+3
-3
@@ -176,13 +176,13 @@ class GuiDocViewer(QTextBrowser):
|
||||
# See issue #298
|
||||
try:
|
||||
aDoc.setText(tHandle)
|
||||
aDoc.doAutoReplace()
|
||||
aDoc.doPreProcessing()
|
||||
aDoc.tokenizeText()
|
||||
aDoc.doConvert()
|
||||
aDoc.doPostProcessing()
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error("Failed to generate preview for document with handle '%s'" % tHandle)
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
self.setText("An error occurred while generating the preview.")
|
||||
return False
|
||||
|
||||
|
||||
+45
-38
@@ -115,22 +115,25 @@ class GuiMainMenu(QMenuBar):
|
||||
##
|
||||
|
||||
def setSpellCheck(self, theMode):
|
||||
"""Set the spell check check box to theMode. This is controlled
|
||||
by the document editor class, which holds the master spell check
|
||||
flag.
|
||||
"""Forward spell check check state to its action.
|
||||
"""
|
||||
self.aSpellCheck.setChecked(theMode)
|
||||
return
|
||||
|
||||
def setAutoOutline(self, theMode):
|
||||
"""Set the auto outline check box to theMode. Used during
|
||||
initialisation.
|
||||
"""Forward auto outline check state to its action.
|
||||
"""
|
||||
self.aAutoOutline.setChecked(theMode)
|
||||
return
|
||||
|
||||
def setFocusMode(self, theMode):
|
||||
"""Forward focus mode check state to its action.
|
||||
"""
|
||||
self.aFocusMode.setChecked(theMode)
|
||||
return
|
||||
|
||||
##
|
||||
# Menu Action
|
||||
# Slots
|
||||
##
|
||||
|
||||
def _toggleSpellCheck(self, isChecked=False):
|
||||
@@ -164,17 +167,11 @@ class GuiMainMenu(QMenuBar):
|
||||
return True
|
||||
|
||||
def _openWebsite(self, theUrl):
|
||||
"""Open an URL in the system's default browser.
|
||||
"""Open a URL in the system's default browser.
|
||||
"""
|
||||
QDesktopServices.openUrl(QUrl(theUrl))
|
||||
return True
|
||||
|
||||
def _openIssue(self):
|
||||
"""Open the issue tracker URL in the system's default browser.
|
||||
"""
|
||||
QDesktopServices.openUrl(QUrl(nw.__issuesurl__))
|
||||
return True
|
||||
|
||||
##
|
||||
# Menu Builders
|
||||
##
|
||||
@@ -263,19 +260,40 @@ class GuiMainMenu(QMenuBar):
|
||||
self.projMenu.addSeparator()
|
||||
|
||||
# Project > Edit
|
||||
self.aEditItem = QAction("Edit Project Item", self)
|
||||
self.aEditItem.setStatusTip("Change item settings")
|
||||
self.aEditItem = QAction("Edit Item", self)
|
||||
self.aEditItem.setStatusTip("Change project item settings")
|
||||
self.aEditItem.setShortcuts(["Ctrl+E", "F2"])
|
||||
self.aEditItem.triggered.connect(lambda: self.theParent.editItem(None))
|
||||
self.projMenu.addAction(self.aEditItem)
|
||||
|
||||
# Project > Delete
|
||||
self.aDeleteItem = QAction("Delete Project Item", self)
|
||||
self.aDeleteItem.setStatusTip("Delete selected item")
|
||||
self.aDeleteItem = QAction("Delete Item", self)
|
||||
self.aDeleteItem.setStatusTip("Delete selected project item")
|
||||
self.aDeleteItem.setShortcut("Ctrl+Shift+Del")
|
||||
self.aDeleteItem.triggered.connect(lambda: self.theParent.treeView.deleteItem(None))
|
||||
self.projMenu.addAction(self.aDeleteItem)
|
||||
|
||||
# Project > Move Up
|
||||
self.aMoveUp = QAction("Move Item Up", self)
|
||||
self.aMoveUp.setStatusTip("Move project item up")
|
||||
self.aMoveUp.setShortcut("Ctrl+Up")
|
||||
self.aMoveUp.triggered.connect(lambda: self._moveTreeItem(-1))
|
||||
self.projMenu.addAction(self.aMoveUp)
|
||||
|
||||
# Project > Move Down
|
||||
self.aMoveDown = QAction("Move Item Down", self)
|
||||
self.aMoveDown.setStatusTip("Move project item down")
|
||||
self.aMoveDown.setShortcut("Ctrl+Down")
|
||||
self.aMoveDown.triggered.connect(lambda: self._moveTreeItem(1))
|
||||
self.projMenu.addAction(self.aMoveDown)
|
||||
|
||||
# Project > Undo Last Action
|
||||
self.aMoveUndo = QAction("Undo Last Move", self)
|
||||
self.aMoveUndo.setStatusTip("Undo last item move")
|
||||
self.aMoveUndo.setShortcut("Ctrl+Shift+Z")
|
||||
self.aMoveUndo.triggered.connect(lambda: self.theParent.treeView.undoLastMove())
|
||||
self.projMenu.addAction(self.aMoveUndo)
|
||||
|
||||
# Project > Empty Trash
|
||||
self.aEmptyTrash = QAction("Empty Trash", self)
|
||||
self.aEmptyTrash.setStatusTip("Permanently delete all files in the Trash folder")
|
||||
@@ -710,6 +728,7 @@ class GuiMainMenu(QMenuBar):
|
||||
self.mInsKWItems = {}
|
||||
self.mInsKWItems[nwKeyWords.TAG_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, G")
|
||||
self.mInsKWItems[nwKeyWords.POV_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, V")
|
||||
self.mInsKWItems[nwKeyWords.FOCUS_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, F")
|
||||
self.mInsKWItems[nwKeyWords.CHAR_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, C")
|
||||
self.mInsKWItems[nwKeyWords.PLOT_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, P")
|
||||
self.mInsKWItems[nwKeyWords.TIME_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, T")
|
||||
@@ -891,40 +910,28 @@ class GuiMainMenu(QMenuBar):
|
||||
# Tools
|
||||
self.toolsMenu = self.addMenu("&Tools")
|
||||
|
||||
# Tools > Move Up
|
||||
self.aMoveUp = QAction("Move Tree Item Up", self)
|
||||
self.aMoveUp.setStatusTip("Move item up")
|
||||
self.aMoveUp.setShortcut("Ctrl+Shift+Up")
|
||||
self.aMoveUp.triggered.connect(lambda: self._moveTreeItem(-1))
|
||||
self.toolsMenu.addAction(self.aMoveUp)
|
||||
|
||||
# Tools > Move Down
|
||||
self.aMoveDown = QAction("Move Tree Item Down", self)
|
||||
self.aMoveDown.setStatusTip("Move item down")
|
||||
self.aMoveDown.setShortcut("Ctrl+Shift+Down")
|
||||
self.aMoveDown.triggered.connect(lambda: self._moveTreeItem(1))
|
||||
self.toolsMenu.addAction(self.aMoveDown)
|
||||
|
||||
# Tools > Separator
|
||||
self.toolsMenu.addSeparator()
|
||||
|
||||
# Tools > Toggle Spell Check
|
||||
# Tools > Check Spelling
|
||||
self.aSpellCheck = QAction("Check Spelling", self)
|
||||
self.aSpellCheck.setStatusTip("Toggle check spelling")
|
||||
self.aSpellCheck.setCheckable(True)
|
||||
self.aSpellCheck.setChecked(self.theProject.spellCheck)
|
||||
# Here we must used triggered, not toggled, to avoid recursion
|
||||
self.aSpellCheck.triggered.connect(self._toggleSpellCheck)
|
||||
self.aSpellCheck.triggered.connect(self._toggleSpellCheck) # triggered, not toggled!
|
||||
self.aSpellCheck.setShortcut("Ctrl+F7")
|
||||
self.toolsMenu.addAction(self.aSpellCheck)
|
||||
|
||||
# Tools > Update Spell Check
|
||||
# Tools > Re-Run Spell Check
|
||||
self.aReRunSpell = QAction("Re-Run Spell Check", self)
|
||||
self.aReRunSpell.setStatusTip("Run the spell checker on current document")
|
||||
self.aReRunSpell.setShortcut("F7")
|
||||
self.aReRunSpell.triggered.connect(lambda: self.theParent.docEditor.spellCheckDocument())
|
||||
self.toolsMenu.addAction(self.aReRunSpell)
|
||||
|
||||
# Tools > Project Word List
|
||||
self.aEditWordList = QAction("Project Word List", self)
|
||||
self.aEditWordList.setStatusTip("Edit the project's word list")
|
||||
self.aEditWordList.triggered.connect(lambda: self.theParent.showProjectWordListDialog())
|
||||
self.toolsMenu.addAction(self.aEditWordList)
|
||||
|
||||
# Tools > Separator
|
||||
self.toolsMenu.addSeparator()
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ class GuiOutline(QTreeWidget):
|
||||
nwOutline.WCOUNT : 50,
|
||||
nwOutline.PCOUNT : 50,
|
||||
nwOutline.POV : 100,
|
||||
nwOutline.FOCUS : 100,
|
||||
nwOutline.CHAR : 100,
|
||||
nwOutline.PLOT : 100,
|
||||
nwOutline.TIME : 100,
|
||||
@@ -68,6 +69,7 @@ class GuiOutline(QTreeWidget):
|
||||
nwOutline.WCOUNT : False,
|
||||
nwOutline.PCOUNT : False,
|
||||
nwOutline.POV : False,
|
||||
nwOutline.FOCUS : True,
|
||||
nwOutline.CHAR : False,
|
||||
nwOutline.PLOT : False,
|
||||
nwOutline.TIME : True,
|
||||
@@ -451,6 +453,7 @@ class GuiOutline(QTreeWidget):
|
||||
|
||||
theRefs = self.theIndex.getReferences(tHandle, sTitle)
|
||||
newItem.setText(self.colIndex[nwOutline.POV], ", ".join(theRefs[nwKeyWords.POV_KEY]))
|
||||
newItem.setText(self.colIndex[nwOutline.FOCUS], ", ".join(theRefs[nwKeyWords.FOCUS_KEY]))
|
||||
newItem.setText(self.colIndex[nwOutline.CHAR], ", ".join(theRefs[nwKeyWords.CHAR_KEY]))
|
||||
newItem.setText(self.colIndex[nwOutline.PLOT], ", ".join(theRefs[nwKeyWords.PLOT_KEY]))
|
||||
newItem.setText(self.colIndex[nwOutline.TIME], ", ".join(theRefs[nwKeyWords.TIME_KEY]))
|
||||
|
||||
+25
-15
@@ -105,6 +105,7 @@ class GuiOutlineDetails(QScrollArea):
|
||||
|
||||
# Tags
|
||||
self.povKeyLabel = QLabel("<b>%s</b>" % nwLabels.KEY_NAME[nwKeyWords.POV_KEY])
|
||||
self.focKeyLabel = QLabel("<b>%s</b>" % nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY])
|
||||
self.chrKeyLabel = QLabel("<b>%s</b>" % nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY])
|
||||
self.pltKeyLabel = QLabel("<b>%s</b>" % nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY])
|
||||
self.timKeyLabel = QLabel("<b>%s</b>" % nwLabels.KEY_NAME[nwKeyWords.TIME_KEY])
|
||||
@@ -114,6 +115,7 @@ class GuiOutlineDetails(QScrollArea):
|
||||
self.cstKeyLabel = QLabel("<b>%s</b>" % nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY])
|
||||
|
||||
self.povKeyLWrap = QHBoxLayout()
|
||||
self.focKeyLWrap = QHBoxLayout()
|
||||
self.chrKeyLWrap = QHBoxLayout()
|
||||
self.pltKeyLWrap = QHBoxLayout()
|
||||
self.timKeyLWrap = QHBoxLayout()
|
||||
@@ -123,6 +125,7 @@ class GuiOutlineDetails(QScrollArea):
|
||||
self.cstKeyLWrap = QHBoxLayout()
|
||||
|
||||
self.povKeyValue = QLabel("")
|
||||
self.focKeyValue = QLabel("")
|
||||
self.chrKeyValue = QLabel("")
|
||||
self.pltKeyValue = QLabel("")
|
||||
self.timKeyValue = QLabel("")
|
||||
@@ -132,6 +135,7 @@ class GuiOutlineDetails(QScrollArea):
|
||||
self.cstKeyValue = QLabel("")
|
||||
|
||||
self.povKeyValue.setWordWrap(True)
|
||||
self.focKeyValue.setWordWrap(True)
|
||||
self.chrKeyValue.setWordWrap(True)
|
||||
self.pltKeyValue.setWordWrap(True)
|
||||
self.timKeyValue.setWordWrap(True)
|
||||
@@ -141,6 +145,7 @@ class GuiOutlineDetails(QScrollArea):
|
||||
self.cstKeyValue.setWordWrap(True)
|
||||
|
||||
self.povKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.focKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.chrKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.pltKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.timKeyValue.linkActivated.connect(self._tagClicked)
|
||||
@@ -150,6 +155,7 @@ class GuiOutlineDetails(QScrollArea):
|
||||
self.cstKeyValue.linkActivated.connect(self._tagClicked)
|
||||
|
||||
self.povKeyLWrap.addWidget(self.povKeyValue, 1)
|
||||
self.focKeyLWrap.addWidget(self.focKeyValue, 1)
|
||||
self.chrKeyLWrap.addWidget(self.chrKeyValue, 1)
|
||||
self.pltKeyLWrap.addWidget(self.pltKeyValue, 1)
|
||||
self.timKeyLWrap.addWidget(self.timKeyValue, 1)
|
||||
@@ -190,23 +196,25 @@ class GuiOutlineDetails(QScrollArea):
|
||||
|
||||
self.tagsForm.addWidget(self.povKeyLabel, 0, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.povKeyLWrap, 0, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.chrKeyLabel, 1, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.chrKeyLWrap, 1, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.pltKeyLabel, 2, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.pltKeyLWrap, 2, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.timKeyLabel, 3, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.timKeyLWrap, 3, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.wldKeyLabel, 4, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.wldKeyLWrap, 4, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.objKeyLabel, 5, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.objKeyLWrap, 5, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.entKeyLabel, 6, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.entKeyLWrap, 6, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.cstKeyLabel, 7, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.cstKeyLWrap, 7, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.focKeyLabel, 1, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.focKeyLWrap, 1, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.chrKeyLabel, 2, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.chrKeyLWrap, 2, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.pltKeyLabel, 3, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.pltKeyLWrap, 3, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.timKeyLabel, 4, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.timKeyLWrap, 4, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.wldKeyLabel, 5, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.wldKeyLWrap, 5, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.objKeyLabel, 6, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.objKeyLWrap, 6, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.entKeyLabel, 7, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.entKeyLWrap, 7, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.cstKeyLabel, 8, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.cstKeyLWrap, 8, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
|
||||
self.tagsForm.setColumnStretch(1, 1)
|
||||
self.tagsForm.setRowStretch(7, 1)
|
||||
self.tagsForm.setRowStretch(8, 1)
|
||||
self.tagsForm.setHorizontalSpacing(hSpace)
|
||||
self.tagsForm.setVerticalSpacing(vSpace)
|
||||
|
||||
@@ -257,6 +265,7 @@ class GuiOutlineDetails(QScrollArea):
|
||||
self.pCValue.setText("")
|
||||
self.synopValue.setText("")
|
||||
self.povKeyValue.setText("")
|
||||
self.focKeyValue.setText("")
|
||||
self.chrKeyValue.setText("")
|
||||
self.pltKeyValue.setText("")
|
||||
self.timKeyValue.setText("")
|
||||
@@ -296,6 +305,7 @@ class GuiOutlineDetails(QScrollArea):
|
||||
self.synopValue.setText(novIdx["synopsis"])
|
||||
|
||||
self.povKeyValue.setText(self._formatTags(theRefs, nwKeyWords.POV_KEY))
|
||||
self.focKeyValue.setText(self._formatTags(theRefs, nwKeyWords.FOCUS_KEY))
|
||||
self.chrKeyValue.setText(self._formatTags(theRefs, nwKeyWords.CHAR_KEY))
|
||||
self.pltKeyValue.setText(self._formatTags(theRefs, nwKeyWords.PLOT_KEY))
|
||||
self.timKeyValue.setText(self._formatTags(theRefs, nwKeyWords.TIME_KEY))
|
||||
|
||||
@@ -358,6 +358,33 @@ class GuiPreferencesProjects(QWidget):
|
||||
"If off, backups will run in the background."
|
||||
)
|
||||
|
||||
# Session Timer
|
||||
# =============
|
||||
self.mainForm.addGroupLabel("Session Timer")
|
||||
|
||||
## Pause when idle
|
||||
self.stopWhenIdle = QSwitch()
|
||||
self.stopWhenIdle.setChecked(self.mainConf.stopWhenIdle)
|
||||
self.mainForm.addRow(
|
||||
"Pause the session timer when not writing",
|
||||
self.stopWhenIdle,
|
||||
"Also pauses when the application window does not have focus."
|
||||
)
|
||||
|
||||
## Inactive time for idle
|
||||
self.userIdleTime = QDoubleSpinBox()
|
||||
self.userIdleTime.setMinimum(0.5)
|
||||
self.userIdleTime.setMaximum(600.0)
|
||||
self.userIdleTime.setSingleStep(0.5)
|
||||
self.userIdleTime.setDecimals(1)
|
||||
self.userIdleTime.setValue(self.mainConf.userIdleTime/60.0)
|
||||
self.mainForm.addRow(
|
||||
"Editor inactive time before pausing timer",
|
||||
self.userIdleTime,
|
||||
"User activity includes typing and changing the content.",
|
||||
theUnit="minutes"
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
def saveValues(self):
|
||||
@@ -372,6 +399,10 @@ class GuiPreferencesProjects(QWidget):
|
||||
self.mainConf.backupOnClose = self.backupOnClose.isChecked()
|
||||
self.mainConf.askBeforeBackup = self.askBeforeBackup.isChecked()
|
||||
|
||||
# Session Timer
|
||||
self.mainConf.stopWhenIdle = self.stopWhenIdle.isChecked()
|
||||
self.mainConf.userIdleTime = round(self.userIdleTime.value() * 60)
|
||||
|
||||
self.mainConf.confChanged = True
|
||||
|
||||
return
|
||||
|
||||
+173
-51
@@ -33,12 +33,12 @@ from time import time
|
||||
from PyQt5.QtCore import Qt, QSize, pyqtSignal
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import (
|
||||
qApp, QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMenu, QAction
|
||||
QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMenu, QAction
|
||||
)
|
||||
|
||||
from nw.core import NWDoc
|
||||
from nw.constants import (
|
||||
nwLabels, nwItemType, nwItemClass, nwItemLayout, nwAlert, nwConst
|
||||
nwLabels, nwItemType, nwItemClass, nwItemLayout, nwAlert, nwConst, nwLists
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -68,6 +68,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
self._treeMap = {}
|
||||
self._treeChanged = False
|
||||
self._timeChanged = 0
|
||||
self._lastMove = {}
|
||||
|
||||
##
|
||||
# Build GUI
|
||||
@@ -269,10 +270,43 @@ class GuiProjectTree(QTreeWidget):
|
||||
logger.error("Failed to add new item")
|
||||
return False
|
||||
|
||||
# If there is no handle set, return here
|
||||
if tHandle is None:
|
||||
return True
|
||||
|
||||
# Add the new item to the tree
|
||||
if tHandle is not None:
|
||||
self.revealNewTreeItem(tHandle, nHandle)
|
||||
self.theParent.editItem(tHandle)
|
||||
self.revealNewTreeItem(tHandle, nHandle)
|
||||
self.theParent.editItem(tHandle)
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
|
||||
# If this is a folder, return here
|
||||
if nwItem.itemType != nwItemType.FILE:
|
||||
return True
|
||||
|
||||
# This is a new files, so let's add some content
|
||||
newDoc = NWDoc(self.theProject, self.theParent)
|
||||
curTxt = newDoc.openDocument(tHandle, showStatus=False)
|
||||
if curTxt == "":
|
||||
if nwItem.itemLayout == nwItemLayout.CHAPTER:
|
||||
newText = f"## {nwItem.itemName}\n\n"
|
||||
elif nwItem.itemLayout == nwItemLayout.UNNUMBERED:
|
||||
newText = f"## {nwItem.itemName}\n\n"
|
||||
elif nwItem.itemLayout == nwItemLayout.SCENE:
|
||||
newText = f"### {nwItem.itemName}\n\n"
|
||||
else:
|
||||
newText = f"# {nwItem.itemName}\n\n"
|
||||
|
||||
# Save the text and index it
|
||||
newDoc.saveDocument(newText)
|
||||
self.theIndex.scanText(tHandle, newText)
|
||||
|
||||
# Get Word Counts
|
||||
cC, wC, pC = self.theIndex.getCounts(tHandle)
|
||||
nwItem.setCharCount(cC)
|
||||
nwItem.setWordCount(wC)
|
||||
nwItem.setParaCount(pC)
|
||||
self.propagateCount(tHandle, wC)
|
||||
self.projectWordCount()
|
||||
|
||||
return True
|
||||
|
||||
@@ -302,7 +336,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
if qApp.focusWidget() != self:
|
||||
if not self.hasFocus():
|
||||
return False
|
||||
|
||||
tHandle = self.getSelectedHandle()
|
||||
@@ -328,6 +362,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
return False
|
||||
cItem = pItem.takeChild(tIndex)
|
||||
pItem.insertChild(nIndex, cItem)
|
||||
self._recordLastMove(cItem, pItem, tIndex)
|
||||
|
||||
self.clearSelection()
|
||||
cItem.setSelected(True)
|
||||
@@ -416,7 +451,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
for tHandle in self.getTreeFromHandle(trashHandle):
|
||||
if tHandle == trashHandle:
|
||||
continue
|
||||
self.deleteItem(tHandle, True)
|
||||
self.deleteItem(tHandle, alreadyAsked=True)
|
||||
|
||||
if nTrash > 0:
|
||||
self._setTreeChanged(True)
|
||||
@@ -484,8 +519,9 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
theDoc = NWDoc(self.theProject, self.theParent)
|
||||
theDoc.deleteDocument(tHandle)
|
||||
del self.theProject.projTree[tHandle]
|
||||
self.theIndex.deleteHandle(tHandle)
|
||||
self._deleteTreeItem(tHandle)
|
||||
self._setTreeChanged(True)
|
||||
|
||||
else:
|
||||
# The file is not already in the trash folder, so we
|
||||
@@ -508,11 +544,12 @@ class GuiProjectTree(QTreeWidget):
|
||||
tIndex = trItemP.indexOfChild(trItemS)
|
||||
trItemC = trItemP.takeChild(tIndex)
|
||||
trItemT.addChild(trItemC)
|
||||
nwItemS.setParent(self.theProject.projTree.trashRoot())
|
||||
self._updateItemParent(tHandle)
|
||||
self.propagateCount(tHandle, wCount)
|
||||
|
||||
self._setTreeChanged(True)
|
||||
self.theIndex.deleteHandle(tHandle)
|
||||
self._recordLastMove(trItemS, trItemP, tIndex)
|
||||
self._setTreeChanged(True)
|
||||
|
||||
elif nwItemS.itemType == nwItemType.FOLDER:
|
||||
logger.debug("User requested folder %s deleted" % tHandle)
|
||||
@@ -523,7 +560,8 @@ class GuiProjectTree(QTreeWidget):
|
||||
tIndex = trItemP.indexOfChild(trItemS)
|
||||
if trItemS.childCount() == 0:
|
||||
trItemP.takeChild(tIndex)
|
||||
del self.theProject.projTree[tHandle]
|
||||
self._deleteTreeItem(tHandle)
|
||||
self._setTreeChanged(True)
|
||||
else:
|
||||
self.makeAlert((
|
||||
"Cannot delete folder. It is not empty. "
|
||||
@@ -537,7 +575,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
tIndex = self.indexOfTopLevelItem(trItemS)
|
||||
if trItemS.childCount() == 0:
|
||||
self.takeTopLevelItem(tIndex)
|
||||
del self.theProject.projTree[tHandle]
|
||||
self._deleteTreeItem(tHandle)
|
||||
self.theParent.mainMenu.setAvailableRoot()
|
||||
self._setTreeChanged(True)
|
||||
else:
|
||||
@@ -646,6 +684,52 @@ class GuiProjectTree(QTreeWidget):
|
||||
logger.debug("%d items added to the project tree" % iCount)
|
||||
return True
|
||||
|
||||
def undoLastMove(self):
|
||||
"""Attempt to undo the last action.
|
||||
"""
|
||||
srcItem = self._lastMove.get("item", None)
|
||||
dstItem = self._lastMove.get("parent", None)
|
||||
dstIndex = self._lastMove.get("index", None)
|
||||
|
||||
if not self.hasFocus():
|
||||
return False
|
||||
|
||||
if srcItem is None or dstItem is None or dstIndex is None:
|
||||
logger.verbose("No tree move to undo")
|
||||
return False
|
||||
|
||||
if srcItem not in self._treeMap.values():
|
||||
logger.warning("Source item no longer exists")
|
||||
return False
|
||||
|
||||
if dstItem not in self._treeMap.values():
|
||||
logger.warning("Previous parent item no longer exists")
|
||||
return False
|
||||
|
||||
dstIndex = min(max(0, dstIndex), dstItem.childCount())
|
||||
wCount = int(srcItem.data(self.C_COUNT, Qt.UserRole))
|
||||
sHandle = srcItem.data(self.C_NAME, Qt.UserRole)
|
||||
dHandle = dstItem.data(self.C_NAME, Qt.UserRole)
|
||||
logger.debug("Moving item %s back to %s, index %d" % (
|
||||
sHandle, dHandle, dstIndex
|
||||
))
|
||||
|
||||
self.propagateCount(sHandle, 0)
|
||||
parItem = srcItem.parent()
|
||||
srcIndex = parItem.indexOfChild(srcItem)
|
||||
movItem = parItem.takeChild(srcIndex)
|
||||
dstItem.insertChild(dstIndex, movItem)
|
||||
|
||||
snItem = self.theProject.projTree[sHandle]
|
||||
dnItem = self.theProject.projTree[dHandle]
|
||||
self._postItemMove(sHandle, snItem, dnItem, wCount)
|
||||
|
||||
self.clearSelection()
|
||||
movItem.setSelected(True)
|
||||
self._lastMove = {}
|
||||
|
||||
return True
|
||||
|
||||
def getSelectedHandle(self):
|
||||
"""Get the currently selected handle. If multiple items are
|
||||
selected, return the first.
|
||||
@@ -754,46 +838,30 @@ class GuiProjectTree(QTreeWidget):
|
||||
self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR)
|
||||
return
|
||||
|
||||
pItem = sItem.parent()
|
||||
pIndex = 0
|
||||
if pItem is not None:
|
||||
pIndex = pItem.indexOfChild(sItem)
|
||||
|
||||
wCount = int(sItem.data(self.C_COUNT, Qt.UserRole))
|
||||
isFile = snItem.itemType == nwItemType.FILE
|
||||
isRoot = snItem.itemType == nwItemType.ROOT
|
||||
onFile = dnItem.itemType == nwItemType.FILE
|
||||
|
||||
isSame = snItem.itemClass == dnItem.itemClass
|
||||
isNone = snItem.itemClass == nwItemClass.NO_CLASS
|
||||
isNote = snItem.itemLayout == nwItemLayout.NOTE
|
||||
onFile = dnItem.itemType == nwItemType.FILE
|
||||
isRoot = snItem.itemType == nwItemType.ROOT
|
||||
onFree = dnItem.itemClass == nwItemClass.ARCHIVE
|
||||
onFree |= dnItem.itemClass == nwItemClass.TRASH
|
||||
onFree &= snItem.itemType == nwItemType.FILE
|
||||
isOnTop = self.dropIndicatorPosition() == QAbstractItemView.OnItem
|
||||
if (isSame or isNone or isNote or onFree) and not (onFile and isOnTop) and not isRoot:
|
||||
onFree = dnItem.itemClass in nwLists.FREE_CLASS and isFile
|
||||
|
||||
allowDrop = isSame or isNone or isNote or onFree
|
||||
allowDrop &= not (self.dropIndicatorPosition() == QAbstractItemView.OnItem and onFile)
|
||||
|
||||
if allowDrop and not isRoot:
|
||||
logger.debug("Drag'n'drop of item %s accepted" % sHandle)
|
||||
self.propagateCount(sHandle, 0)
|
||||
QTreeWidget.dropEvent(self, theEvent)
|
||||
self._updateItemParent(sHandle)
|
||||
|
||||
# If the item does not have the same class as the target,
|
||||
# and the target is not a free root folder, update its class
|
||||
if not (isSame or onFree):
|
||||
logger.debug("Item %s class has been changed from %s to %s" % (
|
||||
sHandle,
|
||||
snItem.itemClass.name,
|
||||
dnItem.itemClass.name
|
||||
))
|
||||
snItem.setClass(dnItem.itemClass)
|
||||
self.setTreeItemValues(sHandle)
|
||||
|
||||
self.propagateCount(sHandle, wCount)
|
||||
|
||||
# The items dropped into archive or trash should be removed
|
||||
# from the project index, for all other items, we rescan the
|
||||
# file to ensure the index is up to date.
|
||||
if onFree:
|
||||
self.theIndex.deleteHandle(sHandle)
|
||||
else:
|
||||
self.theIndex.reIndexHandle(sHandle)
|
||||
|
||||
# Trigger dependent updates
|
||||
self._setTreeChanged(True)
|
||||
self._emitItemChange(sHandle)
|
||||
self._postItemMove(sHandle, snItem, dnItem, wCount)
|
||||
self._recordLastMove(sItem, pItem, pIndex)
|
||||
|
||||
else:
|
||||
theEvent.ignore()
|
||||
@@ -806,22 +874,63 @@ class GuiProjectTree(QTreeWidget):
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _postItemMove(self, sHandle, snItem, dnItem, wCount):
|
||||
"""Run various maintenance tasks for a moved item.
|
||||
"""
|
||||
isFile = snItem.itemType == nwItemType.FILE
|
||||
isSame = snItem.itemClass == dnItem.itemClass
|
||||
onFree = dnItem.itemClass in nwLists.FREE_CLASS and isFile
|
||||
|
||||
self._updateItemParent(sHandle)
|
||||
|
||||
# If the item does not have the same class as the target,
|
||||
# and the target is not a free root folder, update its class
|
||||
if not (isSame or onFree):
|
||||
logger.debug("Item %s class has been changed from %s to %s" % (
|
||||
sHandle, snItem.itemClass.name, dnItem.itemClass.name
|
||||
))
|
||||
snItem.setClass(dnItem.itemClass)
|
||||
self.setTreeItemValues(sHandle)
|
||||
|
||||
self.propagateCount(sHandle, wCount)
|
||||
|
||||
# The items dropped into archive or trash should be removed
|
||||
# from the project index, for all other items, we rescan the
|
||||
# file to ensure the index is up to date.
|
||||
if onFree:
|
||||
self.theIndex.deleteHandle(sHandle)
|
||||
else:
|
||||
self.theIndex.reIndexHandle(sHandle)
|
||||
|
||||
# Trigger dependent updates
|
||||
self._setTreeChanged(True)
|
||||
self._emitItemChange(sHandle)
|
||||
|
||||
return
|
||||
|
||||
def _getTreeItem(self, tHandle):
|
||||
"""Returns the QTreeWidgetItem of a given item handle.
|
||||
"""
|
||||
return self._treeMap.get(tHandle, None)
|
||||
|
||||
def _scanChildren(self, theList, theItem, theIndex):
|
||||
def _deleteTreeItem(self, tHandle):
|
||||
"""Delete a tree item from the project and the map.
|
||||
"""
|
||||
del self.theProject.projTree[tHandle]
|
||||
self._treeMap.pop(tHandle, None)
|
||||
return
|
||||
|
||||
def _scanChildren(self, theList, tItem, tIndex):
|
||||
"""This is a recursive function returning all items in a tree
|
||||
starting at a given QTreeWidgetItem.
|
||||
"""
|
||||
tHandle = theItem.data(self.C_NAME, Qt.UserRole)
|
||||
tHandle = tItem.data(self.C_NAME, Qt.UserRole)
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
nwItem.setExpanded(theItem.isExpanded())
|
||||
nwItem.setOrder(theIndex)
|
||||
nwItem.setExpanded(tItem.isExpanded())
|
||||
nwItem.setOrder(tIndex)
|
||||
theList.append(tHandle)
|
||||
for i in range(theItem.childCount()):
|
||||
self._scanChildren(theList, theItem.child(i), i)
|
||||
for i in range(tItem.childCount()):
|
||||
self._scanChildren(theList, tItem.child(i), i)
|
||||
return theList
|
||||
|
||||
def _addTreeItem(self, nwItem, nHandle=None):
|
||||
@@ -951,6 +1060,19 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
return
|
||||
|
||||
def _recordLastMove(self, srcItem, parItem, parIndex):
|
||||
"""Record the last action so that it can be undone.
|
||||
"""
|
||||
prevItem = self._lastMove.get("item", None)
|
||||
if prevItem is None or srcItem != prevItem:
|
||||
self._lastMove = {
|
||||
"item": srcItem,
|
||||
"parent": parItem,
|
||||
"index": parIndex,
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiProjectTree
|
||||
|
||||
class GuiProjectTreeMenu(QMenu):
|
||||
|
||||
+27
-14
@@ -29,7 +29,6 @@ import logging
|
||||
|
||||
from time import time
|
||||
|
||||
from PyQt5.QtCore import QTimer
|
||||
from PyQt5.QtGui import QColor, QPainter
|
||||
from PyQt5.QtWidgets import qApp, QStatusBar, QLabel, QAbstractButton
|
||||
|
||||
@@ -49,6 +48,7 @@ class GuiMainStatus(QStatusBar):
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.refTime = None
|
||||
self.userIdle = False
|
||||
|
||||
colNone = QColor(*self.theTheme.statNone)
|
||||
colTrue = QColor(*self.theTheme.statUnsaved)
|
||||
@@ -97,9 +97,12 @@ class GuiMainStatus(QStatusBar):
|
||||
|
||||
## The Session Clock
|
||||
### Set the mimimum width so the label doesn't rescale every second
|
||||
self.timePixmap = self.theTheme.getPixmap("status_time", (iPx, iPx))
|
||||
self.idlePixmap = self.theTheme.getPixmap("status_idle", (iPx, iPx))
|
||||
|
||||
self.timeIcon = QLabel()
|
||||
self.timeText = QLabel("")
|
||||
self.timeIcon.setPixmap(self.theTheme.getPixmap("status_time", (iPx, iPx)))
|
||||
self.timeIcon.setPixmap(self.timePixmap)
|
||||
self.timeText.setToolTip("Session Time")
|
||||
self.timeText.setMinimumWidth(self.theTheme.getTextWidth("00:00:00:"))
|
||||
self.timeIcon.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -110,12 +113,6 @@ class GuiMainStatus(QStatusBar):
|
||||
# Other Settings
|
||||
self.setSizeGripEnabled(True)
|
||||
|
||||
# Start the Clock
|
||||
self.sessionTimer = QTimer()
|
||||
self.sessionTimer.setInterval(1000)
|
||||
self.sessionTimer.timeout.connect(self._updateTime)
|
||||
self.sessionTimer.start()
|
||||
|
||||
logger.debug("GuiMainStatus initialisation complete")
|
||||
|
||||
self.clearStatus()
|
||||
@@ -130,7 +127,7 @@ class GuiMainStatus(QStatusBar):
|
||||
self.setStats(0, 0)
|
||||
self.setProjectStatus(None)
|
||||
self.setDocumentStatus(None)
|
||||
self._updateTime()
|
||||
self.updateTime()
|
||||
return True
|
||||
|
||||
##
|
||||
@@ -182,17 +179,33 @@ class GuiMainStatus(QStatusBar):
|
||||
self.statsText.setToolTip("Project word count (session change)")
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
def setUserIdle(self, userIdle):
|
||||
"""Change the idle status icon.
|
||||
"""
|
||||
if not self.mainConf.stopWhenIdle:
|
||||
userIdle = False
|
||||
|
||||
def _updateTime(self):
|
||||
if self.userIdle != userIdle:
|
||||
if userIdle:
|
||||
self.timeIcon.setPixmap(self.idlePixmap)
|
||||
else:
|
||||
self.timeIcon.setPixmap(self.timePixmap)
|
||||
|
||||
self.userIdle = userIdle
|
||||
|
||||
return
|
||||
|
||||
def updateTime(self, idleTime=0.0):
|
||||
"""Update the session clock.
|
||||
"""
|
||||
if self.refTime is None:
|
||||
self.timeText.setText("00:00:00")
|
||||
else:
|
||||
self.timeText.setText(formatTime(round(time() - self.refTime)))
|
||||
if self.mainConf.stopWhenIdle:
|
||||
sessTime = round(time() - self.refTime - idleTime)
|
||||
else:
|
||||
sessTime = round(time() - self.refTime)
|
||||
self.timeText.setText(formatTime(sessTime))
|
||||
return
|
||||
|
||||
# END Class GuiMainStatus
|
||||
|
||||
+9
-8
@@ -268,9 +268,9 @@ class GuiTheme:
|
||||
if os.path.isfile(self.cssFile):
|
||||
with open(self.cssFile, mode="r", encoding="utf8") as inFile:
|
||||
cssData = inFile.read()
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error("Could not load theme css file")
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
return False
|
||||
|
||||
# Config File
|
||||
@@ -278,9 +278,9 @@ class GuiTheme:
|
||||
try:
|
||||
with open(self.confFile, mode="r", encoding="utf8") as inFile:
|
||||
confParser.read_file(inFile)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error("Could not load theme settings from: %s" % self.confFile)
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
return False
|
||||
|
||||
## Main
|
||||
@@ -336,9 +336,9 @@ class GuiTheme:
|
||||
try:
|
||||
with open(self.syntaxFile, mode="r", encoding="utf8") as inFile:
|
||||
confParser.read_file(inFile)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error("Could not load syntax colours from: %s" % self.syntaxFile)
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
return False
|
||||
|
||||
## Main
|
||||
@@ -539,6 +539,7 @@ class GuiIcons:
|
||||
"proj_nwx" : (None, None),
|
||||
"status_lang" : (None, None),
|
||||
"status_time" : (None, None),
|
||||
"status_idle" : (None, None),
|
||||
"status_stats" : (None, None),
|
||||
"status_lines" : (None, None),
|
||||
"doc_h0" : (QStyle.SP_FileIcon, "x-office-document"),
|
||||
@@ -640,9 +641,9 @@ class GuiIcons:
|
||||
try:
|
||||
with open(self.confFile, mode="r", encoding="utf8") as inFile:
|
||||
confParser.read_file(inFile)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error("Could not load icon theme settings from: %s" % self.confFile)
|
||||
logger.error(str(e))
|
||||
nw.logException()
|
||||
return False
|
||||
|
||||
## Main
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
novelWriter – GUI User Wordlist
|
||||
===============================
|
||||
Class holding the user's wordlist dialog
|
||||
|
||||
File History:
|
||||
Created: 2021-02-12 [1.2b1]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2021, Veronica Berglyd Olsen
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import nw
|
||||
import logging
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog, QDialogButtonBox, QVBoxLayout, QHBoxLayout, QListWidget,
|
||||
QAbstractItemView, QPushButton, QLineEdit, QLabel
|
||||
)
|
||||
|
||||
from nw.constants import nwFiles, nwAlert
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiWordList(QDialog):
|
||||
|
||||
def __init__(self, theParent, theProject):
|
||||
QDialog.__init__(self, theParent)
|
||||
|
||||
logger.debug("Initialising GuiWordList ...")
|
||||
self.setObjectName("GuiWordList")
|
||||
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theProject = theProject
|
||||
self.optState = theProject.optState
|
||||
|
||||
self.setWindowTitle("Project Word List")
|
||||
|
||||
mS = self.mainConf.pxInt(250)
|
||||
wW = self.mainConf.pxInt(320)
|
||||
wH = self.mainConf.pxInt(340)
|
||||
|
||||
self.setMinimumWidth(mS)
|
||||
self.setMinimumHeight(mS)
|
||||
self.resize(
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiWordList", "winWidth", wW)),
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiWordList", "winHeight", wH))
|
||||
)
|
||||
|
||||
# Main Widgets
|
||||
# ============
|
||||
|
||||
self.headLabel = QLabel("<b>Project Word List</b>")
|
||||
|
||||
self.listBox = QListWidget()
|
||||
self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
|
||||
self.listBox.setSortingEnabled(True)
|
||||
|
||||
self.newEntry = QLineEdit()
|
||||
|
||||
self.addButton = QPushButton(self.theTheme.getIcon("add"), "")
|
||||
self.addButton.setToolTip("Add new entry")
|
||||
self.addButton.clicked.connect(self._doAdd)
|
||||
|
||||
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
|
||||
self.delButton.setToolTip("Delete selected entry")
|
||||
self.delButton.clicked.connect(self._doDelete)
|
||||
|
||||
self.editBox = QHBoxLayout()
|
||||
self.editBox.addWidget(self.newEntry, 1)
|
||||
self.editBox.addWidget(self.addButton, 0)
|
||||
self.editBox.addWidget(self.delButton, 0)
|
||||
|
||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Close)
|
||||
self.buttonBox.accepted.connect(self._doSave)
|
||||
self.buttonBox.rejected.connect(self._doClose)
|
||||
|
||||
# Assemble
|
||||
# ========
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.addWidget(self.headLabel)
|
||||
self.outerBox.addSpacing(self.mainConf.pxInt(8))
|
||||
self.outerBox.addWidget(self.listBox, 1)
|
||||
self.outerBox.addLayout(self.editBox, 0)
|
||||
self.outerBox.addSpacing(self.mainConf.pxInt(12))
|
||||
self.outerBox.addWidget(self.buttonBox, 0)
|
||||
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
self._loadWordList()
|
||||
|
||||
logger.debug("GuiWordList initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
|
||||
def _doAdd(self):
|
||||
"""Add a new word to the word list.
|
||||
"""
|
||||
newWord = self.newEntry.text().strip()
|
||||
if newWord == "":
|
||||
self.theParent.makeAlert("Cannot add a blank word.", nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
if self.listBox.findItems(newWord, Qt.MatchExactly):
|
||||
self.theParent.makeAlert(
|
||||
"The word '%s' is already in the word list." % newWord, nwAlert.ERROR
|
||||
)
|
||||
return False
|
||||
|
||||
self.listBox.addItem(newWord)
|
||||
self.newEntry.setText("")
|
||||
|
||||
return True
|
||||
|
||||
def _doDelete(self):
|
||||
"""Delete the selected item.
|
||||
"""
|
||||
selItem = self.listBox.selectedItems()
|
||||
if selItem:
|
||||
self.listBox.takeItem(self.listBox.row(selItem[0]))
|
||||
return
|
||||
|
||||
def _doSave(self):
|
||||
"""Save the new word list and close.
|
||||
"""
|
||||
self._saveGuiSettings()
|
||||
|
||||
dctFile = os.path.join(self.theProject.projMeta, nwFiles.PROJ_DICT)
|
||||
tmpFile = dctFile + "~"
|
||||
|
||||
try:
|
||||
with open(tmpFile, mode="w", encoding="utf8") as outFile:
|
||||
for i in range(self.listBox.count()):
|
||||
outFile.write(self.listBox.item(i).text() + "\n")
|
||||
|
||||
except Exception:
|
||||
logger.error("Could not save new word list")
|
||||
nw.logException()
|
||||
self.reject()
|
||||
return False
|
||||
|
||||
if os.path.isfile(dctFile):
|
||||
os.unlink(dctFile)
|
||||
os.rename(tmpFile, dctFile)
|
||||
self.accept()
|
||||
|
||||
return True
|
||||
|
||||
def _doClose(self):
|
||||
"""Close without saving the word list.
|
||||
"""
|
||||
self._saveGuiSettings()
|
||||
self.reject()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _loadWordList(self):
|
||||
"""Load the project's word list, if it exists.
|
||||
"""
|
||||
self.listBox.clear()
|
||||
|
||||
wordList = os.path.join(self.theProject.projMeta, nwFiles.PROJ_DICT)
|
||||
if not os.path.isfile(wordList):
|
||||
logger.debug("No project dictionary file found")
|
||||
return False
|
||||
|
||||
with open(wordList, mode="r", encoding="utf8") as inFile:
|
||||
for inLine in inFile:
|
||||
theWord = inLine.strip()
|
||||
if len(theWord) == 0:
|
||||
continue
|
||||
self.listBox.addItem(theWord)
|
||||
|
||||
return True
|
||||
|
||||
def _saveGuiSettings(self):
|
||||
"""Save GUI settings.
|
||||
"""
|
||||
winWidth = self.mainConf.rpxInt(self.width())
|
||||
winHeight = self.mainConf.rpxInt(self.height())
|
||||
|
||||
self.optState.setValue("GuiWordList", "winWidth", winWidth)
|
||||
self.optState.setValue("GuiWordList", "winHeight", winHeight)
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiWordList
|
||||
+79
-26
@@ -48,8 +48,9 @@ class GuiWritingStats(QDialog):
|
||||
|
||||
C_TIME = 0
|
||||
C_LENGTH = 1
|
||||
C_COUNT = 2
|
||||
C_BAR = 3
|
||||
C_IDLE = 2
|
||||
C_COUNT = 3
|
||||
C_BAR = 4
|
||||
|
||||
FMT_JSON = 0
|
||||
FMT_CSV = 1
|
||||
@@ -89,16 +90,21 @@ class GuiWritingStats(QDialog):
|
||||
wCol2 = self.mainConf.pxInt(
|
||||
self.optState.getInt("GuiWritingStats", "widthCol2", 80)
|
||||
)
|
||||
wCol3 = self.mainConf.pxInt(
|
||||
self.optState.getInt("GuiWritingStats", "widthCol3", 80)
|
||||
)
|
||||
|
||||
self.listBox = QTreeWidget()
|
||||
self.listBox.setHeaderLabels(["Session Start", "Length", "Words", "Histogram"])
|
||||
self.listBox.setHeaderLabels(["Session Start", "Length", "Idle", "Words", "Histogram"])
|
||||
self.listBox.setIndentation(0)
|
||||
self.listBox.setColumnWidth(self.C_TIME, wCol0)
|
||||
self.listBox.setColumnWidth(self.C_LENGTH, wCol1)
|
||||
self.listBox.setColumnWidth(self.C_COUNT, wCol2)
|
||||
self.listBox.setColumnWidth(self.C_IDLE, wCol2)
|
||||
self.listBox.setColumnWidth(self.C_COUNT, wCol3)
|
||||
|
||||
hHeader = self.listBox.headerItem()
|
||||
hHeader.setTextAlignment(self.C_LENGTH, Qt.AlignRight)
|
||||
hHeader.setTextAlignment(self.C_IDLE, Qt.AlignRight)
|
||||
hHeader.setTextAlignment(self.C_COUNT, Qt.AlignRight)
|
||||
|
||||
sortValid = (Qt.AscendingOrder, Qt.DescendingOrder)
|
||||
@@ -127,6 +133,10 @@ class GuiWritingStats(QDialog):
|
||||
self.labelTotal.setFont(self.theTheme.guiFontFixed)
|
||||
self.labelTotal.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
|
||||
|
||||
self.labelIdleT = QLabel(formatTime(0))
|
||||
self.labelIdleT.setFont(self.theTheme.guiFontFixed)
|
||||
self.labelIdleT.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
|
||||
|
||||
self.labelFilter = QLabel(formatTime(0))
|
||||
self.labelFilter.setFont(self.theTheme.guiFontFixed)
|
||||
self.labelFilter.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
|
||||
@@ -144,16 +154,18 @@ class GuiWritingStats(QDialog):
|
||||
self.totalWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
|
||||
|
||||
self.infoForm.addWidget(QLabel("Total Time:"), 0, 0)
|
||||
self.infoForm.addWidget(QLabel("Filtered Time:"), 1, 0)
|
||||
self.infoForm.addWidget(QLabel("Novel Word Count:"), 2, 0)
|
||||
self.infoForm.addWidget(QLabel("Notes Word Count:"), 3, 0)
|
||||
self.infoForm.addWidget(QLabel("Total Word Count:"), 4, 0)
|
||||
self.infoForm.addWidget(QLabel("Idle Time:"), 1, 0)
|
||||
self.infoForm.addWidget(QLabel("Filtered Time:"), 2, 0)
|
||||
self.infoForm.addWidget(QLabel("Novel Word Count:"), 3, 0)
|
||||
self.infoForm.addWidget(QLabel("Notes Word Count:"), 4, 0)
|
||||
self.infoForm.addWidget(QLabel("Total Word Count:"), 5, 0)
|
||||
self.infoForm.addWidget(self.labelTotal, 0, 1)
|
||||
self.infoForm.addWidget(self.labelFilter, 1, 1)
|
||||
self.infoForm.addWidget(self.novelWords, 2, 1)
|
||||
self.infoForm.addWidget(self.notesWords, 3, 1)
|
||||
self.infoForm.addWidget(self.totalWords, 4, 1)
|
||||
self.infoForm.setRowStretch(5, 1)
|
||||
self.infoForm.addWidget(self.labelIdleT, 1, 1)
|
||||
self.infoForm.addWidget(self.labelFilter, 2, 1)
|
||||
self.infoForm.addWidget(self.novelWords, 3, 1)
|
||||
self.infoForm.addWidget(self.notesWords, 4, 1)
|
||||
self.infoForm.addWidget(self.totalWords, 5, 1)
|
||||
self.infoForm.setRowStretch(6, 1)
|
||||
|
||||
# Filter Options
|
||||
sPx = self.theTheme.baseIconSize
|
||||
@@ -192,17 +204,25 @@ class GuiWritingStats(QDialog):
|
||||
)
|
||||
self.groupByDay.clicked.connect(self._updateListBox)
|
||||
|
||||
self.showIdleTime = QSwitch(width=2*sPx, height=sPx)
|
||||
self.showIdleTime.setChecked(
|
||||
self.optState.getBool("GuiWritingStats", "showIdleTime", False)
|
||||
)
|
||||
self.showIdleTime.clicked.connect(self._updateListBox)
|
||||
|
||||
self.filterForm.addWidget(QLabel("Count novel files"), 0, 0)
|
||||
self.filterForm.addWidget(QLabel("Count note files"), 1, 0)
|
||||
self.filterForm.addWidget(QLabel("Hide zero word count"), 2, 0)
|
||||
self.filterForm.addWidget(QLabel("Hide negative word count"), 3, 0)
|
||||
self.filterForm.addWidget(QLabel("Group entries by day"), 4, 0)
|
||||
self.filterForm.addWidget(QLabel("Show idle time"), 5, 0)
|
||||
self.filterForm.addWidget(self.incNovel, 0, 1)
|
||||
self.filterForm.addWidget(self.incNotes, 1, 1)
|
||||
self.filterForm.addWidget(self.hideZeros, 2, 1)
|
||||
self.filterForm.addWidget(self.hideNegative, 3, 1)
|
||||
self.filterForm.addWidget(self.groupByDay, 4, 1)
|
||||
self.filterForm.setRowStretch(5, 1)
|
||||
self.filterForm.addWidget(self.showIdleTime, 5, 1)
|
||||
self.filterForm.setRowStretch(6, 1)
|
||||
|
||||
# Settings
|
||||
self.histMax = QSpinBox(self)
|
||||
@@ -278,6 +298,7 @@ class GuiWritingStats(QDialog):
|
||||
widthCol0 = self.mainConf.rpxInt(self.listBox.columnWidth(0))
|
||||
widthCol1 = self.mainConf.rpxInt(self.listBox.columnWidth(1))
|
||||
widthCol2 = self.mainConf.rpxInt(self.listBox.columnWidth(2))
|
||||
widthCol3 = self.mainConf.rpxInt(self.listBox.columnWidth(3))
|
||||
sortCol = self.listBox.sortColumn()
|
||||
sortOrder = self.listBox.header().sortIndicatorOrder()
|
||||
incNovel = self.incNovel.isChecked()
|
||||
@@ -285,6 +306,7 @@ class GuiWritingStats(QDialog):
|
||||
hideZeros = self.hideZeros.isChecked()
|
||||
hideNegative = self.hideNegative.isChecked()
|
||||
groupByDay = self.groupByDay.isChecked()
|
||||
showIdleTime = self.showIdleTime.isChecked()
|
||||
histMax = self.histMax.value()
|
||||
|
||||
self.optState.setValue("GuiWritingStats", "winWidth", winWidth)
|
||||
@@ -292,6 +314,7 @@ class GuiWritingStats(QDialog):
|
||||
self.optState.setValue("GuiWritingStats", "widthCol0", widthCol0)
|
||||
self.optState.setValue("GuiWritingStats", "widthCol1", widthCol1)
|
||||
self.optState.setValue("GuiWritingStats", "widthCol2", widthCol2)
|
||||
self.optState.setValue("GuiWritingStats", "widthCol3", widthCol3)
|
||||
self.optState.setValue("GuiWritingStats", "sortCol", sortCol)
|
||||
self.optState.setValue("GuiWritingStats", "sortOrder", sortOrder)
|
||||
self.optState.setValue("GuiWritingStats", "incNovel", incNovel)
|
||||
@@ -299,6 +322,7 @@ class GuiWritingStats(QDialog):
|
||||
self.optState.setValue("GuiWritingStats", "hideZeros", hideZeros)
|
||||
self.optState.setValue("GuiWritingStats", "hideNegative", hideNegative)
|
||||
self.optState.setValue("GuiWritingStats", "groupByDay", groupByDay)
|
||||
self.optState.setValue("GuiWritingStats", "showIdleTime", showIdleTime)
|
||||
self.optState.setValue("GuiWritingStats", "histMax", histMax)
|
||||
|
||||
self.optState.saveSettings()
|
||||
@@ -347,23 +371,25 @@ class GuiWritingStats(QDialog):
|
||||
with open(savePath, mode="w", encoding="utf8") as outFile:
|
||||
if dataFmt == self.FMT_JSON:
|
||||
jsonData = []
|
||||
for _, sD, tT, wD, wA, wB in self.filterData:
|
||||
for _, sD, tT, wD, wA, wB, tI in self.filterData:
|
||||
jsonData.append({
|
||||
"date": sD,
|
||||
"length": tT,
|
||||
"newWords": wD,
|
||||
"novelWords": wA,
|
||||
"noteWords": wB,
|
||||
"idleTime": tI,
|
||||
})
|
||||
json.dump(jsonData, outFile, indent=2)
|
||||
wSuccess = True
|
||||
|
||||
if dataFmt == self.FMT_CSV:
|
||||
outFile.write(
|
||||
'"Date","Length (sec)","Words Changed","Novel Words","Note Words"\n'
|
||||
'"Date","Length (sec)","Words Changed",'
|
||||
'"Novel Words","Note Words","Idle Time (sec)"\n'
|
||||
)
|
||||
for _, sD, tT, wD, wA, wB in self.filterData:
|
||||
outFile.write(f'"{sD}",{tT:.0f},{wD},{wA},{wB}\n')
|
||||
for _, sD, tT, wD, wA, wB, tI in self.filterData:
|
||||
outFile.write(f'"{sD}",{tT:.0f},{wD},{wA},{wB},{tI}\n')
|
||||
wSuccess = True
|
||||
|
||||
except Exception as e:
|
||||
@@ -401,6 +427,7 @@ class GuiWritingStats(QDialog):
|
||||
ttNovel = 0
|
||||
ttNotes = 0
|
||||
ttTime = 0
|
||||
ttIdle = 0
|
||||
|
||||
logFile = os.path.join(self.theProject.projMeta, nwFiles.SESS_STATS)
|
||||
if not os.path.isfile(logFile):
|
||||
@@ -419,7 +446,7 @@ class GuiWritingStats(QDialog):
|
||||
continue
|
||||
|
||||
inData = inLine.split()
|
||||
if len(inData) != 6:
|
||||
if len(inData) < 6:
|
||||
continue
|
||||
|
||||
dStart = datetime.strptime(
|
||||
@@ -429,16 +456,21 @@ class GuiWritingStats(QDialog):
|
||||
"%s %s" % (inData[2], inData[3]), nwConst.FMT_TSTAMP
|
||||
)
|
||||
|
||||
sIdle = 0
|
||||
if len(inData) > 6:
|
||||
sIdle = checkInt(inData[6], 0)
|
||||
|
||||
tDiff = dEnd - dStart
|
||||
sDiff = tDiff.total_seconds()
|
||||
ttTime += sDiff
|
||||
ttIdle += sIdle
|
||||
|
||||
wcNovel = int(inData[4])
|
||||
wcNotes = int(inData[5])
|
||||
ttNovel = wcNovel
|
||||
ttNotes = wcNotes
|
||||
|
||||
self.logData.append((dStart, sDiff, wcNovel, wcNotes))
|
||||
self.logData.append((dStart, sDiff, wcNovel, wcNotes, sIdle))
|
||||
|
||||
except Exception as e:
|
||||
self.theParent.makeAlert(
|
||||
@@ -448,12 +480,17 @@ class GuiWritingStats(QDialog):
|
||||
|
||||
ttWords = ttNovel + ttNotes
|
||||
self.labelTotal.setText(formatTime(round(ttTime)))
|
||||
self.labelIdleT.setText(formatTime(round(ttIdle)))
|
||||
self.novelWords.setText(f"{ttNovel:n}")
|
||||
self.notesWords.setText(f"{ttNotes:n}")
|
||||
self.totalWords.setText(f"{ttWords:n}")
|
||||
|
||||
return True
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
|
||||
def _updateListBox(self, dummyVar=None):
|
||||
"""Load/reload the content of the list box. The dummyVar
|
||||
variable captures the variable sent from the widgets connecting
|
||||
@@ -474,25 +511,28 @@ class GuiWritingStats(QDialog):
|
||||
tempData = []
|
||||
sessDate = None
|
||||
sessTime = 0
|
||||
sessIdle = 0
|
||||
lstNovel = 0
|
||||
lstNotes = 0
|
||||
|
||||
for n, (dStart, sDiff, wcNovel, wcNotes) in enumerate(self.logData):
|
||||
for n, (dStart, sDiff, wcNovel, wcNotes, sIdle) in enumerate(self.logData):
|
||||
if n == 0:
|
||||
sessDate = dStart.date()
|
||||
if sessDate != dStart.date():
|
||||
tempData.append((sessDate, sessTime, lstNovel, lstNotes))
|
||||
tempData.append((sessDate, sessTime, lstNovel, lstNotes, sessIdle))
|
||||
sessDate = dStart.date()
|
||||
sessTime = sDiff
|
||||
sessIdle = sIdle
|
||||
lstNovel = wcNovel
|
||||
lstNotes = wcNotes
|
||||
else:
|
||||
sessTime += sDiff
|
||||
sessIdle += sIdle
|
||||
lstNovel = wcNovel
|
||||
lstNotes = wcNotes
|
||||
|
||||
if sessDate is not None:
|
||||
tempData.append((sessDate, sessTime, lstNovel, lstNotes))
|
||||
tempData.append((sessDate, sessTime, lstNovel, lstNotes, sessIdle))
|
||||
|
||||
else:
|
||||
tempData = self.logData
|
||||
@@ -502,7 +542,7 @@ class GuiWritingStats(QDialog):
|
||||
pcTotal = 0
|
||||
listMax = 0
|
||||
isFirst = True
|
||||
for dStart, sDiff, wcNovel, wcNotes in tempData:
|
||||
for dStart, sDiff, wcNovel, wcNotes, sIdle in tempData:
|
||||
|
||||
wcTotal = 0
|
||||
if incNovel:
|
||||
@@ -528,16 +568,24 @@ class GuiWritingStats(QDialog):
|
||||
else:
|
||||
sStart = dStart.strftime(nwConst.FMT_TSTAMP)
|
||||
|
||||
self.filterData.append((dStart, sStart, sDiff, dwTotal, wcNovel, wcNotes))
|
||||
self.filterData.append((dStart, sStart, sDiff, dwTotal, wcNovel, wcNotes, sIdle))
|
||||
listMax = min(max(listMax, dwTotal), histMax)
|
||||
pcTotal = wcTotal
|
||||
|
||||
# Populate the list
|
||||
for _, sStart, sDiff, nWords, _, _ in self.filterData:
|
||||
showIdleTime = self.showIdleTime.isChecked()
|
||||
for _, sStart, sDiff, nWords, _, _, sIdle in self.filterData:
|
||||
|
||||
if showIdleTime:
|
||||
idleEntry = formatTime(sIdle)
|
||||
else:
|
||||
sRatio = sIdle/sDiff if sDiff > 0.0 else 0.0
|
||||
idleEntry = "%d %%" % round(100.0 * sRatio)
|
||||
|
||||
newItem = QTreeWidgetItem()
|
||||
newItem.setText(self.C_TIME, sStart)
|
||||
newItem.setText(self.C_LENGTH, formatTime(round(sDiff)))
|
||||
newItem.setText(self.C_IDLE, idleEntry)
|
||||
newItem.setText(self.C_COUNT, f"{nWords:n}")
|
||||
|
||||
if nWords > 0 and listMax > 0:
|
||||
@@ -550,12 +598,17 @@ class GuiWritingStats(QDialog):
|
||||
newItem.setData(self.C_BAR, Qt.DecorationRole, theBar)
|
||||
|
||||
newItem.setTextAlignment(self.C_LENGTH, Qt.AlignRight)
|
||||
newItem.setTextAlignment(self.C_IDLE, Qt.AlignRight)
|
||||
newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight)
|
||||
newItem.setTextAlignment(self.C_BAR, Qt.AlignLeft | Qt.AlignVCenter)
|
||||
|
||||
newItem.setFont(self.C_TIME, self.theTheme.guiFontFixed)
|
||||
newItem.setFont(self.C_LENGTH, self.theTheme.guiFontFixed)
|
||||
newItem.setFont(self.C_COUNT, self.theTheme.guiFontFixed)
|
||||
if showIdleTime:
|
||||
newItem.setFont(self.C_IDLE, self.theTheme.guiFontFixed)
|
||||
else:
|
||||
newItem.setFont(self.C_IDLE, self.theTheme.guiFont)
|
||||
|
||||
self.listBox.addTopLevelItem(newItem)
|
||||
self.timeFilter += sDiff
|
||||
|
||||
+62
-6
@@ -43,7 +43,7 @@ from nw.gui import (
|
||||
GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiItemEditor,
|
||||
GuiMainMenu, GuiMainStatus, GuiNovelTree, GuiOutline, GuiOutlineDetails,
|
||||
GuiPreferences, GuiProjectDetails, GuiProjectLoad, GuiProjectSettings,
|
||||
GuiProjectTree, GuiProjectWizard, GuiTheme, GuiWritingStats
|
||||
GuiProjectTree, GuiProjectWizard, GuiTheme, GuiWordList, GuiWritingStats
|
||||
)
|
||||
from nw.core import NWProject, NWDoc, NWIndex
|
||||
from nw.constants import nwItemType, nwItemClass, nwAlert, nwLists
|
||||
@@ -86,6 +86,8 @@ class GuiMain(QMainWindow):
|
||||
self.theIndex = NWIndex(self.theProject, self)
|
||||
self.hasProject = False
|
||||
self.isFocusMode = False
|
||||
self.idleRefTime = time()
|
||||
self.idleTime = 0.0
|
||||
|
||||
# Prepare Main Window
|
||||
self.resize(*self.mainConf.getWinSize())
|
||||
@@ -241,6 +243,12 @@ class GuiMain(QMainWindow):
|
||||
self.asDocTimer = QTimer()
|
||||
self.asDocTimer.timeout.connect(self._autoSaveDocument)
|
||||
|
||||
# Main Clock
|
||||
self.mainTimer = QTimer()
|
||||
self.mainTimer.setInterval(1000)
|
||||
self.mainTimer.timeout.connect(self._timeTick)
|
||||
self.mainTimer.start()
|
||||
|
||||
# Shortcuts and Actions
|
||||
self._connectMenuActions()
|
||||
|
||||
@@ -412,7 +420,11 @@ class GuiMain(QMainWindow):
|
||||
self.closeDocument()
|
||||
self.docViewer.clearNavHistory()
|
||||
self.projView.closeOutline()
|
||||
self.theProject.closeProject()
|
||||
|
||||
self.theProject.closeProject(self.idleTime)
|
||||
self.idleRefTime = time()
|
||||
self.idleTime = 0.0
|
||||
|
||||
self.theIndex.clearIndex()
|
||||
self.clearGUI()
|
||||
self.hasProject = False
|
||||
@@ -477,7 +489,9 @@ class GuiMain(QMainWindow):
|
||||
return False
|
||||
|
||||
# Project is loaded
|
||||
self.hasProject = True
|
||||
self.hasProject = True
|
||||
self.idleRefTime = time()
|
||||
self.idleTime = 0.0
|
||||
|
||||
# Load the tag index
|
||||
self.theIndex.loadIndex()
|
||||
@@ -786,7 +800,11 @@ class GuiMain(QMainWindow):
|
||||
return False
|
||||
|
||||
if tHandle is None:
|
||||
tHandle = self.treeView.getSelectedHandle()
|
||||
if self.treeView.hasFocus():
|
||||
tHandle = self.treeView.getSelectedHandle()
|
||||
elif self.docEditor.hasFocus():
|
||||
tHandle = self.docEditor.theHandle
|
||||
|
||||
if tHandle is None:
|
||||
logger.warning("No item selected")
|
||||
return
|
||||
@@ -999,6 +1017,22 @@ class GuiMain(QMainWindow):
|
||||
|
||||
return
|
||||
|
||||
def showProjectWordListDialog(self):
|
||||
"""Open the project word list dialog.
|
||||
"""
|
||||
if not self.hasProject:
|
||||
logger.error("No project open")
|
||||
return
|
||||
|
||||
dlgWords = GuiWordList(self, self.theProject)
|
||||
dlgWords.exec_()
|
||||
|
||||
if dlgWords.result() == QDialog.Accepted:
|
||||
logger.debug("Reloading word list")
|
||||
self.docEditor.setDictionaries()
|
||||
|
||||
return
|
||||
|
||||
def showWritingStatsDialog(self):
|
||||
"""Open the session log dialog.
|
||||
"""
|
||||
@@ -1175,11 +1209,11 @@ class GuiMain(QMainWindow):
|
||||
"""
|
||||
if self.docEditor.theHandle is None:
|
||||
logger.error("No document open, so not activating Focus Mode")
|
||||
self.mainMenu.aFocusMode.setChecked(self.isFocusMode)
|
||||
self.mainMenu.setFocusMode(self.isFocusMode)
|
||||
return False
|
||||
|
||||
self.isFocusMode = not self.isFocusMode
|
||||
self.mainMenu.aFocusMode.setChecked(self.isFocusMode)
|
||||
self.mainMenu.setFocusMode(self.isFocusMode)
|
||||
if self.isFocusMode:
|
||||
logger.debug("Activating Focus Mode")
|
||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||
@@ -1414,6 +1448,28 @@ class GuiMain(QMainWindow):
|
||||
# Slots
|
||||
##
|
||||
|
||||
@pyqtSlot()
|
||||
def _timeTick(self):
|
||||
"""Triggered on every tick of the timer.
|
||||
"""
|
||||
if not self.hasProject:
|
||||
return
|
||||
|
||||
currTime = time()
|
||||
editIdle = currTime - self.docEditor.lastActive > self.mainConf.userIdleTime
|
||||
userIdle = qApp.applicationState() != Qt.ApplicationActive
|
||||
|
||||
if editIdle or userIdle:
|
||||
self.idleTime += currTime - self.idleRefTime
|
||||
self.statusBar.setUserIdle(True)
|
||||
else:
|
||||
self.statusBar.setUserIdle(False)
|
||||
|
||||
self.idleRefTime = currTime
|
||||
self.statusBar.updateTime(idleTime=self.idleTime)
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _treeSingleClick(self):
|
||||
"""Single click on a project tree item just updates the details
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
|
||||
Table of Contents
|
||||
=================
|
||||
|
||||
File Name Class Layout Document Label
|
||||
---------------------------------------------------------------------
|
||||
content/53b69b83cdafc.nwd NOVEL TITLE Title Page
|
||||
content/974e400180a99.nwd NOVEL PAGE Page
|
||||
content/edca4be2fcaf8.nwd NOVEL PARTITION Part One
|
||||
content/6a2d6d5f4f401.nwd NOVEL CHAPTER Chapter One
|
||||
content/636b6aa9b697b.nwd NOVEL SCENE Making a Scene
|
||||
content/bc0cbd2a407f3.nwd NOVEL SCENE Another Scene
|
||||
content/ba8a28a246524.nwd NOVEL UNNUMBERED Interlude
|
||||
content/96b68994dfa3d.nwd NOVEL NOTE A Note on Structure
|
||||
content/88706ddc78b1b.nwd NOVEL CHAPTER Chapter Two
|
||||
content/ae7339df26ded.nwd NOVEL SCENE We Found John!
|
||||
content/14298de4d9524.nwd CHARACTER NOTE John Smith
|
||||
content/bb2c23b3c42cc.nwd CHARACTER NOTE Jane Smith
|
||||
content/b3e74dbc1f584.nwd WORLD NOTE Earth
|
||||
content/f1471bef9f2ae.nwd WORLD NOTE Space
|
||||
content/5eaea4e8cdee8.nwd WORLD NOTE Mars
|
||||
content/8a5deb88c0e97.nwd NOVEL SCENE Old File
|
||||
content/b8136a5a774a0.nwd NOVEL SCENE Delete Me!
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
A scene is defined by a level three heading, like the one at the top of this page. The scene will be assigned to the chapter preceding it in the project tree.
|
||||
|
||||
Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, _italic_ and **_bold italic_**. You can also ~~strike through~~ text. There is **some support for _nested_ emphasis**, but it isn’t fully Markdown compliant. If the syntax highlighter doesn’t show it correctly, the export tool will not either.
|
||||
Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, _italic_ and **_bold italic_**. You can also ~~strike through~~ text. There is **some support for _nested_ emphasis**, but it isn’t fully Markdown compliant. If the syntax highlighter doesn’t show it correctly, the export tool will not either.
|
||||
|
||||
In addition, the editor supports automatic formatting of “quotes”, both double and ‘single’. Depending on the syntax highlighter, these can be in different colours. “You can of course use **bold** and _italic_ text inside of quotes too.”
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
### Another Scene
|
||||
|
||||
@pov: John
|
||||
@focus: Jane
|
||||
@location: Earth
|
||||
|
||||
Adding more scenes to a chapter is as easy as adding more scene files, with a level three heading, or just adding another level three heading in the same file if that works for the way you want to structure your files.
|
||||
@@ -13,6 +14,7 @@ In fact, if you wish, you can add all the scenes in the chapter file too. All no
|
||||
### More Scenes
|
||||
|
||||
@pov: Jane
|
||||
@focus: John
|
||||
@location: Earth
|
||||
|
||||
This is a second scene in the same file as the previous scene. You can always split the files up later.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="1.1.1" hexVersion="0x010101f0" fileVersion="1.2" timeStamp="2021-02-20 16:38:01">
|
||||
<novelWriterXML appVersion="1.2rc1" hexVersion="0x010200c1" fileVersion="1.2" timeStamp="2021-02-20 17:08:19">
|
||||
<project>
|
||||
<name>Sample Project</name>
|
||||
<title>Sample Project</title>
|
||||
<author>Jane Smith</author>
|
||||
<author>Jay Doh</author>
|
||||
<saveCount>881</saveCount>
|
||||
<autoCount>158</autoCount>
|
||||
<editTime>43034</editTime>
|
||||
<saveCount>937</saveCount>
|
||||
<autoCount>161</autoCount>
|
||||
<editTime>46797</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>False</doBackup>
|
||||
@@ -117,10 +117,10 @@
|
||||
<status>1st Draft</status>
|
||||
<exported>True</exported>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>1811</charCount>
|
||||
<charCount>1810</charCount>
|
||||
<wordCount>318</wordCount>
|
||||
<paraCount>8</paraCount>
|
||||
<cursorPos>1880</cursorPos>
|
||||
<cursorPos>3</cursorPos>
|
||||
</item>
|
||||
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
|
||||
<name>Another Scene</name>
|
||||
@@ -132,7 +132,7 @@
|
||||
<charCount>476</charCount>
|
||||
<wordCount>93</wordCount>
|
||||
<paraCount>3</paraCount>
|
||||
<cursorPos>428</cursorPos>
|
||||
<cursorPos>430</cursorPos>
|
||||
</item>
|
||||
<item handle="ba8a28a246524" order="3" parent="e7ded148d6e4a">
|
||||
<name>Interlude</name>
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
Table of Contents
|
||||
===================
|
||||
|
||||
File Name Class Document Label
|
||||
--------------------------------------------------------------------------------
|
||||
content/04468803b92e1.nwd WORLD Ancient Europe
|
||||
content/2426c6f0ca922.nwd PLOT Main
|
||||
content/441420a886d82.nwd NOVEL Chapter Two
|
||||
content/47666c91c7ccf.nwd NOVEL Scene Five
|
||||
content/4c4f28287af27.nwd CHARACTER Mr. Nobody
|
||||
content/7a992350f3eb6.nwd NOVEL Lorem Ipsum
|
||||
content/846352075de7d.nwd NOVEL Interlude
|
||||
content/88243afbe5ed8.nwd NOVEL Scene One
|
||||
content/88d59a277361b.nwd NOVEL Prologue
|
||||
content/8c58a65414c23.nwd NOVEL Front Matter
|
||||
content/db7e733775d4d.nwd NOVEL Act One
|
||||
content/eb103bc70c90c.nwd NOVEL Scene Three
|
||||
content/f8c0562e50f1b.nwd NOVEL Scene Four
|
||||
content/f96ec11c6a3da.nwd NOVEL Scene Two
|
||||
content/fb609cd8319dc.nwd NOVEL Chapter One
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
Table of Contents
|
||||
===================
|
||||
|
||||
File Name Class Document Label
|
||||
--------------------------------------------------------------------------------
|
||||
content/8c659a11cd429.nwd NOVEL New Scene
|
||||
content/a35baf2e93843.nwd NOVEL Title Page
|
||||
content/f5ab3e30151e1.nwd NOVEL New Chapter
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[Main]
|
||||
timestamp = 2020-10-11 22:50:45
|
||||
timestamp = 2021-02-09 21:07:03
|
||||
theme = default
|
||||
syntax = default_light
|
||||
icons = typicons_colour_light
|
||||
guidark = False
|
||||
guifont =
|
||||
guifontsize = 11
|
||||
lastnotes = 1.0
|
||||
lastnotes = 0x0
|
||||
|
||||
[Sizes]
|
||||
geometry = 1200, 650
|
||||
@@ -56,6 +56,8 @@ highlightquotes = True
|
||||
allowopensquote = False
|
||||
allowopendquote = True
|
||||
highlightemph = True
|
||||
stopwhenidle = True
|
||||
useridletime = 300
|
||||
|
||||
[Backup]
|
||||
backuppath =
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
# 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…”
|
||||
|
||||
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
|
||||
|
||||
_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
|
||||
|
||||
“Fusce maximus felis libero”
|
||||
|
||||
## Chapter 1: Chapter One
|
||||
|
||||
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.
|
||||
|
||||
* * *
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
* * *
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Chapter 2: Chapter Two
|
||||
|
||||
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.
|
||||
|
||||
* * *
|
||||
|
||||
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.
|
||||
|
||||
* * *
|
||||
|
||||
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.
|
||||
|
||||
* * *
|
||||
|
||||
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.
|
||||
|
||||
@@ -5,26 +5,27 @@
|
||||
<title>Lorem Ipsum</title>
|
||||
</head>
|
||||
<style>
|
||||
p {text-align: left;}
|
||||
h1, h2 {color: rgb(66, 113, 174);}
|
||||
h3, h4 {color: rgb(50, 50, 50);}
|
||||
h1, h2, h3, h4 {page-break-after: avoid;}
|
||||
body {font-family: 'DejaVu Sans'; font-size: 11pt;}
|
||||
p {text-align: left; line-height: 115%; margin-top: 0.00em; margin-bottom: 0.58em;}
|
||||
h1 {color: rgb(66, 113, 174); page-break-after: avoid; margin-top: 1.00em; margin-bottom: 0.50em;}
|
||||
h2 {color: rgb(66, 113, 174); page-break-after: avoid; margin-top: 0.83em; margin-bottom: 0.50em;}
|
||||
h3 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 0.58em; margin-bottom: 0.50em;}
|
||||
h4 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 0.58em; margin-bottom: 0.50em;}
|
||||
.title {font-size: 2.5em; margin-top: 1.00em; margin-bottom: 0.50em;}
|
||||
.sep, .skip {text-align: center; margin-top: 1.00em; margin-bottom: 1.00em;}
|
||||
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 class='title' style='text-align: center; page-break-before: auto;'>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…”</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…”</p>
|
||||
<p style='text-align: center;'>“Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit…”</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…”</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>
|
||||
@@ -65,7 +66,6 @@ article {width: 800px; margin: 40px auto;}
|
||||
<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>
|
||||
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
# 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…”
|
||||
|
||||
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
|
||||
|
||||
_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
|
||||
|
||||
“Fusce maximus felis libero”
|
||||
|
||||
## Chapter 1: Chapter One
|
||||
|
||||
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.
|
||||
|
||||
* * *
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
* * *
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Chapter 2: Chapter Two
|
||||
|
||||
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.
|
||||
|
||||
* * *
|
||||
|
||||
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.
|
||||
|
||||
* * *
|
||||
|
||||
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.
|
||||
|
||||
* * *
|
||||
|
||||
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.
|
||||
|
||||
@@ -5,44 +5,45 @@
|
||||
<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;}
|
||||
body {font-family: 'DejaVu Sans'; font-size: 11pt;}
|
||||
p {text-align: justify; line-height: 115%; margin-top: 0.00em; margin-bottom: 0.58em;}
|
||||
h1 {color: rgb(66, 113, 174); page-break-after: avoid; margin-top: 1.00em; margin-bottom: 0.50em;}
|
||||
h2 {color: rgb(66, 113, 174); page-break-after: avoid; margin-top: 0.83em; margin-bottom: 0.50em;}
|
||||
h3 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 0.58em; margin-bottom: 0.50em;}
|
||||
h4 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 0.58em; margin-bottom: 0.50em;}
|
||||
.title {font-size: 2.5em; margin-top: 1.00em; margin-bottom: 0.50em;}
|
||||
.sep, .skip {text-align: center; margin-top: 1.00em; margin-bottom: 1.00em;}
|
||||
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 class='title' style='text-align: center; page-break-before: auto;'>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…”</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…”</p>
|
||||
<p style='text-align: center;'>“Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit…”</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…”</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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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>
|
||||
<h2>Scene 1.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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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>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>
|
||||
@@ -50,9 +51,9 @@ article {width: 800px; margin: 40px auto;}
|
||||
<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>
|
||||
<h2>Scene 1.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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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>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>
|
||||
@@ -64,27 +65,27 @@ article {width: 800px; margin: 40px auto;}
|
||||
<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>	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>	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>	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>	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>	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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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>
|
||||
@@ -93,9 +94,9 @@ 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>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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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>
|
||||
@@ -104,22 +105,21 @@ 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>
|
||||
<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><span class='tags'>Tag:</span> <a name='tag_Bod'>Bod</a></p>
|
||||
<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><span class='tags'>Tag:</span> <a name='tag_Main'>Main</a></p>
|
||||
<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><span class='tags'>Tag:</span> <a name='tag_Europe'>Europe</a></p>
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
# 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…”
|
||||
|
||||
**Comment:** 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
|
||||
|
||||
**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
|
||||
|
||||
“Fusce maximus felis libero”
|
||||
|
||||
## Chapter One: Chapter One
|
||||
|
||||
**Point of View:** Bod
|
||||
**Plot:** Main
|
||||
**Locations:** Europe
|
||||
|
||||
**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 1.1: Scene One
|
||||
|
||||
**Point of View:** Bod
|
||||
**Plot:** Main
|
||||
**Locations:** 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.
|
||||
|
||||
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.
|
||||
|
||||
#### 1.1.1: 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 1.2: Scene Two
|
||||
|
||||
**Point of View:** Bod
|
||||
**Plot:** Main
|
||||
**Locations:** 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.
|
||||
|
||||
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.
|
||||
|
||||
#### 1.2.1: 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.
|
||||
|
||||
## Chapter Two: Why do we use it?
|
||||
|
||||
**Comment:** 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 Three: Chapter Two
|
||||
|
||||
**Point of View:** Bod
|
||||
**Plot:** Main
|
||||
**Locations:** Europe
|
||||
|
||||
**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 3.1: Scene Three
|
||||
|
||||
**Point of View:** Bod
|
||||
**Plot:** Main
|
||||
**Locations:** 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.
|
||||
|
||||
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 3.2: Scene Four
|
||||
|
||||
**Point of View:** Bod
|
||||
**Plot:** Main
|
||||
**Locations:** 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.
|
||||
|
||||
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 3.3: Scene Five
|
||||
|
||||
**Point of View:** Bod
|
||||
**Plot:** Main
|
||||
**Locations:** 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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -5,44 +5,45 @@
|
||||
<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;}
|
||||
body {font-family: 'DejaVu Sans'; font-size: 11pt;}
|
||||
p {text-align: justify; line-height: 115%; margin-top: 0.00em; margin-bottom: 0.58em;}
|
||||
h1 {color: rgb(66, 113, 174); page-break-after: avoid; margin-top: 1.00em; margin-bottom: 0.50em;}
|
||||
h2 {color: rgb(66, 113, 174); page-break-after: avoid; margin-top: 0.83em; margin-bottom: 0.50em;}
|
||||
h3 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 0.58em; margin-bottom: 0.50em;}
|
||||
h4 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 0.58em; margin-bottom: 0.50em;}
|
||||
.title {font-size: 2.5em; margin-top: 1.00em; margin-bottom: 0.50em;}
|
||||
.sep, .skip {text-align: center; margin-top: 1.00em; margin-bottom: 1.00em;}
|
||||
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 class='title' style='text-align: center; page-break-before: auto;'>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…”</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…”</p>
|
||||
<p style='text-align: center;'>“Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit…”</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…”</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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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>
|
||||
<h2>Scene 1.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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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>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>
|
||||
@@ -50,9 +51,9 @@ article {width: 800px; margin: 40px auto;}
|
||||
<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>
|
||||
<h2>Scene 1.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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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>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>
|
||||
@@ -64,27 +65,27 @@ article {width: 800px; margin: 40px auto;}
|
||||
<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> 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> 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> 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> 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> 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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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>
|
||||
@@ -93,9 +94,9 @@ 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>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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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>
|
||||
@@ -104,22 +105,21 @@ 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>
|
||||
<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><span class='tags'>Tag:</span> <a name='tag_Bod'>Bod</a></p>
|
||||
<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><span class='tags'>Tag:</span> <a name='tag_Main'>Main</a></p>
|
||||
<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><span class='tags'>Tag:</span> <a name='tag_Europe'>Europe</a></p>
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
# 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…”
|
||||
|
||||
**Comment:** 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
|
||||
|
||||
**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
|
||||
|
||||
“Fusce maximus felis libero”
|
||||
|
||||
## Chapter One: Chapter One
|
||||
|
||||
**Point of View:** Bod
|
||||
**Plot:** Main
|
||||
**Locations:** Europe
|
||||
|
||||
**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 1.1: Scene One
|
||||
|
||||
**Point of View:** Bod
|
||||
**Plot:** Main
|
||||
**Locations:** 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.
|
||||
|
||||
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.
|
||||
|
||||
#### 1.1.1: 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 1.2: Scene Two
|
||||
|
||||
**Point of View:** Bod
|
||||
**Plot:** Main
|
||||
**Locations:** 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.
|
||||
|
||||
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.
|
||||
|
||||
#### 1.2.1: 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.
|
||||
|
||||
## Chapter Two: Why do we use it?
|
||||
|
||||
**Comment:** 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 Three: Chapter Two
|
||||
|
||||
**Point of View:** Bod
|
||||
**Plot:** Main
|
||||
**Locations:** Europe
|
||||
|
||||
**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 3.1: Scene Three
|
||||
|
||||
**Point of View:** Bod
|
||||
**Plot:** Main
|
||||
**Locations:** 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.
|
||||
|
||||
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 3.2: Scene Four
|
||||
|
||||
**Point of View:** Bod
|
||||
**Plot:** Main
|
||||
**Locations:** 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.
|
||||
|
||||
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 3.3: Scene Five
|
||||
|
||||
**Point of View:** Bod
|
||||
**Plot:** Main
|
||||
**Locations:** 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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -5,26 +5,27 @@
|
||||
"authors": [
|
||||
"lipsum.com"
|
||||
],
|
||||
"buildTime": 1611662802
|
||||
"buildTime": 1612954696
|
||||
},
|
||||
"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;}",
|
||||
"body {font-family: 'DejaVu Sans'; font-size: 11pt;}",
|
||||
"p {text-align: justify; line-height: 115%; margin-top: 0.00em; margin-bottom: 0.58em;}",
|
||||
"h1 {color: rgb(66, 113, 174); page-break-after: avoid; margin-top: 1.00em; margin-bottom: 0.50em;}",
|
||||
"h2 {color: rgb(66, 113, 174); page-break-after: avoid; margin-top: 0.83em; margin-bottom: 0.50em;}",
|
||||
"h3 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 0.58em; margin-bottom: 0.50em;}",
|
||||
"h4 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 0.58em; margin-bottom: 0.50em;}",
|
||||
".title {font-size: 2.5em; margin-top: 1.00em; margin-bottom: 0.50em;}",
|
||||
".sep, .skip {text-align: center; margin-top: 1.00em; margin-bottom: 1.00em;}",
|
||||
"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 class='title' style='text-align: center; page-break-before: auto;'>Lorem Ipsum</h1>"
|
||||
],
|
||||
[
|
||||
""
|
||||
@@ -38,53 +39,53 @@
|
||||
],
|
||||
[
|
||||
"<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>",
|
||||
"<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>",
|
||||
"<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>",
|
||||
"<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>",
|
||||
"<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>",
|
||||
"<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>",
|
||||
"<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>",
|
||||
"<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>",
|
||||
"<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>",
|
||||
"<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>",
|
||||
"<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>",
|
||||
"<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>",
|
||||
"<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>",
|
||||
"<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>",
|
||||
"<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>",
|
||||
"<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>",
|
||||
"<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>",
|
||||
"<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>",
|
||||
"<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>",
|
||||
"<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>",
|
||||
"<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>",
|
||||
"<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>"
|
||||
]
|
||||
]
|
||||
|
||||
@@ -5,64 +5,64 @@
|
||||
<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;}
|
||||
body {font-family: 'DejaVu Sans'; font-size: 11pt;}
|
||||
p {text-align: justify; line-height: 115%; margin-top: 0.00em; margin-bottom: 0.58em;}
|
||||
h1 {color: rgb(66, 113, 174); page-break-after: avoid; margin-top: 1.00em; margin-bottom: 0.50em;}
|
||||
h2 {color: rgb(66, 113, 174); page-break-after: avoid; margin-top: 0.83em; margin-bottom: 0.50em;}
|
||||
h3 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 0.58em; margin-bottom: 0.50em;}
|
||||
h4 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 0.58em; margin-bottom: 0.50em;}
|
||||
.title {font-size: 2.5em; margin-top: 1.00em; margin-bottom: 0.50em;}
|
||||
.sep, .skip {text-align: center; margin-top: 1.00em; margin-bottom: 1.00em;}
|
||||
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 class='title' style='text-align: center; page-break-before: auto;'>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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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 style='margin-bottom: 0;'><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></p>
|
||||
<p style='margin-bottom: 0; margin-top: 0;'><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></p>
|
||||
<p style='margin-top: 0;'><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></p>
|
||||
<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>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%%~name: New Scene
|
||||
%%~path: 31489056e0916/0e17daca5f3e1
|
||||
%%~kind: NOVEL/SCENE
|
||||
%%~kind: NOVEL/BOOK
|
||||
# Novel
|
||||
|
||||
## Chapter
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="1.0b3" hexVersion="0x010000b3" fileVersion="1.2" timeStamp="2020-09-21 19:58:53">
|
||||
<novelWriterXML appVersion="1.2a0" hexVersion="0x010200a0" fileVersion="1.2" timeStamp="2021-01-29 01:10:23">
|
||||
<project>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
<saveCount>4</saveCount>
|
||||
<autoCount>1</autoCount>
|
||||
<editTime>11</editTime>
|
||||
<autoCount>2</autoCount>
|
||||
<editTime>8</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>True</doBackup>
|
||||
@@ -83,7 +83,7 @@
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<exported>True</exported>
|
||||
<layout>SCENE</layout>
|
||||
<layout>BOOK</layout>
|
||||
<charCount>466</charCount>
|
||||
<wordCount>83</wordCount>
|
||||
<paraCount>4</paraCount>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
[Main]
|
||||
timestamp = 2020-06-29 17:34:15
|
||||
timestamp = 2021-02-09 21:07:17
|
||||
theme = default
|
||||
syntax = default_light
|
||||
icons = typicons_colour_light
|
||||
guidark = True
|
||||
guifont = Cantarell
|
||||
guifont = Sans
|
||||
guifontsize = 12
|
||||
lastnotes = 1.0
|
||||
lastnotes = 0x0
|
||||
|
||||
[Sizes]
|
||||
geometry = 1100, 650
|
||||
treecols = 120, 30, 50
|
||||
geometry = 1200, 650
|
||||
treecols = 200, 50, 30
|
||||
novelcols = 200, 50
|
||||
projcols = 140, 55, 140
|
||||
projcols = 200, 60, 140
|
||||
mainpane = 300, 800
|
||||
docpane = 400, 400
|
||||
viewpane = 500, 150
|
||||
@@ -26,7 +26,7 @@ autosaveproject = 40
|
||||
autosavedoc = 20
|
||||
|
||||
[Editor]
|
||||
textfont = Cantarell
|
||||
textfont = None
|
||||
textsize = 13
|
||||
fixedwidth = False
|
||||
width = 700
|
||||
@@ -56,6 +56,8 @@ highlightquotes = False
|
||||
allowopensquote = False
|
||||
allowopendquote = True
|
||||
highlightemph = False
|
||||
stopwhenidle = True
|
||||
useridletime = 300
|
||||
|
||||
[Backup]
|
||||
backuppath = some/dir
|
||||
|
||||
@@ -26,7 +26,8 @@ import pytest
|
||||
from nw.common import (
|
||||
checkString, checkBool, checkInt, colRange, formatInt, transferCase,
|
||||
fuzzyTime, checkHandle, formatTimeStamp, formatTime, hexToInt,
|
||||
makeFileNameSafe
|
||||
makeFileNameSafe, isHandle, isTitleTag, isItemClass, isItemType,
|
||||
isItemLayout
|
||||
)
|
||||
from tools import cmpList
|
||||
|
||||
@@ -88,6 +89,95 @@ def testBaseCommon_CheckHandle():
|
||||
|
||||
# END Test testBaseCommon_CheckHandle
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_IsHandle():
|
||||
"""Test the isHandle function.
|
||||
"""
|
||||
assert isHandle("47666c91c7ccf")
|
||||
|
||||
assert not isHandle("47666C91C7CCF")
|
||||
assert not isHandle("h7666c91c7ccf")
|
||||
assert not isHandle("None")
|
||||
assert not isHandle(None)
|
||||
assert not isHandle("STUFF")
|
||||
|
||||
# END Test testBaseCommon_IsHandle
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_IsTitleTag():
|
||||
"""Test the isItemClass function.
|
||||
"""
|
||||
assert isTitleTag("T123456")
|
||||
|
||||
assert not isTitleTag("t123456")
|
||||
assert not isTitleTag("S123456")
|
||||
assert not isTitleTag("T12345A")
|
||||
assert not isTitleTag("T1234567")
|
||||
|
||||
assert not isTitleTag("None")
|
||||
assert not isTitleTag(None)
|
||||
assert not isTitleTag("STUFF")
|
||||
|
||||
# END Test testBaseCommon_IsTitleTag
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_IsItemClass():
|
||||
"""Test the isItemClass function.
|
||||
"""
|
||||
assert isItemClass("NO_CLASS")
|
||||
assert isItemClass("NOVEL")
|
||||
assert isItemClass("PLOT")
|
||||
assert isItemClass("CHARACTER")
|
||||
assert isItemClass("WORLD")
|
||||
assert isItemClass("TIMELINE")
|
||||
assert isItemClass("OBJECT")
|
||||
assert isItemClass("ENTITY")
|
||||
assert isItemClass("CUSTOM")
|
||||
assert isItemClass("ARCHIVE")
|
||||
assert isItemClass("TRASH")
|
||||
|
||||
assert not isItemClass("None")
|
||||
assert not isItemClass(None)
|
||||
assert not isItemClass("STUFF")
|
||||
|
||||
# END Test testBaseCommon_IsItemClass
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_IsItemType():
|
||||
"""Test the isItemType function.
|
||||
"""
|
||||
assert isItemType("NO_TYPE")
|
||||
assert isItemType("ROOT")
|
||||
assert isItemType("FOLDER")
|
||||
assert isItemType("FILE")
|
||||
assert isItemType("TRASH")
|
||||
|
||||
assert not isItemType("None")
|
||||
assert not isItemType(None)
|
||||
assert not isItemType("STUFF")
|
||||
|
||||
# END Test testBaseCommon_IsItemType
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_IsItemLayout():
|
||||
"""Test the isItemLayout function.
|
||||
"""
|
||||
assert isItemLayout("NO_LAYOUT")
|
||||
assert isItemLayout("TITLE")
|
||||
assert isItemLayout("BOOK")
|
||||
assert isItemLayout("PAGE")
|
||||
assert isItemLayout("PARTITION")
|
||||
assert isItemLayout("UNNUMBERED")
|
||||
assert isItemLayout("CHAPTER")
|
||||
assert isItemLayout("SCENE")
|
||||
assert isItemLayout("NOTE")
|
||||
|
||||
assert not isItemLayout("None")
|
||||
assert not isItemLayout(None)
|
||||
assert not isItemLayout("STUFF")
|
||||
|
||||
# END Test testBaseCommon_IsItemLayout
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_HexToInt():
|
||||
"""Test the hexToInt function.
|
||||
|
||||
@@ -115,38 +115,7 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, outDir, refDir):
|
||||
|
||||
# Break the index and check that we notice
|
||||
assert not theIndex.indexBroken
|
||||
theIndex._tagIndex["Bod"].append("Stuff") # No longer len() == 4
|
||||
theIndex.checkIndex()
|
||||
assert theIndex.indexBroken
|
||||
|
||||
assert theIndex.loadIndex()
|
||||
assert not theIndex.indexBroken
|
||||
theIndex._refIndex["fb609cd8319dc"]["T000001"]["tags"].append("Stuff") # No longer len() == 3
|
||||
theIndex.checkIndex()
|
||||
assert theIndex.indexBroken
|
||||
|
||||
assert theIndex.loadIndex()
|
||||
assert not theIndex.indexBroken
|
||||
theIndex._novelIndex["7a992350f3eb6"]["T000001"]["Stuff"] = "" # No longer len(keys()) == 8
|
||||
theIndex.checkIndex()
|
||||
assert theIndex.indexBroken
|
||||
|
||||
assert theIndex.loadIndex()
|
||||
assert not theIndex.indexBroken
|
||||
theIndex._noteIndex["4c4f28287af27"]["T000001"]["Stuff"] = "" # No longer len(keys()) == 8
|
||||
theIndex.checkIndex()
|
||||
assert theIndex.indexBroken
|
||||
|
||||
assert theIndex.loadIndex()
|
||||
assert not theIndex.indexBroken
|
||||
theIndex._textCounts["7a992350f3eb6"].append("Stuff") # No longer len() == 3
|
||||
theIndex.checkIndex()
|
||||
assert theIndex.indexBroken
|
||||
|
||||
# Make the try/except trigger as well
|
||||
assert theIndex.loadIndex()
|
||||
assert not theIndex.indexBroken
|
||||
theIndex._refIndex["fb609cd8319dc"]["T000001"] = {"tagssss": []} # Wrong key name
|
||||
theIndex._tagIndex["Bod"].append("Stuff")
|
||||
theIndex.checkIndex()
|
||||
assert theIndex.indexBroken
|
||||
|
||||
@@ -229,14 +198,28 @@ def testCoreIndex_CheckThese(nwMinimal, dummyGUI):
|
||||
|
||||
assert theIndex.scanText(cHandle, (
|
||||
"# Jane Smith\n"
|
||||
"@tag: Jane"
|
||||
"@tag: Jane\n"
|
||||
"@tag:\n"
|
||||
"@:\n"
|
||||
))
|
||||
assert theIndex.scanText(nHandle, (
|
||||
"# Hello World!\n"
|
||||
"@pov: Jane"
|
||||
"@pov: Jane\n"
|
||||
"@invalid: John\n" # Checks for issue #688
|
||||
))
|
||||
assert theIndex._tagIndex == {"Jane": [2, cHandle, "CHARACTER", "T000001"]}
|
||||
assert theIndex.getNovelData(nHandle, "T000001")["title"] == "Hello World!"
|
||||
assert theIndex.getReferences(nHandle, "T000001") == {
|
||||
"@char": [],
|
||||
"@custom": [],
|
||||
"@entity": [],
|
||||
"@focus": [],
|
||||
"@location": [],
|
||||
"@object": [],
|
||||
"@plot": [],
|
||||
"@pov": ["Jane"],
|
||||
"@time": []
|
||||
}
|
||||
|
||||
assert theIndex.novelChangedSince(0)
|
||||
assert theIndex.notesChangedSince(0)
|
||||
@@ -312,7 +295,7 @@ def testCoreIndex_ScanText(nwMinimal, dummyGUI):
|
||||
"This is a story about Jane Smith.\n\n"
|
||||
"Well, not really.\n"
|
||||
))
|
||||
assert str(theIndex._tagIndex) == "{'Jane': [2, '%s', 'CHARACTER', 'T000001']}" % cHandle
|
||||
assert theIndex._tagIndex == {"Jane": [2, cHandle, "CHARACTER", "T000001"]}
|
||||
assert theIndex.getNovelData(nHandle, "T000001")["title"] == "Hello World!"
|
||||
|
||||
# Check that title sections are indexed properly
|
||||
@@ -523,9 +506,8 @@ def testCoreIndex_ExtractData(nwMinimal, dummyGUI):
|
||||
assert wC == 12 # Words in text and title only
|
||||
assert pC == 2 # Paragraphs in text only
|
||||
|
||||
##
|
||||
# getReferences
|
||||
##
|
||||
# getReferences
|
||||
# =============
|
||||
|
||||
# Look up an ivalid handle
|
||||
theRefs = theIndex.getReferences("Not a handle")
|
||||
@@ -537,9 +519,8 @@ def testCoreIndex_ExtractData(nwMinimal, dummyGUI):
|
||||
assert theRefs["@pov"] == ["Jane"]
|
||||
assert theRefs["@char"] == ["Jane"]
|
||||
|
||||
##
|
||||
# getBackReferenceList
|
||||
##
|
||||
# getBackReferenceList
|
||||
# ====================
|
||||
|
||||
# None handle should return an empty dict
|
||||
assert theIndex.getBackReferenceList(None) == {}
|
||||
@@ -548,16 +529,15 @@ def testCoreIndex_ExtractData(nwMinimal, dummyGUI):
|
||||
theRefs = theIndex.getBackReferenceList(cHandle)
|
||||
assert theRefs == {nHandle: "T000001"}
|
||||
|
||||
##
|
||||
# getTagSource
|
||||
##
|
||||
# getTagSource
|
||||
# ============
|
||||
|
||||
assert theIndex.getTagSource("Jane") == (cHandle, 2, "T000001")
|
||||
assert theIndex.getTagSource("John") == (None, 0, "T000000")
|
||||
|
||||
##
|
||||
# getCounts for whole text and sections
|
||||
##
|
||||
# getCounts
|
||||
# =========
|
||||
# For whole text and sections
|
||||
|
||||
# Get section counts for a novel file
|
||||
assert theIndex.scanText(nHandle, (
|
||||
@@ -586,7 +566,7 @@ def testCoreIndex_ExtractData(nwMinimal, dummyGUI):
|
||||
assert wC == 12
|
||||
assert pC == 2
|
||||
|
||||
# First part
|
||||
# Second part
|
||||
cC, wC, pC = theIndex.getCounts(nHandle, "T000011")
|
||||
assert cC == 90
|
||||
assert wC == 16
|
||||
@@ -619,15 +599,14 @@ def testCoreIndex_ExtractData(nwMinimal, dummyGUI):
|
||||
assert wC == 12
|
||||
assert pC == 2
|
||||
|
||||
# First part
|
||||
# Second part
|
||||
cC, wC, pC = theIndex.getCounts(cHandle, "T000011")
|
||||
assert cC == 90
|
||||
assert wC == 16
|
||||
assert pC == 2
|
||||
|
||||
##
|
||||
# Novel Stats
|
||||
##
|
||||
# Novel Stats
|
||||
# ===========
|
||||
|
||||
hHandle = theProject.newFile("Chapter", nwItemClass.NOVEL, "a508bb932959c")
|
||||
sHandle = theProject.newFile("Scene One", nwItemClass.NOVEL, "a508bb932959c")
|
||||
@@ -696,3 +675,503 @@ def testCoreIndex_ExtractData(nwMinimal, dummyGUI):
|
||||
]
|
||||
|
||||
# END Test testCoreIndex_ExtractData
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreIndex_CheckTagIndex(dummyGUI):
|
||||
"""Test the tag index checker.
|
||||
"""
|
||||
theProject = NWProject(dummyGUI)
|
||||
theIndex = NWIndex(theProject, dummyGUI)
|
||||
|
||||
# Valid Index
|
||||
theIndex._tagIndex = {
|
||||
"John": [3, "14298de4d9524", "CHARACTER", "T000001"],
|
||||
"Jane": [3, "bb2c23b3c42cc", "CHARACTER", "T000001"],
|
||||
}
|
||||
assert theIndex._checkTagIndex() is None
|
||||
|
||||
# Wrong Key Type
|
||||
theIndex._tagIndex = {
|
||||
"John": [3, "14298de4d9524", "CHARACTER", "T000001"],
|
||||
123456: [3, "bb2c23b3c42cc", "CHARACTER", "T000001"],
|
||||
}
|
||||
with pytest.raises(KeyError):
|
||||
theIndex._checkTagIndex()
|
||||
|
||||
# Wrong Length
|
||||
theIndex._tagIndex = {
|
||||
"John": [3, "14298de4d9524", "CHARACTER", "T000001"],
|
||||
"Jane": [3, "bb2c23b3c42cc", "CHARACTER", "T000001", "Stuff"],
|
||||
}
|
||||
with pytest.raises(IndexError):
|
||||
theIndex._checkTagIndex()
|
||||
|
||||
# Wrong Type of Entry 0
|
||||
theIndex._tagIndex = {
|
||||
"John": [3, "14298de4d9524", "CHARACTER", "T000001"],
|
||||
"Jane": ["3", "bb2c23b3c42cc", "CHARACTER", "T000001"],
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkTagIndex()
|
||||
|
||||
# Wrong Type of Entry 1
|
||||
theIndex._tagIndex = {
|
||||
"John": [3, "14298de4d9524", "CHARACTER", "T000001"],
|
||||
"Jane": [3, 0xbb2c23b3c42cc, "CHARACTER", "T000001"],
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkTagIndex()
|
||||
|
||||
# Wrong Type of Entry 2
|
||||
theIndex._tagIndex = {
|
||||
"John": [3, "14298de4d9524", "CHARACTER", "T000001"],
|
||||
"Jane": [3, "bb2c23b3c42cc", "INVALID_CLASS", "T000001"],
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkTagIndex()
|
||||
|
||||
# Wrong Type of Entry 3
|
||||
theIndex._tagIndex = {
|
||||
"John": [3, "14298de4d9524", "CHARACTER", "T000001"],
|
||||
"Jane": [3, "bb2c23b3c42cc", "CHARACTER", "INVALID"],
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkTagIndex()
|
||||
|
||||
# END Test testCoreIndex_CheckTagIndex
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreIndex_CheckRefIndex(dummyGUI):
|
||||
"""Test the reference index checker.
|
||||
"""
|
||||
theProject = NWProject(dummyGUI)
|
||||
theIndex = NWIndex(theProject, dummyGUI)
|
||||
|
||||
# Valid Index
|
||||
theIndex._refIndex = {
|
||||
"6a2d6d5f4f401": {
|
||||
"T000000": {"tags": [], "updated": 1611922868},
|
||||
"T000001": {"tags": [
|
||||
[3, "@pov", "Jane"], [4, "@location", "Earth"]
|
||||
], "updated": 1611922868}
|
||||
}
|
||||
}
|
||||
assert theIndex._checkRefIndex() is None
|
||||
|
||||
# Invalid Handle
|
||||
theIndex._refIndex = {
|
||||
"Ha2d6d5f4f401": {
|
||||
"T000000": {"tags": [], "updated": 1611922868},
|
||||
"T000001": {"tags": [
|
||||
[3, "@pov", "Jane"], [4, "@location", "Earth"]
|
||||
], "updated": 1611922868}
|
||||
}
|
||||
}
|
||||
with pytest.raises(KeyError):
|
||||
theIndex._checkRefIndex()
|
||||
|
||||
# Invalid Title
|
||||
theIndex._refIndex = {
|
||||
"6a2d6d5f4f401": {
|
||||
"T000000": {"tags": [], "updated": 1611922868},
|
||||
"INVALID": {"tags": [
|
||||
[3, "@pov", "Jane"], [4, "@location", "Earth"]
|
||||
], "updated": 1611922868}
|
||||
}
|
||||
}
|
||||
with pytest.raises(KeyError):
|
||||
theIndex._checkRefIndex()
|
||||
|
||||
# Missing 'tags'
|
||||
theIndex._refIndex = {
|
||||
"6a2d6d5f4f401": {
|
||||
"T000000": {"tags": [], "updated": 1611922868},
|
||||
"T000001": {"updated": 1611922868}
|
||||
}
|
||||
}
|
||||
with pytest.raises(KeyError):
|
||||
theIndex._checkRefIndex()
|
||||
|
||||
# Wrong Length of 'tags'
|
||||
theIndex._refIndex = {
|
||||
"6a2d6d5f4f401": {
|
||||
"T000000": {"tags": [], "updated": 1611922868},
|
||||
"T000001": {"tags": [
|
||||
[3, "@pov", "Jane"], [4, "@location", "Earth", "Stuff"]
|
||||
], "updated": 1611922868}
|
||||
}
|
||||
}
|
||||
with pytest.raises(IndexError):
|
||||
theIndex._checkRefIndex()
|
||||
|
||||
# Wrong Type of 'tags' Entry 0
|
||||
theIndex._refIndex = {
|
||||
"6a2d6d5f4f401": {
|
||||
"T000000": {"tags": [], "updated": 1611922868},
|
||||
"T000001": {"tags": [
|
||||
[3, "@pov", "Jane"], ["4", "@location", "Earth"]
|
||||
], "updated": 1611922868}
|
||||
}
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkRefIndex()
|
||||
|
||||
# Wrong Type of 'tags' Entry 1
|
||||
theIndex._refIndex = {
|
||||
"6a2d6d5f4f401": {
|
||||
"T000000": {"tags": [], "updated": 1611922868},
|
||||
"T000001": {"tags": [
|
||||
[3, "@pov", "Jane"], [4, "@stuff", "Earth"]
|
||||
], "updated": 1611922868}
|
||||
}
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkRefIndex()
|
||||
|
||||
# Wrong Type of 'tags' Entry 1
|
||||
theIndex._refIndex = {
|
||||
"6a2d6d5f4f401": {
|
||||
"T000000": {"tags": [], "updated": 1611922868},
|
||||
"T000001": {"tags": [
|
||||
[3, "@pov", "Jane"], [4, "@location", 123456]
|
||||
], "updated": 1611922868}
|
||||
}
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkRefIndex()
|
||||
|
||||
# Missing 'updated'
|
||||
theIndex._refIndex = {
|
||||
"6a2d6d5f4f401": {
|
||||
"T000000": {"tags": [], "updated": 1611922868},
|
||||
"T000001": {"tags": []}
|
||||
}
|
||||
}
|
||||
with pytest.raises(KeyError):
|
||||
theIndex._checkRefIndex()
|
||||
|
||||
# Wrong Type of 'updated' Entry 1
|
||||
theIndex._refIndex = {
|
||||
"6a2d6d5f4f401": {
|
||||
"T000000": {"tags": [], "updated": 1611922868},
|
||||
"T000001": {"tags": [], "updated": "1611922868"}
|
||||
}
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkRefIndex()
|
||||
|
||||
# END Test testCoreIndex_CheckRefIndex
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreIndex_CheckNovelNoteIndex(dummyGUI):
|
||||
"""Test the novel and note index checkers.
|
||||
"""
|
||||
theProject = NWProject(dummyGUI)
|
||||
theIndex = NWIndex(theProject, dummyGUI)
|
||||
|
||||
# Valid Index
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
|
||||
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
theIndex._noteIndex = theIndex._novelIndex.copy()
|
||||
assert theIndex._checkNovelNoteIndex("novelIndex") is None
|
||||
assert theIndex._checkNovelNoteIndex("noteIndex") is None
|
||||
with pytest.raises(IndexError):
|
||||
theIndex._checkNovelNoteIndex("notAnIndex")
|
||||
|
||||
# Invalid Handle
|
||||
theIndex._novelIndex = {
|
||||
"H3b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
|
||||
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(KeyError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Invalid Title
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"INVALID": {
|
||||
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
|
||||
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(KeyError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Wrong Length
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
|
||||
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868, "stuff": None
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(IndexError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Missing Keys
|
||||
# ============
|
||||
|
||||
# Missing 'level'
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"stuff": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
|
||||
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(KeyError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Missing 'title'
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "stuff": "My Novel", "layout": "TITLE", "synopsis": "text",
|
||||
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(KeyError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Missing 'layout'
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "title": "My Novel", "stuff": "TITLE", "synopsis": "text",
|
||||
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(KeyError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Missing 'synopsis'
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "title": "My Novel", "layout": "TITLE", "stuff": "text",
|
||||
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(KeyError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Missing 'cCount'
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
|
||||
"stuff": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(KeyError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Missing 'wCount'
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
|
||||
"cCount": 72, "stuff": 15, "pCount": 2, "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(KeyError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Missing 'pCount'
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
|
||||
"cCount": 72, "wCount": 15, "stuff": 2, "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(KeyError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Missing 'updated'
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
|
||||
"cCount": 72, "wCount": 15, "pCount": 2, "stuff": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(KeyError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Wrong Types
|
||||
# ===========
|
||||
|
||||
# Wrong Type for 'level'
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "XX", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
|
||||
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Wrong Type for 'title'
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "title": 12345678, "layout": "TITLE", "synopsis": "text",
|
||||
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Wrong Type for 'layout'
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "title": "My Novel", "layout": "INVALID", "synopsis": "text",
|
||||
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Wrong Type for 'synopsis'
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": 123456,
|
||||
"cCount": 72, "wCount": 15, "pCount": 2, "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Wrong Type for 'cCount'
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
|
||||
"cCount": "72", "wCount": 15, "pCount": 2, "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Wrong Type for 'wCount'
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
|
||||
"cCount": 72, "wCount": "15", "pCount": 2, "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Wrong Type for 'pCount'
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
|
||||
"cCount": 72, "wCount": 15, "pCount": "2", "updated": 1611922868
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# Wrong Type for 'updated'
|
||||
theIndex._novelIndex = {
|
||||
"53b69b83cdafc": {
|
||||
"T000001": {
|
||||
"level": "H1", "title": "My Novel", "layout": "TITLE", "synopsis": "text",
|
||||
"cCount": 72, "wCount": 15, "pCount": 2, "updated": "1611922868"
|
||||
}
|
||||
}
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkNovelNoteIndex("novelIndex")
|
||||
|
||||
# END Test testCoreIndex_CheckNovelNoteIndex
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreIndex_CheckTextCounts(dummyGUI):
|
||||
"""Test the text counts checker.
|
||||
"""
|
||||
theProject = NWProject(dummyGUI)
|
||||
theIndex = NWIndex(theProject, dummyGUI)
|
||||
|
||||
# Valid Index
|
||||
theIndex._textCounts = {
|
||||
"53b69b83cdafc": [72, 15, 2],
|
||||
"974e400180a99": [210, 40, 2],
|
||||
}
|
||||
assert theIndex._checkTextCounts() is None
|
||||
|
||||
# Invalid Handle
|
||||
theIndex._textCounts = {
|
||||
"53b69b83cdafc": [72, 15, 2],
|
||||
"h74e400180a99": [210, 40, 2],
|
||||
}
|
||||
with pytest.raises(KeyError):
|
||||
theIndex._checkTextCounts()
|
||||
|
||||
# Wrong Length
|
||||
theIndex._textCounts = {
|
||||
"53b69b83cdafc": [72, 15, 2],
|
||||
"974e400180a99": [210, 40, 2, 8],
|
||||
}
|
||||
with pytest.raises(IndexError):
|
||||
theIndex._checkTextCounts()
|
||||
|
||||
# Type of Entry 0
|
||||
theIndex._textCounts = {
|
||||
"53b69b83cdafc": [72, 15, 2],
|
||||
"974e400180a99": ["210", 40, 2],
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkTextCounts()
|
||||
|
||||
# Type of Entry 1
|
||||
theIndex._textCounts = {
|
||||
"53b69b83cdafc": [72, 15, 2],
|
||||
"974e400180a99": [210, "40", 2],
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkTextCounts()
|
||||
|
||||
# Type of Entry 2
|
||||
theIndex._textCounts = {
|
||||
"53b69b83cdafc": [72, 15, 2],
|
||||
"974e400180a99": [210, 40, "2"],
|
||||
}
|
||||
with pytest.raises(ValueError):
|
||||
theIndex._checkTextCounts()
|
||||
|
||||
# END Test testCoreIndex_CheckTextCounts
|
||||
|
||||
@@ -859,12 +859,12 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
|
||||
|
||||
# Session stats
|
||||
monkeypatch.setattr("os.path.isdir", lambda *args, **kwargs: False)
|
||||
assert not theProject._appendSessionStats()
|
||||
assert not theProject._appendSessionStats(idleTime=0)
|
||||
monkeypatch.undo()
|
||||
|
||||
# Block open
|
||||
monkeypatch.setattr("builtins.open", causeOSError)
|
||||
assert not theProject._appendSessionStats()
|
||||
assert not theProject._appendSessionStats(idleTime=0)
|
||||
monkeypatch.undo()
|
||||
|
||||
# Write entry
|
||||
@@ -876,13 +876,13 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
|
||||
theProject.notesWCount = 100
|
||||
|
||||
monkeypatch.setattr("nw.core.project.time", lambda: 1600005600)
|
||||
assert theProject._appendSessionStats()
|
||||
assert theProject._appendSessionStats(idleTime=99)
|
||||
monkeypatch.undo()
|
||||
|
||||
assert readFile(statsFile) == (
|
||||
"# Offset 100\n"
|
||||
"# Start Time End Time Novel Notes\n"
|
||||
"%s %s 200 100\n"
|
||||
"# Start Time End Time Novel Notes Idle\n"
|
||||
"%s %s 200 100 99\n"
|
||||
) % (formatTimeStamp(1600002000), formatTimeStamp(1600005600))
|
||||
|
||||
# Pack XML Value
|
||||
|
||||
@@ -20,8 +20,11 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from tools import readFile
|
||||
|
||||
from nw.core import NWProject, NWIndex, ToHtml
|
||||
|
||||
@pytest.mark.core
|
||||
@@ -44,14 +47,12 @@ def testCoreToHtml_Format(dummyGUI):
|
||||
|
||||
assert theHtml._formatKeywords("") == ""
|
||||
assert theHtml._formatKeywords("tag: Jane") == (
|
||||
"<div><span class='tags'>Tag:</span> <a name='tag_Jane'>Jane</a></div>\n"
|
||||
"<span class='tags'>Tag:</span> <a name='tag_Jane'>Jane</a>"
|
||||
)
|
||||
assert theHtml._formatKeywords("char: Bod, Jane") == (
|
||||
"<div>"
|
||||
"<span class='tags'>Characters:</span> "
|
||||
"<a href='#tag_Bod'>Bod</a>, "
|
||||
"<a href='#tag_Jane'>Jane</a>"
|
||||
"</div>\n"
|
||||
)
|
||||
|
||||
# Preview Mode
|
||||
@@ -68,14 +69,12 @@ def testCoreToHtml_Format(dummyGUI):
|
||||
|
||||
assert theHtml._formatKeywords("") == ""
|
||||
assert theHtml._formatKeywords("tag: Jane") == (
|
||||
"<div><span class='tags'>Tag:</span> <a name='tag_Jane'>Jane</a></div>\n"
|
||||
"<span class='tags'>Tag:</span> <a name='tag_Jane'>Jane</a>"
|
||||
)
|
||||
assert theHtml._formatKeywords("char: Bod, Jane") == (
|
||||
"<div>"
|
||||
"<span class='tags'>Characters:</span> "
|
||||
"<a href='#char=Bod'>Bod</a>, "
|
||||
"<a href='#char=Jane'>Jane</a>"
|
||||
"</div>\n"
|
||||
)
|
||||
|
||||
# END Test testCoreToHtml_Format
|
||||
@@ -200,8 +199,27 @@ def testCoreToHtml_Convert(dummyGUI):
|
||||
theHtml.tokenizeText()
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == (
|
||||
"<div><span class='tags'>Characters:</span> "
|
||||
"<a href='#tag_Bod'>Bod</a>, <a href='#tag_Jane'>Jane</a></div>\n"
|
||||
"<p><span class='tags'>Characters:</span> "
|
||||
"<a href='#tag_Bod'>Bod</a>, <a href='#tag_Jane'>Jane</a></p>\n"
|
||||
)
|
||||
|
||||
# Multiple Keywords
|
||||
theHtml.setKeywords(True)
|
||||
theHtml.theText = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n"
|
||||
theHtml.tokenizeText()
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == (
|
||||
"<h2>"
|
||||
"<a name='T000001'></a>Chapter</h2>\n"
|
||||
"<p style='margin-bottom: 0;'>"
|
||||
"<span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a>"
|
||||
"</p>\n"
|
||||
"<p style='margin-bottom: 0; margin-top: 0;'>"
|
||||
"<span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a>"
|
||||
"</p>\n"
|
||||
"<p style='margin-top: 0;'>"
|
||||
"<span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a>"
|
||||
"</p>\n"
|
||||
)
|
||||
|
||||
# Direct Tests
|
||||
@@ -211,12 +229,12 @@ def testCoreToHtml_Convert(dummyGUI):
|
||||
|
||||
# Title
|
||||
theHtml.theTokens = [
|
||||
(theHtml.T_TITLE, 1, "A Title", None, theHtml.A_PBB_NO | theHtml.A_CENTRE),
|
||||
(theHtml.T_TITLE, 1, "A Title", None, theHtml.A_PBB_AUT | theHtml.A_CENTRE),
|
||||
(theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE),
|
||||
]
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == (
|
||||
"<h1 class='title' style='text-align: center; page-break-before: never;'>"
|
||||
"<h1 class='title' style='text-align: center; page-break-before: auto;'>"
|
||||
"<a name='T000001'></a>A Title</h1>\n"
|
||||
)
|
||||
|
||||
@@ -299,24 +317,14 @@ def testCoreToHtml_Convert(dummyGUI):
|
||||
"style='page-break-before: always; page-break-after: always;'>A Title</h1>\n"
|
||||
)
|
||||
|
||||
# Page Break Avoid
|
||||
# Page Break Auto
|
||||
theHtml.theTokens = [
|
||||
(theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_PBB_AV | theHtml.A_PBA_AV),
|
||||
(theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_PBB_AUT | theHtml.A_PBA_AUT),
|
||||
]
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == (
|
||||
"<h1 class='title' "
|
||||
"style='page-break-before: avoid; page-break-after: avoid;'>A Title</h1>\n"
|
||||
)
|
||||
|
||||
# Page Break ANever
|
||||
theHtml.theTokens = [
|
||||
(theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_PBB_NO | theHtml.A_PBA_NO),
|
||||
]
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == (
|
||||
"<h1 class='title' "
|
||||
"style='page-break-before: never; page-break-after: never;'>A Title</h1>\n"
|
||||
"style='page-break-before: auto; page-break-after: auto;'>A Title</h1>\n"
|
||||
)
|
||||
|
||||
# Preview Mode
|
||||
@@ -336,54 +344,135 @@ def testCoreToHtml_Convert(dummyGUI):
|
||||
|
||||
# END Test testCoreToHtml_Convert
|
||||
|
||||
def testCoreToHtml_Complex(dummyGUI, fncDir):
|
||||
"""Test the ave method of the ToHtml class.
|
||||
"""
|
||||
theProject = NWProject(dummyGUI)
|
||||
theHtml = ToHtml(theProject, dummyGUI)
|
||||
|
||||
# Build Project
|
||||
# =============
|
||||
|
||||
docText = [
|
||||
"# My Novel\n**By Jane Doh**\n",
|
||||
"## Chapter 1\n\nThe text of chapter one.\n",
|
||||
"### Scene 1\n\nThe text of scene one.\n",
|
||||
"#### A Section\n\nMore text in scene one.\n",
|
||||
"## Chapter 2\n\nThe text of chapter two.\n",
|
||||
"### Scene 2\n\nThe text of scene two.\n",
|
||||
"#### A Section\n\n\tMore text in scene two.\n",
|
||||
]
|
||||
resText = [
|
||||
"<h1>My Novel</h1>\n<p><strong>By Jane Doh</strong></p>\n",
|
||||
"<h2>Chapter 1</h2>\n<p>The text of chapter one.</p>\n",
|
||||
"<h3>Scene 1</h3>\n<p>The text of scene one.</p>\n",
|
||||
"<h4>A Section</h4>\n<p>More text in scene one.</p>\n",
|
||||
"<h2>Chapter 2</h2>\n<p>The text of chapter two.</p>\n",
|
||||
"<h3>Scene 2</h3>\n<p>The text of scene two.</p>\n",
|
||||
"<h4>A Section</h4>\n<p>\tMore text in scene two.</p>\n",
|
||||
]
|
||||
|
||||
for i in range(len(docText)):
|
||||
theHtml.theText = docText[i]
|
||||
theHtml.doPreProcessing()
|
||||
theHtml.tokenizeText()
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == resText[i]
|
||||
|
||||
assert theHtml.fullHTML == resText
|
||||
|
||||
theHtml.replaceTabs(nSpaces=2, spaceChar=" ")
|
||||
resText[6] = "<h4>A Section</h4>\n<p> More text in scene two.</p>\n"
|
||||
|
||||
# Check File
|
||||
# ==========
|
||||
|
||||
theStyle = theHtml.getStyleSheet()
|
||||
theStyle.append("article {width: 800px; margin: 40px auto;}")
|
||||
htmlDoc = (
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html>\n"
|
||||
"<head>\n"
|
||||
"<meta charset='utf-8'>\n"
|
||||
"<title></title>\n"
|
||||
"</head>\n"
|
||||
"<style>\n"
|
||||
"{htmlStyle:s}\n"
|
||||
"</style>\n"
|
||||
"<body>\n"
|
||||
"<article>\n"
|
||||
"{bodyText:s}\n"
|
||||
"</article>\n"
|
||||
"</body>\n"
|
||||
"</html>\n"
|
||||
).format(
|
||||
htmlStyle = "\n".join(theStyle),
|
||||
bodyText = "".join(resText).rstrip()
|
||||
)
|
||||
|
||||
saveFile = os.path.join(fncDir, "outFile.htm")
|
||||
theHtml.saveHTML5(saveFile)
|
||||
assert readFile(saveFile) == htmlDoc
|
||||
|
||||
# END Test testCoreToHtml_Save
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreToHtml_Methods(dummyGUI):
|
||||
"""Test all the other methods of the ToHtml class.
|
||||
"""
|
||||
theProject = NWProject(dummyGUI)
|
||||
theHtml = ToHtml(theProject, dummyGUI)
|
||||
theHtml.setKeepMarkdown(True)
|
||||
|
||||
# Auto-Replace
|
||||
# Auto-Replace, keep Unicode
|
||||
docText = "Text with <brackets> & short–dash, long—dash …\n"
|
||||
theHtml.theText = docText
|
||||
theHtml.doAutoReplace()
|
||||
theHtml.setReplaceUnicode(False)
|
||||
theHtml.doPreProcessing()
|
||||
theHtml.tokenizeText()
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == (
|
||||
"<p>Text with <brackets> & short–dash, long—dash …</p>\n"
|
||||
)
|
||||
|
||||
# Auto-Replace, replace Unicode
|
||||
docText = "Text with <brackets> & short–dash, long—dash …\n"
|
||||
theHtml.theText = docText
|
||||
theHtml.setReplaceUnicode(True)
|
||||
theHtml.doPreProcessing()
|
||||
theHtml.tokenizeText()
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theResult == (
|
||||
"<p>Text with <brackets> & short–dash, long—dash …</p>\n"
|
||||
)
|
||||
|
||||
# Revert on MD
|
||||
assert theHtml.theMarkdown == (
|
||||
"Text with <brackets> & short–dash, long—dash …\n\n"
|
||||
)
|
||||
theHtml.doPostProcessing()
|
||||
assert theHtml.theMarkdown == docText + "\n"
|
||||
|
||||
# With Preview, No Revert
|
||||
# With Preview
|
||||
theHtml.setPreview(True, True)
|
||||
theHtml.theText = docText
|
||||
theHtml.doAutoReplace()
|
||||
theHtml.doPreProcessing()
|
||||
theHtml.tokenizeText()
|
||||
theHtml.doConvert()
|
||||
assert theHtml.theMarkdown == (
|
||||
assert theHtml.theMarkdown[-1] == (
|
||||
"Text with <brackets> & short–dash, long—dash …\n\n"
|
||||
)
|
||||
theHtml.doPostProcessing()
|
||||
assert theHtml.theMarkdown == (
|
||||
assert theHtml.theMarkdown[-1] == (
|
||||
"Text with <brackets> & short–dash, long—dash …\n\n"
|
||||
)
|
||||
|
||||
# Result Size
|
||||
assert theHtml.getFullResultSize() == 147
|
||||
|
||||
# CSS
|
||||
# ===
|
||||
|
||||
assert len(theHtml.getStyleSheet()) > 1
|
||||
assert "p {text-align: left;}" in theHtml.getStyleSheet()
|
||||
assert "p {text-align: justify;}" not in theHtml.getStyleSheet()
|
||||
assert "p {text-align: left;" in " ".join(theHtml.getStyleSheet())
|
||||
assert "p {text-align: justify;" not in " ".join(theHtml.getStyleSheet())
|
||||
|
||||
theHtml.setJustify(True)
|
||||
assert "p {text-align: left;}" not in theHtml.getStyleSheet()
|
||||
assert "p {text-align: justify;}" in theHtml.getStyleSheet()
|
||||
assert "p {text-align: left;" not in " ".join(theHtml.getStyleSheet())
|
||||
assert "p {text-align: justify;" in " ".join(theHtml.getStyleSheet())
|
||||
|
||||
theHtml.setStyles(False)
|
||||
assert theHtml.getStyleSheet() == []
|
||||
|
||||
@@ -20,8 +20,11 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from tools import readFile
|
||||
|
||||
from nw.core import NWProject, NWDoc
|
||||
from nw.core.tokenizer import Tokenizer
|
||||
|
||||
@@ -38,6 +41,18 @@ def testCoreToken_Setters(dummyGUI):
|
||||
assert theToken.fmtUnNum == "%title%"
|
||||
assert theToken.fmtScene == "%title%"
|
||||
assert theToken.fmtSection == "%title%"
|
||||
assert theToken.textFont == "Serif"
|
||||
assert theToken.textSize == 11
|
||||
assert theToken.textFixed is False
|
||||
assert theToken.lineHeight == 1.15
|
||||
assert theToken.doJustify is False
|
||||
assert theToken.marginTitle == (1.000, 0.500)
|
||||
assert theToken.marginHead1 == (1.000, 0.500)
|
||||
assert theToken.marginHead2 == (0.834, 0.500)
|
||||
assert theToken.marginHead3 == (0.584, 0.500)
|
||||
assert theToken.marginHead4 == (0.584, 0.500)
|
||||
assert theToken.marginText == (0.000, 0.584)
|
||||
assert theToken.marginMeta == (0.000, 0.584)
|
||||
assert theToken.hideScene is False
|
||||
assert theToken.hideSection is False
|
||||
assert theToken.linkHeaders is False
|
||||
@@ -45,7 +60,6 @@ def testCoreToken_Setters(dummyGUI):
|
||||
assert theToken.doSynopsis is False
|
||||
assert theToken.doComments is False
|
||||
assert theToken.doKeywords is False
|
||||
assert theToken.doJustify is False
|
||||
|
||||
# Set new values
|
||||
theToken.setTitleFormat("T: %title%")
|
||||
@@ -53,12 +67,21 @@ def testCoreToken_Setters(dummyGUI):
|
||||
theToken.setUnNumberedFormat("U: %title%")
|
||||
theToken.setSceneFormat("S: %title%", True)
|
||||
theToken.setSectionFormat("X: %title%", True)
|
||||
theToken.setFont("Monospace", 10, True)
|
||||
theToken.setLineHeight(2)
|
||||
theToken.setJustify(True)
|
||||
theToken.setTitleMargins(2.0, 2.0)
|
||||
theToken.setHead1Margins(2.0, 2.0)
|
||||
theToken.setHead2Margins(2.0, 2.0)
|
||||
theToken.setHead3Margins(2.0, 2.0)
|
||||
theToken.setHead4Margins(2.0, 2.0)
|
||||
theToken.setTextMargins(2.0, 2.0)
|
||||
theToken.setMetaMargins(2.0, 2.0)
|
||||
theToken.setLinkHeaders(True)
|
||||
theToken.setBodyText(False)
|
||||
theToken.setSynopsis(True)
|
||||
theToken.setComments(True)
|
||||
theToken.setKeywords(True)
|
||||
theToken.setJustify(True)
|
||||
|
||||
# Check new values
|
||||
assert theToken.fmtTitle == "T: %title%"
|
||||
@@ -66,6 +89,18 @@ def testCoreToken_Setters(dummyGUI):
|
||||
assert theToken.fmtUnNum == "U: %title%"
|
||||
assert theToken.fmtScene == "S: %title%"
|
||||
assert theToken.fmtSection == "X: %title%"
|
||||
assert theToken.textFont == "Monospace"
|
||||
assert theToken.textSize == 10
|
||||
assert theToken.textFixed is True
|
||||
assert theToken.lineHeight == 2.0
|
||||
assert theToken.doJustify is True
|
||||
assert theToken.marginTitle == (2.0, 2.0)
|
||||
assert theToken.marginHead1 == (2.0, 2.0)
|
||||
assert theToken.marginHead2 == (2.0, 2.0)
|
||||
assert theToken.marginHead3 == (2.0, 2.0)
|
||||
assert theToken.marginHead4 == (2.0, 2.0)
|
||||
assert theToken.marginText == (2.0, 2.0)
|
||||
assert theToken.marginMeta == (2.0, 2.0)
|
||||
assert theToken.hideScene is True
|
||||
assert theToken.hideSection is True
|
||||
assert theToken.linkHeaders is True
|
||||
@@ -73,7 +108,6 @@ def testCoreToken_Setters(dummyGUI):
|
||||
assert theToken.doSynopsis is True
|
||||
assert theToken.doComments is True
|
||||
assert theToken.doKeywords is True
|
||||
assert theToken.doJustify is True
|
||||
|
||||
# END Test testCoreToken_Setters
|
||||
|
||||
@@ -84,6 +118,7 @@ def testCoreToken_TextOps(monkeypatch, nwMinimal, dummyGUI):
|
||||
theProject = NWProject(dummyGUI)
|
||||
theProject.projTree.setSeed(42)
|
||||
theToken = Tokenizer(theProject, dummyGUI)
|
||||
theToken.setKeepMarkdown(True)
|
||||
|
||||
assert theProject.openProject(nwMinimal)
|
||||
sHandle = "8c659a11cd429"
|
||||
@@ -112,7 +147,7 @@ def testCoreToken_TextOps(monkeypatch, nwMinimal, dummyGUI):
|
||||
assert theToken.addRootHeading("dummy") is False
|
||||
assert theToken.addRootHeading(sHandle) is False
|
||||
assert theToken.addRootHeading("7695ce551d265") is True
|
||||
assert theToken.theMarkdown == "# Notes: Plot\n\n"
|
||||
assert theToken.theMarkdown[-1] == "# Notes: Plot\n\n"
|
||||
|
||||
# Set text
|
||||
assert theToken.setText("dummy") is False
|
||||
@@ -141,21 +176,20 @@ def testCoreToken_TextOps(monkeypatch, nwMinimal, dummyGUI):
|
||||
assert theToken.isNote is False
|
||||
assert theToken.isNovel is True
|
||||
|
||||
# Auto replace
|
||||
theToken.doAutoReplace()
|
||||
# Pre Processing
|
||||
theToken.doPreProcessing()
|
||||
assert theToken.theText == docTextR
|
||||
|
||||
# Access
|
||||
assert theToken.getResult() is None
|
||||
assert theToken.getResultSize() == 0
|
||||
theToken.theResult = ""
|
||||
assert theToken.getResultSize() == 0
|
||||
|
||||
# Post Processing
|
||||
theToken.theResult = r"This is text with escapes: \** \~~ \__"
|
||||
theToken.doPostProcessing()
|
||||
assert theToken.theResult == "This is text with escapes: ** ~~ __"
|
||||
|
||||
# Save File
|
||||
savePath = os.path.join(nwMinimal, "dump.nwd")
|
||||
theToken.saveRawMarkdown(savePath)
|
||||
assert readFile(savePath) == "# Notes: Plot\n\n"
|
||||
|
||||
# END Test testCoreToken_TextOps
|
||||
|
||||
@pytest.mark.core
|
||||
@@ -164,6 +198,7 @@ def testCoreToken_Tokenize(dummyGUI):
|
||||
"""
|
||||
theProject = NWProject(dummyGUI)
|
||||
theToken = Tokenizer(theProject, dummyGUI)
|
||||
theToken.setKeepMarkdown(True)
|
||||
|
||||
# Header 1
|
||||
theToken.theText = "# Novel Title\n"
|
||||
@@ -172,7 +207,7 @@ def testCoreToken_Tokenize(dummyGUI):
|
||||
(Tokenizer.T_HEAD1, 1, "Novel Title", None, Tokenizer.A_NONE),
|
||||
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
|
||||
]
|
||||
assert theToken.theMarkdown == "# Novel Title\n\n"
|
||||
assert theToken.theMarkdown[-1] == "# Novel Title\n\n"
|
||||
|
||||
# Header 2
|
||||
theToken.theText = "## Chapter One\n"
|
||||
@@ -181,7 +216,7 @@ def testCoreToken_Tokenize(dummyGUI):
|
||||
(Tokenizer.T_HEAD2, 1, "Chapter One", None, Tokenizer.A_NONE),
|
||||
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
|
||||
]
|
||||
assert theToken.theMarkdown == "## Chapter One\n\n"
|
||||
assert theToken.theMarkdown[-1] == "## Chapter One\n\n"
|
||||
|
||||
# Header 3
|
||||
theToken.theText = "### Scene One\n"
|
||||
@@ -190,7 +225,7 @@ def testCoreToken_Tokenize(dummyGUI):
|
||||
(Tokenizer.T_HEAD3, 1, "Scene One", None, Tokenizer.A_NONE),
|
||||
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
|
||||
]
|
||||
assert theToken.theMarkdown == "### Scene One\n\n"
|
||||
assert theToken.theMarkdown[-1] == "### Scene One\n\n"
|
||||
|
||||
# Header 4
|
||||
theToken.theText = "#### A Section\n"
|
||||
@@ -199,7 +234,7 @@ def testCoreToken_Tokenize(dummyGUI):
|
||||
(Tokenizer.T_HEAD4, 1, "A Section", None, Tokenizer.A_NONE),
|
||||
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
|
||||
]
|
||||
assert theToken.theMarkdown == "#### A Section\n\n"
|
||||
assert theToken.theMarkdown[-1] == "#### A Section\n\n"
|
||||
|
||||
# Comment
|
||||
theToken.theText = "% A comment\n"
|
||||
@@ -208,11 +243,11 @@ def testCoreToken_Tokenize(dummyGUI):
|
||||
(Tokenizer.T_COMMENT, 1, "A comment", None, Tokenizer.A_NONE),
|
||||
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
|
||||
]
|
||||
assert theToken.theMarkdown == "\n"
|
||||
assert theToken.theMarkdown[-1] == "\n"
|
||||
|
||||
theToken.setComments(True)
|
||||
theToken.tokenizeText()
|
||||
assert theToken.theMarkdown == "% A comment\n\n"
|
||||
assert theToken.theMarkdown[-1] == "% A comment\n\n"
|
||||
|
||||
# Symopsis
|
||||
theToken.theText = "%synopsis: The synopsis\n"
|
||||
@@ -227,11 +262,11 @@ def testCoreToken_Tokenize(dummyGUI):
|
||||
(Tokenizer.T_SYNOPSIS, 1, "The synopsis", None, Tokenizer.A_NONE),
|
||||
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
|
||||
]
|
||||
assert theToken.theMarkdown == "\n"
|
||||
assert theToken.theMarkdown[-1] == "\n"
|
||||
|
||||
theToken.setSynopsis(True)
|
||||
theToken.tokenizeText()
|
||||
assert theToken.theMarkdown == "% synopsis: The synopsis\n\n"
|
||||
assert theToken.theMarkdown[-1] == "% synopsis: The synopsis\n\n"
|
||||
|
||||
# Keyword
|
||||
theToken.theText = "@char: Bod\n"
|
||||
@@ -240,11 +275,24 @@ def testCoreToken_Tokenize(dummyGUI):
|
||||
(Tokenizer.T_KEYWORD, 1, "char: Bod", None, Tokenizer.A_NONE),
|
||||
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
|
||||
]
|
||||
assert theToken.theMarkdown == "\n"
|
||||
assert theToken.theMarkdown[-1] == "\n"
|
||||
|
||||
theToken.setKeywords(True)
|
||||
theToken.tokenizeText()
|
||||
assert theToken.theMarkdown == "@char: Bod\n\n"
|
||||
assert theToken.theMarkdown[-1] == "@char: Bod\n\n"
|
||||
|
||||
theToken.theText = "@pov: Bod\n@plot: Main\n@location: Europe\n"
|
||||
theToken.tokenizeText()
|
||||
styTop = Tokenizer.A_NONE | Tokenizer.A_Z_BTMMRG
|
||||
styMid = Tokenizer.A_NONE | Tokenizer.A_Z_BTMMRG | Tokenizer.A_Z_TOPMRG
|
||||
styBtm = Tokenizer.A_NONE | Tokenizer.A_Z_TOPMRG
|
||||
assert theToken.theTokens == [
|
||||
(Tokenizer.T_KEYWORD, 1, "pov: Bod", None, styTop),
|
||||
(Tokenizer.T_KEYWORD, 2, "plot: Main", None, styMid),
|
||||
(Tokenizer.T_KEYWORD, 3, "location: Europe", None, styBtm),
|
||||
(Tokenizer.T_EMPTY, 3, "", None, Tokenizer.A_NONE),
|
||||
]
|
||||
assert theToken.theMarkdown[-1] == "@pov: Bod\n@plot: Main\n@location: Europe\n\n"
|
||||
|
||||
# Text
|
||||
theToken.theText = "Some plain text\non two lines\n\n\n"
|
||||
@@ -256,7 +304,7 @@ def testCoreToken_Tokenize(dummyGUI):
|
||||
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE),
|
||||
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE),
|
||||
]
|
||||
assert theToken.theMarkdown == "Some plain text\non two lines\n\n\n\n"
|
||||
assert theToken.theMarkdown[-1] == "Some plain text\non two lines\n\n\n\n"
|
||||
|
||||
theToken.setBodyText(False)
|
||||
theToken.tokenizeText()
|
||||
@@ -265,7 +313,7 @@ def testCoreToken_Tokenize(dummyGUI):
|
||||
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE),
|
||||
(Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE),
|
||||
]
|
||||
assert theToken.theMarkdown == "\n\n\n"
|
||||
assert theToken.theMarkdown[-1] == "\n\n\n"
|
||||
theToken.setBodyText(True)
|
||||
|
||||
# Text Emphasis
|
||||
@@ -283,7 +331,7 @@ def testCoreToken_Tokenize(dummyGUI):
|
||||
),
|
||||
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
|
||||
]
|
||||
assert theToken.theMarkdown == "Some **bolded text** on this lines\n\n"
|
||||
assert theToken.theMarkdown[-1] == "Some **bolded text** on this lines\n\n"
|
||||
|
||||
theToken.theText = "Some _italic text_ on this lines\n"
|
||||
theToken.tokenizeText()
|
||||
@@ -299,7 +347,7 @@ def testCoreToken_Tokenize(dummyGUI):
|
||||
),
|
||||
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
|
||||
]
|
||||
assert theToken.theMarkdown == "Some _italic text_ on this lines\n\n"
|
||||
assert theToken.theMarkdown[-1] == "Some _italic text_ on this lines\n\n"
|
||||
|
||||
theToken.theText = "Some **_bold italic text_** on this lines\n"
|
||||
theToken.tokenizeText()
|
||||
@@ -317,7 +365,7 @@ def testCoreToken_Tokenize(dummyGUI):
|
||||
),
|
||||
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
|
||||
]
|
||||
assert theToken.theMarkdown == "Some **_bold italic text_** on this lines\n\n"
|
||||
assert theToken.theMarkdown[-1] == "Some **_bold italic text_** on this lines\n\n"
|
||||
|
||||
theToken.theText = "Some ~~strikethrough text~~ on this lines\n"
|
||||
theToken.tokenizeText()
|
||||
@@ -333,7 +381,7 @@ def testCoreToken_Tokenize(dummyGUI):
|
||||
),
|
||||
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
|
||||
]
|
||||
assert theToken.theMarkdown == "Some ~~strikethrough text~~ on this lines\n\n"
|
||||
assert theToken.theMarkdown[-1] == "Some ~~strikethrough text~~ on this lines\n\n"
|
||||
|
||||
theToken.theText = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
|
||||
theToken.tokenizeText()
|
||||
@@ -353,12 +401,7 @@ def testCoreToken_Tokenize(dummyGUI):
|
||||
),
|
||||
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE),
|
||||
]
|
||||
assert theToken.theMarkdown == (
|
||||
"Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n"
|
||||
)
|
||||
|
||||
# Check the markdown function as well
|
||||
assert theToken.getFilteredMarkdown() == (
|
||||
assert theToken.theMarkdown[-1] == (
|
||||
"Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n"
|
||||
)
|
||||
|
||||
@@ -623,7 +666,7 @@ def testCoreToken_Headers(dummyGUI):
|
||||
theToken.isPart = False
|
||||
theToken.doHeaders()
|
||||
assert theToken.theTokens == [
|
||||
(Tokenizer.T_TITLE, 1, "Novel Title", None, Tokenizer.A_PBB_NO | Tokenizer.A_CENTRE),
|
||||
(Tokenizer.T_TITLE, 1, "Novel Title", None, Tokenizer.A_PBB_AUT | Tokenizer.A_CENTRE),
|
||||
(Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_PBA | Tokenizer.A_CENTRE),
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
novelWriter – ToOdt Class Tester
|
||||
=================================
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2021, Veronica Berglyd Olsen
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import nw
|
||||
import pytest
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from nw.core import NWProject, NWIndex, ToOdt
|
||||
|
||||
XML_NS = [
|
||||
' xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"',
|
||||
' xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"',
|
||||
' xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"',
|
||||
' xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"',
|
||||
' xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"',
|
||||
' xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"',
|
||||
]
|
||||
|
||||
def xmlToText(xElem):
|
||||
"""Get the text content of an XML element.
|
||||
"""
|
||||
rTxt = etree.tostring(xElem, encoding="utf-8", xml_declaration=False).decode()
|
||||
for nSpace in XML_NS:
|
||||
rTxt = rTxt.replace(nSpace, "")
|
||||
return rTxt
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreToOdt_Convert(tmpConf, dummyGUI):
|
||||
"""Test the converter of the ToHtml class.
|
||||
"""
|
||||
nw.CONFIG = tmpConf
|
||||
|
||||
theProject = NWProject(dummyGUI)
|
||||
dummyGUI.theIndex = NWIndex(theProject, dummyGUI)
|
||||
theDoc = ToOdt(theProject, dummyGUI, isFlat=True)
|
||||
|
||||
# Export Mode
|
||||
# ===========
|
||||
|
||||
theDoc.isNovel = True
|
||||
|
||||
# Header 1
|
||||
theDoc.theText = "# Title\n"
|
||||
theDoc.tokenizeText()
|
||||
theDoc.initDocument()
|
||||
theDoc.doConvert()
|
||||
theDoc.closeDocument()
|
||||
assert xmlToText(theDoc._xText) == (
|
||||
'<office:text>'
|
||||
'<text:h text:style-name="Heading_1" text:outline-level="1">Title</text:h>'
|
||||
'</office:text>'
|
||||
)
|
||||
|
||||
# Header 1
|
||||
theDoc.theText = "## Chapter Title\n"
|
||||
theDoc.tokenizeText()
|
||||
theDoc.initDocument()
|
||||
theDoc.doConvert()
|
||||
theDoc.closeDocument()
|
||||
assert xmlToText(theDoc._xText) == (
|
||||
'<office:text>'
|
||||
'<text:h text:style-name="Heading_2" text:outline-level="2">Chapter Title</text:h>'
|
||||
'</office:text>'
|
||||
)
|
||||
|
||||
# Nested Text
|
||||
theDoc.theText = "Some ~~nested **bold** and _italics_ text~~ text.\nNo format\n"
|
||||
theDoc.tokenizeText()
|
||||
theDoc.initDocument()
|
||||
theDoc.doConvert()
|
||||
theDoc.closeDocument()
|
||||
assert xmlToText(theDoc._xText) == (
|
||||
'<office:text>'
|
||||
'<text:p text:style-name="Text_Body">Some '
|
||||
'<text:span text:style-name="T1">nested </text:span>'
|
||||
'<text:span text:style-name="T2">bold</text:span>'
|
||||
'<text:span text:style-name="T1"> and </text:span>'
|
||||
'<text:span text:style-name="T3">italics</text:span>'
|
||||
'<text:span text:style-name="T1"> text</text:span> text. No format</text:p>'
|
||||
'</office:text>'
|
||||
)
|
||||
|
||||
# Hard Break
|
||||
theDoc.theText = "Some text. \nNext line\n"
|
||||
theDoc.tokenizeText()
|
||||
theDoc.initDocument()
|
||||
theDoc.doConvert()
|
||||
theDoc.closeDocument()
|
||||
assert xmlToText(theDoc._xText) == (
|
||||
'<office:text>'
|
||||
'<text:p text:style-name="Text_Body">Some text.<text:line-break/>Next line</text:p>'
|
||||
'</office:text>'
|
||||
)
|
||||
|
||||
# Tab
|
||||
theDoc.theText = "\tItem 1\tItem 2\n"
|
||||
theDoc.tokenizeText()
|
||||
theDoc.initDocument()
|
||||
theDoc.doConvert()
|
||||
theDoc.closeDocument()
|
||||
assert xmlToText(theDoc._xText) == (
|
||||
'<office:text>'
|
||||
'<text:p text:style-name="Text_Body"><text:tab/>Item 1<text:tab/>Item 2</text:p>'
|
||||
'</office:text>'
|
||||
)
|
||||
|
||||
# END Test testCoreToOdt_Convert
|
||||
@@ -123,9 +123,9 @@ def testCoreTree_BuildTree(dummyGUI, dummyItems):
|
||||
assert not theTree.isTrashRoot("a000000000003")
|
||||
|
||||
aHandles = []
|
||||
for tHandle, pHande, nwItem in dummyItems:
|
||||
for tHandle, pHandle, nwItem in dummyItems:
|
||||
aHandles.append(tHandle)
|
||||
assert theTree.append(tHandle, pHande, nwItem)
|
||||
assert theTree.append(tHandle, pHandle, nwItem)
|
||||
|
||||
assert theTree._treeChanged
|
||||
|
||||
@@ -202,13 +202,13 @@ def testCoreTree_BuildTree(dummyGUI, dummyItems):
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTree_Methods(dummyGUI, dummyItems):
|
||||
"""Test building a project tree from a list of items.
|
||||
"""Test bvarious class methods.
|
||||
"""
|
||||
theProject = NWProject(dummyGUI)
|
||||
theTree = NWTree(theProject)
|
||||
|
||||
for tHandle, pHande, nwItem in dummyItems:
|
||||
theTree.append(tHandle, pHande, nwItem)
|
||||
for tHandle, pHandle, nwItem in dummyItems:
|
||||
theTree.append(tHandle, pHandle, nwItem)
|
||||
|
||||
assert len(theTree) == len(dummyItems)
|
||||
|
||||
@@ -256,6 +256,131 @@ def testCoreTree_Methods(dummyGUI, dummyItems):
|
||||
|
||||
# END Test testCoreTree_Methods
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTree_UpdateItemLayout(dummyGUI, dummyItems):
|
||||
"""Test building a project tree from a list of items.
|
||||
"""
|
||||
theProject = NWProject(dummyGUI)
|
||||
theTree = NWTree(theProject)
|
||||
|
||||
for tHandle, pHandle, nwItem in dummyItems:
|
||||
theTree.append(tHandle, pHandle, nwItem)
|
||||
|
||||
assert len(theTree) == len(dummyItems)
|
||||
|
||||
# Check rejected items
|
||||
assert not theTree.updateItemLayout("0000000000000", "H1") # Non-existent handle
|
||||
assert not theTree.updateItemLayout("a000000000004", "H2") # Character file
|
||||
assert not theTree.updateItemLayout("c000000000002", "H0") # Wrong header level
|
||||
|
||||
cHandle = "c000000000002"
|
||||
|
||||
# Check layouts we won't change
|
||||
theTree[cHandle].setLayout(nwItemLayout.NO_LAYOUT)
|
||||
assert not theTree.updateItemLayout("c000000000002", "H1")
|
||||
|
||||
theTree[cHandle].setLayout(nwItemLayout.TITLE)
|
||||
assert not theTree.updateItemLayout("c000000000002", "H1")
|
||||
|
||||
theTree[cHandle].setLayout(nwItemLayout.PAGE)
|
||||
assert not theTree.updateItemLayout("c000000000002", "H1")
|
||||
|
||||
theTree[cHandle].setLayout(nwItemLayout.NOTE)
|
||||
assert not theTree.updateItemLayout("c000000000002", "H1")
|
||||
|
||||
# BOOK is also a layout we change to, but never from
|
||||
theTree[cHandle].setLayout(nwItemLayout.BOOK)
|
||||
assert not theTree.updateItemLayout("c000000000002", "H1")
|
||||
|
||||
# Test SCENE Changes
|
||||
# ==================
|
||||
|
||||
# H1 -> BOOK
|
||||
theTree[cHandle].setLayout(nwItemLayout.SCENE)
|
||||
assert theTree.updateItemLayout("c000000000002", "H1")
|
||||
assert theTree[cHandle].itemLayout == nwItemLayout.BOOK
|
||||
|
||||
# H2 -> CHAPTER
|
||||
theTree[cHandle].setLayout(nwItemLayout.SCENE)
|
||||
assert theTree.updateItemLayout("c000000000002", "H2")
|
||||
assert theTree[cHandle].itemLayout == nwItemLayout.CHAPTER
|
||||
|
||||
# H3 -> No CHange
|
||||
theTree[cHandle].setLayout(nwItemLayout.SCENE)
|
||||
assert not theTree.updateItemLayout("c000000000002", "H3")
|
||||
|
||||
# H4 -> No CHange
|
||||
theTree[cHandle].setLayout(nwItemLayout.SCENE)
|
||||
assert not theTree.updateItemLayout("c000000000002", "H4")
|
||||
|
||||
# Test CHAPTER Changes
|
||||
# ====================
|
||||
|
||||
# H1 -> BOOK
|
||||
theTree[cHandle].setLayout(nwItemLayout.CHAPTER)
|
||||
assert theTree.updateItemLayout("c000000000002", "H1")
|
||||
assert theTree[cHandle].itemLayout == nwItemLayout.BOOK
|
||||
|
||||
# H2 -> No Change
|
||||
theTree[cHandle].setLayout(nwItemLayout.CHAPTER)
|
||||
assert not theTree.updateItemLayout("c000000000002", "H2")
|
||||
|
||||
# H3 -> SCENE
|
||||
theTree[cHandle].setLayout(nwItemLayout.CHAPTER)
|
||||
assert theTree.updateItemLayout("c000000000002", "H3")
|
||||
assert theTree[cHandle].itemLayout == nwItemLayout.SCENE
|
||||
|
||||
# H4 -> SCENE
|
||||
theTree[cHandle].setLayout(nwItemLayout.CHAPTER)
|
||||
assert theTree.updateItemLayout("c000000000002", "H4")
|
||||
assert theTree[cHandle].itemLayout == nwItemLayout.SCENE
|
||||
|
||||
# Test UNNUMBERED Changes
|
||||
# =======================
|
||||
|
||||
# H1 -> BOOK
|
||||
theTree[cHandle].setLayout(nwItemLayout.UNNUMBERED)
|
||||
assert theTree.updateItemLayout("c000000000002", "H1")
|
||||
assert theTree[cHandle].itemLayout == nwItemLayout.BOOK
|
||||
|
||||
# H2 -> No Change
|
||||
theTree[cHandle].setLayout(nwItemLayout.UNNUMBERED)
|
||||
assert not theTree.updateItemLayout("c000000000002", "H2")
|
||||
|
||||
# H3 -> SCENE
|
||||
theTree[cHandle].setLayout(nwItemLayout.UNNUMBERED)
|
||||
assert theTree.updateItemLayout("c000000000002", "H3")
|
||||
assert theTree[cHandle].itemLayout == nwItemLayout.SCENE
|
||||
|
||||
# H4 -> SCENE
|
||||
theTree[cHandle].setLayout(nwItemLayout.UNNUMBERED)
|
||||
assert theTree.updateItemLayout("c000000000002", "H4")
|
||||
assert theTree[cHandle].itemLayout == nwItemLayout.SCENE
|
||||
|
||||
# Test PARTITION Changes
|
||||
# ======================
|
||||
|
||||
# H1 -> BOOK
|
||||
theTree[cHandle].setLayout(nwItemLayout.PARTITION)
|
||||
assert not theTree.updateItemLayout("c000000000002", "H1")
|
||||
|
||||
# H2 -> No Change
|
||||
theTree[cHandle].setLayout(nwItemLayout.PARTITION)
|
||||
assert theTree.updateItemLayout("c000000000002", "H2")
|
||||
assert theTree[cHandle].itemLayout == nwItemLayout.CHAPTER
|
||||
|
||||
# H3 -> SCENE
|
||||
theTree[cHandle].setLayout(nwItemLayout.PARTITION)
|
||||
assert theTree.updateItemLayout("c000000000002", "H3")
|
||||
assert theTree[cHandle].itemLayout == nwItemLayout.SCENE
|
||||
|
||||
# H4 -> SCENE
|
||||
theTree[cHandle].setLayout(nwItemLayout.PARTITION)
|
||||
assert theTree.updateItemLayout("c000000000002", "H4")
|
||||
assert theTree[cHandle].itemLayout == nwItemLayout.SCENE
|
||||
|
||||
# END Test testCoreTree_UpdateItemLayout
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTree_MakeHandles(monkeypatch, dummyGUI):
|
||||
"""Test generating item handles.
|
||||
@@ -296,8 +421,8 @@ def testCoreTree_Stats(dummyGUI, dummyItems):
|
||||
theProject = NWProject(dummyGUI)
|
||||
theTree = NWTree(theProject)
|
||||
|
||||
for tHandle, pHande, nwItem in dummyItems:
|
||||
theTree.append(tHandle, pHande, nwItem)
|
||||
for tHandle, pHandle, nwItem in dummyItems:
|
||||
theTree.append(tHandle, pHandle, nwItem)
|
||||
|
||||
assert len(theTree) == len(dummyItems)
|
||||
theTree._treeOrder.append("dummy")
|
||||
@@ -323,9 +448,9 @@ def testCoreTree_Reorder(dummyGUI, dummyItems):
|
||||
theTree = NWTree(theProject)
|
||||
|
||||
aHandle = []
|
||||
for tHandle, pHande, nwItem in dummyItems:
|
||||
for tHandle, pHandle, nwItem in dummyItems:
|
||||
aHandle.append(tHandle)
|
||||
theTree.append(tHandle, pHande, nwItem)
|
||||
theTree.append(tHandle, pHandle, nwItem)
|
||||
|
||||
assert len(theTree) == len(dummyItems)
|
||||
|
||||
@@ -348,13 +473,13 @@ def testCoreTree_Reorder(dummyGUI, dummyItems):
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTree_XMLPackUnpack(dummyGUI, dummyItems):
|
||||
"""Test changing tree order.
|
||||
"""Test packing and unpacking the tree to and from XML.
|
||||
"""
|
||||
theProject = NWProject(dummyGUI)
|
||||
theTree = NWTree(theProject)
|
||||
|
||||
for tHandle, pHande, nwItem in dummyItems:
|
||||
theTree.append(tHandle, pHande, nwItem)
|
||||
for tHandle, pHandle, nwItem in dummyItems:
|
||||
theTree.append(tHandle, pHandle, nwItem)
|
||||
|
||||
assert len(theTree) == len(dummyItems)
|
||||
|
||||
@@ -408,8 +533,8 @@ def testCoreTree_ToCFile(monkeypatch, dummyGUI, dummyItems, tmpDir):
|
||||
theProject = NWProject(dummyGUI)
|
||||
theTree = NWTree(theProject)
|
||||
|
||||
for tHandle, pHande, nwItem in dummyItems:
|
||||
theTree.append(tHandle, pHande, nwItem)
|
||||
for tHandle, pHandle, nwItem in dummyItems:
|
||||
theTree.append(tHandle, pHandle, nwItem)
|
||||
|
||||
assert len(theTree) == len(dummyItems)
|
||||
theTree._treeOrder.append("dummy")
|
||||
|
||||
@@ -59,6 +59,9 @@ def testGuiBuild_Tool(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
nwBuild = getGuiItem("GuiBuildNovel")
|
||||
assert isinstance(nwBuild, GuiBuildNovel)
|
||||
|
||||
nwBuild.textFont.setText("DejaVu Sans")
|
||||
nwBuild.textSize.setValue(11)
|
||||
|
||||
# Default Settings
|
||||
qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
|
||||
|
||||
@@ -77,6 +80,20 @@ def testGuiBuild_Tool(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_MD)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.md")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step1_Lorem_Ipsum.md")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step1_Lorem_Ipsum.md")
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_GH)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.md")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step1G_Lorem_Ipsum.md")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step1G_Lorem_Ipsum.md")
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
# Change Title Formats and Flip Switches
|
||||
nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%")
|
||||
qtbot.wait(stepDelay)
|
||||
@@ -93,6 +110,8 @@ def testGuiBuild_Tool(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwBuild.includeKeywords, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwBuild.replaceUCode, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
qtbot.mouseClick(nwBuild.noteFiles, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
@@ -116,6 +135,13 @@ def testGuiBuild_Tool(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_MD)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.md")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step2_Lorem_Ipsum.md")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step2_Lorem_Ipsum.md")
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
# Replace Tabs with Spaces
|
||||
qtbot.mouseClick(nwBuild.replaceTabs, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
@@ -137,6 +163,13 @@ def testGuiBuild_Tool(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_MD)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.md")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step3_Lorem_Ipsum.md")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step3_Lorem_Ipsum.md")
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
# Putline Mode
|
||||
nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%")
|
||||
qtbot.wait(stepDelay)
|
||||
@@ -196,16 +229,9 @@ def testGuiBuild_Tool(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_PDF)
|
||||
assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.pdf"))
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_MD)
|
||||
assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.md"))
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_TXT)
|
||||
assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.txt"))
|
||||
|
||||
# Close the build tool
|
||||
htmlText = nwBuild.htmlText
|
||||
htmlStyle = nwBuild.htmlStyle
|
||||
nwdText = nwBuild.nwdText
|
||||
buildTime = nwBuild.buildTime
|
||||
nwBuild._doClose()
|
||||
|
||||
@@ -219,7 +245,6 @@ def testGuiBuild_Tool(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
assert nwBuild.viewCachedDoc()
|
||||
assert nwBuild.htmlText == htmlText
|
||||
assert nwBuild.htmlStyle == htmlStyle
|
||||
assert nwBuild.nwdText == nwdText
|
||||
assert nwBuild.buildTime == buildTime
|
||||
|
||||
nwBuild._doClose()
|
||||
|
||||
@@ -61,7 +61,7 @@ def testGuiDialogs_Quotes(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
# END Test testDialogs_Quotes
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiDialogs_Other(qtbot, monkeypatch, nwGUI, nwMinimal, tmpDir):
|
||||
def testGuiDialogs_Other(qtbot, monkeypatch, nwGUI, tmpDir):
|
||||
"""Various other dialog tests.
|
||||
"""
|
||||
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: tmpDir)
|
||||
|
||||
@@ -28,8 +28,10 @@ from tools import cmpFiles
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QTextCursor
|
||||
from PyQt5.QtWidgets import QAction, QMessageBox
|
||||
from PyQt5.QtWidgets import QAction, QMessageBox, QDialog
|
||||
|
||||
from nw.gui.itemeditor import GuiItemEditor
|
||||
from nw.gui.doceditor import GuiDocEditor
|
||||
from nw.gui.projtree import GuiProjectTree
|
||||
from nw.constants import nwItemType, nwDocAction
|
||||
|
||||
@@ -43,7 +45,11 @@ def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir, outDi
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiItemEditor, "exec_", lambda *args: None)
|
||||
monkeypatch.setattr(GuiItemEditor, "result", lambda *args: QDialog.Accepted)
|
||||
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *args: True)
|
||||
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *args: True)
|
||||
|
||||
# Create new, save, close project
|
||||
nwGUI.theProject.projTree.setSeed(42)
|
||||
@@ -353,6 +359,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *args: True)
|
||||
|
||||
nwGUI.theProject.projTree.setSeed(42)
|
||||
assert nwGUI.openProject(nwLipsum)
|
||||
|
||||
@@ -38,6 +38,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
|
||||
|
||||
# Open project
|
||||
nwGUI.theProject.projTree.setSeed(42)
|
||||
|
||||
@@ -28,7 +28,7 @@ from tools import cmpFiles, getGuiItem
|
||||
|
||||
from PyQt5.QtWidgets import QAction, QMessageBox
|
||||
|
||||
from nw.gui import GuiItemEditor
|
||||
from nw.gui import GuiItemEditor, GuiProjectTree
|
||||
from nw.constants import nwItemLayout
|
||||
|
||||
keyDelay = 2
|
||||
@@ -45,6 +45,7 @@ def testGuiItemEditor_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir,
|
||||
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *args: True)
|
||||
|
||||
# Create new, save, open project
|
||||
nwGUI.theProject.projTree.setSeed(42)
|
||||
|
||||
@@ -27,6 +27,7 @@ from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QTextCursor, QTextBlock
|
||||
from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
|
||||
|
||||
from nw.gui.doceditor import GuiDocEditor
|
||||
from nw.constants import nwUnicode, nwDocAction, nwDocInsert, nwKeyWords
|
||||
|
||||
keyDelay = 2
|
||||
@@ -39,6 +40,7 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *args: True)
|
||||
|
||||
# Test Document Action with No Project
|
||||
assert not nwGUI.docEditor.docAction(nwDocAction.COPY)
|
||||
@@ -498,6 +500,10 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
nwGUI.mainMenu.mInsKWItems[nwKeyWords.POV_KEY][0].activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.POV_KEY
|
||||
|
||||
nwGUI.docEditor.setText("Stuff")
|
||||
nwGUI.mainMenu.mInsKWItems[nwKeyWords.FOCUS_KEY][0].activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.FOCUS_KEY
|
||||
|
||||
nwGUI.docEditor.setText("Stuff")
|
||||
nwGUI.mainMenu.mInsKWItems[nwKeyWords.CHAR_KEY][0].activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.CHAR_KEY
|
||||
|
||||
@@ -37,6 +37,7 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
|
||||
|
||||
assert nwGUI.openProject(nwLipsum)
|
||||
nwGUI.mainConf.lastPath = nwLipsum
|
||||
|
||||
@@ -48,6 +48,7 @@ def testGuiProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDi
|
||||
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *args: QMessageBox.Yes)
|
||||
|
||||
# Check that we cannot open when there is no project
|
||||
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
|
||||
|
||||
@@ -38,6 +38,9 @@ def testGuiProjTree_TreeItems(qtbot, caplog, monkeypatch, nwGUI, nwMinimal):
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *args: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *args: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiMain, "editItem", lambda *args: None)
|
||||
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *args: True)
|
||||
|
||||
@@ -96,12 +99,12 @@ def testGuiProjTree_TreeItems(qtbot, caplog, monkeypatch, nwGUI, nwMinimal):
|
||||
nwTree.setSelectedHandle("8c659a11cd429")
|
||||
|
||||
# Shift focus and try to move item
|
||||
monkeypatch.setattr("PyQt5.QtWidgets.qApp.focusWidget", lambda: None)
|
||||
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *args: False)
|
||||
assert not nwTree.moveTreeItem(1)
|
||||
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
|
||||
"a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048", "71ee45a3c0db9"
|
||||
]
|
||||
monkeypatch.setattr("PyQt5.QtWidgets.qApp.focusWidget", lambda: nwTree)
|
||||
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *args: True)
|
||||
|
||||
# Move second item up twice (should give same result)
|
||||
nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
|
||||
@@ -131,6 +134,15 @@ def testGuiProjTree_TreeItems(qtbot, caplog, monkeypatch, nwGUI, nwMinimal):
|
||||
"a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9", "8c659a11cd429"
|
||||
]
|
||||
|
||||
# Move up twice, and undo
|
||||
nwTree._lastMove = {}
|
||||
nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
|
||||
nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
|
||||
nwGUI.mainMenu.aMoveUndo.activate(QAction.Trigger)
|
||||
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
|
||||
"a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9", "8c659a11cd429"
|
||||
]
|
||||
|
||||
# Move a root item (top level items are different) twice
|
||||
nwTree.flushTreeOrder()
|
||||
assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 10
|
||||
|
||||
@@ -46,6 +46,7 @@ def testGuiProjectWizard_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *args: QMessageBox.Yes)
|
||||
|
||||
if sys.platform.startswith("darwin"):
|
||||
# Disable for macOS because the test segfaults on QWizard.show()
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
novelWriter – Other Dialog Classes Tester
|
||||
=========================================
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2021, Veronica Berglyd Olsen
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QDialog, QMessageBox, QAction
|
||||
|
||||
from tools import writeFile, readFile, getGuiItem
|
||||
from dummy import causeOSError
|
||||
|
||||
from nw.gui.wordlist import GuiWordList
|
||||
from nw.constants import nwFiles
|
||||
|
||||
keyDelay = 2
|
||||
typeDelay = 1
|
||||
stepDelay = 20
|
||||
|
||||
def testGuiWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal, tmpDir):
|
||||
"""test the word list editor.
|
||||
"""
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *args: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiWordList, "exec_", lambda *args: None)
|
||||
monkeypatch.setattr(GuiWordList, "result", lambda *args: QDialog.Accepted)
|
||||
monkeypatch.setattr(GuiWordList, "accept", lambda *args: None)
|
||||
|
||||
# Open project
|
||||
nwGUI.openProject(nwMinimal)
|
||||
qtbot.wait(stepDelay)
|
||||
dictFile = os.path.join(nwMinimal, "meta", nwFiles.PROJ_DICT)
|
||||
|
||||
# Load the dialog
|
||||
nwGUI.mainMenu.aEditWordList.activate(QAction.Trigger)
|
||||
qtbot.waitUntil(lambda: getGuiItem("GuiWordList") is not None, timeout=1000)
|
||||
|
||||
wList = getGuiItem("GuiWordList")
|
||||
assert isinstance(wList, GuiWordList)
|
||||
wList.show()
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# List should be blank
|
||||
assert wList.listBox.count() == 0
|
||||
|
||||
# Add words
|
||||
writeFile(dictFile, (
|
||||
"word_a\n"
|
||||
"word_c\n"
|
||||
"word_g\n"
|
||||
" \n" # Should be ignored
|
||||
"word_f\n"
|
||||
"word_b\n"
|
||||
))
|
||||
qtbot.wait(stepDelay)
|
||||
assert wList._loadWordList()
|
||||
|
||||
# Check that the content was loaded
|
||||
assert wList.listBox.item(0).text() == "word_a"
|
||||
assert wList.listBox.item(1).text() == "word_b"
|
||||
assert wList.listBox.item(2).text() == "word_c"
|
||||
assert wList.listBox.item(3).text() == "word_f"
|
||||
assert wList.listBox.item(4).text() == "word_g"
|
||||
|
||||
# Add a blank word
|
||||
wList.newEntry.setText(" ")
|
||||
assert not wList._doAdd()
|
||||
|
||||
# Add an existing word
|
||||
wList.newEntry.setText("word_c")
|
||||
assert not wList._doAdd()
|
||||
|
||||
# Add a new word
|
||||
wList.newEntry.setText("word_d")
|
||||
assert wList._doAdd()
|
||||
|
||||
# Check that the content now
|
||||
assert wList.listBox.item(0).text() == "word_a"
|
||||
assert wList.listBox.item(1).text() == "word_b"
|
||||
assert wList.listBox.item(2).text() == "word_c"
|
||||
assert wList.listBox.item(3).text() == "word_d"
|
||||
assert wList.listBox.item(4).text() == "word_f"
|
||||
assert wList.listBox.item(5).text() == "word_g"
|
||||
|
||||
# Delete a word
|
||||
wList.newEntry.setText("delete_me")
|
||||
assert wList._doAdd()
|
||||
assert wList.listBox.item(0).text() == "delete_me"
|
||||
|
||||
delItem = wList.listBox.findItems("delete_me", Qt.MatchExactly)[0]
|
||||
assert delItem.text() == "delete_me"
|
||||
delItem.setSelected(True)
|
||||
wList._doDelete()
|
||||
assert wList.listBox.findItems("delete_me", Qt.MatchExactly) == []
|
||||
assert wList.listBox.item(0).text() == "word_a"
|
||||
|
||||
# Save files
|
||||
assert wList._doSave()
|
||||
assert readFile(dictFile) == (
|
||||
"word_a\n"
|
||||
"word_b\n"
|
||||
"word_c\n"
|
||||
"word_d\n"
|
||||
"word_f\n"
|
||||
"word_g\n"
|
||||
)
|
||||
|
||||
# Save again and make it fail
|
||||
monkeypatch.setattr("builtins.open", causeOSError)
|
||||
assert not wList._doSave()
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
wList._doClose()
|
||||
|
||||
# END Test testGuiWordList_Dialog
|
||||
@@ -72,7 +72,7 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
# Make a test log file
|
||||
writeFile(sessFile, (
|
||||
"# Offset 123\n"
|
||||
"# Start Time End Time Novel Notes\n"
|
||||
"# Start Time End Time Novel Notes Idle\n"
|
||||
"2020-01-01 21:00:00 2020-01-01 21:00:05 6 0\n"
|
||||
"2020-01-03 21:00:00 2020-01-03 21:00:15 125 0\n"
|
||||
"2020-01-03 21:30:00 2020-01-03 21:30:15 125 5\n"
|
||||
@@ -86,8 +86,8 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
# Make sure a faulty file can still be read
|
||||
writeFile(sessFile, (
|
||||
"# Offset abc123\n"
|
||||
"# Start Time End Time Novel Notes\n"
|
||||
"2020-01-01 21:00:00 2020-01-01 21:00:05 6 0\n"
|
||||
"# Start Time End Time Novel Notes Idle\n"
|
||||
"2020-01-01 21:00:00 2020-01-01 21:00:05 6 0 50\n"
|
||||
"2020-01-03 21:00:00 2020-01-03 21:00:15 125 0\n"
|
||||
"2020-01-03 21:30:00 2020-01-03 21:30:15 125 5\n"
|
||||
"2020-01-06 21:00:00 2020-01-06 21:00:10 125\n"
|
||||
@@ -101,17 +101,17 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
|
||||
writeFile(sessFile, (
|
||||
"# Offset 1075\n"
|
||||
"# Start Time End Time Novel Notes\n"
|
||||
"2021-01-31 19:00:00 2021-01-31 19:30:00 700 375\n"
|
||||
"2021-02-01 19:00:00 2021-02-01 19:30:00 700 375\n"
|
||||
"2021-02-01 20:00:00 2021-02-01 20:30:00 600 275\n"
|
||||
"2021-02-02 19:00:00 2021-02-02 19:30:00 750 425\n"
|
||||
"2021-02-02 20:00:00 2021-02-02 20:30:00 690 365\n"
|
||||
"2021-02-03 19:00:00 2021-02-03 19:30:00 680 355\n"
|
||||
"2021-02-04 19:00:00 2021-02-04 19:30:00 700 375\n"
|
||||
"2021-02-05 19:00:00 2021-02-05 19:30:00 500 175\n"
|
||||
"2021-02-06 19:00:00 2021-02-06 19:30:00 600 275\n"
|
||||
"2021-02-07 19:00:00 2021-02-07 19:30:00 600 275\n"
|
||||
"# Start Time End Time Novel Notes Idle\n"
|
||||
"2021-01-31 19:00:00 2021-01-31 19:30:00 700 375 0\n"
|
||||
"2021-02-01 19:00:00 2021-02-01 19:30:00 700 375 10\n"
|
||||
"2021-02-01 20:00:00 2021-02-01 20:30:00 600 275 20\n"
|
||||
"2021-02-02 19:00:00 2021-02-02 19:30:00 750 425 30\n"
|
||||
"2021-02-02 20:00:00 2021-02-02 20:30:00 690 365 40\n"
|
||||
"2021-02-03 19:00:00 2021-02-03 19:30:00 680 355 50\n"
|
||||
"2021-02-04 19:00:00 2021-02-04 19:30:00 700 375 60\n"
|
||||
"2021-02-05 19:00:00 2021-02-05 19:30:00 500 175 70\n"
|
||||
"2021-02-06 19:00:00 2021-02-06 19:30:00 600 275 80\n"
|
||||
"2021-02-07 19:00:00 2021-02-07 19:30:00 600 275 90\n"
|
||||
))
|
||||
sessLog.populateGUI()
|
||||
|
||||
@@ -156,28 +156,28 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
assert jsonData == [
|
||||
{
|
||||
"date": "2021-01-31 19:00:00", "length": 1800.0,
|
||||
"newWords": 1, "novelWords": 700, "noteWords": 375
|
||||
"newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0
|
||||
}, {
|
||||
"date": "2021-02-01 20:00:00", "length": 1800.0,
|
||||
"newWords": -200, "novelWords": 600, "noteWords": 275
|
||||
"newWords": -200, "novelWords": 600, "noteWords": 275, "idleTime": 20
|
||||
}, {
|
||||
"date": "2021-02-02 19:00:00", "length": 1800.0,
|
||||
"newWords": 300, "novelWords": 750, "noteWords": 425
|
||||
"newWords": 300, "novelWords": 750, "noteWords": 425, "idleTime": 30
|
||||
}, {
|
||||
"date": "2021-02-02 20:00:00", "length": 1800.0,
|
||||
"newWords": -120, "novelWords": 690, "noteWords": 365
|
||||
"newWords": -120, "novelWords": 690, "noteWords": 365, "idleTime": 40
|
||||
}, {
|
||||
"date": "2021-02-03 19:00:00", "length": 1800.0,
|
||||
"newWords": -20, "novelWords": 680, "noteWords": 355
|
||||
"newWords": -20, "novelWords": 680, "noteWords": 355, "idleTime": 50
|
||||
}, {
|
||||
"date": "2021-02-04 19:00:00", "length": 1800.0,
|
||||
"newWords": 40, "novelWords": 700, "noteWords": 375
|
||||
"newWords": 40, "novelWords": 700, "noteWords": 375, "idleTime": 60
|
||||
}, {
|
||||
"date": "2021-02-05 19:00:00", "length": 1800.0,
|
||||
"newWords": -400, "novelWords": 500, "noteWords": 175
|
||||
"newWords": -400, "novelWords": 500, "noteWords": 175, "idleTime": 70
|
||||
}, {
|
||||
"date": "2021-02-06 19:00:00", "length": 1800.0,
|
||||
"newWords": 200, "novelWords": 600, "noteWords": 275
|
||||
"newWords": 200, "novelWords": 600, "noteWords": 275, "idleTime": 80
|
||||
}
|
||||
]
|
||||
|
||||
@@ -206,28 +206,28 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
assert jsonData == [
|
||||
{
|
||||
"date": "2021-01-31 19:00:00", "length": 1800.0,
|
||||
"newWords": 1, "novelWords": 700, "noteWords": 375
|
||||
"newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0
|
||||
}, {
|
||||
"date": "2021-02-01 20:00:00", "length": 1800.0,
|
||||
"newWords": -100, "novelWords": 600, "noteWords": 275
|
||||
"newWords": -100, "novelWords": 600, "noteWords": 275, "idleTime": 20
|
||||
}, {
|
||||
"date": "2021-02-02 19:00:00", "length": 1800.0,
|
||||
"newWords": 150, "novelWords": 750, "noteWords": 425
|
||||
"newWords": 150, "novelWords": 750, "noteWords": 425, "idleTime": 30
|
||||
}, {
|
||||
"date": "2021-02-02 20:00:00", "length": 1800.0,
|
||||
"newWords": -60, "novelWords": 690, "noteWords": 365
|
||||
"newWords": -60, "novelWords": 690, "noteWords": 365, "idleTime": 40
|
||||
}, {
|
||||
"date": "2021-02-03 19:00:00", "length": 1800.0,
|
||||
"newWords": -10, "novelWords": 680, "noteWords": 355
|
||||
"newWords": -10, "novelWords": 680, "noteWords": 355, "idleTime": 50
|
||||
}, {
|
||||
"date": "2021-02-04 19:00:00", "length": 1800.0,
|
||||
"newWords": 20, "novelWords": 700, "noteWords": 375
|
||||
"newWords": 20, "novelWords": 700, "noteWords": 375, "idleTime": 60
|
||||
}, {
|
||||
"date": "2021-02-05 19:00:00", "length": 1800.0,
|
||||
"newWords": -200, "novelWords": 500, "noteWords": 175
|
||||
"newWords": -200, "novelWords": 500, "noteWords": 175, "idleTime": 70
|
||||
}, {
|
||||
"date": "2021-02-06 19:00:00", "length": 1800.0,
|
||||
"newWords": 100, "novelWords": 600, "noteWords": 275
|
||||
"newWords": 100, "novelWords": 600, "noteWords": 275, "idleTime": 80
|
||||
}
|
||||
]
|
||||
|
||||
@@ -254,28 +254,28 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
assert jsonData == [
|
||||
{
|
||||
"date": "2021-01-31 19:00:00", "length": 1800.0,
|
||||
"newWords": 1, "novelWords": 700, "noteWords": 375
|
||||
"newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0
|
||||
}, {
|
||||
"date": "2021-02-01 20:00:00", "length": 1800.0,
|
||||
"newWords": -100, "novelWords": 600, "noteWords": 275
|
||||
"newWords": -100, "novelWords": 600, "noteWords": 275, "idleTime": 20
|
||||
}, {
|
||||
"date": "2021-02-02 19:00:00", "length": 1800.0,
|
||||
"newWords": 150, "novelWords": 750, "noteWords": 425
|
||||
"newWords": 150, "novelWords": 750, "noteWords": 425, "idleTime": 30
|
||||
}, {
|
||||
"date": "2021-02-02 20:00:00", "length": 1800.0,
|
||||
"newWords": -60, "novelWords": 690, "noteWords": 365
|
||||
"newWords": -60, "novelWords": 690, "noteWords": 365, "idleTime": 40
|
||||
}, {
|
||||
"date": "2021-02-03 19:00:00", "length": 1800.0,
|
||||
"newWords": -10, "novelWords": 680, "noteWords": 355
|
||||
"newWords": -10, "novelWords": 680, "noteWords": 355, "idleTime": 50
|
||||
}, {
|
||||
"date": "2021-02-04 19:00:00", "length": 1800.0,
|
||||
"newWords": 20, "novelWords": 700, "noteWords": 375
|
||||
"newWords": 20, "novelWords": 700, "noteWords": 375, "idleTime": 60
|
||||
}, {
|
||||
"date": "2021-02-05 19:00:00", "length": 1800.0,
|
||||
"newWords": -200, "novelWords": 500, "noteWords": 175
|
||||
"newWords": -200, "novelWords": 500, "noteWords": 175, "idleTime": 70
|
||||
}, {
|
||||
"date": "2021-02-06 19:00:00", "length": 1800.0,
|
||||
"newWords": 100, "novelWords": 600, "noteWords": 275
|
||||
"newWords": 100, "novelWords": 600, "noteWords": 275, "idleTime": 80
|
||||
}
|
||||
]
|
||||
|
||||
@@ -300,16 +300,16 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
assert jsonData == [
|
||||
{
|
||||
"date": "2021-01-31 19:00:00", "length": 1800.0,
|
||||
"newWords": 1, "novelWords": 700, "noteWords": 375
|
||||
"newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0
|
||||
}, {
|
||||
"date": "2021-02-02 19:00:00", "length": 1800.0,
|
||||
"newWords": 300, "novelWords": 750, "noteWords": 425
|
||||
"newWords": 300, "novelWords": 750, "noteWords": 425, "idleTime": 30
|
||||
}, {
|
||||
"date": "2021-02-04 19:00:00", "length": 1800.0,
|
||||
"newWords": 40, "novelWords": 700, "noteWords": 375
|
||||
"newWords": 40, "novelWords": 700, "noteWords": 375, "idleTime": 60
|
||||
}, {
|
||||
"date": "2021-02-06 19:00:00", "length": 1800.0,
|
||||
"newWords": 200, "novelWords": 600, "noteWords": 275
|
||||
"newWords": 200, "novelWords": 600, "noteWords": 275, "idleTime": 80
|
||||
}
|
||||
]
|
||||
|
||||
@@ -338,34 +338,34 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
assert jsonData == [
|
||||
{
|
||||
"date": "2021-01-31 19:00:00", "length": 1800.0,
|
||||
"newWords": 1, "novelWords": 700, "noteWords": 375
|
||||
"newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0
|
||||
}, {
|
||||
"date": "2021-02-01 19:00:00", "length": 1800.0,
|
||||
"newWords": 0, "novelWords": 700, "noteWords": 375
|
||||
"newWords": 0, "novelWords": 700, "noteWords": 375, "idleTime": 10
|
||||
}, {
|
||||
"date": "2021-02-01 20:00:00", "length": 1800.0,
|
||||
"newWords": -200, "novelWords": 600, "noteWords": 275
|
||||
"newWords": -200, "novelWords": 600, "noteWords": 275, "idleTime": 20
|
||||
}, {
|
||||
"date": "2021-02-02 19:00:00", "length": 1800.0,
|
||||
"newWords": 300, "novelWords": 750, "noteWords": 425
|
||||
"newWords": 300, "novelWords": 750, "noteWords": 425, "idleTime": 30
|
||||
}, {
|
||||
"date": "2021-02-02 20:00:00", "length": 1800.0,
|
||||
"newWords": -120, "novelWords": 690, "noteWords": 365
|
||||
"newWords": -120, "novelWords": 690, "noteWords": 365, "idleTime": 40
|
||||
}, {
|
||||
"date": "2021-02-03 19:00:00", "length": 1800.0,
|
||||
"newWords": -20, "novelWords": 680, "noteWords": 355
|
||||
"newWords": -20, "novelWords": 680, "noteWords": 355, "idleTime": 50
|
||||
}, {
|
||||
"date": "2021-02-04 19:00:00", "length": 1800.0,
|
||||
"newWords": 40, "novelWords": 700, "noteWords": 375
|
||||
"newWords": 40, "novelWords": 700, "noteWords": 375, "idleTime": 60
|
||||
}, {
|
||||
"date": "2021-02-05 19:00:00", "length": 1800.0,
|
||||
"newWords": -400, "novelWords": 500, "noteWords": 175
|
||||
"newWords": -400, "novelWords": 500, "noteWords": 175, "idleTime": 70
|
||||
}, {
|
||||
"date": "2021-02-06 19:00:00", "length": 1800.0,
|
||||
"newWords": 200, "novelWords": 600, "noteWords": 275
|
||||
"newWords": 200, "novelWords": 600, "noteWords": 275, "idleTime": 80
|
||||
}, {
|
||||
"date": "2021-02-07 19:00:00", "length": 1800.0,
|
||||
"newWords": 0, "novelWords": 600, "noteWords": 275
|
||||
"newWords": 0, "novelWords": 600, "noteWords": 275, "idleTime": 90
|
||||
}
|
||||
]
|
||||
|
||||
@@ -391,28 +391,28 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
assert jsonData == [
|
||||
{
|
||||
"date": "2021-01-31", "length": 1800.0,
|
||||
"newWords": 1, "novelWords": 700, "noteWords": 375
|
||||
"newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0
|
||||
}, {
|
||||
"date": "2021-02-01", "length": 3600.0,
|
||||
"newWords": -200, "novelWords": 600, "noteWords": 275
|
||||
"newWords": -200, "novelWords": 600, "noteWords": 275, "idleTime": 30
|
||||
}, {
|
||||
"date": "2021-02-02", "length": 3600.0,
|
||||
"newWords": 180, "novelWords": 690, "noteWords": 365
|
||||
"newWords": 180, "novelWords": 690, "noteWords": 365, "idleTime": 70
|
||||
}, {
|
||||
"date": "2021-02-03", "length": 1800.0,
|
||||
"newWords": -20, "novelWords": 680, "noteWords": 355
|
||||
"newWords": -20, "novelWords": 680, "noteWords": 355, "idleTime": 50
|
||||
}, {
|
||||
"date": "2021-02-04", "length": 1800.0,
|
||||
"newWords": 40, "novelWords": 700, "noteWords": 375
|
||||
"newWords": 40, "novelWords": 700, "noteWords": 375, "idleTime": 60
|
||||
}, {
|
||||
"date": "2021-02-05", "length": 1800.0,
|
||||
"newWords": -400, "novelWords": 500, "noteWords": 175
|
||||
"newWords": -400, "novelWords": 500, "noteWords": 175, "idleTime": 70
|
||||
}, {
|
||||
"date": "2021-02-06", "length": 1800.0,
|
||||
"newWords": 200, "novelWords": 600, "noteWords": 275
|
||||
"newWords": 200, "novelWords": 600, "noteWords": 275, "idleTime": 80
|
||||
}, {
|
||||
"date": "2021-02-07", "length": 1800.0,
|
||||
"newWords": 0, "novelWords": 600, "noteWords": 275
|
||||
"newWords": 0, "novelWords": 600, "noteWords": 275, "idleTime": 90
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user