├── uploads └── .gitkeep ├── screenshots ├── 0.png ├── 1.png └── 2.png ├── .gitignore ├── data-default ├── data.json └── users │ └── content-default.json ├── data ├── users │ └── content.json └── data.json ├── js ├── init.js ├── modal.js ├── posts.js ├── likes.js ├── post.js ├── zeroframe.js ├── posteditor.js ├── top.js ├── storage.js ├── poster.js ├── comments.js ├── vuex.js └── moment.min.js ├── README.md ├── index.html ├── utils ├── cleaner.py └── poster.py ├── dbschema.json ├── css ├── main.css └── bulma.min.css.map └── content.json /uploads/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /screenshots/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergei-bondarenko/poster/HEAD/screenshots/0.png -------------------------------------------------------------------------------- /screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergei-bondarenko/poster/HEAD/screenshots/1.png -------------------------------------------------------------------------------- /screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergei-bondarenko/poster/HEAD/screenshots/2.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | data/poster.db 3 | uploads/* 4 | !uploads/.gitkeep 5 | data/users/* 6 | !data/users/content.json 7 | -------------------------------------------------------------------------------- /data-default/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Poster", 3 | "description": "A simple blog for zeronet", 4 | "next_post_id": 2, 5 | "modified": 1432515194, 6 | "post": [ 7 | { 8 | "post_id": 1, 9 | "date_published": 1433033779604, 10 | "body": "Your first post." 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /data-default/users/content-default.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": {}, 3 | "ignore": ".*", 4 | "modified": 1509022165, 5 | "signs": { 6 | "1J2ZJjs97wEcW36x5d9f1QnPVPVDv8wbJ": "HLciC9tOLC0VX95fBTCn2hBvOhjTIFG3XjGDLCWOwusGEeH41NMY0VNejlHaMK+5erDS3bXG6kXmn2h4ywKSqfI=" 7 | }, 8 | "user_contents": { 9 | "cert_signers": { 10 | "zeroid.bit": ["1iD5ZQJMNXu43w1qLB8sfdHVKppVMduGz"] 11 | }, 12 | "permission_rules": { 13 | ".*": { 14 | "files_allowed": "data.json", 15 | "max_size": 40000 16 | }, 17 | "bitid/.*@zeroid.bit": {"max_size": 40000}, 18 | "bitmsg/.*@zeroid.bit": {"max_size": 35000} 19 | }, 20 | "permissions": { 21 | "bad@zeroid.bit": false, 22 | "nofish@zeroid.bit": {"max_size": 200000} 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /data/users/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "1J2ZJjs97wEcW36x5d9f1QnPVPVDv8wbJ", 3 | "files": {}, 4 | "ignore": ".*", 5 | "inner_path": "data/users/content.json", 6 | "modified": 1509022165, 7 | "signs": { 8 | "1J2ZJjs97wEcW36x5d9f1QnPVPVDv8wbJ": "HLciC9tOLC0VX95fBTCn2hBvOhjTIFG3XjGDLCWOwusGEeH41NMY0VNejlHaMK+5erDS3bXG6kXmn2h4ywKSqfI=" 9 | }, 10 | "user_contents": { 11 | "cert_signers": { 12 | "zeroid.bit": ["1iD5ZQJMNXu43w1qLB8sfdHVKppVMduGz"] 13 | }, 14 | "permission_rules": { 15 | ".*": { 16 | "files_allowed": "data.json", 17 | "max_size": 40000 18 | }, 19 | "bitid/.*@zeroid.bit": {"max_size": 40000}, 20 | "bitmsg/.*@zeroid.bit": {"max_size": 35000} 21 | }, 22 | "permissions": { 23 | "bad@zeroid.bit": false, 24 | "nofish@zeroid.bit": {"max_size": 200000} 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /js/init.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const poster = new Poster() 4 | const bus = new Vue() 5 | new Vue({ 6 | el: '#root', 7 | template: ` 8 |
9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 | `, 18 | 19 | computed: { 20 | mainPageView() { 21 | return storage.state.url == '' 22 | }, 23 | 24 | own() { 25 | if ("settings" in storage.state.site_info) { 26 | return storage.state.site_info.settings.own 27 | } 28 | } 29 | }, 30 | 31 | mounted() { 32 | storage.commit('loadURL') 33 | storage.commit('loadSiteInfo') 34 | storage.commit('loadLikes') 35 | storage.commit('loadComments') 36 | storage.commit('loadPosts') 37 | } 38 | }) 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Poster 2 | 3 | ## Description 4 | 5 | A simple blog platform for [ZeroNet](https://github.com/HelloZeroNet/ZeroNet) written from scratch using [Vue.js](https://vuejs.org/) and [Bulma](https://bulma.io/). This project available [online](http://127.0.0.1:43110/1J2ZJjs97wEcW36x5d9f1QnPVPVDv8wbJ/) (you need a ZeroNet client running to view the site). 6 | 7 | ## Key features 8 | 9 | - Mobile friendly 10 | - Endless newsfeed (no pages) with dynamic post appending when the page bottom has been reached 11 | - Editable posts and comments 12 | - Post editor supports uploading of images, videos and audio files 13 | - Filtering by last comments and most liked posts by day, week, month, year or all the time 14 | - Direct links to posts are available (click on the post date to get it) 15 | - Vertical wrapping of long posts 16 | - Easily clonable 17 | - Site title and description configurable via `content.json` file (or via the sidebar) 18 | 19 | ## Utils 20 | 21 | There are currently two utils in the `utils/` folder: 22 | 23 | - `cleaner.py` - cleaner for the files in `uploads/` directory: Deletes files which are not included in the `data/data.json`. Also finds duplicates. 24 | - `poster.py` - for creating multiple blog posts using files from a specified directory. One file per post. Reqires a [python-magic](https://github.com/ahupp/python-magic) installed. 25 | 26 | ## Screenshots 27 | 28 | ![](screenshots/0.png?raw=true) 29 | ![](screenshots/1.png?raw=true) 30 | ![](screenshots/2.png?raw=true) 31 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | New ZeroNet site! 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 | -------------------------------------------------------------------------------- /utils/cleaner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import json 3 | from os import listdir, remove, path 4 | 5 | ''' 6 | Cleaner for the files in uploads/ directory: Deletes files which 7 | are not included in the data/data.json. Also finds duplicates. 8 | ''' 9 | 10 | def main(): 11 | abs_path = path.dirname(path.realpath(__file__)) 12 | 13 | print("Finding duplicates...") 14 | with open(path.join(abs_path, '../content.json'), 'r') as f: 15 | content = f.read() 16 | files = json.loads(content)['files_optional'] 17 | seen = {} 18 | for filename, value in files.items(): 19 | if value['sha512'] not in seen: 20 | seen[ value['sha512'] ] = 1 21 | else: 22 | if seen[ value['sha512'] ] == 1: 23 | print(filename) 24 | 25 | with open(path.join(abs_path, '../data/data.json'), 'r') as f: 26 | data = f.read() 27 | print("\nFindind files which are not present in data/data.json...") 28 | for file in listdir(path.join(abs_path, '../uploads/')): 29 | if file.endswith('.piecemap.msgpack'): 30 | continue 31 | if file not in data: 32 | ans = input("{} is not in data.json. Delete? [Y/n] ".format(file)) 33 | ans = ans.lower() 34 | if ans == 'yes' or ans == 'y' or ans == '': 35 | remove(path.join(abs_path, '../uploads/') + file) 36 | print('Deleted.') 37 | else: 38 | print('Skipped.') 39 | 40 | print("Don't forget to sign and publish.") 41 | 42 | if __name__ == "__main__": 43 | main() 44 | -------------------------------------------------------------------------------- /js/modal.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | Vue.component('modal', { 4 | template: ` 5 | 28 | `, 29 | 30 | computed: { 31 | show() { 32 | return storage.getters.getModal().show 33 | }, 34 | 35 | message() { 36 | return storage.getters.getModal().message 37 | }, 38 | 39 | buttonText() { 40 | return storage.getters.getModal().buttonText 41 | }, 42 | 43 | buttonClass() { 44 | return storage.getters.getModal().buttonClass 45 | } 46 | }, 47 | 48 | methods: { 49 | close() { 50 | storage.commit('destroyModal') 51 | }, 52 | 53 | modalAffirmed() { 54 | storage.commit('modalAffirmed') 55 | } 56 | } 57 | }) 58 | -------------------------------------------------------------------------------- /js/posts.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | Vue.component('posts', { 4 | template: ` 5 |
6 |
7 |
8 |
9 | No posts. 10 |
11 |
12 |
13 |
16 | 17 |
18 |
19 |
20 | `, 21 | 22 | props: ['ownAndMainPage'], 23 | 24 | data: () => { 25 | return { 26 | post_count: 15 27 | } 28 | }, 29 | 30 | computed: { 31 | posts() { 32 | return storage.state.posts.slice(0, this.post_count) 33 | } 34 | }, 35 | 36 | mounted() { 37 | window.addEventListener('scroll', this.scroll) 38 | storage.watch(storage.getters.getModal, () => { 39 | if (storage.getters.getModal().action == 'delPost' 40 | && storage.getters.getModal().affirmed == true) { 41 | poster.delPost(storage.getters.getModal().id) 42 | storage.commit('destroyModal') 43 | } 44 | }, { deep: true }) 45 | }, 46 | 47 | methods: { 48 | scroll() { 49 | if (this.post_count > 10 50 | && this.post_count < storage.state.posts.length) { 51 | let current_position = window.scrollY 52 | let trigger_post = 'post' + (this.post_count - 10) 53 | let trigger_position = this.$refs[trigger_post][0].offsetTop 54 | if (current_position > trigger_position) { 55 | this.post_count += 15 56 | } 57 | } 58 | } 59 | } 60 | }) 61 | -------------------------------------------------------------------------------- /data/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Poster", 3 | "description": "A simple blog for zeronet", 4 | "next_post_id": 2, 5 | "modified": 1432515194, 6 | "post": [ 7 | { 8 | "post_id": 1, 9 | "date_published": 1510496706392, 10 | "body": "

Poster

Description
A simple blog platform for ZeroNet written from scratch using Vue.js and Bulma. The source code available on GitHub and Git Center.

Key featuresUtils
There are currently two utils in the utils/ folder: