From d89bf1ab716a9b9e7972bc5aab9154f9f46bbc5d Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 12 Sep 2019 20:50:31 -0400 Subject: [PATCH] feat: browse tags localization --- client/components/common/nav-header.vue | 184 +++++++++++------------- client/components/editor.vue | 1 + client/components/tags.vue | 47 +++--- package.json | 2 +- yarn.lock | Bin 625883 -> 626318 bytes 5 files changed, 114 insertions(+), 120 deletions(-) diff --git a/client/components/common/nav-header.vue b/client/components/common/nav-header.vue index a9aefd3f..e7cacf11 100644 --- a/client/components/common/nav-header.vue +++ b/client/components/common/nav-header.vue @@ -7,7 +7,7 @@ clearable background-color='deep-purple' color='white' - label='Search...' + :label='$t(`common:header.search`)' single-line solo flat @@ -22,41 +22,19 @@ v-menu(open-on-hover, offset-y, bottom, left, min-width='250', transition='slide-y-transition') template(v-slot:activator='{ on }') v-app-bar-nav-icon.btn-animate-app(v-on='on', :class='$vuetify.rtl ? `mx-0` : ``') - v-icon mdi-menu-open + v-icon mdi-menu v-list(nav, :light='!$vuetify.theme.dark', :dark='$vuetify.theme.dark', :class='$vuetify.theme.dark ? `grey darken-4` : ``') v-list-item.pl-4(href='/') v-list-item-avatar(size='24'): v-icon(color='blue') mdi-home v-list-item-title.body-2 {{$t('common:header.home')}} - v-list-item.pl-4(@click='pageNew', v-if='isAuthenticated') - v-list-item-avatar(size='24'): v-icon(color='green') mdi-file-document-box-plus-outline - v-list-item-title.body-2 {{$t('common:header.newPage')}} - template(v-if='path && path.length') - v-divider.my-0 - .overline.pa-4.grey--text {{$t('common:header.currentPage')}} - v-list-item.pl-4(@click='pageView', v-if='mode !== `view`') - v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-file-document-box-outline - v-list-item-title.body-2 {{$t('common:header.view')}} - v-list-item.pl-4(@click='pageEdit', v-if='mode !== `edit` && isAuthenticated') - v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-file-document-edit-outline - v-list-item-title.body-2 {{$t('common:header.edit')}} - v-list-item.pl-4(@click='pageHistory', v-if='mode !== `history`') - v-list-item-avatar(size='24'): v-icon(color='grey lighten-2') mdi-history - v-list-item-content - v-list-item-title.body-2.grey--text.text--ligten-2 {{$t('common:header.history')}} - v-list-item-subtitle.overline.grey--text.text--lighten-2 Coming soon - v-list-item.pl-4(@click='pageSource', v-if='mode !== `source`') - v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-code-tags - v-list-item-title.body-2 {{$t('common:header.viewSource')}} - v-list-item.pl-4(@click='pageMove', v-if='isAuthenticated') - v-list-item-avatar(size='24'): v-icon(color='grey lighten-2') mdi-content-save-move-outline - v-list-item-content - v-list-item-title.body-2.grey--text.text--ligten-2 {{$t('common:header.move')}} - v-list-item-subtitle.overline.grey--text.text--lighten-2 Coming soon - v-list-item.pl-4(@click='pageDelete', v-if='isAuthenticated') - v-list-item-avatar(size='24'): v-icon(color='red darken-2') mdi-trash-can-outline - v-list-item-title.body-2 {{$t('common:header.delete')}} - v-divider.my-0 - .overline.pa-4.grey--text {{$t('common:header.assets')}} + v-list-item.pl-4(@click='') + v-list-item-avatar(size='24'): v-icon(color='grey lighten-2') mdi-file-tree + v-list-item-content + v-list-item-title.body-2.grey--text.text--ligten-2 {{$t('common:header.siteMap')}} + v-list-item-subtitle.overline.grey--text.text--lighten-2 Coming soon + v-list-item.pl-4(href='/t') + v-list-item-avatar(size='24'): v-icon(color='teal') mdi-tag-multiple + v-list-item-title.body-2 {{$t('common:header.browseTags')}} v-list-item.pl-4(@click='assets') v-list-item-avatar(size='24'): v-icon(color='grey lighten-2') mdi-folder-multiple-image v-list-item-content @@ -88,89 +66,98 @@ @keyup.down='searchMove(`down`)' @keyup.up='searchMove(`up`)' ) - //- v-menu( - //- v-model='searchAdvMenuShown' - //- left - //- offset-y - //- min-width='450' - //- :close-on-content-click='false' - //- nudge-bottom='7' - //- nudge-right='5' - //- v-if='searchIsShown' - //- ) - //- template(v-slot:activator='{ on }') - //- v-btn.nav-header-search-adv(icon, color='grey darken-2', v-on='on') - //- v-icon(color='white') mdi-chevron-down - //- v-card.radius-0(dark) - //- v-toolbar(flat, color='grey darken-4', dense) - //- v-icon.mr-2 mdi-feature-search-outline - //- v-subheader.pl-0 Advanced Search - //- v-spacer - //- v-chip(label, small, color='primary') Coming soon - //- v-card-text.pa-4 - //- v-checkbox.mt-0( - //- label='Restrict to current language' - //- color='white' - //- v-model='searchRestrictLocale' - //- hide-details - //- ) - //- v-checkbox( - //- label='Search below current path only' - //- color='white' - //- v-model='searchRestrictPath' - //- hide-details - //- ) - //- v-divider - //- v-card-actions.grey.darken-3-d4 - //- v-container.pa-0(grid-list-md) - //- v-layout(row) - //- v-flex(xs6) - //- v-btn(depressed, color='grey darken-3', block) - //- v-icon(left) mdi-chevron-right - //- span Save as defaults - //- v-flex(xs6) - //- v-btn(depressed, color='grey darken-3', block) - //- v-icon(left) mdi-cached - //- span Reset - v-tooltip(bottom, v-if='isAuthenticated && isAdmin') + v-tooltip(bottom) template(v-slot:activator='{ on }') v-btn.ml-2.mr-0(icon, v-on='on', href='/t') v-icon(color='grey') mdi-tag-multiple - span Browse Tags + span {{$t('common:header.browseTags')}} v-flex(xs6, md4) v-toolbar.nav-header-inner.pr-4(color='black', dark, flat) v-spacer .navHeaderLoading.mr-3 v-progress-circular(indeterminate, color='blue', :size='22', :width='2' v-show='isLoading') + slot(name='actions') + + //- (mobile) SEARCH TOGGLE + v-btn( v-if='!hideSearch && $vuetify.breakpoint.smAndDown' @click='searchToggle' icon ) v-icon(color='grey') mdi-magnify - v-menu(offset-y, left, transition='slide-y-transition', v-if='mode === `view` && locales.length > 0') + + //- LANGUAGES + + template(v-if='mode === `view` && locales.length > 0') + v-menu(offset-y, bottom, nudge-bottom='30', transition='slide-y-transition') + template(v-slot:activator='{ on: menu }') + v-tooltip(bottom) + template(v-slot:activator='{ on: tooltip }') + v-btn(icon, v-on='{ ...menu, ...tooltip }', :class='$vuetify.rtl ? `ml-3` : ``', tile, height='64') + v-icon(color='grey') mdi-web + span {{$t('common:header.language')}} + v-list(nav) + template(v-for='(lc, idx) of locales') + v-list-item(@click='changeLocale(lc)') + v-list-item-action: v-chip(:color='lc.code === locale ? `blue` : `grey`', small, label, dark) {{lc.code.toUpperCase()}} + v-list-item-title {{lc.name}} + v-divider(vertical) + + //- PAGE ACTIONS + + template(v-if='isAuthenticated && path') + v-menu(offset-y, bottom, nudge-bottom='30', transition='slide-y-transition') + template(v-slot:activator='{ on: menu }') + v-tooltip(bottom) + template(v-slot:activator='{ on: tooltip }') + v-btn(icon, v-on='{ ...menu, ...tooltip }', :class='$vuetify.rtl ? `ml-3` : ``', tile, height='64') + v-icon(color='grey') mdi-file-document-edit-outline + span {{$t('common:header.pageActions')}} + v-list(nav, :light='!$vuetify.theme.dark', :dark='$vuetify.theme.dark', :class='$vuetify.theme.dark ? `grey darken-4` : ``') + .overline.pa-4.grey--text {{$t('common:header.currentPage')}} + v-list-item.pl-4(@click='pageView', v-if='mode !== `view`') + v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-file-document-box-outline + v-list-item-title.body-2 {{$t('common:header.view')}} + v-list-item.pl-4(@click='pageEdit', v-if='mode !== `edit` && isAuthenticated') + v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-file-document-edit-outline + v-list-item-title.body-2 {{$t('common:header.edit')}} + v-list-item.pl-4(@click='pageHistory', v-if='mode !== `history`') + v-list-item-avatar(size='24'): v-icon(color='grey lighten-2') mdi-history + v-list-item-content + v-list-item-title.body-2.grey--text.text--ligten-2 {{$t('common:header.history')}} + v-list-item-subtitle.overline.grey--text.text--lighten-2 Coming soon + v-list-item.pl-4(@click='pageSource', v-if='mode !== `source`') + v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-code-tags + v-list-item-title.body-2 {{$t('common:header.viewSource')}} + v-list-item.pl-4(@click='pageMove', v-if='isAuthenticated') + v-list-item-avatar(size='24'): v-icon(color='grey lighten-2') mdi-content-save-move-outline + v-list-item-content + v-list-item-title.body-2.grey--text.text--ligten-2 {{$t('common:header.move')}} + v-list-item-subtitle.overline.grey--text.text--lighten-2 Coming soon + v-list-item.pl-4(@click='pageDelete', v-if='isAuthenticated') + v-list-item-avatar(size='24'): v-icon(color='red darken-2') mdi-trash-can-outline + v-list-item-title.body-2 {{$t('common:header.delete')}} + v-divider(vertical) + + //- NEW PAGE + + template(v-if='isAuthenticated') + v-tooltip(bottom) + template(v-slot:activator='{ on }') + v-btn(icon, tile, height='64', v-on='on', @click='pageNew') + v-icon(color='grey') mdi-file-document-box-plus-outline + span {{$t('common:header.newPage')}} + v-divider(vertical) + + //- ACCOUNT + + v-menu(v-if='isAuthenticated', offset-y, bottom, nudge-bottom='30', min-width='300', transition='slide-y-transition') template(v-slot:activator='{ on: menu }') v-tooltip(bottom) template(v-slot:activator='{ on: tooltip }') - v-btn(icon, v-on='{ ...menu, ...tooltip }', :class='$vuetify.rtl ? `ml-3` : ``') - v-icon(color='grey') mdi-web - span {{$t('common:header.language')}} - v-list(nav) - template(v-for='(lc, idx) of locales') - v-list-item(@click='changeLocale(lc)') - v-list-item-action: v-chip(:color='lc.code === locale ? `blue` : `grey`', small, label, dark) {{lc.code.toUpperCase()}} - v-list-item-title {{lc.name}} - v-tooltip(bottom, v-if='isAuthenticated && isAdmin') - template(v-slot:activator='{ on }') - v-btn.btn-animate-rotate(icon, href='/a', v-on='on', :class='$vuetify.rtl ? `ml-3` : ``') - v-icon(color='grey') mdi-settings - span {{$t('common:header.admin')}} - v-menu(v-if='isAuthenticated', offset-y, min-width='300', left, transition='slide-y-transition') - template(v-slot:activator='{ on: menu }') - v-tooltip(bottom) - template(v-slot:activator='{ on: tooltip }') - v-btn(icon, v-on='{ ...menu, ...tooltip }', :class='$vuetify.rtl ? `ml-0` : ``') + v-btn(icon, v-on='{ ...menu, ...tooltip }', :class='$vuetify.rtl ? `ml-0` : ``', tile, height='64') v-icon(v-if='picture.kind === `initials`', color='grey') mdi-account-circle v-avatar(v-else-if='picture.kind === `image`', :size='34') v-img(:src='picture.url') @@ -195,6 +182,9 @@ v-list-item-content v-list-item-title {{$t('common:header.profile')}} v-list-item-subtitle.overline Coming soon + v-list-item(href='/a', v-if='isAuthenticated && isAdmin') + v-list-item-action.btn-animate-rotate: v-icon(color='blue-grey') mdi-settings + v-list-item-title.blue-grey--text {{$t('common:header.admin')}} v-list-item(@click='logout') v-list-item-action: v-icon(color='red') mdi-logout v-list-item-title.red--text {{$t('common:header.logout')}} diff --git a/client/components/editor.vue b/client/components/editor.vue index 8cefed1e..7dc9c388 100644 --- a/client/components/editor.vue +++ b/client/components/editor.vue @@ -31,6 +31,7 @@ ) v-icon(color='red', :left='$vuetify.breakpoint.lgAndUp') mdi-close span.white--text(v-if='$vuetify.breakpoint.lgAndUp') {{ $t('common:actions.close') }} + v-divider(vertical) v-content component(:is='currentEditor', :save='save') editor-modal-properties(v-model='dialogProps') diff --git a/client/components/tags.vue b/client/components/tags.vue index e0d0a20f..fa3439bf 100644 --- a/client/components/tags.vue +++ b/client/components/tags.vue @@ -18,7 +18,7 @@ v-content.grey(:class='$vuetify.theme.dark ? `darken-4-d5` : `lighten-3`') v-toolbar(color='primary', dark, flat, height='58') template(v-if='selection.length > 0') - .overline.mr-3.animated.fadeInLeft Current Selection + .overline.mr-3.animated.fadeInLeft {{$t('tags:currentSelection')}} v-chip.mr-3.primary--text( v-for='tag of tagsSelected' :key='`tagSelected-` + tag.tag' @@ -35,14 +35,14 @@ @click='selection = []' ) v-icon(left) mdi-close - span Clear Selection + span {{$t('tags:clearSelection')}} template(v-else) v-icon.mr-3.animated.fadeInRight mdi-arrow-left - .overline.animated.fadeInRight Select one or more tags + .overline.animated.fadeInRight {{$t('tags:selectOneMoreTags')}} v-toolbar(:color='$vuetify.theme.dark ? `grey darken-4-l5` : `grey lighten-4`', flat, height='58') v-text-field.tags-search( v-model='innerSearch' - label='Search within results...' + :label='$t(`tags:searchWithinResultsPlaceholder`)' solo hide-details flat @@ -55,13 +55,13 @@ ) template(v-if='locales.length > 1') v-divider.mx-3(vertical) - .overline Locale + .overline {{$t('tags:locale')}} v-select.ml-2( :items='locales' v-model='locale' :background-color='$vuetify.theme.dark ? `grey darken-3` : `white`' hide-details - label='Locale' + :label='$t(`tags:locale`)' item-text='name' item-value='code' rounded @@ -71,13 +71,13 @@ style='max-width: 170px;' ) v-divider.mx-3(vertical) - .overline Order By + .overline {{$t('tags:orderBy')}} v-select.ml-2( :items='orderByItems' v-model='orderBy' :background-color='$vuetify.theme.dark ? `grey darken-3` : `white`' hide-details - label='Order By' + :label='$t(`tags:orderBy`)' rounded single-line dense @@ -90,7 +90,7 @@ v-divider .text-center.pt-10(v-if='selection.length < 1') img(src='/svg/icon-price-tag.svg') - .subtitle-2.grey--text Select one or more tags on the left. + .subtitle-2.grey--text {{$t('tags:selectOneMoreTagsHint')}} .px-5.py-2(v-else) v-data-iterator( :items='pages' @@ -109,15 +109,15 @@ size='96' width='2' ) - .subtitle-2.grey--text.mt-5 Retrieving page results... + .subtitle-2.grey--text.mt-5 {{$t('tags:retrievingResultsLoading')}} template(v-slot:no-data) .text-center.pt-10 img(src='/svg/icon-info.svg') - .subtitle-2.grey--text Couldn't find any page with the selected tags. + .subtitle-2.grey--text {{$t('tags:noResults')}} template(v-slot:no-results) .text-center.pt-10 img(src='/svg/icon-info.svg') - .subtitle-2.grey--text Couldn't find any page matching the current filtering options. + .subtitle-2.grey--text {{$t('tags:noResultsWithFilter')}} template(v-slot:default='props') v-row(align='stretch') v-col( @@ -135,7 +135,8 @@ .d-flex.flex-row.align-center .body-1: strong.primary--text {{item.title}} v-spacer - .caption Last updated {{item.updatedAt | moment('from')}} + i18next.caption(tag='div', path='tags:pageLastUpdated') + span(place='date') {{item.updatedAt | moment('from')}} .body-2.grey--text {{item.description || '---'}} v-divider.my-2 .d-flex.flex-row.align-center @@ -165,6 +166,7 @@ const router = new VueRouter({ }) export default { + i18nOptions: { namespaces: 'tags' }, data() { return { tags: [], @@ -173,13 +175,6 @@ export default { locale: 'any', locales: [], orderBy: 'title', - orderByItems: [ - { text: 'Creation Date', value: 'createdAt' }, - { text: 'ID', value: 'id' }, - { text: 'Last Modified', value: 'updatedAt' }, - { text: 'Path', value: 'path' }, - { text: 'Title', value: 'title' } - ], orderByDirection: 0, pagination: { page: 1, @@ -223,6 +218,15 @@ export default { }, pageTotal () { return Math.ceil(this.pages.length / this.pagination.itemsPerPage) + }, + orderByItems () { + return [ + { text: this.$t('tags:orderByField.creationDate'), value: 'createdAt' }, + { text: this.$t('tags:orderByField.ID'), value: 'id' }, + { text: this.$t('tags:orderByField.lastModified'), value: 'updatedAt' }, + { text: this.$t('tags:orderByField.path'), value: 'path' }, + { text: this.$t('tags:orderByField.title'), value: 'title' } + ] } }, watch: { @@ -243,7 +247,7 @@ export default { this.$store.commit('page/SET_MODE', 'tags') this.locales = _.concat( - [{name: 'Any', code: 'any'}], + [{name: this.$t('tags:localeAny'), code: 'any'}], (siteLangs.length > 0 ? siteLangs : []) ) @@ -257,7 +261,6 @@ export default { this.selection.push(tag) } this.rebuildURL() - console.info(this.$refs.dude) }, isSelected (tag) { return _.includes(this.selection, tag) diff --git a/package.json b/package.json index 37837e94..10b7121e 100644 --- a/package.json +++ b/package.json @@ -182,7 +182,7 @@ "@babel/preset-env": "^7.6.0", "@mdi/font": "4.3.95", "@panter/vue-i18next": "0.15.1", - "@requarks/ckeditor5": "12.4.0-wiki.11", + "@requarks/ckeditor5": "12.4.0-wiki.14", "@vue/babel-preset-app": "3.11.0", "animate-sass": "0.8.2", "animated-number-vue": "1.0.0", diff --git a/yarn.lock b/yarn.lock index 003925c8b44cb9be682cc5f6e4a17a521405663e..f93fcda9ab540536dc194f12fcac2b9b5736a55a 100644 GIT binary patch delta 1519 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