Commit 71017c48c22279ca599d00429a772a88887eb7e1
add navbar, profile page, raw messages, infinite scroller
Jolyon committed on 11/15/2017, 4:43:18 PMParent: 68d042cfbe29198d1147fd3e4d3d90fe1d485648
Files changed
modules/sbot.js | changed |
package-lock.json | changed |
package.json | changed |
src/App.vue | changed |
src/components/Infinitely.vue | changed |
src/components/Message.vue | changed |
src/components/Public.vue | changed |
src/components/Navbar.vue | added |
src/components/Profile.vue | added |
src/main.js | changed |
src/router/index.js | changed |
modules/sbot.js | ||
---|---|---|
@@ -52,9 +52,11 @@ | ||
52 | 52 … | sbot_whoami: true, |
53 | 53 … | sbot_stream: true, |
54 | 54 … | sbot_friends_get: true, |
55 | 55 … | sbot_signs_get: true, |
56 | - sbot_relatedMessages_get: true | |
56 … | + sbot_relatedMessages_get: true, | |
57 … | + sbot_the_log: true, | |
58 … | + sbot_getLatest: true | |
57 | 59 … | }, |
58 | 60 … | |
59 | 61 … | create: function (api) { |
60 | 62 … | |
@@ -117,8 +119,14 @@ | ||
117 | 119 … | CACHE[e.key] = CACHE[e.key] || e.value |
118 | 120 … | }) |
119 | 121 … | ) |
120 | 122 … | }), |
123 … | + sbot_the_log: rec.source(function (opts) { | |
124 … | + return sbot.createLogStream(opts) | |
125 … | + }), | |
126 … | + sbot_getLatest: rec.source(function (opts) { | |
127 … | + return sbot.getLatest(opts) | |
128 … | + }), | |
121 | 129 … | sbot_user_feed: rec.source(function (opts) { |
122 | 130 … | return sbot.createUserStream(opts) |
123 | 131 … | }), |
124 | 132 … | sbot_fulltext_search: rec.source(function (opts) { |
package-lock.json | ||
---|---|---|
The diff is too large to show. Use a local git client to view these changes. Old file size: 375977 bytes New file size: 376323 bytes |
package.json | ||
---|---|---|
@@ -41,8 +41,9 @@ | ||
41 | 41 … | "is-visible": "^2.1.1", |
42 | 42 … | "kvgraph": "^0.1.0", |
43 | 43 … | "map-filter-reduce": "^3.0.3", |
44 | 44 … | "minimist": "^1.2.0", |
45 … | + "moment": "^2.19.2", | |
45 | 46 … | "multiblob": "^1.12.0", |
46 | 47 … | "muxrpcli": "^1.1.0", |
47 | 48 … | "nevernull": "^1.3.0", |
48 | 49 … | "open-external": "^0.1.1", |
src/App.vue | ||
---|---|---|
@@ -1,14 +1,17 @@ | ||
1 | 1 … | <template> |
2 | 2 … | <div id="app" class="container"> |
3 … | + <navbar></navbar> | |
3 | 4 … | <router-view></router-view> |
4 | 5 … | </div> |
5 | 6 … | </template> |
6 | 7 … | |
7 | 8 … | <script> |
9 … | +import Navbar from './components/Navbar.vue' | |
8 | 10 … | |
9 | 11 … | export default { |
10 | 12 … | name: 'app', |
11 | 13 … | components: { |
14 … | + Navbar | |
12 | 15 … | } |
13 | 16 … | } |
14 | 17 … | </script> |
src/components/Infinitely.vue | ||
---|---|---|
@@ -13,9 +13,9 @@ | ||
13 | 13 … | |
14 | 14 … | export default { |
15 | 15 … | data() { |
16 | 16 … | return { |
17 | - list: [], | |
17 … | + list: [] | |
18 | 18 … | } |
19 | 19 … | }, |
20 | 20 … | methods: { |
21 | 21 … | infiniteHandler($state) { |
@@ -26,12 +26,12 @@ | ||
26 | 26 … | } |
27 | 27 … | this.list = this.list.concat(temp); |
28 | 28 … | $state.loaded() |
29 | 29 … | }, 1000) |
30 | - }, | |
30 … | + } | |
31 | 31 … | }, |
32 | 32 … | components: { |
33 | - InfiniteLoading, | |
34 | - }, | |
33 … | + InfiniteLoading | |
34 … | + } | |
35 | 35 … | } |
36 | 36 … | </script> |
37 | 37 … |
src/components/Message.vue | ||
---|---|---|
@@ -7,15 +7,18 @@ | ||
7 | 7 … | {{ author }} |
8 | 8 … | <span class="text-muted"> |
9 | 9 … | {{ message.value.content.type() }} |
10 | 10 … | <strong v-if="message.value.content.channel()">#{{ message.value.content.channel() }}</strong> |
11 … | + {{ message.value.timestamp() | formatDate}} | |
12 … | + <button type="button" class="btn btn-outline-info btn-sm" @click="raw = !raw">Raw</button> | |
11 | 13 … | </span> |
12 | 14 … | </h5> |
13 | 15 … | |
14 | - <p v-html="content_text()"></p> | |
15 | - | |
16 … | + <p v-html="content_text_md()"></p> | |
17 … | + | |
18 … | + <pre v-if="raw" v-html="content_json()"></pre> | |
16 | 19 … | </div> |
17 | - | |
20 … | + | |
18 | 21 … | {{ relatedMessages.length }} |
19 | 22 … | <!-- <message v-for="mess in relatedMessages" :message="mess"> |
20 | 23 … | </message> --> |
21 | 24 … | |
@@ -36,9 +39,10 @@ | ||
36 | 39 … | data () { |
37 | 40 … | return { |
38 | 41 … | author: "...", |
39 | 42 … | image_url: "http://via.placeholder.com/90x90", |
40 | - relatedMessages: [] | |
43 … | + relatedMessages: [], | |
44 … | + raw: false | |
41 | 45 … | } |
42 | 46 … | }, |
43 | 47 … | |
44 | 48 … | methods: { |
@@ -55,11 +59,21 @@ | ||
55 | 59 … | if(a) |
56 | 60 … | this.relatedMessages = a.every(function(e){ return nn(e) }) |
57 | 61 … | }, |
58 | 62 … | |
59 | - // Get markdown formatted version of message content | |
63 … | + // Get raw pretty printed json version of message | |
64 … | + content_json() | |
65 … | + { | |
66 … | + return JSON.stringify(this.message.value(), null, 2) | |
67 … | + }, | |
68 … | + // Get text formatted version of message content | |
60 | 69 … | content_text() |
61 | 70 … | { |
71 … | + return this.message.value.content.text() | |
72 … | + }, | |
73 … | + // Get markdown formatted version of message content | |
74 … | + content_text_md() | |
75 … | + { | |
62 | 76 … | return md.block( this.message.value.content.text() ) |
63 | 77 … | } |
64 | 78 … | }, |
65 | 79 … | updated() { |
src/components/Public.vue | ||
---|---|---|
@@ -1,30 +1,69 @@ | ||
1 | - | |
2 | 1 … | <template> |
3 | 2 … | <div id="public" class="row"> |
4 | 3 … | <div class="col-md-12"> |
5 | - <h2>Public feed</h2> | |
4 … | + <!-- <h2>Public feed</h2> --> | |
6 | 5 … | |
7 | - <Message v-for="message in messages" :message="message"> | |
8 | - </Message> | |
6 … | + <Message v-for="message in messages" :message="message"></Message> | |
7 … | + <!-- <infinite-loading @infinite="infiniteHandler"></infinite-loading> --> | |
9 | 8 … | </div> |
10 | 9 … | </div> |
11 | 10 … | </template> |
12 | 11 … | |
13 | 12 … | <script> |
14 | 13 … | import Message from "./Message.vue" |
14 … | +import InfiniteLoading from 'vue-infinite-loading' | |
15 | 15 … | |
16 | 16 … | export default { |
17 | 17 … | name: 'public', |
18 | 18 … | components: { |
19 | - "Message": Message | |
19 … | + Message, | |
20 … | + InfiniteLoading | |
20 | 21 … | }, |
22 … | + data() { | |
23 … | + return { | |
24 … | + list: [], | |
25 … | + lastMsg: {} | |
26 … | + } | |
27 … | + }, | |
28 … | + methods: { | |
29 … | + infiniteHandler($state) { | |
30 … | + setTimeout(() => { | |
31 … | + console.log('messages', this.messages) | |
32 … | + // this.lastMsg = this.messages.slice(-1)[0]() // a timestamp 1510740987518 | |
33 … | + console.log('last message', this.lastMsg) | |
34 … | + const temp = [] | |
35 … | + for (let i = this.list.length + 1; i <= this.list.length + 20; i++) { | |
36 … | + temp.push(i) | |
37 … | + } | |
38 … | + this.list = this.list.concat(temp); | |
39 … | + $state.loaded() | |
40 … | + }, 1000) | |
41 … | + } | |
42 … | + }, | |
21 | 43 … | subscriptions: function () { |
22 | 44 … | return { |
23 | 45 … | messages: this.$observers.accumulated_observable( |
24 | - this.$depject_api.sbot_log[0]( | |
25 | - { limit: 20, reverse: true, live: true } | |
46 … | + // this.$depject_api.sbot_user_feed[0]( | |
47 … | + // { id: '@dfCIY3rP5idQFdjuOHrBJqrv6EgsSiNyn1NKz87UTJw=.ed25519', lte: 1502583510, limit: 20, reverse: false, live: true } // fillCache: true = leveldb LRU-cache filled with read data | |
48 … | + // ) | |
49 … | + | |
50 … | + // dangerousbeans: sbot_the_log appears to output the same feed as sbot_log | |
51 … | + // but without being wrapped in a pull-stream | |
52 … | + // see sbot.js line 114 to 124 | |
53 … | + // ought to be able to filter with lt lte gt gte on timestamp but it doesn't seem to work | |
54 … | + // eg gte: 1510707316, lte: 1510756323, | |
55 … | + this.$depject_api.sbot_the_log[0]( | |
56 … | + { limit: 60, reverse: true, live: false } | |
26 | 57 … | ) |
58 … | + | |
59 … | + // this.$depject_api.sbot_getLatest[0]( | |
60 … | + // function (e) { | |
61 … | + // console.log('sbot_getLatest', e) | |
62 … | + // return e | |
63 … | + // } | |
64 … | + // ) | |
65 … | + // sbot_getLatest | |
27 | 66 … | ) |
28 | 67 … | } |
29 | 68 … | } |
30 | 69 … | } |
src/components/Navbar.vue | ||
---|---|---|
@@ -1,0 +1,24 @@ | ||
1 … | +<template> | |
2 … | + <!-- <div class="col-md-12"> --> | |
3 … | + <!-- <span> --> | |
4 … | + <!-- <div id="navbar" class="column"> --> | |
5 … | + <nav class="navbar navbar-light bg-light justify-content-between"> | |
6 … | + <span> | |
7 … | + <a class="navbar-brand" href="#/public">Public</a> | |
8 … | + <a class="navbar-brand" href="#/profile">Profile</a> | |
9 … | + <a class="navbar-brand" href="#/infinitely">Infinite scroller</a> | |
10 … | + </span> | |
11 … | + <form class="form-inline"> | |
12 … | + <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search"> | |
13 … | + <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button> | |
14 … | + </form> | |
15 … | + </nav> | |
16 … | + <!-- </span> --> | |
17 … | + <!-- </div> --> | |
18 … | +</template> | |
19 … | + | |
20 … | +<script> | |
21 … | + export default { | |
22 … | + name: 'navbar' | |
23 … | + } | |
24 … | +</script> |
src/components/Profile.vue | ||
---|---|---|
@@ -1,0 +1,49 @@ | ||
1 … | +<template> | |
2 … | + <div id="profile" class="row"> | |
3 … | + <div class="col-md-12"> | |
4 … | + <!-- <h2>Profile feed</h2> --> | |
5 … | + <Message v-for="message in messages" :message="message"></Message> | |
6 … | + </div> | |
7 … | + </div> | |
8 … | +</template> | |
9 … | + | |
10 … | +<script> | |
11 … | +import Message from "./Message.vue" | |
12 … | + | |
13 … | +export default { | |
14 … | + name: 'profile', | |
15 … | + components: { | |
16 … | + Message | |
17 … | + }, | |
18 … | + data() { | |
19 … | + return { | |
20 … | + whoami: '' | |
21 … | + } | |
22 … | + }, | |
23 … | + subscriptions: function () { | |
24 … | + return { | |
25 … | + messages: this.$observers.accumulated_observable( | |
26 … | + this.$depject_api.sbot_user_feed[0]( // todo: whoami | |
27 … | + { id: '@dfCIY3rP5idQFdjuOHrBJqrv6EgsSiNyn1NKz87UTJw=.ed25519', limit: 200, gte: 170, lte: 199, reverse: false, live: false } // fillCache: true = leveldb LRU-cache filled with read data // lte: seq | |
28 … | + ) | |
29 … | + ) | |
30 … | + } | |
31 … | + }, | |
32 … | + mounted() { | |
33 … | + this.whoami = localStorage.getItem('whoami') | |
34 … | + console.log('whoami', this.whoami) // ugh. looks fine in console! errors when calling id: this.whoami in the subscription | |
35 … | + // Object {message: "Param 0 must have a .id of type "feedId"", name: "UsageError", stack: "UsageError: Param 0 must have a .id of type "feedI…lebot\node_modules\packet-stream\index.js:230:11)"} | |
36 … | + // "Param 0 must have a .id of type "feedId"" | |
37 … | + // name | |
38 … | + // : | |
39 … | + // "UsageError" | |
40 … | + // stack | |
41 … | + // : | |
42 … | + // "UsageError: Param 0 must have a .id of type "feedId"↵ at new ZE (C:\Users\av8ta\AppData\Roaming\nvm\v8.8.0\node_modules\scuttlebot\node_modules\zerr\index.js:11:42)↵ at ZError (C:\Users\av8ta\AppData\Roaming\nvm\v8.8.0\node_modules\scuttlebot\node_modules\zerr\index.js:13:14)↵ at Object.createUserStreamOpts (C:\Users\av8ta\AppData\Roaming\nvm\v8.8.0\node_modules\scuttlebot\lib\validators.js:142:14)↵ at validate (C:\Users\av8ta\AppData\Roaming\nvm\v8.8.0\node_modules\scuttlebot\node_modules\muxrpc-validation\index.js:130:15)↵ at Object.<anonymous> (C:\Users\av8ta\AppData\Roaming\nvm\v8.8.0\node_modules\scuttlebot\node_modules\muxrpc-validation\index.js:91:17)↵ at Object.hooked (C:\Users\av8ta\AppData\Roaming\nvm\v8.8.0\node_modules\scuttlebot\node_modules\hoox\index.js:10:15)↵ at Object.localCall (C:\Users\av8ta\AppData\Roaming\nvm\v8.8.0\node_modules\scuttlebot\node_modules\muxrpc\local-api.js:31:31)↵ at Object.<anonymous> (C:\Users\av8ta\AppData\Roaming\nvm\v8.8.0\node_modules\scuttlebot\node_modules\muxrpc\local-api.js:37:22)↵ at PacketStreamSubstream.stream.read (C:\Users\av8ta\AppData\Roaming\nvm\v8.8.0\node_modules\scuttlebot\node_modules\muxrpc\stream.js:67:23)↵ at PacketStream._onstream (C:\Users\av8ta\AppData\Roaming\nvm\v8.8.0\node_modules\scuttlebot\node_modules\packet-stream\index.js:230:11)" | |
43 … | + | |
44 … | + } | |
45 … | +} | |
46 … | +</script> | |
47 … | + | |
48 … | +<style scoped> | |
49 … | +</style> |
src/main.js | ||
---|---|---|
@@ -25,8 +25,16 @@ | ||
25 | 25 … | // Observable adapters for pull-streams etc |
26 | 26 … | import SourceObserver from './plugins/source_observer' |
27 | 27 … | Vue.use(SourceObserver) |
28 | 28 … | |
29 … | +import moment from 'moment' | |
30 … | + | |
31 … | +Vue.filter('formatDate', function(value) { | |
32 … | + if (value) { | |
33 … | + return moment(value).format('YYYY/MM/DD hh:mm') | |
34 … | + } | |
35 … | +}) | |
36 … | + | |
29 | 37 … | var vm = new Vue({ // eslint-disable-line no-new |
30 | 38 … | el: '#app', |
31 | 39 … | router, |
32 | 40 … | components: { App }, |
src/router/index.js | ||
---|---|---|
@@ -1,10 +1,10 @@ | ||
1 | 1 … | import Vue from 'vue' |
2 | 2 … | import Router from 'vue-router' |
3 | 3 … | import Public from './../components/Public.vue' |
4 … | +import Profile from './../components/Profile.vue' | |
4 | 5 … | import Infinitely from './../components/Infinitely.vue' |
5 | 6 … | |
6 | - | |
7 | 7 … | Vue.use(Router) |
8 | 8 … | |
9 | 9 … | export default new Router({ |
10 | 10 … | routes: [ |
@@ -19,8 +19,14 @@ | ||
19 | 19 … | component: Public, |
20 | 20 … | props: true |
21 | 21 … | }, |
22 | 22 … | { |
23 … | + path: '/profile', | |
24 … | + name: 'profile', | |
25 … | + component: Profile, | |
26 … | + props: true | |
27 … | + }, | |
28 … | + { | |
23 | 29 … | path: '/infinitely', |
24 | 30 … | name: 'infinitely', |
25 | 31 … | component: Infinitely |
26 | 32 … | } |
Built with git-ssb-web