Refactored Platypush blog repo.

Removed all the Python logic + templates and styles.
Those have now been moved to a stand-alone project (madblog),
therefore this repo should only contain the static blog
pages and images.
fediverse-article
Fabio Manganiello 9 months ago
parent d176b47944
commit f34f1f6232
  1. 23
      README.md
  2. 157
      app/__init__.py
  3. 18
      app/__main__.py
  4. 248
      app/latex.py
  5. 10
      config.yaml
  6. 0
      img/adafruit-16-pwm.jpg
  7. 0
      img/arduino-1.gif
  8. 0
      img/baby-1.png
  9. 0
      img/baby-2.jpg
  10. 0
      img/brushless-motor-1.png
  11. 0
      img/ci-1.png
  12. 0
      img/custom-widget-1.png
  13. 0
      img/dashboard-1.png
  14. 0
      img/data-visualization-1.png
  15. 0
      img/drone-lift.gif
  16. 0
      img/drone-schema.png
  17. 0
      img/drone-warning.png
  18. 0
      img/esc-1.jpg
  19. 0
      img/esc-pwm.png
  20. 0
      img/extension-1.png
  21. 0
      img/extension-2.png
  22. 0
      img/extension-3.png
  23. 0
      img/extension-4.png
  24. 0
      img/extension-5.png
  25. 0
      img/extension-6.png
  26. 0
      img/extension-7.png
  27. 0
      img/extension-8.png
  28. 0
      img/favicon.ico
  29. 0
      img/git-integration-1.png
  30. 0
      img/gitlab-1.png
  31. 0
      img/google-fit-1.png
  32. 0
      img/grafana-1.png
  33. 0
      img/grafana-2.png
  34. 0
      img/grafana-3.png
  35. 0
      img/icon.png
  36. 0
      img/light-hue-ui-1.png
  37. 0
      img/lipo-1.jpg
  38. 0
      img/madness-screenshot.png
  39. 0
      img/mopidy-iris-1.jpeg
  40. 0
      img/mpd-1.png
  41. 0
      img/ncmpcpp-1.png
  42. 0
      img/ncmpcpp-2.png
  43. 0
      img/ncmpcpp-3.png
  44. 0
      img/newsletter-1.png
  45. 0
      img/notebook.jpg
  46. 0
      img/obsidian-screenshot.png
  47. 0
      img/people-detect-1.png
  48. 0
      img/people-detect-2.png
  49. 0
      img/people-detect-3.png
  50. 0
      img/people-detect-4.png
  51. 0
      img/people-detect-5.png
  52. 0
      img/pitch-roll-yaw-1.png
  53. 0
      img/pitch-roll-yaw-2.png
  54. 0
      img/propeller-1.png
  55. 0
      img/propeller-schema.png
  56. 0
      img/pwm-1.png
  57. 0
      img/rpi-pinout.jpg
  58. 0
      img/rss-1.jpeg
  59. 0
      img/rss.png
  60. 0
      img/self-hosted-notebook-architecture.png
  61. 0
      img/self-hosted-notebook-architecture.svg
  62. 0
      img/self-hosted-notebook-extension-1.png
  63. 0
      img/snapcast-1.png
  64. 0
      img/snapcast-2.jpeg
  65. 0
      img/standards.png
  66. 0
      img/static-thrust-1.png
  67. 0
      img/tasker-screen-1.jpeg
  68. 0
      img/tasker-screen-2.jpeg
  69. 0
      img/tasker-screen-3.jpeg
  70. 0
      img/tasker-screen-4.jpeg
  71. 0
      img/tasker-screen-5.jpeg
  72. 0
      img/tasker-screen-6.jpeg
  73. 0
      img/telegram-1.jpg
  74. 0
      img/telegram-2.png
  75. 0
      img/telegram-3.png
  76. 0
      img/telegram-4.png
  77. 0
      img/voice-1.jpg
  78. 0
      img/voice-assistant-1.png
  79. 0
      img/xt60-board.jpg
  80. 0
      img/zigbee-zwave-1.jpg
  81. 0
      img/zigbee-zwave-2.png
  82. 0
      img/zigbee-zwave-3.png
  83. 0
      img/zigbee-zwave-4.png
  84. 0
      img/zigbee-zwave-5.png
  85. 0
      img/zigbee-zwave-6.png
  86. 0
      markdown/Build-a-bot-to-communicate-with-your-smart-home-over-Telegram.md
  87. 0
      markdown/Build-an-open-source-drone-with-a-Raspberry-Pi-and-Platypush.md
  88. 0
      markdown/Build-custom-voice-assistants.md
  89. 0
      markdown/Build-your-customizable-voice-assistant-with-Platypush.md
  90. 0
      markdown/Build-your-open-source-multi-room-and-multi-provider-sound-server-with-Platypush-Mopidy-and-Snapcast.md
  91. 0
      markdown/Build-your-self-hosted-Evernote.md
  92. 0
      markdown/Create-your-smart-baby-monitor-with-Platypush-and-Tensorflow.md
  93. 0
      markdown/Deliver-articles-to-your-favourite-e-reader-using-Platypush.md
  94. 0
      markdown/Deliver-customized-newsletters-from-RSS-feeds-with-Platypush.md
  95. 0
      markdown/Detect-people-with-a-RaspberryPi-a-thermal-camera-Platypush-and-a-pinch-of-machine-learning.md
  96. 0
      markdown/How-to-build-your-personal-infrastructure-for-data-collection-and-visualization.md
  97. 0
      markdown/One-browser-extension-to-rule-them-all.md
  98. 0
      markdown/Set-up-self-hosted-CI-CD-git-pipelines-with-Platypush.md
  99. 0
      markdown/Transform-a-RaspberryPi-into-a-universal-Zigbee-and-Z-Wave-bridge.md
  100. 0
      markdown/Ultimate-self-hosted-automation-with-Platypush.md
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,16 +1,21 @@
# Platypush blog pages and engine
# Platypush blog pages
This project provides the pages and the webapp needed by the Platypush blog.
This project provides the content of the Platypush blog.
## Dependencies
It uses [`madblog`](https://git.platypush.tech/blacklight/madblog)
as a Markdown-based blogging micro-framework.
- `flask`
- `markdown`
- `pygments`
## Setup
## Start the web app
```shell
$ pip install madblog
```
## Run
```shell
# The application will listen on port 8000
python -m app
$ git clone https://git.platypush.tech/platypush/blog.git
$ cd blog
$ madblog
```

@ -1,157 +0,0 @@
import datetime
import os
import re
from glob import glob
from typing import Optional
from flask import Flask, Response, abort, send_from_directory, render_template
from markdown import markdown
from .latex import MarkdownLatex
basedir = os.path.abspath(os.path.join(os.path.realpath(__file__), '..', '..'))
templates_dir = os.path.join(basedir, 'templates')
static_dir = os.path.join(basedir, 'static')
pages_dir = os.path.join(static_dir, 'pages')
img_dir = os.path.join(static_dir, 'img')
css_dir = os.path.join(static_dir, 'css')
fonts_dir = os.path.join(static_dir, 'fonts')
app = Flask(__name__, template_folder=templates_dir)
def parse_page_title(page: str) -> str:
if page.endswith('.md'):
page = page[:-3]
return page.replace('-', ' ')
def get_page_metadata(page: str) -> dict:
if not page.endswith('.md'):
page = page + '.md'
if not os.path.isfile(os.path.join(pages_dir, page)):
abort(404)
metadata = {}
with open(os.path.join(pages_dir, page), 'r') as f:
metadata['uri'] = '/article/' + page[:-3]
for line in f.readlines():
if not line:
continue
if not (m := re.match(r'^\[//]: # \(([^:]+):\s*([^)]+)\)\s*$', line)):
break
if m.group(1) == 'published':
metadata[m.group(1)] = datetime.date.fromisoformat(m.group(2))
else:
metadata[m.group(1)] = m.group(2)
return metadata
def get_page(page: str, title: Optional[str] = None, skip_header: bool = False):
if not page.endswith('.md'):
page = page + '.md'
metadata = get_page_metadata(page)
with open(os.path.join(pages_dir, page), 'r') as f:
return render_template('article.html',
title=title if title else metadata.get('title', 'Platypush - Blog'),
image=metadata.get('image'),
description=metadata.get('description'),
author=re.match(r'(.+?)\s+<([^>]+>)', metadata['author'])[1] if 'author' in metadata else None,
author_email=re.match(r'(.+?)\s+<([^>]+)>', metadata['author'])[2] if 'author' in metadata else None,
published=(metadata['published'].strftime('%b %d, %Y')
if metadata.get('published') else None),
content=markdown(f.read(), extensions=['fenced_code', 'codehilite', MarkdownLatex()]),
skip_header=skip_header)
def get_pages(with_content: bool = False, skip_header: bool = False) -> list:
return sorted([
{
'path': path,
'content': get_page(path, skip_header=skip_header) if with_content else '',
**get_page_metadata(os.path.basename(path)),
}
for path in glob(os.path.join(pages_dir, '*.md'))
], key=lambda page: page.get('published'), reverse=True)
@app.route('/', methods=['GET'])
def home_route():
return render_template('index.html', pages=get_pages())
@app.route('/favicon.ico', methods=['GET'])
def favicon_route():
return send_from_directory(img_dir, 'favicon.ico')
@app.route('/img/<img>', methods=['GET'])
def img_route(img: str):
return send_from_directory(img_dir, img)
@app.route('/css/<style>', methods=['GET'])
def css_route(style: str):
return send_from_directory(css_dir, style)
@app.route('/fonts/<file>', methods=['GET'])
def fonts_route(file: str):
return send_from_directory(fonts_dir, file)
@app.route('/article/<article>', methods=['GET'])
def article_route(article: str):
return get_page(article)
@app.route('/rss', methods=['GET'])
def rss_route():
pages = get_pages(with_content=True, skip_header=True)
return Response('''<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
<channel>
<title>Platypush blog feeds</title>
<link>http://blog.platypush.tech</link>
<description>Insights and inspirational projects using Platypush as an automation platform</description>
<category>Programming, automation, Python, machine learning, IoT, smart home</category>
<image>
<url>https://git.platypush.tech/uploads/-/system/appearance/header_logo/1/icon-256.png</url>
<title>Platypush</title>
<link>https://git.platypush.tech</link>
</image>
<pubDate>{last_pub_date}</pubDate>
<language>en-us</language>
{items}
</channel>
</rss>'''.format(
last_pub_date=pages[0]['published'].strftime('%a, %d %b %Y %H:%M:%S GMT'),
items='\n\n'.join([
'''
<item>
<title>{title}</title>
<link>https://blog.platypush.tech{link}</link>
<pubDate>{published}</pubDate>
<description><![CDATA[{content}]]></description>
<media:content medium="image" url="https://blog.platypush.tech{image}" width="200" height="150" />
</item>
'''.format(
title=page.get('title', '[No Title]'),
link=page.get('uri', ''),
published=page['published'].strftime('%a, %d %b %Y %H:%M:%S GMT') if 'published' in page else '',
content=page.get('content', ''),
image=page.get('image', ''),
)
for page in pages
]),
), mimetype='application/rss+xml')

@ -1,18 +0,0 @@
import argparse
import sys
from . import app
def get_args():
parser = argparse.ArgumentParser()
parser.add_argument('--host', dest='host', required=False, default='0.0.0.0', help='Bind host/address')
parser.add_argument('--port', dest='port', required=False, type=int, default=8000, help='Bind port (default: 8000)')
parser.add_argument('--debug', dest='debug', required=False, action='store_true', default=False,
help='Enable debug mode (default: False)')
return parser.parse_known_args(sys.argv[1:])
opts = get_args()[0]
app.run(host=opts.host, port=opts.port, debug=opts.debug)

@ -1,248 +0,0 @@
"""
Licensed under Public Domain Mark 1.0.
See https://creativecommons.org/publicdomain/mark/1.0/
Author: Justin Bruce Van Horne <justinvh@gmail.com>
Python-Markdown LaTeX Extension
Adds support for $math mode$ and %text mode%. This plugin supports
multiline equations/text.
The actual image generation is done via LaTeX/DVI output.
It encodes data as base64 so there is no need for images directly.
All the work is done in the preprocessor.
"""
import base64
import hashlib
import json
import os
import re
import tempfile
from subprocess import call as rawcall, PIPE
import markdown
def call(*args, **kwargs):
"""
Proxy to subprocess.call(), removes timeout argument in case of
Python2 because that was only implemented in Python3.
"""
return rawcall(*args, **kwargs)
# Defines our basic inline image
img_expr = '<img class="latex inline math-%s" alt="%s" id="%s" src="data:image/png;base64,%s">'
# Defines multiline expression image
multiline_img_expr = '''<div class="multiline-wrapper">
<img class="latex multiline math-%s" alt="%s" id="%s" src="data:image/png;base64,%s"></div>'''
# Base CSS template
img_css = """<style scoped>
.multiline-wrapper {
width: 100%;
text-align: center;
}
img.latex.multiline {
height: 65%;
}
img.latex.inline {
height: .9em;
vertical-align: middle;
}
</style>"""
# Cache and temp file paths
tmpdir = tempfile.gettempdir() + '/markdown-latex'
cache_file = tmpdir + '/latex.cache'
class LaTeXPreprocessor(markdown.preprocessors.Preprocessor):
# These are our cached expressions that are stored in latex.cache
cached = {}
# Basic LaTex Setup as well as our list of expressions to parse
tex_preamble = r"""\documentclass[14pt]{article}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{amssymb}
\usepackage{bm}
\usepackage{graphicx}
\usepackage[usenames,dvipsnames]{color}
\pagestyle{empty}
"""
# Math TeX extraction regex
math_extract_regex = re.compile(r'(.+?)((\\\(.+?\\\))|(\$\$\n.+?\n\$\$\n))(.*)', re.MULTILINE | re.DOTALL)
# Math TeX matching regex
math_match_regex = re.compile(r'\s*(\\\(.+?\\\))|(\$\$\n.+?\n\$\$\n)\s*', re.MULTILINE | re.DOTALL)
def __init__(self, *_, **__):
if not os.path.isdir(tmpdir):
os.makedirs(tmpdir)
try:
with open(cache_file, 'r') as f:
self.cached = json.load(f)
except (IOError, json.JSONDecodeError):
self.cached = {}
self.config = {
("general", "preamble"): "",
("dvipng", "args"): "-q -T tight -bg Transparent -z 9 -D 200",
("delimiters", "text"): "%",
("delimiters", "math"): "$",
("delimiters", "preamble"): "%%"}
def _latex_to_base64(self, tex):
"""Generates a base64 representation of TeX string"""
# Generate the temporary file
tmp_file_fd, path = tempfile.mkstemp(dir=tmpdir)
with os.fdopen(tmp_file_fd, "w") as tmp_file:
tmp_file.write(self.tex_preamble)
tmp_file.write(tex)
tmp_file.write('\n\\end{document}')
# compile LaTeX document. A DVI file is created
status = call(('latex -halt-on-error -output-directory={:s} {:s}'
.format(tmpdir, path)).split(),
stdout=PIPE, timeout=10)
# clean up if the above failed
if status:
self._cleanup(path, err=True)
raise Exception("Couldn't compile LaTeX document." +
"Please read '%s.log' for more detail." % path)
# Run dvipng on the generated DVI file. Use tight bounding box.
# Magnification is set to 1200
dvi = "%s.dvi" % path
png = "%s.png" % path
# Extract the image
cmd = "dvipng %s %s -o %s" % (self.config[("dvipng", "args")], dvi, png)
status = call(cmd.split(), stdout=PIPE)
# clean up if we couldn't make the above work
if status:
self._cleanup(path, err=True)
raise Exception("Couldn't convert LaTeX to image." +
"Please read '%s.log' for more detail." % path)
# Read the png and encode the data
try:
with open(png, "rb") as png:
data = png.read()
return base64.b64encode(data)
finally:
self._cleanup(path)
@staticmethod
def _cleanup(path, err=False):
# don't clean up the log if there's an error
extensions = ["", ".aux", ".dvi", ".png", ".log"]
if err:
extensions.pop()
# now do the actual cleanup, passing on non-existent files
for extension in extensions:
try:
os.remove("%s%s" % (path, extension))
except (IOError, OSError):
pass
def run(self, lines):
"""Parses the actual page"""
# Checks for the LaTeX header
use_latex = any(line == '[//]: # (latex: 1)' for line in lines)
if not use_latex:
return lines
# Re-creates the entire page so we can parse in a multiline env.
page = "\n".join(lines)
# Adds a preamble mode
self.tex_preamble += self.config[("general", "preamble")] + "\n\\begin{document}\n"
# Figure out our text strings and math-mode strings
tex_expr = self.math_extract_regex.findall(page)
# No sense in doing the extra work
if not len(tex_expr):
return page.split("\n")
# Parse the expressions
new_cache = {}
new_page = ''
n_multiline_expressions = 0
while page:
m = self.math_extract_regex.match(page)
if not m:
new_page += page
break
new_page += m.group(1)
math_match = self.math_match_regex.match(m.group(2))
if not math_match:
new_page += m.group(2)
else:
expr = m.group(2)
is_multiline = math_match.group(2) is not None
tex_hash = self.hash(expr)
if tex_hash in self.cached:
data = self.cached[tex_hash]
else:
data = self._latex_to_base64(expr).decode()
new_cache[tex_hash] = data
if is_multiline and n_multiline_expressions > 0:
new_page += '</p>'
new_page += (multiline_img_expr if is_multiline else img_expr) % ('true', expr, tex_hash, data)
if is_multiline:
new_page += '<p>'
n_multiline_expressions += 1
page = m.group(5)
if n_multiline_expressions > 0:
new_page += '</p>'
# Cache our data
self.cached.update(new_cache)
with open(cache_file, 'w') as f:
json.dump(self.cached, f)
# Make sure to re-split the lines
return new_page.split("\n")
@staticmethod
def hash(tex: str) -> str:
return hashlib.sha1(tex.encode()).hexdigest()
class LaTeXPostprocessor(markdown.postprocessors.Postprocessor):
"""This post processor extension just allows us to further
refine, if necessary, the document after it has been parsed."""
# noinspection PyMethodMayBeStatic
def run(self, text):
# Inline a style for default behavior
text = img_css + text
return text
class MarkdownLatex(markdown.Extension):
"""Wrapper for LaTeXPreprocessor"""
def extendMarkdown(self, md):
# Our base LaTeX extension
md.preprocessors.add('latex',
LaTeXPreprocessor(self), ">html_block")
# Our cleanup postprocessing extension
md.postprocessors.add('latex',
LaTeXPostprocessor(self), ">amp_substitute")

@ -0,0 +1,10 @@
title: Platypush
description: The Platypush blog
link: https://blog.platypush.tech
home_link: https://platypush.tech
categories:
- IoT
- automation
- python
- programming
- machine learning

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 206 KiB

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 727 KiB

After

Width:  |  Height:  |  Size: 727 KiB

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 211 KiB

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before

Width:  |  Height:  |  Size: 231 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Before

Width:  |  Height:  |  Size: 4.4 MiB

After

Width:  |  Height:  |  Size: 4.4 MiB

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Before

Width:  |  Height:  |  Size: 175 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 186 KiB

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save