Commit 349587cf authored by Jan-Michael Brummer's avatar Jan-Michael Brummer

Add clean reader mode

Main part is Readability.js which does the page magic on every page load. Idea based on Eolie implementation.

Fixes: https://bugzilla.gnome.org/show_bug.cgi?id=772831
parent dfca5570
......@@ -88,6 +88,14 @@ struct _EphyWebView {
char *link_message;
GdkPixbuf *icon;
/* Reader mode */
char *reader_content;
char *reader_byline;
gboolean reader_loading;
gboolean reader_active;
guint reader_js_timeout;
char *reader_url;
/* Local file watch. */
EphyFileMonitor *file_monitor;
......@@ -150,6 +158,7 @@ enum {
PROP_STATUS_MESSAGE,
PROP_TYPED_ADDRESS,
PROP_IS_BLANK,
PROP_READER_MODE,
LAST_PROP
};
......@@ -416,6 +425,9 @@ ephy_web_view_get_property (GObject *object,
case PROP_IS_BLANK:
g_value_set_boolean (value, view->is_blank);
break;
case PROP_READER_MODE:
g_value_set_boolean (value, view->reader_content != NULL);
break;
default:
break;
}
......@@ -444,6 +456,7 @@ ephy_web_view_set_property (GObject *object,
case PROP_SECURITY:
case PROP_STATUS_MESSAGE:
case PROP_IS_BLANK:
case PROP_READER_MODE:
/* read only */
break;
default:
......@@ -936,6 +949,11 @@ ephy_web_view_dispose (GObject *object)
view->snapshot_timeout_id = 0;
}
if (view->reader_js_timeout) {
g_source_remove (view->reader_js_timeout);
view->reader_js_timeout = 0;
}
g_clear_object (&view->certificate);
G_OBJECT_CLASS (ephy_web_view_parent_class)->dispose (object);
......@@ -956,6 +974,10 @@ ephy_web_view_finalize (GObject *object)
g_free (view->tls_error_failing_uri);
g_free (view->pending_snapshot_uri);
g_free (view->reader_content);
g_free (view->reader_byline);
g_free (view->reader_url);
G_OBJECT_CLASS (ephy_web_view_parent_class)->finalize (object);
}
......@@ -969,6 +991,72 @@ _ephy_web_view_set_is_blank (EphyWebView *view,
}
}
static
char *readability_get_property_string (WebKitJavascriptResult *js_result,
char *property)
{
JSCValue *jsc_value;
char *result = NULL;
jsc_value = webkit_javascript_result_get_js_value (js_result);
if (!jsc_value_is_object (jsc_value))
return NULL;
if (jsc_value_object_has_property (jsc_value, property)) {
JSCValue *jsc_content = jsc_value_object_get_property (jsc_value, property);
result = jsc_value_to_string (jsc_content);
if (result && strcmp (result, "null") == 0)
g_clear_pointer (&result, g_free);
g_object_unref (jsc_content);
}
return result;
}
static void
readability_js_finish_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
EphyWebView *view = EPHY_WEB_VIEW (user_data);
WebKitJavascriptResult *js_result;
GError *error = NULL;
js_result = webkit_web_view_run_javascript_finish (WEBKIT_WEB_VIEW (object), result, &error);
if (!js_result) {
g_warning ("Error running javascript: %s", error->message);
g_error_free (error);
return;
}
g_clear_pointer (&view->reader_byline, g_free);
g_clear_pointer (&view->reader_content, g_free);
view->reader_byline = readability_get_property_string (js_result, "byline");
view->reader_content = readability_get_property_string (js_result, "content");
g_object_notify_by_pspec (G_OBJECT (view), obj_properties[PROP_READER_MODE]);
}
static gboolean
run_readability_js (gpointer data)
{
EphyWebView *web_view = data;
web_view->reader_js_timeout = 0;
webkit_web_view_run_javascript_from_gresource (WEBKIT_WEB_VIEW (web_view),
"/org/gnome/epiphany/readability.js",
NULL,
readability_js_finish_cb,
web_view);
return G_SOURCE_REMOVE;
}
static void
title_changed_cb (WebKitWebView *web_view,
GParamSpec *spec,
......@@ -1239,6 +1327,18 @@ ephy_web_view_class_init (EphyWebViewClass *klass)
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
/**
* EphyWebView:reader-mode:
*
* Whether the view is in reader mode.
**/
obj_properties[PROP_READER_MODE] =
g_param_spec_boolean ("reader-mode",
"Reader mode",
"If the EphyWebView is in reader mode",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, LAST_PROP, obj_properties);
/**
......@@ -1793,6 +1893,16 @@ load_changed_cb (WebKitWebView *web_view,
ephy_web_view_set_address (view, loading_uri);
ephy_web_view_set_loading_message (view, loading_uri);
if (!view->reader_loading) {
g_clear_pointer (&view->reader_byline, g_free);
g_clear_pointer (&view->reader_content, g_free);
view->reader_active = FALSE;
}
g_object_notify_by_pspec (G_OBJECT (view), obj_properties[PROP_READER_MODE]);
break;
}
case WEBKIT_LOAD_REDIRECTED:
......@@ -1834,6 +1944,10 @@ load_changed_cb (WebKitWebView *web_view,
/* Zoom level. */
restore_zoom_level (view, uri);
/* Reset reader content */
if (!view->reader_active)
g_clear_pointer (&view->reader_content, g_free);
break;
}
case WEBKIT_LOAD_FINISHED:
......@@ -1862,6 +1976,15 @@ load_changed_cb (WebKitWebView *web_view,
ephy_web_view_thaw_history (view);
if (view->reader_js_timeout) {
g_source_remove (view->reader_js_timeout);
view->reader_js_timeout = 0;
}
view->reader_loading = FALSE;
if (!view->reader_active)
view->reader_js_timeout = g_idle_add (run_readability_js, web_view);
break;
default:
......@@ -2766,6 +2889,8 @@ ephy_web_view_load_url (EphyWebView *view,
g_assert (EPHY_IS_WEB_VIEW (view));
g_assert (url);
view->reader_active = FALSE;
effective_url = ephy_embed_utils_normalize_address (url);
if (g_str_has_prefix (effective_url, "javascript:")) {
char *decoded_url;
......@@ -3550,3 +3675,77 @@ ephy_web_view_set_visit_type (EphyWebView *view, EphyHistoryPageVisitType visit_
view->visit_type = visit_type;
}
/**
* ephy_web_view_toggle_reader_mode:
* @view: an #EphyWebView
* @active: active flag
*
* Sets reader mode state to @active if necessary.
**/
void
ephy_web_view_toggle_reader_mode (EphyWebView *view,
gboolean active)
{
WebKitWebView *web_view = WEBKIT_WEB_VIEW (view);
GString *html;
GBytes *style_css;
const gchar *title;
if (view->reader_active == active)
return;
if (view->reader_active) {
ephy_web_view_freeze_history (view);
webkit_web_view_load_uri (web_view, view->reader_url);
view->reader_active = FALSE;
return;
}
if (!ephy_web_view_is_reader_mode_available (view)) {
view->reader_active = FALSE;
return;
}
view->reader_url = g_strdup (ephy_web_view_get_address (view));
html = g_string_new ("");
style_css = g_resources_lookup_data ("/org/gnome/epiphany/reader.css", G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
title = webkit_web_view_get_title (web_view);
g_string_append_printf (html, "<style>%s</style>"
"<title>%s</title>"
"<body>"
"<article>"
"<h2>"
"%s"
"</h2>"
"<i>"
"%s"
"</i>"
"<hr>",
(gchar *)g_bytes_get_data (style_css, NULL),
title,
title,
view->reader_byline != NULL ? view->reader_byline : "");
g_string_append (html, view->reader_content);
g_string_append (html, "</article>");
ephy_web_view_freeze_history (view);
view->reader_loading = TRUE;
webkit_web_view_load_alternate_html (web_view, html->str, view->reader_url, NULL);
view->reader_active = TRUE;
g_string_free (html, TRUE);
}
gboolean
ephy_web_view_is_reader_mode_available (EphyWebView *view)
{
return view->reader_content != NULL && strlen (view->reader_content) > 0;
}
gboolean
ephy_web_view_get_reader_mode_state (EphyWebView *view)
{
return view->reader_active;
}
......@@ -149,4 +149,11 @@ char * ephy_web_view_create_web_application (EphyWebView
const char *title,
GdkPixbuf *icon);
void ephy_web_view_toggle_reader_mode (EphyWebView *view,
gboolean active);
gboolean ephy_web_view_is_reader_mode_available (EphyWebView *view);
gboolean ephy_web_view_get_reader_mode_state (EphyWebView *view);
G_END_DECLS
......@@ -66,6 +66,8 @@ struct _EphyHeaderBar {
EphyWindow *window;
EphyTitleWidget *title_widget;
GtkWidget *navigation_box;
GtkWidget *reader_mode_revealer;
GtkWidget *reader_mode_button;
GtkWidget *new_tab_revealer;
GtkWidget *new_tab_button;
GtkWidget *combined_stop_reload_button;
......@@ -605,6 +607,18 @@ notebook_show_tabs_changed_cb (GtkNotebook *notebook,
}
}
static void
reader_mode_button_toggled_cb (GtkWidget *button,
gpointer user_data)
{
EphyHeaderBar *header_bar = EPHY_HEADER_BAR (user_data);
EphyWindow *window = ephy_header_bar_get_window (header_bar);
EphyEmbed *embed = ephy_embed_container_get_active_child (EPHY_EMBED_CONTAINER (window));
EphyWebView *view = ephy_embed_get_web_view (embed);
ephy_web_view_toggle_reader_mode (view, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)));
}
static void
ephy_header_bar_constructed (GObject *object)
{
......@@ -813,6 +827,23 @@ ephy_header_bar_constructed (GObject *object)
G_CALLBACK (downloads_estimated_progress_cb),
object, 0);
/* Reader Mode */
header_bar->reader_mode_revealer = gtk_revealer_new ();
gtk_revealer_set_transition_type (GTK_REVEALER (header_bar->reader_mode_revealer), GTK_REVEALER_TRANSITION_TYPE_CROSSFADE);
gtk_header_bar_pack_end (GTK_HEADER_BAR (header_bar), header_bar->reader_mode_revealer);
button = gtk_toggle_button_new ();
g_signal_connect_object (button, "toggled",
G_CALLBACK (reader_mode_button_toggled_cb),
object, 0);
header_bar->reader_mode_button = button;
gtk_button_set_image (GTK_BUTTON (button),
gtk_image_new_from_icon_name ("view-dual-symbolic", GTK_ICON_SIZE_BUTTON));
gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
gtk_container_add (GTK_CONTAINER (header_bar->reader_mode_revealer), button);
gtk_widget_show (button);
gtk_widget_show (header_bar->reader_mode_revealer);
gtk_header_bar_pack_end (GTK_HEADER_BAR (header_bar), header_bar->downloads_revealer);
gtk_widget_show (header_bar->downloads_revealer);
}
......@@ -891,3 +922,20 @@ ephy_header_bar_get_window (EphyHeaderBar *header_bar)
{
return header_bar->window;
}
void
ephy_header_bar_set_reader_mode_state (EphyHeaderBar *header_bar,
EphyWebView *view)
{
EphyWindow *window = ephy_header_bar_get_window (header_bar);
EphyEmbed *embed = ephy_embed_container_get_active_child (EPHY_EMBED_CONTAINER (window));
EphyWebView *active_view = ephy_embed_get_web_view (embed);
gboolean available = ephy_web_view_is_reader_mode_available (view);
if (active_view != view)
return;
gtk_revealer_set_reveal_child (GTK_REVEALER (header_bar->reader_mode_revealer), available);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (header_bar->reader_mode_button),
ephy_web_view_get_reader_mode_state (view));
}
......@@ -41,5 +41,7 @@ EphyTitleWidget *ephy_header_bar_get_title_widget (EphyHeaderBa
GtkWidget *ephy_header_bar_get_zoom_level_button (EphyHeaderBar *header_bar);
GtkWidget *ephy_header_bar_get_page_menu_button (EphyHeaderBar *header_bar);
EphyWindow *ephy_header_bar_get_window (EphyHeaderBar *header_bar);
void ephy_header_bar_set_reader_mode_state (EphyHeaderBar *header_bar,
EphyWebView *view);
G_END_DECLS
......@@ -2435,6 +2435,14 @@ download_only_load_cb (EphyWebView *view,
g_idle_add (delayed_remove_child, EPHY_GET_EMBED_FROM_EPHY_WEB_VIEW (view));
}
static void
reader_mode_cb (EphyWebView *view,
GParamSpec *pspec,
EphyWindow *window)
{
ephy_header_bar_set_reader_mode_state (EPHY_HEADER_BAR (window->header_bar), view);
}
static void
notebook_page_added_cb (EphyNotebook *notebook,
EphyEmbed *embed,
......@@ -2448,6 +2456,9 @@ notebook_page_added_cb (EphyNotebook *notebook,
g_signal_connect_object (ephy_embed_get_web_view (embed), "download-only-load",
G_CALLBACK (download_only_load_cb), window, G_CONNECT_AFTER);
g_signal_connect_object (ephy_embed_get_web_view (embed), "notify::reader-mode",
G_CALLBACK (reader_mode_cb), window, G_CONNECT_AFTER);
if (window->present_on_insert) {
window->present_on_insert = FALSE;
g_idle_add ((GSourceFunc)present_on_idle_cb, g_object_ref (window));
......@@ -2573,6 +2584,7 @@ notebook_switch_page_cb (GtkNotebook *notebook,
EphyEmbed *embed;
GActionGroup *group;
GAction *action;
EphyWebView *view;
LOG ("switch-page notebook %p position %u\n", notebook, page_num);
......@@ -2581,6 +2593,7 @@ notebook_switch_page_cb (GtkNotebook *notebook,
/* get the new tab */
embed = real_get_active_tab (window, page_num);
view = ephy_embed_get_web_view (embed);
/* update new tab */
ephy_window_set_active_tab (window, embed);
......@@ -2589,6 +2602,8 @@ notebook_switch_page_cb (GtkNotebook *notebook,
group = gtk_widget_get_action_group (GTK_WIDGET (window), "win");
action = g_action_map_lookup_action (G_ACTION_MAP (group), "show-tab");
g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_uint32 (page_num));
ephy_header_bar_set_reader_mode_state (EPHY_HEADER_BAR (window->header_bar), view);
}
static GtkNotebook *
......
......@@ -30,6 +30,8 @@
<file preprocess="xml-stripblanks" compressed="true">gtk/search-engine-dialog.ui</file>
<file preprocess="xml-stripblanks" compressed="true">gtk/synced-tabs-dialog.ui</file>
<file preprocess="xml-stripblanks" compressed="true">gtk/shortcuts-dialog.ui</file>
<file compressed="true">readability.js</file>
<file compressed="true">reader.css</file>
</gresource>
<gresource prefix="/org/gnome/Epiphany/icons">
<file compressed="true" alias="scalable/actions/ephy-bookmarks-symbolic.svg">ephy-bookmarks-symbolic.svg</file>
......
This diff is collapsed.
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Alternatively, the contents of this file may be used under the terms
* of the GNU General Public License Version 3, as described below:
*
* This file is free software: you may copy, redistribute and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Changes
* - replace Firefox specific css extensions with WebKit css extensions
* - append FeedView css selectors
*
* - Adjusted for Epiphany (Removing footer)
*/
h1,
h2,
h3 {
font-weight: bold;
}
h1 {
font-size: 1.6em;
line-height: 1.25em;
}
h2 {
font-size: 1.2em;
line-height: 1.51em;
}
h3 {
font-size: 1em;
line-height: 1.66em;
}
a {
text-decoration: underline;
font-weight: normal;
}
a,
a:visited,
a:hover,
a:active {
color: #0095dd;
}
* {
max-width: 100%;
height: auto;
}
p,
code,
pre,
blockquote,
ul,
ol,
li,
figure,
.wp-caption {
margin: 0 0 30px 0;
}
p > img:only-child,
p > a:only-child > img:only-child,
.wp-caption img,
figure img {
display: block;
}
.caption,
.wp-caption-text,
figcaption {
font-size: 0.9em;
line-height: 1.48em;
font-style: italic;
}
code,
pre {
white-space: pre-wrap;
}
blockquote {
padding: 0;
-webkit-padding-start: 16px;
}
ul,
ol {
padding: 0;
}
ul {
-webkit-padding-start: 30px;
list-style: disc;
}
ol {
-webkit-padding-start: 30px;
list-style: decimal;
}
/* Hide elements with common "hidden" class names */
.visually-hidden,
.visuallyhidden,
.hidden,
.invisible,
.sr-only {
display: none;
}
article {
overflow-y: hidden;
margin: 20px auto;
width: 640px;
color: #333;
font-family: Sans;
font-size: 18px;
word-wrap:break-word;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment