branch bow-v2-go updated (e804d12 -> 38b4e52)
This is an automated email from the git hooks/post-receive script. New change to branch bow-v2-go in repository bow. See https://gitlab.nuiton.org/chorem/bow.git from e804d12 Agencement un peu meilleur pour l'edition new 38b4e52 introduction de vuex pour avoir un etat partage sur la recherche en cours ajout nuage de tags ajout indication de navigation The 1 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "adds" were already present in the repository and have only been added to this reference. Detailed log of new commits: commit 38b4e5283009de65ce9d668af5306cd25e912e27 Author: Benjamin <poussin@codelutin.com> Date: Wed May 20 03:40:35 2020 +0200 introduction de vuex pour avoir un etat partage sur la recherche en cours ajout nuage de tags ajout indication de navigation Summary of changes: pkg/repository/bookmarkRepository.go | 40 ++++++--- web/package.json | 3 +- web/src/App.vue | 4 +- web/src/components/CloudTags.vue | 59 +++++++++++++ web/src/components/layout/Header.vue | 12 +-- web/src/components/layout/Sidebar.vue | 153 +++++++++++++++++----------------- web/src/main.js | 10 ++- web/src/router/index.js | 12 +-- web/src/store/index.js | 22 +++++ web/src/views/Home.vue | 17 +++- web/src/views/Login.vue | 17 ++-- web/yarn.lock | 5 ++ 12 files changed, 229 insertions(+), 125 deletions(-) create mode 100644 web/src/components/CloudTags.vue create mode 100644 web/src/store/index.js -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.
This is an automated email from the git hooks/post-receive script. New commit to branch bow-v2-go in repository bow. See https://gitlab.nuiton.org/chorem/bow.git commit 38b4e5283009de65ce9d668af5306cd25e912e27 Author: Benjamin <poussin@codelutin.com> Date: Wed May 20 03:40:35 2020 +0200 introduction de vuex pour avoir un etat partage sur la recherche en cours ajout nuage de tags ajout indication de navigation --- pkg/repository/bookmarkRepository.go | 40 ++++++--- web/package.json | 3 +- web/src/App.vue | 4 +- web/src/components/CloudTags.vue | 59 +++++++++++++ web/src/components/layout/Header.vue | 12 +-- web/src/components/layout/Sidebar.vue | 153 +++++++++++++++++----------------- web/src/main.js | 10 ++- web/src/router/index.js | 12 +-- web/src/store/index.js | 22 +++++ web/src/views/Home.vue | 17 +++- web/src/views/Login.vue | 17 ++-- web/yarn.lock | 5 ++ 12 files changed, 229 insertions(+), 125 deletions(-) diff --git a/pkg/repository/bookmarkRepository.go b/pkg/repository/bookmarkRepository.go index 64eac14..8e1db09 100644 --- a/pkg/repository/bookmarkRepository.go +++ b/pkg/repository/bookmarkRepository.go @@ -15,6 +15,9 @@ import ( /* BookmarkJSON retourne le bookmark au format json +si id est non vide alors fait une recherche exact sur l'id +si uri est non vide alors fait une recherche exact sur l'uri +sinon fait une recherche sur les critères: tags, fulltext */ func BookmarkJSON(currentUser model.BowUser, id string, uri string, tags string, fulltext string, orderBy string, orderDesc bool, first int) (string, error) { var result string @@ -45,9 +48,20 @@ func BookmarkJSON(currentUser model.BowUser, id string, uri string, tags string, result, err = q.QueryString(currentUser, uri) } else { tagsJSON := "{" + strings.Join(strings.Fields(tags), ",") + "}" - q := &query{sql: fmt.Sprintf(` - WITH __all AS (select * from bookmark where tags @> $1::text[] order by %s %s OFFSET %d LIMIT %d) SELECT json_agg(__all.*) as j FROM __all - `, orderBy, orderDirection, first, maxResult)} + q := &query{sql: fmt.Sprintf(`WITH + __query AS (select * from bookmark where tags @> $1::text[]), + __orderby AS (select '%[1]s' as orderby), + __orderdesc AS (select to_json('%[2]s' = 'desc') as orderdesc), + __first AS (select %[3]d as first), + __limit AS (select %[4]d as limit), + __total AS (select count(id) as total from __query), + __allTags AS (select unnest(tags) as tag from __query), + __tags AS (select tag, count(tag) from __allTags group by tag order by 2 desc), + __jsonTags AS (select ('[' || json_agg(a.tag) || ', ' || json_agg(a.count) || ']')::json as tags from __tags a), + __result AS (select * from __query order by %[1]s %[2]s OFFSET %[3]d LIMIT %[4]d), + __jsonResult AS (SELECT json_agg(r.*) as result FROM __result r) + select row_to_json(v) from (select * from __first, __limit, __total, __orderby, __orderdesc, __jsonTags, __jsonResult) v; + `, orderBy, orderDirection, first, maxResult)} result, err = q.QueryString(currentUser, tagsJSON) } @@ -115,7 +129,7 @@ func URIsFromAliasJSON(currentUser model.BowUser, alias string) (string, error) if err != nil { return "", utils.NewHTTPError500(err, currentUser) } - + return result, nil } @@ -130,15 +144,15 @@ func URIFromAlias(currentUser model.BowUser, alias string) (string, error) { if err != nil { return "", utils.NewHTTPError500(err, currentUser) } - - if result == "" { - // s'il n'y a pas de resultat recherche dans les alias public - q = &query{asNobody: true, sql: `select uri from bookmark where publicAlias && $1::text[];`} - result, err = q.QueryString(currentUser, arg) - if err != nil { - return "", utils.NewHTTPError500(err, currentUser) - } - } + + if result == "" { + // s'il n'y a pas de resultat recherche dans les alias public + q = &query{asNobody: true, sql: `select uri from bookmark where publicAlias && $1::text[];`} + result, err = q.QueryString(currentUser, arg) + if err != nil { + return "", utils.NewHTTPError500(err, currentUser) + } + } return result, nil } diff --git a/web/package.json b/web/package.json index 2bb54f5..ae2d420 100644 --- a/web/package.json +++ b/web/package.json @@ -13,7 +13,8 @@ "vue": "^2.6.11", "vue-property-decorator": "^8.4.1", "vue-router": "^3.1.6", - "vue-select": "^3.9.5" + "vue-select": "^3.9.5", + "vuex": "^3.4.0" }, "devDependencies": { "@vue/cli-plugin-babel": "~4.3.0", diff --git a/web/src/App.vue b/web/src/App.vue index daf25ae..4b2d485 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -28,7 +28,7 @@ class App extends Vue { let old = document.getElementById(linkId) old && old.remove() - let token = this.$store.getCookie('bow-token') + let token = this.$storage.getCookie('bow-token') if (token) { // <link title="bow" type="application/opensearchdescription+xml" rel="search" href="<%= VUE_APP_BOW_PUBLIC_API_URL %>/api/v1/opensearch"> let link = document.createElement('link') @@ -44,7 +44,7 @@ class App extends Vue { beforeMount() { console.log('beforeMounted App') this.insertOpenSearch() - this.$store.addListener('bow-user', this.insertOpenSearch, this) + this.$storage.addListener('bow-user', this.insertOpenSearch, this) } } export default App diff --git a/web/src/components/CloudTags.vue b/web/src/components/CloudTags.vue new file mode 100644 index 0000000..8bf9d7b --- /dev/null +++ b/web/src/components/CloudTags.vue @@ -0,0 +1,59 @@ +<template> + <div class="cloud-tags"> + <span class="tag" v-for="(tag, i) in tags" :key="tag" :style="{fontSize: getFont(counts[i]) + 'px'}">{{ tag }}</span> + </div> +</template> + +<script> +import { Component, Vue } from 'vue-property-decorator' + +@Component({ + name: 'CloudTags', + components: {} +}) +class CloudTags extends Vue { + + get user() { + return this.$storage.get('bow-user') + } + + get tags() { + return this.$store.state.tags[0].slice(0, this.user.maxtagincloud) + } + + get counts() { + return this.$store.state.tags[1] + } + + get tmax() { + return this.counts[0] || 1 + } + + get tmin() { + return this.counts[this.counts.length - 1] || 0 + } + + getFont(count) { + let result = 30 * (count - this.tmin) / (this.tmax - this.tmin) + if (result < 10) { + result = 10 + } + + return result + } +} + +export default CloudTags +</script> + +<style lang="less" scoped> +.cloud-tags { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; +} +.tag { + margin: 4px; +} +</style> \ No newline at end of file diff --git a/web/src/components/layout/Header.vue b/web/src/components/layout/Header.vue index b7efaf2..221ffbe 100644 --- a/web/src/components/layout/Header.vue +++ b/web/src/components/layout/Header.vue @@ -41,26 +41,26 @@ class Header extends Vue { this.$fetch .delete(`/users/${this.user.id}/auth`) .then(() => this.loadData()) - .then(() => this.$store.delete('bow-user')) + .then(() => this.$storage.delete('bow-user')) .then(() => this.$router.push({ name: 'Login' })) } loadData() { console.log('loadData Header') - this.user = this.$store.get('bow-user') || {} - this.authenticated = !!this.$store.getCookie('bow-token') + this.user = this.$storage.get('bow-user') || {} + this.authenticated = !!this.$storage.getCookie('bow-token') } beforeMount() { console.log('beforeMounted Header') this.loadData() - this.$store.addListener('bow-user', this.loadData, this) + this.$storage.addListener('bow-user', this.loadData, this) } // beforeUpdate() { // console.log('beforeUpdate Header') - // this.user = this.$store.get('bow-user') - // this.token = this.$store.getCookie('bow-token') + // this.user = this.$storage.get('bow-user') + // this.token = this.$storage.getCookie('bow-token') // } } diff --git a/web/src/components/layout/Sidebar.vue b/web/src/components/layout/Sidebar.vue index 4b75544..13d7c22 100644 --- a/web/src/components/layout/Sidebar.vue +++ b/web/src/components/layout/Sidebar.vue @@ -1,7 +1,7 @@ <template> <div class="sidebar"> <div class="sidebar-logo"> - <a href="/"><img src="../../assets/img/logos/bow.svg" alt="Bow Logo" /></a> + <a href="/"><img src="../../assets/img/logos/bow.svg" alt="Bow Logo"/></a> </div> <div class="sidebar-menu"> <div class="sidebar-menu-link"> @@ -38,108 +38,107 @@ class Sidebar extends Vue { loadData() { console.log('loadData Header') - this.user = this.$store.get('bow-user') || {} + this.user = this.$storage.get('bow-user') || {} } beforeMount() { this.statsUrl = this.$fetch.createUrl('/system/stats') this.loadData() - this.$store.addListener('bow-user', this.loadData, this) + this.$storage.addListener('bow-user', this.loadData, this) } - } export default Sidebar </script> <style scoped lang="less"> - .sidebar { - position: fixed; - left: 0; +.sidebar { + position: fixed; + left: 0; + + width: var(--sidebar-width); + height: 100%; + /* FIXME LK : TMP */ + overflow: hidden; +} - width: var(--sidebar-width); - height: 100%; - /* FIXME LK : TMP */ overflow: hidden; - } +.sidebar-logo { + display: flex; + justify-content: center; + position: fixed; - .sidebar-logo { - display: flex; - justify-content: center; - position: fixed; + width: var(--sidebar-width); + height: var(--header-height); + padding: 20px; + + text-align: center; + + background-color: #fff; + border-right: 1px solid #eee; + + font-size: 36px; +} + +.sidebar-menu { + display: flex; + flex-direction: column; + justify-content: space-between; + + height: 100%; + padding-top: var(--header-height); + + background-color: var(--color-bg-sidebar); +} - width: var(--sidebar-width); - height: var(--header-height); - padding: 20px; +.sidebar-menu-link { + display: flex; + justify-content: center; + align-items: center; - text-align: center; + position: relative; + flex: 1; + max-height: 150px; - background-color: #FFF; - border-right: 1px solid #EEE; + text-align: center; - font-size: 36px; + .icon { + color: var(--color-white); + font-size: 30px; } + a, + span { + width: 100%; - .sidebar-menu { + z-index: 10; + } + a { display: flex; - flex-direction: column; - justify-content: space-between; + align-items: center; + justify-content: center; height: 100%; - padding-top: var(--header-height); - - background-color: var(--color-bg-sidebar); } + &:hover .icon { + color: #fff; + } + &:before { + content: ''; + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 0; - .sidebar-menu-link { - display: flex; - justify-content: center; - align-items: center; + background-color: var(--color-hover); + opacity: 0; - position: relative; - flex: 1; - max-height: 150px; - - text-align: center; - - .icon { - color: var(--color-white); - font-size: 30px; - } - a, - span { - width: 100%; - - z-index: 10; - } - a { - display: flex; - align-items: center; - justify-content: center; - - height: 100%; - } - &:hover .icon { - color: #FFF; - } - &:before{ - content: ''; - position: absolute; - top: 0; - left: 0; - - height: 100%; - width: 0; - - background-color: var(--color-hover); - opacity: 0; - - transition: width 0.4s ease-in-out, opacity 0.4s ease-in-out; - } - &:hover:before { - width: 100%; - - opacity: 1; - } + transition: width 0.4s ease-in-out, opacity 0.4s ease-in-out; } + &:hover:before { + width: 100%; + + opacity: 1; + } +} </style> diff --git a/web/src/main.js b/web/src/main.js index 3d471b5..4a0ecfe 100644 --- a/web/src/main.js +++ b/web/src/main.js @@ -4,6 +4,7 @@ import './class-component-hooks' import Vue from 'vue' import App from './App.vue' import router from './router' +import store from './store' import FetchHelper from '@/utils/FetchHelper.js' import StoreHelper from './utils/Store' @@ -13,17 +14,18 @@ window.FRONTEND_URL = process.env.BASE_URL // register utilities function on window, Vue and Vue instance if (typeof window !== 'undefined') { window.$fetch = FetchHelper - window.$store = StoreHelper + window.$storage = StoreHelper } Vue.$fetch = FetchHelper -Vue.$store = StoreHelper +Vue.$storage = StoreHelper Vue.config.productionTip = false Vue.prototype.$fetch = FetchHelper -Vue.prototype.$store = StoreHelper +Vue.prototype.$storage = StoreHelper new Vue({ router, - render: h => h(App) + store, + render: (h) => h(App) }).$mount('#app') diff --git a/web/src/router/index.js b/web/src/router/index.js index 437a8e0..29185ad 100644 --- a/web/src/router/index.js +++ b/web/src/router/index.js @@ -28,7 +28,7 @@ const routes = [ meta: { requiresAuth: true }, - props: route => ({ + props: (route) => ({ id: route.query.id, tags: route.query.tags, fulltext: route.query.fulltext, @@ -45,7 +45,7 @@ const routes = [ meta: { requiresAuth: true }, - props: route => ({ + props: (route) => ({ id: route.params.id, uri: route.query.uri, description: route.query.description, @@ -61,8 +61,7 @@ const routes = [ // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. - component: () => - import(/* webpackChunkName: "about" */ '../views/About.vue') + component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') } ] @@ -73,10 +72,7 @@ const router = new VueRouter({ }) router.beforeEach((to, from, next) => { - if ( - to.matched.some(record => record.meta.requiresAuth) && - !window.$store.getCookie('bow-token') - ) { + if (to.matched.some((record) => record.meta.requiresAuth) && !window.$storage.getCookie('bow-token')) { next({ path: '/login', params: { nextUrl: to.fullPath } diff --git a/web/src/store/index.js b/web/src/store/index.js new file mode 100644 index 0000000..bbbbc16 --- /dev/null +++ b/web/src/store/index.js @@ -0,0 +1,22 @@ +import Vue from 'vue' +import Vuex from 'vuex' + +Vue.use(Vuex) + +export default new Vuex.Store({ + state: { + user: {}, + tags: [[],[]], + result: [], + size: 0 + }, + getters: {}, + mutations: { + queryResponse(state, payload) { + state.tags = payload.tags + state.result = payload.result + state.size = payload.size + } + }, + actions: {} +}) diff --git a/web/src/views/Home.vue b/web/src/views/Home.vue index 404de4c..a6008e9 100644 --- a/web/src/views/Home.vue +++ b/web/src/views/Home.vue @@ -1,6 +1,12 @@ <template> <div class="home"> <div>{{ errorMsg }}</div> + <CloudTags></CloudTags> + <div class="navigation"> + <button><<</button> <button><</button> + {{ 1 + queryResponse.first }}-{{ queryResponse.first + queryResponse.limit }} + <button>></button> <button>>></button> / {{ queryResponse.total }} + </div> <div class="bookmarks-list"> <Bookmark v-for="bookmark in bookmarks" @@ -15,11 +21,12 @@ // @ is an alias to /src import { Component, Prop, Vue } from 'vue-property-decorator' import Bookmark from '@/components/Bookmark' +import CloudTags from '@/components/CloudTags' @Component({ name: 'Home', props: ['id', 'tags', 'fulltext', 'query', 'orderby', 'orderdesc', 'first'], - components: { Bookmark } + components: { CloudTags, Bookmark } }) class Home extends Vue { @Prop id @@ -38,7 +45,10 @@ class Home extends Vue { mFirst = this.first || 0 errorMsg = '' - bookmarks = [] + queryResponse = {} + get bookmarks() { + return this.queryResponse.result + } fetchBookmark() { let searchParams = new URLSearchParams() @@ -52,7 +62,8 @@ class Home extends Vue { this.$fetch.get(`/bookmarks?${searchParams.toString()}`).then( data => { - this.bookmarks = data + this.queryResponse = data + this.$store.commit('queryResponse', data) }, err => { this.errorMsg = err.cause diff --git a/web/src/views/Login.vue b/web/src/views/Login.vue index acac899..8d647bc 100644 --- a/web/src/views/Login.vue +++ b/web/src/views/Login.vue @@ -5,12 +5,7 @@ <div class="form-row"> <label for="login">Login</label> - <input - v-model="data.email" - type="text" - name="login" - placeholder="somebody@somewhere.world" - /> + <input v-model="data.email" type="text" name="login" placeholder="somebody@somewhere.world" /> </div> <div class="form-row"> @@ -47,17 +42,17 @@ class Login extends Vue { login() { this.$fetch.post('/users/auth', this.data).then( - user => { + (user) => { console.log('ok', user) - this.$store.set('bow-user', user) + this.$storage.set('bow-user', user) - if(this.$route.params.nextUrl != null){ + if (this.$route.params.nextUrl != null) { this.$router.push(this.$route.params.nextUrl) } else { - this.$router.push({ name: 'Home'}) + this.$router.push({ name: 'Home' }) } }, - err => { + (err) => { console.log('ko', err) this.errorMsg = err.cause } diff --git a/web/yarn.lock b/web/yarn.lock index f9561a4..06c9027 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -8029,6 +8029,11 @@ vue@^2.6.10, vue@^2.6.11: resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35..." integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ== +vuex@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.4.0.tgz#20cc086062d750769fce1febb..." + integrity sha512-ajtqwEW/QhnrBZQsZxCLHThZZaa+Db45c92Asf46ZDXu6uHXgbfVuBaJ4gzD2r4UX0oMJHstFwd2r2HM4l8umg== + watchpack@^1.6.0: version "1.6.1" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.1.tgz#280da0a87185921..." -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.
participants (1)
-
chorem.org scm