kaktus/qml/WebPreviewPage.qml
2022-04-24 19:57:35 +02:00

497 lines
17 KiB
QML

/*
Copyright (C) 2016 Michal Kosciesza <michal@mkiol.net>
This file is part of Kaktus.
Kaktus is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Kaktus is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Kaktus. If not, see <http://www.gnu.org/licenses/>.
*/
// Some ideas heavily inspired and partially borrowed from
// harbour-webpirate project (https://github.com/Dax89/harbour-webpirate)
import QtQuick 2.1
import Sailfish.Silica 1.0
import QtWebKit 3.0
Page {
id: root
property bool showBar: false
property string title
property string entryId
property string offlineUrl
property string onlineUrl
property bool stared
property bool liked
property bool broadcast
property bool read
property int index
property int feedindex
property bool cached
property variant _settings: settings
property int markAsReadTime: 4000
property int toolbarHideTime: 4000
property bool readerMode: false
property bool nightMode: false
property bool readerModePossible: false
property bool nightModePossible: true
property bool autoReaderMode: settings.readerMode
property bool autoRead: true
readonly property color bgColor: Theme.colorScheme ? Qt.lighter(Theme.highlightBackgroundColor, 1.9) :
Qt.darker(Theme.highlightBackgroundColor, 4.0)
function share() {
pageStack.push(Qt.resolvedUrl("ShareLinkPage.qml"),{"link": root.onlineUrl, "linkTitle": root.title});
}
function openUrlEntryInBrowser(url) {
Qt.openUrlExternally(url)
}
function onlineDownload(url, id) {
dm.onlineDownload(id, url)
proggressPanel.text = qsTr("Loading page content...")
proggressPanel.open = true
}
function init() {
navigate(settings.offlineMode ? offlineUrl : onlineUrl)
}
function navigate(url) {
if (settings.offlineMode) {
// WORKAROUND for https://github.com/mkiol/kaktus/issues/14
//utils.resetQtWebKit()
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
/*console.log("xhr.onreadystatechange")
console.log(" xhr.readyState: " + xhr.readyState)
console.log(" xhr.status: " + xhr.status)
console.log(" xhr.responseType: " + xhr.responseType)
console.log(" xhr.responseURL : " + xhr.responseURL )
console.log(" xhr.statusText: " + xhr.statusText)*/
if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
view.loadHtml(xhr.responseText)
}
}
xhr.open("GET", offlineUrl);
xhr.send()
} else {
view.url = url
}
}
function navigateBack() {
if (view.canGoBack) {
root.readerModePossible = false
root.nightModePossible = false
view.goBack()
} else {
pageStack.pop()
}
}
function initTheme() {
var theme = { "primaryColor": Theme.rgba(Theme.primaryColor, 1.0).toString(),
"secondaryColor": Theme.rgba(Theme.secondaryColor, 1.0).toString(),
"highlightColor": Theme.rgba(Theme.highlightColor, 1.0).toString(),
"bgColor": root.bgColor.toString(),
"fontFamily": Theme.fontFamily,
"fontFamilyHeading": Theme.fontFamilyHeading,
"pageMargin": Theme.horizontalPageMargin/Theme.pixelRatio,
"pageMarginBottom": Theme.itemSizeMedium/Theme.pixelRatio,
"fontSize": Theme.fontSizeMedium,
"fontSizeTitle": Theme.fontSizeLarge,
"zoom": settings.zoom}
postMessage("theme_set", { "theme": theme })
postMessage("theme_update_scale")
}
function updateZoom(delta) {
var zoom = settings.zoom;
settings.zoom = ((zoom + delta) <= 0.5) || ((zoom + delta) >= 2.0) ? zoom : zoom + delta
var theme = { "zoom": settings.zoom }
postMessage("theme_set", { "theme": theme })
postMessage("theme_update_scale")
baner.show("" + Math.floor(settings.zoom * 100) + "%")
}
function switchReaderMode() {
postMessage(root.readerMode ? "readability_disable" : "readability_enable")
}
function switchNightMode() {
postMessage(root.nightMode ? "nightmode_disable" : "nightmode_enable")
}
function messageReceivedHandler(message) {
//console.log("view.url: " + view.url)
if (message.type === "inited") {
// NightMode
root.nightModePossible = true
if ((settings.nightMode || root.nightMode) && !settings.offlineMode) {
postMessage("nightmode_enable")
} else {
postMessage("nightmode_disable")
}
// Theme
initTheme()
} else if (message.type === "readability_result") {
root.readerModePossible = message.data.possible
root.readerMode = message.data.enabled
// Auto switch to reader mode
if (!root.readerMode && root.readerModePossible &&
(root.autoReaderMode || settings.offlineMode)) {
switchReaderMode()
root.autoReaderMode = false
} else if (settings.offlineMode) {
postMessage("theme_apply")
}
} else if (message.type === "readability_status") {
console.log("readability_status: " + message.data.enabled)
} else if (message.type === "readability_enabled") {
root.readerMode = true
view.scrollToTop()
} else if (message.type === "readability_disabled") {
root.readerMode = false
view.scrollToTop()
} else if (message.type === "nightmode_enabled") {
root.nightMode = true
} else if (message.type === "nightmode_disabled") {
root.nightMode = false
}
}
function postMessage(message, data) {
view.experimental.postMessage(JSON.stringify({ "type": message, "data": data }));
}
showNavigationIndicator: false
allowedOrientations: {
switch (settings.allowedOrientations) {
case 1:
return Orientation.PortraitMask;
case 2:
return Orientation.LandscapeMask;
}
return Orientation.All;
}
Component.onCompleted: init()
Connections {
target: fetcher
onBusyChanged: {
if(fetcher.busy) {
pageStack.pop();
}
}
}
SilicaWebView {
id: view
anchors { top: parent.top; left: parent.left; right: parent.right}
height: parent.height
experimental.preferences.javascriptEnabled: true
experimental.preferences.navigatorQtObjectEnabled: true
experimental.preferredMinimumContentsWidth: 980
experimental.overview: false
experimental.enableResizeContent: true
experimental.userAgent: _settings.getDmUserAgent()
//experimental.transparentBackground: true
experimental.userScripts: [
Qt.resolvedUrl("js/Kaktus.js"),
Qt.resolvedUrl("js/Console.js"),
Qt.resolvedUrl("js/MessageListener.js"),
Qt.resolvedUrl("js/NightMode.js"),
Qt.resolvedUrl("js/Readability.js"),
Qt.resolvedUrl("js/Theme.js"),
Qt.resolvedUrl("js/ReaderMode.js"),
Qt.resolvedUrl("js/init.js")]
experimental.onMessageReceived: {
console.log("onMessageReceived data:", message.data)
root.messageReceivedHandler(JSON.parse(message.data))
}
onLoadingChanged: {
switch (loadRequest.status) {
case WebView.LoadStartedStatus:
proggressPanel.text = qsTr("Loading page content...");
proggressPanel.open = true;
break;
case WebView.LoadSucceededStatus:
proggressPanel.open = false;
// Start timer to mark as read
if (!root.read && root.autoRead)
timer.start();
// Readability.js
postMessage("readability_apply_fixups")
postMessage("readability_check", { "title": view.canGoBack ? "" : root.title });
break;
case WebView.LoadFailedStatus:
proggressPanel.open = false;
if (_settings.offlineMode) {
notification.show(qsTr("Failed to load page from local cache"));
} else {
notification.show(qsTr("Failed to load page content"));
}
break;
default:
proggressPanel.open = false;
}
}
onNavigationRequested: {
/*console.log("onNavigationRequested: ")
console.log(" url:",request.url)
console.log(" navigation type:", request.navigationType)
console.log(" navigation LinkClickedNavigation:", request.navigationType === WebView.LinkClickedNavigation)
console.log(" navigation FormSubmittedNavigation:", request.navigationType === WebView.FormSubmittedNavigation)
console.log(" navigation BackForwardNavigation:", request.navigationType === WebView.BackForwardNavigation)
console.log(" navigation ReloadNavigation:", request.navigationType === WebView.ReloadNavigation)
console.log(" navigation FormResubmittedNavigation:", request.navigationType === WebView.FormResubmittedNavigation)
console.log(" navigation OtherNavigation:", request.navigationType === WebView.OtherNavigation)
console.log(" action:", request.action);*/
if (!Qt.application.active) {
request.action = WebView.IgnoreRequest
return
}
// Offline
if (settings.offlineMode) {
if (request.navigationType === WebView.LinkClickedNavigation) {
request.action = WebView.IgnoreRequest
} else {
request.action = WebView.AcceptRequest
}
return
}
// Online
if (request.navigationType === WebView.LinkClickedNavigation) {
if (_settings.webviewNavigation === 0) {
request.action = WebView.IgnoreRequest;
return;
}
if (_settings.webviewNavigation === 1) {
request.action = WebView.IgnoreRequest;
root.openUrlEntryInBrowser(request.url);
return;
}
if (_settings.webviewNavigation === 2) {
request.action = WebView.AcceptRequest
return;
}
}
request.action = WebView.AcceptRequest
}
}
TempBaner {
id: baner
anchors.centerIn: root
}
IconBar {
id: controlbar
flickable: view
color: root.bgColor
showable: !hideToolbarTimer.running
IconBarItem {
text: qsTr("Back")
icon: "image://theme/icon-m-back"
onClicked: root.navigateBack()
}
IconBarItem {
text: qsTr("Toggle Read")
icon: root.read ? "image://icons/icon-m-read-selected" : "image://icons/icon-m-read"
onClicked: {
if (root.read) {
root.read=false;
entryModel.setData(root.index, "read", 0, "");
} else {
root.read=true;
entryModel.setData(root.index, "read", 1, "");
}
}
}
IconBarItem {
text: app.isNetvibes ? qsTr("Toggle Save") : qsTr("Toggle Star")
icon: root.stared ? "image://theme/icon-m-favorite-selected" : "image://theme/icon-m-favorite"
onClicked: {
if (root.stared) {
root.stared=false;
entryModel.setData(root.index, "readlater", 0, "");
} else {
root.stared=true;
entryModel.setData(root.index, "readlater", 1, "");
}
}
}
IconBarItem {
text: qsTr("Toggle Reader View")
icon: root.readerMode ? "image://icons/icon-m-reader-selected" : "image://icons/icon-m-reader"
enabled: root.readerModePossible && !settings.offlineMode
visible: !settings.offlineMode
onClicked: {
root.switchReaderMode()
}
}
IconBarItem {
text: qsTr("Toggle Night View")
icon: root.nightMode ? "image://icons/icon-m-night-selected" : "image://icons/icon-m-night"
enabled: !root.readerMode && !settings.offlineMode
visible: !settings.offlineMode
onClicked: {
root.switchNightMode()
}
}
IconBarItem {
text: qsTr("Browser")
icon: "image://icons/icon-m-browser"
onClicked: {
var url = view.url.toString().lastIndexOf("about") === 0 ||
view.url.length === 0 ? root.onlineUrl : view.url
console.log("Opening: " + url)
Qt.openUrlExternally(url)
}
}
IconMenuItem_ {
text: qsTr("Add to Pocket")
visible: settings.pocketEnabled
enabled: settings.pocketEnabled && dm.online
icon.source: "image://icons/icon-m-pocket?" + Theme.primaryColor
busy: pocket.busy
onClicked: {
pocket.add(root.onlineUrl, root.title)
}
}
// not available in harbour package
IconMenuItem_ {
text: qsTr("Share link")
icon.source: "image://theme/icon-m-share"
onClicked: root.share()
visible: !settings.isHarbour()
}
IconBarItem {
text: qsTr("Toggle Like")
icon: root.liked ? "image://icons/icon-m-like-selected" : "image://icons/icon-m-like"
enabled: settings.showBroadcast && app.isOldReader
onClicked: {
entryModel.setData(root.index, "liked", !root.liked, "");
root.liked = !root.liked
}
}
IconBarItem {
text: qsTr("Toggle Share")
icon: root.broadcast ? "image://icons/icon-m-share-selected" : "image://icons/icon-m-share"
enabled: settings.showBroadcast && app.isOldReader && !root.friendStream
onClicked: {
if (root.broadcast) {
entryModel.setData(root.index, "broadcast", false, "");
} else {
pageStack.push(Qt.resolvedUrl("ShareDialog.qml"),{"index": root.index});
}
root.broadcast = !root.broadcast
}
}
IconBarItem {
text: qsTr("Copy URL")
icon: "image://theme/icon-m-clipboard"
onClicked: {
notification.show(qsTr("URL was copied to the clipboard"));
Clipboard.text = root.onlineUrl;
}
}
IconBarItem {
text: qsTr("Decrease font")
icon: "image://icons/icon-m-fontdown"
onClicked: {
root.updateZoom(-0.1)
}
}
IconBarItem {
text: qsTr("Increase font")
icon: "image://icons/icon-m-fontup"
onClicked: {
root.updateZoom(0.1)
}
}
IconBarItem {
text: qsTr("Hide toolbar")
icon: "image://theme/icon-m-dismiss"
onClicked: {
hideToolbarTimer.start()
controlbar.hide()
}
}
}
ProgressPanel {
id: proggressPanel
anchors.left: parent.left
anchors.bottom: parent.bottom
cancelable: true
onCloseClicked: view.stop()
}
Timer {
id: timer
interval: root.markAsReadTime
onTriggered: {
if (!root.read) {
read=true;
entryModel.setData(root.index, "read", 1, "");
}
}
}
Timer {
id: hideToolbarTimer
interval: root.toolbarHideTime
}
}