├── README.md ├── pages ├── about.md ├── blog │ ├── 2014 │ │ └── ipsum-lorem.md │ └── 2015 │ │ ├── instructions.md │ │ └── markdown-sample.md ├── contact.md └── index.md ├── requirements.txt ├── site-build.command ├── site-run.command ├── site.py ├── static ├── blog │ ├── 2014 │ │ └── snow_leopard_background.jpg │ └── 2015 │ │ ├── branding_blank_ad.jpg │ │ ├── lake.jpg │ │ ├── smartphone_background.jpg │ │ └── zte-blade.jpg ├── images │ ├── aboutme_bw.jpg │ ├── favicon.ico │ ├── lake3_branding.jpg │ ├── maciphone.jpg │ ├── people_bw.jpg │ ├── pier_bw.jpg │ ├── social-accounts.svg │ └── workarea_bw.jpg └── styles │ ├── extras.css │ └── style.css └── templates ├── base ├── 404.html ├── base.html ├── comments.html ├── footer.html ├── header.html ├── navbar.html └── section.html ├── blog ├── articles.html ├── index.html └── page.html └── pages ├── index.html └── page.html /README.md: -------------------------------------------------------------------------------- 1 | # Flask-static-site 2 | 3 | Flask-static-site is a skeleton Python/Flask application ready to be deployed as a static website. You just need to define your own styles, update the content and build the website. 4 | 5 | Flask-static-site is released under an MIT Licence. 6 | 7 | 8 | ## Motivation 9 | 10 | Building static websites with frameworks such as Flask allows for a clear separation of concerns. For instance, you can separate website functionality over multiple files or use technologies, such as markdown, to generate HTML content. It gives you the benefits of a dynamic framework with the speed of serving static files. 11 | 12 | This project is based on [this tutorial by Nicolas Perriault](https://nicolas.perriault.net/code/2012/dead-easy-yet-powerful-static-website-generator-with-flask/). You should try to do it, and maybe check my code for some other ideas. 13 | 14 | 15 | ## Installation 16 | 17 | Flask-static-site is a Python 3 application and depends on the **Flask** microframework, **flask_flatpages** to read and parse markdown files from the filesystem and **flask_frozen** to generate static html files for each URL route. Install them with: 18 | 19 | > pip3 install -r requirements.txt 20 | 21 | To get flask-static-site, you can clone this repository or download a zip file from the right menu. 22 | 23 | 24 | ## Development & Building 25 | 26 | Start the development server with `python3 site.py`, open a browser and go to [http://localhost:8000](http://localhost:8000). You should see the skeleton website fully functional. 27 | 28 | To add content to the website, open the **pages** folder and check its content. Basically, every website page is a markdown file converted to HTML. In **pages/blog** you will find the blog articles. 29 | 30 | In the **static** folder you should put all static files, such as images, css styles and other files. Adapt the css to your own needs. 31 | 32 | If you need to change some functionality, check the flask source code at **site.py**. It's the only Python code, and it is very simple to understand once you understand Flask. 33 | 34 | Finally, to build the static website, just run `python3 site.py build` in the console. The result will be in **/build**. Upload it to a static webserver and you're done! 35 | 36 | -------------------------------------------------------------------------------- /pages/about.md: -------------------------------------------------------------------------------- 1 | title: About 2 | image: /static/images/aboutme_bw.jpg 3 | 4 | 5 | 6 | Flask-static-site is a skeleton Python/Flask application ready to be deployed as a static website. You just need to define your own styles, update the content and build the website. It is released under an MIT Licence. 7 | 8 | Feel free to explore the source-code and when ready, adapt it to your own needs, change the css styles, and start writing your own content. 9 | 10 | --- 11 | 12 | # Ipsum Lorem 13 | 14 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec leo diam. Donec porta, sem quis semper dictum, sem ex pharetra nulla, vitae tristique turpis urna vel augue. In justo dolor, tincidunt ut nibh vel, eleifend aliquet metus. Duis nisl augue, vehicula quis vehicula at, dictum at diam. Aliquam ante purus, ornare nec leo nec, dignissim porttitor turpis. Integer urna odio, venenatis vitae leo eget, sollicitudin scelerisque ante. Vestibulum commodo id quam id dapibus. In non mauris at libero egestas posuere accumsan vitae est. Phasellus turpis urna, rhoncus id volutpat tempor, pulvinar condimentum justo. Nulla cursus ante sed urna sollicitudin dapibus non dapibus odio. Sed vel mollis velit. Nulla mauris turpis, gravida non ultrices in, placerat et odio. 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | INTERESTED? SAY HELLO 23 |
24 | -------------------------------------------------------------------------------- /pages/blog/2014/ipsum-lorem.md: -------------------------------------------------------------------------------- 1 | title: Ipsum Lorem 2 | date: 2014-06-03 3 | published: true 4 | image: /static/blog/2014/snow_leopard_background.jpg 5 | summary: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec leo diam. Donec porta, sem quis semper dictum, sem ex pharetra nulla, vitae tristique turpis urna vel augue. (...) 6 | 7 | 8 | 9 | # A title 10 | 11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec leo diam. Donec porta, sem quis semper dictum, sem ex pharetra nulla, vitae tristique turpis urna vel augue. In justo dolor, tincidunt ut nibh vel, eleifend aliquet metus. Duis nisl augue, vehicula quis vehicula at, dictum at diam. Aliquam ante purus, ornare nec leo nec, dignissim porttitor turpis. Integer urna odio, venenatis vitae leo eget, sollicitudin scelerisque ante. Vestibulum commodo id quam id dapibus. In non mauris at libero egestas posuere accumsan vitae est. Phasellus turpis urna, rhoncus id volutpat tempor, pulvinar condimentum justo. Nulla cursus ante sed urna sollicitudin dapibus non dapibus odio. Sed vel mollis velit. Nulla mauris turpis, gravida non ultrices in, placerat et odio. 12 | 13 | 14 | ## A subtitle 15 | 16 | Aenean id tincidunt velit. Integer nec ligula eu turpis bibendum consequat ac a ligula. In pharetra, elit et semper lacinia, diam arcu pharetra arcu, sed sollicitudin est erat vitae sem. Aliquam sed urna nulla. Praesent volutpat risus iaculis purus dignissim tincidunt. Phasellus semper tellus ut dictum egestas. Phasellus quis pretium eros. Pellentesque consequat enim vitae luctus feugiat. Aliquam ut porttitor sem, eu tincidunt sapien. Proin gravida erat sed eros condimentum rutrum. Etiam et pulvinar velit. 17 | 18 | ### An H3 19 | 20 | Mauris tristique efficitur elit, vel bibendum ligula finibus non. Quisque in nisl eget elit cursus consectetur vel at lacus. Praesent ornare, ligula vitae ornare ullamcorper, lacus mi commodo magna, nec luctus felis lacus at ligula. Proin massa leo, consectetur in dolor quis, porta dictum leo. Etiam eleifend pharetra metus in tempus. Sed dapibus consectetur facilisis. Sed pharetra metus non urna fringilla, ac rhoncus purus tempus. Etiam sit amet tempor nibh. Duis id imperdiet ligula. 21 | 22 | 23 | ## Another subtitle 24 | 25 | Praesent et libero lectus. Donec sagittis ligula quis sapien ullamcorper cursus. Integer non tincidunt tortor, at aliquam arcu. Nulla gravida diam lacinia posuere consequat. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Maecenas rhoncus, mauris nec dignissim tincidunt, elit justo semper velit, in congue mauris ex ac odio. Nulla placerat turpis nec arcu fermentum auctor. Pellentesque vel risus sed arcu pharetra semper. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque vehicula sem sodales, blandit purus vel, sodales sapien. Ut neque nisi, maximus eu placerat at, ullamcorper sit amet metus. 26 | 27 | Sed enim velit, molestie id justo in, blandit venenatis felis. In auctor suscipit interdum. Etiam ultrices, nibh quis fringilla ullamcorper, sapien lacus pretium lorem, ut venenatis arcu sem vel elit. Maecenas a orci eu libero tincidunt vestibulum. Duis id bibendum dolor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer quis venenatis nibh, eget aliquet tellus. Suspendisse dictum sem eget accumsan laoreet. In at dapibus libero, vel gravida massa. In sed lacus consequat, imperdiet mi nec, euismod mi. 28 | -------------------------------------------------------------------------------- /pages/blog/2015/instructions.md: -------------------------------------------------------------------------------- 1 | title: Some instructions 2 | date: 2015-04-23 3 | published: true 4 | image: /static/blog/2015/branding_blank_ad.jpg 5 | summary: Welcome to the blog section of flask-static-site. To add a blog article just create a file in pages/blog/year and fill the following details. (...) 6 | 7 | 8 | Welcome to the blog section of flask-static-site. To add a blog article just create a file in pages/blog/year and fill the following details. 9 | 10 | * **title**: the title of the blog. 11 | * **date**: date of the publication. 12 | * **published**: if true, the article will appear in the blog index page. 13 | * **image**: background image for the article. 14 | * **summary**: a brief summary to be displayed in the blog index page. 15 | 16 | There is a **static/blog** folder, you should drop all blog related files (images or other) in that folder. 17 | 18 | The blog index page will only include the 3 latest blog articles. If you want to change that number, or include all articles, just update the following function in **site.py**. 19 | 20 | :::python 21 | @app.route('/blog/') 22 | def blog(): 23 | articles = get_blog_articles(reverse=True)[:3] 24 | return render_template('blog/index.html', pages=articles) 25 | 26 | Enjoy it! 27 | -------------------------------------------------------------------------------- /pages/blog/2015/markdown-sample.md: -------------------------------------------------------------------------------- 1 | title: Markdown sample 2 | date: 2015-04-21 3 | published: true 4 | image: /static/blog/2015/smartphone_background.jpg 5 | summary: This blog article shows some examples of markdown code that you can use in your own articles. Check the source code at blog/2015/markdown-sample.md. (...) 6 | 7 | 8 | 9 | This blog article shows some examples of markdown code that you can use in your own articles. Check the source code at **blog/2015/markdown-sample.md**. 10 | 11 | 12 | ## Text style 13 | 14 | You can set *italic text* or **bold text**. 15 | 16 | 17 | ## Hyperlinks 18 | 19 | There are two ways that you can add links to your articles. Using the standard markdown approach, such as this link to [Python](https://www.python.org/) or with html code, such as this link to PySide. You can use what you prefer, the end result is the same. 20 | 21 | 22 | ## Images 23 | 24 | As for images, there you can use markdown: 25 | 26 | ![Alt text](/static/blog/2015/lake.jpg) 27 | 28 | or a regular html image tag (with style set): 29 | 30 | 31 | 32 | You can only style an image and set its size if its an html image element. 33 | 34 | 35 | ## Lists 36 | 37 | Here's an example of an ordered list: 38 | 39 | 1. First element 40 | 2. Second element 41 | 3. Third element 42 | 4. Fourth element 43 | 44 | and one of an unordered list: 45 | 46 | * First element 47 | * Second element 48 | * Third element 49 | * Fourth element 50 | 51 | 52 | ## More examples 53 | 54 | Check [this page](http://daringfireball.net/projects/markdown/) for more examples. 55 | 56 | -------------------------------------------------------------------------------- /pages/contact.md: -------------------------------------------------------------------------------- 1 | title: Contact me 2 | subtitle: Please... 3 | image: /static/images/people_bw.jpg 4 | 5 | 6 | # FLASK-STATIC 7 | 8 | ## Email 9 | myemail@gmail.com 10 | 11 | ## Facebook 12 | https://www.facebook.com/myfacebook 13 | 14 | ## More 15 | Check the social icons at the footer to know where else I can be found! 16 | 17 | --- 18 | 19 | 20 | 21 | 22 |
23 | GOOD PLACE TO WORK 24 |
25 | -------------------------------------------------------------------------------- /pages/index.md: -------------------------------------------------------------------------------- 1 | title: FLASK-STATIC-SITE 2 | image: /static/images/pier_bw.jpg 3 | 4 | 5 | # A brief background 6 | 7 | Flask-static-site is a skeleton Python/Flask application ready to be deployed as a static website. You just need to define your own styles, update the content and build the website. 8 | 9 | Building static websites with frameworks such as Flask allows for a clear separation of concerns. For instance, you can separate website functionality over multiple files or use technologies, such as markdown, to generate HTML content. It gives you the benefits of a dynamic framework with the speed of serving static files. 10 | 11 |
12 | ABOUT FLASK-SKELETON 13 |
14 | 15 | --- 16 | 17 | 18 | # HTML in markdown files 19 | 20 | You can add html code in markdown files. The buttons and this image are HTML. 21 | 22 |
23 | 24 |

I could work everyday in a place like this..

25 |
26 | 27 | --- 28 | 29 | 30 | # Ipsum lorem 31 | 32 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec leo diam. Donec porta, sem quis semper dictum, sem ex pharetra nulla, vitae tristique turpis urna vel augue. In justo dolor, tincidunt ut nibh vel, eleifend aliquet metus. Duis nisl augue, vehicula quis vehicula at, dictum at diam. Aliquam ante purus, ornare nec leo nec, dignissim porttitor turpis. Integer urna odio, venenatis vitae leo eget, sollicitudin scelerisque ante. Vestibulum commodo id quam id dapibus. In non mauris at libero egestas posuere accumsan vitae est. Phasellus turpis urna, rhoncus id volutpat tempor, pulvinar condimentum justo. Nulla cursus ante sed urna sollicitudin dapibus non dapibus odio. Sed vel mollis velit. Nulla mauris turpis, gravida non ultrices in, placerat et odio. 33 | 34 | Aenean id tincidunt velit. Integer nec ligula eu turpis bibendum consequat ac a ligula. In pharetra, elit et semper lacinia, diam arcu pharetra arcu, sed sollicitudin est erat vitae sem. Aliquam sed urna nulla. Praesent volutpat risus iaculis purus dignissim tincidunt. Phasellus semper tellus ut dictum egestas. Phasellus quis pretium eros. Pellentesque consequat enim vitae luctus feugiat. Aliquam ut porttitor sem, eu tincidunt sapien. Proin gravida erat sed eros condimentum rutrum. Etiam et pulvinar velit. 35 | 36 | --- 37 | 38 | 39 | 40 | 41 |
42 | CONTACT 43 |
44 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | flask_flatpages 3 | frozen_flask 4 | pygments -------------------------------------------------------------------------------- /site-build.command: -------------------------------------------------------------------------------- 1 | cd "`dirname "$0"`" 2 | python3 site.py build 3 | -------------------------------------------------------------------------------- /site-run.command: -------------------------------------------------------------------------------- 1 | cd "`dirname "$0"`" 2 | python3 site.py 3 | -------------------------------------------------------------------------------- /site.py: -------------------------------------------------------------------------------- 1 | """ 2 | Flask-static-site is a skeleton Python/Flask application ready to 3 | be deployed as a static website. It is released under an MIT Licence. 4 | 5 | This file is the only Python script, and controls the entire app. 6 | Feel free to explore and adapt it to your own needs. 7 | 8 | """ 9 | 10 | import sys 11 | from datetime import datetime 12 | 13 | from flask import Flask, render_template, render_template_string 14 | 15 | from flask_frozen import Freezer 16 | from flask_flatpages import ( 17 | FlatPages, pygmented_markdown, pygments_style_defs) 18 | 19 | 20 | 21 | def prerender_jinja(text): 22 | """ Pre-renders Jinja templates before markdown. """ 23 | prerendered_body = render_template_string(text) 24 | return pygmented_markdown(prerendered_body) 25 | 26 | 27 | DEBUG = True 28 | FLATPAGES_AUTO_RELOAD = DEBUG 29 | FLATPAGES_EXTENSION = '.md' 30 | FLATPAGES_HTML_RENDERER = prerender_jinja 31 | FLATPAGES_MARKDOWN_EXTENSIONS = ['codehilite'] 32 | 33 | app = Flask(__name__) 34 | app.config.from_object(__name__) 35 | pages = FlatPages(app) 36 | freezer = Freezer(app) 37 | 38 | 39 | 40 | # === URL Routes === # 41 | 42 | @app.route('/') 43 | def index(): 44 | page = pages.get_or_404('index') 45 | return render_template('pages/index.html', page=page) 46 | 47 | 48 | @app.route('//') 49 | def page(path): 50 | page = pages.get_or_404(path) 51 | return render_template('pages/page.html', page=page) 52 | 53 | 54 | @app.route('/blog/') 55 | def blog(): 56 | articles = get_blog_articles(reverse=True)[:3] 57 | return render_template('blog/index.html', pages=articles) 58 | 59 | 60 | @app.route('/blog/articles/') 61 | def blog_articles(): 62 | articles = get_blog_articles_year(reverse=True) 63 | return render_template('blog/articles.html', pages=articles) 64 | 65 | 66 | @app.route('/blog//') 67 | def blog_article(path): 68 | page = pages.get_or_404('blog/' + path) 69 | return render_template('blog/page.html', page=page) 70 | 71 | 72 | @app.route('/pygments.css') 73 | def pygments_css(): 74 | return pygments_style_defs('tango'), 200, {'Content-Type': 'text/css'} 75 | 76 | 77 | 78 | # === Blog articles === # 79 | 80 | def get_blog_articles(reverse=False): 81 | """ Returns all published blog articles ordered by date. """ 82 | articles = [p for p in pages if p.path.startswith('blog')] 83 | articles = [p for p in articles if p.meta.get('published', False)] 84 | articles = sorted(articles, 85 | reverse=reverse, 86 | key=lambda k: k.meta['date']) 87 | return articles 88 | 89 | 90 | def get_blog_articles_year(reverse=False): 91 | """ Returns a list of articles indexed by year. """ 92 | articles = [] 93 | for article in get_blog_articles(reverse): 94 | year = article.meta['date'].year 95 | if len(articles) == 0 or articles[-1][0] != year: 96 | articles.append([year, [article]]) 97 | else: 98 | articles[-1][1].append(article) 99 | return articles 100 | 101 | 102 | 103 | # === Main function === # 104 | 105 | if __name__ == '__main__': 106 | if len(sys.argv) > 1 and sys.argv[1] == 'build': 107 | freezer.freeze() 108 | else: 109 | app.run(host='0.0.0.0', port=8000) 110 | -------------------------------------------------------------------------------- /static/blog/2014/snow_leopard_background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoventura/flask-static-site/ff3e166f4f1913415a0440d71f9f3557a5e10e13/static/blog/2014/snow_leopard_background.jpg -------------------------------------------------------------------------------- /static/blog/2015/branding_blank_ad.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoventura/flask-static-site/ff3e166f4f1913415a0440d71f9f3557a5e10e13/static/blog/2015/branding_blank_ad.jpg -------------------------------------------------------------------------------- /static/blog/2015/lake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoventura/flask-static-site/ff3e166f4f1913415a0440d71f9f3557a5e10e13/static/blog/2015/lake.jpg -------------------------------------------------------------------------------- /static/blog/2015/smartphone_background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoventura/flask-static-site/ff3e166f4f1913415a0440d71f9f3557a5e10e13/static/blog/2015/smartphone_background.jpg -------------------------------------------------------------------------------- /static/blog/2015/zte-blade.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoventura/flask-static-site/ff3e166f4f1913415a0440d71f9f3557a5e10e13/static/blog/2015/zte-blade.jpg -------------------------------------------------------------------------------- /static/images/aboutme_bw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoventura/flask-static-site/ff3e166f4f1913415a0440d71f9f3557a5e10e13/static/images/aboutme_bw.jpg -------------------------------------------------------------------------------- /static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoventura/flask-static-site/ff3e166f4f1913415a0440d71f9f3557a5e10e13/static/images/favicon.ico -------------------------------------------------------------------------------- /static/images/lake3_branding.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoventura/flask-static-site/ff3e166f4f1913415a0440d71f9f3557a5e10e13/static/images/lake3_branding.jpg -------------------------------------------------------------------------------- /static/images/maciphone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoventura/flask-static-site/ff3e166f4f1913415a0440d71f9f3557a5e10e13/static/images/maciphone.jpg -------------------------------------------------------------------------------- /static/images/people_bw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoventura/flask-static-site/ff3e166f4f1913415a0440d71f9f3557a5e10e13/static/images/people_bw.jpg -------------------------------------------------------------------------------- /static/images/pier_bw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoventura/flask-static-site/ff3e166f4f1913415a0440d71f9f3557a5e10e13/static/images/pier_bw.jpg -------------------------------------------------------------------------------- /static/images/social-accounts.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | -------------------------------------------------------------------------------- /static/images/workarea_bw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaoventura/flask-static-site/ff3e166f4f1913415a0440d71f9f3557a5e10e13/static/images/workarea_bw.jpg -------------------------------------------------------------------------------- /static/styles/extras.css: -------------------------------------------------------------------------------- 1 | /*****************************/ 2 | /* CODE HILITE */ 3 | /*****************************/ 4 | 5 | .codehilite pre { 6 | font-size: 1em; 7 | line-height: 1.4em; 8 | overflow-x: scroll; 9 | } 10 | -------------------------------------------------------------------------------- /static/styles/style.css: -------------------------------------------------------------------------------- 1 | /******************************/ 2 | /* PAGE LAYOUT */ 3 | /******************************/ 4 | 5 | html, body { 6 | width: 100%; 7 | height: 100%; 8 | } 9 | 10 | body { 11 | margin: 0px; 12 | font-family: 'Open Sans', sans-serif; 13 | font-weight: 300; 14 | font-size: 18px; 15 | line-height: 1.6; 16 | color: #000; 17 | } 18 | 19 | nav, header, section, footer { 20 | display: block; 21 | } 22 | 23 | hr { 24 | margin: 50px 0; 25 | border: none; 26 | height: 1px; 27 | background-color: #ccc; 28 | } 29 | 30 | h1, h2, h3 { 31 | font-weight: normal; 32 | } 33 | 34 | img { 35 | max-width: 100%; 36 | } 37 | 38 | * { 39 | box-sizing: border-box; 40 | } 41 | 42 | 43 | 44 | /******************************/ 45 | /* CONTAINERS */ 46 | /******************************/ 47 | 48 | .container { 49 | padding-right: 15px; 50 | padding-left: 15px; 51 | margin-right: auto; 52 | margin-left: auto; 53 | } 54 | 55 | .center { 56 | text-align: center; 57 | } 58 | 59 | .isolated { 60 | margin: 50px 50px; 61 | } 62 | 63 | 64 | 65 | /******************************/ 66 | /* NAVBAR */ 67 | /******************************/ 68 | 69 | .navbar { 70 | position: absolute; 71 | width: 100%; 72 | margin: 0; 73 | min-height: 50px; 74 | border: 1px solid #ccc; 75 | border-width: 0 0 1px; 76 | background-color: rgba(255,255,255,0.95); 77 | font-size: 15px; 78 | z-index: 10; 79 | } 80 | 81 | .navbar a { 82 | color: #000; 83 | } 84 | 85 | /* Navbar containers */ 86 | 87 | .navbar-container { 88 | padding-left: 20px; 89 | padding-right: 20px; 90 | } 91 | 92 | .navbar-left { 93 | float: left; 94 | margin-top: 10px; 95 | } 96 | 97 | .navbar-right { 98 | margin-top: 0px; 99 | float: right; 100 | } 101 | 102 | /* Left-side content */ 103 | 104 | #logo-image { 105 | float: left; 106 | width: 30px; 107 | height: 30px; 108 | } 109 | 110 | #logo-image svg { 111 | stroke: orange; 112 | } 113 | 114 | #logo-name { 115 | float: right; 116 | font-size: 22px; 117 | margin-left: 14px; 118 | margin-top: -2px; 119 | } 120 | 121 | /* Right-side content */ 122 | 123 | .navbar-list { 124 | list-style-type: none; 125 | } 126 | 127 | .navbar-list li { 128 | float: left; 129 | margin-left: 30px; 130 | } 131 | 132 | .navbar-list a { 133 | text-decoration: none; 134 | padding-bottom: 1px; 135 | } 136 | 137 | .navbar-list a:hover { 138 | color: orange; 139 | } 140 | 141 | .menu-selected { 142 | border-bottom: solid 2px orange; 143 | color: orange; 144 | } 145 | 146 | /* Submenus */ 147 | 148 | .navbar-expand { 149 | 150 | } 151 | 152 | .navbar-sublist { 153 | display: none; 154 | position: absolute; 155 | background-color: rgba(255,255,255,0.95); 156 | list-style-type: none; 157 | margin-top: 0px; 158 | margin-left: -10px; 159 | border: 1px solid black; 160 | } 161 | 162 | .navbar-expand:hover > .navbar-sublist { 163 | display: inherit; 164 | } 165 | 166 | .navbar-sublist li { 167 | display: list-item; 168 | min-width: 170px; 169 | float:none; 170 | position: relative; 171 | margin-left: -20px; 172 | margin-right: 0px; 173 | margin-top: 10px; 174 | margin-bottom: 10px; 175 | } 176 | 177 | 178 | 179 | /******************************/ 180 | /* HEADER */ 181 | /******************************/ 182 | 183 | header { 184 | position: relative; 185 | background-repeat: no-repeat; 186 | background-position: center top; 187 | background-size: cover; 188 | background-color: black; 189 | width: 100%; 190 | color: white; 191 | } 192 | 193 | /* Header style and image */ 194 | 195 | .header-large { 196 | /*min-height: 100%; 197 | height: 100%;*/ 198 | height: 650px; 199 | } 200 | 201 | .header-small { 202 | height: 550px; 203 | } 204 | 205 | .header-smaller { 206 | height: 500px; 207 | } 208 | 209 | .mask { 210 | width: 100%; 211 | height: 100%; 212 | background-color: rgba(0,0,0,0.5); 213 | } 214 | 215 | /* Header content */ 216 | 217 | .header-content { 218 | position: absolute; 219 | width: 100%; 220 | top: 50%; 221 | padding: 0px 50px; 222 | text-align: center; 223 | transform: translateY(-50%); 224 | -webkit-transform: translateY(-50%); 225 | -ms-transform: translateY(-50%); 226 | } 227 | 228 | .header-content-inner { 229 | margin-right: auto; 230 | margin-left: auto; 231 | max-width: 1000px; 232 | } 233 | 234 | .header-title { 235 | font-size: 62px; 236 | line-height: 1.1; 237 | } 238 | 239 | .header-subtitle { 240 | font-size: 32px; 241 | color: #ffe0b2; 242 | } 243 | 244 | /* Media Modifiers */ 245 | 246 | @media all and (max-width: 750px) { 247 | .header-large { 248 | height: 450px; 249 | } 250 | 251 | .header-small { 252 | height: 350px; 253 | } 254 | 255 | .header-smaller { 256 | height: 300px; 257 | } 258 | 259 | .header-title { 260 | font-size: 42px; 261 | } 262 | } 263 | 264 | 265 | 266 | /******************************/ 267 | /* PAGE CONTENT */ 268 | /******************************/ 269 | 270 | section { 271 | height: auto; 272 | color: rgba(20,20,20,1); 273 | line-height: 1.6; 274 | } 275 | 276 | .page-container { 277 | max-width: 900px; 278 | margin: 0 auto; 279 | padding: 50px; 280 | } 281 | 282 | @media all and (max-width: 500px) { 283 | .page-container { 284 | padding: 20px; 285 | } 286 | } 287 | 288 | section a { 289 | color: #000; 290 | } 291 | 292 | section h1 { 293 | text-align: center; 294 | } 295 | 296 | 297 | 298 | /******************************/ 299 | /* BLOG SPECIFIC */ 300 | /******************************/ 301 | 302 | .blog-image { 303 | text-align: center; 304 | margin: 50px 50px; 305 | } 306 | 307 | .blog-image p { 308 | font-style: italic; 309 | } 310 | 311 | 312 | .codehilite { 313 | line-height: 1.2; 314 | background-color: #efefef; 315 | padding: 5px; 316 | color: black; 317 | white-space: pre-wrap; /* CSS3 */ 318 | white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */ 319 | word-wrap: break-word; /* IE 5.5+ */ 320 | font-size: 14px; 321 | font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, 322 | DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, 323 | monospace,serif; 324 | width: 100%; 325 | } 326 | 327 | 328 | 329 | /******************************/ 330 | /* FOOTER */ 331 | /******************************/ 332 | 333 | footer { 334 | background-color: #1f1f1f; 335 | font-size: 13px; 336 | color: white; 337 | text-align: center; 338 | letter-spacing: 1px; 339 | } 340 | 341 | /* Footer message */ 342 | 343 | .footer-message { 344 | padding: 20px 0 0 0; 345 | margin: 0; 346 | } 347 | 348 | .footer-message li { 349 | list-style-type: none; 350 | margin: 5px 0px; 351 | } 352 | 353 | /* Social icons */ 354 | 355 | .social-link:hover .social-icon-background { 356 | fill: rgba(255,183,77,0.9); 357 | } 358 | 359 | .social-icon { 360 | width: 40px; 361 | height: 40px; 362 | margin: 0px 5px; 363 | } 364 | 365 | .social-icon-glyph { 366 | fill: black; 367 | } 368 | 369 | .social-icon-background { 370 | fill: white; 371 | transition: fill 0.3s ease; 372 | } 373 | 374 | 375 | 376 | /******************************/ 377 | /* BUTTONS */ 378 | /******************************/ 379 | 380 | .button { 381 | font-size: 13px; 382 | letter-spacing: 1px; 383 | border: 2px solid black; 384 | text-align: center; 385 | padding: 14px 36px; 386 | transition: background-color 0.3s ease; 387 | text-align: center; 388 | text-decoration: none; 389 | } 390 | 391 | .button:hover { 392 | background-color: rgba(255,183,77,0.3); 393 | } 394 | 395 | .button-header { 396 | font-size: 16px; 397 | border-color: white; 398 | color: white; 399 | } 400 | -------------------------------------------------------------------------------- /templates/base/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base/base.html" %} 2 | 3 | 4 | 5 | {# ===== Header style and image ===== #} 6 | 7 | {% block header_class %} 8 | header-small 9 | {% endblock %} 10 | 11 | {% block header_background %} 12 | {{ url_for('static', filename='images/pier_bw.jpg') }} 13 | {% endblock %} 14 | 15 | 16 | 17 | {# ===== Header content ===== #} 18 | 19 | {% block header_content %} 20 |

Oh oh

21 |

404

22 | {% endblock %} 23 | 24 | 25 | 26 | {# ===== Content ===== #} 27 | 28 | {% block page_content %} 29 |

The page you requested could not be found!

30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /templates/base/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Flask-static-site 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% include 'base/navbar.html' %} 24 | 25 | 26 | {% with template = self %} 27 | {% include 'base/header.html' %} 28 | {% endwith %} 29 | 30 | 31 | {% with template = self %} 32 | {% include 'base/section.html' %} 33 | {% endwith %} 34 | 35 | 36 | {% if comments is defined %} 37 | {% include 'base/comments.html' %} 38 | {% endif %} 39 | 40 | 41 | {% include 'base/footer.html' %} 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /templates/base/comments.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 | -------------------------------------------------------------------------------- /templates/base/footer.html: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | {% macro social_link(href, glyph) %} 16 | 22 | {% endmacro %} 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 | 38 | 41 |
42 |
43 | -------------------------------------------------------------------------------- /templates/base/header.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 |
12 | 13 |
14 |
15 |
16 | {{ template.header_content() }} 17 |
18 |
19 |
20 |
21 | 22 | -------------------------------------------------------------------------------- /templates/base/navbar.html: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 45 | -------------------------------------------------------------------------------- /templates/base/section.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |
9 |
10 | {{ template.page_content() }} 11 |
12 |
13 | -------------------------------------------------------------------------------- /templates/blog/articles.html: -------------------------------------------------------------------------------- 1 | {% extends "base/base.html" %} 2 | 3 | 4 | 5 | {# ===== Header style and image ===== #} 6 | 7 | {% block header_class %} 8 | header-small 9 | {% endblock %} 10 | 11 | {% block header_background %} 12 | {{ url_for('static', filename='images/maciphone.jpg') }} 13 | {% endblock %} 14 | 15 | 16 | 17 | {# ===== Header content ===== #} 18 | 19 | {% block header_content %} 20 |

The Blog

21 |

All articles

22 | {% endblock %} 23 | 24 | 25 | 26 | {# ===== Content ===== #} 27 | 28 | {% block page_content %} 29 |

All articles by year

30 | {% for year, articles in pages %} 31 |

{{year}}

32 | 37 | {% else %} 38 |

No content

39 | {% endfor %} 40 |
41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /templates/blog/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base/base.html" %} 2 | 3 | 4 | 5 | {# ===== Header style and image ===== #} 6 | 7 | {% block header_class %} 8 | header-small 9 | {% endblock %} 10 | 11 | {% block header_background %} 12 | {{ url_for('static', filename='images/maciphone.jpg') }} 13 | {% endblock %} 14 | 15 | 16 | 17 | {# ===== Header content ===== #} 18 | 19 | {% block header_content %} 20 |

The Blog

21 | ALL ARTICLES 22 | {% endblock %} 23 | 24 | 25 | 26 | {# ===== Content ===== #} 27 | 28 | {% block page_content %} 29 |
30 | 31 | {% for page in pages %} 32 |

{{ page.title }}

33 |

{{ page.date.strftime('%B %d, %Y') }}

34 |

{{page.summary}}

35 |
36 | {% else %} 37 |

No content

38 | {% endfor %} 39 | 40 |
41 |
42 | ALL ARTICLES 43 |
44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /templates/blog/page.html: -------------------------------------------------------------------------------- 1 | {% extends "base/base.html" %} 2 | 3 | 4 | 5 | {# ===== Header style and image ===== #} 6 | 7 | {% block header_class %} 8 | header-smaller 9 | {% endblock %} 10 | 11 | {% block header_background %} 12 | {{ page.image }} 13 | {% endblock %} 14 | 15 | 16 | 17 | {# ===== Header content ===== #} 18 | 19 | {% block header_content %} 20 |

The Blog

21 |

22 | {% endblock %} 23 | 24 | 25 | 26 | {# ===== Page content ===== #} 27 | 28 | {% block page_content %} 29 |

{{ page.title }}

30 | {% if not page.static %} 31 |

32 | Published {{ page.date.strftime('%B %d, %Y') }} 33 |

34 | {% endif %} 35 | {{ page.html|safe }} 36 | {% endblock %} 37 | 38 | 39 | 40 | {# ===== Use comments ===== #} 41 | 42 | {% if not page.static %} 43 | {% set comments = True %} 44 | {% endif %} 45 | -------------------------------------------------------------------------------- /templates/pages/index.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | {% extends "base/base.html" %} 10 | 11 | 12 | 13 | {# ===== Header style and image ===== #} 14 | 15 | {% block header_class %} 16 | header-large 17 | {% endblock %} 18 | 19 | {% block header_background %} 20 | {{ page.image }} 21 | {% endblock %} 22 | 23 | 24 | 25 | {# ===== Header content ===== #} 26 | 27 | {% block header_content %} 28 |

{{ page.title }}

29 |

{{ page.subtitle }}

30 | 31 | 32 | ABOUT 33 |   34 | CONTACT 35 | {% endblock %} 36 | 37 | 38 | 39 | {# ===== Page content ===== #} 40 | 41 | {% block page_content %} 42 | {{ page.html|safe }} 43 | {% endblock %} 44 | -------------------------------------------------------------------------------- /templates/pages/page.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | {% extends "base/base.html" %} 9 | 10 | 11 | 12 | {# ===== Header style and image ===== #} 13 | 14 | {% block header_class %} 15 | header-small 16 | {% endblock %} 17 | 18 | {% block header_background %} 19 | {{ page.image }} 20 | {% endblock %} 21 | 22 | 23 | 24 | {# ===== Header content ===== #} 25 | 26 | {% block header_content %} 27 |

{{ page.title }}

28 |

{{ page.subtitle }}

29 | {% endblock %} 30 | 31 | 32 | 33 | {# ===== Page content ===== #} 34 | 35 | {% block page_content %} 36 | {{ page.html|safe }} 37 | {% endblock %} 38 | --------------------------------------------------------------------------------