From 5f2cac54e8688b563c83936bdedf9a931abc8dd0 Mon Sep 17 00:00:00 2001 From: Mathieu PATUREL Date: Fri, 15 Nov 2019 07:21:41 +1100 Subject: [PATCH] use a resource system to load images and stylesheet --- MarkdownLivePreview.py | 10 +++++++--- live-testing/images.md | 2 +- markdown2html.py | 15 ++++++--------- resources.py | 17 +++++++++++++++++ resources/404.base64 | 1 + resources/404.png | Bin 0 -> 5938 bytes resources/convertresources.py | 12 ++++++++++++ resources/loading.base64 | 1 + resources/loading.png | Bin 0 -> 953 bytes resources/stylesheet.css | 3 +++ 10 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 resources.py create mode 100644 resources/404.base64 create mode 100644 resources/404.png create mode 100644 resources/convertresources.py create mode 100644 resources/loading.base64 create mode 100644 resources/loading.png create mode 100644 resources/stylesheet.css diff --git a/MarkdownLivePreview.py b/MarkdownLivePreview.py index 5bc40fb..5f99e63 100644 --- a/MarkdownLivePreview.py +++ b/MarkdownLivePreview.py @@ -6,6 +6,7 @@ from functools import partial from .markdown2html import markdown2html from .utils import * +from .resources import resources def plugin_loaded(): pass @@ -52,7 +53,6 @@ class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand): # FIXME: save the document to a temporary file, so that if we crash, # the user doesn't lose what he wrote - sublime.run_command('new_window') preview_window = sublime.active_window() @@ -183,8 +183,12 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener): markdown = markdown_view.substr(total_region) basepath = os.path.dirname(markdown_view.file_name()) - html = markdown2html(markdown, basepath, partial(self._update_preview, - markdown_view)) + html = markdown2html( + markdown, + basepath, + partial(self._update_preview, markdown_view), + resources + ) self.phantom_sets[markdown_view.id()].update([ sublime.Phantom(sublime.Region(0), html, sublime.LAYOUT_BLOCK, diff --git a/live-testing/images.md b/live-testing/images.md index 268e112..734f94e 100644 --- a/live-testing/images.md +++ b/live-testing/images.md @@ -3,7 +3,7 @@ I'm not sure that it **actually** going to work, but it seems nicer than the [pr This is the first image from the local file system (absolute path, sorry, it's not going to work on your system unless your username is math2001): -![The sublime text logo!](file:///home/math2001/.config/sublime-text-3/Packages/MarkdownLivePreview2/live-testing/sublime_text.png) +![The sublime text logo!](file:///home/math2001/.config/sublime-text-3/Packages/MarkdownLivePreview/live-testing/sublime_text.png) This is the first image from the local file system, *relative* path! diff --git a/markdown2html.py b/markdown2html.py index 3d4792c..04430ca 100644 --- a/markdown2html.py +++ b/markdown2html.py @@ -16,16 +16,12 @@ markdowner = Markdown(extras=['fenced-code-blocks']) # does it stupidly throw them out? (we could implement something of our own) executor = concurrent.futures.ThreadPoolExecutor(max_workers=5) -# FIXME: put a nice picture please :^) -BASE64_LOADING_IMAGE = 'loading image!' -BASE64_404_IMAGE = '404 not found :-(' - images_cache = {} class LoadingError(Exception): pass -def markdown2html(markdown, basepath, re_render): +def markdown2html(markdown, basepath, re_render, resources): """ converts the markdown to html, loads the images and puts in base64 for sublime to understand them correctly. That means that we are responsible for loading the images from the internet. Hence, we take in re_render, which is just a function we @@ -55,10 +51,9 @@ def markdown2html(markdown, basepath, re_render): try: base64 = get_base64_image(path, re_render) except FileNotFoundError as e: - base64 = BASE64_404_IMAGE + base64 = resources['base64_404_image'] except LoadingError: - # the image is loading - base64 = BASE64_LOADING_IMAGE + base64 = resources['base64_loading_image'] img_element['src'] = base64 @@ -72,7 +67,7 @@ def markdown2html(markdown, basepath, re_render): # FIXME: include a stylesheet - return str(soup) + return "\n\n{}".format(resources['stylesheet'], soup) def get_base64_image(path, re_render): @@ -93,6 +88,8 @@ def get_base64_image(path, re_render): executor.submit(load_image, path).add_done_callback(partial(callback, path)) raise LoadingError() + # FIXME: use some kind of cache for this as well, because it decodes on every + # keystroke here... with open(path, 'rb') as fp: return 'data:image/png;base64,' + base64.b64encode(fp.read()).decode('utf-8') diff --git a/resources.py b/resources.py new file mode 100644 index 0000000..cecc707 --- /dev/null +++ b/resources.py @@ -0,0 +1,17 @@ +import os.path +import sublime + +def get_resource(resource): + path = 'Packages/MarkdownLivePreview/resources/' + resource + abs_path = os.path.join(sublime.packages_path(), '..', path) + if os.path.isfile(abs_path): + with open(abs_path, 'r') as fp: + return fp.read() + return sublime.load_resource(path) + +resources = {} + +def plugin_loaded(): + resources["base64_loading_image"] = get_resource('loading.base64') + resources["base64_404_image"] = get_resource('404.base64') + resources["stylesheet"] = get_resource('stylesheet.css') diff --git a/resources/404.base64 b/resources/404.base64 new file mode 100644 index 0000000..d82c610 --- /dev/null +++ b/resources/404.base64 @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/resources/404.png b/resources/404.png new file mode 100644 index 0000000000000000000000000000000000000000..af1ef6a23a4769907969e3fa8b9d89bbb069281a GIT binary patch literal 5938 zcmV-27tQF2P)004&$004{<008`G004!O004R9008an00288001E0zggwW000W8 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 brmE_ythVavtFXo@>#VfaYU{1H1_1y&aJ8`j literal 0 HcmV?d00001 diff --git a/resources/stylesheet.css b/resources/stylesheet.css new file mode 100644 index 0000000..12780e3 --- /dev/null +++ b/resources/stylesheet.css @@ -0,0 +1,3 @@ +body { + font-family: "Open Sans", sans-serif, serif; +} \ No newline at end of file