Commit 35780ccf authored by Christian Hergert's avatar Christian Hergert

html-preview: implement html/markdown preview as a plugin

This moves the WebKit2 dependency into a plugin, allowing us to
drop it from the core binary.
parent 689513fe
......@@ -3,12 +3,18 @@ if ENABLE_HTML_PREVIEW_PLUGIN
EXTRA_DIST = $(plugin_DATA)
plugindir = $(libdir)/gnome-builder/plugins
plugin_DATA = \
html-preview.plugin \
html_preview.py
dist_plugin_DATA = html-preview.plugin
moduledir = $(libdir)/gnome-builder/plugins/html_preview_plugin
dist_module_DATA = \
html_preview_plugin/__init__.py \
html_preview_plugin/markdown.css \
html_preview_plugin/markdown-view.js \
html_preview_plugin/marked.js \
$(NULL)
endif
GITIGNOREFILES = __pycache__
GITIGNOREFILES = html_preview_plugin/__pycache__
-include $(top_srcdir)/git.mk
#!/usr/bin/env python3
#
# html_preview_plugin.py
#
# Copyright (C) 2015 Christian Hergert <chris@dronelabs.com>
#
# This program 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.
#
# This program 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/>.
#
from gettext import gettext as _
from gi.repository import Builder
from gi.repository import Gio
from gi.repository import GObject
from gi.repository import Ide
class HtmlPreviewAddin(GObject.Object, Builder.EditorView):
def do_load(self, editor):
self.menu_extension = Builder.MenuExtension(editor.get_menu())
menu_item = Gio.MenuItem()
menu_item.set_label(_("Preview as HTML"))
menu_item.set_detailed_action('view.preview-as-html')
self.menu_extension.append_menu_item(menu_item)
def do_unload(self, editor):
self.menu_extension.remove_items()
#!/usr/bin/env python3
#
# html_preview_plugin.py
#
# Copyright (C) 2015 Christian Hergert <chris@dronelabs.com>
#
# This program 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.
#
# This program 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/>.
#
from gettext import gettext as _
import markdown2
import gi
import os
gi.require_version('Builder', '1.0')
gi.require_version('Ide', '1.0')
gi.require_version('WebKit2', '4.0')
from gi.repository import Builder
from gi.repository import GLib
from gi.repository import Gio
from gi.repository import Gtk
from gi.repository import GObject
from gi.repository import Ide
from gi.repository import WebKit2
from gi.repository import Peas
class HtmlPreviewData(GObject.Object, Builder.ApplicationAddin):
MARKDOWN_CSS = None
MARKED_JS = None
MARKDOWN_VIEW_JS = None
def do_load(self, app):
settings = WebKit2.Settings()
settings.enable_html5_local_storage = False
HtmlPreviewData.MARKDOWN_CSS = self.get_data('markdown.css')
HtmlPreviewData.MARKED_JS = self.get_data('marked.js')
HtmlPreviewData.MARKDOWN_VIEW_JS = self.get_data('markdown-view.js')
def get_data(self, name):
engine = Peas.Engine.get_default()
info = engine.get_plugin_info('html_preview_plugin')
datadir = info.get_data_dir()
print ("DATA_DIR: %s\n", datadir)
path = os.path.join(datadir, name)
return open(path, 'r').read()
class HtmlPreviewAddin(GObject.Object, Builder.EditorViewAddin):
def do_load(self, editor):
self.menu = HtmlPreviewMenu(editor.get_menu())
actions = editor.get_action_group('view')
action = Gio.SimpleAction(name='preview-as-html', enabled=True)
action.connect('activate', lambda *_: self.preview_activated(editor))
actions.add_action(action)
def do_unload(self, editor):
self.menu.hide()
def do_language_changed(self, language_id):
self.menu.hide()
if language_id in ('html', 'markdown'):
self.menu.show()
def preview_activated(self, editor):
document = editor.get_document()
view = HtmlPreviewView(document, visible=True)
stack = editor.get_ancestor(Builder.ViewStack)
stack.add(view)
class HtmlPreviewMenu:
exten = None
def __init__(self, menu):
self.exten = Builder.MenuExtension.new_for_section(menu, 'preview-section')
def show(self):
item = Gio.MenuItem.new(_("Preview as HTML"), 'view.preview-as-html')
self.exten.append_menu_item(item)
def hide(self):
self.exten.remove_items()
class HtmlPreviewView(Builder.View):
markdown = False
def __init__(self, document, *args, **kwargs):
super().__init__(*args, **kwargs)
self.document = document
self.webview = WebKit2.WebView(visible=True, expand=True)
self.add(self.webview)
language = document.get_language()
if language and language.get_id() == 'markdown':
self.markdown = True
document.connect('changed', self.on_changed)
self.on_changed(document)
def do_get_title(self):
title = self.document.get_title()
return '%s (Preview)' % title
def do_get_document(self):
return self.document
def get_markdown(self, text):
text = text.replace("\"", "\\\"").replace("\n", "\\n")
params = (HtmlPreviewData.MARKDOWN_CSS,
text,
HtmlPreviewData.MARKED_JS,
HtmlPreviewData.MARKDOWN_VIEW_JS)
return """
<html>
<head>
<style>%s</style>
<script>var str="%s";</script>
<script>%s</script>
<script>%s</script>
</head>
<body onload="preview()">
<div class="markdown-body" id="preview">
</div>
</body>
</html>
""" % params
def reload(self):
base_uri = self.document.get_file().get_file().get_uri()
begin, end = self.document.get_bounds()
text = self.document.get_text(begin, end, True)
if self.markdown:
text = self.get_markdown(text)
self.webview.load_html(text, base_uri)
def on_changed(self, document):
self.reload()
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: false,
smartLists: true,
smartypants: false
});
function preview(){
document.getElementById('preview').innerHTML = marked(str);
}
/* this file came from https://raw.githubusercontent.com/sindresorhus/github-markdown-css/gh-pages/github-markdown.css */
.markdown-body {
font-family: sans-serif;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
font: 13px Helvetica, arial, freesans, clean, sans-serif;
line-height: 1.4;
color: #333333;
font-size: 15px;
line-height: 1.7;
overflow: hidden;
word-wrap: break-word;
}
.markdown-body a {
background: transparent;
}
.markdown-body a:active,
.markdown-body a:hover {
outline: 0;
}
.markdown-body strong {
font-weight: bold;
}
.markdown-body h1 {
font-size: 2em;
margin: 0.67em 0;
}
.markdown-body img {
border: 0;
}
.markdown-body hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
.markdown-body pre {
overflow: auto;
}
.markdown-body code,
.markdown-body pre {
font-family: monospace, monospace;
font-size: 1em;
}
.markdown-body table {
border-collapse: collapse;
border-spacing: 0;
}
.markdown-body td,
.markdown-body th {
padding: 0;
}
.markdown-body * {
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.markdown-body a {
color: #4183c4;
text-decoration: none;
}
.markdown-body a:hover {
text-decoration: underline;
}
.markdown-body a:focus,
.markdown-body a:active {
text-decoration: underline;
}
.markdown-body hr {
height: 0;
margin: 15px 0;
overflow: hidden;
background: transparent;
border: 0;
border-bottom: 1px solid #ddd;
}
.markdown-body hr:before,
.markdown-body hr:after {
content: " ";
display: table;
}
.markdown-body hr:after {
clear: both;
}
.markdown-body ol ol {
list-style-type: lower-roman;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin-top: 15px;
margin-bottom: 15px;
line-height: 1.1;
}
.markdown-body h1 {
font-size: 30px;
}
.markdown-body h2 {
font-size: 21px;
}
.markdown-body h3 {
font-size: 16px;
}
.markdown-body h4 {
font-size: 14px;
}
.markdown-body h5 {
font-size: 12px;
}
.markdown-body h6 {
font-size: 11px;
}
.markdown-body blockquote {
margin: 0;
}
.markdown-body ul,
.markdown-body ol {
padding: 0;
margin-top: 0;
margin-bottom: 0;
}
.markdown-body dd {
margin-left: 0;
}
.markdown-body code,
.markdown-body pre {
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 12px;
}
.markdown-body pre {
margin-top: 0;
margin-bottom: 0;
}
.markdown-body>*:first-child {
margin-top: 0 !important;
}
.markdown-body>*:last-child {
margin-bottom: 0 !important;
}
.markdown-body a.anchor {
display: block;
padding-right: 6px;
padding-left: 30px;
margin-left: -30px;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
bottom: 0;
}
.markdown-body a.anchor:focus {
outline: none;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin: 1em 0 15px;
padding: 0;
font-weight: bold;
line-height: 1.7;
cursor: text;
position: relative;
}
.markdown-body h1 {
font-size: 2.5em;
border-bottom: 1px solid #ddd;
}
.markdown-body h2 {
font-size: 2em;
border-bottom: 1px solid #eee;
}
.markdown-body h3 {
font-size: 1.5em;
}
.markdown-body h4 {
font-size: 1.2em;
}
.markdown-body h5 {
font-size: 1em;
}
.markdown-body h6 {
color: #777;
font-size: 1em;
}
.markdown-body p,
.markdown-body blockquote,
.markdown-body ul,
.markdown-body ol,
.markdown-body dl,
.markdown-body table,
.markdown-body pre {
margin: 15px 0;
}
.markdown-body hr {
background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC) repeat-x 0 0;
border: 0 none;
color: #ccc;
height: 4px;
padding: 0;
margin: 15px 0;
}
.markdown-body ul,
.markdown-body ol {
padding-left: 30px;
}
.markdown-body ol ol,
.markdown-body ol ul {
margin-top: 0;
margin-bottom: 0;
}
.markdown-body dl {
padding: 0;
}
.markdown-body dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin-top: 15px;
}
.markdown-body dl dd {
margin-bottom: 15px;
padding: 0 15px;
}
.markdown-body blockquote {
border-left: 4px solid #DDD;
padding: 0 15px;
color: #777;
}
.markdown-body blockquote>:first-child {
margin-top: 0px;
}
.markdown-body blockquote>:last-child {
margin-bottom: 0px;
}
.markdown-body table {
width: 100%;
overflow: auto;
display: block;
}
.markdown-body table th {
font-weight: bold;
}
.markdown-body table th,
.markdown-body table td {
border: 1px solid #ddd;
padding: 6px 13px;
}
.markdown-body table tr {
border-top: 1px solid #ccc;
background-color: #fff;
}
.markdown-body table tr:nth-child(2n) {
background-color: #f8f8f8;
}
.markdown-body img {
max-width: 100%;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.markdown-body code {
margin: 0;
border: 1px solid #ddd;
background-color: #f8f8f8;
border-radius: 3px;
padding: 0;
}
.markdown-body code:before,
.markdown-body code:after {
content: "\00a0";
letter-spacing: -0.2em;
}
.markdown-body pre>code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent;
}
.markdown-body .highlight pre,
.markdown-body pre {
background-color: #f8f8f8;
border: 1px solid #ddd;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px;
}
.markdown-body pre {
word-wrap: normal;
}
.markdown-body pre code {
margin: 0;
padding: 0;
background-color: transparent;
border: none;
word-wrap: normal;
max-width: initial;
display: inline;
overflow: initial;
line-height: inherit;