diff options
author | Yigit Akcay <yigit.akcay@qt.io> | 2023-03-23 16:12:06 +0100 |
---|---|---|
committer | Yigit Akcay <yigit.akcay@qt.io> | 2023-04-05 14:19:20 +0200 |
commit | fe9d72d931f40eafca97c1012c03e24561c973ff (patch) | |
tree | 08e36634557eadae962bb56b82137294a384879c /examples | |
parent | 88ef502099d535823156c31e77251dd2a723ad1c (diff) |
Merge recipe browser, stylesheet browser and markdown editor examples
This patch merges the qtwebengine examples recipe browser, stylesheet
browser and markdown editor into one single example.
Pick-to: 6.5
Task-number: QTBUG-108751
Change-Id: I338707d7d3275b03bf2a2d7b65064ac91e562d7f
Reviewed-by: Michal Klocek <michal.klocek@qt.io>
Diffstat (limited to 'examples')
78 files changed, 872 insertions, 3734 deletions
diff --git a/examples/webenginequick/CMakeLists.txt b/examples/webenginequick/CMakeLists.txt index ff291fdcf..52ba7cb0b 100644 --- a/examples/webenginequick/CMakeLists.txt +++ b/examples/webenginequick/CMakeLists.txt @@ -3,6 +3,3 @@ qt_internal_add_example(lifecycle) qt_internal_add_example(quicknanobrowser) -if(TARGET Qt::QuickControls2) - qt_internal_add_example(recipebrowser) -endif() diff --git a/examples/webenginequick/recipebrowser/CMakeLists.txt b/examples/webenginequick/recipebrowser/CMakeLists.txt deleted file mode 100644 index 12aa7c1d3..000000000 --- a/examples/webenginequick/recipebrowser/CMakeLists.txt +++ /dev/null @@ -1,158 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -cmake_minimum_required(VERSION 3.16) -project(recipebrowser LANGUAGES CXX) - -set(CMAKE_AUTOMOC ON) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginequick/recipebrowser") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick QuickControls2 WebEngineQuick) - -qt_add_executable(recipebrowser - main.cpp -) - -set_target_properties(recipebrowser PROPERTIES - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -target_link_libraries(recipebrowser PUBLIC - Qt::Core - Qt::Gui - Qt::Qml - Qt::Quick - Qt::QuickControls2 - Qt::WebEngineQuick -) - -# Resources: -set_source_files_properties("resources/pages/assets/3rdparty/markdown.css" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/assets/3rdparty/marked.js" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/assets/custom.css" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/assets/custom.js" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/burger.html" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/cupcakes.html" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/images/burger.jpg" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/images/cupcakes.jpg" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/images/pasta.jpg" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/images/pizza.jpg" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/images/skewers.jpg" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/images/soup.jpg" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/images/steak.jpg" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/pasta.html" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/pizza.html" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/skewers.html" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/soup.html" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/steak.html" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/qml/RecipeList.qml" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/qml/main.qml" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set(resources_resource_files - "resources/pages/assets/3rdparty/markdown.css" - "resources/pages/assets/3rdparty/marked.js" - "resources/pages/assets/custom.css" - "resources/pages/assets/custom.js" - "resources/pages/burger.html" - "resources/pages/cupcakes.html" - "resources/pages/images/burger.jpg" - "resources/pages/images/cupcakes.jpg" - "resources/pages/images/pasta.jpg" - "resources/pages/images/pizza.jpg" - "resources/pages/images/skewers.jpg" - "resources/pages/images/soup.jpg" - "resources/pages/images/steak.jpg" - "resources/pages/pasta.html" - "resources/pages/pizza.html" - "resources/pages/skewers.html" - "resources/pages/soup.html" - "resources/pages/steak.html" - "resources/qml/main.qml" - "resources/qml/RecipeList.qml" -) - -qt_add_resources(recipebrowser "resources" - PREFIX - "/" - BASE - "resources" - FILES - ${resources_resource_files} -) - -if(CMAKE_CROSSCOMPILING AND (LINUX OR QNX OR posix)) - target_compile_definitions(recipebrowser PUBLIC - QTWEBENGINE_RECIPE_BROWSER_EMBEDDED - ) -endif() - -install(TARGETS recipebrowser - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/webenginequick/recipebrowser/doc/images/recipebrowser-demo.jpg b/examples/webenginequick/recipebrowser/doc/images/recipebrowser-demo.jpg Binary files differdeleted file mode 100644 index 761ad3576..000000000 --- a/examples/webenginequick/recipebrowser/doc/images/recipebrowser-demo.jpg +++ /dev/null diff --git a/examples/webenginequick/recipebrowser/doc/src/recipebrowser.qdoc b/examples/webenginequick/recipebrowser/doc/src/recipebrowser.qdoc deleted file mode 100644 index d2de5780c..000000000 --- a/examples/webenginequick/recipebrowser/doc/src/recipebrowser.qdoc +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \example webenginequick/recipebrowser - \title WebEngine Recipe Browser - \ingroup webengine-examples - \brief A small hybrid application based on the WebEngineView QML type and Qt Quick Controls 2. - - \image recipebrowser-demo.jpg - - \e {Recipe Browser} demonstrates how to use the \l{WebEngineView} item, \l{Qt Quick} items, and - \l{Qt Quick Controls 2} items to develop a small hybrid web browser application. - A \l{ListView}-based item is used to display a list of recipe names. Clicking on - a name causes the web view to load the respective recipe page. The overall appearance - of the application is provided by the \l{Qt Quick Controls 2} items, which have their active - style set to the \l{Material Style}{Material} style. The web content is a mix of HTML and - Markdown source compiled to HTML, along with CSS and JavaScript. - - \include examples-run.qdocinc - - \section1 C++ Code - - In \c main.cpp, we use the \l{QGuiApplication} and \l{QQmlApplicationEngine} - classes to set up and load the main QML file. We call \l{QtWebEngineQuick::initialize} so we can use - \l{WebEngineView} in our QML code. We set the default Qt Quick Controls 2 style - to the Material style, so we do not have to specify it for each new item we add. Finally, we use - a C++ define to check whether the application is compiled for an embedded platform. - The value will be used in the main QML code to determine the window size. - - \quotefromfile webenginequick/recipebrowser/main.cpp - \skipto #include - \printuntil } - - \section1 QML Code - - In the \c main.qml file, we first create a top-level window and set a title for it. We also set - up the size of the window depending on its primary orientation as well as the platform, so that - the application is usable on both desktop and embedded platforms. On desktop, the size - is constrained by a minimum of 320x480 pixels up to the maximum size that the screen supports. - The default window size is 1024 pixels wide and 768 pixels high in landscape orientation. - On embedded devices, the window will occupy the whole screen. - - \quotefromfile webenginequick/recipebrowser/resources/qml/main.qml - \skipto ApplicationWindow - \printuntil minimumHeight - - Next, we add a \l{RowLayout} item so we can divide the window into two parts: one being a - custom \c RecipeList item containing the recipe titles, and the other being the - \l{WebEngineView}, which shows the recipe details. The spacing is set to zero so the items are - positioned directly next to each other. - - \printuntil RecipeList - \dots 16 - \skipuntil webView.showRecipe - \printline } - \printuntil WebEngineView - \dots 16 - \skipuntil busy.running = true - \skipline } - \skipline } - \printline } - \printline } - - The \c RecipeList item has a few \l{Layout}{attached Layout properties}, in order to scale the - item to a maximum of one third of the layout width. We give the item focus, so that the keyboard - can be used to navigate the recipes, in addition to using mouse and touch. We also add a handler - for the custom \c recipeSelected signal, to tell the WebEngineView to load the URL of the - selected recipe. - - \quotefromfile webenginequick/recipebrowser/resources/qml/main.qml - \skipto RecipeList - \printuntil } - - The WebEngineView has similar layout properties, to make it occupy two thirds of the layout - width. - - \skipto WebEngineView - \printuntil Layout.fillHeight - - We then disable the \l{WebEngineSettings::focusOnNavigationEnabled}{focusOnNavigationEnabled} - setting to make sure that the \l{WebEngineView} does not steal focus from the \c RecipeList - item every time its URL is changed. This allows the user to continue navigating through the - recipes using the keyboard. We also disable the default context menu by accepting the - ContextMenuRequest. - - \skipto focusOnNavigationEnabled - \printuntil } - - When the application starts, instead of directly showing the \l{WebEngineView}, we show a - placeholder \l{Rectangle} with a \l{BusyIndicator} to provide a nicer user experience while the - application is loading. - - \printuntil } - \dots 12 - \skipto Rectangle - \printuntil } - - Once the first page in the view is loaded, we start a \l{Timer} that - will hide the placeholder and show the actual page. The delay provides more time for the recipe - images to load, so that when the view is shown, the page is completely rendered. The timer also - shows a help \l{ToolTip} that informs the user on how to navigate the recipes. - - \quotefromfile webenginequick/recipebrowser/resources/qml/main.qml - \skipto Timer { - \printuntil } - - Let's see what the \c RecipeList item looks like from the inside. The root item is a - FocusScope to allow transferring focus to the child ListView whenever the root item receives - focus. We also declare a custom \c recipeSelected signal, which will be emitted when the current - item of the ListView changes. - - \quotefromfile webenginequick/recipebrowser/resources/qml/RecipeList.qml - \skipto FocusScope - \printuntil recipeSelected - - A ColumnLayout holds a header \l{Label} above the ListView, and the ListView itself. - Again, we set the spacing to zero and make sure the layout occupies the whole space of - the parent item. - - \skipto ColumnLayout - \printuntil anchors.fill - - Inside the layout there is a styled \l{ToolBar} item, with a \l{Label} inside of it serving as - the ListView header. - - \skipto ToolBar - \printuntil Label - \printuntil } - \printuntil } - - The second item inside the layout is a \l{ListView}, whose contents will fill the remaining - space in the layout. We set \l{Item::}{clip} to true, so that the delegates that are scrolled - up are not seen under the ToolBar item. We set \l{Item::}{focus} to true, so the ListView gains - focus when the FocusScope does. We add a vertical scroll bar, so the user can scroll through the - recipes if the window size is small. We also specify the recipe model to be used by the - ListView as described later in this topic. - - \skipto ListView - \printuntil model - - We have an \l{ItemDelegate} set as the ListView delegate, which displays the - recipe title. The contentItem is a \l{Text} item, customized with a few properties to adjust the - visual appearance and position of the text. We create a binding to the current delegate's model - URL, so we can access the respective URL outside the delegate itself. We set the - \l{ItemDelegate::}{highlighted} property to \c true whenever the item is the current one in the - ListView to provide visual feedback. And we set the focus on the ListView whenever a delegate - is clicked, so that keyboard navigation works in case the focus was previously in the - WebEngineView. - - \skipto delegate - \printuntil onClicked - \printuntil } - \printuntil } - - A handler is defined for the \c currentItemChanged signal to emit our own \c recipeSelected - signal with the URL that the WebEngineView should load. - - \skipto onCurrentItemChanged - \printuntil } - - We use a \l{ListModel} with seven \l{ListElement}s, each of which contains a recipe - title and the URL to an HTML page contained in a resource file. The model is used to populate - the ListView with the recipes and to show the recipe details in the WebEngineView. - - \skipto ListModel - \printuntil Cupcakes - \printuntil } - \printuntil } - - We use a \l{ToolTip} item that is displayed on application startup to inform the users - how they can navigate and view the details of each recipe. The ToolTip is shown using the - \c showHelp method, which is invoked by the \l{Timer} in the main.qml file. - - \skipto ToolTip - \printuntil help.open() - \printuntil } - \printuntil } - - An example of a recipe page can be seen below. The page uses two stylesheets and - two JavaScript files: - \list - \li \l{https://bitbucket.org/kevinburke/markdowncss/src/master/}{markdown.css} is - a markdown-friendly stylesheet created by Kevin Burke - \li \l{https://github.com/chjj/marked}{marked.min.js} is a markdown parser and - compiler designed for speed written by Christopher Jeffrey - \li custom.css makes some small styling adjustments to the final recipe page - \li custom.js is used to invoke the conversion of the recipe content (which is written in - markdown syntax) into HTML - \endlist - - The images on the pages are loaded from the compiled resource file. - - \quotefromfile webenginequick/recipebrowser/resources/pages/soup.html - \printuntil </html> - - \section1 Files and Attributions - - The example bundles the following code with third-party licenses: - - \table - \row - \li \l{recipebrowser-marked}{Marked} - \li MIT License - \row - \li \l{recipebrowser-markdowncss}{Markdown.css} - \li Apache License 2.0 - \endtable -*/ diff --git a/examples/webenginequick/recipebrowser/main.cpp b/examples/webenginequick/recipebrowser/main.cpp deleted file mode 100644 index 076a3be9a..000000000 --- a/examples/webenginequick/recipebrowser/main.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include <QGuiApplication> -#include <QQmlApplicationEngine> -#include <QQmlContext> -#include <QQuickStyle> -#include <QtWebEngineQuick/qtwebenginequickglobal.h> - -int main(int argc, char *argv[]) -{ - QCoreApplication::setOrganizationName("QtExamples"); - QtWebEngineQuick::initialize(); - - QGuiApplication app(argc, argv); - - QQuickStyle::setStyle(QStringLiteral("Material")); - - QQmlApplicationEngine engine; - - bool isEmbedded = false; -#ifdef QTWEBENGINE_RECIPE_BROWSER_EMBEDDED - isEmbedded = true; -#endif - engine.rootContext()->setContextProperty(QStringLiteral("isEmbedded"), isEmbedded); - - engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); - - return app.exec(); -} diff --git a/examples/webenginequick/recipebrowser/recipebrowser.pro b/examples/webenginequick/recipebrowser/recipebrowser.pro deleted file mode 100644 index e358d00a3..000000000 --- a/examples/webenginequick/recipebrowser/recipebrowser.pro +++ /dev/null @@ -1,21 +0,0 @@ -TEMPLATE = app - -QT += quick qml quickcontrols2 webenginequick - -cross_compile { - posix|qnx|linux: DEFINES += QTWEBENGINE_RECIPE_BROWSER_EMBEDDED -} - -SOURCES += main.cpp - -RESOURCES += resources/resources.qrc - -# Make sure Qt Quick compiler does not remove the source code of the .js files. -QTQUICK_COMPILER_SKIPPED_RESOURCES = resources/resources.qrc - -DISTFILES += \ - resources/pages/assets/3rdparty/MARKDOWN-LICENSE.txt \ - resources/pages/assets/3rdparty/MARKED-LICENSE.txt - -target.path = $$[QT_INSTALL_EXAMPLES]/webenginequick/recipebrowser -INSTALLS += target diff --git a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/qt_attribution.json b/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/qt_attribution.json deleted file mode 100644 index 4dafa1acd..000000000 --- a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/qt_attribution.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "Id": "recipebrowser-marked", - "Name": "Marked (WebEngine RecipeBrowser example)", - "QDocModule": "qtwebengine", - "QtUsage": "Marked is used in the WebEngine RecipeBrowser example", - "QtParts": [ "examples" ], - "Files": "marked.js", - "Description": "A full-featured markdown parser and compiler, written in JavaScript. Built for speed.", - "Homepage": "https://github.com/chjj/marked", - "Version": "0.4.0", - "DownloadLocation": "https://github.com/markedjs/marked/blob/0.4.0/lib/marked.js", - "Copyright": "Copyright (c) 2011-2018, Christopher Jeffrey", - "License": "MIT License", - "LicenseId": "MIT", - "LicenseFile": "MARKED-LICENSE.txt" - }, - { - "Id": "recipebrowser-markdowncss", - "Name": "Markdown.css (WebEngine RecipeBrowser example)", - "QDocModule": "qtwebengine", - "QtUsage": "markdown.css is used in the WebEngine RecipeBrowser example", - "QtParts": [ "examples" ], - "Files": "markdown.css", - "Description": "Markdown.css is better default styling for your Markdown files.", - "Version": "188530e4b5d020d7e237fc6b26be13ebf4a8def3", - "DownloadLocation": "https://bitbucket.org/kevinburke/markdowncss/src/188530e4b5d020d7e237fc6b26be13ebf4a8def3/markdown.css", - "Copyright": "Copyright 2011 Kevin Burke - Copyright Twitter Inc.", - "License": "Apache License 2.0", - "LicenseId": "Apache-2.0", - "LicenseFile": "MARKDOWN-LICENSE.txt" - } -] diff --git a/examples/webenginequick/recipebrowser/resources/pages/assets/custom.js b/examples/webenginequick/recipebrowser/resources/pages/assets/custom.js deleted file mode 100644 index 2be2cf1ec..000000000 --- a/examples/webenginequick/recipebrowser/resources/pages/assets/custom.js +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -marked.setOptions({ - renderer: new marked.Renderer(), - gfm: true, - tables: true, - breaks: false, - pedantic: false, - sanitize: false, - smartLists: true, - smartypants: false -}); - -// Poor man document.ready(); -(function() { - var placeholder = document.getElementById('placeholder'); - var content = document.getElementById('content'); - placeholder.innerHTML = marked(content.innerHTML); -})(); diff --git a/examples/webenginequick/recipebrowser/resources/qml/RecipeList.qml b/examples/webenginequick/recipebrowser/resources/qml/RecipeList.qml deleted file mode 100644 index bfaf59112..000000000 --- a/examples/webenginequick/recipebrowser/resources/qml/RecipeList.qml +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Controls -import QtQuick.Controls.Material -import QtQuick.Layouts - -FocusScope { - id: root - signal recipeSelected(url url) - - ColumnLayout { - spacing: 0 - anchors.fill: parent - - ToolBar { - id: headerBackground - Layout.fillWidth: true - implicitHeight: headerText.height + 20 - - Label { - id: headerText - width: parent.width - text: qsTr("Favorite recipes") - padding: 10 - anchors.centerIn: parent - } - } - - ListView { - id: listView - Layout.fillWidth: true - Layout.fillHeight: true - keyNavigationWraps: true - clip: true - focus: true - ScrollBar.vertical: ScrollBar { } - - model: recipeModel - - delegate: ItemDelegate { - width: parent.width - text: model.name - contentItem: Text { - text: parent.text - font: parent.font - color: parent.enabled ? parent.Material.primaryTextColor - : parent.Material.hintTextColor - elide: Text.ElideRight - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - } - - property url url: model.url - highlighted: ListView.isCurrentItem - - onClicked: { - listView.forceActiveFocus() - listView.currentIndex = model.index - } - } - - onCurrentItemChanged: { - root.recipeSelected(currentItem.url) - } - - ListModel { - id: recipeModel - - ListElement { - name: "Pizza Diavola" - url: "qrc:///pages/pizza.html" - } - ListElement { - name: "Steak" - url: "qrc:///pages/steak.html" - } - ListElement { - name: "Burger" - url: "qrc:///pages/burger.html" - } - ListElement { - name: "Soup" - url: "qrc:///pages/soup.html" - } - ListElement { - name: "Pasta" - url: "qrc:///pages/pasta.html" - } - ListElement { - name: "Grilled Skewers" - url: "qrc:///pages/skewers.html" - } - ListElement { - name: "Cupcakes" - url: "qrc:///pages/cupcakes.html" - } - } - - ToolTip { - id: help - implicitWidth: root.width - padding * 3 - y: root.y + root.height - delay: 1000 - timeout: 5000 - text: qsTr("Use keyboard, mouse, or touch controls to navigate through the\ - recipes.") - - contentItem: Text { - text: help.text - font: help.font - color: help.Material.primaryTextColor - wrapMode: Text.Wrap - } - } - } - } - - function showHelp() { - help.open() - } -} - diff --git a/examples/webenginequick/recipebrowser/resources/qml/main.qml b/examples/webenginequick/recipebrowser/resources/qml/main.qml deleted file mode 100644 index 7db43c871..000000000 --- a/examples/webenginequick/recipebrowser/resources/qml/main.qml +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQml -import QtQuick -import QtQuick.Controls -import QtQuick.Controls.Material -import QtQuick.Layouts -import QtQuick.Window -import QtWebEngine - -ApplicationWindow { - id: appWindow - title: qsTr("Recipe Browser") - visible: true - - property int shorterDesktop: 768 - property int longerDesktop: 1024 - property int shorterMin: 360 - property int longerMin: 480 - property bool isPortrait: Screen.primaryOrientation === Qt.PortraitOrientation - width: { - if (isEmbedded) - return Screen.width - var potentialWidth = shorterDesktop - if (!isPortrait) - potentialWidth = longerDesktop - return potentialWidth > Screen.width ? Screen.width : potentialWidth - } - height: { - if (isEmbedded) - return Screen.height - var potentialHeight = longerDesktop - if (!isPortrait) - potentialHeight = shorterDesktop - return potentialHeight > Screen.height ? Screen.height : potentialHeight - } - minimumWidth: isPortrait ? shorterMin : longerMin - minimumHeight: isPortrait ? longerMin : shorterMin - - RowLayout { - id: container - anchors.fill: parent - spacing: 0 - - RecipeList { - id: recipeList - Layout.minimumWidth: 124 - Layout.preferredWidth: parent.width / 3 - Layout.maximumWidth: 300 - Layout.fillWidth: true - Layout.fillHeight: true - focus: true - activeFocusOnTab: true - onRecipeSelected: function(url) { - webView.showRecipe(url) - } - } - - WebEngineView { - id: webView - Layout.preferredWidth: 2 * parent.width / 3 - Layout.fillWidth: true - Layout.fillHeight: true - // Make sure focus is not taken by the web view, so user can continue navigating - // recipes with the keyboard. - settings.focusOnNavigationEnabled: false - - onContextMenuRequested: function(request) { - request.accepted = true - } - - property bool firstLoadComplete: false - onLoadingChanged: function(loadRequest) { - if (loadRequest.status === WebEngineView.LoadSucceededStatus - && !firstLoadComplete) { - // Debounce the showing of the web content, so images are more likely - // to have loaded completely. - showTimer.start() - } - } - - Timer { - id: showTimer - interval: 500 - repeat: false - onTriggered: { - webView.show(true) - webView.firstLoadComplete = true - recipeList.showHelp() - } - } - - Rectangle { - id: webViewPlaceholder - anchors.fill: parent - z: 1 - color: "white" - - BusyIndicator { - id: busy - anchors.centerIn: parent - } - } - - function showRecipe(url) { - webView.url = url - } - - function show(show) { - if (show === true) { - busy.running = false - webViewPlaceholder.visible = false - } else { - webViewPlaceholder.visible = true - busy.running = true - } - } - } - } -} diff --git a/examples/webenginequick/recipebrowser/resources/resources.qrc b/examples/webenginequick/recipebrowser/resources/resources.qrc deleted file mode 100644 index bd13dcfae..000000000 --- a/examples/webenginequick/recipebrowser/resources/resources.qrc +++ /dev/null @@ -1,27 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>qml/main.qml</file> - <file>qml/RecipeList.qml</file> - - <file>pages/pizza.html</file> - <file>pages/burger.html</file> - <file>pages/steak.html</file> - <file>pages/soup.html</file> - <file>pages/pasta.html</file> - <file>pages/skewers.html</file> - <file>pages/cupcakes.html</file> - - <file>pages/assets/3rdparty/marked.js</file> - <file>pages/assets/3rdparty/markdown.css</file> - <file>pages/assets/custom.css</file> - <file>pages/assets/custom.js</file> - - <file>pages/images/burger.jpg</file> - <file>pages/images/pizza.jpg</file> - <file>pages/images/steak.jpg</file> - <file>pages/images/soup.jpg</file> - <file>pages/images/pasta.jpg</file> - <file>pages/images/skewers.jpg</file> - <file>pages/images/cupcakes.jpg</file> - </qresource> -</RCC> diff --git a/examples/webenginequick/webenginequick.pro b/examples/webenginequick/webenginequick.pro index acf6127d6..fb44f2c54 100644 --- a/examples/webenginequick/webenginequick.pro +++ b/examples/webenginequick/webenginequick.pro @@ -5,6 +5,5 @@ SUBDIRS += \ qtHaveModule(quickcontrols2) { SUBDIRS += \ - lifecycle \ - recipebrowser + lifecycle } diff --git a/examples/webenginewidgets/CMakeLists.txt b/examples/webenginewidgets/CMakeLists.txt index c15089420..cc4a4d6c0 100644 --- a/examples/webenginewidgets/CMakeLists.txt +++ b/examples/webenginewidgets/CMakeLists.txt @@ -5,14 +5,13 @@ qt_internal_add_example(contentmanipulation) qt_internal_add_example(cookiebrowser) qt_internal_add_example(notifications) qt_internal_add_example(simplebrowser) -qt_internal_add_example(stylesheetbrowser) qt_internal_add_example(push-notifications) qt_internal_add_example(videoplayer) if(QT_FEATURE_webengine_geolocation) qt_internal_add_example(maps) endif() if(QT_FEATURE_webengine_webchannel) - qt_internal_add_example(markdowneditor) + qt_internal_add_example(recipebrowser) endif() if(QT_FEATURE_webengine_printing_and_pdf) qt_internal_add_example(printme) diff --git a/examples/webenginewidgets/markdowneditor/CMakeLists.txt b/examples/webenginewidgets/markdowneditor/CMakeLists.txt deleted file mode 100644 index 808c69521..000000000 --- a/examples/webenginewidgets/markdowneditor/CMakeLists.txt +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -cmake_minimum_required(VERSION 3.16) -project(markdowneditor LANGUAGES CXX) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginewidgets/markdowneditor") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui WebChannel WebEngineWidgets) - -qt_add_executable(markdowneditor - document.cpp document.h - main.cpp - mainwindow.cpp mainwindow.h mainwindow.ui - previewpage.cpp previewpage.h -) - -set_target_properties(markdowneditor PROPERTIES - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -target_link_libraries(markdowneditor PUBLIC - Qt::Core - Qt::Gui - Qt::WebChannel - Qt::WebEngineWidgets -) - -# Resources: -set(markdowneditor_resource_files - "resources/3rdparty/markdown.css" - "resources/3rdparty/marked.js" - "resources/default.md" - "resources/index.html" -) - -qt_add_resources(markdowneditor "markdowneditor" - PREFIX - "/" - BASE - "resources" - FILES - ${markdowneditor_resource_files} -) - -install(TARGETS markdowneditor - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/webenginewidgets/markdowneditor/doc/images/markdowneditor-example.png b/examples/webenginewidgets/markdowneditor/doc/images/markdowneditor-example.png Binary files differdeleted file mode 100644 index 9f456c4db..000000000 --- a/examples/webenginewidgets/markdowneditor/doc/images/markdowneditor-example.png +++ /dev/null diff --git a/examples/webenginewidgets/markdowneditor/doc/src/markdowneditor.qdoc b/examples/webenginewidgets/markdowneditor/doc/src/markdowneditor.qdoc deleted file mode 100644 index f8c67fd63..000000000 --- a/examples/webenginewidgets/markdowneditor/doc/src/markdowneditor.qdoc +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \example webenginewidgets/markdowneditor - \title WebEngine Markdown Editor Example - \ingroup webengine-widgetexamples - \brief Demonstrates how to integrate a web engine in a hybrid desktop - application. - - \image markdowneditor-example.png - - \e {Markdown Editor} demonstrates how to use QWebChannel and JavaScript - libraries to provide a rich text preview tool for a custom markup language. - - \l{http://daringfireball.net/projects/markdown/}{Markdown} is a lightweight - markup language with a plain text formatting syntax. - Some services, such as \l{http://github.com}{github}, acknowledge the - format, and render the content as rich text when viewed in a browser. - - The Markdown Editor main window is split into an editor and a preview area. - The editor supports the Markdown syntax and is implemented by using - QPlainTextEdit. The document is rendered as rich text in the preview area, - which is implemented by using QWebEngineView. To render the text, the - Markdown text is converted to HTML format with the help of a JavaScript - library inside the web engine. The preview is updated from the editor - through QWebChannel. - - \include examples-run.qdocinc - - \section1 Exposing Document Text - - Because we expose the current Markdown text to be rendered to the web engine - through QWebChannel, we need to somehow make the current text available - through the Qt metatype system. This is done by using a dedicated - \c Document class that exposes the document text as a \c{Q_PROPERTY}: - - \quotefromfile webenginewidgets/markdowneditor/document.h - \skipto class Document - \printto #endif - - The \c Document class wraps a QString to be set on the C++ side with - the \c setText() method and exposes it at runtime as a \c text property - with a \c textChanged signal. - - We define the \c setText method as follows: - - \quotefromfile webenginewidgets/markdowneditor/document.cpp - \skipto Document::setText - \printuntil - - \section1 Previewing Text - - We implement our own \c PreviewPage class that publicly inherits from - \c QWebEnginePage: - - \quotefromfile webenginewidgets/markdowneditor/previewpage.h - \skipto class PreviewPage - \printto #endif - - We reimplement the virtual \c acceptNavigationRequest method to - stop the page from navigating away from the current document. Instead, - we redirect external links to the system browser: - - \quotefromfile webenginewidgets/markdowneditor/previewpage.cpp - \skipto acceptNavigationRequest - \printuntil - - \section1 Creating the Main Window - - The \c MainWindow class inherits the QMainWindow class: - - \quotefromfile webenginewidgets/markdowneditor/mainwindow.h - \skipto class MainWindow : - \printto endif - - The class declares private slots that match the actions in the menu, - as well as the \c isModified() helper method. - - The actual layout of the main window is specified in a \c .ui file. - The widgets and actions are available at runtime in the \c ui member - variable. - - \c m_filePath holds the file path to the currently loaded document. - \c m_content is an instance of the \c Document class. - - The actual setup of the different objects is done in the \c MainWindow - constructor: - - \quotefromfile webenginewidgets/markdowneditor/mainwindow.cpp - \skipto MainWindow::MainWindow - \printto PreviewPage - - The constructor first calls \c setupUi to construct the widgets and menu - actions according to the UI file. The text editor font is set to one - with a fixed character width, and the QWebEngineView widget is told not - to show a context menu. - - \printto connect - - Here the constructor makes sure our custom - \c PreviewPage is used by the QWebEngineView instance in \c{ui->preview}. - - \printto ui->preview - - Here the \c textChanged signal of the editor is connected to a lambda that - updates the text in \c m_content. This object is then exposed to the JS side - by \c QWebChannel under the name \c{content}. - - \printto connect - - Now we can actually load the \e index.html file from the - resources. For more information about the file, see - \l{Creating an Index File}. - - \printto defaultTextFile - - The menu items are connected to the corresponding member slots. The - \uicontrol Save item is activated or deactivated depending on whether - the user has edited the content. - - \printuntil } - - Finally, we load a default document \e default.md from the resources. - - \section1 Creating an Index File - - \quotefile webenginewidgets/markdowneditor/resources/index.html - - In the \e index.html, we load a custom stylesheet and two JavaScript - libraries. \l{https://bitbucket.org/kevinburke/markdowncss/src/master/}{markdown.css} is - a markdown-friendly stylesheet created by Kevin Burke. - \l{https://github.com/chjj/marked}{marked.js} is a markdown parser and - compiler designed for speed written by Christopher Jeffrey and - \e qwebchannel.js is part of the \l{QWebChannel} module. - - In the \c <body> element we first define a \c placeholder element, and - make it available as a JavaScript variable. We then define the \c updateText - helper method that updates the content of \c placeholder with the HTML - that the JavaScript method \c marked() returns. - - Finally, we set up the web channel to access the \c content proxy object - and make sure that \c updateText() is called whenever \c content.text - changes. - - \section1 Files and Attributions - - The example bundles the following code with third-party licenses: - - \table - \row - \li \l{markdowneditor-marked}{Marked} - \li MIT License - \row - \li \l{markdowneditor-markdowncss}{Markdown.css} - \li Apache License 2.0 - \endtable -*/ - diff --git a/examples/webenginewidgets/markdowneditor/document.cpp b/examples/webenginewidgets/markdowneditor/document.cpp deleted file mode 100644 index 8ece76509..000000000 --- a/examples/webenginewidgets/markdowneditor/document.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "document.h" - -void Document::setText(const QString &text) -{ - if (text == m_text) - return; - m_text = text; - emit textChanged(m_text); -} diff --git a/examples/webenginewidgets/markdowneditor/document.h b/examples/webenginewidgets/markdowneditor/document.h deleted file mode 100644 index f4eabbdaa..000000000 --- a/examples/webenginewidgets/markdowneditor/document.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef DOCUMENT_H -#define DOCUMENT_H - -#include <QObject> -#include <QString> - -class Document : public QObject -{ - Q_OBJECT - Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged FINAL) -public: - explicit Document(QObject *parent = nullptr) : QObject(parent) {} - - void setText(const QString &text); - -signals: - void textChanged(const QString &text); - -private: - QString m_text; -}; - -#endif // DOCUMENT_H diff --git a/examples/webenginewidgets/markdowneditor/main.cpp b/examples/webenginewidgets/markdowneditor/main.cpp deleted file mode 100644 index 98e76bfba..000000000 --- a/examples/webenginewidgets/markdowneditor/main.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "document.h" -#include "mainwindow.h" - -#include <QApplication> -#include <QFile> - -int main(int argc, char *argv[]) -{ - QCoreApplication::setOrganizationName("QtExamples"); - QApplication a(argc, argv); - - MainWindow window; - window.show(); - - return a.exec(); -} diff --git a/examples/webenginewidgets/markdowneditor/mainwindow.cpp b/examples/webenginewidgets/markdowneditor/mainwindow.cpp deleted file mode 100644 index a4ef50a31..000000000 --- a/examples/webenginewidgets/markdowneditor/mainwindow.cpp +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "mainwindow.h" -#include "previewpage.h" -#include "ui_mainwindow.h" - -#include <QFile> -#include <QFileDialog> -#include <QFontDatabase> -#include <QMessageBox> -#include <QStatusBar> -#include <QTextStream> -#include <QWebChannel> - -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow) -{ - ui->setupUi(this); - ui->editor->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); - ui->preview->setContextMenuPolicy(Qt::NoContextMenu); - - PreviewPage *page = new PreviewPage(this); - ui->preview->setPage(page); - - connect(ui->editor, &QPlainTextEdit::textChanged, - [this]() { m_content.setText(ui->editor->toPlainText()); }); - - QWebChannel *channel = new QWebChannel(this); - channel->registerObject(QStringLiteral("content"), &m_content); - page->setWebChannel(channel); - - ui->preview->setUrl(QUrl("qrc:/index.html")); - - connect(ui->actionNew, &QAction::triggered, this, &MainWindow::onFileNew); - connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::onFileOpen); - connect(ui->actionSave, &QAction::triggered, this, &MainWindow::onFileSave); - connect(ui->actionSaveAs, &QAction::triggered, this, &MainWindow::onFileSaveAs); - connect(ui->actionExit, &QAction::triggered, this, &QWidget::close); - - connect(ui->editor->document(), &QTextDocument::modificationChanged, - ui->actionSave, &QAction::setEnabled); - - QFile defaultTextFile(":/default.md"); - defaultTextFile.open(QIODevice::ReadOnly); - ui->editor->setPlainText(defaultTextFile.readAll()); -} - -MainWindow::~MainWindow() -{ - delete ui; -} - -void MainWindow::openFile(const QString &path) -{ - QFile f(path); - if (!f.open(QIODevice::ReadOnly)) { - QMessageBox::warning(this, windowTitle(), - tr("Could not open file %1: %2").arg( - QDir::toNativeSeparators(path), f.errorString())); - return; - } - m_filePath = path; - ui->editor->setPlainText(f.readAll()); - statusBar()->showMessage(tr("Opened %1").arg(QDir::toNativeSeparators(path))); -} - -bool MainWindow::isModified() const -{ - return ui->editor->document()->isModified(); -} - -void MainWindow::onFileNew() -{ - if (isModified()) { - QMessageBox::StandardButton button = QMessageBox::question(this, windowTitle(), - tr("You have unsaved changes. Do you want to create a new document anyway?")); - if (button != QMessageBox::Yes) - return; - } - - m_filePath.clear(); - ui->editor->setPlainText(tr("## New document")); - ui->editor->document()->setModified(false); -} - -void MainWindow::onFileOpen() -{ - if (isModified()) { - QMessageBox::StandardButton button = QMessageBox::question(this, windowTitle(), - tr("You have unsaved changes. Do you want to open a new document anyway?")); - if (button != QMessageBox::Yes) - return; - } - - QFileDialog dialog(this, tr("Open MarkDown File")); - dialog.setMimeTypeFilters({"text/markdown"}); - dialog.setAcceptMode(QFileDialog::AcceptOpen); - if (dialog.exec() == QDialog::Accepted) - openFile(dialog.selectedFiles().constFirst()); -} - -void MainWindow::onFileSave() -{ - if (m_filePath.isEmpty()) { - onFileSaveAs(); - return; - } - - QFile f(m_filePath); - if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { - QMessageBox::warning(this, windowTitle(), - tr("Could not write to file %1: %2").arg( - QDir::toNativeSeparators(m_filePath), f.errorString())); - return; - } - QTextStream str(&f); - str << ui->editor->toPlainText(); - - ui->editor->document()->setModified(false); - - statusBar()->showMessage(tr("Wrote %1").arg(QDir::toNativeSeparators(m_filePath))); -} - -void MainWindow::onFileSaveAs() -{ - QFileDialog dialog(this, tr("Save MarkDown File")); - dialog.setMimeTypeFilters({"text/markdown"}); - dialog.setAcceptMode(QFileDialog::AcceptSave); - dialog.setDefaultSuffix("md"); - if (dialog.exec() != QDialog::Accepted) - return; - - m_filePath = dialog.selectedFiles().constFirst(); - onFileSave(); -} - -void MainWindow::closeEvent(QCloseEvent *e) -{ - if (isModified()) { - QMessageBox::StandardButton button = QMessageBox::question(this, windowTitle(), - tr("You have unsaved changes. Do you want to exit anyway?")); - if (button != QMessageBox::Yes) - e->ignore(); - } -} diff --git a/examples/webenginewidgets/markdowneditor/mainwindow.h b/examples/webenginewidgets/markdowneditor/mainwindow.h deleted file mode 100644 index 271664852..000000000 --- a/examples/webenginewidgets/markdowneditor/mainwindow.h +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include "document.h" - -#include <QMainWindow> -#include <QString> - -QT_BEGIN_NAMESPACE -namespace Ui { -class MainWindow; -} -QT_END_NAMESPACE - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent = nullptr); - ~MainWindow(); - - void openFile(const QString &path); - -protected: - void closeEvent(QCloseEvent *e) override; - -private slots: - void onFileNew(); - void onFileOpen(); - void onFileSave(); - void onFileSaveAs(); - -private: - bool isModified() const; - - Ui::MainWindow *ui; - QString m_filePath; - Document m_content; -}; - -#endif // MAINWINDOW_H diff --git a/examples/webenginewidgets/markdowneditor/mainwindow.ui b/examples/webenginewidgets/markdowneditor/mainwindow.ui deleted file mode 100644 index 36ab352b7..000000000 --- a/examples/webenginewidgets/markdowneditor/mainwindow.ui +++ /dev/null @@ -1,115 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>MainWindow</class> - <widget class="QMainWindow" name="MainWindow"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>800</width> - <height>600</height> - </rect> - </property> - <property name="windowTitle"> - <string>MarkDown Editor</string> - </property> - <widget class="QWidget" name="centralwidget"> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QSplitter" name="splitter"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <widget class="QPlainTextEdit" name="editor"/> - <widget class="QWebEngineView" name="preview" native="true"/> - </widget> - </item> - </layout> - </widget> - <widget class="QMenuBar" name="menubar"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>800</width> - <height>26</height> - </rect> - </property> - <widget class="QMenu" name="menu_File"> - <property name="title"> - <string>&File</string> - </property> - <addaction name="actionNew"/> - <addaction name="actionOpen"/> - <addaction name="actionSave"/> - <addaction name="actionSaveAs"/> - <addaction name="separator"/> - <addaction name="actionExit"/> - </widget> - <addaction name="menu_File"/> - </widget> - <widget class="QStatusBar" name="statusbar"/> - <action name="actionOpen"> - <property name="text"> - <string>&Open...</string> - </property> - <property name="toolTip"> - <string>Open document</string> - </property> - <property name="shortcut"> - <string>Ctrl+O</string> - </property> - </action> - <action name="actionSave"> - <property name="text"> - <string>&Save</string> - </property> - <property name="toolTip"> - <string>Save current document</string> - </property> - <property name="shortcut"> - <string>Ctrl+S</string> - </property> - </action> - <action name="actionExit"> - <property name="text"> - <string>E&xit</string> - </property> - <property name="toolTip"> - <string>Exit editor</string> - </property> - <property name="shortcut"> - <string>Ctrl+Q</string> - </property> - </action> - <action name="actionSaveAs"> - <property name="text"> - <string>Save &As...</string> - </property> - <property name="toolTip"> - <string>Save document under different name</string> - </property> - </action> - <action name="actionNew"> - <property name="text"> - <string>&New</string> - </property> - <property name="toolTip"> - <string>Create new document</string> - </property> - <property name="shortcut"> - <string>Ctrl+N</string> - </property> - </action> - </widget> - <customwidgets> - <customwidget> - <class>QWebEngineView</class> - <extends>QWidget</extends> - <header>qwebengineview.h</header> - <container>1</container> - </customwidget> - </customwidgets> - <resources/> - <connections/> -</ui> diff --git a/examples/webenginewidgets/markdowneditor/markdowneditor.pro b/examples/webenginewidgets/markdowneditor/markdowneditor.pro deleted file mode 100644 index 099edf4b5..000000000 --- a/examples/webenginewidgets/markdowneditor/markdowneditor.pro +++ /dev/null @@ -1,32 +0,0 @@ -TEMPLATE = app - -QT += webenginewidgets webchannel - -HEADERS += \ - mainwindow.h \ - previewpage.h \ - document.h - -SOURCES = \ - main.cpp \ - mainwindow.cpp \ - previewpage.cpp \ - document.cpp - -RESOURCES = \ - resources/markdowneditor.qrc - -# Disable Qt Quick compiler because the example doesn't use QML, but more importantly so that -# the source code of the .js files is not removed from the embedded qrc file. -CONFIG -= qtquickcompiler - -FORMS += \ - mainwindow.ui - -DISTFILES += \ - resources/3rdparty/MARKDOWN-LICENSE.txt \ - resources/3rdparty/MARKED-LICENSE.txt - -# install -target.path = $$[QT_INSTALL_EXAMPLES]/webenginewidgets/markdowneditor -INSTALLS += target diff --git a/examples/webenginewidgets/markdowneditor/previewpage.cpp b/examples/webenginewidgets/markdowneditor/previewpage.cpp deleted file mode 100644 index 17249fdb0..000000000 --- a/examples/webenginewidgets/markdowneditor/previewpage.cpp +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "previewpage.h" - -#include <QDesktopServices> - -bool PreviewPage::acceptNavigationRequest(const QUrl &url, - QWebEnginePage::NavigationType /*type*/, - bool /*isMainFrame*/) -{ - // Only allow qrc:/index.html. - if (url.scheme() == QString("qrc")) - return true; - QDesktopServices::openUrl(url); - return false; -} diff --git a/examples/webenginewidgets/markdowneditor/previewpage.h b/examples/webenginewidgets/markdowneditor/previewpage.h deleted file mode 100644 index 4a5d98c7c..000000000 --- a/examples/webenginewidgets/markdowneditor/previewpage.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef PREVIEWPAGE_H -#define PREVIEWPAGE_H - -#include <QWebEnginePage> - -class PreviewPage : public QWebEnginePage -{ - Q_OBJECT -public: - using QWebEnginePage::QWebEnginePage; - -protected: - bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) override; -}; - -#endif // PREVIEWPAGE_H diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKDOWN-LICENSE.txt b/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKDOWN-LICENSE.txt deleted file mode 100644 index 23c52cc43..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKDOWN-LICENSE.txt +++ /dev/null @@ -1,16 +0,0 @@ -Copyright 2011 Kevin Burke unless otherwise noted. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -Some content is copyrighted by Twitter, Inc., and also released under an -Apache License; these sections are noted in the source. diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKED-LICENSE.txt b/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKED-LICENSE.txt deleted file mode 100644 index 8e3ba0e0a..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKED-LICENSE.txt +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/) - -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: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/markdown.css b/examples/webenginewidgets/markdowneditor/resources/3rdparty/markdown.css deleted file mode 100644 index 24fc2ffe2..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/3rdparty/markdown.css +++ /dev/null @@ -1,260 +0,0 @@ -body{ - margin: 0 auto; - font-family: Georgia, Palatino, serif; - color: #444444; - line-height: 1; - max-width: 960px; - padding: 30px; -} -h1, h2, h3, h4 { - color: #111111; - font-weight: 400; -} -h1, h2, h3, h4, h5, p { - margin-bottom: 24px; - padding: 0; -} -h1 { - font-size: 48px; -} -h2 { - font-size: 36px; - /* The bottom margin is small. It's designed to be used with gray meta text - * below a post title. */ - margin: 24px 0 6px; -} -h3 { - font-size: 24px; -} -h4 { - font-size: 21px; -} -h5 { - font-size: 18px; -} -a { - color: #0099ff; - margin: 0; - padding: 0; - vertical-align: baseline; -} -a:hover { - text-decoration: none; - color: #ff6600; -} -a:visited { - color: purple; -} -ul, ol { - padding: 0; - margin: 0; -} -li { - line-height: 24px; -} -li ul, li ul { - margin-left: 24px; -} -p, ul, ol { - font-size: 16px; - line-height: 24px; - max-width: 540px; -} -pre { - padding: 0px 24px; - max-width: 800px; - white-space: pre-wrap; -} -code { - font-family: Consolas, Monaco, Andale Mono, monospace; - line-height: 1.5; - font-size: 13px; -} -aside { - display: block; - float: right; - width: 390px; -} -blockquote { - border-left:.5em solid #eee; - padding: 0 2em; - margin-left:0; - max-width: 476px; -} -blockquote cite { - font-size:14px; - line-height:20px; - color:#bfbfbf; -} -blockquote cite:before { - content: '\2014 \00A0'; -} - -blockquote p { - color: #666; - max-width: 460px; -} -hr { - width: 540px; - text-align: left; - margin: 0 auto 0 0; - color: #999; -} - -/* Code below this line is copyright Twitter Inc. */ - -button, -input, -select, -textarea { - font-size: 100%; - margin: 0; - vertical-align: baseline; - *vertical-align: middle; -} -button, input { - line-height: normal; - *overflow: visible; -} -button::-moz-focus-inner, input::-moz-focus-inner { - border: 0; - padding: 0; -} -button, -input[type="button"], -input[type="reset"], -input[type="submit"] { - cursor: pointer; - -webkit-appearance: button; -} -input[type=checkbox], input[type=radio] { - cursor: pointer; -} -/* override default chrome & firefox settings */ -input:not([type="image"]), textarea { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -input[type="search"] { - -webkit-appearance: textfield; - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; -} -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} -label, -input, -select, -textarea { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - font-weight: normal; - line-height: normal; - margin-bottom: 18px; -} -input[type=checkbox], input[type=radio] { - cursor: pointer; - margin-bottom: 0; -} -input[type=text], -input[type=password], -textarea, -select { - display: inline-block; - width: 210px; - padding: 4px; - font-size: 13px; - font-weight: normal; - line-height: 18px; - height: 18px; - color: #808080; - border: 1px solid #ccc; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} -select, input[type=file] { - height: 27px; - line-height: 27px; -} -textarea { - height: auto; -} - -/* grey out placeholders */ -:-moz-placeholder { - color: #bfbfbf; -} -::-webkit-input-placeholder { - color: #bfbfbf; -} - -input[type=text], -input[type=password], -select, -textarea { - -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; - -moz-transition: border linear 0.2s, box-shadow linear 0.2s; - transition: border linear 0.2s, box-shadow linear 0.2s; - -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); - -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); - box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); -} -input[type=text]:focus, input[type=password]:focus, textarea:focus { - outline: none; - border-color: rgba(82, 168, 236, 0.8); - -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); - -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); - box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); -} - -/* buttons */ -button { - display: inline-block; - padding: 4px 14px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - line-height: 18px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - background-color: #0064cd; - background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd)); - background-image: -moz-linear-gradient(top, #049cdb, #0064cd); - background-image: -ms-linear-gradient(top, #049cdb, #0064cd); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd)); - background-image: -webkit-linear-gradient(top, #049cdb, #0064cd); - background-image: -o-linear-gradient(top, #049cdb, #0064cd); - background-image: linear-gradient(top, #049cdb, #0064cd); - color: #fff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - border: 1px solid #004b9a; - border-bottom-color: #003f81; - -webkit-transition: 0.1s linear all; - -moz-transition: 0.1s linear all; - transition: 0.1s linear all; - border-color: #0064cd #0064cd #003f81; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); -} -button:hover { - color: #fff; - background-position: 0 -15px; - text-decoration: none; -} -button:active { - -webkit-box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); -} -button::-moz-focus-inner { - padding: 0; - border: 0; -} diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js b/examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js deleted file mode 100644 index 33c02d9cf..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js +++ /dev/null @@ -1,1514 +0,0 @@ -/** - * marked - a markdown parser - * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) - * https://github.com/markedjs/marked - */ - -;(function(root) { -'use strict'; - -/** - * Block-Level Grammar - */ - -var block = { - newline: /^\n+/, - code: /^( {4}[^\n]+\n*)+/, - fences: noop, - hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, - heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/, - nptable: noop, - blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, - list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, - html: '^ {0,3}(?:' // optional indentation - + '<(script|pre|style)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)' // (1) - + '|comment[^\\n]*(\\n+|$)' // (2) - + '|<\\?[\\s\\S]*?\\?>\\n*' // (3) - + '|<![A-Z][\\s\\S]*?>\\n*' // (4) - + '|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>\\n*' // (5) - + '|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:\\n{2,}|$)' // (6) - + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) open tag - + '|</(?!script|pre|style)[a-z][\\w-]*\\s*>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) closing tag - + ')', - def: /^ {0,3}\[(label)\]: *\n? *<?([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/, - table: noop, - lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, - paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)*)/, - text: /^[^\n]+/ -}; - -block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/; -block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/; -block.def = edit(block.def) - .replace('label', block._label) - .replace('title', block._title) - .getRegex(); - -block.bullet = /(?:[*+-]|\d+\.)/; -block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; -block.item = edit(block.item, 'gm') - .replace(/bull/g, block.bullet) - .getRegex(); - -block.list = edit(block.list) - .replace(/bull/g, block.bullet) - .replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))') - .replace('def', '\\n+(?=' + block.def.source + ')') - .getRegex(); - -block._tag = 'address|article|aside|base|basefont|blockquote|body|caption' - + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption' - + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe' - + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' - + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr' - + '|track|ul'; -block._comment = /<!--(?!-?>)[\s\S]*?-->/; -block.html = edit(block.html, 'i') - .replace('comment', block._comment) - .replace('tag', block._tag) - .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/) - .getRegex(); - -block.paragraph = edit(block.paragraph) - .replace('hr', block.hr) - .replace('heading', block.heading) - .replace('lheading', block.lheading) - .replace('tag', block._tag) // pars can be interrupted by type (6) html blocks - .getRegex(); - -block.blockquote = edit(block.blockquote) - .replace('paragraph', block.paragraph) - .getRegex(); - -/** - * Normal Block Grammar - */ - -block.normal = merge({}, block); - -/** - * GFM Block Grammar - */ - -block.gfm = merge({}, block.normal, { - fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/, - paragraph: /^/, - heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/ -}); - -block.gfm.paragraph = edit(block.paragraph) - .replace('(?!', '(?!' - + block.gfm.fences.source.replace('\\1', '\\2') + '|' - + block.list.source.replace('\\1', '\\3') + '|') - .getRegex(); - -/** - * GFM + Tables Block Grammar - */ - -block.tables = merge({}, block.gfm, { - nptable: /^ *([^|\n ].*\|.*)\n *([-:]+ *\|[-| :]*)(?:\n((?:.*[^>\n ].*(?:\n|$))*)\n*|$)/, - table: /^ *\|(.+)\n *\|?( *[-:]+[-| :]*)(?:\n((?: *[^>\n ].*(?:\n|$))*)\n*|$)/ -}); - -/** - * Pedantic grammar - */ - -block.pedantic = merge({}, block.normal, { - html: edit( - '^ *(?:comment *(?:\\n|\\s*$)' - + '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag - + '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))') - .replace('comment', block._comment) - .replace(/tag/g, '(?!(?:' - + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' - + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' - + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b') - .getRegex(), - def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/ -}); - -/** - * Block Lexer - */ - -function Lexer(options) { - this.tokens = []; - this.tokens.links = {}; - this.options = options || marked.defaults; - this.rules = block.normal; - - if (this.options.pedantic) { - this.rules = block.pedantic; - } else if (this.options.gfm) { - if (this.options.tables) { - this.rules = block.tables; - } else { - this.rules = block.gfm; - } - } -} - -/** - * Expose Block Rules - */ - -Lexer.rules = block; - -/** - * Static Lex Method - */ - -Lexer.lex = function(src, options) { - var lexer = new Lexer(options); - return lexer.lex(src); -}; - -/** - * Preprocessing - */ - -Lexer.prototype.lex = function(src) { - src = src - .replace(/\r\n|\r/g, '\n') - .replace(/\t/g, ' ') - .replace(/\u00a0/g, ' ') - .replace(/\u2424/g, '\n'); - - return this.token(src, true); -}; - -/** - * Lexing - */ - -Lexer.prototype.token = function(src, top) { - src = src.replace(/^ +$/gm, ''); - var next, - loose, - cap, - bull, - b, - item, - space, - i, - tag, - l, - isordered, - istask, - ischecked; - - while (src) { - // newline - if (cap = this.rules.newline.exec(src)) { - src = src.substring(cap[0].length); - if (cap[0].length > 1) { - this.tokens.push({ - type: 'space' - }); - } - } - - // code - if (cap = this.rules.code.exec(src)) { - src = src.substring(cap[0].length); - cap = cap[0].replace(/^ {4}/gm, ''); - this.tokens.push({ - type: 'code', - text: !this.options.pedantic - ? cap.replace(/\n+$/, '') - : cap - }); - continue; - } - - // fences (gfm) - if (cap = this.rules.fences.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'code', - lang: cap[2], - text: cap[3] || '' - }); - continue; - } - - // heading - if (cap = this.rules.heading.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'heading', - depth: cap[1].length, - text: cap[2] - }); - continue; - } - - // table no leading pipe (gfm) - if (top && (cap = this.rules.nptable.exec(src))) { - item = { - type: 'table', - header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), - align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [] - }; - - if (item.header.length === item.align.length) { - src = src.substring(cap[0].length); - - for (i = 0; i < item.align.length; i++) { - if (/^ *-+: *$/.test(item.align[i])) { - item.align[i] = 'right'; - } else if (/^ *:-+: *$/.test(item.align[i])) { - item.align[i] = 'center'; - } else if (/^ *:-+ *$/.test(item.align[i])) { - item.align[i] = 'left'; - } else { - item.align[i] = null; - } - } - - for (i = 0; i < item.cells.length; i++) { - item.cells[i] = splitCells(item.cells[i], item.header.length); - } - - this.tokens.push(item); - - continue; - } - } - - // hr - if (cap = this.rules.hr.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'hr' - }); - continue; - } - - // blockquote - if (cap = this.rules.blockquote.exec(src)) { - src = src.substring(cap[0].length); - - this.tokens.push({ - type: 'blockquote_start' - }); - - cap = cap[0].replace(/^ *> ?/gm, ''); - - // Pass `top` to keep the current - // "toplevel" state. This is exactly - // how markdown.pl works. - this.token(cap, top); - - this.tokens.push({ - type: 'blockquote_end' - }); - - continue; - } - - // list - if (cap = this.rules.list.exec(src)) { - src = src.substring(cap[0].length); - bull = cap[2]; - isordered = bull.length > 1; - - this.tokens.push({ - type: 'list_start', - ordered: isordered, - start: isordered ? +bull : '' - }); - - // Get each top-level item. - cap = cap[0].match(this.rules.item); - - next = false; - l = cap.length; - i = 0; - - for (; i < l; i++) { - item = cap[i]; - - // Remove the list item's bullet - // so it is seen as the next token. - space = item.length; - item = item.replace(/^ *([*+-]|\d+\.) +/, ''); - - // Outdent whatever the - // list item contains. Hacky. - if (~item.indexOf('\n ')) { - space -= item.length; - item = !this.options.pedantic - ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') - : item.replace(/^ {1,4}/gm, ''); - } - - // Determine whether the next list item belongs here. - // Backpedal if it does not belong in this list. - if (this.options.smartLists && i !== l - 1) { - b = block.bullet.exec(cap[i + 1])[0]; - if (bull !== b && !(bull.length > 1 && b.length > 1)) { - src = cap.slice(i + 1).join('\n') + src; - i = l - 1; - } - } - - // Determine whether item is loose or not. - // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ - // for discount behavior. - loose = next || /\n\n(?!\s*$)/.test(item); - if (i !== l - 1) { - next = item.charAt(item.length - 1) === '\n'; - if (!loose) loose = next; - } - - // Check for task list items - istask = /^\[[ xX]\] /.test(item); - ischecked = undefined; - if (istask) { - ischecked = item[1] !== ' '; - item = item.replace(/^\[[ xX]\] +/, ''); - } - - this.tokens.push({ - type: loose - ? 'loose_item_start' - : 'list_item_start', - task: istask, - checked: ischecked - }); - - // Recurse. - this.token(item, false); - - this.tokens.push({ - type: 'list_item_end' - }); - } - - this.tokens.push({ - type: 'list_end' - }); - - continue; - } - - // html - if (cap = this.rules.html.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: this.options.sanitize - ? 'paragraph' - : 'html', - pre: !this.options.sanitizer - && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), - text: cap[0] - }); - continue; - } - - // def - if (top && (cap = this.rules.def.exec(src))) { - src = src.substring(cap[0].length); - if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1); - tag = cap[1].toLowerCase().replace(/\s+/g, ' '); - if (!this.tokens.links[tag]) { - this.tokens.links[tag] = { - href: cap[2], - title: cap[3] - }; - } - continue; - } - - // table (gfm) - if (top && (cap = this.rules.table.exec(src))) { - item = { - type: 'table', - header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), - align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - cells: cap[3] ? cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') : [] - }; - - if (item.header.length === item.align.length) { - src = src.substring(cap[0].length); - - for (i = 0; i < item.align.length; i++) { - if (/^ *-+: *$/.test(item.align[i])) { - item.align[i] = 'right'; - } else if (/^ *:-+: *$/.test(item.align[i])) { - item.align[i] = 'center'; - } else if (/^ *:-+ *$/.test(item.align[i])) { - item.align[i] = 'left'; - } else { - item.align[i] = null; - } - } - - for (i = 0; i < item.cells.length; i++) { - item.cells[i] = splitCells( - item.cells[i].replace(/^ *\| *| *\| *$/g, ''), - item.header.length); - } - - this.tokens.push(item); - - continue; - } - } - - // lheading - if (cap = this.rules.lheading.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'heading', - depth: cap[2] === '=' ? 1 : 2, - text: cap[1] - }); - continue; - } - - // top-level paragraph - if (top && (cap = this.rules.paragraph.exec(src))) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'paragraph', - text: cap[1].charAt(cap[1].length - 1) === '\n' - ? cap[1].slice(0, -1) - : cap[1] - }); - continue; - } - - // text - if (cap = this.rules.text.exec(src)) { - // Top-level should never reach here. - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'text', - text: cap[0] - }); - continue; - } - - if (src) { - throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); - } - } - - return this.tokens; -}; - -/** - * Inline-Level Grammar - */ - -var inline = { - escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/, - autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/, - url: noop, - tag: '^comment' - + '|^</[a-zA-Z][\\w:-]*\\s*>' // self-closing tag - + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag - + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. <?php ?> - + '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // declaration, e.g. <!DOCTYPE html> - + '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>', // CDATA section - link: /^!?\[(label)\]\(href(?:\s+(title))?\s*\)/, - reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/, - nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/, - strong: /^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)|^__([^\s])__(?!_)|^\*\*([^\s])\*\*(?!\*)/, - em: /^_([^\s][\s\S]*?[^\s_])_(?!_)|^_([^\s_][\s\S]*?[^\s])_(?!_)|^\*([^\s][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*][\s\S]*?[^\s])\*(?!\*)|^_([^\s_])_(?!_)|^\*([^\s*])\*(?!\*)/, - code: /^(`+)\s*([\s\S]*?[^`]?)\s*\1(?!`)/, - br: /^ {2,}\n(?!\s*$)/, - del: noop, - text: /^[\s\S]+?(?=[\\<!\[`*]|\b_| {2,}\n|$)/ -}; - -inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g; - -inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/; -inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/; -inline.autolink = edit(inline.autolink) - .replace('scheme', inline._scheme) - .replace('email', inline._email) - .getRegex(); - -inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/; - -inline.tag = edit(inline.tag) - .replace('comment', block._comment) - .replace('attribute', inline._attribute) - .getRegex(); - -inline._label = /(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?/; -inline._href = /\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f()\\]*\)|[^\s\x00-\x1f()\\])*?)/; -inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/; - -inline.link = edit(inline.link) - .replace('label', inline._label) - .replace('href', inline._href) - .replace('title', inline._title) - .getRegex(); - -inline.reflink = edit(inline.reflink) - .replace('label', inline._label) - .getRegex(); - -/** - * Normal Inline Grammar - */ - -inline.normal = merge({}, inline); - -/** - * Pedantic Inline Grammar - */ - -inline.pedantic = merge({}, inline.normal, { - strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, - em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/, - link: edit(/^!?\[(label)\]\((.*?)\)/) - .replace('label', inline._label) - .getRegex(), - reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/) - .replace('label', inline._label) - .getRegex() -}); - -/** - * GFM Inline Grammar - */ - -inline.gfm = merge({}, inline.normal, { - escape: edit(inline.escape).replace('])', '~|])').getRegex(), - url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/) - .replace('email', inline._email) - .getRegex(), - _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/, - del: /^~~(?=\S)([\s\S]*?\S)~~/, - text: edit(inline.text) - .replace(']|', '~]|') - .replace('|', '|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&\'*+/=?^_`{\\|}~-]+@|') - .getRegex() -}); - -/** - * GFM + Line Breaks Inline Grammar - */ - -inline.breaks = merge({}, inline.gfm, { - br: edit(inline.br).replace('{2,}', '*').getRegex(), - text: edit(inline.gfm.text).replace('{2,}', '*').getRegex() -}); - -/** - * Inline Lexer & Compiler - */ - -function InlineLexer(links, options) { - this.options = options || marked.defaults; - this.links = links; - this.rules = inline.normal; - this.renderer = this.options.renderer || new Renderer(); - this.renderer.options = this.options; - - if (!this.links) { - throw new Error('Tokens array requires a `links` property.'); - } - - if (this.options.pedantic) { - this.rules = inline.pedantic; - } else if (this.options.gfm) { - if (this.options.breaks) { - this.rules = inline.breaks; - } else { - this.rules = inline.gfm; - } - } -} - -/** - * Expose Inline Rules - */ - -InlineLexer.rules = inline; - -/** - * Static Lexing/Compiling Method - */ - -InlineLexer.output = function(src, links, options) { - var inline = new InlineLexer(links, options); - return inline.output(src); -}; - -/** - * Lexing/Compiling - */ - -InlineLexer.prototype.output = function(src) { - var out = '', - link, - text, - href, - title, - cap; - - while (src) { - // escape - if (cap = this.rules.escape.exec(src)) { - src = src.substring(cap[0].length); - out += cap[1]; - continue; - } - - // autolink - if (cap = this.rules.autolink.exec(src)) { - src = src.substring(cap[0].length); - if (cap[2] === '@') { - text = escape(this.mangle(cap[1])); - href = 'mailto:' + text; - } else { - text = escape(cap[1]); - href = text; - } - out += this.renderer.link(href, null, text); - continue; - } - - // url (gfm) - if (!this.inLink && (cap = this.rules.url.exec(src))) { - cap[0] = this.rules._backpedal.exec(cap[0])[0]; - src = src.substring(cap[0].length); - if (cap[2] === '@') { - text = escape(cap[0]); - href = 'mailto:' + text; - } else { - text = escape(cap[0]); - if (cap[1] === 'www.') { - href = 'http://' + text; - } else { - href = text; - } - } - out += this.renderer.link(href, null, text); - continue; - } - - // tag - if (cap = this.rules.tag.exec(src)) { - if (!this.inLink && /^<a /i.test(cap[0])) { - this.inLink = true; - } else if (this.inLink && /^<\/a>/i.test(cap[0])) { - this.inLink = false; - } - src = src.substring(cap[0].length); - out += this.options.sanitize - ? this.options.sanitizer - ? this.options.sanitizer(cap[0]) - : escape(cap[0]) - : cap[0] - continue; - } - - // link - if (cap = this.rules.link.exec(src)) { - src = src.substring(cap[0].length); - this.inLink = true; - href = cap[2]; - if (this.options.pedantic) { - link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href); - - if (link) { - href = link[1]; - title = link[3]; - } else { - title = ''; - } - } else { - title = cap[3] ? cap[3].slice(1, -1) : ''; - } - href = href.trim().replace(/^<([\s\S]*)>$/, '$1'); - out += this.outputLink(cap, { - href: InlineLexer.escapes(href), - title: InlineLexer.escapes(title) - }); - this.inLink = false; - continue; - } - - // reflink, nolink - if ((cap = this.rules.reflink.exec(src)) - || (cap = this.rules.nolink.exec(src))) { - src = src.substring(cap[0].length); - link = (cap[2] || cap[1]).replace(/\s+/g, ' '); - link = this.links[link.toLowerCase()]; - if (!link || !link.href) { - out += cap[0].charAt(0); - src = cap[0].substring(1) + src; - continue; - } - this.inLink = true; - out += this.outputLink(cap, link); - this.inLink = false; - continue; - } - - // strong - if (cap = this.rules.strong.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.strong(this.output(cap[4] || cap[3] || cap[2] || cap[1])); - continue; - } - - // em - if (cap = this.rules.em.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.em(this.output(cap[6] || cap[5] || cap[4] || cap[3] || cap[2] || cap[1])); - continue; - } - - // code - if (cap = this.rules.code.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.codespan(escape(cap[2].trim(), true)); - continue; - } - - // br - if (cap = this.rules.br.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.br(); - continue; - } - - // del (gfm) - if (cap = this.rules.del.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.del(this.output(cap[1])); - continue; - } - - // text - if (cap = this.rules.text.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.text(escape(this.smartypants(cap[0]))); - continue; - } - - if (src) { - throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); - } - } - - return out; -}; - -InlineLexer.escapes = function(text) { - return text ? text.replace(InlineLexer.rules._escapes, '$1') : text; -} - -/** - * Compile Link - */ - -InlineLexer.prototype.outputLink = function(cap, link) { - var href = link.href, - title = link.title ? escape(link.title) : null; - - return cap[0].charAt(0) !== '!' - ? this.renderer.link(href, title, this.output(cap[1])) - : this.renderer.image(href, title, escape(cap[1])); -}; - -/** - * Smartypants Transformations - */ - -InlineLexer.prototype.smartypants = function(text) { - if (!this.options.smartypants) return text; - return text - // em-dashes - .replace(/---/g, '\u2014') - // en-dashes - .replace(/--/g, '\u2013') - // opening singles - .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') - // closing singles & apostrophes - .replace(/'/g, '\u2019') - // opening doubles - .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') - // closing doubles - .replace(/"/g, '\u201d') - // ellipses - .replace(/\.{3}/g, '\u2026'); -}; - -/** - * Mangle Links - */ - -InlineLexer.prototype.mangle = function(text) { - if (!this.options.mangle) return text; - var out = '', - l = text.length, - i = 0, - ch; - - for (; i < l; i++) { - ch = text.charCodeAt(i); - if (Math.random() > 0.5) { - ch = 'x' + ch.toString(16); - } - out += '&#' + ch + ';'; - } - - return out; -}; - -/** - * Renderer - */ - -function Renderer(options) { - this.options = options || marked.defaults; -} - -Renderer.prototype.code = function(code, lang, escaped) { - if (this.options.highlight) { - var out = this.options.highlight(code, lang); - if (out != null && out !== code) { - escaped = true; - code = out; - } - } - - if (!lang) { - return '<pre><code>' - + (escaped ? code : escape(code, true)) - + '</code></pre>'; - } - - return '<pre><code class="' - + this.options.langPrefix - + escape(lang, true) - + '">' - + (escaped ? code : escape(code, true)) - + '</code></pre>\n'; -}; - -Renderer.prototype.blockquote = function(quote) { - return '<blockquote>\n' + quote + '</blockquote>\n'; -}; - -Renderer.prototype.html = function(html) { - return html; -}; - -Renderer.prototype.heading = function(text, level, raw) { - if (this.options.headerIds) { - return '<h' - + level - + ' id="' - + this.options.headerPrefix - + raw.toLowerCase().replace(/[^\w]+/g, '-') - + '">' - + text - + '</h' - + level - + '>\n'; - } - // ignore IDs - return '<h' + level + '>' + text + '</h' + level + '>\n'; -}; - -Renderer.prototype.hr = function() { - return this.options.xhtml ? '<hr/>\n' : '<hr>\n'; -}; - -Renderer.prototype.list = function(body, ordered, start) { - var type = ordered ? 'ol' : 'ul', - startatt = (ordered && start !== 1) ? (' start="' + start + '"') : ''; - return '<' + type + startatt + '>\n' + body + '</' + type + '>\n'; -}; - -Renderer.prototype.listitem = function(text) { - return '<li>' + text + '</li>\n'; -}; - -Renderer.prototype.checkbox = function(checked) { - return '<input ' - + (checked ? 'checked="" ' : '') - + 'disabled="" type="checkbox"' - + (this.options.xhtml ? ' /' : '') - + '> '; -} - -Renderer.prototype.paragraph = function(text) { - return '<p>' + text + '</p>\n'; -}; - -Renderer.prototype.table = function(header, body) { - if (body) body = '<tbody>' + body + '</tbody>'; - - return '<table>\n' - + '<thead>\n' - + header - + '</thead>\n' - + body - + '</table>\n'; -}; - -Renderer.prototype.tablerow = function(content) { - return '<tr>\n' + content + '</tr>\n'; -}; - -Renderer.prototype.tablecell = function(content, flags) { - var type = flags.header ? 'th' : 'td'; - var tag = flags.align - ? '<' + type + ' align="' + flags.align + '">' - : '<' + type + '>'; - return tag + content + '</' + type + '>\n'; -}; - -// span level renderer -Renderer.prototype.strong = function(text) { - return '<strong>' + text + '</strong>'; -}; - -Renderer.prototype.em = function(text) { - return '<em>' + text + '</em>'; -}; - -Renderer.prototype.codespan = function(text) { - return '<code>' + text + '</code>'; -}; - -Renderer.prototype.br = function() { - return this.options.xhtml ? '<br/>' : '<br>'; -}; - -Renderer.prototype.del = function(text) { - return '<del>' + text + '</del>'; -}; - -Renderer.prototype.link = function(href, title, text) { - if (this.options.sanitize) { - try { - var prot = decodeURIComponent(unescape(href)) - .replace(/[^\w:]/g, '') - .toLowerCase(); - } catch (e) { - return text; - } - if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { - return text; - } - } - if (this.options.baseUrl && !originIndependentUrl.test(href)) { - href = resolveUrl(this.options.baseUrl, href); - } - try { - href = encodeURI(href).replace(/%25/g, '%'); - } catch (e) { - return text; - } - var out = '<a href="' + escape(href) + '"'; - if (title) { - out += ' title="' + title + '"'; - } - out += '>' + text + '</a>'; - return out; -}; - -Renderer.prototype.image = function(href, title, text) { - if (this.options.baseUrl && !originIndependentUrl.test(href)) { - href = resolveUrl(this.options.baseUrl, href); - } - var out = '<img src="' + href + '" alt="' + text + '"'; - if (title) { - out += ' title="' + title + '"'; - } - out += this.options.xhtml ? '/>' : '>'; - return out; -}; - -Renderer.prototype.text = function(text) { - return text; -}; - -/** - * TextRenderer - * returns only the textual part of the token - */ - -function TextRenderer() {} - -// no need for block level renderers - -TextRenderer.prototype.strong = -TextRenderer.prototype.em = -TextRenderer.prototype.codespan = -TextRenderer.prototype.del = -TextRenderer.prototype.text = function (text) { - return text; -} - -TextRenderer.prototype.link = -TextRenderer.prototype.image = function(href, title, text) { - return '' + text; -} - -TextRenderer.prototype.br = function() { - return ''; -} - -/** - * Parsing & Compiling - */ - -function Parser(options) { - this.tokens = []; - this.token = null; - this.options = options || marked.defaults; - this.options.renderer = this.options.renderer || new Renderer(); - this.renderer = this.options.renderer; - this.renderer.options = this.options; -} - -/** - * Static Parse Method - */ - -Parser.parse = function(src, options) { - var parser = new Parser(options); - return parser.parse(src); -}; - -/** - * Parse Loop - */ - -Parser.prototype.parse = function(src) { - this.inline = new InlineLexer(src.links, this.options); - // use an InlineLexer with a TextRenderer to extract pure text - this.inlineText = new InlineLexer( - src.links, - merge({}, this.options, {renderer: new TextRenderer()}) - ); - this.tokens = src.reverse(); - - var out = ''; - while (this.next()) { - out += this.tok(); - } - - return out; -}; - -/** - * Next Token - */ - -Parser.prototype.next = function() { - return this.token = this.tokens.pop(); -}; - -/** - * Preview Next Token - */ - -Parser.prototype.peek = function() { - return this.tokens[this.tokens.length - 1] || 0; -}; - -/** - * Parse Text Tokens - */ - -Parser.prototype.parseText = function() { - var body = this.token.text; - - while (this.peek().type === 'text') { - body += '\n' + this.next().text; - } - - return this.inline.output(body); -}; - -/** - * Parse Current Token - */ - -Parser.prototype.tok = function() { - switch (this.token.type) { - case 'space': { - return ''; - } - case 'hr': { - return this.renderer.hr(); - } - case 'heading': { - return this.renderer.heading( - this.inline.output(this.token.text), - this.token.depth, - unescape(this.inlineText.output(this.token.text))); - } - case 'code': { - return this.renderer.code(this.token.text, - this.token.lang, - this.token.escaped); - } - case 'table': { - var header = '', - body = '', - i, - row, - cell, - j; - - // header - cell = ''; - for (i = 0; i < this.token.header.length; i++) { - cell += this.renderer.tablecell( - this.inline.output(this.token.header[i]), - { header: true, align: this.token.align[i] } - ); - } - header += this.renderer.tablerow(cell); - - for (i = 0; i < this.token.cells.length; i++) { - row = this.token.cells[i]; - - cell = ''; - for (j = 0; j < row.length; j++) { - cell += this.renderer.tablecell( - this.inline.output(row[j]), - { header: false, align: this.token.align[j] } - ); - } - - body += this.renderer.tablerow(cell); - } - return this.renderer.table(header, body); - } - case 'blockquote_start': { - body = ''; - - while (this.next().type !== 'blockquote_end') { - body += this.tok(); - } - - return this.renderer.blockquote(body); - } - case 'list_start': { - body = ''; - var ordered = this.token.ordered, - start = this.token.start; - - while (this.next().type !== 'list_end') { - body += this.tok(); - } - - return this.renderer.list(body, ordered, start); - } - case 'list_item_start': { - body = ''; - - if (this.token.task) { - body += this.renderer.checkbox(this.token.checked); - } - - while (this.next().type !== 'list_item_end') { - body += this.token.type === 'text' - ? this.parseText() - : this.tok(); - } - - return this.renderer.listitem(body); - } - case 'loose_item_start': { - body = ''; - - while (this.next().type !== 'list_item_end') { - body += this.tok(); - } - - return this.renderer.listitem(body); - } - case 'html': { - // TODO parse inline content if parameter markdown=1 - return this.renderer.html(this.token.text); - } - case 'paragraph': { - return this.renderer.paragraph(this.inline.output(this.token.text)); - } - case 'text': { - return this.renderer.paragraph(this.parseText()); - } - } -}; - -/** - * Helpers - */ - -function escape(html, encode) { - return html - .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); -} - -function unescape(html) { - // explicitly match decimal, hex, and named HTML entities - return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function(_, n) { - n = n.toLowerCase(); - if (n === 'colon') return ':'; - if (n.charAt(0) === '#') { - return n.charAt(1) === 'x' - ? String.fromCharCode(parseInt(n.substring(2), 16)) - : String.fromCharCode(+n.substring(1)); - } - return ''; - }); -} - -function edit(regex, opt) { - regex = regex.source || regex; - opt = opt || ''; - return { - replace: function(name, val) { - val = val.source || val; - val = val.replace(/(^|[^\[])\^/g, '$1'); - regex = regex.replace(name, val); - return this; - }, - getRegex: function() { - return new RegExp(regex, opt); - } - }; -} - -function resolveUrl(base, href) { - if (!baseUrls[' ' + base]) { - // we can ignore everything in base after the last slash of its path component, - // but we might need to add _that_ - // https://tools.ietf.org/html/rfc3986#section-3 - if (/^[^:]+:\/*[^/]*$/.test(base)) { - baseUrls[' ' + base] = base + '/'; - } else { - baseUrls[' ' + base] = base.replace(/[^/]*$/, ''); - } - } - base = baseUrls[' ' + base]; - - if (href.slice(0, 2) === '//') { - return base.replace(/:[\s\S]*/, ':') + href; - } else if (href.charAt(0) === '/') { - return base.replace(/(:\/*[^/]*)[\s\S]*/, '$1') + href; - } else { - return base + href; - } -} -var baseUrls = {}; -var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; - -function noop() {} -noop.exec = noop; - -function merge(obj) { - var i = 1, - target, - key; - - for (; i < arguments.length; i++) { - target = arguments[i]; - for (key in target) { - if (Object.prototype.hasOwnProperty.call(target, key)) { - obj[key] = target[key]; - } - } - } - - return obj; -} - -function splitCells(tableRow, count) { - var cells = tableRow.replace(/([^\\])\|/g, '$1 |').split(/ +\| */), - i = 0; - - if (cells.length > count) { - cells.splice(count); - } else { - while (cells.length < count) cells.push(''); - } - - for (; i < cells.length; i++) { - cells[i] = cells[i].replace(/\\\|/g, '|'); - } - return cells; -} - -/** - * Marked - */ - -function marked(src, opt, callback) { - // throw error in case of non string input - if (typeof src === 'undefined' || src === null) { - throw new Error('marked(): input parameter is undefined or null'); - } - if (typeof src !== 'string') { - throw new Error('marked(): input parameter is of type ' - + Object.prototype.toString.call(src) + ', string expected'); - } - - if (callback || typeof opt === 'function') { - if (!callback) { - callback = opt; - opt = null; - } - - opt = merge({}, marked.defaults, opt || {}); - - var highlight = opt.highlight, - tokens, - pending, - i = 0; - - try { - tokens = Lexer.lex(src, opt) - } catch (e) { - return callback(e); - } - - pending = tokens.length; - - var done = function(err) { - if (err) { - opt.highlight = highlight; - return callback(err); - } - - var out; - - try { - out = Parser.parse(tokens, opt); - } catch (e) { - err = e; - } - - opt.highlight = highlight; - - return err - ? callback(err) - : callback(null, out); - }; - - if (!highlight || highlight.length < 3) { - return done(); - } - - delete opt.highlight; - - if (!pending) return done(); - - for (; i < tokens.length; i++) { - (function(token) { - if (token.type !== 'code') { - return --pending || done(); - } - return highlight(token.text, token.lang, function(err, code) { - if (err) return done(err); - if (code == null || code === token.text) { - return --pending || done(); - } - token.text = code; - token.escaped = true; - --pending || done(); - }); - })(tokens[i]); - } - - return; - } - try { - if (opt) opt = merge({}, marked.defaults, opt); - return Parser.parse(Lexer.lex(src, opt), opt); - } catch (e) { - e.message += '\nPlease report this to https://github.com/markedjs/marked.'; - if ((opt || marked.defaults).silent) { - return '<p>An error occurred:</p><pre>' - + escape(e.message + '', true) - + '</pre>'; - } - throw e; - } -} - -/** - * Options - */ - -marked.options = -marked.setOptions = function(opt) { - merge(marked.defaults, opt); - return marked; -}; - -marked.getDefaults = function () { - return { - baseUrl: null, - breaks: false, - gfm: true, - headerIds: true, - headerPrefix: '', - highlight: null, - langPrefix: 'language-', - mangle: true, - pedantic: false, - renderer: new Renderer(), - sanitize: false, - sanitizer: null, - silent: false, - smartLists: false, - smartypants: false, - tables: true, - xhtml: false - }; -} - -marked.defaults = marked.getDefaults(); - -/** - * Expose - */ - -marked.Parser = Parser; -marked.parser = Parser.parse; - -marked.Renderer = Renderer; -marked.TextRenderer = TextRenderer; - -marked.Lexer = Lexer; -marked.lexer = Lexer.lex; - -marked.InlineLexer = InlineLexer; -marked.inlineLexer = InlineLexer.output; - -marked.parse = marked; - -if (typeof module !== 'undefined' && typeof exports === 'object') { - module.exports = marked; -} else if (typeof define === 'function' && define.amd) { - define(function() { return marked; }); -} else { - root.marked = marked; -} -})(this || (typeof window !== 'undefined' ? window : global)); diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json b/examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json deleted file mode 100644 index d51ac744b..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "Id": "markdowneditor-marked", - "Name": "Marked (WebEngine Markdown Editor example)", - "QDocModule": "qtwebengine", - "QtUsage": "Marked is used in the WebEngine MarkDown Editor example", - "QtParts": [ "examples" ], - "Files": "marked.js", - "Description": "A full-featured markdown parser and compiler, written in JavaScript. Built for speed.", - "Homepage": "https://github.com/chjj/marked", - "Version": "0.4.0", - "DownloadLocation": "https://github.com/markedjs/marked/blob/0.4.0/lib/marked.js", - "Copyright": "Copyright (c) 2011-2018, Christopher Jeffrey", - "License": "MIT License", - "LicenseId": "MIT", - "LicenseFile": "MARKED-LICENSE.txt" - }, - { - "Id": "markdowneditor-markdowncss", - "Name": "Markdown.css (WebEngine Markdown Editor example)", - "QDocModule": "qtwebengine", - "QtUsage": "markdown.css is used in the WebEngine MarkDown Editor example", - "QtParts": [ "examples" ], - "Files": "markdown.css", - "Description": "Markdown.css is better default styling for your Markdown files.", - "Version": "188530e4b5d020d7e237fc6b26be13ebf4a8def3", - "DownloadLocation": "https://bitbucket.org/kevinburke/markdowncss/src/188530e4b5d020d7e237fc6b26be13ebf4a8def3/markdown.css", - "Copyright": "Copyright 2011 Kevin Burke - Copyright Twitter Inc.", - "License": "Apache License 2.0", - "LicenseId": "Apache-2.0", - "LicenseFile": "MARKDOWN-LICENSE.txt" - } -] diff --git a/examples/webenginewidgets/markdowneditor/resources/default.md b/examples/webenginewidgets/markdowneditor/resources/default.md deleted file mode 100644 index af835fa4d..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/default.md +++ /dev/null @@ -1,12 +0,0 @@ -## WebEngine Markdown Editor Example - -This example uses [QWebEngineView](http://doc.qt.io/qt-5/qwebengineview.html) -to preview text written using the [Markdown](https://en.wikipedia.org/wiki/Markdown) -syntax. - -### Acknowledgments - -The conversion from Markdown to HTML is done with the help of the -[marked JavaScript library](https://github.com/chjj/marked) by _Christopher Jeffrey_. -The [style sheet](https://kevinburke.bitbucket.io/markdowncss/) -was created by _Kevin Burke_. diff --git a/examples/webenginewidgets/markdowneditor/resources/index.html b/examples/webenginewidgets/markdowneditor/resources/index.html deleted file mode 100644 index c8e30b49b..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/index.html +++ /dev/null @@ -1,32 +0,0 @@ -<!doctype html> -<html lang="en"> -<meta charset="utf-8"> -<head> - <link rel="stylesheet" type="text/css" href="3rdparty/markdown.css"> - <script src="3rdparty/marked.js"></script> - <script src="qrc:/qtwebchannel/qwebchannel.js"></script> -</head> -<body> - <div id="placeholder"></div> - <script> - 'use strict'; - - var placeholder = document.getElementById('placeholder'); - - var updateText = function(text) { - placeholder.innerHTML = marked.parse(text); - } - - new QWebChannel(qt.webChannelTransport, - function(channel) { - var content = channel.objects.content; - updateText(content.text); - content.textChanged.connect(updateText); - } - ); - </script> -</body> -</html> - - - diff --git a/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc b/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc deleted file mode 100644 index bc738f1cf..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc +++ /dev/null @@ -1,8 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>default.md</file> - <file>index.html</file> - <file>3rdparty/markdown.css</file> - <file>3rdparty/marked.js</file> - </qresource> -</RCC> diff --git a/examples/webenginewidgets/recipebrowser/CMakeLists.txt b/examples/webenginewidgets/recipebrowser/CMakeLists.txt new file mode 100644 index 000000000..d10409c09 --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/CMakeLists.txt @@ -0,0 +1,71 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(recipebrowser LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginewidgets/recipebrowser") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui WebEngineWidgets) + +qt_add_executable(recipebrowser + main.cpp + mainwindow.cpp mainwindow.h mainwindow.ui + stylesheetdialog.cpp stylesheetdialog.h stylesheetdialog.ui + document.cpp document.h +) + +set_target_properties(recipebrowser PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(recipebrowser PUBLIC + Qt::Core + Qt::Gui + Qt::WebEngineWidgets +) + +# Resources: +set(recipebrowser_resource_files + "assets/3rdparty/markdown.css" + "assets/3rdparty/marked.js" + "assets/custom.css" + "assets/custom.js" + "assets/pages/burger.html" + "assets/pages/cupcakes.html" + "assets/pages/images/burger.jpg" + "assets/pages/images/cupcakes.jpg" + "assets/pages/images/pasta.jpg" + "assets/pages/images/pizza.jpg" + "assets/pages/images/skewers.jpg" + "assets/pages/images/soup.jpg" + "assets/pages/images/steak.jpg" + "assets/pages/pasta.html" + "assets/pages/pizza.html" + "assets/pages/skewers.html" + "assets/pages/soup.html" + "assets/pages/steak.html" +) + +qt_add_resources(recipebrowser "recipebrowser" + PREFIX + "/" + BASE + "assets" + FILES + ${recipebrowser_resource_files} +) + +install(TARGETS recipebrowser + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/MARKDOWN-LICENSE.txt b/examples/webenginewidgets/recipebrowser/assets/3rdparty/MARKDOWN-LICENSE.txt index 23c52cc43..23c52cc43 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/MARKDOWN-LICENSE.txt +++ b/examples/webenginewidgets/recipebrowser/assets/3rdparty/MARKDOWN-LICENSE.txt diff --git a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/MARKED-LICENSE.txt b/examples/webenginewidgets/recipebrowser/assets/3rdparty/MARKED-LICENSE.txt index 8e3ba0e0a..8e3ba0e0a 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/MARKED-LICENSE.txt +++ b/examples/webenginewidgets/recipebrowser/assets/3rdparty/MARKED-LICENSE.txt diff --git a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/markdown.css b/examples/webenginewidgets/recipebrowser/assets/3rdparty/markdown.css index 24fc2ffe2..24fc2ffe2 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/markdown.css +++ b/examples/webenginewidgets/recipebrowser/assets/3rdparty/markdown.css diff --git a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/marked.js b/examples/webenginewidgets/recipebrowser/assets/3rdparty/marked.js index 33c02d9cf..33c02d9cf 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/marked.js +++ b/examples/webenginewidgets/recipebrowser/assets/3rdparty/marked.js diff --git a/examples/webenginewidgets/recipebrowser/assets/3rdparty/qt_attribution.json b/examples/webenginewidgets/recipebrowser/assets/3rdparty/qt_attribution.json new file mode 100644 index 000000000..f8b0fd023 --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/assets/3rdparty/qt_attribution.json @@ -0,0 +1,34 @@ +[ + { + "Id" : "recipebrowser-marked", + "Name" : "Marked (WebEngine Recipe Browser example)", + "QDocModule" : "qtwebengine", + "QtUsage" : "Marked is used in the WebEngine Recipe Browser example", + "QtParts" : ["examples"], + "Files" : "marked.js", + "Description" : + "A full-featured markdown parser and compiler, written in JavaScript. Built for speed.", + "Homepage" : "https://github.com/chjj/marked", + "Version" : "0.4.0", + "DownloadLocation" : "https://github.com/markedjs/marked/blob/0.4.0/lib/marked.js", + "Copyright" : "Copyright (c) 2011-2018, Christopher Jeffrey", + "License" : "MIT License", + "LicenseId" : "MIT", + "LicenseFile" : "MARKED-LICENSE.txt" + }, + { + "Id" : "recipebrowser-markdowncss", + "Name" : "Markdown.css (WebEngine Recipe Browser example)", + "QDocModule" : "qtwebengine", + "QtUsage" : "markdown.css is used in the WebEngine Recipe Browser example", + "QtParts" : ["examples"], + "Files" : "markdown.css", + "Description" : "Markdown.css is better default styling for your Markdown files.", + "Version" : "188530e4b5d020d7e237fc6b26be13ebf4a8def3", + "DownloadLocation" : "https://bitbucket.org/kevinburke/markdowncss/src/188530e4b5d020d7e237fc6b26be13ebf4a8def3/markdown.css", + "Copyright" : "Copyright 2011 Kevin Burke Copyright Twitter Inc.", + "License" : "Apache License 2.0", + "LicenseId" : "Apache-2.0", + "LicenseFile" : "MARKDOWN-LICENSE.txt" + } + ] diff --git a/examples/webenginequick/recipebrowser/resources/pages/assets/custom.css b/examples/webenginewidgets/recipebrowser/assets/custom.css index 8d2f6cb0b..cc1106af3 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/assets/custom.css +++ b/examples/webenginewidgets/recipebrowser/assets/custom.css @@ -1,4 +1,4 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause body { diff --git a/examples/webenginewidgets/recipebrowser/assets/custom.js b/examples/webenginewidgets/recipebrowser/assets/custom.js new file mode 100644 index 000000000..34470164e --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/assets/custom.js @@ -0,0 +1,13 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +marked.setOptions({ + renderer : new marked.Renderer(), + gfm : true, + tables : true, + breaks : false, + pedantic : false, + sanitize : false, + smartLists : true, + smartypants : false +}); diff --git a/examples/webenginequick/recipebrowser/resources/pages/burger.html b/examples/webenginewidgets/recipebrowser/assets/pages/burger.html index 6651cc0f0..99497959f 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/burger.html +++ b/examples/webenginewidgets/recipebrowser/assets/pages/burger.html @@ -3,8 +3,11 @@ <head> <meta charset="utf-8"> <title>Insanity Burger</title> - <link rel="stylesheet" type="text/css" href="assets/3rdparty/markdown.css"> - <link rel="stylesheet" type="text/css" href="assets/custom.css"> + <link rel="stylesheet" type="text/css" href="../3rdparty/markdown.css"> + <link rel="stylesheet" type="text/css" href="../custom.css"> + <script src="../3rdparty/marked.js"></script> + <script src="../custom.js"></script> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> </head> <body> <div id="placeholder"></div> @@ -68,8 +71,24 @@ give it a minute to go gorgeous and sloppy. </div><!--End of content--> - <script src="assets/3rdparty/marked.js"></script> - <script src="assets/custom.js"></script> + <script> + 'use strict'; + + var jsContent = document.getElementById('content'); + var placeholder = document.getElementById('placeholder'); + + var updateText = function(text) { + placeholder.innerHTML = marked.parse(text); + } + + new QWebChannel(qt.webChannelTransport, + function(channel) { + var content = channel.objects.content; + content.setInitialText(jsContent.innerHTML); + content.textChanged.connect(updateText); + } + ); + </script> </body> </html> diff --git a/examples/webenginequick/recipebrowser/resources/pages/cupcakes.html b/examples/webenginewidgets/recipebrowser/assets/pages/cupcakes.html index 4791c7ffa..e8e14a9b6 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/cupcakes.html +++ b/examples/webenginewidgets/recipebrowser/assets/pages/cupcakes.html @@ -3,8 +3,11 @@ <head> <meta charset="utf-8"> <title>Cupcakes</title> - <link rel="stylesheet" type="text/css" href="assets/3rdparty/markdown.css"> - <link rel="stylesheet" type="text/css" href="assets/custom.css"> + <link rel="stylesheet" type="text/css" href="../3rdparty/markdown.css"> + <link rel="stylesheet" type="text/css" href="../custom.css"> + <script src="../3rdparty/marked.js"></script> + <script src="../custom.js"></script> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> </head> <body> <div id="placeholder"></div> @@ -46,8 +49,25 @@ Cupcakes **Enjoy!** </div><!--End of content--> - <script src="assets/3rdparty/marked.js"></script> - <script src="assets/custom.js"></script> + + <script> + 'use strict'; + + var jsContent = document.getElementById('content'); + var placeholder = document.getElementById('placeholder'); + + var updateText = function(text) { + placeholder.innerHTML = marked.parse(text); + } + + new QWebChannel(qt.webChannelTransport, + function(channel) { + var content = channel.objects.content; + content.setInitialText(jsContent.innerHTML); + content.textChanged.connect(updateText); + } + ); + </script> </body> </html> diff --git a/examples/webenginequick/recipebrowser/resources/pages/images/burger.jpg b/examples/webenginewidgets/recipebrowser/assets/pages/images/burger.jpg Binary files differindex edc0c65de..edc0c65de 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/images/burger.jpg +++ b/examples/webenginewidgets/recipebrowser/assets/pages/images/burger.jpg diff --git a/examples/webenginequick/recipebrowser/resources/pages/images/cupcakes.jpg b/examples/webenginewidgets/recipebrowser/assets/pages/images/cupcakes.jpg Binary files differindex cce52ba23..cce52ba23 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/images/cupcakes.jpg +++ b/examples/webenginewidgets/recipebrowser/assets/pages/images/cupcakes.jpg diff --git a/examples/webenginequick/recipebrowser/resources/pages/images/pasta.jpg b/examples/webenginewidgets/recipebrowser/assets/pages/images/pasta.jpg Binary files differindex 7ac924b79..7ac924b79 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/images/pasta.jpg +++ b/examples/webenginewidgets/recipebrowser/assets/pages/images/pasta.jpg diff --git a/examples/webenginequick/recipebrowser/resources/pages/images/pizza.jpg b/examples/webenginewidgets/recipebrowser/assets/pages/images/pizza.jpg Binary files differindex 8d8f756af..8d8f756af 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/images/pizza.jpg +++ b/examples/webenginewidgets/recipebrowser/assets/pages/images/pizza.jpg diff --git a/examples/webenginequick/recipebrowser/resources/pages/images/skewers.jpg b/examples/webenginewidgets/recipebrowser/assets/pages/images/skewers.jpg Binary files differindex 6bb2f1172..6bb2f1172 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/images/skewers.jpg +++ b/examples/webenginewidgets/recipebrowser/assets/pages/images/skewers.jpg diff --git a/examples/webenginequick/recipebrowser/resources/pages/images/soup.jpg b/examples/webenginewidgets/recipebrowser/assets/pages/images/soup.jpg Binary files differindex fc9dff906..fc9dff906 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/images/soup.jpg +++ b/examples/webenginewidgets/recipebrowser/assets/pages/images/soup.jpg diff --git a/examples/webenginequick/recipebrowser/resources/pages/images/steak.jpg b/examples/webenginewidgets/recipebrowser/assets/pages/images/steak.jpg Binary files differindex 240b72eb4..240b72eb4 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/images/steak.jpg +++ b/examples/webenginewidgets/recipebrowser/assets/pages/images/steak.jpg diff --git a/examples/webenginequick/recipebrowser/resources/pages/pasta.html b/examples/webenginewidgets/recipebrowser/assets/pages/pasta.html index 41ed1a756..c2b3d840a 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/pasta.html +++ b/examples/webenginewidgets/recipebrowser/assets/pages/pasta.html @@ -3,8 +3,11 @@ <head> <meta charset="utf-8"> <title>Pasta</title> - <link rel="stylesheet" type="text/css" href="assets/3rdparty/markdown.css"> - <link rel="stylesheet" type="text/css" href="assets/custom.css"> + <link rel="stylesheet" type="text/css" href="../3rdparty/markdown.css"> + <link rel="stylesheet" type="text/css" href="../custom.css"> + <script src="../3rdparty/marked.js"></script> + <script src="../custom.js"></script> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> </head> <body> <div id="placeholder"></div> @@ -49,8 +52,25 @@ Pasta **Enjoy!** </div><!--End of content--> - <script src="assets/3rdparty/marked.js"></script> - <script src="assets/custom.js"></script> + + <script> + 'use strict'; + + var jsContent = document.getElementById('content'); + var placeholder = document.getElementById('placeholder'); + + var updateText = function(text) { + placeholder.innerHTML = marked.parse(text); + } + + new QWebChannel(qt.webChannelTransport, + function(channel) { + var content = channel.objects.content; + content.setInitialText(jsContent.innerHTML); + content.textChanged.connect(updateText); + } + ); + </script> </body> </html> diff --git a/examples/webenginequick/recipebrowser/resources/pages/pizza.html b/examples/webenginewidgets/recipebrowser/assets/pages/pizza.html index 348d809e8..7e390a373 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/pizza.html +++ b/examples/webenginewidgets/recipebrowser/assets/pages/pizza.html @@ -3,8 +3,11 @@ <head> <meta charset="utf-8"> <title>Pizza Diavola</title> - <link rel="stylesheet" type="text/css" href="assets/3rdparty/markdown.css"> - <link rel="stylesheet" type="text/css" href="assets/custom.css"> + <link rel="stylesheet" type="text/css" href="../3rdparty/markdown.css"> + <link rel="stylesheet" type="text/css" href="../custom.css"> + <script src="../3rdparty/marked.js"></script> + <script src="../custom.js"></script> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> </head> <body> <div id="placeholder"></div> @@ -40,8 +43,25 @@ Pizza Diavola **Enjoy!** </div><!--End of content--> - <script src="assets/3rdparty/marked.js"></script> - <script src="assets/custom.js"></script> + + <script> + 'use strict'; + + var jsContent = document.getElementById('content'); + var placeholder = document.getElementById('placeholder'); + + var updateText = function(text) { + placeholder.innerHTML = marked.parse(text); + } + + new QWebChannel(qt.webChannelTransport, + function(channel) { + var content = channel.objects.content; + content.setInitialText(jsContent.innerHTML); + content.textChanged.connect(updateText); + } + ); + </script> </body> </html> diff --git a/examples/webenginequick/recipebrowser/resources/pages/skewers.html b/examples/webenginewidgets/recipebrowser/assets/pages/skewers.html index aca4c4859..db2df5472 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/skewers.html +++ b/examples/webenginewidgets/recipebrowser/assets/pages/skewers.html @@ -3,8 +3,11 @@ <head> <meta charset="utf-8"> <title>Grilled skewers</title> - <link rel="stylesheet" type="text/css" href="assets/3rdparty/markdown.css"> - <link rel="stylesheet" type="text/css" href="assets/custom.css"> + <link rel="stylesheet" type="text/css" href="../3rdparty/markdown.css"> + <link rel="stylesheet" type="text/css" href="../custom.css"> + <script src="../3rdparty/marked.js"></script> + <script src="../custom.js"></script> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> </head> <body> <div id="placeholder"></div> @@ -46,8 +49,24 @@ Grilled skewers </div><!--End of content--> - <script src="assets/3rdparty/marked.js"></script> - <script src="assets/custom.js"></script> + <script> + 'use strict'; + + var jsContent = document.getElementById('content'); + var placeholder = document.getElementById('placeholder'); + + var updateText = function(text) { + placeholder.innerHTML = marked.parse(text); + } + + new QWebChannel(qt.webChannelTransport, + function(channel) { + var content = channel.objects.content; + content.setInitialText(jsContent.innerHTML); + content.textChanged.connect(updateText); + } + ); + </script> </body> </html> diff --git a/examples/webenginequick/recipebrowser/resources/pages/soup.html b/examples/webenginewidgets/recipebrowser/assets/pages/soup.html index 1b7027e5d..ea51fc8a5 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/soup.html +++ b/examples/webenginewidgets/recipebrowser/assets/pages/soup.html @@ -3,8 +3,11 @@ <head> <meta charset="utf-8"> <title>Soup</title> - <link rel="stylesheet" type="text/css" href="assets/3rdparty/markdown.css"> - <link rel="stylesheet" type="text/css" href="assets/custom.css"> + <link rel="stylesheet" type="text/css" href="../3rdparty/markdown.css"> + <link rel="stylesheet" type="text/css" href="../custom.css"> + <script src="../3rdparty/marked.js"></script> + <script src="../custom.js"></script> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> </head> <body> <div id="placeholder"></div> @@ -34,8 +37,25 @@ Soup **Enjoy!** </div><!--End of content--> - <script src="assets/3rdparty/marked.js"></script> - <script src="assets/custom.js"></script> + + <script> + 'use strict'; + + var jsContent = document.getElementById('content'); + var placeholder = document.getElementById('placeholder'); + + var updateText = function(text) { + placeholder.innerHTML = marked.parse(text); + } + + new QWebChannel(qt.webChannelTransport, + function(channel) { + var content = channel.objects.content; + content.setInitialText(jsContent.innerHTML); + content.textChanged.connect(updateText); + } + ); + </script> </body> </html> diff --git a/examples/webenginequick/recipebrowser/resources/pages/steak.html b/examples/webenginewidgets/recipebrowser/assets/pages/steak.html index a56313e27..26391b409 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/steak.html +++ b/examples/webenginewidgets/recipebrowser/assets/pages/steak.html @@ -3,8 +3,11 @@ <head> <meta charset="utf-8"> <title>Grilled steak and rice</title> - <link rel="stylesheet" type="text/css" href="assets/3rdparty/markdown.css"> - <link rel="stylesheet" type="text/css" href="assets/custom.css"> + <link rel="stylesheet" type="text/css" href="../3rdparty/markdown.css"> + <link rel="stylesheet" type="text/css" href="../custom.css"> + <script src="../3rdparty/marked.js"></script> + <script src="../custom.js"></script> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> </head> <body> <div id="placeholder"></div> @@ -60,8 +63,24 @@ Grilled steak and rice </div><!--End of content--> - <script src="assets/3rdparty/marked.js"></script> - <script src="assets/custom.js"></script> + <script> + 'use strict'; + + var jsContent = document.getElementById('content'); + var placeholder = document.getElementById('placeholder'); + + var updateText = function(text) { + placeholder.innerHTML = marked.parse(text); + } + + new QWebChannel(qt.webChannelTransport, + function(channel) { + var content = channel.objects.content; + content.setInitialText(jsContent.innerHTML); + content.textChanged.connect(updateText); + } + ); + </script> </body> </html> diff --git a/examples/webenginewidgets/recipebrowser/doc/images/recipebrowser.webp b/examples/webenginewidgets/recipebrowser/doc/images/recipebrowser.webp Binary files differnew file mode 100644 index 000000000..8446bcde3 --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/doc/images/recipebrowser.webp diff --git a/examples/webenginewidgets/recipebrowser/doc/src/recipebrowser.qdoc b/examples/webenginewidgets/recipebrowser/doc/src/recipebrowser.qdoc new file mode 100644 index 000000000..4ac9973b3 --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/doc/src/recipebrowser.qdoc @@ -0,0 +1,197 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example webenginewidgets/recipebrowser + \title Recipe Browser + \meta category {Application Examples} + \meta tag {widgets, webengine, webchannel, webenginescript} + \ingroup webengine-widgetexamples + \brief Injecting custom stylsheets into web pages and providing a rich text preview + tool for a custom markup language. + + \image recipebrowser.webp + + \e {Recipe Browser} is a small hybrid web browser application. It demonstrates how to + use the \l{Qt WebEngine Widgets C++ Classes} {Qt WebEngine C++ classes} to combine + C++ and JavaScript logic in the following ways. + + \list + \li Running arbitrary JavaScript code via \c QWebEnginePage::runJavaScript() to + inject custom CSS stylesheets + \li Using QWebEngineScript and QWebEngineScriptCollection to persist the JavaScript + code and inject it to every page + \li Using QWebChannel to interact with and provide a rich text preview for a custom + markup language + \endlist + + \l{http://daringfireball.net/projects/markdown/}{Markdown} is a lightweight + markup language with a plain text formatting syntax. + Some services, such as \l{http://github.com}{github}, acknowledge the + format, and render the content as rich text when viewed in a browser. + + The Recipe Browser main window is split into a navigation on the left and + a preview area on the right. The preview area on the right switches to an + editor when the user clicks the Edit button on the top left of the main window. + The editor supports the Markdown syntax and is implemented by using + QPlainTextEdit. The document is rendered as rich text in the preview area, + once the user clicks the View button,to which the Edit button transforms to. + This rendering is implemented by using QWebEngineView. To render the text, + a JavaScript library inside the web engine converts the Markdown text to HTML. + The preview is updated from the editor through QWebChannel. + + \include examples-run.qdocinc + + \section1 Exposing Document Text + + To render the current Markdown text it needs to be exposed to the web engine through + QWebChannel. To achieve this it has to be part of Qt metatype system. This is done + by using a dedicated \c Document class that exposes the document text as a + \c {Q_PROPERTY}: + + \quotefromfile webenginewidgets/recipebrowser/document.h + \skipto class Document + \printto #endif + + The \c Document class wraps a QString \c m_currentText to be set on the C++ + side with the \c setText() method and exposes it at runtime as a \c text + property with a \c textChanged signal. We define the \c setText method as + follows: + + \quotefromfile webenginewidgets/recipebrowser/document.cpp + \skipto Document::setText(const QString &text) + \printuntil /^\}/ + + Additionally, the \c Document class keeps track of the current recipe via + \c m_currentPage. We call the recipes pages here, because each recipe has + its distinct HTML document that contains the initial text content. + Furthermore, \c m_textCollection is a QMap<QString, QString> that contains + the key/value pairs \{page, text\}, so that changes made to the text content + of a page is persisted between navigation. Nevertheless, we do not write the + modified text contents to the drive, but instead we persist them between + application start and shutdown via QSettings. + + \section1 Creating the Main Window + + The \c MainWindow class inherits the QMainWindow class: + + \quotefromfile webenginewidgets/recipebrowser/mainwindow.h + \skipto class MainWindow : + \printto endif + + The class declares private slots that match the two buttons on the + top left, over the navigation list view. Additionally, helper + methods for custom CSS stylesheets are declared. + + The actual layout of the main window is specified in a \c .ui file. + The widgets and actions are available at runtime in the \c ui member + variable. + + \c m_isEditMode is a boolean that toggles between the editor and the + preview area. + \c m_content is an instance of the \c Document class. + + The actual setup of the different objects is done in the \c MainWindow + constructor: + + \quotefromfile webenginewidgets/recipebrowser/mainwindow.cpp + \skipto MainWindow::MainWindow + \printto connect + + The constructor first calls \c setupUi to construct the widgets and menu + actions according to the UI file. The text editor font is set to one + with a fixed character width, and the QWebEngineView widget is told not + to show a context menu. Furthermore, the editor is hidden away. + + \printto ui->recipes + + Here the \c clicked signals of QPushButton are connected to respective functions + that show the stylesheets dialog or toggle between edit and view mode, that is, + hide and show the editor and preview area respectively. + + \printto m_content.setTextEdit + + Here the navigation QListWidget on the left is setup with the 7 recipes. + Also, the currentItemChanged signal of QListWidget is connected to a lambda + that loads the new, current recipe page and updates the page in \c m_content. + + \printto connect + + Next, the pointer to the ui editor, a QPlainTextEdit, is passed to \c m_content to ensure that + calls to \c Document::setInitialText() work properly. + + \printto QSettings + + Here the \c textChanged signal of the editor is connected to a lambda that + updates the text in \c m_content. This object is then exposed to the JS side + by \c QWebChannel under the name \c{content}. + + \printto ui->recipes + + By using QSettings we persist stylesheets between application runs. If there + should be no stylesheets configured, for example, because the user deleted all of them + in a previous run, we load default ones. + + \printto } + + Finally, we set the currently selected list item to the first contained in the + navigation list widget. This triggers the previously mentioned + QListWidget::currentItemChanged signal and navigates to the page of the list item. + + \section1 Working With Stylesheets + + We use JavaScript to create and append CSS elements to the documents. + After declaring the script source, QWebEnginePage::runJavaScript() can run it + immediately and apply newly created styles on the current content of the web view. + Encapsulating the script into a QWebEngineScript and adding it to the script collection + of QWebEnginePage makes its effect permanent. + + \quotefromfile webenginewidgets/recipebrowser/mainwindow.cpp + \skipto MainWindow::insertStyleSheet + \printuntil /^\}/ + + Removing stylesheets can be done similarly: + + \quotefromfile webenginewidgets/recipebrowser/mainwindow.cpp + \skipto MainWindow::removeStyleSheet + \printuntil /^\}/ + + \section1 Creating a recipe file + + \quotefile webenginewidgets/recipebrowser/assets/pages/burger.html + + All the different recipe pages are set up the same way. + + In the \c <head> part they + include two CSS files: \c markdown.css, that styles the markdown, and + custom.css, that does some further styling but most importantly hides the + \c <div> with id \e content, as this \c <div> only contains the unmodified, + initial content text. Also, three JS scripts are included. \c marked.js + is responsible for parsing the markdown and transforming it into HTML. + \c custom.js does some configuration of \c marked.js, and \c qwebchannel.js + exposes the QWebChannel JavaScript API. + + In the body there are two \c <div> elements. The \c <div> with id \e placeholder + gets the markdown text injected that is rendered and visible. The \c <div> with id + \e content is hidden by \c custom.css and only contains the original, unmodified + text content of the recipe. + + Finally, on the bottom of each recipe HTML file is a script that is responsible for + the communication between the C++ and JavaScript side via QWebChannel. The original, + unmodified text content inside the \c <div> with id \e content is passed to the C++ + side and a callback is setup that is invoked when the \c textChanged signal of + \c m_content is emitted. The callback then updates the contents of the \c <div> + \e placeholder with the parsed markdown. + + \section1 Files and Attributions + + The example bundles the following code with third-party licenses: + \table + \row + \li \l{recipebrowser-marked}{Marked} + \li MIT License + \row + \li \l{recipebrowser-markdowncss}{Markdown.css} + \li Apache License 2.0 + \endtable +*/ diff --git a/examples/webenginewidgets/recipebrowser/document.cpp b/examples/webenginewidgets/recipebrowser/document.cpp new file mode 100644 index 000000000..c991e14f8 --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/document.cpp @@ -0,0 +1,48 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "document.h" + +#include <QSettings> + +Document::Document(QObject *parent) : QObject(parent) +{ + QSettings settings; + settings.beginGroup("textCollection"); + QStringList pageTexts = settings.allKeys(); + for (const QString &name : std::as_const(pageTexts)) { + QString pageText = settings.value(name).value<QString>(); + if (!pageText.isEmpty()) + m_textCollection.insert(name, pageText); + } + settings.endGroup(); +} + +void Document::setTextEdit(QPlainTextEdit *textEdit) +{ + m_textEdit = textEdit; +} + +void Document::setCurrentPage(const QString &page) +{ + m_currentPage = page; +} + +void Document::setInitialText(const QString &text) +{ + m_textEdit->setPlainText(m_textCollection.value(m_currentPage, text)); +} + +void Document::setText(const QString &text) +{ + if (text == m_currentText) + return; + m_currentText = text; + emit textChanged(m_currentText); + + QSettings settings; + settings.beginGroup("textCollection"); + settings.setValue(m_currentPage, text); + m_textCollection.insert(m_currentPage, text); + settings.endGroup(); +} diff --git a/examples/webenginewidgets/recipebrowser/document.h b/examples/webenginewidgets/recipebrowser/document.h new file mode 100644 index 000000000..f6b537eb8 --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/document.h @@ -0,0 +1,36 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef DOCUMENT_H +#define DOCUMENT_H + +#include <QObject> +#include <QString> +#include <QPlainTextEdit> + +class Document : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString text MEMBER m_currentText NOTIFY textChanged FINAL) +public: + explicit Document(QObject *parent = nullptr); + + void setTextEdit(QPlainTextEdit *textEdit); + void setCurrentPage(const QString &page); + +public slots: + void setInitialText(const QString &text); + void setText(const QString &text); + +signals: + void textChanged(const QString &text); + +private: + QPlainTextEdit *m_textEdit; + + QString m_currentText; + QString m_currentPage; + QMap<QString, QString> m_textCollection; +}; + +#endif // DOCUMENT_H diff --git a/examples/webenginewidgets/stylesheetbrowser/main.cpp b/examples/webenginewidgets/recipebrowser/main.cpp index cf1e5f718..b0b42d16b 100644 --- a/examples/webenginewidgets/stylesheetbrowser/main.cpp +++ b/examples/webenginewidgets/recipebrowser/main.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "mainwindow.h" @@ -12,7 +12,7 @@ int main(int argc, char *argv[]) QCoreApplication::setOrganizationName("QtExamples"); QApplication a(argc, argv); - MainWindow w(QUrl("http://qt.io")); + MainWindow w; w.show(); return a.exec(); } diff --git a/examples/webenginewidgets/recipebrowser/mainwindow.cpp b/examples/webenginewidgets/recipebrowser/mainwindow.cpp new file mode 100644 index 000000000..7288fe4ce --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/mainwindow.cpp @@ -0,0 +1,153 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwindow.h" +#include "stylesheetdialog.h" +#include "ui_mainwindow.h" + +#include <QWebChannel> + +static QMap<QString, QString> defaultStyleSheets = { + { "Upside down", "body { -webkit-transform: rotate(180deg); }" }, + { "Darkmode", + "body { color: #FFF; background-color: #000 }" + "h1, h2, h3, h4, h5 { color: #FFF }" } +}; + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent), ui(new Ui::MainWindow), m_isEditMode(false) +{ + ui->setupUi(this); + ui->textEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + ui->textEdit->hide(); + ui->webEngineView->setContextMenuPolicy(Qt::NoContextMenu); + + connect(ui->stylesheetsButton, &QPushButton::clicked, this, &MainWindow::showStyleSheetsDialog); + connect(ui->editViewButton, &QPushButton::clicked, this, &MainWindow::toggleEditView); + + ui->recipes->insertItem(0, "Burger"); + ui->recipes->insertItem(1, "Cupcakes"); + ui->recipes->insertItem(2, "Pasta"); + ui->recipes->insertItem(3, "Pizza"); + ui->recipes->insertItem(4, "Skewers"); + ui->recipes->insertItem(5, "Soup"); + ui->recipes->insertItem(6, "Steak"); + connect(ui->recipes, &QListWidget::currentItemChanged, this, + [this](QListWidgetItem *current, QListWidgetItem * /* previous */) { + const QString page = current->text().toLower(); + const QString url = QStringLiteral("qrc:/pages/") + page + QStringLiteral(".html"); + ui->webEngineView->setUrl(QUrl(url)); + m_content.setCurrentPage(page); + }); + + m_content.setTextEdit(ui->textEdit); + + connect(ui->textEdit, &QPlainTextEdit::textChanged, this, + [this]() { m_content.setText(ui->textEdit->toPlainText()); }); + + QWebChannel *channel = new QWebChannel(this); + channel->registerObject(QStringLiteral("content"), &m_content); + ui->webEngineView->page()->setWebChannel(channel); + + QSettings settings; + settings.beginGroup("styleSheets"); + QStringList styleSheets = settings.allKeys(); + if (styleSheets.empty()) { + // Add back default style sheets if the user cleared them out + loadDefaultStyleSheets(); + } else { + for (const auto &name : std::as_const(styleSheets)) { + StyleSheet styleSheet = settings.value(name).value<StyleSheet>(); + if (styleSheet.second) + insertStyleSheet(name, styleSheet.first, false); + } + } + settings.endGroup(); + + ui->recipes->setCurrentItem(ui->recipes->item(0)); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::insertStyleSheet(const QString &name, const QString &source, bool immediately) +{ + QWebEngineScript script; + QString s = QString::fromLatin1("(function() {" + " css = document.createElement('style');" + " css.type = 'text/css';" + " css.id = '%1';" + " document.head.appendChild(css);" + " css.innerText = '%2';" + "})()") + .arg(name, source.simplified()); + if (immediately) + ui->webEngineView->page()->runJavaScript(s, QWebEngineScript::ApplicationWorld); + + script.setName(name); + script.setSourceCode(s); + script.setInjectionPoint(QWebEngineScript::DocumentReady); + script.setRunsOnSubFrames(true); + script.setWorldId(QWebEngineScript::ApplicationWorld); + ui->webEngineView->page()->scripts().insert(script); +} + +void MainWindow::removeStyleSheet(const QString &name, bool immediately) +{ + QString s = QString::fromLatin1("(function() {" + " var element = document.getElementById('%1');" + " element.outerHTML = '';" + " delete element;" + "})()") + .arg(name); + if (immediately) + ui->webEngineView->page()->runJavaScript(s, QWebEngineScript::ApplicationWorld); + + const QList<QWebEngineScript> scripts = ui->webEngineView->page()->scripts().find(name); + if (!scripts.isEmpty()) + ui->webEngineView->page()->scripts().remove(scripts.first()); +} + +bool MainWindow::hasStyleSheet(const QString &name) +{ + const QList<QWebEngineScript> scripts = ui->webEngineView->page()->scripts().find(name); + return !scripts.isEmpty(); +} + +void MainWindow::loadDefaultStyleSheets() +{ + QSettings settings; + settings.beginGroup("styleSheets"); + + for (auto it = defaultStyleSheets.constBegin(); it != defaultStyleSheets.constEnd(); ++it) { + settings.setValue(it.key(), QVariant::fromValue(qMakePair(it.value(), true))); + insertStyleSheet(it.key(), it.value(), false); + } + + settings.endGroup(); +} + +void MainWindow::showStyleSheetsDialog() +{ + StylesheetDialog *dialog = new StylesheetDialog(this); + dialog->show(); +} + +void MainWindow::toggleEditView() +{ + m_isEditMode = !m_isEditMode; + + if (m_isEditMode) { + ui->webEngineView->hide(); + ui->textEdit->show(); + + ui->editViewButton->setText(QStringLiteral("View")); + } else { + ui->textEdit->hide(); + ui->webEngineView->show(); + + ui->editViewButton->setText(QStringLiteral("Edit")); + } +} diff --git a/examples/webenginewidgets/stylesheetbrowser/mainwindow.h b/examples/webenginewidgets/recipebrowser/mainwindow.h index 92d6d02fe..959c82af8 100644 --- a/examples/webenginewidgets/stylesheetbrowser/mainwindow.h +++ b/examples/webenginewidgets/recipebrowser/mainwindow.h @@ -1,12 +1,15 @@ -// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include "document.h" #include <QMainWindow> #include <QSettings> #include <QWebEngineScriptCollection> +#include <QWebEngineView> +#include <QPlainTextEdit> QT_BEGIN_NAMESPACE namespace Ui { @@ -19,7 +22,7 @@ class MainWindow : public QMainWindow Q_OBJECT public: - explicit MainWindow(const QUrl &url); + explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); void insertStyleSheet(const QString &name, const QString &source, bool immediately); @@ -28,13 +31,14 @@ public: void loadDefaultStyleSheets(); private slots: - void urlEntered(); - void urlChanged(const QUrl &url); void showStyleSheetsDialog(); - void reloadRequested(); + void toggleEditView(); private: Ui::MainWindow *ui; + + bool m_isEditMode; + Document m_content; }; #endif // MAINWINDOW_H diff --git a/examples/webenginewidgets/recipebrowser/mainwindow.ui b/examples/webenginewidgets/recipebrowser/mainwindow.ui new file mode 100644 index 000000000..b7c29f1b8 --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/mainwindow.ui @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>713</width> + <height>455</height> + </rect> + </property> + <property name="windowTitle"> + <string>Recipe Browser</string> + </property> + <property name="unifiedTitleAndToolBarOnMac"> + <bool>false</bool> + </property> + <widget class="QWidget" name="centralWidget"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QVBoxLayout" name="navigation"> + <item> + <layout class="QHBoxLayout" name="navigationBar"> + <item> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Recipes</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="editViewButton"> + <property name="text"> + <string>Edit</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="stylesheetsButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Stylesheets</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QListWidget" name="recipes"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QWebEngineView" name="webEngineView" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QPlainTextEdit" name="textEdit"/> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + <layoutdefault spacing="6" margin="11"/> + <customwidgets> + <customwidget> + <class>QWebEngineView</class> + <extends>QWidget</extends> + <header>qwebengineview.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/examples/webenginewidgets/stylesheetbrowser/stylesheetbrowser.pro b/examples/webenginewidgets/recipebrowser/recipebrowser.pro index a9ff54400..8f8895b52 100644 --- a/examples/webenginewidgets/stylesheetbrowser/stylesheetbrowser.pro +++ b/examples/webenginewidgets/recipebrowser/recipebrowser.pro @@ -1,22 +1,24 @@ TEMPLATE = app -TARGET = stylesheetbrowser +TARGET = recipebrowser QT += webenginewidgets HEADERS += \ mainwindow.h \ - stylesheetdialog.h + stylesheetdialog.h \ + document.h SOURCES += \ main.cpp \ mainwindow.cpp \ - stylesheetdialog.cpp + stylesheetdialog.cpp \ + document.cpp FORMS += \ mainwindow.ui \ stylesheetdialog.ui -RESOURCES += stylesheetbrowser.qrc +RESOURCES += recipebrowser.qrc # install -target.path = $$[QT_INSTALL_EXAMPLES]/webenginewidgets/stylesheetbrowser +target.path = $$[QT_INSTALL_EXAMPLES]/webenginewidgets/recipebrowser INSTALLS += target diff --git a/examples/webenginewidgets/recipebrowser/recipebrowser.qrc b/examples/webenginewidgets/recipebrowser/recipebrowser.qrc new file mode 100644 index 000000000..2dc003f30 --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/recipebrowser.qrc @@ -0,0 +1,15 @@ +<RCC> + <qresource prefix="/"> + <file>assets/3rdparty/markdown.css</file> + <file>assets/3rdparty/marked.js</file> + <file>assets/custom.js</file> + <file>assets/custom.css</file> + <file>assets/pages/burger.html</file> + <file>assets/pages/cupcakes.html</file> + <file>assets/pages/pasta.html</file> + <file>assets/pages/pizza.html</file> + <file>assets/pages/skewers.html</file> + <file>assets/pages/soup.html</file> + <file>assets/pages/steak.html</file> + </qresource> +</RCC> diff --git a/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.cpp b/examples/webenginewidgets/recipebrowser/stylesheetdialog.cpp index 30409f107..2137617c3 100644 --- a/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.cpp +++ b/examples/webenginewidgets/recipebrowser/stylesheetdialog.cpp @@ -1,18 +1,18 @@ -// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "mainwindow.h" #include "stylesheetdialog.h" #include "ui_stylesheetdialog.h" -StylesheetDialog::StylesheetDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::StylesheetDialog) +StylesheetDialog::StylesheetDialog(QWidget *parent) : QDialog(parent), ui(new Ui::StylesheetDialog) { ui->setupUi(this); - connect(ui->styleSheetList, &QListWidget::currentItemChanged, this, &StylesheetDialog::currentStyleSheetChanged); - connect(ui->styleSheetList, &QListWidget::itemClicked, this, &StylesheetDialog::listItemClicked); + connect(ui->styleSheetList, &QListWidget::currentItemChanged, this, + &StylesheetDialog::currentStyleSheetChanged); + connect(ui->styleSheetList, &QListWidget::itemClicked, this, + &StylesheetDialog::listItemClicked); connect(ui->fileNameEdit, &QLineEdit::textChanged, this, &StylesheetDialog::fileNameChanged); connect(ui->addButton, &QPushButton::clicked, this, &StylesheetDialog::addButtonClicked); @@ -20,8 +20,8 @@ StylesheetDialog::StylesheetDialog(QWidget *parent) : QSettings settings; settings.beginGroup("styleSheets"); - for (auto name : settings.allKeys()) { - QListWidgetItem *listItem = new QListWidgetItem(name, ui->styleSheetList); + for (const auto &name : settings.allKeys()) { + QListWidgetItem *listItem = new QListWidgetItem(name, ui->styleSheetList); listItem->setFlags(listItem->flags() | Qt::ItemIsUserCheckable); bool checked = settings.value(name).value<StyleSheet>().second; listItem->setCheckState(checked ? Qt::Checked : Qt::Unchecked); @@ -59,9 +59,8 @@ void StylesheetDialog::listItemClicked(QListWidgetItem *item) { MainWindow *window = static_cast<MainWindow *>(parent()); const QString name = item->text(); - bool checkedStateChanged = - (item->checkState() == Qt::Checked && !window->hasStyleSheet(name)) || - (item->checkState() == Qt::Unchecked && window->hasStyleSheet(name)); + bool checkedStateChanged = (item->checkState() == Qt::Checked && !window->hasStyleSheet(name)) + || (item->checkState() == Qt::Unchecked && window->hasStyleSheet(name)); if (!checkedStateChanged) return; @@ -96,7 +95,7 @@ void StylesheetDialog::addButtonClicked() if (name.isEmpty() || source.isEmpty()) return; - QListWidgetItem *listItem = new QListWidgetItem(ui->fileNameEdit->text(), ui->styleSheetList); + QListWidgetItem *listItem = new QListWidgetItem(ui->fileNameEdit->text(), ui->styleSheetList); listItem->setFlags(listItem->flags() | Qt::ItemIsUserCheckable); listItem->setCheckState(Qt::Checked); diff --git a/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.h b/examples/webenginewidgets/recipebrowser/stylesheetdialog.h index 7bebff74f..ca1b4ae99 100644 --- a/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.h +++ b/examples/webenginewidgets/recipebrowser/stylesheetdialog.h @@ -1,4 +1,4 @@ -// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #ifndef STYLESHEETDIALOG_H diff --git a/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.ui b/examples/webenginewidgets/recipebrowser/stylesheetdialog.ui index 19db267e8..19db267e8 100644 --- a/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.ui +++ b/examples/webenginewidgets/recipebrowser/stylesheetdialog.ui diff --git a/examples/webenginewidgets/stylesheetbrowser/3rdparty/COPYING b/examples/webenginewidgets/stylesheetbrowser/3rdparty/COPYING deleted file mode 100644 index 220881da6..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/3rdparty/COPYING +++ /dev/null @@ -1 +0,0 @@ -The icons in this repository are herefore released into the Public Domain. diff --git a/examples/webenginewidgets/stylesheetbrowser/3rdparty/qt_attribution.json b/examples/webenginewidgets/stylesheetbrowser/3rdparty/qt_attribution.json deleted file mode 100644 index f779da7e2..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/3rdparty/qt_attribution.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "Id": "stylesheetbrowser-tango", - "Name": "Tango Icon Library", - "QDocModule": "qtwebengine", - "QtUsage": "Used in WebEngine StyleSheet Browser example.", - - "QtParts": [ "examples" ], - "Description": "Selected icons from the Tango Icon Library", - "Homepage": "http://tango.freedesktop.org/Tango_Icon_Library", - "Version": "0.8.90", - "DownloadLocation": "http://tango.freedesktop.org/releases/tango-icon-theme-0.8.90.tar.gz", - "LicenseId": "urn:dje:license:public-domain", - "License": "Public Domain", - "LicenseFile": "COPYING", - "Copyright": "Ulisse Perusin <uli.peru@gmail.com> -Steven Garrity <sgarrity@silverorange.com> -Lapo Calamandrei <calamandrei@gmail.com> -Ryan Collier <rcollier@novell.com> -Rodney Dawes <dobey@novell.com> -Andreas Nilsson <nisses.mail@home.se> -Tuomas Kuosmanen <tigert@tigert.com> -Garrett LeSage <garrett@novell.com> -Jakub Steiner <jimmac@novell.com>" -} diff --git a/examples/webenginewidgets/stylesheetbrowser/3rdparty/view-refresh.png b/examples/webenginewidgets/stylesheetbrowser/3rdparty/view-refresh.png Binary files differdeleted file mode 100644 index cab4d02c7..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/3rdparty/view-refresh.png +++ /dev/null diff --git a/examples/webenginewidgets/stylesheetbrowser/CMakeLists.txt b/examples/webenginewidgets/stylesheetbrowser/CMakeLists.txt deleted file mode 100644 index 56ecd51f3..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/CMakeLists.txt +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -cmake_minimum_required(VERSION 3.16) -project(stylesheetbrowser LANGUAGES CXX) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginewidgets/stylesheetbrowser") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui WebEngineWidgets) - -qt_add_executable(stylesheetbrowser - main.cpp - mainwindow.cpp mainwindow.h mainwindow.ui - stylesheetdialog.cpp stylesheetdialog.h stylesheetdialog.ui -) - -set_target_properties(stylesheetbrowser PROPERTIES - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -target_link_libraries(stylesheetbrowser PUBLIC - Qt::Core - Qt::Gui - Qt::WebEngineWidgets -) - -# Resources: -set(stylesheetbrowser_resource_files - "3rdparty/view-refresh.png" -) - -qt_add_resources(stylesheetbrowser "stylesheetbrowser" - PREFIX - "/" - BASE - "3rdparty" - FILES - ${stylesheetbrowser_resource_files} -) - -install(TARGETS stylesheetbrowser - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/webenginewidgets/stylesheetbrowser/doc/images/stylesheetbrowser.png b/examples/webenginewidgets/stylesheetbrowser/doc/images/stylesheetbrowser.png Binary files differdeleted file mode 100644 index 32c7c43ed..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/doc/images/stylesheetbrowser.png +++ /dev/null diff --git a/examples/webenginewidgets/stylesheetbrowser/doc/src/stylesheetbrowser.qdoc b/examples/webenginewidgets/stylesheetbrowser/doc/src/stylesheetbrowser.qdoc deleted file mode 100644 index 80f333323..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/doc/src/stylesheetbrowser.qdoc +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \example webenginewidgets/stylesheetbrowser - \title WebEngine StyleSheet Browser Example - \ingroup webengine-widgetexamples - \brief Demonstrates how to inject CSS into web pages using user scripts. - - \image stylesheetbrowser.png - - \e {StyleSheet Browser} demonstrates how to use the \l{Qt WebEngine Widgets C++ Classes} - {Qt WebEngine C++ classes} to inject user stylesheets into web pages. - - \include examples-run.qdocinc - - \section1 Working With Stylesheets - - We use JavaScript to create and append CSS elements to the documents. - After declaring the script source, QWebEnginePage::runJavaScript() can run it - immediately and apply newly created styles on the current content of the web view. - Encapsulating the script into a QWebEngineScript and adding it to the script collection - of QWebEnginePage makes its effect permanent. - - \quotefromfile webenginewidgets/stylesheetbrowser/mainwindow.cpp - \skipto MainWindow::insertStyleSheet - \printuntil /^\}/ - - Removing stylesheets can be done similarly: - - \quotefromfile webenginewidgets/stylesheetbrowser/mainwindow.cpp - \skipto MainWindow::removeStyleSheet - \printuntil /^\}/ - - \section1 Files and Attributions - - The example uses icons from the Tango Icon Library: - - \table - \row - \li \l{stylesheetbrowser-tango}{Tango Icon Library} - \li Public Domain - \endtable -*/ diff --git a/examples/webenginewidgets/stylesheetbrowser/mainwindow.cpp b/examples/webenginewidgets/stylesheetbrowser/mainwindow.cpp deleted file mode 100644 index ab786d5c9..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/mainwindow.cpp +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "mainwindow.h" -#include "stylesheetdialog.h" -#include "ui_mainwindow.h" - -static QMap<QString, QString> defaultStyleSheets = { - {"Upside down", "body { -webkit-transform: rotate(180deg); }"} -}; - -MainWindow::MainWindow(const QUrl &url) : - QMainWindow(), - ui(new Ui::MainWindow) -{ - ui->setupUi(this); - - connect(ui->urlBar, &QLineEdit::returnPressed, this, &MainWindow::urlEntered); - connect(ui->webEngineView, &QWebEngineView::urlChanged, this, &MainWindow::urlChanged); - connect(ui->settingsButton, &QPushButton::clicked, this, &MainWindow::showStyleSheetsDialog); - connect(ui->reloadButton, &QPushButton::clicked, this, &MainWindow::reloadRequested); - - QSettings settings; - settings.beginGroup("styleSheets"); - QStringList styleSheets = settings.allKeys(); - if (styleSheets.empty()) { - // Add back default style sheets if the user cleared them out - loadDefaultStyleSheets(); - } else { - for (auto name : std::as_const(styleSheets)) { - StyleSheet styleSheet = settings.value(name).value<StyleSheet>(); - if (styleSheet.second) - insertStyleSheet(name, styleSheet.first, false); - } - } - settings.endGroup(); - - ui->webEngineView->setUrl(url); -} - -MainWindow::~MainWindow() -{ - delete ui; -} - -void MainWindow::insertStyleSheet(const QString &name, const QString &source, bool immediately) -{ - QWebEngineScript script; - QString s = QString::fromLatin1("(function() {"\ - " css = document.createElement('style');"\ - " css.type = 'text/css';"\ - " css.id = '%1';"\ - " document.head.appendChild(css);"\ - " css.innerText = '%2';"\ - "})()").arg(name).arg(source.simplified()); - if (immediately) - ui->webEngineView->page()->runJavaScript(s, QWebEngineScript::ApplicationWorld); - - script.setName(name); - script.setSourceCode(s); - script.setInjectionPoint(QWebEngineScript::DocumentReady); - script.setRunsOnSubFrames(true); - script.setWorldId(QWebEngineScript::ApplicationWorld); - ui->webEngineView->page()->scripts().insert(script); -} - -void MainWindow::removeStyleSheet(const QString &name, bool immediately) -{ - QString s = QString::fromLatin1("(function() {"\ - " var element = document.getElementById('%1');"\ - " element.outerHTML = '';"\ - " delete element;"\ - "})()").arg(name); - if (immediately) - ui->webEngineView->page()->runJavaScript(s, QWebEngineScript::ApplicationWorld); - - const QList<QWebEngineScript> scripts = ui->webEngineView->page()->scripts().find(name); - if (!scripts.isEmpty()) - ui->webEngineView->page()->scripts().remove(scripts.first()); -} - -bool MainWindow::hasStyleSheet(const QString &name) -{ - const QList<QWebEngineScript> scripts = ui->webEngineView->page()->scripts().find(name); - return !scripts.isEmpty(); -} - -void MainWindow::loadDefaultStyleSheets() -{ - QSettings settings; - settings.beginGroup("styleSheets"); - - auto it = defaultStyleSheets.constBegin(); - while (it != defaultStyleSheets.constEnd()) { - settings.setValue(it.key(), QVariant::fromValue(qMakePair(it.value(), true))); - insertStyleSheet(it.key(), it.value(), false); - ++it; - } - - settings.endGroup(); -} - -void MainWindow::urlEntered() -{ - ui->webEngineView->setUrl(QUrl::fromUserInput(ui->urlBar->text())); -} - -void MainWindow::urlChanged(const QUrl &url) -{ - ui->urlBar->setText(url.toString()); -} - -void MainWindow::showStyleSheetsDialog() -{ - StylesheetDialog *dialog = new StylesheetDialog(this); - dialog->show(); -} - -void MainWindow::reloadRequested() -{ - ui->webEngineView->reload(); -} diff --git a/examples/webenginewidgets/stylesheetbrowser/mainwindow.ui b/examples/webenginewidgets/stylesheetbrowser/mainwindow.ui deleted file mode 100644 index bc68c16bb..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/mainwindow.ui +++ /dev/null @@ -1,133 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>MainWindow</class> - <widget class="QMainWindow" name="MainWindow"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>713</width> - <height>455</height> - </rect> - </property> - <property name="windowTitle"> - <string>StyleSheet Browser</string> - </property> - <property name="unifiedTitleAndToolBarOnMac"> - <bool>false</bool> - </property> - <widget class="QWidget" name="centralWidget"> - <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="spacing"> - <number>0</number> - </property> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <widget class="QWidget" name="webContentsWidget" native="true"> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <property name="spacing"> - <number>0</number> - </property> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <widget class="QWidget" name="urlBarWidget" native="true"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QLineEdit" name="urlBar"/> - </item> - <item> - <widget class="QPushButton" name="reloadButton"> - <property name="text"> - <string/> - </property> - <property name="icon"> - <iconset resource="stylesheetbrowser.qrc"> - <normaloff>:/view-refresh.png</normaloff>:/view-refresh.png</iconset> - </property> - <property name="shortcut"> - <string>Ctrl+R</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="settingsButton"> - <property name="text"> - <string>Settings</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QWebEngineView" name="webEngineView" native="true"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="url" stdset="0"> - <url> - <string>about:blank</string> - </url> - </property> - </widget> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - <widget class="QMenuBar" name="menuBar"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>713</width> - <height>20</height> - </rect> - </property> - </widget> - </widget> - <layoutdefault spacing="6" margin="11"/> - <customwidgets> - <customwidget> - <class>QWebEngineView</class> - <extends>QWidget</extends> - <header location="global">QtWebEngineWidgets/QWebEngineView</header> - </customwidget> - </customwidgets> - <resources> - <include location="stylesheetbrowser.qrc"/> - </resources> - <connections/> -</ui> diff --git a/examples/webenginewidgets/stylesheetbrowser/stylesheetbrowser.qrc b/examples/webenginewidgets/stylesheetbrowser/stylesheetbrowser.qrc deleted file mode 100644 index a1cebd6a7..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/stylesheetbrowser.qrc +++ /dev/null @@ -1,5 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file alias="view-refresh.png">3rdparty/view-refresh.png</file> - </qresource> -</RCC> diff --git a/examples/webenginewidgets/webenginewidgets.pro b/examples/webenginewidgets/webenginewidgets.pro index aedd2cc62..2a24686cf 100644 --- a/examples/webenginewidgets/webenginewidgets.pro +++ b/examples/webenginewidgets/webenginewidgets.pro @@ -7,12 +7,11 @@ SUBDIRS += \ cookiebrowser \ notifications \ simplebrowser \ - stylesheetbrowser \ push-notifications \ videoplayer qtConfig(webengine-geolocation): SUBDIRS += maps -qtConfig(webengine-webchannel): SUBDIRS += markdowneditor +qtConfig(webengine-webchannel): SUBDIRS += recipebrowser qtConfig(webengine-printing-and-pdf) { SUBDIRS += printme html2pdf |