diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..4567c3b Binary files /dev/null and b/.DS_Store differ diff --git a/Info.plist b/Info.plist old mode 100755 new mode 100644 diff --git a/WorkPad.pro b/WorkPad.pro old mode 100755 new mode 100644 index 337ead2..ca05650 --- a/WorkPad.pro +++ b/WorkPad.pro @@ -1,11 +1,11 @@ QT += core gui -greaterThan(QT_MAJOR_VERSION, 5): QT += widgets +greaterThan(QT_MAJOR_VERSION, 5): QT += widgets webenginewidgets CONFIG += c++17 -win32:VERSION = 2.1.0.0 # major.minor.patch.build -else:VERSION = 2.1.0 # major.minor.patch +win32:VERSION = 3.0.0.0 # major.minor.patch.build +else:VERSION = 3.0.0 # major.minor.patch DEFINES += APP_VERSION=\"\\\"$${VERSION}\\\"\" DEFINES += APP_NAME=\"\\\"WorkPad\\\"\" @@ -83,6 +83,8 @@ SOURCES += \ src/services/savemanager.cpp HEADERS += \ + maddy/parserconfig.h \ + maddy/parser.h \ src/frames/configdialog.h \ src/services/configmanager.h \ src/frames/exportdialog.h \ @@ -109,4 +111,5 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target RESOURCES += \ - icons.qrc + icons.qrc \ + static.qrc diff --git a/icon.icns b/icon.icns old mode 100755 new mode 100644 diff --git a/icon.ico b/icon.ico old mode 100755 new mode 100644 diff --git a/icons.qrc b/icons.qrc old mode 100755 new mode 100644 diff --git a/maddy/blockparser.h b/maddy/blockparser.h new file mode 100644 index 0000000..f93dd2c --- /dev/null +++ b/maddy/blockparser.h @@ -0,0 +1,192 @@ +/* + * This project is licensed under the MIT license. For more information see the + * LICENSE file. + */ +#pragma once + +// ----------------------------------------------------------------------------- + +#include +#include +#include +// windows compatibility includes +#include +#include + +// ----------------------------------------------------------------------------- + +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} parseLineCallback + * @param {std::function(const std::string& + * line)>} getBlockParserForLineCallback + */ + BlockParser( + std::function parseLineCallback, + std::function(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 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(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 getBlockParserForLine(const std::string& line) + { + if (getBlockParserForLineCallback) + { + return getBlockParserForLineCallback(line); + } + + return nullptr; + } + +private: + std::function parseLineCallback; + std::function(const std::string& line)> + getBlockParserForLineCallback; +}; // class BlockParser + +// ----------------------------------------------------------------------------- + +} // namespace maddy diff --git a/maddy/breaklineparser.h b/maddy/breaklineparser.h new file mode 100644 index 0000000..e60dda2 --- /dev/null +++ b/maddy/breaklineparser.h @@ -0,0 +1,50 @@ +/* + * This project is licensed under the MIT license. For more information see the + * LICENSE file. + */ +#pragma once + +// ----------------------------------------------------------------------------- + +#include +#include + +#include "maddy/lineparser.h" + +// ----------------------------------------------------------------------------- + +namespace maddy { + +// ----------------------------------------------------------------------------- + +/** + * BreakLineParser + * + * @class + */ +class BreakLineParser : public LineParser +{ +public: + /** + * Parse + * + * From Markdown: `text\r\n text` + * + * To HTML: `text
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 = "
"; + + line = std::regex_replace(line, re, replacement); + } +}; // class BreakLineParser + +// ----------------------------------------------------------------------------- + +} // namespace maddy diff --git a/maddy/checklistparser.h b/maddy/checklistparser.h new file mode 100644 index 0000000..36a154b --- /dev/null +++ b/maddy/checklistparser.h @@ -0,0 +1,127 @@ +/* + * This project is licensed under the MIT license. For more information see the + * LICENSE file. + */ +#pragma once + +// ----------------------------------------------------------------------------- + +#include +#include +#include + +#include "maddy/blockparser.h" + +// ----------------------------------------------------------------------------- + +namespace maddy { + +// ----------------------------------------------------------------------------- + +/** + * ChecklistParser + * + * @class + */ +class ChecklistParser : public BlockParser +{ +public: + /** + * ctor + * + * @method + * @param {std::function} parseLineCallback + * @param {std::function(const std::string& + * line)>} getBlockParserForLineCallback + */ + ChecklistParser( + std::function parseLineCallback, + std::function(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 = ""; + line = std::regex_replace(line, emptyBoxRegex, emptyBoxReplacement); + + static std::regex boxRegex(R"(^\[x\])"); + static std::string boxReplacement = + ""; + line = std::regex_replace(line, boxRegex, boxReplacement); + + if (!this->isStarted) + { + line = "
") != std::string::npos) + { + line = "" + line; + this->isFinished = true; + return; + } + + if (isStartOfNewListItem) + { + line = "
  • ") != std::string::npos) + { + line = "" + line; + this->isFinished = true; + return; + } + + if (isStartOfNewListItem) + { + line = "
  • " + 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 diff --git a/maddy/paragraphparser.h b/maddy/paragraphparser.h new file mode 100644 index 0000000..4c477b8 --- /dev/null +++ b/maddy/paragraphparser.h @@ -0,0 +1,115 @@ +/* + * This project is licensed under the MIT license. For more information see the + * LICENSE file. + */ +#pragma once + +// ----------------------------------------------------------------------------- + +#include +#include + +#include "maddy/blockparser.h" + +// ----------------------------------------------------------------------------- + +namespace maddy { + +// ----------------------------------------------------------------------------- + +/** + * ParagraphParser + * + * @class + */ +class ParagraphParser : public BlockParser +{ +public: + /** + * ctor + * + * @method + * @param {std::function} parseLineCallback + * @param {std::function(const std::string& + * line)>} getBlockParserForLineCallback + */ + ParagraphParser( + std::function parseLineCallback, + std::function(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 = "

    " + line + " "; + this->isStarted = true; + return; + } + else if (!this->isEnabled && !this->isStarted) + { + line += " "; + this->isStarted = true; + return; + } + + if (this->isEnabled && line.empty()) + { + line += "

    "; + this->isFinished = true; + return; + } + else if (!this->isEnabled && line.empty()) + { + line += "
    "; + this->isFinished = true; + return; + } + + line += " "; + } + +private: + bool isStarted; + bool isFinished; + bool isEnabled; +}; // class ParagraphParser + +// ----------------------------------------------------------------------------- + +} // namespace maddy diff --git a/maddy/parser.h b/maddy/parser.h new file mode 100644 index 0000000..9f4a4cb --- /dev/null +++ b/maddy/parser.h @@ -0,0 +1,412 @@ +/* + * This project is licensed under the MIT license. For more information see the + * LICENSE file. + */ +#pragma once + +// ----------------------------------------------------------------------------- + +#include +#include +#include + +#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 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(); + } + + if (!this->config || + (this->config->enabledParsers & maddy::types::EMPHASIZED_PARSER) != 0) + { + this->emphasizedParser = std::make_shared(); + } + + if (!this->config || + (this->config->enabledParsers & maddy::types::IMAGE_PARSER) != 0) + { + this->imageParser = std::make_shared(); + } + + if (!this->config || + (this->config->enabledParsers & maddy::types::INLINE_CODE_PARSER) != 0) + { + this->inlineCodeParser = std::make_shared(); + } + + if (!this->config || + (this->config->enabledParsers & maddy::types::ITALIC_PARSER) != 0) + { + this->italicParser = std::make_shared(); + } + + if (!this->config || + (this->config->enabledParsers & maddy::types::LINK_PARSER) != 0) + { + this->linkParser = std::make_shared(); + } + + if (!this->config || (this->config->enabledParsers & + maddy::types::STRIKETHROUGH_PARSER) != 0) + { + this->strikeThroughParser = std::make_shared(); + } + + if (!this->config || + (this->config->enabledParsers & maddy::types::STRONG_PARSER) != 0) + { + this->strongParser = std::make_shared(); + } + } + + /** + * Parse + * + * @method + * @param {const std::istream&} markdown + * @return {std::string} HTML + */ + std::string Parse(std::istream& markdown) const + { + std::string result = ""; + std::shared_ptr 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 config; + std::shared_ptr breakLineParser; + std::shared_ptr emphasizedParser; + std::shared_ptr imageParser; + std::shared_ptr inlineCodeParser; + std::shared_ptr italicParser; + std::shared_ptr linkParser; + std::shared_ptr strikeThroughParser; + std::shared_ptr 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 getBlockParserForLine(const std::string& line + ) const + { + std::shared_ptr parser; + + if ((!this->config || (this->config->enabledParsers & + maddy::types::CODE_BLOCK_PARSER) != 0) && + maddy::CodeBlockParser::IsStartingLine(line)) + { + parser = std::make_shared(nullptr, nullptr); + } + else if (this->config && + (this->config->enabledParsers & maddy::types::LATEX_BLOCK_PARSER + ) != 0 && + maddy::LatexBlockParser::IsStartingLine(line)) + { + parser = std::make_shared(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( + [this](std::string& line) { this->runLineParser(line); }, + nullptr, + true + ); + } + else + { + parser = + std::make_shared(nullptr, nullptr, false); + } + } + else if ((!this->config || (this->config->enabledParsers & + maddy::types::HORIZONTAL_LINE_PARSER) != 0) && + maddy::HorizontalLineParser::IsStartingLine(line)) + { + parser = std::make_shared(nullptr, nullptr); + } + else if ((!this->config || (this->config->enabledParsers & + maddy::types::QUOTE_PARSER) != 0) && + maddy::QuoteParser::IsStartingLine(line)) + { + parser = std::make_shared( + [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( + [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(nullptr, nullptr); + } + else if (maddy::ParagraphParser::IsStartingLine(line)) + { + parser = std::make_shared( + [this](std::string& line) { this->runLineParser(line); }, + nullptr, + (!this->config || + (this->config->enabledParsers & maddy::types::PARAGRAPH_PARSER) != 0) + ); + } + + return parser; + } + + std::shared_ptr createChecklistParser() const + { + return std::make_shared( + [this](std::string& line) { this->runLineParser(line); }, + [this](const std::string& line) + { + std::shared_ptr parser; + + if ((!this->config || (this->config->enabledParsers & + maddy::types::CHECKLIST_PARSER) != 0) && + maddy::ChecklistParser::IsStartingLine(line)) + { + parser = this->createChecklistParser(); + } + + return parser; + } + ); + } + + std::shared_ptr createOrderedListParser() const + { + return std::make_shared( + [this](std::string& line) { this->runLineParser(line); }, + [this](const std::string& line) + { + std::shared_ptr 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 createUnorderedListParser() const + { + return std::make_shared( + [this](std::string& line) { this->runLineParser(line); }, + [this](const std::string& line) + { + std::shared_ptr 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 diff --git a/maddy/parserconfig.h b/maddy/parserconfig.h new file mode 100644 index 0000000..04acdc7 --- /dev/null +++ b/maddy/parserconfig.h @@ -0,0 +1,97 @@ +/* + * This project is licensed under the MIT license. For more information see the + * LICENSE file. + */ +#pragma once + +#include + +// ----------------------------------------------------------------------------- + +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 diff --git a/maddy/quoteparser.h b/maddy/quoteparser.h new file mode 100644 index 0000000..13151ef --- /dev/null +++ b/maddy/quoteparser.h @@ -0,0 +1,152 @@ +/* + * This project is licensed under the MIT license. For more information see the + * LICENSE file. + */ +#pragma once + +// ----------------------------------------------------------------------------- + +#include +#include +#include + +#include "maddy/blockparser.h" + +// ----------------------------------------------------------------------------- + +namespace maddy { + +// ----------------------------------------------------------------------------- + +/** + * QuoteParser + * + * @class + */ +class QuoteParser : public BlockParser +{ +public: + /** + * ctor + * + * @method + * @param {std::function} parseLineCallback + * @param {std::function(const std::string& + * line)>} getBlockParserForLineCallback + */ + QuoteParser( + std::function parseLineCallback, + std::function(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 << "
    "; + 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 << "
    "; + 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 diff --git a/maddy/strikethroughparser.h b/maddy/strikethroughparser.h new file mode 100644 index 0000000..effa5d8 --- /dev/null +++ b/maddy/strikethroughparser.h @@ -0,0 +1,52 @@ +/* + * This project is licensed under the MIT license. For more information see the + * LICENSE file. + */ +#pragma once + +// ----------------------------------------------------------------------------- + +#include +#include + +#include "maddy/lineparser.h" + +// ----------------------------------------------------------------------------- + +namespace maddy { + +// ----------------------------------------------------------------------------- + +/** + * StrikeThroughParser + * + * @class + */ +class StrikeThroughParser : public LineParser +{ +public: + /** + * Parse + * + * From Markdown: `text ~~text~~` + * + * To HTML: `text text` + * + * @method + * @param {std::string&} line The line to interpret + * @return {void} + */ + void Parse(std::string& line) override + { + static std::regex re( + R"((?!.*`.*|.*.*)\~\~(?!.*`.*|.*<\/code>.*)([^\~]*)\~\~(?!.*`.*|.*<\/code>.*))" + ); + static std::string replacement = "$1"; + + line = std::regex_replace(line, re, replacement); + } +}; // class StrikeThroughParser + +// ----------------------------------------------------------------------------- + +} // namespace maddy diff --git a/maddy/strongparser.h b/maddy/strongparser.h new file mode 100644 index 0000000..348f2d4 --- /dev/null +++ b/maddy/strongparser.h @@ -0,0 +1,61 @@ +/* + * This project is licensed under the MIT license. For more information see the + * LICENSE file. + */ +#pragma once + +// ----------------------------------------------------------------------------- + +#include +#include + +#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 text text` + * + * @method + * @param {std::string&} line The line to interpret + * @return {void} + */ + void Parse(std::string& line) override + { + static std::vector res{ + std::regex{ + R"((?!.*`.*|.*.*)\*\*(?!.*`.*|.*<\/code>.*)([^\*\*]*)\*\*(?!.*`.*|.*<\/code>.*))" + }, + std::regex{ + R"((?!.*`.*|.*.*)__(?!.*`.*|.*<\/code>.*)([^__]*)__(?!.*`.*|.*<\/code>.*))" + } + }; + static std::string replacement = "$1"; + for (const auto& re : res) + { + line = std::regex_replace(line, re, replacement); + } + } +}; // class StrongParser + +// ----------------------------------------------------------------------------- + +} // namespace maddy diff --git a/maddy/tableparser.h b/maddy/tableparser.h new file mode 100644 index 0000000..9f1051f --- /dev/null +++ b/maddy/tableparser.h @@ -0,0 +1,233 @@ +/* + * This project is licensed under the MIT license. For more information see the + * LICENSE file. + */ +#pragma once + +// ----------------------------------------------------------------------------- + +#include +#include +#include + +#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} parseLineCallback + * @param {std::function(const std::string& + * line)>} getBlockParserForLineCallback + */ + TableParser( + std::function parseLineCallback, + std::function(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 == "|parseBlock(emptyLine); + this->isFinished = true; + return; + } + + if (this->table.size() < this->currentBlock + 1) + { + this->table.push_back(std::vector>()); + } + this->table[this->currentBlock].push_back(std::vector()); + + 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 `|isFinished; } + +protected: + bool isInlineBlockAllowed() const override { return false; } + + bool isLineParserAllowed() const override { return true; } + + void parseBlock(std::string&) override + { + result << ""; + + 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>& block : this->table) + { + bool isInHeader = false; + bool isInFooter = false; + ++currentBlockNumber; + + if (hasHeader && isFirstBlock) + { + result << ""; + isInHeader = true; + } + else if (hasFooter && currentBlockNumber == this->table.size()) + { + result << ""; + isInFooter = true; + } + else + { + result << ""; + } + + for (const std::vector& row : block) + { + result << ""; + + for (const std::string& column : row) + { + if (isInHeader) + { + result << ""; + } + } + + result << ""; + } + + if (isInHeader) + { + result << ""; + } + else if (isInFooter) + { + result << ""; + } + else + { + result << ""; + } + + isFirstBlock = false; + } + + result << "
    "; + } + else + { + result << ""; + } + + result << column; + + if (isInHeader) + { + result << ""; + } + else + { + result << "
    "; + } + +private: + bool isStarted; + bool isFinished; + uint32_t currentBlock; + uint32_t currentRow; + std::vector>> table; +}; // class TableParser + +// ----------------------------------------------------------------------------- + +} // namespace maddy diff --git a/maddy/unorderedlistparser.h b/maddy/unorderedlistparser.h new file mode 100644 index 0000000..53859bf --- /dev/null +++ b/maddy/unorderedlistparser.h @@ -0,0 +1,118 @@ +/* + * This project is licensed under the MIT license. For more information see the + * LICENSE file. + */ +#pragma once + +// ----------------------------------------------------------------------------- + +#include +#include +#include + +#include "maddy/blockparser.h" + +// ----------------------------------------------------------------------------- + +namespace maddy { + +// ----------------------------------------------------------------------------- + +/** + * UnorderedListParser + * + * @class + */ +class UnorderedListParser : public BlockParser +{ +public: + /** + * ctor + * + * @method + * @param {std::function} parseLineCallback + * @param {std::function(const std::string& + * line)>} getBlockParserForLineCallback + */ + UnorderedListParser( + std::function parseLineCallback, + std::function(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 = "
    • " + line; + this->isStarted = true; + return; + } + + if (indentation >= 2) + { + line = line.substr(2); + return; + } + + if (line.empty() || line.find("
    • ") != std::string::npos || + line.find("
    • ") != std::string::npos || + line.find("
    ") != std::string::npos) + { + line = "
  • " + line; + this->isFinished = true; + return; + } + + if (isStartOfNewListItem) + { + line = "
  • " + line; + } + } + +private: + bool isStarted; + bool isFinished; +}; // class UnorderedListParser + +// ----------------------------------------------------------------------------- + +} // namespace maddy diff --git a/resources/logo.png b/resources/logo.png old mode 100755 new mode 100644 diff --git a/resources/style.css b/resources/style.css new file mode 100644 index 0000000..f1f9803 --- /dev/null +++ b/resources/style.css @@ -0,0 +1,1106 @@ +/* light */ +.markdown-body { + color-scheme: light; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + margin: 0; + color: #1f2328; + background-color: #ffffff; + font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; + margin: 2rem; +} + +.markdown-body .octicon { + display: inline-block; + fill: currentColor; + vertical-align: text-bottom; +} + +.markdown-body h1:hover .anchor .octicon-link:before, +.markdown-body h2:hover .anchor .octicon-link:before, +.markdown-body h3:hover .anchor .octicon-link:before, +.markdown-body h4:hover .anchor .octicon-link:before, +.markdown-body h5:hover .anchor .octicon-link:before, +.markdown-body h6:hover .anchor .octicon-link:before { + width: 16px; + height: 16px; + content: ' '; + display: inline-block; + background-color: currentColor; + -webkit-mask-image: url("data:image/svg+xml,"); + mask-image: url("data:image/svg+xml,"); +} + +.markdown-body details, +.markdown-body figcaption, +.markdown-body figure { + display: block; +} + +.markdown-body summary { + display: list-item; +} + +.markdown-body [hidden] { + display: none !important; +} + +.markdown-body a { + background-color: transparent; + color: #0969da; + text-decoration: none; +} + +.markdown-body abbr[title] { + border-bottom: none; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +.markdown-body b, +.markdown-body strong { + font-weight: 600; +} + +.markdown-body dfn { + font-style: italic; +} + +.markdown-body h1 { + margin: .67em 0; + font-weight: 600; + padding-bottom: .3em; + font-size: 2em; + border-bottom: 1px solid #d1d9e0b3; +} + +.markdown-body mark { + background-color: #fff8c5; + color: #1f2328; +} + +.markdown-body small { + font-size: 90%; +} + +.markdown-body sub, +.markdown-body sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +.markdown-body sub { + bottom: -0.25em; +} + +.markdown-body sup { + top: -0.5em; +} + +.markdown-body img { + border-style: none; + max-width: 100%; + box-sizing: content-box; +} + +.markdown-body code, +.markdown-body kbd, +.markdown-body pre, +.markdown-body samp { + font-family: monospace; + font-size: 1em; +} + +.markdown-body figure { + margin: 1em 2.5rem; +} + +.markdown-body hr { + box-sizing: content-box; + overflow: hidden; + background: transparent; + border-bottom: 1px solid #d1d9e0b3; + height: .25em; + padding: 0; + margin: 1.5rem 0; + background-color: #d1d9e0; + border: 0; +} + +.markdown-body input { + font: inherit; + margin: 0; + overflow: visible; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +.markdown-body [type=button], +.markdown-body [type=reset], +.markdown-body [type=submit] { + -webkit-appearance: button; + appearance: button; +} + +.markdown-body [type=checkbox], +.markdown-body [type=radio] { + box-sizing: border-box; + padding: 0; +} + +.markdown-body [type=number]::-webkit-inner-spin-button, +.markdown-body [type=number]::-webkit-outer-spin-button { + height: auto; +} + +.markdown-body [type=search]::-webkit-search-cancel-button, +.markdown-body [type=search]::-webkit-search-decoration { + -webkit-appearance: none; + appearance: none; +} + +.markdown-body ::-webkit-input-placeholder { + color: inherit; + opacity: .54; +} + +.markdown-body ::-webkit-file-upload-button { + -webkit-appearance: button; + appearance: button; + font: inherit; +} + +.markdown-body a:hover { + text-decoration: underline; +} + +.markdown-body ::placeholder { + color: #59636e; + opacity: 1; +} + +.markdown-body hr::before { + display: table; + content: ""; +} + +.markdown-body hr::after { + display: table; + clear: both; + content: ""; +} + +.markdown-body table { + border-spacing: 0; + border-collapse: collapse; + display: block; + width: max-content; + max-width: 100%; + overflow: auto; + font-variant: tabular-nums; +} + +.markdown-body td, +.markdown-body th { + padding: 0; +} + +.markdown-body details summary { + cursor: pointer; +} + +.markdown-body a:focus, +.markdown-body [role=button]:focus, +.markdown-body input[type=radio]:focus, +.markdown-body input[type=checkbox]:focus { + outline: 2px solid #0969da; + outline-offset: -2px; + box-shadow: none; +} + +.markdown-body a:focus:not(:focus-visible), +.markdown-body [role=button]:focus:not(:focus-visible), +.markdown-body input[type=radio]:focus:not(:focus-visible), +.markdown-body input[type=checkbox]:focus:not(:focus-visible) { + outline: solid 1px transparent; +} + +.markdown-body a:focus-visible, +.markdown-body [role=button]:focus-visible, +.markdown-body input[type=radio]:focus-visible, +.markdown-body input[type=checkbox]:focus-visible { + outline: 2px solid #0969da; + outline-offset: -2px; + box-shadow: none; +} + +.markdown-body a:not([class]):focus, +.markdown-body a:not([class]):focus-visible, +.markdown-body input[type=radio]:focus, +.markdown-body input[type=radio]:focus-visible, +.markdown-body input[type=checkbox]:focus, +.markdown-body input[type=checkbox]:focus-visible { + outline-offset: 0; +} + +.markdown-body kbd { + display: inline-block; + padding: 0.25rem; + font: 11px ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; + line-height: 10px; + color: #1f2328; + vertical-align: middle; + background-color: #f6f8fa; + border: solid 1px #d1d9e0b3; + border-bottom-color: #d1d9e0b3; + border-radius: 6px; + box-shadow: inset 0 -1px 0 #d1d9e0b3; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + margin-top: 1.5rem; + margin-bottom: 1rem; + font-weight: 600; + line-height: 1.25; +} + +.markdown-body h2 { + font-weight: 600; + padding-bottom: .3em; + font-size: 1.5em; + border-bottom: 1px solid #d1d9e0b3; +} + +.markdown-body h3 { + font-weight: 600; + font-size: 1.25em; +} + +.markdown-body h4 { + font-weight: 600; + font-size: 1em; +} + +.markdown-body h5 { + font-weight: 600; + font-size: .875em; +} + +.markdown-body h6 { + font-weight: 600; + font-size: .85em; + color: #59636e; +} + +.markdown-body p { + margin-top: 0; + margin-bottom: 10px; +} + +.markdown-body blockquote { + margin: 0; + padding: 0 1em; + color: #59636e; + border-left: .25em solid #d1d9e0; +} + +.markdown-body ul, +.markdown-body ol { + margin-top: 0; + margin-bottom: 0; + padding-left: 2em; +} + +.markdown-body ol ol, +.markdown-body ul ol { + list-style-type: lower-roman; +} + +.markdown-body ul ul ol, +.markdown-body ul ol ol, +.markdown-body ol ul ol, +.markdown-body ol ol ol { + list-style-type: lower-alpha; +} + +.markdown-body dd { + margin-left: 0; +} + +.markdown-body tt, +.markdown-body code, +.markdown-body samp { + font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; + font-size: 12px; +} + +.markdown-body pre { + margin-top: 0; + margin-bottom: 0; + font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; + font-size: 12px; + word-wrap: normal; +} + +.markdown-body .octicon { + display: inline-block; + overflow: visible !important; + vertical-align: text-bottom; + fill: currentColor; +} + +.markdown-body input::-webkit-outer-spin-button, +.markdown-body input::-webkit-inner-spin-button { + margin: 0; + appearance: none; +} + +.markdown-body .mr-2 { + margin-right: 0.5rem !important; +} + +.markdown-body::before { + display: table; + content: ""; +} + +.markdown-body::after { + display: table; + clear: both; + content: ""; +} + +.markdown-body>*:first-child { + margin-top: 0 !important; +} + +.markdown-body>*:last-child { + margin-bottom: 0 !important; +} + +.markdown-body a:not([href]) { + color: inherit; + text-decoration: none; +} + +.markdown-body .absent { + color: #d1242f; +} + +.markdown-body .anchor { + float: left; + padding-right: 0.25rem; + margin-left: -20px; + line-height: 1; +} + +.markdown-body .anchor:focus { + outline: none; +} + +.markdown-body p, +.markdown-body blockquote, +.markdown-body ul, +.markdown-body ol, +.markdown-body dl, +.markdown-body table, +.markdown-body pre, +.markdown-body details { + margin-top: 0; + margin-bottom: 1rem; +} + +.markdown-body blockquote>:first-child { + margin-top: 0; +} + +.markdown-body blockquote>:last-child { + margin-bottom: 0; +} + +.markdown-body h1 .octicon-link, +.markdown-body h2 .octicon-link, +.markdown-body h3 .octicon-link, +.markdown-body h4 .octicon-link, +.markdown-body h5 .octicon-link, +.markdown-body h6 .octicon-link { + color: #1f2328; + vertical-align: middle; + visibility: hidden; +} + +.markdown-body h1:hover .anchor, +.markdown-body h2:hover .anchor, +.markdown-body h3:hover .anchor, +.markdown-body h4:hover .anchor, +.markdown-body h5:hover .anchor, +.markdown-body h6:hover .anchor { + text-decoration: none; +} + +.markdown-body h1:hover .anchor .octicon-link, +.markdown-body h2:hover .anchor .octicon-link, +.markdown-body h3:hover .anchor .octicon-link, +.markdown-body h4:hover .anchor .octicon-link, +.markdown-body h5:hover .anchor .octicon-link, +.markdown-body h6:hover .anchor .octicon-link { + visibility: visible; +} + +.markdown-body h1 tt, +.markdown-body h1 code, +.markdown-body h2 tt, +.markdown-body h2 code, +.markdown-body h3 tt, +.markdown-body h3 code, +.markdown-body h4 tt, +.markdown-body h4 code, +.markdown-body h5 tt, +.markdown-body h5 code, +.markdown-body h6 tt, +.markdown-body h6 code { + padding: 0 .2em; + font-size: inherit; +} + +.markdown-body summary h1, +.markdown-body summary h2, +.markdown-body summary h3, +.markdown-body summary h4, +.markdown-body summary h5, +.markdown-body summary h6 { + display: inline-block; +} + +.markdown-body summary h1 .anchor, +.markdown-body summary h2 .anchor, +.markdown-body summary h3 .anchor, +.markdown-body summary h4 .anchor, +.markdown-body summary h5 .anchor, +.markdown-body summary h6 .anchor { + margin-left: -40px; +} + +.markdown-body summary h1, +.markdown-body summary h2 { + padding-bottom: 0; + border-bottom: 0; +} + +.markdown-body ul.no-list, +.markdown-body ol.no-list { + padding: 0; + list-style-type: none; +} + +.markdown-body ol[type="a s"] { + list-style-type: lower-alpha; +} + +.markdown-body ol[type="A s"] { + list-style-type: upper-alpha; +} + +.markdown-body ol[type="i s"] { + list-style-type: lower-roman; +} + +.markdown-body ol[type="I s"] { + list-style-type: upper-roman; +} + +.markdown-body ol[type="1"] { + list-style-type: decimal; +} + +.markdown-body div>ol:not([type]) { + list-style-type: decimal; +} + +.markdown-body ul ul, +.markdown-body ul ol, +.markdown-body ol ol, +.markdown-body ol ul { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body li>p { + margin-top: 1rem; +} + +.markdown-body li+li { + margin-top: .25em; +} + +.markdown-body dl { + padding: 0; +} + +.markdown-body dl dt { + padding: 0; + margin-top: 1rem; + font-size: 1em; + font-style: italic; + font-weight: 600; +} + +.markdown-body dl dd { + padding: 0 1rem; + margin-bottom: 1rem; +} + +.markdown-body table th { + font-weight: 600; +} + +.markdown-body table th, +.markdown-body table td { + padding: 6px 13px; + border: 1px solid #d1d9e0; +} + +.markdown-body table td>:last-child { + margin-bottom: 0; +} + +.markdown-body table tr { + background-color: #ffffff; + border-top: 1px solid #d1d9e0b3; +} + +.markdown-body table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.markdown-body table img { + background-color: transparent; +} + +.markdown-body img[align=right] { + padding-left: 20px; +} + +.markdown-body img[align=left] { + padding-right: 20px; +} + +.markdown-body .emoji { + max-width: none; + vertical-align: text-top; + background-color: transparent; +} + +.markdown-body span.frame { + display: block; + overflow: hidden; +} + +.markdown-body span.frame>span { + display: block; + float: left; + width: auto; + padding: 7px; + margin: 13px 0 0; + overflow: hidden; + border: 1px solid #d1d9e0; +} + +.markdown-body span.frame span img { + display: block; + float: left; +} + +.markdown-body span.frame span span { + display: block; + padding: 5px 0 0; + clear: both; + color: #1f2328; +} + +.markdown-body span.align-center { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-body span.align-center>span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: center; +} + +.markdown-body span.align-center span img { + margin: 0 auto; + text-align: center; +} + +.markdown-body span.align-right { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-body span.align-right>span { + display: block; + margin: 13px 0 0; + overflow: hidden; + text-align: right; +} + +.markdown-body span.align-right span img { + margin: 0; + text-align: right; +} + +.markdown-body span.float-left { + display: block; + float: left; + margin-right: 13px; + overflow: hidden; +} + +.markdown-body span.float-left span { + margin: 13px 0 0; +} + +.markdown-body span.float-right { + display: block; + float: right; + margin-left: 13px; + overflow: hidden; +} + +.markdown-body span.float-right>span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: right; +} + +.markdown-body code, +.markdown-body tt { + padding: .2em .4em; + margin: 0; + font-size: 85%; + white-space: break-spaces; + background-color: #818b981f; + border-radius: 6px; +} + +.markdown-body code br, +.markdown-body tt br { + display: none; +} + +.markdown-body del code { + text-decoration: inherit; +} + +.markdown-body samp { + font-size: 85%; +} + +.markdown-body pre code { + font-size: 100%; +} + +.markdown-body pre>code { + padding: 0; + margin: 0; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; +} + +.markdown-body .highlight { + margin-bottom: 1rem; +} + +.markdown-body .highlight pre { + margin-bottom: 0; + word-break: normal; +} + +.markdown-body .highlight pre, +.markdown-body pre { + padding: 1rem; + overflow: auto; + font-size: 85%; + line-height: 1.45; + color: #1f2328; + background-color: #f6f8fa; + border-radius: 6px; +} + +.markdown-body pre code, +.markdown-body pre tt { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; +} + +.markdown-body .csv-data td, +.markdown-body .csv-data th { + padding: 5px; + overflow: hidden; + font-size: 12px; + line-height: 1; + text-align: left; + white-space: nowrap; +} + +.markdown-body .csv-data .blob-num { + padding: 10px 0.5rem 9px; + text-align: right; + background: #ffffff; + border: 0; +} + +.markdown-body .csv-data tr { + border-top: 0; +} + +.markdown-body .csv-data th { + font-weight: 600; + background: #f6f8fa; + border-top: 0; +} + +.markdown-body [data-footnote-ref]::before { + content: "["; +} + +.markdown-body [data-footnote-ref]::after { + content: "]"; +} + +.markdown-body .footnotes { + font-size: 12px; + color: #59636e; + border-top: 1px solid #d1d9e0; +} + +.markdown-body .footnotes ol { + padding-left: 1rem; +} + +.markdown-body .footnotes ol ul { + display: inline-block; + padding-left: 1rem; + margin-top: 1rem; +} + +.markdown-body .footnotes li { + position: relative; +} + +.markdown-body .footnotes li:target::before { + position: absolute; + top: calc(0.5rem*-1); + right: calc(0.5rem*-1); + bottom: calc(0.5rem*-1); + left: calc(1.5rem*-1); + pointer-events: none; + content: ""; + border: 2px solid #0969da; + border-radius: 6px; +} + +.markdown-body .footnotes li:target { + color: #1f2328; +} + +.markdown-body .footnotes .data-footnote-backref g-emoji { + font-family: monospace; +} + +.markdown-body body:has(:modal) { + padding-right: var(--dialog-scrollgutter) !important; +} + +.markdown-body .pl-c { + color: #59636e; +} + +.markdown-body .pl-c1, +.markdown-body .pl-s .pl-v { + color: #0550ae; +} + +.markdown-body .pl-e, +.markdown-body .pl-en { + color: #6639ba; +} + +.markdown-body .pl-smi, +.markdown-body .pl-s .pl-s1 { + color: #1f2328; +} + +.markdown-body .pl-ent { + color: #0550ae; +} + +.markdown-body .pl-k { + color: #cf222e; +} + +.markdown-body .pl-s, +.markdown-body .pl-pds, +.markdown-body .pl-s .pl-pse .pl-s1, +.markdown-body .pl-sr, +.markdown-body .pl-sr .pl-cce, +.markdown-body .pl-sr .pl-sre, +.markdown-body .pl-sr .pl-sra { + color: #0a3069; +} + +.markdown-body .pl-v, +.markdown-body .pl-smw { + color: #953800; +} + +.markdown-body .pl-bu { + color: #82071e; +} + +.markdown-body .pl-ii { + color: #f6f8fa; + background-color: #82071e; +} + +.markdown-body .pl-c2 { + color: #f6f8fa; + background-color: #cf222e; +} + +.markdown-body .pl-sr .pl-cce { + font-weight: bold; + color: #116329; +} + +.markdown-body .pl-ml { + color: #3b2300; +} + +.markdown-body .pl-mh, +.markdown-body .pl-mh .pl-en, +.markdown-body .pl-ms { + font-weight: bold; + color: #0550ae; +} + +.markdown-body .pl-mi { + font-style: italic; + color: #1f2328; +} + +.markdown-body .pl-mb { + font-weight: bold; + color: #1f2328; +} + +.markdown-body .pl-md { + color: #82071e; + background-color: #ffebe9; +} + +.markdown-body .pl-mi1 { + color: #116329; + background-color: #dafbe1; +} + +.markdown-body .pl-mc { + color: #953800; + background-color: #ffd8b5; +} + +.markdown-body .pl-mi2 { + color: #d1d9e0; + background-color: #0550ae; +} + +.markdown-body .pl-mdr { + font-weight: bold; + color: #8250df; +} + +.markdown-body .pl-ba { + color: #59636e; +} + +.markdown-body .pl-sg { + color: #818b98; +} + +.markdown-body .pl-corl { + text-decoration: underline; + color: #0a3069; +} + +.markdown-body [role=button]:focus:not(:focus-visible), +.markdown-body [role=tabpanel][tabindex="0"]:focus:not(:focus-visible), +.markdown-body button:focus:not(:focus-visible), +.markdown-body summary:focus:not(:focus-visible), +.markdown-body a:focus:not(:focus-visible) { + outline: none; + box-shadow: none; +} + +.markdown-body [tabindex="0"]:focus:not(:focus-visible), +.markdown-body details-dialog:focus:not(:focus-visible) { + outline: none; +} + +.markdown-body g-emoji { + display: inline-block; + min-width: 1ch; + font-family: "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; + font-size: 1em; + font-style: normal !important; + font-weight: 400; + line-height: 1; + vertical-align: -0.075em; +} + +.markdown-body g-emoji img { + width: 1em; + height: 1em; +} + +.markdown-body .task-list-item { + list-style-type: none; +} + +.markdown-body .task-list-item label { + font-weight: 400; +} + +.markdown-body .task-list-item.enabled label { + cursor: pointer; +} + +.markdown-body .task-list-item+.task-list-item { + margin-top: 0.25rem; +} + +.markdown-body .task-list-item .handle { + display: none; +} + +.markdown-body .task-list-item-checkbox { + margin: 0 .2em .25em -1.4em; + vertical-align: middle; +} + +.markdown-body ul:dir(rtl) .task-list-item-checkbox { + margin: 0 -1.6em .25em .2em; +} + +.markdown-body ol:dir(rtl) .task-list-item-checkbox { + margin: 0 -1.6em .25em .2em; +} + +.markdown-body .contains-task-list:hover .task-list-item-convert-container, +.markdown-body .contains-task-list:focus-within .task-list-item-convert-container { + display: block; + width: auto; + height: 24px; + overflow: visible; + clip: auto; +} + +.markdown-body ::-webkit-calendar-picker-indicator { + filter: invert(50%); +} + +.markdown-body .markdown-alert { + padding: 0.5rem 1rem; + margin-bottom: 1rem; + color: inherit; + border-left: .25em solid #d1d9e0; +} + +.markdown-body .markdown-alert>:first-child { + margin-top: 0; +} + +.markdown-body .markdown-alert>:last-child { + margin-bottom: 0; +} + +.markdown-body .markdown-alert .markdown-alert-title { + display: flex; + font-weight: 500; + align-items: center; + line-height: 1; +} + +.markdown-body .markdown-alert.markdown-alert-note { + border-left-color: #0969da; +} + +.markdown-body .markdown-alert.markdown-alert-note .markdown-alert-title { + color: #0969da; +} + +.markdown-body .markdown-alert.markdown-alert-important { + border-left-color: #8250df; +} + +.markdown-body .markdown-alert.markdown-alert-important .markdown-alert-title { + color: #8250df; +} + +.markdown-body .markdown-alert.markdown-alert-warning { + border-left-color: #9a6700; +} + +.markdown-body .markdown-alert.markdown-alert-warning .markdown-alert-title { + color: #9a6700; +} + +.markdown-body .markdown-alert.markdown-alert-tip { + border-left-color: #1a7f37; +} + +.markdown-body .markdown-alert.markdown-alert-tip .markdown-alert-title { + color: #1a7f37; +} + +.markdown-body .markdown-alert.markdown-alert-caution { + border-left-color: #cf222e; +} + +.markdown-body .markdown-alert.markdown-alert-caution .markdown-alert-title { + color: #d1242f; +} + +.markdown-body>*:first-child>.heading-element:first-child { + margin-top: 0 !important; +} + +.markdown-body .highlight pre:has(+.zeroclipboard-container) { + min-height: 52px; +} + diff --git a/src/frames/configdialog.ui b/src/frames/configdialog.ui index 89e1d30..19c0fce 100644 --- a/src/frames/configdialog.ui +++ b/src/frames/configdialog.ui @@ -95,7 +95,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -121,7 +121,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -161,7 +161,7 @@ Author: Aurélie Delhaie (github.com/mojitaurelie) - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter @@ -177,7 +177,7 @@ Version 1.2.0.0 - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter @@ -198,7 +198,7 @@ WorkPad - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -217,7 +217,7 @@ - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -233,7 +233,7 @@ <html><head/><body><p>Under MIT license</p></body></html> - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter @@ -249,7 +249,7 @@ true - Copyright 2022 Aurélie Delhaie + 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 "Software"), 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 "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRES - QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Ok diff --git a/src/frames/exportdialog.cpp b/src/frames/exportdialog.cpp index f2ba33b..8dd8fbf 100644 --- a/src/frames/exportdialog.cpp +++ b/src/frames/exportdialog.cpp @@ -19,5 +19,9 @@ int ExportDialog::getResult() { return MARKDOWN; } + else if (ui->pdfRadio->isChecked()) + { + return PDF; + } return PLAIN; } diff --git a/src/frames/exportdialog.h b/src/frames/exportdialog.h index 2fb2536..124bf94 100644 --- a/src/frames/exportdialog.h +++ b/src/frames/exportdialog.h @@ -5,6 +5,7 @@ #define MARKDOWN 1 #define PLAIN 2 +#define PDF 3 namespace Ui { class ExportDialog; diff --git a/src/frames/exportdialog.ui b/src/frames/exportdialog.ui index a6e9516..ef1aa82 100644 --- a/src/frames/exportdialog.ui +++ b/src/frames/exportdialog.ui @@ -35,10 +35,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok @@ -61,7 +61,7 @@ 30 - 60 + 40 92 23 @@ -70,6 +70,19 @@ Text file + + + + 30 + 60 + 92 + 23 + + + + PDF file + + diff --git a/src/frames/mainwindow.cpp b/src/frames/mainwindow.cpp old mode 100755 new mode 100644 index 5e53c48..f8d626d --- a/src/frames/mainwindow.cpp +++ b/src/frames/mainwindow.cpp @@ -7,32 +7,71 @@ #include "renamedialog.h" #include "exportdialog.h" +#include + MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); timer = new QTimer(this); + parser = std::make_shared(); + + // 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, &QAction::triggered, this, &MainWindow::createNote); connect(ui->actionSave, &QAction::triggered, this, &MainWindow::save); 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->treeWidget, &QTreeWidget::itemSelectionChanged, this, &MainWindow::selectionChanged); connect(ui->treeWidget, &QTreeWidget::customContextMenuRequested, this, &MainWindow::prepareMenu); + connect(this, &MainWindow::updateViewers, this, &MainWindow::viewReady); const QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); - ui->contentEdit->setFont(fixedFont); ui->plainTextEdit->setFont(fixedFont); ui->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu); + ui->renderingProgressBar->setVisible(false); this->savemng = new SaveManager(); this->cfgmng = new ConfigManager(); updateListView(); connect(timer, &QTimer::timeout, this, &MainWindow::save); + + // start render thread + threaddone = new bool(false); + queue = new QVector; + 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 = ""; + htmlOutput += QString::fromStdString(parser->Parse(markdownInput)); + htmlOutput += ""; + emit updateViewers(htmlOutput); + } + }); } MainWindow::~MainWindow() { + *threaddone = true; + updateViewThread->join(); + delete queue; + delete queueMutex; + delete threaddone; + delete updateViewThread; delete timer; delete savemng; delete ui; @@ -91,20 +130,14 @@ void MainWindow::selectionChanged() return; } ui->plainTextEdit->blockSignals(true); - ui->contentEdit->blockSignals(true); this->setWindowTitle(n->getTitle() + " - WorkPad"); - ui->titleLabel->setText(n->getTitle()); ui->plainTextEdit->setDisabled(false); - ui->contentEdit->setDisabled(false); ui->plainTextEdit->setPlainText(n->getContent()); - ui->contentEdit->setPlainText(n->getContent()); - ui->markdownViewer->setMarkdown(ui->contentEdit->toPlainText()); - ui->markdownViewer2->setMarkdown(n->getContent()); + updateHTMLView(); ui->plainTextEdit->blockSignals(false); - ui->contentEdit->blockSignals(false); } else { @@ -307,6 +340,10 @@ void MainWindow::exportNote() { filter = "Markdown file (*.md)"; } + else if (fileType == PDF) + { + filter = "PDF file (*.pdf)"; + } QString fileName = QFileDialog::getSaveFileName(this, tr("Export note"), "", filter); if (!fileName.isEmpty()) { @@ -318,44 +355,57 @@ void MainWindow::exportNote() { fileName += ".txt"; } - QFile *f = new QFile(fileName); - if (f->open(QIODevice::WriteOnly)) + else if (fileType == PDF && !fileName.endsWith(".pdf", Qt::CaseInsensitive)) { - f->write(n->getContent().toUtf8()); - f->close(); + fileName += ".pdf"; } - 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() { timer->stop(); ui->plainTextEdit->blockSignals(true); - ui->contentEdit->blockSignals(true); if (ui->treeWidget->selectedItems().length() == 1) { QString uuid = ui->treeWidget->selectedItems()[0]->text(COLUMN_UUID); Note *n = savemng->getNoteByUUID(uuid); if (n != nullptr) { - QScrollBar *scrollbar = ui->markdownViewer->verticalScrollBar(); - - QString content = ui->contentEdit->toPlainText(); - - int pos = scrollbar->sliderPosition(); - ui->markdownViewer->setMarkdown(content); - scrollbar->setSliderPosition(pos); - - ui->markdownViewer2->setMarkdown(content); - ui->plainTextEdit->setPlainText(content); + QString content = ui->plainTextEdit->toPlainText(); n->setContent(content); + updateHTMLView(); } } ui->plainTextEdit->blockSignals(false); - ui->contentEdit->blockSignals(false); ui->actionSave->setDisabled(false); if (cfgmng->getConfiguration()->isEnableAutoSave()) { @@ -367,21 +417,17 @@ void MainWindow::plainContentChanged() { timer->stop(); ui->plainTextEdit->blockSignals(true); - ui->contentEdit->blockSignals(true); if (ui->treeWidget->selectedItems().length() == 1) { QString uuid = ui->treeWidget->selectedItems()[0]->text(COLUMN_UUID); Note *n = savemng->getNoteByUUID(uuid); if (n != nullptr) { QString content = ui->plainTextEdit->toPlainText(); - ui->markdownViewer->setMarkdown(content); - ui->markdownViewer2->setMarkdown(content); - ui->contentEdit->setPlainText(content); n->setContent(content); + updateHTMLView(); } } ui->plainTextEdit->blockSignals(false); - ui->contentEdit->blockSignals(false); ui->actionSave->setDisabled(false); 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() { - ui->contentEdit->blockSignals(true); ui->plainTextEdit->blockSignals(true); - - ui->contentEdit->setDisabled(true); ui->plainTextEdit->setDisabled(true); ui->plainTextEdit->clear(); - ui->contentEdit->clear(); - ui->markdownViewer->clear(); - ui->markdownViewer2->clear(); - ui->titleLabel->setText(""); this->setWindowTitle("WorkPad"); - ui->contentEdit->blockSignals(false); ui->plainTextEdit->blockSignals(false); } diff --git a/src/frames/mainwindow.h b/src/frames/mainwindow.h old mode 100755 new mode 100644 index 1c329a9..c3fd05d --- a/src/frames/mainwindow.h +++ b/src/frames/mainwindow.h @@ -10,10 +10,15 @@ #include #include #include +#include +#include #include "../services/savemanager.h" #include "../services/configmanager.h" +#include "../../maddy/parserconfig.h" +#include "../../maddy/parser.h" + #define COLUMN_NAME 0 #define COLUMN_UUID 1 #define COLUMN_TYPE 2 @@ -33,6 +38,9 @@ public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); +signals: + void updateViewers(QString html); + private slots: void createNote(); void createFolder(); @@ -46,15 +54,26 @@ private slots: void moveNote(); void editName(); void exportNote(); + void viewReady(QString html); private: Ui::MainWindow *ui; SaveManager *savemng; ConfigManager *cfgmng; QTimer *timer; + std::shared_ptr parser; + + QString style; + + //shared between threads + QVector* queue; + QMutex* queueMutex; + bool* threaddone; + std::thread *updateViewThread; void updateListView(); void clearAndDisableFields(); void selectTreeItem(QString uuid); + void updateHTMLView(); }; #endif // MAINWINDOW_H diff --git a/src/frames/mainwindow.ui b/src/frames/mainwindow.ui old mode 100755 new mode 100644 index 214f585..5c7f4df --- a/src/frames/mainwindow.ui +++ b/src/frames/mainwindow.ui @@ -25,156 +25,11 @@ - - - - - - 250 - 16777215 - - - - QAbstractItemView::NoEditTriggers - - - true - - - - Workspace - - - - - - - - Qt::Vertical - - - - - - - - - - 14 - - - - - - - - - - - QTabWidget::South - - - QTabWidget::Rounded - - - 0 - - - - Plain text - - - - - - false - - - - - - - - Markdown editor - - - - - - - - false - - - - 200 - 0 - - - - - false - - - - QPlainTextEdit::NoWrap - - - - - - - true - - - - 200 - 0 - - - - QTextEdit::AutoAll - - - QTextEdit::NoWrap - - - true - - - false - - - Qt::TextSelectableByMouse - - - - - - - - - - Markdown viewer - - - - - - QTextEdit::NoWrap - - - true - - - - - - - - - - + + + false + + @@ -189,7 +44,7 @@ - Qt::PreventContextMenu + Qt::ContextMenuPolicy::PreventContextMenu toolBar @@ -198,7 +53,7 @@ false - Qt::ToolButtonTextUnderIcon + Qt::ToolButtonStyle::ToolButtonTextUnderIcon false @@ -216,6 +71,96 @@ + + + + 500 + 84 + + + + QDockWidget::DockWidgetFeature::DockWidgetFloatable|QDockWidget::DockWidgetFeature::DockWidgetMovable + + + 2 + + + + + + + + + + 0 + 0 + + + + + + + + + + + 160 + 16777215 + + + + 0 + + + 0 + + + false + + + + + + + + + + 250 + 120 + + + + QDockWidget::DockWidgetFeature::NoDockWidgetFeatures + + + 1 + + + + + + + + 250 + 16777215 + + + + QAbstractItemView::EditTrigger::NoEditTriggers + + + true + + + + Workspace + + + + + + + false @@ -277,6 +222,14 @@ + + + QWebEngineView + QWidget +
    qwebengineview.h
    + 1 +
    +
    diff --git a/src/main.cpp b/src/main.cpp old mode 100755 new mode 100644 diff --git a/src/models/note.cpp b/src/models/note.cpp old mode 100755 new mode 100644 diff --git a/src/models/note.h b/src/models/note.h old mode 100755 new mode 100644 diff --git a/src/services/savemanager.cpp b/src/services/savemanager.cpp old mode 100755 new mode 100644 diff --git a/src/services/savemanager.h b/src/services/savemanager.h old mode 100755 new mode 100644 diff --git a/static.qrc b/static.qrc new file mode 100644 index 0000000..aa40dcb --- /dev/null +++ b/static.qrc @@ -0,0 +1,5 @@ + + + resources/style.css + +