7 Commits

Author SHA1 Message Date
03ccf03808 set dock properties 2025-03-27 11:33:23 +01:00
cdeb1205ef rework ui 2025-03-27 11:29:55 +01:00
c4d74bb22b fix first tab 2024-11-28 22:15:31 +01:00
f99ab4741b add thread, export to pdf, new css 2024-11-28 19:14:32 +01:00
b014daad7e using maddy for markdown parsing 2024-11-26 14:13:57 +01:00
Aurélie
73363bf12c Update README.md Ubuntu command 2023-01-13 12:07:15 +01:00
Aurélie
ff4ebbc82f Update README.md 2022-12-27 12:31:07 +01:00
45 changed files with 4003 additions and 209 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

0
Info.plist Executable file → Normal file
View File

View File

@@ -5,8 +5,13 @@ Wrote with Qt 6 Open Source : [Download](https://www.qt.io/download-open-source)
This is a very simple note editor. The editor has a Markdown text area and a rendering view This is a very simple note editor. The editor has a Markdown text area and a rendering view
## Prepare to use on linux ## Prepare to use on linux
You need to install Qt 6 dependencies You need to install Qt 6.2.4 dependencies
```sh ```sh
sudo apt install libqt6widgets6 libqt6gui6 libqt6core5compat6 libqt6core5compat6 libqt6core6 libqt6dbus6 sudo apt install libxcb1 libxcb-xinerama0 libqt6widgets6 libqt6gui6 libqt6core5compat6 libqt6core5compat6 libqt6core6 libqt6dbus6 qt6-qpa-plugins
```
### To run on Wayland
```sh
sudo apt install qt6-wayland
``` ```

11
WorkPad.pro Executable file → Normal file
View File

@@ -1,11 +1,11 @@
QT += core gui QT += core gui
greaterThan(QT_MAJOR_VERSION, 5): QT += widgets greaterThan(QT_MAJOR_VERSION, 5): QT += widgets webenginewidgets
CONFIG += c++17 CONFIG += c++17
win32:VERSION = 2.1.0.0 # major.minor.patch.build win32:VERSION = 3.0.0.0 # major.minor.patch.build
else:VERSION = 2.1.0 # major.minor.patch else:VERSION = 3.0.0 # major.minor.patch
DEFINES += APP_VERSION=\"\\\"$${VERSION}\\\"\" DEFINES += APP_VERSION=\"\\\"$${VERSION}\\\"\"
DEFINES += APP_NAME=\"\\\"WorkPad\\\"\" DEFINES += APP_NAME=\"\\\"WorkPad\\\"\"
@@ -83,6 +83,8 @@ SOURCES += \
src/services/savemanager.cpp src/services/savemanager.cpp
HEADERS += \ HEADERS += \
maddy/parserconfig.h \
maddy/parser.h \
src/frames/configdialog.h \ src/frames/configdialog.h \
src/services/configmanager.h \ src/services/configmanager.h \
src/frames/exportdialog.h \ src/frames/exportdialog.h \
@@ -109,4 +111,5 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target !isEmpty(target.path): INSTALLS += target
RESOURCES += \ RESOURCES += \
icons.qrc icons.qrc \
static.qrc

0
icon.icns Executable file → Normal file
View File

0
icon.ico Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

0
icons.qrc Executable file → Normal file
View File

192
maddy/blockparser.h Normal file
View File

@@ -0,0 +1,192 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <sstream>
#include <string>
// windows compatibility includes
#include <algorithm>
#include <cctype>
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* BlockParser
*
* The code expects every child to have the following static function to be
* implemented:
* `static bool IsStartingLine(const std::string& line)`
*
* @class
*/
class BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string&
* line)>} getBlockParserForLineCallback
*/
BlockParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)>
getBlockParserForLineCallback
)
: result("", std::ios_base::ate | std::ios_base::in | std::ios_base::out)
, childParser(nullptr)
, parseLineCallback(parseLineCallback)
, getBlockParserForLineCallback(getBlockParserForLineCallback)
{}
/**
* dtor
*
* @method
*/
virtual ~BlockParser() {}
/**
* AddLine
*
* Adding a line which has to be parsed.
*
* @method
* @param {std::string&} line
* @return {void}
*/
virtual void AddLine(std::string& line)
{
this->parseBlock(line);
if (this->isInlineBlockAllowed() && !this->childParser)
{
this->childParser = this->getBlockParserForLine(line);
}
if (this->childParser)
{
this->childParser->AddLine(line);
if (this->childParser->IsFinished())
{
this->result << this->childParser->GetResult().str();
this->childParser = nullptr;
}
return;
}
if (this->isLineParserAllowed())
{
this->parseLine(line);
}
this->result << line;
}
/**
* IsFinished
*
* Check if the BlockParser is done
*
* @method
* @return {bool}
*/
virtual bool IsFinished() const = 0;
/**
* GetResult
*
* Get the parsed HTML output.
*
* @method
* @return {std::stringstream}
*/
std::stringstream& GetResult() { return this->result; }
/**
* Clear
*
* Clear the result to reuse the parser object.
*
* It is only used by one test for now.
*
* @method
* @return {void}
*/
void Clear() { this->result.str(""); }
protected:
std::stringstream result;
std::shared_ptr<BlockParser> childParser;
virtual bool isInlineBlockAllowed() const = 0;
virtual bool isLineParserAllowed() const = 0;
virtual void parseBlock(std::string& line) = 0;
void parseLine(std::string& line)
{
if (parseLineCallback)
{
parseLineCallback(line);
}
}
uint32_t getIndentationWidth(const std::string& line) const
{
bool hasMetNonSpace = false;
uint32_t indentation = static_cast<uint32_t>(std::count_if(
line.begin(),
line.end(),
[&hasMetNonSpace](unsigned char c)
{
if (hasMetNonSpace)
{
return false;
}
if (std::isspace(c))
{
return true;
}
hasMetNonSpace = true;
return false;
}
));
return indentation;
}
std::shared_ptr<BlockParser> getBlockParserForLine(const std::string& line)
{
if (getBlockParserForLineCallback)
{
return getBlockParserForLineCallback(line);
}
return nullptr;
}
private:
std::function<void(std::string&)> parseLineCallback;
std::function<std::shared_ptr<BlockParser>(const std::string& line)>
getBlockParserForLineCallback;
}; // class BlockParser
// -----------------------------------------------------------------------------
} // namespace maddy

50
maddy/breaklineparser.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <regex>
#include <string>
#include "maddy/lineparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* BreakLineParser
*
* @class
*/
class BreakLineParser : public LineParser
{
public:
/**
* Parse
*
* From Markdown: `text\r\n text`
*
* To HTML: `text<br> text`
*
* @method
* @param {std::string&} line The line to interpret
* @return {void}
*/
void Parse(std::string& line) override
{
static std::regex re(R"((\r\n|\r))");
static std::string replacement = "<br>";
line = std::regex_replace(line, re, replacement);
}
}; // class BreakLineParser
// -----------------------------------------------------------------------------
} // namespace maddy

127
maddy/checklistparser.h Normal file
View File

@@ -0,0 +1,127 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <regex>
#include <string>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* ChecklistParser
*
* @class
*/
class ChecklistParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string&
* line)>} getBlockParserForLineCallback
*/
ChecklistParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)>
getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isStarted(false)
, isFinished(false)
{}
/**
* IsStartingLine
*
* An unordered list starts with `* `.
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool IsStartingLine(const std::string& line)
{
static std::regex re(R"(^- \[[x| ]\] .*)");
return std::regex_match(line, re);
}
/**
* IsFinished
*
* @method
* @return {bool}
*/
bool IsFinished() const override { return this->isFinished; }
protected:
bool isInlineBlockAllowed() const override { return true; }
bool isLineParserAllowed() const override { return true; }
void parseBlock(std::string& line) override
{
bool isStartOfNewListItem = IsStartingLine(line);
uint32_t indentation = getIndentationWidth(line);
static std::regex lineRegex("^(- )");
line = std::regex_replace(line, lineRegex, "");
static std::regex emptyBoxRegex(R"(^\[ \])");
static std::string emptyBoxReplacement = "<input type=\"checkbox\"/>";
line = std::regex_replace(line, emptyBoxRegex, emptyBoxReplacement);
static std::regex boxRegex(R"(^\[x\])");
static std::string boxReplacement =
"<input type=\"checkbox\" checked=\"checked\"/>";
line = std::regex_replace(line, boxRegex, boxReplacement);
if (!this->isStarted)
{
line = "<ul class=\"checklist\"><li><label>" + line;
this->isStarted = true;
return;
}
if (indentation >= 2)
{
line = line.substr(2);
return;
}
if (line.empty() ||
line.find("</label></li><li><label>") != std::string::npos ||
line.find("</label></li></ul>") != std::string::npos)
{
line = "</label></li></ul>" + line;
this->isFinished = true;
return;
}
if (isStartOfNewListItem)
{
line = "</label></li><li><label>" + line;
}
}
private:
bool isStarted;
bool isFinished;
}; // class ChecklistParser
// -----------------------------------------------------------------------------
} // namespace maddy

132
maddy/codeblockparser.h Normal file
View File

@@ -0,0 +1,132 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <regex>
#include <string>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* CodeBlockParser
*
* From Markdown: 3 times surrounded code (without space in the beginning)
*
* ```
* ```
* some code
* ```
* ```
*
* To HTML:
*
* ```
* <pre><code>
* some code
* </code></pre>
* ```
*
* @class
*/
class CodeBlockParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string&
* line)>} getBlockParserForLineCallback
*/
CodeBlockParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)>
getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isStarted(false)
, isFinished(false)
{}
/**
* IsStartingLine
*
* If the line starts with three code signs, then it is a code block.
*
* ```
* ```
* ```
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool IsStartingLine(const std::string& line)
{
static std::regex re("^(?:`){3}(.*)$");
return std::regex_match(line, re);
}
/**
* IsFinished
*
* @method
* @return {bool}
*/
bool IsFinished() const override { return this->isFinished; }
protected:
bool isInlineBlockAllowed() const override { return false; }
bool isLineParserAllowed() const override { return false; }
void parseBlock(std::string& line) override
{
if (line == "```")
{
if (!this->isStarted)
{
line = "<pre><code>\n";
this->isStarted = true;
this->isFinished = false;
return;
}
else
{
line = "</code></pre>";
this->isFinished = true;
this->isStarted = false;
return;
}
}
else if (!this->isStarted && line.substr(0, 3) == "```")
{
line = "<pre class=\"" + line.substr(3) + "\"><code>\n";
this->isStarted = true;
this->isFinished = false;
return;
}
line += "\n";
}
private:
bool isStarted;
bool isFinished;
}; // class CodeBlockParser
// -----------------------------------------------------------------------------
} // namespace maddy

54
maddy/emphasizedparser.h Normal file
View File

@@ -0,0 +1,54 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <regex>
#include <string>
#include "maddy/lineparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* EmphasizedParser
*
* Has to be used after the `StrongParser`.
*
* @class
*/
class EmphasizedParser : public LineParser
{
public:
/**
* Parse
*
* From Markdown: `text _text_`
*
* To HTML: `text <em>text</em>`
*
* @method
* @param {std::string&} line The line to interpret
* @return {void}
*/
void Parse(std::string& line) override
{
static std::regex re(
R"((?!.*`.*|.*<code>.*)_(?!.*`.*|.*<\/code>.*)([^_]*)_(?!.*`.*|.*<\/code>.*))"
);
static std::string replacement = "<em>$1</em>";
line = std::regex_replace(line, re, replacement);
}
}; // class EmphasizedParser
// -----------------------------------------------------------------------------
} // namespace maddy

134
maddy/headlineparser.h Normal file
View File

@@ -0,0 +1,134 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <regex>
#include <string>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* HeadlineParser
*
* From Markdown:
*
* ```
* # Headline 1
* ## Headline 2
* ### Headline 3
* #### Headline 4
* ##### Headline 5
* ###### Headline 6
* ```
*
* To HTML:
*
* ```
* <h1>Headline 1</h1>
* <h2>Headline 2</h2>
* <h3>Headline 3</h3>
* <h4>Headline 4</h4>
* <h5>Headline 5</h5>
* <h6>Headline 6</h6>
* ```
*
* @class
*/
class HeadlineParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string&
* line)>} getBlockParserForLineCallback
*/
HeadlineParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)>
getBlockParserForLineCallback,
bool isInlineParserAllowed = true
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isInlineParserAllowed(isInlineParserAllowed)
{}
/**
* IsStartingLine
*
* If the line starts with 1 - 6 `#`, then it is a headline.
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool IsStartingLine(const std::string& line)
{
static std::regex re("^(?:#){1,6} (.*)");
return std::regex_match(line, re);
}
/**
* IsFinished
*
* The headline is always only one line long, so this method always returns
* true.
*
* @method
* @return {bool}
*/
bool IsFinished() const override { return true; }
protected:
bool isInlineBlockAllowed() const override { return false; }
bool isLineParserAllowed() const override
{
return this->isInlineParserAllowed;
}
void parseBlock(std::string& line) override
{
static std::vector<std::regex> hlRegex = {
std::regex("^# (.*)"),
std::regex("^(?:#){2} (.*)"),
std::regex("^(?:#){3} (.*)"),
std::regex("^(?:#){4} (.*)"),
std::regex("^(?:#){5} (.*)"),
std::regex("^(?:#){6} (.*)")
};
static std::vector<std::string> hlReplacement = {
"<h1>$1</h1>",
"<h2>$1</h2>",
"<h3>$1</h3>",
"<h4>$1</h4>",
"<h5>$1</h5>",
"<h6>$1</h6>"
};
for (uint8_t i = 0; i < 6; ++i)
{
line = std::regex_replace(line, hlRegex[i], hlReplacement[i]);
}
}
private:
bool isInlineParserAllowed;
}; // class HeadlineParser
// -----------------------------------------------------------------------------
} // namespace maddy

View File

@@ -0,0 +1,94 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <regex>
#include <string>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* HorizontalLineParser
*
* From Markdown: `---`
*
* To HTML: `<hr/>`
*
* @class
*/
class HorizontalLineParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string&
* line)>} getBlockParserForLineCallback
*/
HorizontalLineParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)>
getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, lineRegex("^---$")
{}
/**
* IsStartingLine
*
* If the line has exact three dashes `---`, then it is a horizontal line.
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool IsStartingLine(const std::string& line)
{
static std::regex re("^---$");
return std::regex_match(line, re);
}
/**
* IsFinished
*
* The horizontal line is always only one line long, so this method always
* returns true.
*
* @method
* @return {bool}
*/
bool IsFinished() const override { return true; }
protected:
bool isInlineBlockAllowed() const override { return false; }
bool isLineParserAllowed() const override { return false; }
void parseBlock(std::string& line) override
{
static std::string replacement = "<hr/>";
line = std::regex_replace(line, lineRegex, replacement);
}
private:
std::regex lineRegex;
}; // class HorizontalLineParser
// -----------------------------------------------------------------------------
} // namespace maddy

112
maddy/htmlparser.h Normal file
View File

@@ -0,0 +1,112 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <string>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* HtmlParser
*
* @class
*/
class HtmlParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string&
* line)>} getBlockParserForLineCallback
*/
HtmlParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)>
getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isStarted(false)
, isFinished(false)
, isGreaterThanFound(false)
{}
/**
* IsStartingLine
*
* If the line is starting with `<`, HTML is expected to follow.
* Nothing after that will be parsed, it only is copied.
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool IsStartingLine(const std::string& line) { return line[0] == '<'; }
/**
* IsFinished
*
* `>` followed by an empty line will end the HTML block.
*
* @method
* @return {bool}
*/
bool IsFinished() const override { return this->isFinished; }
protected:
bool isInlineBlockAllowed() const override { return false; }
bool isLineParserAllowed() const override { return false; }
void parseBlock(std::string& line) override
{
if (!this->isStarted)
{
this->isStarted = true;
}
if (!line.empty() && line[line.size() - 1] == '>')
{
this->isGreaterThanFound = true;
return;
}
if (line.empty() && this->isGreaterThanFound)
{
this->isFinished = true;
return;
}
if (!line.empty() && this->isGreaterThanFound)
{
this->isGreaterThanFound = false;
}
if (!line.empty())
{
line += " ";
}
}
private:
bool isStarted;
bool isFinished;
bool isGreaterThanFound;
}; // class HtmlParser
// -----------------------------------------------------------------------------
} // namespace maddy

52
maddy/imageparser.h Normal file
View File

@@ -0,0 +1,52 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <regex>
#include <string>
#include "maddy/lineparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* ImageParser
*
* Has to be used before the `LinkParser`.
*
* @class
*/
class ImageParser : public LineParser
{
public:
/**
* Parse
*
* From Markdown: `![text](http://example.com/a.png)`
*
* To HTML: `<img src="http://example.com/a.png" alt="text"/>`
*
* @method
* @param {std::string&} line The line to interpret
* @return {void}
*/
void Parse(std::string& line) override
{
static std::regex re(R"(\!\[([^\]]*)\]\(([^\]]*)\))");
static std::string replacement = "<img src=\"$2\" alt=\"$1\"/>";
line = std::regex_replace(line, re, replacement);
}
}; // class ImageParser
// -----------------------------------------------------------------------------
} // namespace maddy

50
maddy/inlinecodeparser.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <regex>
#include <string>
#include "maddy/lineparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* InlineCodeParser
*
* @class
*/
class InlineCodeParser : public LineParser
{
public:
/**
* Parse
*
* From Markdown: `text `some code``
*
* To HTML: `text <code>some code</code>`
*
* @method
* @param {std::string&} line The line to interpret
* @return {void}
*/
void Parse(std::string& line) override
{
static std::regex re("`([^`]*)`");
static std::string replacement = "<code>$1</code>";
line = std::regex_replace(line, re, replacement);
}
}; // class InlineCodeParser
// -----------------------------------------------------------------------------
} // namespace maddy

51
maddy/italicparser.h Normal file
View File

@@ -0,0 +1,51 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <regex>
#include <string>
#include "maddy/lineparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* ItalicParser
*
* @class
*/
class ItalicParser : public LineParser
{
public:
/**
* Parse
*
* From Markdown: `text *text*`
*
* To HTML: `text <i>text</i>`
*
* @method
* @param {std::string&} line The line to interpret
* @return {void}
*/
void Parse(std::string& line) override
{
static std::regex re(
R"((?!.*`.*|.*<code>.*)\*(?!.*`.*|.*<\/code>.*)([^\*]*)\*(?!.*`.*|.*<\/code>.*))"
);
static std::string replacement = "<i>$1</i>";
line = std::regex_replace(line, re, replacement);
}
}; // class ItalicParser
// -----------------------------------------------------------------------------
} // namespace maddy

124
maddy/latexblockparser.h Normal file
View File

@@ -0,0 +1,124 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <regex>
#include <string>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* LatexBlockParser
*
* Support for https://www.mathjax.org/
* Be aware, that if you want to make MathJax work, you need also their
* JavaScript library added to your HTML code.
* maddy does not itself add that code to be more flexible in how you write your
* head and full body.
*
* From Markdown: `$$` surrounded text
*
* ```
* $$some formula
* $$
* ```
*
* To HTML:
*
* ```
* $$some formula
* $$
* ```
*
* @class
*/
class LatexBlockParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string&
* line)>} getBlockParserForLineCallback
*/
LatexBlockParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)>
getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isStarted(false)
, isFinished(false)
{}
/**
* IsStartingLine
*
* If the line starts with two dollars, then it is a latex block.
*
* ```
* $$
* ```
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool IsStartingLine(const std::string& line)
{
static std::regex re(R"(^(?:\$){2}(.*)$)");
return std::regex_match(line, re);
}
/**
* IsFinished
*
* @method
* @return {bool}
*/
bool IsFinished() const override { return this->isFinished; }
protected:
bool isInlineBlockAllowed() const override { return false; }
bool isLineParserAllowed() const override { return false; }
void parseBlock(std::string& line) override
{
if (!this->isStarted && line.substr(0, 2) == "$$")
{
this->isStarted = true;
this->isFinished = false;
}
if (this->isStarted && !this->isFinished && line.size() > 1 &&
line.substr(line.size() - 2, 2) == "$$")
{
this->isFinished = true;
this->isStarted = false;
}
line += "\n";
}
private:
bool isStarted;
bool isFinished;
}; // class LatexBlockParser
// -----------------------------------------------------------------------------
} // namespace maddy

46
maddy/lineparser.h Normal file
View File

@@ -0,0 +1,46 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <string>
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* LineParser
*
* @class
*/
class LineParser
{
public:
/**
* dtor
*
* @method
*/
virtual ~LineParser() {}
/**
* Parse
*
* From Markdown to HTML
*
* @method
* @param {std::string&} line The line to interpret
* @return {void}
*/
virtual void Parse(std::string& line) = 0;
}; // class LineParser
// -----------------------------------------------------------------------------
} // namespace maddy

52
maddy/linkparser.h Normal file
View File

@@ -0,0 +1,52 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <regex>
#include <string>
#include "maddy/lineparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* LinkParser
*
* Has to be used after the `ImageParser`.
*
* @class
*/
class LinkParser : public LineParser
{
public:
/**
* Parse
*
* From Markdown: `[text](http://example.com)`
*
* To HTML: `<a href="http://example.com">text</a>`
*
* @method
* @param {std::string&} line The line to interpret
* @return {void}
*/
void Parse(std::string& line) override
{
static std::regex re(R"(\[([^\]]*)\]\(([^\]]*)\))");
static std::string replacement = "<a href=\"$2\">$1</a>";
line = std::regex_replace(line, re, replacement);
}
}; // class LinkParser
// -----------------------------------------------------------------------------
} // namespace maddy

126
maddy/orderedlistparser.h Normal file
View File

@@ -0,0 +1,126 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <regex>
#include <string>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* OrderedListParser
*
* @class
*/
class OrderedListParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string&
* line)>} getBlockParserForLineCallback
*/
OrderedListParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)>
getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isStarted(false)
, isFinished(false)
{}
/**
* IsStartingLine
*
* An ordered list starts with `1. `.
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool IsStartingLine(const std::string& line)
{
static std::regex re("^1\\. .*");
return std::regex_match(line, re);
}
/**
* IsFinished
*
* @method
* @return {bool}
*/
bool IsFinished() const override { return this->isFinished; }
protected:
bool isInlineBlockAllowed() const override { return true; }
bool isLineParserAllowed() const override { return true; }
void parseBlock(std::string& line) override
{
bool isStartOfNewListItem = this->isStartOfNewListItem(line);
uint32_t indentation = getIndentationWidth(line);
static std::regex orderedlineRegex(R"(^[1-9]+[0-9]*\. )");
line = std::regex_replace(line, orderedlineRegex, "");
static std::regex unorderedlineRegex(R"(^\* )");
line = std::regex_replace(line, unorderedlineRegex, "");
if (!this->isStarted)
{
line = "<ol><li>" + line;
this->isStarted = true;
return;
}
if (indentation >= 2)
{
line = line.substr(2);
return;
}
if (line.empty() || line.find("</li><li>") != std::string::npos ||
line.find("</li></ol>") != std::string::npos ||
line.find("</li></ul>") != std::string::npos)
{
line = "</li></ol>" + line;
this->isFinished = true;
return;
}
if (isStartOfNewListItem)
{
line = "</li><li>" + line;
}
}
private:
bool isStarted;
bool isFinished;
bool isStartOfNewListItem(const std::string& line) const
{
static std::regex re(R"(^(?:[1-9]+[0-9]*\. |\* ).*)");
return std::regex_match(line, re);
}
}; // class OrderedListParser
// -----------------------------------------------------------------------------
} // namespace maddy

115
maddy/paragraphparser.h Normal file
View File

@@ -0,0 +1,115 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <string>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* ParagraphParser
*
* @class
*/
class ParagraphParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string&
* line)>} getBlockParserForLineCallback
*/
ParagraphParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)>
getBlockParserForLineCallback,
bool isEnabled
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isStarted(false)
, isFinished(false)
, isEnabled(isEnabled)
{}
/**
* IsStartingLine
*
* If the line is not empty, it will be a paragraph.
*
* This block parser has to always run as the last one!
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool IsStartingLine(const std::string& line) { return !line.empty(); }
/**
* IsFinished
*
* An empty line will end the paragraph.
*
* @method
* @return {bool}
*/
bool IsFinished() const override { return this->isFinished; }
protected:
bool isInlineBlockAllowed() const override { return false; }
bool isLineParserAllowed() const override { return true; }
void parseBlock(std::string& line) override
{
if (this->isEnabled && !this->isStarted)
{
line = "<p>" + line + " ";
this->isStarted = true;
return;
}
else if (!this->isEnabled && !this->isStarted)
{
line += " ";
this->isStarted = true;
return;
}
if (this->isEnabled && line.empty())
{
line += "</p>";
this->isFinished = true;
return;
}
else if (!this->isEnabled && line.empty())
{
line += "<br/>";
this->isFinished = true;
return;
}
line += " ";
}
private:
bool isStarted;
bool isFinished;
bool isEnabled;
}; // class ParagraphParser
// -----------------------------------------------------------------------------
} // namespace maddy

412
maddy/parser.h Normal file
View File

@@ -0,0 +1,412 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <memory>
#include <string>
#include "maddy/parserconfig.h"
// BlockParser
#include "maddy/checklistparser.h"
#include "maddy/codeblockparser.h"
#include "maddy/headlineparser.h"
#include "maddy/horizontallineparser.h"
#include "maddy/htmlparser.h"
#include "maddy/latexblockparser.h"
#include "maddy/orderedlistparser.h"
#include "maddy/paragraphparser.h"
#include "maddy/quoteparser.h"
#include "maddy/tableparser.h"
#include "maddy/unorderedlistparser.h"
// LineParser
#include "maddy/breaklineparser.h"
#include "maddy/emphasizedparser.h"
#include "maddy/imageparser.h"
#include "maddy/inlinecodeparser.h"
#include "maddy/italicparser.h"
#include "maddy/linkparser.h"
#include "maddy/strikethroughparser.h"
#include "maddy/strongparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* Parser
*
* Transforms Markdown to HTML
*
* @class
*/
class Parser
{
public:
/**
* Version info
*
* Check https://github.com/progsource/maddy/blob/master/CHANGELOG.md
* for the changelog.
*/
static const std::string& version()
{
static const std::string v = "1.3.0";
return v;
}
/**
* ctor
*
* Initializes all `LineParser`
*
* @method
*/
Parser(std::shared_ptr<ParserConfig> config = nullptr) : config(config)
{
// deprecated backward compatibility
// will be removed in 1.4.0 latest including the booleans
if (this->config && !this->config->isEmphasizedParserEnabled)
{
this->config->enabledParsers &= ~maddy::types::EMPHASIZED_PARSER;
}
if (this->config && !this->config->isHTMLWrappedInParagraph)
{
this->config->enabledParsers |= maddy::types::HTML_PARSER;
}
if (!this->config ||
(this->config->enabledParsers & maddy::types::BREAKLINE_PARSER) != 0)
{
this->breakLineParser = std::make_shared<BreakLineParser>();
}
if (!this->config ||
(this->config->enabledParsers & maddy::types::EMPHASIZED_PARSER) != 0)
{
this->emphasizedParser = std::make_shared<EmphasizedParser>();
}
if (!this->config ||
(this->config->enabledParsers & maddy::types::IMAGE_PARSER) != 0)
{
this->imageParser = std::make_shared<ImageParser>();
}
if (!this->config ||
(this->config->enabledParsers & maddy::types::INLINE_CODE_PARSER) != 0)
{
this->inlineCodeParser = std::make_shared<InlineCodeParser>();
}
if (!this->config ||
(this->config->enabledParsers & maddy::types::ITALIC_PARSER) != 0)
{
this->italicParser = std::make_shared<ItalicParser>();
}
if (!this->config ||
(this->config->enabledParsers & maddy::types::LINK_PARSER) != 0)
{
this->linkParser = std::make_shared<LinkParser>();
}
if (!this->config || (this->config->enabledParsers &
maddy::types::STRIKETHROUGH_PARSER) != 0)
{
this->strikeThroughParser = std::make_shared<StrikeThroughParser>();
}
if (!this->config ||
(this->config->enabledParsers & maddy::types::STRONG_PARSER) != 0)
{
this->strongParser = std::make_shared<StrongParser>();
}
}
/**
* Parse
*
* @method
* @param {const std::istream&} markdown
* @return {std::string} HTML
*/
std::string Parse(std::istream& markdown) const
{
std::string result = "";
std::shared_ptr<BlockParser> currentBlockParser = nullptr;
for (std::string line; std::getline(markdown, line);)
{
if (!currentBlockParser)
{
currentBlockParser = getBlockParserForLine(line);
}
if (currentBlockParser)
{
currentBlockParser->AddLine(line);
if (currentBlockParser->IsFinished())
{
result += currentBlockParser->GetResult().str();
currentBlockParser = nullptr;
}
}
}
// make sure, that all parsers are finished
if (currentBlockParser)
{
std::string emptyLine = "";
currentBlockParser->AddLine(emptyLine);
if (currentBlockParser->IsFinished())
{
result += currentBlockParser->GetResult().str();
currentBlockParser = nullptr;
}
}
return result;
}
private:
std::shared_ptr<ParserConfig> config;
std::shared_ptr<BreakLineParser> breakLineParser;
std::shared_ptr<EmphasizedParser> emphasizedParser;
std::shared_ptr<ImageParser> imageParser;
std::shared_ptr<InlineCodeParser> inlineCodeParser;
std::shared_ptr<ItalicParser> italicParser;
std::shared_ptr<LinkParser> linkParser;
std::shared_ptr<StrikeThroughParser> strikeThroughParser;
std::shared_ptr<StrongParser> strongParser;
// block parser have to run before
void runLineParser(std::string& line) const
{
// Attention! ImageParser has to be before LinkParser
if (this->imageParser)
{
this->imageParser->Parse(line);
}
if (this->linkParser)
{
this->linkParser->Parse(line);
}
// Attention! StrongParser has to be before EmphasizedParser
if (this->strongParser)
{
this->strongParser->Parse(line);
}
if (this->emphasizedParser)
{
this->emphasizedParser->Parse(line);
}
if (this->strikeThroughParser)
{
this->strikeThroughParser->Parse(line);
}
if (this->inlineCodeParser)
{
this->inlineCodeParser->Parse(line);
}
if (this->italicParser)
{
this->italicParser->Parse(line);
}
if (this->breakLineParser)
{
this->breakLineParser->Parse(line);
}
}
std::shared_ptr<BlockParser> getBlockParserForLine(const std::string& line
) const
{
std::shared_ptr<BlockParser> parser;
if ((!this->config || (this->config->enabledParsers &
maddy::types::CODE_BLOCK_PARSER) != 0) &&
maddy::CodeBlockParser::IsStartingLine(line))
{
parser = std::make_shared<maddy::CodeBlockParser>(nullptr, nullptr);
}
else if (this->config &&
(this->config->enabledParsers & maddy::types::LATEX_BLOCK_PARSER
) != 0 &&
maddy::LatexBlockParser::IsStartingLine(line))
{
parser = std::make_shared<LatexBlockParser>(nullptr, nullptr);
}
else if ((!this->config || (this->config->enabledParsers &
maddy::types::HEADLINE_PARSER) != 0) &&
maddy::HeadlineParser::IsStartingLine(line))
{
if (!this->config || this->config->isHeadlineInlineParsingEnabled)
{
parser = std::make_shared<maddy::HeadlineParser>(
[this](std::string& line) { this->runLineParser(line); },
nullptr,
true
);
}
else
{
parser =
std::make_shared<maddy::HeadlineParser>(nullptr, nullptr, false);
}
}
else if ((!this->config || (this->config->enabledParsers &
maddy::types::HORIZONTAL_LINE_PARSER) != 0) &&
maddy::HorizontalLineParser::IsStartingLine(line))
{
parser = std::make_shared<maddy::HorizontalLineParser>(nullptr, nullptr);
}
else if ((!this->config || (this->config->enabledParsers &
maddy::types::QUOTE_PARSER) != 0) &&
maddy::QuoteParser::IsStartingLine(line))
{
parser = std::make_shared<maddy::QuoteParser>(
[this](std::string& line) { this->runLineParser(line); },
[this](const std::string& line)
{ return this->getBlockParserForLine(line); }
);
}
else if ((!this->config || (this->config->enabledParsers &
maddy::types::TABLE_PARSER) != 0) &&
maddy::TableParser::IsStartingLine(line))
{
parser = std::make_shared<maddy::TableParser>(
[this](std::string& line) { this->runLineParser(line); }, nullptr
);
}
else if ((!this->config || (this->config->enabledParsers &
maddy::types::CHECKLIST_PARSER) != 0) &&
maddy::ChecklistParser::IsStartingLine(line))
{
parser = this->createChecklistParser();
}
else if ((!this->config || (this->config->enabledParsers &
maddy::types::ORDERED_LIST_PARSER) != 0) &&
maddy::OrderedListParser::IsStartingLine(line))
{
parser = this->createOrderedListParser();
}
else if ((!this->config || (this->config->enabledParsers &
maddy::types::UNORDERED_LIST_PARSER) != 0) &&
maddy::UnorderedListParser::IsStartingLine(line))
{
parser = this->createUnorderedListParser();
}
else if (this->config &&
(this->config->enabledParsers & maddy::types::HTML_PARSER) != 0 &&
maddy::HtmlParser::IsStartingLine(line))
{
parser = std::make_shared<maddy::HtmlParser>(nullptr, nullptr);
}
else if (maddy::ParagraphParser::IsStartingLine(line))
{
parser = std::make_shared<maddy::ParagraphParser>(
[this](std::string& line) { this->runLineParser(line); },
nullptr,
(!this->config ||
(this->config->enabledParsers & maddy::types::PARAGRAPH_PARSER) != 0)
);
}
return parser;
}
std::shared_ptr<BlockParser> createChecklistParser() const
{
return std::make_shared<maddy::ChecklistParser>(
[this](std::string& line) { this->runLineParser(line); },
[this](const std::string& line)
{
std::shared_ptr<BlockParser> parser;
if ((!this->config || (this->config->enabledParsers &
maddy::types::CHECKLIST_PARSER) != 0) &&
maddy::ChecklistParser::IsStartingLine(line))
{
parser = this->createChecklistParser();
}
return parser;
}
);
}
std::shared_ptr<BlockParser> createOrderedListParser() const
{
return std::make_shared<maddy::OrderedListParser>(
[this](std::string& line) { this->runLineParser(line); },
[this](const std::string& line)
{
std::shared_ptr<BlockParser> parser;
if ((!this->config || (this->config->enabledParsers &
maddy::types::ORDERED_LIST_PARSER) != 0) &&
maddy::OrderedListParser::IsStartingLine(line))
{
parser = this->createOrderedListParser();
}
else if ((!this->config || (this->config->enabledParsers &
maddy::types::UNORDERED_LIST_PARSER) != 0
) &&
maddy::UnorderedListParser::IsStartingLine(line))
{
parser = this->createUnorderedListParser();
}
return parser;
}
);
}
std::shared_ptr<BlockParser> createUnorderedListParser() const
{
return std::make_shared<maddy::UnorderedListParser>(
[this](std::string& line) { this->runLineParser(line); },
[this](const std::string& line)
{
std::shared_ptr<BlockParser> parser;
if ((!this->config || (this->config->enabledParsers &
maddy::types::ORDERED_LIST_PARSER) != 0) &&
maddy::OrderedListParser::IsStartingLine(line))
{
parser = this->createOrderedListParser();
}
else if ((!this->config || (this->config->enabledParsers &
maddy::types::UNORDERED_LIST_PARSER) != 0
) &&
maddy::UnorderedListParser::IsStartingLine(line))
{
parser = this->createUnorderedListParser();
}
return parser;
}
);
}
}; // class Parser
// -----------------------------------------------------------------------------
} // namespace maddy

97
maddy/parserconfig.h Normal file
View File

@@ -0,0 +1,97 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
#include <stdint.h>
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
namespace types {
// clang-format off
/**
* PARSER_TYPE
*
* Bitwise flags to turn on/off each parser
*/
enum PARSER_TYPE : uint32_t
{
NONE = 0,
BREAKLINE_PARSER = 0b1,
CHECKLIST_PARSER = 0b10,
CODE_BLOCK_PARSER = 0b100,
EMPHASIZED_PARSER = 0b1000,
HEADLINE_PARSER = 0b10000,
HORIZONTAL_LINE_PARSER = 0b100000,
HTML_PARSER = 0b1000000,
IMAGE_PARSER = 0b10000000,
INLINE_CODE_PARSER = 0b100000000,
ITALIC_PARSER = 0b1000000000,
LINK_PARSER = 0b10000000000,
ORDERED_LIST_PARSER = 0b100000000000,
PARAGRAPH_PARSER = 0b1000000000000,
QUOTE_PARSER = 0b10000000000000,
STRIKETHROUGH_PARSER = 0b100000000000000,
STRONG_PARSER = 0b1000000000000000,
TABLE_PARSER = 0b10000000000000000,
UNORDERED_LIST_PARSER = 0b100000000000000000,
LATEX_BLOCK_PARSER = 0b1000000000000000000,
DEFAULT = 0b0111111111110111111,
ALL = 0b1111111111111111111,
};
// clang-format on
} // namespace types
/**
* ParserConfig
*
* @class
*/
struct ParserConfig
{
/**
* @deprecated will be removed in 1.4.0 latest
*
* this flag = false == `enabledParsers &= ~maddy::types::EMPHASIZED_PARSER`
*/
bool isEmphasizedParserEnabled;
/**
* @deprecated will be removed in 1.4.0 latest
*
* this flag = false == `enabledParsers |= maddy::types::HTML_PARSER`
*/
bool isHTMLWrappedInParagraph;
/**
* en-/disable headline inline-parsing
*
* default: enabled
*/
bool isHeadlineInlineParsingEnabled;
/**
* enabled parsers bitfield
*/
uint32_t enabledParsers;
ParserConfig()
: isEmphasizedParserEnabled(true)
, isHTMLWrappedInParagraph(true)
, isHeadlineInlineParsingEnabled(true)
, enabledParsers(maddy::types::DEFAULT)
{}
}; // class ParserConfig
// -----------------------------------------------------------------------------
} // namespace maddy

152
maddy/quoteparser.h Normal file
View File

@@ -0,0 +1,152 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <regex>
#include <string>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* QuoteParser
*
* @class
*/
class QuoteParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string&
* line)>} getBlockParserForLineCallback
*/
QuoteParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)>
getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isStarted(false)
, isFinished(false)
{}
/**
* IsStartingLine
*
* A quote starts with `> `.
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool IsStartingLine(const std::string& line)
{
static std::regex re(R"(^\>.*)");
return std::regex_match(line, re);
}
/**
* AddLine
*
* Adding a line which has to be parsed.
*
* @method
* @param {std::string&} line
* @return {void}
*/
void AddLine(std::string& line) override
{
if (!this->isStarted)
{
this->result << "<blockquote>";
this->isStarted = true;
}
bool finish = false;
if (line.empty())
{
finish = true;
}
this->parseBlock(line);
if (this->isInlineBlockAllowed() && !this->childParser)
{
this->childParser = this->getBlockParserForLine(line);
}
if (this->childParser)
{
this->childParser->AddLine(line);
if (this->childParser->IsFinished())
{
this->result << this->childParser->GetResult().str();
this->childParser = nullptr;
}
return;
}
if (this->isLineParserAllowed())
{
this->parseLine(line);
}
if (finish)
{
this->result << "</blockquote>";
this->isFinished = true;
}
this->result << line;
}
/**
* IsFinished
*
* @method
* @return {bool}
*/
bool IsFinished() const override { return this->isFinished; }
protected:
bool isInlineBlockAllowed() const override { return true; }
bool isLineParserAllowed() const override { return true; }
void parseBlock(std::string& line) override
{
static std::regex lineRegexWithSpace(R"(^\> )");
line = std::regex_replace(line, lineRegexWithSpace, "");
static std::regex lineRegexWithoutSpace(R"(^\>)");
line = std::regex_replace(line, lineRegexWithoutSpace, "");
if (!line.empty())
{
line += " ";
}
}
private:
bool isStarted;
bool isFinished;
}; // class QuoteParser
// -----------------------------------------------------------------------------
} // namespace maddy

View File

@@ -0,0 +1,52 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <regex>
#include <string>
#include "maddy/lineparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* StrikeThroughParser
*
* @class
*/
class StrikeThroughParser : public LineParser
{
public:
/**
* Parse
*
* From Markdown: `text ~~text~~`
*
* To HTML: `text <s>text</s>`
*
* @method
* @param {std::string&} line The line to interpret
* @return {void}
*/
void Parse(std::string& line) override
{
static std::regex re(
R"((?!.*`.*|.*<code>.*)\~\~(?!.*`.*|.*<\/code>.*)([^\~]*)\~\~(?!.*`.*|.*<\/code>.*))"
);
static std::string replacement = "<s>$1</s>";
line = std::regex_replace(line, re, replacement);
}
}; // class StrikeThroughParser
// -----------------------------------------------------------------------------
} // namespace maddy

61
maddy/strongparser.h Normal file
View File

@@ -0,0 +1,61 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <regex>
#include <string>
#include "maddy/lineparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* StrongParser
*
* Has to be used before the `EmphasizedParser`.
*
* @class
*/
class StrongParser : public LineParser
{
public:
/**
* Parse
*
* From Markdown: `text **text** __text__`
*
* To HTML: `text <strong>text</strong> <strong>text</strong>`
*
* @method
* @param {std::string&} line The line to interpret
* @return {void}
*/
void Parse(std::string& line) override
{
static std::vector<std::regex> res{
std::regex{
R"((?!.*`.*|.*<code>.*)\*\*(?!.*`.*|.*<\/code>.*)([^\*\*]*)\*\*(?!.*`.*|.*<\/code>.*))"
},
std::regex{
R"((?!.*`.*|.*<code>.*)__(?!.*`.*|.*<\/code>.*)([^__]*)__(?!.*`.*|.*<\/code>.*))"
}
};
static std::string replacement = "<strong>$1</strong>";
for (const auto& re : res)
{
line = std::regex_replace(line, re, replacement);
}
}
}; // class StrongParser
// -----------------------------------------------------------------------------
} // namespace maddy

233
maddy/tableparser.h Normal file
View File

@@ -0,0 +1,233 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <regex>
#include <string>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* TableParser
*
* For more information, see the docs folder.
*
* @class
*/
class TableParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string&
* line)>} getBlockParserForLineCallback
*/
TableParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)>
getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isStarted(false)
, isFinished(false)
, currentBlock(0)
, currentRow(0)
{}
/**
* IsStartingLine
*
* If the line has exact `|table>`, then it is starting the table.
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool IsStartingLine(const std::string& line)
{
static std::string matchString("|table>");
return line == matchString;
}
/**
* AddLine
*
* Adding a line which has to be parsed.
*
* @method
* @param {std::string&} line
* @return {void}
*/
void AddLine(std::string& line) override
{
if (!this->isStarted && line == "|table>")
{
this->isStarted = true;
return;
}
if (this->isStarted)
{
if (line == "- | - | -")
{
++this->currentBlock;
this->currentRow = 0;
return;
}
if (line == "|<table")
{
static std::string emptyLine = "";
this->parseBlock(emptyLine);
this->isFinished = true;
return;
}
if (this->table.size() < this->currentBlock + 1)
{
this->table.push_back(std::vector<std::vector<std::string>>());
}
this->table[this->currentBlock].push_back(std::vector<std::string>());
std::string segment;
std::stringstream streamToSplit(line);
while (std::getline(streamToSplit, segment, '|'))
{
this->parseLine(segment);
this->table[this->currentBlock][this->currentRow].push_back(segment);
}
++this->currentRow;
}
}
/**
* IsFinished
*
* A table ends with `|<table`.
*
* @method
* @return {bool}
*/
bool IsFinished() const override { return this->isFinished; }
protected:
bool isInlineBlockAllowed() const override { return false; }
bool isLineParserAllowed() const override { return true; }
void parseBlock(std::string&) override
{
result << "<table>";
bool hasHeader = false;
bool hasFooter = false;
bool isFirstBlock = true;
uint32_t currentBlockNumber = 0;
if (this->table.size() > 1)
{
hasHeader = true;
}
if (this->table.size() >= 3)
{
hasFooter = true;
}
for (const std::vector<std::vector<std::string>>& block : this->table)
{
bool isInHeader = false;
bool isInFooter = false;
++currentBlockNumber;
if (hasHeader && isFirstBlock)
{
result << "<thead>";
isInHeader = true;
}
else if (hasFooter && currentBlockNumber == this->table.size())
{
result << "<tfoot>";
isInFooter = true;
}
else
{
result << "<tbody>";
}
for (const std::vector<std::string>& row : block)
{
result << "<tr>";
for (const std::string& column : row)
{
if (isInHeader)
{
result << "<th>";
}
else
{
result << "<td>";
}
result << column;
if (isInHeader)
{
result << "</th>";
}
else
{
result << "</td>";
}
}
result << "</tr>";
}
if (isInHeader)
{
result << "</thead>";
}
else if (isInFooter)
{
result << "</tfoot>";
}
else
{
result << "</tbody>";
}
isFirstBlock = false;
}
result << "</table>";
}
private:
bool isStarted;
bool isFinished;
uint32_t currentBlock;
uint32_t currentRow;
std::vector<std::vector<std::vector<std::string>>> table;
}; // class TableParser
// -----------------------------------------------------------------------------
} // namespace maddy

118
maddy/unorderedlistparser.h Normal file
View File

@@ -0,0 +1,118 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <regex>
#include <string>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* UnorderedListParser
*
* @class
*/
class UnorderedListParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string&
* line)>} getBlockParserForLineCallback
*/
UnorderedListParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)>
getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isStarted(false)
, isFinished(false)
{}
/**
* IsStartingLine
*
* An unordered list starts with `* `.
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool IsStartingLine(const std::string& line)
{
static std::regex re("^[+*-] .*");
return std::regex_match(line, re);
}
/**
* IsFinished
*
* @method
* @return {bool}
*/
bool IsFinished() const override { return this->isFinished; }
protected:
bool isInlineBlockAllowed() const override { return true; }
bool isLineParserAllowed() const override { return true; }
void parseBlock(std::string& line) override
{
bool isStartOfNewListItem = IsStartingLine(line);
uint32_t indentation = getIndentationWidth(line);
static std::regex lineRegex("^([+*-] )");
line = std::regex_replace(line, lineRegex, "");
if (!this->isStarted)
{
line = "<ul><li>" + line;
this->isStarted = true;
return;
}
if (indentation >= 2)
{
line = line.substr(2);
return;
}
if (line.empty() || line.find("</li><li>") != std::string::npos ||
line.find("</li></ol>") != std::string::npos ||
line.find("</li></ul>") != std::string::npos)
{
line = "</li></ul>" + line;
this->isFinished = true;
return;
}
if (isStartOfNewListItem)
{
line = "</li><li>" + line;
}
}
private:
bool isStarted;
bool isFinished;
}; // class UnorderedListParser
// -----------------------------------------------------------------------------
} // namespace maddy

0
resources/logo.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

1106
resources/style.css Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -95,7 +95,7 @@
</rect> </rect>
</property> </property>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
</widget> </widget>
<widget class="QLabel" name="label_6"> <widget class="QLabel" name="label_6">
@@ -121,7 +121,7 @@
</rect> </rect>
</property> </property>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
</widget> </widget>
<widget class="QSpinBox" name="autoSaveDelaySpinbox"> <widget class="QSpinBox" name="autoSaveDelaySpinbox">
@@ -161,7 +161,7 @@
<string>Author: Aurélie Delhaie (github.com/mojitaurelie)</string> <string>Author: Aurélie Delhaie (github.com/mojitaurelie)</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> <set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
</property> </property>
</widget> </widget>
<widget class="QLabel" name="versionLabel"> <widget class="QLabel" name="versionLabel">
@@ -177,7 +177,7 @@
<string>Version 1.2.0.0</string> <string>Version 1.2.0.0</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> <set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
</property> </property>
</widget> </widget>
<widget class="QLabel" name="appNameLabel"> <widget class="QLabel" name="appNameLabel">
@@ -198,7 +198,7 @@
<string>WorkPad</string> <string>WorkPad</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignCenter</set> <set>Qt::AlignmentFlag::AlignCenter</set>
</property> </property>
</widget> </widget>
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
@@ -217,7 +217,7 @@
<string/> <string/>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignCenter</set> <set>Qt::AlignmentFlag::AlignCenter</set>
</property> </property>
</widget> </widget>
<widget class="QLabel" name="label_5"> <widget class="QLabel" name="label_5">
@@ -233,7 +233,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Under MIT license&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Under MIT license&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> <set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
</property> </property>
</widget> </widget>
<widget class="QPlainTextEdit" name="plainTextEdit"> <widget class="QPlainTextEdit" name="plainTextEdit">
@@ -249,7 +249,7 @@
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="plainText"> <property name="plainText">
<string>Copyright 2022 Aurélie Delhaie <string>Copyright 2025 Aurélie Delhaie
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the &quot;Software&quot;), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the &quot;Software&quot;), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
@@ -270,7 +270,7 @@ THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRES
</rect> </rect>
</property> </property>
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::Ok</set> <set>QDialogButtonBox::StandardButton::Ok</set>
</property> </property>
</widget> </widget>
</widget> </widget>

View File

@@ -19,5 +19,9 @@ int ExportDialog::getResult()
{ {
return MARKDOWN; return MARKDOWN;
} }
else if (ui->pdfRadio->isChecked())
{
return PDF;
}
return PLAIN; return PLAIN;
} }

View File

@@ -5,6 +5,7 @@
#define MARKDOWN 1 #define MARKDOWN 1
#define PLAIN 2 #define PLAIN 2
#define PDF 3
namespace Ui { namespace Ui {
class ExportDialog; class ExportDialog;

View File

@@ -35,10 +35,10 @@
</rect> </rect>
</property> </property>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> <set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property> </property>
</widget> </widget>
<widget class="QRadioButton" name="markdownButton"> <widget class="QRadioButton" name="markdownButton">
@@ -61,7 +61,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>30</x> <x>30</x>
<y>60</y> <y>40</y>
<width>92</width> <width>92</width>
<height>23</height> <height>23</height>
</rect> </rect>
@@ -70,6 +70,19 @@
<string>Text file</string> <string>Text file</string>
</property> </property>
</widget> </widget>
<widget class="QRadioButton" name="pdfRadio">
<property name="geometry">
<rect>
<x>30</x>
<y>60</y>
<width>92</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>PDF file</string>
</property>
</widget>
</widget> </widget>
<resources/> <resources/>
<connections> <connections>

127
src/frames/mainwindow.cpp Executable file → Normal file
View File

@@ -7,32 +7,71 @@
#include "renamedialog.h" #include "renamedialog.h"
#include "exportdialog.h" #include "exportdialog.h"
#include <chrono>
MainWindow::MainWindow(QWidget *parent) MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) : QMainWindow(parent)
, ui(new Ui::MainWindow) , ui(new Ui::MainWindow)
{ {
ui->setupUi(this); ui->setupUi(this);
timer = new QTimer(this); timer = new QTimer(this);
parser = std::make_shared<maddy::Parser>();
// open stylesheet resource
QFile f(":/css/resources/style.css");
f.open(QFile::OpenModeFlag::ReadOnly);
style = f.readAll();
f.close();
connect(ui->actionAdd_folder, &QAction::triggered, this, &MainWindow::createFolder); connect(ui->actionAdd_folder, &QAction::triggered, this, &MainWindow::createFolder);
connect(ui->actionAdd, &QAction::triggered, this, &MainWindow::createNote); connect(ui->actionAdd, &QAction::triggered, this, &MainWindow::createNote);
connect(ui->actionSave, &QAction::triggered, this, &MainWindow::save); connect(ui->actionSave, &QAction::triggered, this, &MainWindow::save);
connect(ui->actionSettings, &QAction::triggered, this, &MainWindow::showSettingsBox); connect(ui->actionSettings, &QAction::triggered, this, &MainWindow::showSettingsBox);
connect(ui->contentEdit, &QPlainTextEdit::textChanged, this, &MainWindow::markdownContentChanged);
connect(ui->plainTextEdit, &QPlainTextEdit::textChanged, this, &MainWindow::plainContentChanged); connect(ui->plainTextEdit, &QPlainTextEdit::textChanged, this, &MainWindow::plainContentChanged);
connect(ui->treeWidget, &QTreeWidget::itemSelectionChanged, this, &MainWindow::selectionChanged); connect(ui->treeWidget, &QTreeWidget::itemSelectionChanged, this, &MainWindow::selectionChanged);
connect(ui->treeWidget, &QTreeWidget::customContextMenuRequested, this, &MainWindow::prepareMenu); connect(ui->treeWidget, &QTreeWidget::customContextMenuRequested, this, &MainWindow::prepareMenu);
connect(this, &MainWindow::updateViewers, this, &MainWindow::viewReady);
const QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); const QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
ui->contentEdit->setFont(fixedFont);
ui->plainTextEdit->setFont(fixedFont); ui->plainTextEdit->setFont(fixedFont);
ui->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu); ui->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
ui->renderingProgressBar->setVisible(false);
this->savemng = new SaveManager(); this->savemng = new SaveManager();
this->cfgmng = new ConfigManager(); this->cfgmng = new ConfigManager();
updateListView(); updateListView();
connect(timer, &QTimer::timeout, this, &MainWindow::save); connect(timer, &QTimer::timeout, this, &MainWindow::save);
// start render thread
threaddone = new bool(false);
queue = new QVector<QString>;
queueMutex = new QMutex();
updateViewThread = new std::thread([this](){
while (!*threaddone) {
queueMutex->lock();
if (queue->count() == 0) {
queueMutex->unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
continue;
}
QString content = (*queue)[0];
queue->pop_front();
queueMutex->unlock();
std::stringstream markdownInput(content.toStdString());
QString htmlOutput = "<html><body class='markdown-body'>";
htmlOutput += QString::fromStdString(parser->Parse(markdownInput));
htmlOutput += "</body><style>" + style + "</style></html>";
emit updateViewers(htmlOutput);
}
});
} }
MainWindow::~MainWindow() MainWindow::~MainWindow()
{ {
*threaddone = true;
updateViewThread->join();
delete queue;
delete queueMutex;
delete threaddone;
delete updateViewThread;
delete timer; delete timer;
delete savemng; delete savemng;
delete ui; delete ui;
@@ -91,20 +130,14 @@ void MainWindow::selectionChanged()
return; return;
} }
ui->plainTextEdit->blockSignals(true); ui->plainTextEdit->blockSignals(true);
ui->contentEdit->blockSignals(true);
this->setWindowTitle(n->getTitle() + " - WorkPad"); this->setWindowTitle(n->getTitle() + " - WorkPad");
ui->titleLabel->setText(n->getTitle());
ui->plainTextEdit->setDisabled(false); ui->plainTextEdit->setDisabled(false);
ui->contentEdit->setDisabled(false);
ui->plainTextEdit->setPlainText(n->getContent()); ui->plainTextEdit->setPlainText(n->getContent());
ui->contentEdit->setPlainText(n->getContent()); updateHTMLView();
ui->markdownViewer->setMarkdown(ui->contentEdit->toPlainText());
ui->markdownViewer2->setMarkdown(n->getContent());
ui->plainTextEdit->blockSignals(false); ui->plainTextEdit->blockSignals(false);
ui->contentEdit->blockSignals(false);
} }
else else
{ {
@@ -307,6 +340,10 @@ void MainWindow::exportNote()
{ {
filter = "Markdown file (*.md)"; filter = "Markdown file (*.md)";
} }
else if (fileType == PDF)
{
filter = "PDF file (*.pdf)";
}
QString fileName = QFileDialog::getSaveFileName(this, tr("Export note"), "", filter); QString fileName = QFileDialog::getSaveFileName(this, tr("Export note"), "", filter);
if (!fileName.isEmpty()) if (!fileName.isEmpty())
{ {
@@ -318,44 +355,57 @@ void MainWindow::exportNote()
{ {
fileName += ".txt"; fileName += ".txt";
} }
QFile *f = new QFile(fileName); else if (fileType == PDF && !fileName.endsWith(".pdf", Qt::CaseInsensitive))
if (f->open(QIODevice::WriteOnly))
{ {
f->write(n->getContent().toUtf8()); fileName += ".pdf";
f->close();
} }
delete f;
if (fileType == PDF)
{
ui->webEngineViewer->printToPdf(fileName);
}
else
{
QFile *f = new QFile(fileName);
if (f->open(QIODevice::WriteOnly))
{
f->write(n->getContent().toUtf8());
f->close();
}
delete f;
}
} }
} }
} }
} }
} }
void MainWindow::viewReady(QString html)
{
if (queue->count() == 0)
{
ui->renderingProgressBar->setVisible(false);
}
ui->webEngineViewer->setHtml(html);
}
void MainWindow::markdownContentChanged() void MainWindow::markdownContentChanged()
{ {
timer->stop(); timer->stop();
ui->plainTextEdit->blockSignals(true); ui->plainTextEdit->blockSignals(true);
ui->contentEdit->blockSignals(true);
if (ui->treeWidget->selectedItems().length() == 1) if (ui->treeWidget->selectedItems().length() == 1)
{ {
QString uuid = ui->treeWidget->selectedItems()[0]->text(COLUMN_UUID); QString uuid = ui->treeWidget->selectedItems()[0]->text(COLUMN_UUID);
Note *n = savemng->getNoteByUUID(uuid); Note *n = savemng->getNoteByUUID(uuid);
if (n != nullptr) { if (n != nullptr) {
QScrollBar *scrollbar = ui->markdownViewer->verticalScrollBar(); QString content = ui->plainTextEdit->toPlainText();
QString content = ui->contentEdit->toPlainText();
int pos = scrollbar->sliderPosition();
ui->markdownViewer->setMarkdown(content);
scrollbar->setSliderPosition(pos);
ui->markdownViewer2->setMarkdown(content);
ui->plainTextEdit->setPlainText(content);
n->setContent(content); n->setContent(content);
updateHTMLView();
} }
} }
ui->plainTextEdit->blockSignals(false); ui->plainTextEdit->blockSignals(false);
ui->contentEdit->blockSignals(false);
ui->actionSave->setDisabled(false); ui->actionSave->setDisabled(false);
if (cfgmng->getConfiguration()->isEnableAutoSave()) if (cfgmng->getConfiguration()->isEnableAutoSave())
{ {
@@ -367,21 +417,17 @@ void MainWindow::plainContentChanged()
{ {
timer->stop(); timer->stop();
ui->plainTextEdit->blockSignals(true); ui->plainTextEdit->blockSignals(true);
ui->contentEdit->blockSignals(true);
if (ui->treeWidget->selectedItems().length() == 1) if (ui->treeWidget->selectedItems().length() == 1)
{ {
QString uuid = ui->treeWidget->selectedItems()[0]->text(COLUMN_UUID); QString uuid = ui->treeWidget->selectedItems()[0]->text(COLUMN_UUID);
Note *n = savemng->getNoteByUUID(uuid); Note *n = savemng->getNoteByUUID(uuid);
if (n != nullptr) { if (n != nullptr) {
QString content = ui->plainTextEdit->toPlainText(); QString content = ui->plainTextEdit->toPlainText();
ui->markdownViewer->setMarkdown(content);
ui->markdownViewer2->setMarkdown(content);
ui->contentEdit->setPlainText(content);
n->setContent(content); n->setContent(content);
updateHTMLView();
} }
} }
ui->plainTextEdit->blockSignals(false); ui->plainTextEdit->blockSignals(false);
ui->contentEdit->blockSignals(false);
ui->actionSave->setDisabled(false); ui->actionSave->setDisabled(false);
if (cfgmng->getConfiguration()->isEnableAutoSave()) if (cfgmng->getConfiguration()->isEnableAutoSave())
{ {
@@ -410,22 +456,25 @@ void MainWindow::updateListView()
} }
} }
void MainWindow::updateHTMLView()
{
queueMutex->lock();
ui->renderingProgressBar->setVisible(true);
if (queue->count() > 4) {
queue->pop_front();
}
queue->append(ui->plainTextEdit->toPlainText());
queueMutex->unlock();
}
void MainWindow::clearAndDisableFields() void MainWindow::clearAndDisableFields()
{ {
ui->contentEdit->blockSignals(true);
ui->plainTextEdit->blockSignals(true); ui->plainTextEdit->blockSignals(true);
ui->contentEdit->setDisabled(true);
ui->plainTextEdit->setDisabled(true); ui->plainTextEdit->setDisabled(true);
ui->plainTextEdit->clear(); ui->plainTextEdit->clear();
ui->contentEdit->clear();
ui->markdownViewer->clear();
ui->markdownViewer2->clear();
ui->titleLabel->setText("");
this->setWindowTitle("WorkPad"); this->setWindowTitle("WorkPad");
ui->contentEdit->blockSignals(false);
ui->plainTextEdit->blockSignals(false); ui->plainTextEdit->blockSignals(false);
} }

19
src/frames/mainwindow.h Executable file → Normal file
View File

@@ -10,10 +10,15 @@
#include <QPoint> #include <QPoint>
#include <QFileDialog> #include <QFileDialog>
#include <QScrollBar> #include <QScrollBar>
#include <QMutex>
#include <thread>
#include "../services/savemanager.h" #include "../services/savemanager.h"
#include "../services/configmanager.h" #include "../services/configmanager.h"
#include "../../maddy/parserconfig.h"
#include "../../maddy/parser.h"
#define COLUMN_NAME 0 #define COLUMN_NAME 0
#define COLUMN_UUID 1 #define COLUMN_UUID 1
#define COLUMN_TYPE 2 #define COLUMN_TYPE 2
@@ -33,6 +38,9 @@ public:
MainWindow(QWidget *parent = nullptr); MainWindow(QWidget *parent = nullptr);
~MainWindow(); ~MainWindow();
signals:
void updateViewers(QString html);
private slots: private slots:
void createNote(); void createNote();
void createFolder(); void createFolder();
@@ -46,15 +54,26 @@ private slots:
void moveNote(); void moveNote();
void editName(); void editName();
void exportNote(); void exportNote();
void viewReady(QString html);
private: private:
Ui::MainWindow *ui; Ui::MainWindow *ui;
SaveManager *savemng; SaveManager *savemng;
ConfigManager *cfgmng; ConfigManager *cfgmng;
QTimer *timer; QTimer *timer;
std::shared_ptr<maddy::Parser> parser;
QString style;
//shared between threads
QVector<QString>* queue;
QMutex* queueMutex;
bool* threaddone;
std::thread *updateViewThread;
void updateListView(); void updateListView();
void clearAndDisableFields(); void clearAndDisableFields();
void selectTreeItem(QString uuid); void selectTreeItem(QString uuid);
void updateHTMLView();
}; };
#endif // MAINWINDOW_H #endif // MAINWINDOW_H

257
src/frames/mainwindow.ui Executable file → Normal file
View File

@@ -25,156 +25,11 @@
<widget class="QWidget" name="centralwidget"> <widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <widget class="QPlainTextEdit" name="plainTextEdit">
<item> <property name="enabled">
<widget class="QTreeWidget" name="treeWidget"> <bool>false</bool>
<property name="maximumSize"> </property>
<size> </widget>
<width>250</width>
<height>16777215</height>
</size>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="animated">
<bool>true</bool>
</property>
<column>
<property name="text">
<string notr="true">Workspace</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="titleLabel">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="tabPosition">
<enum>QTabWidget::South</enum>
</property>
<property name="tabShape">
<enum>QTabWidget::Rounded</enum>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_3">
<attribute name="title">
<string>Plain text</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="0">
<widget class="QPlainTextEdit" name="plainTextEdit">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Markdown editor</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QPlainTextEdit" name="contentEdit">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<kerning>false</kerning>
</font>
</property>
<property name="lineWrapMode">
<enum>QPlainTextEdit::NoWrap</enum>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="markdownViewer">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="autoFormatting">
<set>QTextEdit::AutoAll</set>
</property>
<property name="lineWrapMode">
<enum>QTextEdit::NoWrap</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="acceptRichText">
<bool>false</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Markdown viewer</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QTextEdit" name="markdownViewer2">
<property name="lineWrapMode">
<enum>QTextEdit::NoWrap</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</item> </item>
</layout> </layout>
</widget> </widget>
@@ -189,7 +44,7 @@
</size> </size>
</property> </property>
<property name="contextMenuPolicy"> <property name="contextMenuPolicy">
<enum>Qt::PreventContextMenu</enum> <enum>Qt::ContextMenuPolicy::PreventContextMenu</enum>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>toolBar</string> <string>toolBar</string>
@@ -198,7 +53,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="toolButtonStyle"> <property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum> <enum>Qt::ToolButtonStyle::ToolButtonTextUnderIcon</enum>
</property> </property>
<property name="floatable"> <property name="floatable">
<bool>false</bool> <bool>false</bool>
@@ -216,6 +71,96 @@
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionSettings"/> <addaction name="actionSettings"/>
</widget> </widget>
<widget class="QDockWidget" name="dockWidget_2">
<property name="minimumSize">
<size>
<width>500</width>
<height>84</height>
</size>
</property>
<property name="features">
<set>QDockWidget::DockWidgetFeature::DockWidgetFloatable|QDockWidget::DockWidgetFeature::DockWidgetMovable</set>
</property>
<attribute name="dockWidgetArea">
<number>2</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_2">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QWebEngineView" name="webEngineViewer" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QProgressBar" name="renderingProgressBar">
<property name="maximumSize">
<size>
<width>160</width>
<height>16777215</height>
</size>
</property>
<property name="maximum">
<number>0</number>
</property>
<property name="value">
<number>0</number>
</property>
<property name="invertedAppearance">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="dockWidget_3">
<property name="minimumSize">
<size>
<width>250</width>
<height>120</height>
</size>
</property>
<property name="features">
<set>QDockWidget::DockWidgetFeature::NoDockWidgetFeatures</set>
</property>
<attribute name="dockWidgetArea">
<number>1</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_3">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTreeWidget" name="treeWidget">
<property name="maximumSize">
<size>
<width>250</width>
<height>16777215</height>
</size>
</property>
<property name="editTriggers">
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
</property>
<property name="animated">
<bool>true</bool>
</property>
<column>
<property name="text">
<string notr="true">Workspace</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
<action name="actionAdd"> <action name="actionAdd">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
@@ -277,6 +222,14 @@
</property> </property>
</action> </action>
</widget> </widget>
<customwidgets>
<customwidget>
<class>QWebEngineView</class>
<extends>QWidget</extends>
<header>qwebengineview.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources> <resources>
<include location="../../icons.qrc"/> <include location="../../icons.qrc"/>
</resources> </resources>

0
src/main.cpp Executable file → Normal file
View File

0
src/models/note.cpp Executable file → Normal file
View File

0
src/models/note.h Executable file → Normal file
View File

0
src/services/savemanager.cpp Executable file → Normal file
View File

0
src/services/savemanager.h Executable file → Normal file
View File

5
static.qrc Normal file
View File

@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/css">
<file>resources/style.css</file>
</qresource>
</RCC>