init
This commit is contained in:
		
							
								
								
									
										8
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					[*]
 | 
				
			||||||
 | 
					charset = utf-8
 | 
				
			||||||
 | 
					end_of_line = lf
 | 
				
			||||||
 | 
					insert_final_newline = true
 | 
				
			||||||
 | 
					trim_trailing_whitespace = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[*.v]
 | 
				
			||||||
 | 
					indent_style = tab
 | 
				
			||||||
							
								
								
									
										7
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					* text=auto eol=lf
 | 
				
			||||||
 | 
					*.bat eol=crlf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**/*.v linguist-language=V
 | 
				
			||||||
 | 
					**/*.vv linguist-language=V
 | 
				
			||||||
 | 
					**/*.vsh linguist-language=V
 | 
				
			||||||
 | 
					**/v.mod linguist-language=V
 | 
				
			||||||
							
								
								
									
										20
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					# Binaries for programs and plugins
 | 
				
			||||||
 | 
					habraview
 | 
				
			||||||
 | 
					v
 | 
				
			||||||
 | 
					*.exe
 | 
				
			||||||
 | 
					*.exe~
 | 
				
			||||||
 | 
					*.so
 | 
				
			||||||
 | 
					*.dylib
 | 
				
			||||||
 | 
					*.dll
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Ignore binary output folders
 | 
				
			||||||
 | 
					bin/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Ignore common editor/system specific metadata
 | 
				
			||||||
 | 
					.DS_Store
 | 
				
			||||||
 | 
					.idea/
 | 
				
			||||||
 | 
					.vscode/
 | 
				
			||||||
 | 
					*.iml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ENV
 | 
				
			||||||
 | 
					.env
 | 
				
			||||||
							
								
								
									
										7
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					all: build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					build:
 | 
				
			||||||
 | 
						v -o habraview src
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					prod:
 | 
				
			||||||
 | 
						v -o habraview -prod -cc clang -compress src
 | 
				
			||||||
							
								
								
									
										51
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					# habraview
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Спартанский микрофронтенд habr.com для возможности архивации страниц
 | 
				
			||||||
 | 
					инструментами вроде ArchiveBox и т.п.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Современные фронтенды к Хабру не позволяют сохранить содержимое страницы
 | 
				
			||||||
 | 
					без искажений и в полном объёме. Всё обмазано JavaScript с ленивой загрузкой
 | 
				
			||||||
 | 
					изображений, отчего картинки на сохранённой странице только заблюренные, а
 | 
				
			||||||
 | 
					возможность архивации комментариев полностью отсутствует. Страницы
 | 
				
			||||||
 | 
					альтернативного фронтенда geekr.vercel.app вовсе непригодны для архивации.
 | 
				
			||||||
 | 
					Поэтому появился этот костыль.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Фичи:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Умеет отображать страницу в минимальном сносном CSS.
 | 
				
			||||||
 | 
					* Отображает все комментарии (отрисовка дерева не удалась, но и так сойдёт).
 | 
				
			||||||
 | 
					* Решает проблему с заблюренными изображениями.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Работает только с `article`, то есть новостные посты и статьи из `sandbox`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Как пользоваться
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`habraview` это веб-приложение. Просто запускаем файл:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					./habraview
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Приложение будет по умолчанию будет слушать на 8888 порту. Чтобы получить
 | 
				
			||||||
 | 
					страницу, открываем в брайзере:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					http://localhost:8888?url=https://habr.com/ru/articles/853062/
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Адрес статьи на Хабре можно передать целиком как значение quey-параметра `url`
 | 
				
			||||||
 | 
					или как `id`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					http://localhost:8888?id=853062
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Теперь на эту страницу можно натравить архиватор веб-страниц.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Компиляция
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Нужны компиляторы `gcc` и [v](https://vlang.io):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					v -prod -cflags -static -cflags -s .
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								assets/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 7.2 KiB  | 
							
								
								
									
										14
									
								
								assets/habr-fixer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								assets/habr-fixer.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					(function(document)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    const onDOMBuild = () =>
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // Fix blurred images
 | 
				
			||||||
 | 
					        const images = document.querySelectorAll('img[data-blurred="true"]');
 | 
				
			||||||
 | 
					        for (let image of images)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            image.src = image.dataset.src;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    document.addEventListener('DOMContentLoaded', onDOMBuild);
 | 
				
			||||||
 | 
					})(document);
 | 
				
			||||||
							
								
								
									
										9
									
								
								assets/highlight.min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								assets/highlight.min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					/*!
 | 
				
			||||||
 | 
					  Theme: Default
 | 
				
			||||||
 | 
					  Description: Original highlight.js style
 | 
				
			||||||
 | 
					  Author: (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
 | 
				
			||||||
 | 
					  Maintainer: @highlightjs/core-team
 | 
				
			||||||
 | 
					  Website: https://highlightjs.org/
 | 
				
			||||||
 | 
					  License: see project LICENSE
 | 
				
			||||||
 | 
					  Touched: 2021
 | 
				
			||||||
 | 
					*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
 | 
				
			||||||
							
								
								
									
										1213
									
								
								assets/highlight.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1213
									
								
								assets/highlight.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										89
									
								
								assets/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								assets/style.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
				
			|||||||
 | 
					body {
 | 
				
			||||||
 | 
					  background-color: #F1F5F9;
 | 
				
			||||||
 | 
					  font-family: sans-serif;
 | 
				
			||||||
 | 
					  font-size: 14px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					header, main, footer {
 | 
				
			||||||
 | 
					  margin: 0 auto;
 | 
				
			||||||
 | 
					  max-width: 1080px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					article {
 | 
				
			||||||
 | 
					  background-color: white;
 | 
				
			||||||
 | 
					  padding: 0.5rem 1.5rem;
 | 
				
			||||||
 | 
					  border: 1px solid #E6E7E9;
 | 
				
			||||||
 | 
					  border-radius: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					pre code.hljs {
 | 
				
			||||||
 | 
					  border-radius: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					article img {
 | 
				
			||||||
 | 
					  height: 100% !important;
 | 
				
			||||||
 | 
					  width: 100% !important;
 | 
				
			||||||
 | 
					  object-fit: scale-down;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					blockquote {
 | 
				
			||||||
 | 
					  border-left: 4px solid #bbbbbb;
 | 
				
			||||||
 | 
					  padding-left: 8px;
 | 
				
			||||||
 | 
					  margin-left: 4px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					figure {
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					figcaption {
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					  opacity: 60%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#date-published {
 | 
				
			||||||
 | 
					  opacity: 60%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#article-title {
 | 
				
			||||||
 | 
					  font-size: 28px;
 | 
				
			||||||
 | 
					  font-weight: 500;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#tags {
 | 
				
			||||||
 | 
					  margin: 1rem 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#hubs {
 | 
				
			||||||
 | 
					  margin: 1rem 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					span.comma:not(:empty) ~ span.comma:not(:empty):before {
 | 
				
			||||||
 | 
					  content: ", ";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#comments {
 | 
				
			||||||
 | 
					  margin-top: 1rem;
 | 
				
			||||||
 | 
					  background-color: white;
 | 
				
			||||||
 | 
					  padding: 0.5rem 1.5rem;
 | 
				
			||||||
 | 
					  border: 1px solid #E6E7E9;
 | 
				
			||||||
 | 
					  border-radius: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.comment {
 | 
				
			||||||
 | 
					  padding: 0.5rem 0;
 | 
				
			||||||
 | 
					  margin: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.comment-header {
 | 
				
			||||||
 | 
					  background-color: #f5f5f5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.replies-list {
 | 
				
			||||||
 | 
					  margin: 1rem 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					ul.inline-list {
 | 
				
			||||||
 | 
					  margin: 0;
 | 
				
			||||||
 | 
					  padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					ul.inline-list li {
 | 
				
			||||||
 | 
					  display: inline;
 | 
				
			||||||
 | 
					  list-style: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					table, th, td {
 | 
				
			||||||
 | 
					  border: 1px solid #E6E7E9;
 | 
				
			||||||
 | 
					  border-collapse: collapse;
 | 
				
			||||||
 | 
					  padding: 2px 16px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					:target {
 | 
				
			||||||
 | 
					  border-left: 4px solid black;
 | 
				
			||||||
 | 
					  padding: 0 1rem !important;
 | 
				
			||||||
 | 
					  background-color: #f8f8f8;
 | 
				
			||||||
 | 
					  .comment-header {
 | 
				
			||||||
 | 
					    background-color: #cccccc;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								habr/api.v
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								habr/api.v
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					module habr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.http
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Habr {
 | 
				
			||||||
 | 
						baseurl string = $d('habr_baseurl', 'https://habr.com')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn Habr.new() Habr {
 | 
				
			||||||
 | 
						return Habr{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn (h Habr) get_article(id int) !string {
 | 
				
			||||||
 | 
						response := http.get('${h.baseurl}/kek/v2/articles/${id}/') or { return err }
 | 
				
			||||||
 | 
						return response.body
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn (h Habr) get_article_comments(id int) !string {
 | 
				
			||||||
 | 
						response := http.get('${h.baseurl}/kek/v2/articles/${id}/comments/') or { return err }
 | 
				
			||||||
 | 
						return response.body
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										65
									
								
								habr/article.v
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								habr/article.v
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
				
			|||||||
 | 
					module habr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import veb { RawHtml }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Article {
 | 
				
			||||||
 | 
					pub:
 | 
				
			||||||
 | 
						published_at string  @[json: timePublished]
 | 
				
			||||||
 | 
						title        string  @[json: titleHtml]
 | 
				
			||||||
 | 
						text         RawHtml @[json: textHtml]
 | 
				
			||||||
 | 
						hubs         []Hub
 | 
				
			||||||
 | 
						tags         []Tag
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn Article.parse(input string) Article {
 | 
				
			||||||
 | 
						return json.decode(Article, input) or { Article{} }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Hub {
 | 
				
			||||||
 | 
					pub:
 | 
				
			||||||
 | 
						id    string
 | 
				
			||||||
 | 
						alias string
 | 
				
			||||||
 | 
						title string @[json: titleHtml]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Tag {
 | 
				
			||||||
 | 
					pub:
 | 
				
			||||||
 | 
						title string @[json: titleHtml]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Comment {
 | 
				
			||||||
 | 
					pub:
 | 
				
			||||||
 | 
						id          string
 | 
				
			||||||
 | 
						parent_id   string   @[json: parentId]
 | 
				
			||||||
 | 
						replies_ids []string @[json: children]
 | 
				
			||||||
 | 
						level       int
 | 
				
			||||||
 | 
						author      CommentAuthor
 | 
				
			||||||
 | 
						message     RawHtml
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct CommentAuthor {
 | 
				
			||||||
 | 
					pub:
 | 
				
			||||||
 | 
						alias string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct CommentsMapped {
 | 
				
			||||||
 | 
					mut:
 | 
				
			||||||
 | 
						items map[string]Comment @[json: comments]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Comments {
 | 
				
			||||||
 | 
					pub mut:
 | 
				
			||||||
 | 
						items []Comment
 | 
				
			||||||
 | 
					mut:
 | 
				
			||||||
 | 
						idx int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn Comments.parse(input string) Comments {
 | 
				
			||||||
 | 
						mut comments := Comments{}
 | 
				
			||||||
 | 
						mapped := json.decode(CommentsMapped, input) or { CommentsMapped{} }
 | 
				
			||||||
 | 
						for _, v in mapped.items {
 | 
				
			||||||
 | 
							comments.items << v
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return comments
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										12
									
								
								habr/util.v
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								habr/util.v
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					module habr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import regex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn get_id_from_url(url string) ?string {
 | 
				
			||||||
 | 
						mut re := regex.regex_opt(r'/\d+/?$') or { return none }
 | 
				
			||||||
 | 
						begin, end := re.find(url)
 | 
				
			||||||
 | 
						if begin > 0 && end > 0 {
 | 
				
			||||||
 | 
							return url[begin..end].trim('/')
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return none
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										67
									
								
								habraview.v
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								habraview.v
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					module main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cli
 | 
				
			||||||
 | 
					import habr
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import veb
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Context {
 | 
				
			||||||
 | 
						veb.Context
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct App {
 | 
				
			||||||
 | 
						veb.StaticHandler
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Response {
 | 
				
			||||||
 | 
						msg string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@[get]
 | 
				
			||||||
 | 
					fn (a &App) index(mut ctx Context) veb.Result {
 | 
				
			||||||
 | 
						article_id := ctx.query['id'] or { habr.get_id_from_url(ctx.query['url']) or { '' } }
 | 
				
			||||||
 | 
						client := habr.Habr.new()
 | 
				
			||||||
 | 
						raw_article := client.get_article(article_id.int()) or {
 | 
				
			||||||
 | 
							return ctx.json(Response{ msg: err.str() })
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						raw_comments := client.get_article_comments(article_id.int()) or {
 | 
				
			||||||
 | 
							return ctx.json(Response{ msg: err.str() })
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						article := habr.Article.parse(raw_article)
 | 
				
			||||||
 | 
						comments := habr.Comments.parse(raw_comments)
 | 
				
			||||||
 | 
						return $veb.html()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn runserver(port int) ! {
 | 
				
			||||||
 | 
						os.chdir(os.dir(@FILE))!
 | 
				
			||||||
 | 
						mut app := &App{}
 | 
				
			||||||
 | 
						app.handle_static('assets', false)!
 | 
				
			||||||
 | 
						app.serve_static('/favicon.ico', 'assets/favicon.ico')!
 | 
				
			||||||
 | 
						veb.run[App, Context](mut app, port)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
						mut app := cli.Command{
 | 
				
			||||||
 | 
							name:        'habraview'
 | 
				
			||||||
 | 
							description: 'Habr.com posts viewer.'
 | 
				
			||||||
 | 
							version:     $d('habraview_version', '0.0.0')
 | 
				
			||||||
 | 
							defaults:    struct {
 | 
				
			||||||
 | 
								man: false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							execute:     fn (cmd cli.Command) ! {
 | 
				
			||||||
 | 
								port := cmd.flags.get_int('port') or { 8080 }
 | 
				
			||||||
 | 
								runserver(port)!
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							flags:       [
 | 
				
			||||||
 | 
								cli.Flag{
 | 
				
			||||||
 | 
									flag:          .int
 | 
				
			||||||
 | 
									name:          'port'
 | 
				
			||||||
 | 
									abbrev:        'p'
 | 
				
			||||||
 | 
									description:   'Listen port [default: 8888].'
 | 
				
			||||||
 | 
									default_value: ['8888']
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						app.setup()
 | 
				
			||||||
 | 
						app.parse(os.args)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										16
									
								
								templates/comment.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								templates/comment.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					<div class="comment" id="@{comment.id}">
 | 
				
			||||||
 | 
					  <p class="comment-header">[<a href="#@{comment.id}">@{comment.id}</a>] <strong>@{comment.author.alias}</strong>
 | 
				
			||||||
 | 
					    @if comment.parent_id != ''
 | 
				
			||||||
 | 
					    <span> in reply to <a href="#@{comment.parent_id}">#@{comment.parent_id}</a></span>
 | 
				
			||||||
 | 
					    @end
 | 
				
			||||||
 | 
					  </p>
 | 
				
			||||||
 | 
					  <p>@{comment.message}</p>
 | 
				
			||||||
 | 
					  @if comment.replies_ids.len > 0  
 | 
				
			||||||
 | 
					  <div class="replies-list">
 | 
				
			||||||
 | 
					    <span>Replies: </span>
 | 
				
			||||||
 | 
					    @for reply_id in comment.replies_ids
 | 
				
			||||||
 | 
					    <span><a href="#@{reply_id}">#@{reply_id}</a></span>
 | 
				
			||||||
 | 
					    @end
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					  @end
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
							
								
								
									
										56
									
								
								templates/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								templates/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					  <meta charset="UTF-8">
 | 
				
			||||||
 | 
					  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
 | 
					  <title>@{article.title}</title>
 | 
				
			||||||
 | 
					  @css '/assets/style.css'
 | 
				
			||||||
 | 
					  @css '/assets/highlight.min.css'
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					  <nav></nav>
 | 
				
			||||||
 | 
					  <header>
 | 
				
			||||||
 | 
					    <h1 id="article-title">@{article.title}</h1>
 | 
				
			||||||
 | 
					    <p id="date-published">@{article.published_at}</p>
 | 
				
			||||||
 | 
					  </header>
 | 
				
			||||||
 | 
					  <main>
 | 
				
			||||||
 | 
					    <article>
 | 
				
			||||||
 | 
					      @{article.text}
 | 
				
			||||||
 | 
					    </article>
 | 
				
			||||||
 | 
					    <div id="tags">
 | 
				
			||||||
 | 
					    @if article.tags.len > 0
 | 
				
			||||||
 | 
					      <span><strong>Тэги: </strong></span>
 | 
				
			||||||
 | 
					      @for tag in article.tags
 | 
				
			||||||
 | 
					      <span class="comma">
 | 
				
			||||||
 | 
					        <a href="https://habr.com/ru/search/?target_type=posts&order=relevance&q=%5B@{tag.title}%5D">
 | 
				
			||||||
 | 
					          @{tag.title}
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					      </span>
 | 
				
			||||||
 | 
					      @end
 | 
				
			||||||
 | 
					    @end
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div id="hubs">
 | 
				
			||||||
 | 
					    @if article.hubs.len > 0
 | 
				
			||||||
 | 
					      <span><strong>Хабы: </strong></span>
 | 
				
			||||||
 | 
					      @for hub in article.hubs
 | 
				
			||||||
 | 
					      <span class="comma">
 | 
				
			||||||
 | 
					        <a href="https://habr.com/ru/hubs/@{hub.alias}/articles/">
 | 
				
			||||||
 | 
					          @{hub.title}
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					      </span>
 | 
				
			||||||
 | 
					      @end
 | 
				
			||||||
 | 
					    @end
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div id="comments">
 | 
				
			||||||
 | 
					      <p><strong>Комментарии</strong> (@{comments.items.len})</p>
 | 
				
			||||||
 | 
					      @for comment in comments.items
 | 
				
			||||||
 | 
					        @include 'comment.html'
 | 
				
			||||||
 | 
					      @end
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					  </main>
 | 
				
			||||||
 | 
					  <footer>
 | 
				
			||||||
 | 
					  </footer>
 | 
				
			||||||
 | 
					  @js '/assets/habr-fixer.js'
 | 
				
			||||||
 | 
					  @js '/assets/highlight.min.js'
 | 
				
			||||||
 | 
					  <script>hljs.highlightAll();</script>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
		Reference in New Issue
	
	Block a user