git.fiddlerwoaroof.com
Browse code

Rework ui, save feed xml

- Limit width of article text
- Make feed list / article list collapsible
- Fix issue with formatting of code blocks
- Add a function for serializing the feed to an xml file

fiddlerwoaroof authored on 12/03/2017 06:07:25
Showing 5 changed files
... ...
@@ -3,3 +3,5 @@
3 3
 [#]*#
4 4
 feed-archiver
5 5
 *.fasl
6
+vendor
7
+node_modules
6 8
\ No newline at end of file
... ...
@@ -8,7 +8,7 @@
8 8
   </head>
9 9
   <body>
10 10
     <div id="container" v-cloak>
11
-      <div class="left">
11
+      <div :class="{left: true, collapsed: collapsed}">
12 12
 	<div class="feeds">
13 13
 	  <span class="pull-time">{{pull_time}}</span>
14 14
 	  <!--<div v-for="url in feed_urls">{{url}}</div>-->
... ...
@@ -22,10 +22,10 @@
22 22
 	  </div>
23 23
 	</div>
24 24
 
25
-	<div v-if="current_feed.title !== null && current_feed.items.length > 0" class="current_feed">
26
-	  <h2>{{current_feed.title}}</h2>
27
-	  <h3>{{current_feed.link}}</h3>
28
-	  <div>{{current_feed.description}}</div>
25
+	<div v-if="current_feed.metadata.title !== null && current_feed.items.length > 0" class="current_feed">
26
+	  <h2>{{current_feed.metadata.title}}</h2>
27
+	  <h3>{{current_feed.metadata.link}}</h3>
28
+	  <div>{{current_feed.metadata.description}}</div>
29 29
 	  <ul>
30 30
 	    <li v-for="item in current_feed.items | orderBy 'path' -1">
31 31
 	      <a v-on:click="get_item(item.path)">
... ...
@@ -36,9 +36,11 @@
36 36
 	</div>
37 37
       </div>
38 38
       <div class="right">
39
-	<div v-if="current_item.title !== null">
39
+	<button @click="toggleCollapse" class="collapse-toggle">(Un)Collapse</button>
40
+	<div v-if="current_item.title !== null" class="article-container">
40 41
 	  <h2>
41 42
 	    <a href="{{current_item.link}}">{{current_item.title}}</a>
43
+	    <button @click="like(current_item, current_feed)">&lt3</button>
42 44
 	  </h2>
43 45
 	  <h3>{{current_item.author}}</h3>
44 46
 	  <div>{{{  item_content }}}</div>
... ...
@@ -6,6 +6,7 @@ root = new Vue({
6 6
 	feeds: {
7 7
 	    feeds: []
8 8
 	},
9
+	collapsed: false,
9 10
 
10 11
 	current_feed: {
11 12
 	    metadata: {
... ...
@@ -34,13 +35,20 @@ root = new Vue({
34 35
 	item_content() {
35 36
 	    let result = null;
36 37
 	    if (this.current_item.content !== null) {
37
-		result = DOMPurify.sanitize(this.current_item.content);
38
+		result = DOMPurify.sanitize(this.current_item.content, {
39
+		    FORBID_TAG: ['style'],
40
+		    FORBID_ATTR: ['style'],
41
+		});
38 42
 	    }
39 43
 	    return result;
40 44
 	},
41 45
     },
42 46
 
43 47
     methods: {
48
+	toggleCollapse() {
49
+	    this.collapsed = !this.collapsed;
50
+	},
51
+	
44 52
 	sanitize(html) {
45 53
 	    return DOMPurify.sanitize(html, {
46 54
 		FORBID_TAG: ['style'],
... ...
@@ -71,9 +79,34 @@ root = new Vue({
71 79
 		      Object.assign(root.current_feed, result));
72 80
 	},
73 81
 
82
+	like(item, feed) {
83
+	    fetch('<<< LIKE WEBHOOK >>>', {
84
+		method: 'POST',
85
+		body: JSON.stringify({
86
+		    'event': 'like-item',
87
+		    'item': item.link,
88
+		    'title': item.title,
89
+		    'author': item.author,
90
+		    'feed-title': feed.metadata.title,
91
+		    'feed-link': feed.metadata.link,
92
+		}),
93
+	    });
94
+	},
95
+
74 96
 	get_item(path) {
75 97
 	    window.fetch(this.current_feed.base_path + path).then((resp) => resp.json())
76 98
 		.then((data) => {
99
+		    fetch('<<< READ WEBHOOK >>>', {
100
+			method: 'POST',
101
+			body: JSON.stringify({
102
+			    'event': 'read-item',
103
+			    'item': data.link,
104
+			    'title': data.title,
105
+			    'author': data.author,
106
+			    'feed-title': this.current_feed.metadata.title,
107
+			    'feed-link': this.current_feed.metadata.link,
108
+			}),
109
+		    });
77 110
 		    window.history.pushState({
78 111
 			'current_feed': root.current_feed,
79 112
 			'current_item': data
... ...
@@ -29,10 +29,7 @@ a {
29 29
   vertical-align: middle;
30 30
   text-decoration: none;
31 31
   cursor: pointer;
32
-  /*width: 100%;*/
33 32
   margin-right: 1em;
34
-  /*text-shadow: 0em 0em 0.1em #000*/
35
-  /*,            0em 0em 0.2em #888;*/
36 33
 }
37 34
 
38 35
 a:hover {
... ...
@@ -43,11 +40,11 @@ a:focus {
43 40
   text-decoration: underline;
44 41
 }
45 42
 
46
-.right {
43
+#container > .right {
47 44
   overflow-y: auto;
48 45
 }
49 46
 
50
-.right {
47
+#container > .right {
51 48
   width: 50vw;
52 49
   height: 100vh;
53 50
   box-sizing: border-box;
... ...
@@ -57,14 +54,22 @@ a:focus {
57 54
   right: 0;
58 55
   font-size: 16px;
59 56
   line-height: 1.25;
60
-  /*text-shadow: 0 0 0.1em #333, 0 0 0.2em #cce5dd;*/
61 57
 }
62 58
 
63 59
 h1, h2, h3, h4, h5, h6 {
64 60
   border-bottom: 3px #28b0b1 double;
65 61
 }
66 62
 
67
-.left {
63
+#container > *, #container > .left > * {
64
+    transition: width 2s ease;
65
+}
66
+
67
+.article-container {
68
+    width: 30em;
69
+    margin: auto;
70
+}
71
+
72
+#container > .left {
68 73
   border-right: 4px #28b0b1 double;
69 74
   box-shadow: 0px 0px 1em #28b0b1;
70 75
   width: 50vw;
... ...
@@ -77,8 +82,9 @@ h1, h2, h3, h4, h5, h6 {
77 82
 }
78 83
 
79 84
 
80
-.right img {
81
-  max-width: 50vw;
85
+#container > .right img {
86
+    max-width: 95%;
87
+    height: auto;
82 88
   display: block;
83 89
   margin: auto;
84 90
 }
... ...
@@ -116,3 +122,42 @@ h1, h2, h3, h4, h5, h6 {
116 122
     padding-left: 2.5em;
117 123
     margin-bottom: 0.5em;
118 124
 }
125
+
126
+.collapsed {
127
+    width: 0vw !important;
128
+    overflow: hidden;
129
+}
130
+
131
+.collapsed > * {
132
+    width: 0vw !important;
133
+    overflow: hidden;
134
+}
135
+
136
+.collapsed + * {
137
+    width: 100vw !important;
138
+    padding-left: 25vw;
139
+    padding-right: 25vw;
140
+}
141
+
142
+.collapse-toggle {
143
+    position: fixed;
144
+}
145
+
146
+
147
+.code, code, pre {
148
+    white-space: pre;
149
+    background: black;
150
+    padding: 1em;
151
+    border: medium ridge;
152
+    font-family: monospace;
153
+    font-size: 10px;
154
+}
155
+
156
+.code .code, code code, pre pre,
157
+.code pre, code .code, pre code,
158
+.code code, code pre, pre .code,
159
+{
160
+    background: transparent;
161
+    padding: 0;
162
+    border: none;
163
+}
... ...
@@ -113,13 +113,22 @@
113 113
     (invoke-restart restart)))
114 114
 
115 115
 
116
+(defun save-feed (feed output-file &key (if-exists :supersede))
117
+  (with-output-to-file (s output-file :if-exists if-exists)
118
+    (plump:serialize (alimenta:doc feed) s)))
119
+
116 120
 (defun pull-and-store-feeds (feeds pull-directory)
117 121
   (mapcar (lambda (feed-url)
118 122
 	    (with-simple-restart (skip-feed "Skip ~a" feed-url)
119
-	      (let ((feed (with-retry ("Pull feed again.")
120
-			    (log-pull t feed-url))))
121
-		(store (coerce-feed-link feed-url feed)
122
-		       pull-directory))))
123
+	      (let* ((feed (with-retry ("Pull feed again.")
124
+			     (log-pull t feed-url)))
125
+		     (result (store (coerce-feed-link feed-url feed)
126
+				    pull-directory)))
127
+		(prog1 result
128
+		  (format t "Serializing XML...")
129
+		  (save-feed feed
130
+			     (merge-pathnames "feed.xml"
131
+					      (cadr result)))))))
123 132
 	  feeds))
124 133
 
125 134
 (defun feed-index (index-stream pull-time paths)