-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathdata.go
17 lines (15 loc) · 59.5 KB
/
data.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Generated by "go run gen.go". DO NOT EDIT.
package static
var fs = embeddedFilesystem{
"/frontend/app.html": &fileData{name: "app.html", mtime: 1556164706, size: 3914, body: []byte("<!doctype html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">\n <title>-</title>\n <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css\"\n integrity=\"sha256-916EbMg70RQy9LHiGkXzG8hSg9EdNy97GazNG/aiY1w=\" crossorigin=\"anonymous\" />\n <link rel=\"stylesheet\"\n href=\"https://cdnjs.cloudflare.com/ajax/libs/bootstrap-markdown/2.10.0/css/bootstrap-markdown.min.css\"\n integrity=\"sha256-umMZCcE/LUcJ3F3V/D6NmvQxdm3OWtRMiMApkNnDIOw=\" crossorigin=\"anonymous\" />\n <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css\"\n integrity=\"sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=\" crossorigin=\"anonymous\" />\n <link rel=\"stylesheet\" href=\"static/-/frontend/css/bebop.css\">\n</head>\n\n<body>\n <div id=\"app\"></div>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js\"\n integrity=\"sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=\" crossorigin=\"anonymous\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/fetch/3.0.0/fetch.min.js\"\n integrity=\"sha256-E1M+0f/hvoNVoV8K5RSn1gwe4EFwlvORnOrFzghX0wM=\" crossorigin=\"anonymous\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js\"\n integrity=\"sha256-7/yoZS3548fXSRXqc/xYzjsmuW3sFKzuvOCHd06Pmps=\" crossorigin=\"anonymous\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/lowdb/1.0.0/LocalStorage.min.js\"\n integrity=\"sha256-xhIlNEnkat+QjVp6Z6lClODzAIq80GR81ZGsRXpKiNo=\" crossorigin=\"anonymous\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/lowdb/1.0.0/low.min.js\"\n integrity=\"sha256-lVDmzWdUsCf4ikBrbm4vE+x0gRiAgDNYTHgWRCbVclg=\" crossorigin=\"anonymous\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js\"\n integrity=\"sha256-U5ZEeKfGNOja007MMD3YBI0A3OSZOQbeG6z2f2Y0hu8=\" crossorigin=\"anonymous\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js\"\n integrity=\"sha256-cWZZjnj99rynB+b8FaNGUivxc1kJSRa8ZM/E77cDq0I=\" crossorigin=\"anonymous\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/vue-router/2.4.0/vue-router.min.js\"\n integrity=\"sha256-fxzMMjPZbIwP33mgE/4GTQ9BTPM7X1PBAHaJ3Kvz6fo=\" crossorigin=\"anonymous\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/vue-resource/1.3.1/vue-resource.min.js\"\n integrity=\"sha256-vLNsWeWD+1TzgeVJX92ft87XtRoH3UVqKwbfB2nopMY=\" crossorigin=\"anonymous\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.6/marked.min.js\"\n integrity=\"sha256-mJAzKDq6kSoKqZKnA6UNLtPaIj8zT2mFnWu/GSouhgQ=\" crossorigin=\"anonymous\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/bootstrap-markdown/2.10.0/js/bootstrap-markdown.min.js\"\n integrity=\"sha256-vT9X0tmmfKfNTg0U/Iv0rM9mhu8LA0MaDFrzIflHN9A=\" crossorigin=\"anonymous\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js\"\n integrity=\"sha256-1hjUhpc44NwiNg8OwMu2QzJXhD8kcj+sJA3aCQZoUjg=\" crossorigin=\"anonymous\"></script>\n <script src=\"static/-/frontend/js/bebop-init.js\"></script>\n <script src=\"static/-/frontend/js/bebop-nav.js\"></script>\n <script src=\"static/-/frontend/js/bebop-username-modal.js\"></script>\n <script src=\"static/-/frontend/js/bebop-topics.js\"></script>\n <script src=\"static/-/frontend/js/bebop-new-topic.js\"></script>\n <script src=\"static/-/frontend/js/bebop-comments.js\"></script>\n <script src=\"static/-/frontend/js/bebop-new-comment.js\"></script>\n <script src=\"static/-/frontend/js/bebop-user.js\"></script>\n <script src=\"static/-/frontend/js/bebop-app.js\"></script>\n <script async defer src=\"https://buttons.github.io/buttons.js\"></script>\n</body>\n\n</html>")},
"/frontend/css/bebop.css": &fileData{name: "bebop.css", mtime: 1556164706, size: 6898, body: []byte("body { padding-top: 55px; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif; font-size: 16px; line-height: 1.5; color: #222; }\na { color: #286090; }\nh1 { margin: 12px 5px; font-size: 2.4rem; color: #333; }\nh2 { margin: 11px 5px; font-size: 2.2rem; color: #333; }\nh3 { margin: 10px 5px; font-size: 2.0rem; color: #333; }\n\n.container { max-width: 800px; }\n.content-container { padding: 0 5px; }\n\n.navbar { min-height: 55px; }\n.navbar-default { background-color: #2d2f33; border-bottom: #d0dbe5 1px solid; }\n.navbar-sign-in { padding: 15px 5px !important; color: white !important; }\n.navbar-user { padding: 8px 15px !important; }\n.navbar-title { color: white; letter-spacing: 1px; }\n.navbar-brand { padding: 5px; }\n.nav>li>a:focus, .nav>li>a:hover, .nav .open>a, .nav .open>a:focus, .nav .open>a:hover { background-color: #d0dbe5; }\n\n.avatar-block { display: block; padding:5px; }\n.avatar-block-l { display: table-cell; vertical-align: middle; }\n.avatar-block-r { display: table-cell; padding-left: 10px; vertical-align: middle; }\n\n.icon-s { width:15px; padding-right: 5px; }\n.loading-info { text-align: center; padding: 50px 0; }\n.info-separator { padding: 0 3px; }\n.btn-fix { min-width: 36px; }\n\n.card { background-color: #fff; border-top: #ccc 1px dashed; }\n\n.topics-topic { margin: 2px 0; padding: 2px 0; }\n.topics-topic-title { font-size: 1.5rem; padding-left: 5px;}\n.topics-topic-info { font-size: 1.2rem; color: #777; padding-left: 5px; margin-top: 2px; }\n.topics-topic-admin-tools { padding-left: 5px; font-size: 1.2rem; color: #d55; margin-top: 2px; }\n.topics-topic-admin-tools a { color: #d55; }\n.topics-topic-admin-tools a:hover { color: #f55; text-decoration: none; }\n.topics-topic-top-buttons { margin: 10px 5px; }\n\n.comments-comment { margin: 5px 0; padding: 5px 0; }\n.comments-comment-author { font-size: 1.4rem; color: #333; }\n.comments-comment-date { font-size: 1.2rem; color: #777; }\n.comments-comment-content { padding: 10px 5px 0 5px; overflow-x: auto; font-size: 1.5rem; }\n.comments-comment-admin-tools {padding-left: 5px; font-size: 1.2rem; color: #d55; margin-top: 4px; }\n.comments-comment-admin-tools a { color: #d55; }\n.comments-comment-admin-tools a:hover { color: #f55; text-decoration: none; }\n.comments-comment-new { margin: 15px 5px; }\n\n.comments-comment-content h1, .md-preview h1 { font-size: 2.2rem; color: #333; margin: 10px 0; }\n.comments-comment-content h2, .md-preview h2 { font-size: 2.1rem; color: #333; margin: 10px 0; }\n.comments-comment-content h3, .md-preview h3 { font-size: 2.0rem; color: #333; margin: 10px 0; }\n.comments-comment-content h4, .md-preview h4 { font-size: 1.9rem; color: #333; margin: 10px 0; }\n.comments-comment-content h5, .md-preview h5 { font-size: 1.8rem; color: #333; margin: 10px 0; }\n.comments-comment-content h6, .md-preview h6 { font-size: 1.7rem; color: #333; margin: 10px 0; }\n.comments-comment-content td, .md-preview td { border: #ccc 1px solid; padding: 5px; }\n.comments-comment-content th, .md-preview th { border: #ccc 1px solid; padding: 5px; }\n.comments-comment-content blockquote, .md-preview blockquote { color: #777; font-size: 1.3rem; }\n\n.user-profile { margin: 5px 0; padding: 5px; }\n\n#comment-input { height: 240px; background-color: #fff; }\n.md-editor { border-radius: 3px; }\n.md-header { border-top-left-radius: 3px; border-top-right-radius: 3px; }\ntextarea.md-input { border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; padding: 5px; }\n.md-preview { border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; padding: 5px; }\n\npre { \n border: 0;\n color: #333;\n background-color: #f5f6f7;\n white-space: pre;\n word-wrap: normal;\n word-break: normal;\n overflow-x: auto;\n font-size: 1.3rem;\n font-family: Consolas, Menlo, monospace;\n}\ncode, pre code {\n color: #333;\n background-color: #f5f6f7; \n font-size: 1.3rem;\n font-family: Consolas, Menlo, monospace;\n white-space: pre;\n}\n\n.pagination { margin: 10px 5px; }\n\n/* covenantsql */\n#main { min-height: 85vh; }\n.cqldb.list { float: right; }\n.cqldb.comment { float: right; font-size: 12px; padding: 6px 0; }\n.cqldb.comment a {\n padding: 4px 5px;\n background: #286090;\n border-radius: 6px;\n color: white;\n text-decoration: none;\n}\n.cqldb.comment a:hover {\n background: #5098e8;\n}\n.cqldb.comment .disabled {\n background: grey;\n pointer-events: none;\n cursor: not-allowed;\n}\nimg { max-width: 100%; }\n\n/* footer */\n.nav-footer {\n background: #20232a;\n border: none;\n color: #202020;\n font-size: 15px;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n font-weight: 400;\n line-height: 24px;\n padding-bottom: 20px;\n padding-top: 20px;\n position: relative;\n }\n @media only screen and (min-width: 1024px) {\n .nav-footer {\n flex-shrink: 0;\n }\n }\n .nav-footer .powerby {\n color: white;\n font-weight: 600;\n padding-right: 10px;\n text-decoration: none;\n }\n .nav-footer .sitemap {\n display: flex;\n justify-content: space-between;\n margin: 0 auto 1em;\n max-width: 1080px;\n }\n .nav-footer .sitemap .logoicon {\n height: 58px;\n margin-right: 32px;\n margin-bottom: 32px;\n opacity: 0.9;\n transition: opacity 0.15s ease-in-out;\n }\n .nav-footer .sitemap .logoicon:hover {\n opacity: 1;\n }\n .nav-footer .sitemap div {\n flex: 1;\n }\n .nav-footer .sitemap .nav-home {\n display: table;\n height: 72px;\n margin: -12px 20px 0 0;\n opacity: 0.4;\n padding: 10px;\n transition: opacity 0.15s ease-in-out;\n width: 72px;\n }\n .nav-footer .sitemap .nav-home:focus,\n .nav-footer .sitemap .nav-home:hover {\n opacity: 1;\n }\n @media only screen and (max-width: 735px) {\n .nav-footer .sitemap {\n display: flex;\n flex-direction: column;\n margin: 0 2em 3em;\n width: calc(100% - 4em);\n }\n .nav-footer .sitemap > div {\n margin-bottom: 18px;\n }\n }\n .nav-footer .sitemap a {\n color: hsla(0, 0%, 100%, 0.6);\n display: block;\n margin: 2px -10px;\n padding: 0px 10px;\n }\n .nav-footer .sitemap a:focus,\n .nav-footer .sitemap a:hover,\n .nav-footer .sitemap h3 > a:focus,\n .nav-footer .sitemap h3 > a:hover {\n color: #fff;\n text-decoration: none;\n }\n .nav-footer .sitemap h3,\n .nav-footer .sitemap h6 {\n margin: 0 0 10px;\n }\n .nav-footer .sitemap h3,\n .nav-footer .sitemap h3 > a,\n .nav-footer .sitemap h6,\n .nav-footer .sitemap h6 > a {\n color: #fff;\n }\n .nav-footer .sitemap h3 > a,\n .nav-footer .sitemap h6 > a {\n margin: 0 -10px;\n }\n .nav-footer .covenant-icon {\n display: block;\n margin: 0 auto;\n opacity: 0.4;\n transition: opacity 0.15s ease-in-out;\n width: 300px;\n }\n .nav-footer .covenant-icon:hover {\n opacity: 1;\n }\n .nav-footer .copyright {\n color: hsla(0, 0%, 100%, 0.4);\n text-align: center;\n }\n .nav-footer .social {\n padding: 5px 0;\n }")},
"/frontend/js/bebop-app.js": &fileData{name: "bebop-app.js", mtime: 1556164706, size: 6461, body: []byte("const BEBOP_LOCAL_STORAGE_TOKEN_KEY = \"bebop_auth_token\";\nconst BEBOP_OAUTH_RESULT_COOKIE = \"bebop_oauth_result\";\n\n// global vars\nwindow.API_HOST = \"https://explorer.dbhub.org\"\nwindow.DBID = \"\"\nwindow.HEAD_API = () => `${window.API_HOST}/apiproxy.covenantsql/v2/head/${window.DBID}`\nwindow.SQL_HASH_API = (hash) => `${window.API_HOST}/apiproxy.covenantsql/v1/request/${window.DBID}/${hash}`\nwindow.BLOCK_API = (height) => `${window.API_HOST}/apiproxy.covenantsql/v3/count/${window.DBID}/${height}?page=1&size=999`\n\n// lowdb\nconst adapter = new LocalStorage('db')\nwindow.db = low(adapter)\n// init db\ndb.defaults({ blocks: [], sql: [], head: {} }).write()\n\nvar BebopApp = new Vue({\n el: \"#app\",\n\n template: `\n <div>\n <bebop-nav :config=\"config\" :auth=\"auth\"></bebop-nav>\n <bebop-username-modal ref=\"usernameModal\"></bebop-username-modal>\n <div id=\"main\">\n <router-view :config=\"config\" :auth=\"auth\" :rawConfig=\"rawConfig\"></router-view>\n </div>\n\n <footer class=\"nav-footer\" id=\"footer\">\n <a\n href=\"https://github.com/CovenantSQL/CovenantSQL\"\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n class=\"covenant-icon\"\n >\n <span class=\"powerby\">Powered by</span>\n <img\n src=\"https://developers.covenantsql.io/img/horizontal_logo.svg\"\n alt=\"CovenantSQL\"\n width=\"170\"\n height=\"45\"\n >\n </a>\n </footer>\n\n </div>\n `,\n\n router: new VueRouter({\n routes: [\n { path: \"/\", component: BebopTopics },\n { path: \"/p/:page\", component: BebopTopics },\n { path: \"/t/:topic\", component: BebopComments },\n { path: \"/t/:topic/p/:page\", component: BebopComments },\n { path: \"/t/:topic/p/:page/c/:comment\", component: BebopComments },\n { path: \"/new-topic\", component: BebopNewTopic },\n { path: \"/new-comment/:topic\", component: BebopNewComment },\n { path: \"/me\", component: BebopUser },\n { path: \"/u/:user\", component: BebopUser },\n ],\n scrollBehavior: function (to, from, savedPosition) {\n if (savedPosition) {\n return savedPosition;\n } else {\n return { x: 0, y: 0 };\n }\n },\n }),\n\n data: function () {\n return {\n config: {\n title: \"\",\n oauth: [],\n },\n rawConfig: {},\n auth: {\n authenticated: false,\n user: {},\n }\n };\n },\n\n mounted: function () {\n this.getConfig().then(this.getBlockHead)\n this.checkAuth()\n\n window.getBlockHead = this.getBlockHead\n },\n\n methods: {\n getBlockHead: function () {\n if (window.DBID) {\n let url = HEAD_API()\n console.log(\" ___ _ __ ____ __ \")\n console.log(\" / __\\\\_____ _____ _ __ __ _ _ __ | |_/ _\\\\ /___ \\\\/ /\")\n console.log(\" / / / _ \\\\ \\\\ / / _ | '_ \\\\ / _\\` | '_ \\\\| __\\\\ \\\\ // / / /\")\n console.log(\"/ /__| (_) \\\\ V | __| | | | (_| | | | | |__\\\\ / \\\\_/ / /___\")\n console.log(\"\\\\____/\\\\___/ \\\\_/ \\\\___|_| |_|\\\\__,_|_| |_|\\\\__\\\\__\\\\___,_\\\\____/\")\n console.log('--- connected db:', DBID)\n\n fetch(url).then(res => res.json()).then((d) => {\n let head = _.get(d, 'data.block', {})\n console.log('--- current head block', head)\n db.set('head', head).write()\n }).catch(e => {\n console.error(e)\n })\n }\n },\n\n getConfig: function () {\n return this.$http.get(\"config.json\").then(\n response => {\n this.config = response.body\n console.log('// raw config response:', response.body.raw)\n this.rawConfig = response.body.raw\n\n let _dsn = _.get(this.rawConfig, ['Store', 'CovenantSQL', 'Database'], '')\n window.DBID = _dsn.split('//')[1] || ''\n\n if (this.config.title) {\n document.title = this.config.title;\n }\n },\n response => {\n console.log(\"ERROR: getConfig: \" + response.status);\n }\n );\n },\n\n signIn: function (provider) {\n window.open(\"oauth/begin/\" + provider, \"\", \"width=800,height=600\");\n },\n\n signOut: function () {\n localStorage.removeItem(BEBOP_LOCAL_STORAGE_TOKEN_KEY);\n Vue.http.headers.common[\"Authorization\"] = \"\";\n this.auth = {\n authenticated: false,\n user: {},\n };\n },\n\n oauthEnd: function () {\n var result = this.getCookieByName(BEBOP_OAUTH_RESULT_COOKIE);\n var parts = result.split(\":\");\n\n if (parts.length !== 2) {\n this.oauthError(\"Unknown\");\n return;\n }\n\n if (parts[0] === \"error\") {\n this.oauthError(parts[1]);\n return;\n }\n\n if (parts[0] !== \"success\") {\n this.oauthError(\"Unknown\");\n return;\n }\n\n this.oauthSuccess(parts[1]);\n },\n\n getCookieByName: function (name) {\n var value = \"; \" + document.cookie;\n var parts = value.split(\"; \" + name + \"=\");\n if (parts.length === 2) return parts.pop().split(\";\").shift();\n },\n\n oauthSuccess: function (token) {\n localStorage.setItem(BEBOP_LOCAL_STORAGE_TOKEN_KEY, token);\n this.checkAuth();\n },\n\n oauthError: function (error) {\n if (error === \"UserBlocked\") {\n console.log(\"oauth error: USER IS BLOCKED\");\n } else {\n console.log(\"oauth error: \" + error);\n }\n this.signOut();\n },\n\n checkAuth: function () {\n var token = localStorage.getItem(BEBOP_LOCAL_STORAGE_TOKEN_KEY);\n if (token) {\n Vue.http.headers.common[\"Authorization\"] = \"Bearer \" + token;\n }\n this.getMe();\n },\n\n getMe: function () {\n this.$http.get(\"api/v1/me\").then(\n response => {\n this.auth = {\n authenticated: response.body.authenticated ? true : false,\n user: response.body.authenticated ? response.body.user : {},\n };\n if (this.auth.authenticated && this.auth.user.name === \"\") {\n this.setMyName();\n }\n },\n response => {\n console.log(\"ERROR: getMe: \" + JSON.stringify(response.body));\n if (response.status === 401) {\n this.signOut();\n }\n }\n );\n },\n\n setMyName: function () {\n this.$refs.usernameModal.show(this.auth.user.id, \"\", success => {\n if (!success) {\n this.signOut();\n }\n this.getMe();\n });\n },\n },\n});\n\nfunction bebopOAuthEnd() {\n BebopApp.oauthEnd();\n}\n")},
"/frontend/js/bebop-comments.js": &fileData{name: "bebop-comments.js", mtime: 1556172430, size: 13393, body: []byte("const COMMENTS_PER_PAGE = 10\nconst API_HOST = window.API_HOST\nconst findCommentHashByCreatedAt = (createdAt) => {\n let sqls = db.get('sql').value()\n let hash = ''\n sqls.forEach(sql => {\n sql.queries.forEach(query => {\n if (_.startsWith(query.pattern, 'insert into comments') && _.get(query, ['args', 3, 'value']) === createdAt) {\n hash = sql.hash\n }\n })\n })\n return hash\n}\n\nvar BebopComments = Vue.component(\"bebop-comments\", {\n template: `\n <div class=\"container content-container\">\n\n <div v-if=\"!dataReady\" class=\"loading-info\">\n <div v-if=\"error\" >\n <p class=\"text-danger\">\n Sorry, could not load that topic. Please check your connection.\n </p>\n <a class=\"btn btn-primary btn-sm\" role=\"button\" @click=\"load\">\n <i class=\"fa fa-refresh\"></i> Try Again\n </a>\n </div>\n <div v-else>\n <i class=\"fa fa-circle-o-notch fa-spin fa-3x fa-fw\"></i>\n </div>\n </div>\n <div v-else>\n\n <h2>{{topic.title}}</h2>\n\n <nav v-if=\"lastPage > 1\">\n <ul class=\"pagination pagination-sm\">\n <li v-for=\"p in pagination\" :class=\"{active: page === p}\">\n <span v-if=\"p === '...'\">\u2026</span>\n <router-link v-if=\"p !== '...'\" :to=\"'/t/' + topicId + '/p/' + p\">{{p}}</router-link>\n </li>\n </ul>\n </nav>\n\n <div v-for=\"comment in comments\" class=\"card comments-comment\" :id=\"'comment-' + comment.id\">\n\n <div class=\"avatar-block\">\n <div class=\"cqldb comment\">\n <a target=\"_blank\" :href=\"comment.requestHash | getCommentSQLRequestHref\" v-bind:class=\"{ disabled: !requestHashReady[comment.requestHash] }\" >\n <svg v-show=\"!requestHashReady[comment.requestHash]\" style=\"transform: translateY(6px);\" width=\"20px\" height=\"20px\" viewBox=\"0 0 45 45\" xmlns=\"http://www.w3.org/2000/svg\"><path opacity=\".2\" fill=\"#fff\" d=\"M20.201 5.169c-8.254 0-14.946 6.692-14.946 14.946 0 8.255 6.692 14.946 14.946 14.946s14.946-6.691 14.946-14.946c-.001-8.254-6.692-14.946-14.946-14.946zm0 26.58c-6.425 0-11.634-5.208-11.634-11.634 0-6.425 5.209-11.634 11.634-11.634 6.425 0 11.633 5.209 11.633 11.634 0 6.426-5.208 11.634-11.633 11.634z\"/><path fill=\"#fff\" d=\"M26.013 10.047l1.654-2.866a14.855 14.855 0 0 0-7.466-2.012v3.312c2.119 0 4.1.576 5.812 1.566z\"><animateTransform attributeType=\"xml\" attributeName=\"transform\" type=\"rotate\" from=\"0 20 20\" to=\"360 20 20\" dur=\"0.5s\" repeatCount=\"indefinite\"/></path></svg>\n <svg v-show=\"requestHashReady[comment.requestHash]\" style=\"transform: translateY(6px);\" width=\"20px\" height=\"20px\" viewBox=\"0 0 45 45\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"><defs><path id=\"a\" d=\"M0 0h45v45H0z\"/></defs><g fill=\"none\" fill-rule=\"evenodd\"><mask id=\"b\" fill=\"#fff\"><use xlink:href=\"#a\"/></mask><g mask=\"url(#b)\"><path d=\"M22.44 2L5 12.08v20.07l17.4 10.1h.06a60.48 60.48 0 0 1-.84-10.59v-1a8.94 8.94 0 1 1 7.85-3.31l.71.45a38.42 38.42 0 0 0 9.72 4.4V12.06L22.44 2zm-8.71 14.19a7.94 7.94 0 0 1 .51-7.32s.06-.09.08 0a14.83 14.83 0 0 0 1.11 3.58c.233.375.497.73.79 1.06a10.49 10.49 0 0 0-2.49 2.68zm17.38-.27a10.49 10.49 0 0 0-2.52-2.55 8 8 0 0 0 .75-1 7.91 7.91 0 0 0 1.1-3.44v-.18c0-.12 0-.07.08 0a7.94 7.94 0 0 1 .59 7.17z\" fill=\"#FFF\" fill-rule=\"nonzero\"/></g></g></svg>\n CovenantSQL\n </a>\n </div>\n <div class=\"avatar-block-l\">\n <img v-if=\"users[comment.authorId].avatar\" class=\"img-circle\" :src=\"users[comment.authorId].avatar\" width=\"35\" height=\"35\">\n <img v-else class=\"img-circle\" src=\"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\" width=\"35\" height=\"35\">\n </div>\n <div class=\"avatar-block-r\">\n <div class=\"comments-comment-author\">{{users[comment.authorId].name}}</div>\n <div class=\"comments-comment-date\">\n commented <span :title=\"comment.createdAt|formatTime\">{{comment.createdAt|formatTimeAgo}}</span>\n </div>\n </div>\n </div>\n\n <div class=\"comments-comment-content\" v-html=\"comment.content\">\n </div>\n\n <div v-if=\"auth.authenticated && auth.user.admin\" class=\"comments-comment-admin-tools\">\n <a v-if=\"topic.commentCount > 1\" class=\"a-tool\" role=\"button\" @click=\"delComment(comment.id)\"><i class=\"fa fa-times\" aria-hidden=\"true\"></i> delete comment</a>\n <span v-if=\"topic.commentCount > 1\" class=\"info-separator\"> | </span>\n <router-link :to=\"'/u/' + users[comment.authorId].id\" class=\"a-tool\"><i class=\"fa fa-user\" aria-hidden=\"true\"></i> user profile</router-link>\n </div>\n </div>\n\n <div v-if=\"auth.authenticated && page === lastPage\" class=\"comments-comment-new\">\n <router-link :to=\"'/new-comment/' + topicId\" class=\"btn btn-primary btn-sm\">\n <i class=\"fa fa-reply\" aria-hidden=\"true\"></i>\n Reply\n </router-link>\n </div>\n\n <nav v-if=\"lastPage > 1\">\n <ul class=\"pagination pagination-sm\">\n <li v-for=\"p in pagination\" :class=\"{active: page === p}\">\n <span v-if=\"p === '...'\">\u2026</span>\n <router-link v-if=\"p !== '...'\" :to=\"'/t/' + topicId + '/p/' + p\">{{p}}</router-link>\n </li>\n </ul>\n </nav>\n\n </div>\n\n </div>\n `,\n\n props: [\"config\", \"auth\", \"dbid\"],\n\n data: function () {\n return {\n topic: {},\n topicReady: false,\n requestHashReady: {},\n comments: [],\n commentCount: 0,\n commentsReady: false,\n users: {},\n usersReady: false,\n error: false,\n };\n },\n\n computed: {\n dataReady: function () {\n return this.topicReady && this.commentsReady && this.usersReady;\n },\n\n topicId: function () {\n var topicId = parseInt(this.$route.params.topic, 10);\n if (!topicId) {\n return 0;\n }\n return topicId;\n },\n\n page: function () {\n var page = parseInt(this.$route.params.page, 10);\n if (!page || page < 1) {\n return 1;\n }\n return page;\n },\n\n lastPage: function () {\n if (!this.commentsReady) {\n return 1;\n }\n var p = Math.floor((this.commentCount - 1) / COMMENTS_PER_PAGE) + 1;\n if (p < 1) {\n p = 1;\n }\n return p;\n },\n\n pagination: function () {\n if (!this.commentsReady) {\n return [];\n }\n return getPagination(this.page, this.lastPage);\n },\n },\n\n watch: {\n page: function (val) {\n this.load();\n },\n topicId: function (val) {\n this.load();\n },\n dataReady: function (val) {\n if (val && this.$route.params.comment) {\n this.$nextTick(() => {\n $(\"html, body\").animate(\n {\n scrollTop: $(\"#comment-\" + this.$route.params.comment).offset().top,\n },\n 500\n );\n });\n }\n },\n },\n\n created: function () {\n this.load();\n },\n\n filters: {\n getCommentSQLRequestHref: function (hash) {\n // let hash = findCommentHashByCreatedAt(createdAt)\n return !!hash ? `${window.API_HOST}/dbs/${window.DBID}/requests/${hash}` : ''\n },\n },\n\n methods: {\n load: function () {\n this.topic = {};\n this.topicReady = false;\n this.requestHashReady = {};\n this.comments = [];\n this.commentCount = 0;\n this.commentsReady = false;\n this.users = {};\n this.usersReady = false;\n this.waitNewComment = false;\n this.error = false;\n\n // async calls\n this.getTopic();\n this.getComments().then(this.getCommentSQLQueries);\n },\n\n // if comment.requestHash is empty, disable the button\n isCommentHashReady: function (comment) {\n return this.requestHashReady[comment.requestHash] === true\n },\n getCommentSQLQueries: function () {\n // DEPRECATED find possible height by Chenxi 2019-04-24\n // Add polling to get current requestHash's block by Chenxi 2019-04-25\n // set requestHashReady when the block is not emtpy, then stop polling\n this.comments.forEach(comment => {\n // init requestHashReady\n const hash = comment.requestHash\n // if comment created two mins ago then consider it as on-chain\n let initState = (new Date()).getTime() - (new Date(comment.createdAt)).getTime() > 1000 * 120 ? true : false\n this.$set(this.requestHashReady, hash, initState)\n console.log('init hash ready', hash, initState)\n\n if (hash) {\n let interval = setInterval(() => {\n let url = window.SQL_HASH_API(hash)\n fetch(url).then(res => res.json())\n .then(d => {\n if (d && d.data) {\n clearInterval(interval)\n this.requestHashReady[hash] = true\n console.log(hash, this.requestHashReady[hash])\n }\n })\n .catch(e => { console.info(\"get hash's block\", e) })\n }, 3000)\n }\n // this.getTimeRelatedBlocks(possibleHeight)\n })\n },\n writeSQL: function (block) {\n if (!_.isEmpty(block)) {\n block.queries.forEach(q => {\n console.log('// write sql db', q.request)\n db.get('sql').push(q.request).write()\n })\n }\n },\n getTimeRelatedBlocks: function (height, offset = 1) {\n // get [height - offset, height, height + offset] blocks\n let heightArr = []\n for (let i = height - offset; i <= height + offset; i++) {\n if (i > -1) {\n heightArr.push(i)\n }\n }\n\n let promises = []\n heightArr.forEach(h => {\n if (!db.get('blocks').find({ count: h }).value()) {\n let url = window.BLOCK_API(h)\n\n let promise = new Promise((resolve, reject) => {\n fetch(url).then(res => res.json()).then(data => {\n const block = _.get(data, ['data', 'block'])\n console.log('// getTimeRelatedBlocks: ', block)\n db.get('blocks').push(block).write()\n\n // writeSQL\n this.writeSQL(block)\n resolve(block)\n }).catch(e => {\n console.error(e)\n reject(e)\n })\n })\n\n return promise\n }\n })\n\n return Promise.all(promises)\n },\n computeTimeHeight: function (unixTimestamp) {\n let headTimestamp = db.get('head').value().timestamp\n let headHeight = db.get('head').value().count\n\n if (headTimestamp && headHeight) {\n let offset = Math.floor((headTimestamp - unixTimestamp) / (1000 * 60))\n return headHeight - offset\n }\n },\n\n getTopic: function () {\n var url = \"api/v1/topics/\" + this.topicId;\n this.$http.get(url).then(\n response => {\n this.topic = response.body.topic;\n this.topicReady = true;\n },\n response => {\n this.error = true;\n console.log(\"ERROR: getTopic: \" + JSON.stringify(response.body));\n }\n );\n },\n\n getComments: function () {\n var url = \"api/v1/comments?topic=\" + this.topicId + \"&limit=\" + COMMENTS_PER_PAGE;\n if (this.page > 0) {\n var offset = (this.page - 1) * COMMENTS_PER_PAGE;\n url += \"&offset=\" + offset;\n }\n return this.$http.get(url).then(\n response => {\n this.comments = response.body.comments;\n this.commentCount = response.body.count;\n for (var i = 0; i < this.comments.length; i++) {\n this.comments[i].content = marked(this.comments[i].content, {\n sanitize: true,\n breaks: true,\n });\n }\n this.commentsReady = true;\n\n if (this.page > this.lastPage) {\n this.$parent.$router.replace(\"/t/\" + this.topicId + \"/p/\" + this.lastPage);\n return;\n }\n\n this.getUsers();\n },\n response => {\n this.error = true;\n console.log(\"ERROR: getComments: \" + JSON.stringify(response.body));\n }\n );\n },\n\n getUsers: function () {\n var url = \"api/v1/users\";\n var ids = [];\n for (var i = 0; i < this.comments.length; i++) {\n ids.push(this.comments[i].authorId);\n }\n ids = ids.filter((v, i, a) => a.indexOf(v) === i);\n if (ids.length === 0) {\n this.users = {};\n this.usersReady = true;\n return;\n }\n url += \"?ids=\" + ids.join(\",\");\n this.$http.get(url).then(\n response => {\n var users = {};\n for (var i = 0; i < response.body.users.length; i++) {\n users[response.body.users[i].id] = response.body.users[i];\n }\n this.users = users;\n this.usersReady = true;\n },\n response => {\n this.error = true;\n console.log(\"ERROR: getUsers: \" + JSON.stringify(response.body));\n }\n );\n },\n\n delComment: function (id) {\n if (!confirm(\"Are you sure you want to delete comment \" + id + \"?\")) {\n return;\n }\n var url = \"api/v1/comments/\" + id;\n this.$http.delete(url).then(\n response => {\n this.load();\n },\n response => {\n console.log(\"ERROR: delComment: \" + JSON.stringify(response.body));\n }\n );\n },\n },\n});\n")},
"/frontend/js/bebop-init.js": &fileData{name: "bebop-init.js", mtime: 1556164706, size: 890, body: []byte("marked.setOptions({\n sanitize: true,\n breaks: true,\n});\n\nVue.filter(\"formatTime\", function(value) {\n if (value) {\n return moment(String(value)).format(\"MMMM Do YYYY, hh:mm\");\n }\n});\n\nVue.filter(\"formatTimeAgo\", function(value) {\n if (value) {\n return moment(String(value)).fromNow();\n }\n});\n\nVue.filter(\"capitalize\", function(value) {\n if (value) {\n value = String(value);\n return value[0].toUpperCase() + value.slice(1);\n }\n});\n\nfunction getPagination(curPage, lastPage) {\n var pagination = [];\n var lr = 2;\n\n pagination.push(1);\n\n if (curPage - lr > 2) {\n pagination.push(\"...\");\n }\n\n for (var p = curPage - lr; p <= curPage + lr; p++) {\n if (p > 1 && p < lastPage) {\n pagination.push(p);\n }\n }\n\n if (curPage + lr < lastPage - 1) {\n pagination.push(\"...\");\n }\n\n if (lastPage > 1) {\n pagination.push(lastPage);\n }\n\n return pagination;\n}\n")},
"/frontend/js/bebop-nav.js": &fileData{name: "bebop-nav.js", mtime: 1556164706, size: 2465, body: []byte("Vue.component(\"bebop-nav\", {\n template: `\n <nav class=\"navbar navbar-default navbar-fixed-top\">\n <div class=\"container\">\n <div class=\"navbar-header pull-left\">\n <router-link to=\"/\" class=\"navbar-brand\">\n <span class=\"navbar-title\">\n <img src='https://cdn.jsdelivr.net/gh/CovenantSQL/logos/logo_icon_white.svg' alt='logo' />\n {{ config.title }}\n </span>\n </router-link>\n </div>\n <div class=\"navbar-header pull-right\">\n <ul class=\"nav pull-left\">\n <li v-if=\"auth.authenticated\">\n <a class=\"navbar-link dropdown-toggle navbar-user\" role=\"button\" data-toggle=\"dropdown\" :title=\"auth.user.name\">\n <img v-if=\"auth.user.avatar\" class=\"img-circle\" :src=\"auth.user.avatar\" width=\"35\" height=\"35\"> \n <img v-else class=\"img-circle\" src=\"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\" width=\"35\" height=\"35\"> \n <span class=\"caret\"></span>\n </a>\n <ul class=\"dropdown-menu pull-right\">\n <li>\n <router-link to=\"/me\">\n <i class=\"fa fa-user icon-s\"></i>\n {{auth.user.name}}\n </router-link>\n </li>\n <li role=\"separator\" class=\"divider\"></li>\n <li>\n <a href=\"#\" @click.prevent=\"$parent.signOut()\">\n <i class=\"fa fa-sign-out icon-s\"></i>\n Sign out\n </a>\n </li>\n </ul>\n </li>\n <li v-else>\n <a class=\"navbar-link dropdown-toggle navbar-sign-in\" href=\"#\" data-toggle=\"dropdown\">\n <i class=\"fa fa-user icon-s\"></i>\n Sign In / Up \n <span class=\"caret\"></span>\n </a>\n <ul class=\"dropdown-menu pull-right\">\n <li v-for=\"provider in config.oauth\">\n <a href=\"#\" @click.prevent=\"$parent.signIn(provider)\">\n <i :class=\"'icon-s fa fa-' + provider\" aria-hidden=\"true\"></i>\n with {{provider|capitalize}}\n </a>\n </li>\n </ul>\n </li>\n </ul>\n </div>\n </div>\n </nav>\n `,\n\n props: [\"config\", \"auth\"],\n\n data: function () {\n return {};\n },\n\n created: function () {\n },\n});\n")},
"/frontend/js/bebop-new-comment.js": &fileData{name: "bebop-new-comment.js", mtime: 1556164706, size: 2234, body: []byte("var BebopNewComment = Vue.component(\"bebop-new-comment\", {\n template: `\n <div class=\"container content-container\">\n <h2>New Comment</h2>\n <div>\n <div class=\"form-group\">\n <label for=\"user-name\" class=\"form-control-label\">Comment:</label>\n <textarea class=\"form-control\" id=\"comment-input\" @change=\"hideErrorMessage\" @keyup=\"hideErrorMessage\" maxlength=\"10000\"></textarea>\n </div>\n <div id=\"form-error\" class=\"alert alert-danger\" :class=\"{hidden: errorMessage===''}\" role=\"alert\" style=\"cursor:pointer\" @click=\"hideErrorMessage\">\n {{errorMessage}}\n </div>\n </div>\n <div>\n <button type=\"button\" class=\"btn btn-primary btn-sm\" @click=\"postComment\" :disabled=\"posting\">\n <i class=\"fa fa-reply\"></i> Reply\n </button>\n </div>\n </div>\n `,\n\n props: [\"config\", \"auth\"],\n\n data: function() {\n return {\n errorMessage: \"\",\n posting: false,\n };\n },\n\n mounted: function() {\n $(\"#comment-input\").markdown({\n iconlibrary: \"fa\",\n fullscreen: {\n enable: false,\n },\n });\n },\n\n methods: {\n postComment: function() {\n var topicId = parseInt(this.$route.params.topic, 10);\n var comment = $(\"#comment-input\").val().trim();\n if (comment.length < 1 || comment.length > 10000) {\n this.showErrorMessage(\"Invalid comment\");\n return;\n }\n this.posting = true;\n this.$http\n .post(\"api/v1/comments\", {\n topic: topicId,\n content: comment,\n })\n .then(\n response => {\n var id = response.data.id;\n var page = Math.floor((response.data.count - 1) / COMMENTS_PER_PAGE) + 1;\n this.posting = false;\n this.$parent.$router.push(\"/t/\" + topicId + \"/p/\" + page + /c/ + id);\n },\n response => {\n this.posting = false;\n this.showErrorMessage(\"An error occured\");\n console.log(\"ERROR: postComment: \" + JSON.stringify(response.body));\n }\n );\n },\n\n showErrorMessage: function(message) {\n this.errorMessage = message;\n },\n\n hideErrorMessage: function() {\n this.errorMessage = \"\";\n },\n },\n});\n")},
"/frontend/js/bebop-new-topic.js": &fileData{name: "bebop-new-topic.js", mtime: 1556164706, size: 2474, body: []byte("var BebopNewTopic = Vue.component(\"bebop-new-topic\", {\n template: `\n <div class=\"container content-container\">\n <h2>New Topic</h2>\n <div>\n <div class=\"form-group\">\n <label for=\"user-name\" class=\"form-control-label\">Title:</label>\n <input type=\"text\" class=\"form-control\" id=\"topic-title-input\" @change=\"hideErrorMessage\" @keyup=\"hideErrorMessage\" maxlength=\"100\">\n </div>\n <div class=\"form-group\">\n <label for=\"user-name\" class=\"form-control-label\">Comment:</label>\n <textarea class=\"form-control\" id=\"comment-input\" @change=\"hideErrorMessage\" @keyup=\"hideErrorMessage\" maxlength=\"10000\"></textarea>\n </div>\n <div id=\"form-error\" class=\"alert alert-danger\" :class=\"{hidden: errorMessage===''}\" role=\"alert\" style=\"cursor:pointer\" @click=\"hideErrorMessage\">\n {{errorMessage}}\n </div>\n </div>\n <div>\n <button type=\"button\" class=\"btn btn-primary btn-sm\" @click=\"postTopic\" :disabled=\"posting\">\n <i class=\"fa fa-plus\"></i> Create Topic\n </button>\n </div>\n </div>\n `,\n\n props: [\"config\", \"auth\"],\n\n data: function() {\n return {\n errorMessage: \"\",\n posting: false,\n };\n },\n\n mounted: function() {\n $(\"#comment-input\").markdown({\n iconlibrary: \"fa\",\n fullscreen: {\n enable: false,\n },\n });\n },\n\n methods: {\n postTopic: function() {\n var title = $(\"#topic-title-input\").val().trim();\n if (title.length < 1 || title.length > 100) {\n this.showErrorMessage(\"Invalid topic title\");\n return;\n }\n var comment = $(\"#comment-input\").val().trim();\n if (comment.length < 1 || comment.length > 10000) {\n this.showErrorMessage(\"Invalid comment\");\n return;\n }\n this.posting = true;\n this.$http\n .post(\"api/v1/topics\", {\n title: title,\n content: comment,\n })\n .then(\n response => {\n this.posting = false;\n this.$parent.$router.push(\"/t/\" + response.data.id);\n },\n response => {\n this.posting = false;\n this.showErrorMessage(\"An error occured\");\n console.log(\"ERROR: postTopic: \" + JSON.stringify(response.body));\n }\n );\n },\n\n showErrorMessage: function(message) {\n this.errorMessage = message;\n },\n\n hideErrorMessage: function() {\n this.errorMessage = \"\";\n },\n },\n});\n")},
"/frontend/js/bebop-topics.js": &fileData{name: "bebop-topics.js", mtime: 1556164706, size: 6786, body: []byte("const TOPICS_PER_PAGE = 10;\n\nvar BebopTopics = Vue.component(\"bebop-topics\", {\n template: `\n <div class=\"container content-container\">\n\n <div v-if=\"!dataReady\" class=\"loading-info\">\n <div v-if=\"error\" >\n <p class=\"text-danger\">\n Sorry, could not load topics. Please check your connection.\n </p>\n <a class=\"btn btn-primary btn-sm\" role=\"button\" @click=\"load\">\n <i class=\"fa fa-refresh\"></i> Try Again\n </a>\n </div>\n <div v-else>\n <i class=\"fa fa-circle-o-notch fa-spin fa-3x fa-fw\"></i>\n </div>\n </div>\n <div v-else>\n\n <div class=\"topics-topic-top-buttons\">\n <a target=\"_blank\" class=\"cqldb list btn btn-primary btn-sm\" :href=\"dbLink\">\n <i class=\"fa fa-external-link\"></i> CovenantSQL DB Chain\n </a>\n <router-link v-if=\"auth.authenticated\" to=\"/new-topic\" class=\"btn btn-primary btn-sm\">\n <i class=\"fa fa-plus\"></i> New Topic\n </router-link>\n <a class=\"btn btn-primary btn-sm\" role=\"button\" @click=\"load\">\n <i class=\"fa fa-refresh\"></i> Refresh\n </a>\n </div>\n\n <nav v-if=\"page > 1\">\n <ul class=\"pagination pagination-sm\">\n <li v-for=\"p in pagination\" :class=\"{active: page === p}\">\n <span v-if=\"p === '...'\">\u2026</span>\n <router-link v-if=\"p !== '...'\" :to=\"'/p/' + p\">{{p}}</router-link>\n </li>\n </ul>\n </nav>\n\n <div v-for=\"topic in topics\" class=\"card topics-topic\">\n <div class=\"avatar-block\">\n <div class=\"avatar-block-l\">\n <img v-if=\"users[topic.authorId].avatar\" class=\"img-circle\" :src=\"users[topic.authorId].avatar\" width=\"40\" height=\"40\"> \n <img v-else class=\"img-circle\" src=\"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\" width=\"40\" height=\"40\"> \n </div>\n <div class=\"avatar-block-r\">\n <div class=\"topics-topic-title\">\n <router-link :to=\"'/t/' + topic.id\">{{topic.title}}</router-link>\n </div>\n <div class=\"topics-topic-info\">\n <i class=\"fa fa-user-o\"></i> {{users[topic.authorId].name}}\n <span class=\"info-separator\"> | </span>\n <i class=\"fa fa-comment-o\"></i> {{topic.commentCount}}\n <span class=\"info-separator\"> | </span>\n <i class=\"fa fa-clock-o\"></i> <span :title=\"topic.lastCommentAt|formatTime\">{{topic.lastCommentAt|formatTimeAgo}}</span>\n </div>\n <div class=\"topics-topic-admin-tools\" v-if=\"auth.authenticated && auth.user.admin\">\n <a class=\"a-tool\" role=\"button\" @click=\"delTopic(topic.id)\"><i class=\"fa fa-times\" aria-hidden=\"true\"></i> delete topic</a>\n <span class=\"info-separator\"> | </span> \n <router-link :to=\"'/u/' + users[topic.authorId].id\" class=\"a-tool\"><i class=\"fa fa-user\" aria-hidden=\"true\"></i> user profile</router-link>\n </div>\n </div>\n </div>\n </div>\n\n <nav v-if=\"lastPage > 1\">\n <ul class=\"pagination pagination-sm\">\n <li v-for=\"p in pagination\" :class=\"{active: page === p}\">\n <span v-if=\"p === '...'\">\u2026</span>\n <router-link v-if=\"p !== '...'\" :to=\"'/p/' + p\">{{p}}</router-link>\n </li>\n </ul>\n </nav>\n\n </div>\n\n </div>\n `,\n\n props: [\"config\", \"auth\", \"rawConfig\"],\n\n data: function () {\n return {\n topics: [],\n topicsReady: false,\n topicCount: 0,\n users: {},\n usersReady: false,\n error: false,\n };\n },\n\n computed: {\n dbLink: function () {\n return window.API_HOST + '/dbs/' + window.DBID + '/blocks/'\n },\n\n dataReady: function () {\n return this.topicsReady && this.usersReady;\n },\n\n page: function () {\n var page = parseInt(this.$route.params.page, 10);\n if (!page || page < 1) {\n return 1;\n }\n return page;\n },\n\n lastPage: function () {\n if (!this.topicsReady) {\n return 1;\n }\n var p = Math.floor((this.topicCount - 1) / TOPICS_PER_PAGE) + 1;\n if (p < 1) {\n p = 1;\n }\n return p;\n },\n\n pagination: function () {\n if (!this.topicsReady) {\n return [];\n }\n return getPagination(this.page, this.lastPage);\n },\n },\n\n watch: {\n page: function (val) {\n this.load();\n },\n },\n\n created: function () {\n this.load();\n },\n\n methods: {\n load: function () {\n this.topics = [];\n this.topicsReady = false;\n this.topicCount = 0;\n this.users = {};\n this.usersReady = false;\n this.error = false;\n this.getTopics();\n },\n\n getTopics: function () {\n var url = \"api/v1/topics?limit=\" + TOPICS_PER_PAGE;\n if (this.page > 1) {\n var offset = (this.page - 1) * TOPICS_PER_PAGE;\n url += \"&offset=\" + offset;\n }\n this.$http.get(url).then(\n response => {\n this.topics = response.body.topics;\n this.topicCount = response.body.count;\n this.topicsReady = true;\n\n if (this.page > this.lastPage) {\n this.$parent.$router.replace(\"/p/\" + this.lastPage);\n return;\n }\n\n this.getUsers();\n },\n response => {\n this.error = true;\n console.log(\"ERROR: getTopics: \" + JSON.stringify(response.body));\n }\n );\n },\n\n getUsers: function () {\n var url = \"api/v1/users\";\n var ids = [];\n for (var i = 0; i < this.topics.length; i++) {\n ids.push(this.topics[i].authorId);\n }\n ids = ids.filter((v, i, a) => a.indexOf(v) === i);\n if (ids.length === 0) {\n this.users = {};\n this.usersReady = true;\n return;\n }\n url += \"?ids=\" + ids.join(\",\");\n this.$http.get(url).then(\n response => {\n var users = {};\n for (var i = 0; i < response.body.users.length; i++) {\n users[response.body.users[i].id] = response.body.users[i];\n }\n this.users = users;\n this.usersReady = true;\n },\n response => {\n this.error = true;\n console.log(\"ERROR: getUsers: \" + JSON.stringify(response.body));\n }\n );\n },\n\n delTopic: function (id) {\n if (!confirm(\"Are you sure you want to delete topic \" + id + \"?\")) {\n return;\n }\n var url = \"api/v1/topics/\" + id;\n this.$http.delete(url).then(\n response => {\n this.load();\n },\n response => {\n console.log(\"ERROR: delTopic: \" + JSON.stringify(response.body));\n }\n );\n },\n },\n});\n")},
"/frontend/js/bebop-user.js": &fileData{name: "bebop-user.js", mtime: 1556164706, size: 7799, body: []byte("var BebopUser = Vue.component(\"bebop-user\", {\n template: `\n <div class=\"container content-container\">\n\n <div v-if=\"!dataReady\" class=\"loading-info\">\n <div v-if=\"error\" >\n <p class=\"text-danger\">\n Sorry, could not load the user profile. Please check your connection.\n </p>\n <a class=\"btn btn-primary btn-sm\" role=\"button\" @click=\"load\">\n <i class=\"fa fa-refresh\"></i> Try Again\n </a>\n </div>\n <div v-else>\n <i class=\"fa fa-circle-o-notch fa-spin fa-3x fa-fw\"></i>\n </div>\n </div>\n <div v-else>\n\n <h2 v-if=\"isMe\">My profile</h2>\n <h2 v-else>User profile: {{user.name}}</h2>\n\n <div class=\"card user-profile\">\n\n <div class=\"row\">\n <div class=\"col-xs-3\">\n Username\n </div>\n <div class=\"col-xs-6\">\n {{user.name}}\n </div>\n <div class=\"col-xs-3 text-right\">\n <label class=\"btn btn-fix\" :class=\"{'btn-default': isMe, 'btn-danger': !isMe}\" role=\"button\" @click=\"changeUsername()\"><i class=\"fa fa-pencil-square-o\" aria-hidden=\"true\"></i></label>\n </div>\n </div>\n\n <hr>\n\n <div class=\"row\">\n <div class=\"col-xs-3\">\n Avatar\n </div>\n <div class=\"col-xs-6\">\n <div v-if=\"uploadingAvatar\">\n <i class=\"fa fa-circle-o-notch fa-spin fa-2x fa-fw\"></i>\n </div>\n <div v-else>\n <img v-if=\"user.avatar\" class=\"img-circle\" :src=\"user.avatar\" width=\"35\" height=\"35\"> \n <img v-else class=\"img-circle\" src=\"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\" width=\"35\" height=\"35\"> \n </div>\n </div>\n <div class=\"col-xs-3 text-right\">\n <label for=\"avatar-upload-input\" class=\"btn btn-fix\" :class=\"{'btn-default': isMe, 'btn-danger': !isMe}\" role=\"button\">\n <i class=\"fa fa-cloud-upload\"></i>\n </label>\n <input id=\"avatar-upload-input\" class=\"hidden\" type=\"file\" @change=\"uploadAvatar()\"/>\n </div>\n </div>\n <div v-if=\"avatarUploadError\" class=\"row\">\n <div class=\"col-xs-12\">\n <div class=\"alert alert-danger\" style=\"margin-top:10px\">{{avatarUploadError}}</div>\n </div>\n </div>\n\n <hr>\n\n <div class=\"row\">\n <div class=\"col-xs-3\">\n Sign in with\n </div>\n <div class=\"col-xs-6\">\n {{user.authService|capitalize}}\n </div>\n </div>\n\n <hr>\n\n <div class=\"row\">\n <div class=\"col-xs-3\">\n Activated\n </div>\n <div class=\"col-xs-6\">\n {{user.createdAt|formatTime}}\n </div>\n </div>\n \n <hr v-if=\"auth.authenticated && auth.user.admin && !isMe\">\n\n <div v-if=\"auth.authenticated && auth.user.admin && !isMe\" class=\"row\">\n <div class=\"col-xs-3\">\n Blocked\n </div>\n <div class=\"col-xs-6\">\n <span v-if=\"user.blocked\" class=\"text-danger\">Yes</span>\n <span v-else class=\"text-success\">No</span>\n </div>\n <div class=\"col-xs-3 text-right\">\n <button v-if=\"user.blocked\" class=\"btn btn-danger btn-fix\" @click=\"setBlocked(false)\"><i class=\"fa fa-unlock-alt\" aria-hidden=\"true\"></i></button>\n <button v-else class=\"btn btn-danger btn-fix\" @click=\"setBlocked(true)\"><i class=\"fa fa-lock\" aria-hidden=\"true\"></i></button>\n </div>\n </div>\n\n </div>\n </div>\n\n </div>\n `,\n\n props: [\"config\", \"auth\"],\n\n data: function() {\n return {\n user: {},\n userReady: false,\n error: false,\n uploadingAvatar: false,\n avatarUploadError: \"\",\n };\n },\n\n computed: {\n dataReady: function() {\n return this.userReady;\n },\n\n userId: function() {\n if (!this.auth.authenticated) {\n return 0;\n }\n\n var userId = parseInt(this.$route.params.user, 10);\n if (!userId) {\n return this.auth.user.id;\n }\n\n return userId;\n },\n\n isMe: function() {\n if (!this.auth.authenticated) {\n return false;\n }\n return this.userId === this.auth.user.id;\n },\n },\n\n watch: {\n userId: function(val) {\n this.load();\n },\n },\n\n created: function() {\n this.load();\n },\n\n methods: {\n load: function() {\n this.user = {};\n this.userReady = false;\n this.error = false;\n this.uploadingAvatar = false;\n this.avatarUploadError = \"\";\n this.getUser();\n },\n\n getUser: function() {\n if (!this.auth.authenticated) {\n this.$parent.$router.replace(\"/\");\n return;\n }\n\n if (!this.auth.user.admin && this.auth.user.id !== this.userId) {\n this.$parent.$router.replace(\"/me\");\n return;\n }\n\n var url = \"api/v1/me\";\n if (this.auth.user.id !== this.userId) {\n url = \"api/v1/users/\" + this.userId;\n }\n\n this.$http.get(url).then(\n response => {\n this.user = response.body.user;\n this.userReady = true;\n },\n response => {\n console.log(\"ERROR: getUser: \" + JSON.stringify(response.body));\n this.error = true;\n }\n );\n },\n\n changeUsername: function() {\n if (!this.userReady) {\n return;\n }\n this.$parent.$refs.usernameModal.show(this.userId, this.user.name, success => {\n if (success) {\n if (this.isMe) {\n this.$parent.getMe();\n }\n this.load();\n }\n });\n },\n\n uploadAvatar: function() {\n var input = document.getElementById(\"avatar-upload-input\");\n var file = input.files[0];\n input.value = \"\";\n var reader = new FileReader();\n reader.onload = () => {\n var parts = reader.result.split(\";base64,\");\n var imageData = \"\";\n if (parts.length === 2) {\n imageData = parts[1];\n }\n this.putUserAvatar(imageData);\n };\n reader.readAsDataURL(file);\n },\n\n putUserAvatar: function(imageData) {\n if (!this.userReady) {\n return;\n }\n this.uploadingAvatar = true;\n this.avatarUploadError = \"\";\n this.$http.put(\"api/v1/users/\" + this.userId + \"/avatar\", { avatar: imageData }).then(\n response => {\n if (this.isMe) {\n this.$parent.getMe();\n }\n this.uploadingAvatar = false;\n this.load();\n },\n response => {\n console.log(\"ERROR: putUserAvatar: \" + JSON.stringify(response.body));\n this.uploadingAvatar = false;\n var error = \"Sorry, could not upload that image. An error occured.\";\n if (response.body.error && response.body.error.code === \"BadRequest\") {\n error = \"Sorry, could not upload that image. \";\n error += \"Please choose an image from 50x50 to 2000x2000 pixels in size. \";\n error += \"The supported formats are JPEG, PNG, GIF, TIFF, BMP. \";\n error += \"The maximum file size is 5MB.\";\n }\n this.avatarUploadError = error;\n }\n );\n },\n\n setBlocked(val) {\n action = val ? \"block\" : \"unblock\";\n if (!confirm(\"Are you sure you want to \" + action + \" this user?\")) {\n return;\n }\n this.$http.put(\"api/v1/users/\" + this.userId + \"/blocked\", { blocked: val }).then(\n response => {\n this.load();\n },\n response => {\n console.log(\"ERROR: setBlocked: \" + JSON.stringify(response.body));\n }\n );\n },\n },\n});\n")},
"/frontend/js/bebop-username-modal.js": &fileData{name: "bebop-username-modal.js", mtime: 1556164706, size: 3048, body: []byte("var BebopUsernameModal = Vue.component(\"bebop-username-modal\", {\n template: `\n <div class=\"modal fade\" id=\"username-modal\" tabindex=\"-1\" role=\"dialog\" data-backdrop=\"static\">\n <div class=\"modal-dialog\" role=\"document\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h2 class=\"modal-title\">Username</h2>\n </div>\n <div class=\"modal-body\">\n <div style=\"margin-bottom: 15px;\">\n Please choose a username that is between 3 and 20 characters in length and containing only \n alphanumeric characters (letters A-Z, numbers 0-9), hyphens, and underscores.\n </div>\n <div class=\"form-group\">\n <label for=\"user-name\" class=\"form-control-label\">Username:</label>\n <input type=\"text\" class=\"form-control\" id=\"username-modal-input\" v-model=\"name\" @change=\"hideErrorMessage\" @keyup=\"hideErrorMessage\" @keyup.13=\"send\">\n </div>\n <div id=\"username-modal-error\" class=\"alert alert-danger\" :class=\"{hidden: errorMessage===''}\" role=\"alert\" style=\"cursor:pointer\" @click=\"hideErrorMessage\">\n {{errorMessage}}\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\">Cancel</button>\n <button type=\"button\" class=\"btn btn-primary\" id=\"username-modal-ok\" @click=\"send\">OK</button>\n </div>\n </div>\n </div>\n </div>\n `,\n\n data: function() {\n return {\n userId: 0,\n success: false,\n callback: function() {},\n name: \"\",\n errorMessage: \"\",\n };\n },\n\n mounted: function() {\n $(\"#username-modal\").on(\"hidden.bs.modal\", () => {\n this.callback(this.success);\n });\n $(\"#username-modal\").on(\"shown.bs.modal\", () => {\n $(\"#username-modal-input\")[0].focus();\n });\n },\n\n methods: {\n show: function(userId, initialName, callback) {\n this.userId = userId;\n this.success = false;\n this.callback = callback;\n this.errorMessage = \"\";\n this.name = initialName;\n $(\"#username-modal\").modal(\"show\");\n },\n\n send: function() {\n this.$http.put(\"api/v1/users/\" + this.userId + \"/name\", { name: this.name }).then(\n response => {\n this.success = true;\n $(\"#username-modal\").modal(\"hide\");\n },\n response => {\n if (response.data.error && response.data.error.code === \"UnavailableUserName\") {\n this.showErrorMessage(\"Sorry, that username is taken.\");\n } else if (response.data.error && response.data.error.code === \"InvalidUserName\") {\n this.showErrorMessage(\"Invalid username.\");\n } else {\n this.showErrorMessage(\"An error occured.\");\n }\n $(\"#username-modal-input\")[0].focus();\n }\n );\n },\n\n showErrorMessage: function(message) {\n this.errorMessage = message;\n },\n\n hideErrorMessage: function() {\n this.errorMessage = \"\";\n },\n },\n});\n")},
}