1
1
mirror of https://github.com/aelve/guide.git synced 2024-12-22 20:31:31 +03:00

Front/chore/migrate to vuetify 2.0 (#348)

* Vuetify updated

* Correct component creation in dialog mixins

* Vuetify 2.0 migration

* Conflict dialog content style fixed

* Removed duplicate attribute
This commit is contained in:
avele 2019-08-03 06:18:26 +04:00 committed by GitHub
parent 41c3523323
commit 9e4146b5bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 2669 additions and 1955 deletions

View File

@ -4,18 +4,12 @@ module.exports = {
targets: {
browsers: ['last 3 versions', '> 2%', 'ie >= 10', 'Firefox >= 30', 'Chrome >= 30']
},
modules: false,
loose: true,
useBuiltIns: 'entry'
modules: 'commonjs',
useBuiltIns: 'usage'
}]
],
plugins: [
'@babel/plugin-transform-runtime',
'@babel/plugin-syntax-dynamic-import'
],
env: {
test: {
plugins: ['transform-es2015-modules-commonjs']
}
}
]
}

View File

@ -2,6 +2,7 @@ const path = require('path')
const { DefinePlugin } = require('webpack')
const { VueLoaderPlugin } = require('vue-loader')
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
const VuetifyLoaderPlugin = require('vuetify-loader/lib/plugin')
const isProduction = process.env.NODE_ENV === 'production'
@ -50,14 +51,13 @@ const config = {
test: /\.js$/,
use: {
loader: 'babel-loader'
}
},
// exclude: /node_modules\/(?!(vuetify)\/).*/
exclude: /(node_modules)/
},
{
test: /\.ts$/,
use: [
{
loader: 'babel-loader'
},
{
loader: 'ts-loader',
options: {
@ -70,9 +70,6 @@ const config = {
{
test: /\.tsx$/,
use: [
{
loader: 'babel-loader'
},
{
loader: 'ts-loader',
options: {
@ -125,32 +122,16 @@ const config = {
]
},
{
test: /\.scss$/,
test: /\.s(c|a)ss$/,
use: [
{
loader: 'vue-style-loader',
options: {
sourceMap: false,
shadowMode: false
}
},
{
loader: 'css-loader',
options: {
sourceMap: false,
importLoaders: 2
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: false
}
},
'vue-style-loader',
'css-loader',
{
loader: 'sass-loader',
options: {
sourceMap: false
implementation: require('sass'),
fiber: require('fibers'),
indentedSyntax: true // optional
}
}
]
@ -188,7 +169,8 @@ const config = {
new FriendlyErrorsWebpackPlugin(),
new DefinePlugin({
NODE_ENV: isProduction ? "'production'" : "'development'"
})
}),
new VuetifyLoaderPlugin()
]
}

View File

@ -9,11 +9,10 @@ const isProduction = process.env.NODE_ENV === 'production'
const webpackConfig = merge(baseConfig, {
mode: isProduction ? 'production' : 'development',
target: 'node',
devtool: isProduction ? false : 'source-map',
entry: path.resolve(__dirname, '../client/entry.server.ts'),
devtool: isProduction ? false : 'source-map',
target: 'node',
output: {
filename: 'server-bundle.js',
libraryTarget: 'commonjs2'
},
externals: nodeExternals({
@ -21,7 +20,8 @@ const webpackConfig = merge(baseConfig, {
/\.css$/,
/\.sass$/,
/\.scss$/,
/\.svg$/
/\.svg$/,
/vuetify\/.*/
]
}),

View File

@ -162,12 +162,15 @@ code.sourceCode {
.sourceCode:not(:last-child) code.sourceCode {
margin: 0 0 0.2rem;
}
a {
a,
/* So much selectors is required to overwrite vuetify styles */
.v-application .app-content a {
text-decoration-line: none;
color: #0061c0;
}
a:hover {
text-decoration-line: underline;
color: #005ebd;
&:hover {
text-decoration-line: underline;
}
}
.text-transform-none {
text-transform: none !important;
@ -212,23 +215,23 @@ blockquote {
margin-right: 0;
border-radius: 5px;
}
/* vuetify v-select component when opened almost overlays its title */
.v-menu__content {
margin-top: 5px;
}
.svg-inline--fa {
width: 1.25em !important;
text-align: center !important;
}
.v-toolbar__title {
.v-toolbar__title {
letter-spacing: inherit;
}
/* Some useless and obstructive div that vuetify add along with "v-menu" component */
.v-menu--inline {
display: none;
}
</style>
<style scoped>
/* Should be same padding on any screen cause it changes with toolbar and we fixed toolbar's height (see Toolbar.vue) height on each screen */
.app-content {
padding: 64px 0px 0px !important;
padding: 100px 0px 36px !important;
line-height: 150%;
}
</style>

View File

@ -1,16 +1,10 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import Vuex from 'vuex'
import Vuetify from 'vuetify'
import vuetify from 'client/plugins/vuetify'
import { sync } from 'vuex-router-sync'
import ALink from 'client/components/ALink.vue'
import confirmDialogMixin from 'client/mixins/confirmDialogMixin'
import 'vuetify/dist/vuetify.css'
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { fas } from '@fortawesome/free-solid-svg-icons'
import { faSquare } from '@fortawesome/free-regular-svg-icons'
library.add(fas, faSquare)
import 'client/assets/code-highlight.css'
@ -18,27 +12,11 @@ import AppComponent from './App.vue'
import { createRouter } from './router'
import { createStore } from './store'
const icons = {}
// TODO import and add only used icons for production
Object.values({ ...fas, faSquare }).forEach(({ prefix, iconName }) => {
icons[iconName] = {
component: 'font-awesome-icon',
props: {
icon: [prefix, iconName]
}
}
})
function initVue () {
Vue.use(VueRouter)
Vue.use(Vuex)
Vue.mixin(confirmDialogMixin)
Vue.component('ALink', ALink)
Vue.component('font-awesome-icon', FontAwesomeIcon)
Vue.use(Vuetify, {
iconfont: 'faSvg',
icons
})
}
function createApp () {
@ -50,6 +28,7 @@ function createApp () {
const app = new Vue({
router,
store,
vuetify,
render: h => h(AppComponent)
})

View File

@ -56,7 +56,7 @@ export default class AFooter extends Vue { }
display: inline-block;
}
.footer {
margin-right: -10px;
margin: 0 -10px 0 0;
> * {
margin-right: 10px;

View File

@ -1,79 +1,94 @@
<template>
<div>
<v-dialog
lazy
:value="value"
@input="close"
max-width="500px"
<v-dialog
:value="value"
@input="close"
max-width="500px"
>
<template
v-if="$slots.activator"
v-slot:activator="{ on }"
>
<slot
slot="activator"
v-on="on"
/>
</template>
<slot slot="activator" />
<v-card @keyup.esc.native="close" tabindex="0">
<v-card-text>
<v-form
ref="form"
lazy-validation
v-model="isValid"
@keydown.native.enter="submit"
>
<!-- v-if="value" - cause without it autofocus triggers on first modal open
https://stackoverflow.com/questions/51472947/vuetifys-autofocus-works-only-on-first-modal-open -->
<v-text-field
v-if="value"
class="mb-3"
label="Category name"
autofocus
:rules="categoryValidationRules"
v-model="categoryName"
ref="categoryNameInput"
/>
<v-text-field
v-model="groupNameInternal"
label="Group"
/>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
flat
title="Cancel"
color="primary"
@click.native="close"
>
Cancel
</v-btn>
<v-btn
title="Create"
color="info"
class="add-category-submit-btn"
:disabled="!isValid || !categoryName"
@click.native="submit"
>
Create
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-card>
<v-card-text>
<v-form
ref="form"
v-model="isValid"
@keydown.native.enter="submit"
>
<!-- v-if="value" - cause without it autofocus triggers on first modal open
https://stackoverflow.com/questions/51472947/vuetifys-autofocus-works-only-on-first-modal-open -->
<v-text-field
v-if="value"
autofocus
ref="categoryNameInput"
class="mb-3"
label="Category name"
:rules="categoryValidationRules"
v-model="categoryName"
/>
<v-text-field
v-model="groupNameInternal"
label="Group"
/>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
text
title="Cancel"
color="primary"
@click.native="close"
>
Cancel
</v-btn>
<v-btn
title="Submit"
color="info"
class="add-category-submit-btn"
:disabled="!isValid"
@click.native="submit"
>
Submit
</v-btn>
</v-card-actions>
</v-card>
<!--
`eager` prop is provided here because dialog doesn't get focus on first render without it
which leads to inability of closing it with `esc` btn
TODO fix, when issue is fixed in vuetify https://github.com/vuetifyjs/vuetify/issues/8220
-->
<ConfirmDialog
v-if="isDuplicateConfirmShow"
:value="isDuplicateConfirmShow"
max-width="500px"
attach="#app"
eager
ref="duplicateConfirm"
max-width="500px"
:value="isDuplicateConfirmShow"
>
This group already has categories with the same name:
<router-link
v-for="category in sameNameCategories"
:key="category.id"
:to="`/haskell/${getCategoryUrl(category)}`"
target="_blank"
>{{ category.title }}</router-link>
<ul class="duplicate-categories-list">
<li
v-for="category in sameNameCategories"
:key="category.id"
>
<router-link
v-for="category in sameNameCategories"
:key="category.id"
:to="`/haskell/${getCategoryUrl(category)}`"
target="_blank"
>{{ category.title }}</router-link>>
</li>
</ul>
</ConfirmDialog>
</div>
</v-dialog>
</template>
<script lang="ts">
@ -140,6 +155,7 @@ export default class AddCategoryDialog extends Vue {
title: this.categoryName,
group: this.groupNameInternal
})
this.close()
this.$router.push(`haskell/${createdId}`)
}
@ -148,9 +164,14 @@ export default class AddCategoryDialog extends Vue {
this.isDuplicateConfirmShow = true
this.$nextTick(() => {
const duplicateConfirm = this.$refs.duplicateConfirm
duplicateConfirm.$once('canceled', () => {
promise.resolve(false)
this.isDuplicateConfirmShow = false
// when duplicateConfirm dialog closed it automatically sets focus on <body>
// and we focus on name input for: so that user could change category name and so that he could push esc to close dialog
this.$nextTick(() => this.$refs.categoryNameInput.focus())
})
duplicateConfirm.$once('confirmed', () => {
promise.resolve(true)
@ -164,5 +185,20 @@ export default class AddCategoryDialog extends Vue {
}
</script>
<style>
<style lang="postcss" scoped>
.duplicate-categories-list {
display: inline;
margin: 0;
padding: 0;
> li {
list-style-type: none;
display: inline;
&:not(:last-child):after {
content: ", ";
}
}
}
</style>

View File

@ -1,29 +1,34 @@
<template>
<v-dialog
lazy
:value="value"
@input="close"
@keyup.esc.native="close"
max-width="500px"
>
<slot slot="activator" />
<template
v-if="$slots.activator"
v-slot:activator="{ on }"
>
<slot
slot="activator"
v-on="on"
/>
</template>
<v-card>
<v-card-text>
<v-form
lazy-validation
ref="form"
v-model="isValid"
@keydown.native.prevent.enter="submit"
ref="form"
>
<!-- v-if="value" - cause without it autofocus triggers on first modal open
https://stackoverflow.com/questions/51472947/vuetifys-autofocus-works-only-on-first-modal-open -->
<!-- reason for "v-if" - see AddCategoryDialog.vue template-->
<v-text-field
v-if="value"
autofocus
class="mb-2"
label="Item name"
:rules="itemValidationRules"
:rules="nameValidationRules"
v-model="name"
/>
<v-text-field
@ -42,7 +47,7 @@
<v-card-actions>
<v-spacer />
<v-btn
flat
text
title="Cancel"
color="primary"
@click.native="close"
@ -51,8 +56,8 @@
</v-btn>
<v-btn
color="info"
:disabled="!isValid"
title="Create"
:disabled="!isValid || !name"
@click.native="submit"
>
Create
@ -73,13 +78,15 @@ export default class AddItemDialog extends Vue {
@Prop(String) categoryId!: string
name: string = ''
itemValidationRules: Array<(x: string) => boolean | string> = [
nameValidationRules: Array<(x: string) => boolean | string> = [
(x: string) => !!x || 'Item name can not be empty'
]
hackage: string = ''
link: string = ''
isValid: boolean = false
// TODO create mixin or external dialog component which reset data on new open,
// cause this code is duplicated in another dialog components (AddCategoryDialog)
@Watch('value')
onOpen () {
this.name = ''

View File

@ -51,13 +51,13 @@
</template>
<v-btn
flat
text
class="ma-0 mt-1 px-1"
color="grey darken-2"
title="Add new category"
@click="openAddCategoryDialog(groupName)"
>
<v-icon size="14" class="mr-1" left>$vuetify.icons.plus</v-icon>
<v-icon size="14" class="mr-1" left v-text="'$vuetify.icons.plus'"></v-icon>
Add new category
</v-btn>
</div>
@ -69,6 +69,9 @@
v-model="isAddGroupDialogOpen"
:groupName="addCategoryGroupName"
/>
<!-- <v-dialog v-model="isAddGroupDialogOpen">
</v-dialog> -->
</div>
</template>
@ -125,7 +128,8 @@ export default class Categories extends Vue {
<style lang="postcss" scoped>
.categories {
margin-top: 30px;
/* Bellow we use negative margins for flex container and it creates unwanted horizontal scroll bar */
overflow-x: hidden;
}
.categories-flex-container {
display: flex;
@ -185,20 +189,23 @@ export default class Categories extends Vue {
word-break: break-word;
}
.category-title {
line-height: 1.2;
font-size: 0.9rem;
font-weight: 600;
}
.category-title:not(:first-child) {
margin-top: 5px;
}
.category-title,
.status-title {
line-height: 1.2;
}
.status-title {
font-size: 0.9rem;
margin: 4px 0 0 8px;
line-height: 1.7;
margin: 6px 0 4px 8px;
+ .category-title {
margin-top: 0;
}
}
.group-title + * {
margin-top: 0;
}
</style>

View File

@ -34,7 +34,7 @@
/>
</template>
<v-btn
flat
text
class="ml-2"
color="grey darken-2"
title="Add new item"

View File

@ -16,8 +16,7 @@
</a-link>
</template>
<span>RSS feed for all new items in this category</span>
</v-tooltip>{{categoryTitle}}
</h1>
</v-tooltip>{{categoryTitle}}</h1>
<div class="category-header__second-row">
<div class="category-group-title-wrap">
@ -47,7 +46,7 @@
<v-menu bottom left offset-y>
<template v-slot:activator="{ on }">
<v-btn
flat
text
icon
title="Actions"
class="category-actions-menu-btn"
@ -164,15 +163,15 @@ export default class CategoryHeader extends Vue {
letter-spacing: -1px;
}
.rss-link {
/* For vertical aligning on one line with category title */
font-size: 1px;
margin-right: 6px;
/* Space beetwen category rss ling title */
.rss-link:after {
content: " ";
visibility: hidden;
}
.rss-link-icon {
height: calc(1.8rem - 5px) !important;
width: auto;
vertical-align: bottom;
&:hover {
color: #000;
@ -198,8 +197,10 @@ export default class CategoryHeader extends Vue {
}
.category-actions-menu-btn {
margin: 0;
display: none;
margin: 0;
width: 36px;
height: 36px;
}
.category-actions {

View File

@ -1,9 +1,9 @@
<template>
<v-btn
flat
text
class="category-header-btn"
:title="text"
color="grey darken-2"
:title="text"
v-bind="$attrs"
v-on="$listeners"
>
@ -25,9 +25,10 @@ export default class CategoryHeaderBtn extends Vue {
</script>
<style scoped>
.category-header-btn {
margin: 0;
/* .v-btn selector added for more specificity to override vuetify styles without using !important*/
.v-btn.category-header-btn {
padding: 0 4px;
font-size: 0.8rem;
font-weight: bold;
}
</style>

View File

@ -1,20 +1,30 @@
<template>
<v-dialog
lazy
:value="value"
@input="close"
max-width="500px"
>
<slot slot="activator" />
<template
v-if="$slots.activator"
v-slot:activator="{ on }"
>
<slot
slot="activator"
v-on="on"
/>
</template>
<v-card>
<v-card-text>
<v-form
lazy-validation
v-model="isValid"
@keydown.native.prevent.ctrl.enter="updateCategoryInfo"
>
<!-- reason for "v-if" - see AddCategoryDialog.vue template-->
<v-text-field
v-if="value"
autofocus
class="mb-2"
label="Title"
@ -22,47 +32,53 @@
v-model="title"
/>
<v-text-field
autofocus
class="mb-2"
label="Group"
:rules="inputValidationRules"
v-model="group"
/>
<v-select
:items="categoryStatuses"
item-text="name"
item-value="value"
:menu-props="{ offsetY: true, closeOnClick: true }"
:items="categoryStatuses"
v-model="categoryStatus"
label="Status"
class="mb-2"
/>
<v-checkbox
:inputValue="checkboxSections"
@click.native.capture.prevent.stop="updateSectionEnabling('ItemProsConsSection', 'Pros/Cons')"
hide-details
color="info"
class="category-info-edit__checkbox"
label="Pros/cons section"
value="ItemProsConsSection"
class="mt-0 hide-v-messages"
:inputValue="checkboxSections"
@click.native.capture.prevent.stop="updateSectionEnabling('ItemProsConsSection', 'Pros/Cons')"
/>
<v-checkbox
:inputValue="checkboxSections"
@click.native.capture.prevent.stop="updateSectionEnabling('ItemEcosystemSection', 'Ecosystem')"
hide-details
color="info"
class="category-info-edit__checkbox"
label="Ecosystem section"
value="ItemEcosystemSection"
class="mt-0 hide-v-messages"
:inputValue="checkboxSections"
@click.native.capture.prevent.stop="updateSectionEnabling('ItemEcosystemSection', 'Ecosystem')"
/>
<v-checkbox
:inputValue="checkboxSections"
@click.native.capture.prevent.stop="updateSectionEnabling('ItemNotesSection', 'Notes')"
hide-details
color="info"
class="category-info-edit__checkbox"
label="Notes section"
value="ItemNotesSection"
class="mt-0 hide-v-messages"
:inputValue="checkboxSections"
@click.native.capture.prevent.stop="updateSectionEnabling('ItemNotesSection', 'Notes')"
/>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
flat
text
title="Cancel"
color="primary"
@click.native="close"
@ -177,9 +193,17 @@ export default class CategoryInfoEdit extends Vue {
}
</script>
<style scoped>
>>> .hide-v-messages .v-messages {
display: none;
<style lang="postcss" scoped>
.category-info-edit__checkbox >>> {
.v-input--selection-controls__input {
margin-right: 12px;
}
/* We using fontawesome icons which turned out to be bigger than vuetify default icons
and checkbox icon gets some strange offset if overflow is visible */
svg {
overflow: hidden;
}
}
</style>

View File

@ -67,7 +67,7 @@
<v-btn
small
dark
round
rounded
title="Expand"
class="mx-0"
@click="expandNotes"
@ -77,7 +77,7 @@
<v-btn
small
dark
round
rounded
title="Collapse"
class="mx-0"
@click="collapseNotes"
@ -211,14 +211,15 @@ export default class CategoryItem extends Vue {
</script>
<style lang="postcss" scoped>
.category-item-body {
padding: 15px 20px;
}
.category-item {
position: relative;
background: #eeeeee;
margin: 0 0 40px;
border-radius: 4px;
}
.category-item-body {
padding: 15px 20px;
}
.category-item-traits {

View File

@ -1,12 +1,13 @@
<template>
<v-btn
:icon="!!icon && !showTitle"
flat
class="ma-0 font-weight-bold"
color="grey darken-2"
:style="style"
:title="title"
v-bind="$attrs"
class="font-weight-bold"
v-bind="{
color: 'grey darken-2',
icon: !!icon && !showTitle,
style,
title,
...$attrs
}"
v-on="$listeners"
>
<v-icon

View File

@ -1,5 +1,5 @@
<template>
<div class="category-item-section">
<section class="category-item-section">
<h3 class="category-item-section__title title font-weight-bold mb-1 mt-0">
{{ title }}
<category-item-btn
@ -22,7 +22,7 @@
@cancel="toggleEdit"
@save="save"
/>
</div>
</section>
</template>
<script lang="ts">
@ -80,6 +80,9 @@ export default class CategoryItemSection extends Vue {
<style scoped>
.category-item-section {
max-width: 100%;
}
.category-item-section__title {
display: flex;
align-items: center;

View File

@ -1,15 +1,9 @@
<template>
<v-expansion-panel class="category-item-toolbar">
<v-expansion-panel-content
hide-actions
:value="isEditItemInfoMenuOpen"
>
<div class="category-item-toolbar">
<div class="category-item-toolbar__header">
<v-toolbar
flat
slot="header"
color="#dedede"
class="elevation-2"
@click.stop=""
>
<v-toolbar-title class="text-h2">
<span class="category-item-toolbar-title">
@ -26,7 +20,6 @@
openInNewTab
>{{ itemName }}</a-link>
<span class="category-item-name" v-else>{{ itemName }}</span>
<div class="category-item-badges">
<a
v-if="this.itemHackage"
@ -36,8 +29,7 @@
>
<v-icon
color="#fff"
class="mr-1"
size="12"
size="10"
>$vuetify.icons.link</v-icon>hackage</a>
</div>
</div>
@ -49,18 +41,21 @@
<v-toolbar-items>
<div class="category-item-toolbar-btns">
<category-item-btn
size="40px"
iconSize="18"
title="Move item up"
icon="arrow-up"
@click="moveItem('up')"
/>
<category-item-btn
size="40px"
iconSize="18"
title="Move item down"
icon="arrow-down"
@click="moveItem('down')"
/>
<category-item-btn
size="40px"
iconSize="18"
title="Edit item info"
icon="cog"
@ -75,6 +70,7 @@
</category-item-btn>
<category-item-btn
size="40px"
iconSize="18"
title="Delete item"
icon="trash-alt"
@ -85,8 +81,8 @@
<v-menu bottom left offset-y>
<template v-slot:activator="{ on }">
<v-btn
flat
icon
small
title="Actions"
class="category-toolbar-mobile-menu-btn"
v-on="on"
@ -98,27 +94,33 @@
</v-btn>
</template>
<v-list class="category-item-toolbar-mobile-menu-list">
<v-list-tile>
<v-list class="category-item-toolbar__mobile-menu-list">
<v-list-item>
<category-item-btn
block
text
showTitle
iconSize="18"
title="Move item up"
icon="arrow-up"
@click="moveItem('up')"
/>
</v-list-tile>
<v-list-tile>
</v-list-item>
<v-list-item>
<category-item-btn
block
text
showTitle
iconSize="18"
title="Move item down"
icon="arrow-down"
@click="moveItem('down')"
/>
</v-list-tile>
<v-list-tile>
</v-list-item>
<v-list-item>
<category-item-btn
block
text
showTitle
iconSize="18"
title="Edit item info"
@ -132,21 +134,29 @@
size="8"
>$vuetify.icons.circle</v-icon>
</category-item-btn>
</v-list-tile>
<v-list-tile>
</v-list-item>
<v-list-item>
<category-item-btn
block
text
showTitle
iconSize="18"
title="Delete item"
icon="trash-alt"
@click="deleteItem"
/>
</v-list-tile>
</v-list-item>
</v-list>
</v-menu>
</v-toolbar-items>
</v-toolbar>
</div>
<div
class="category-item-toolbar__expanding-content"
:class="{ 'category-item-toolbar__expanding-content_expanded': isEditItemInfoMenuOpen }"
:aria-hidden="!isEditItemInfoMenuOpen"
>
<v-layout column class="pa-3">
<v-flex>
<v-form @keydown.native.enter.ctrl="updateItemInfo">
@ -166,6 +176,7 @@
</v-flex>
<v-flex align-self-end>
<v-btn
class="mr-1"
title="Cancel"
@click="resetAndToggleEditItemInfoMenu"
>
@ -173,7 +184,6 @@
</v-btn>
<v-btn
color="info"
class="mr-0"
title="Save"
:disabled="!isInfoSaveEnabled"
@click="updateItemInfo"
@ -182,8 +192,8 @@
</v-btn>
</v-flex>
</v-layout>
</v-expansion-panel-content>
</v-expansion-panel>
</div>
</div>
</template>
<script lang="ts">
@ -286,53 +296,40 @@ export default class CategoryItemToolbar extends Vue {
</script>
<style lang="postcss" scoped>
.category-item-toolbar {
display: flex;
margin: 0;
box-shadow: none;
/* TODO move expanding panel to external component */
.category-item-toolbar__expanding-content {
max-height: 0;
overflow: hidden;
background: #d6d6d6;
transition: max-height 0.2s ease-in-out;
}
.category-item-toolbar__expanding-content_expanded {
max-height: 300px;
}
.category-item-toolbar__header >>> {
.v-toolbar__content {
justify-content: space-between;
/* Vuetify sets inline height style */
height: auto !important;
align-items: flex-start;
padding: 8px 16px;
>>> {
.v-toolbar__content {
justify-content: space-between;
height: auto !important;
min-height: 56px;
.v-toolbar__title {
flex-wrap: wrap;
flex: 1;
overflow: visible;
padding: 12px 0;
}
.spacer {
display: none;
}
.v-toolbar__items {
height: 100%;
margin-top: 10px;
align-self: baseline;
margin-left: 5px;
}
@media screen and (max-width: 959px) {
padding: 0 8px;
}
.spacer {
display: none;
}
.v-expansion-panel__header {
padding: 0;
align-items: center;
cursor: unset;
.v-toolbar__items {
margin-left: 15px;
}
.v-expansion-panel__body {
background: rgb(222, 222, 222);
@media screen and (max-width: 959px) {
padding: 8px;
}
}
}
.category-item-toolbar-title {
display: inline-flex;
display: flex;
line-height: 1;
}
.category-item-anchor {
color: rgb(151, 151, 151);
@ -343,22 +340,35 @@ export default class CategoryItemToolbar extends Vue {
.category-item-name {
white-space: pre-line;
word-break: break-word;
/* A gap beetwen category item name and badges */
@media (min-width: 768px) {
&:after {
content: " ";
visibility: hidden;
}
}
}
.category-item-badges {
display: inline-block;
margin-left: 8px;
display: inline-flex;
align-items: center;
vertical-align: bottom;
> * {
vertical-align: middle;
display: inline-flex;
align-items: center;
padding: 0.1px 5px;
line-height: 1;
border-radius: 5px;
padding: 4px 6px;
}
.hackage-link {
background: #5e5184;
color: #fff;
svg {
margin-right: 2px;
}
}
}
.unsaved-changes-icon {
@ -371,42 +381,59 @@ export default class CategoryItemToolbar extends Vue {
display: flex;
align-items: center;
flex: 1;
> * {
width: 1.6rem !important;
height: 1.6rem !important;
}
> *:not(:last-child) {
margin-right: 6px;
}
}
.category-toolbar-mobile-menu-btn {
display: none;
/* Somewhy vuetify sets important "height: 100%"" for direct child buttons of toolbar */
height: 1.6rem !important;
width: 1.6rem !important;
margin: 0;
}
.category-item-toolbar-mobile-menu-list {
>>> .v-list__tile {
.category-item-toolbar__mobile-menu-list {
>>> .v-list-item {
height: 36px;
padding: 0 6px;
}
padding: 0;
>>> button {
width: 100%;
padding: 5px;
button {
height: 100% !important;
padding: 0 6px;
.v-btn__content {
justify-content: flex-start;
.v-btn__content {
justify-content: flex-start;
}
}
}
}
@media (max-width: 768px) {
.category-toolbar-mobile-menu-btn {
display: block;
display: flex;
}
.category-item-toolbar-btns {
display: none;
}
.category-item-badges {
display: block;
margin: 5px 0 0 0;
display: flex;
flex-wrap: wrap;
margin: 6px 0 0 0;
}
.unsaved-changes-icon {
right: unset;
left: 13px;
}
>>> .v-toolbar__items {
margin-left: 5px;
align-self: baseline;
}
}
</style>

View File

@ -13,63 +13,59 @@
class="position-relative category-item-trait"
>
<div
v-html="trait.content.html"
v-if="!trait.isEdit"
/>
<template v-if="!trait.isEdit">
<div v-html="trait.content.html" />
<v-menu
v-if="!trait.isEdit"
lazy
offset-y
nudge-bottom="2"
class="category-item-edit-trait-menu"
>
<category-item-btn
slot="activator"
size="22px"
title="Actions"
iconSize="14"
icon="ellipsis-v"
/>
<v-list dense>
<v-list-tile
tag="div"
@click="moveTrait(trait, 'up')"
>
<v-list-tile-title>
Move up
</v-list-tile-title>
</v-list-tile>
<v-menu bottom left offset-y>
<template v-slot:activator="{ on }">
<category-item-btn
class="trait-actions-menu-btn"
size="22px"
title="Actions"
iconSize="14"
icon="ellipsis-v"
v-on='on'
/>
</template>
<v-list dense>
<v-list-item
tag="div"
@click="moveTrait(trait, 'up')"
>
<v-list-item-title>
Move up
</v-list-item-title>
</v-list-item>
<v-list-tile
tag="div"
@click="moveTrait(trait, 'down')"
>
<v-list-tile-title>
Move down
</v-list-tile-title>
</v-list-tile>
<v-list-item
tag="div"
@click="moveTrait(trait, 'down')"
>
<v-list-item-title>
Move down
</v-list-item-title>
</v-list-item>
<v-list-tile
tag="div"
@click="trait.isEdit = true"
>
<v-list-tile-title>
Edit
</v-list-tile-title>
</v-list-tile>
<v-list-item
tag="div"
@click="trait.isEdit = true"
>
<v-list-item-title>
Edit
</v-list-item-title>
</v-list-item>
<v-list-tile
tag="div"
@click="deleteTrait(trait)"
>
<v-list-tile-title>
Delete
</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
<v-list-item
tag="div"
@click="deleteTrait(trait)"
>
<v-list-item-title>
Delete
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</template>
<markdown-editor
v-if="trait.isEdit"
@ -208,12 +204,12 @@ export default class CategoryItemTraits extends Vue {
.category-item-trait {
padding-right: 24px;
}
.category-item-edit-trait-menu {
.trait-actions-menu-btn {
position: absolute;
top: 0;
right: 0;
}
>>> .v-list__tile {
>>> .v-list-item {
padding: 0 8px;
}
</style>

View File

@ -1,27 +1,38 @@
<!-- Universal confirmation dialog, just pass text and function in Props -->
<template>
<v-dialog
lazy
:value="value"
:attach="attach"
max-width="500px"
@input="close"
v-bind="$attrs"
@input="cancel"
>
<slot slot="activator" />
<template
v-if="$slots.activator"
v-slot:activator="{ on }"
>
<slot
slot="activator"
v-on="on"
/>
</template>
<v-card>
<v-card-text v-if="$slots.default">
<slot />
</v-card-text>
<v-card-text v-else>
{{ fullText || `Are you sure you want to ${text}?` }}
<v-card-text class="confirm-dialog__text">
<slot v-if="$slots.default"/>
<template v-else>
{{ fullText || `Are you sure you want to ${text}?` }}
</template>
</v-card-text>
<v-divider />
<v-card-actions>
<v-spacer />
<v-btn
v-bind="{
flat: true,
text: true,
color:'primary',
title: cancelBtnText,
...cancelBtnProps
@ -50,7 +61,9 @@ import Vue from 'vue'
import Component from 'vue-class-component'
import { Prop } from 'vue-property-decorator'
@Component
@Component({
inheritAttrs: false
})
export default class ConfirmDialog extends Vue {
@Prop(String) text!: string
@Prop(String) fullText!: string
@ -63,7 +76,6 @@ export default class ConfirmDialog extends Vue {
close () {
this.$emit('input', false)
this.$emit('canceled')
}
confirm () {
this.$emit('confirmed')
@ -75,3 +87,12 @@ export default class ConfirmDialog extends Vue {
}
}
</script>
<style scoped>
.v-card__text.confirm-dialog__text {
font-size: 1rem;
line-height: inherit;
color: #000;
}
</style>

View File

@ -1,10 +1,18 @@
<template>
<v-dialog
lazy
persistent
:value="value"
>
<slot slot="activator" />
<template
v-if="$slots.activator"
v-slot:activator="{ on }"
>
<slot
slot="activator"
v-on="on"
/>
</template>
<div class="conflict-box">
<div class="conflict-item">
@ -135,6 +143,10 @@ export default class ConflictDialog extends Vue {
}
.v-card__text {
word-break: break-word;
font-size: 1rem;
line-height: inherit;
letter-spacing: normal;
color: #000;
}
}
}

View File

@ -16,33 +16,31 @@
<v-toolbar
flat
height="30"
height="auto"
color="#e5e5e5"
class="pa-2 markdown-editor__bottom-toolbar"
class="markdown-editor__bottom-toolbar"
v-if="bottomToolbar"
v-show="editor"
>
<span class="markdown-editor-save-tip">
{{ saveTip }}
</span>
<v-toolbar-items>
<v-btn
small
title="Save"
class="mr-2 text-transform-none"
@click="save"
>
Save
</v-btn>
<v-btn
small
class="mr-2"
title="Cancel"
class="text-transform-none"
@click="cancel"
>
Cancel
</v-btn>
<v-btn
color="info"
title="Save"
@click="save"
>
Save
</v-btn>
</v-toolbar-items>
<span class="markdown-editor-save-tip ml-1">
{{ saveTip }}
</span>
</v-toolbar>
</div>
</template>
@ -266,14 +264,25 @@ export default class MarkdownEditor extends Vue {
border-bottom: 1px solid #ddd;
}
.markdown-editor__bottom-toolbar {
border: 1px solid #bbb !important;
border-top: none !important;
}
>>> .v-toolbar__content {
padding: 0;
}
.markdown-editor-save-tip {
font-size: 11px;
line-height: 14px;
>>> {
.v-toolbar__content {
padding: 8px;
justify-content: flex-end;
}
.v-toolbar__items > .v-btn {
height: 26px !important;
border-radius: 4px;
}
}
.markdown-editor-save-tip {
font-size: 11px;
line-height: 14px;
margin-right: 5px;
@media screen and (max-width: 425px) {
display: none;
}
}
}
</style>

View File

@ -2,6 +2,7 @@
<v-text-field
dark
solo
hide-details
class="toolbar-search"
label="Search in all pages"
:value="searchInput"
@ -36,10 +37,5 @@ export default class SearchField extends Vue {
</script>
<style scoped>
.toolbar-search >>> div {
margin-bottom: 0 !important;
}
.toolbar-search >>> label {
font-size: 18px;
}
</style>

View File

@ -1,17 +1,21 @@
<template>
<v-toolbar dark app>
<v-app-bar
dark
app
color="#232323"
>
<v-toolbar-title>
<logo
:class="{ 'mobile-hidden': !isSearchFieldHidden }"
/>
<logo :class="{ 'mobile-hidden': !isSearchFieldHidden }"/>
</v-toolbar-title>
<v-spacer></v-spacer>
<search-field
:class="{ 'mobile-hidden': isSearchFieldHidden }"
ref="searchField"
/>
<v-btn
flat
text
icon
title="Search"
color="#fff"
@ -20,7 +24,7 @@
>
<v-icon size="20">$vuetify.icons.search</v-icon>
</v-btn>
</v-toolbar>
</v-app-bar>
</template>
<script lang="ts">
@ -28,7 +32,6 @@ import Vue from 'vue'
import SearchField from 'client/components/SearchField.vue'
import Logo from 'client/components/Logo.vue'
import Component from 'vue-class-component'
import axios from 'axios'
@Component({
components: {
@ -52,9 +55,6 @@ export default class Toolbar extends Vue {
>>> .v-toolbar__content {
height: 64px !important;
}
.mobile-hidden {
display: block;
}
.mobile-displayed {
display: none;
}
@ -63,7 +63,7 @@ export default class Toolbar extends Vue {
display: none;
}
.mobile-displayed {
display: block;
display: inline-flex;
}
}
</style>

View File

@ -62,6 +62,9 @@ function getComponentAndItsChildren (component, result?) {
if (!result) {
result = []
}
if (!component.options) {
return result
}
if (!result.includes(component)) {
result.push(component)
}

View File

@ -33,11 +33,11 @@ export default class ConfirmDialogMixin extends Vue {
cancelBtnText,
confirmBtnProps,
cancelBtnProps
}
},
parent: this
})
instance.$mount()
const deferredPromise = new DeferredPromise()
this.$el.appendChild(instance.$el)
instance.$on('confirmed', () => {
instance.$destroy()
deferredPromise.resolve(true)
@ -46,6 +46,8 @@ export default class ConfirmDialogMixin extends Vue {
instance.$destroy()
deferredPromise.resolve(false)
})
this.$el.appendChild(instance.$el)
return deferredPromise
}
}

View File

@ -20,15 +20,16 @@ export default class ConflictDialogMixin extends Vue {
serverModified,
modified,
merged
}
},
parent: this
})
instance.$mount()
const deferredPromise = new DeferredPromise()
this.$el.appendChild(instance.$el)
instance.$on('save', (newVal) => {
instance.$destroy()
deferredPromise.resolve(newVal)
})
this.$el.appendChild(instance.$el)
return deferredPromise
}

View File

@ -49,7 +49,7 @@
<div
v-else
class="text-md-center display-1"
class="text-center display-1"
>
Nothing found
</div>

View File

@ -0,0 +1,34 @@
import Vue from 'vue'
import Vuetify from 'vuetify/lib'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { library } from '@fortawesome/fontawesome-svg-core'
import { fas } from '@fortawesome/free-solid-svg-icons'
import { faSquare, faCheckSquare } from '@fortawesome/free-regular-svg-icons'
library.add(fas, faSquare, faCheckSquare)
const icons = {}
const addIcon = ({ prefix, iconName }, iconUsageName = iconName) => {
icons[iconUsageName] = {
component: 'font-awesome-icon',
props: {
icon: [prefix, iconName]
}
}
}
// TODO import and add only used icons for production
Object.values({ ...fas, faSquare, faCheckSquare }).forEach(x => addIcon(x))
// This is for vuetify v-checkbox, v-select components, which uses by default icons with such names
addIcon(faCheckSquare, 'checkboxOn')
addIcon(faSquare, 'checkboxOff')
addIcon(fas.faCaretDown, 'dropdown')
Vue.component('font-awesome-icon', FontAwesomeIcon)
Vue.use(Vuetify)
export default new Vuetify({
icons: {
values: icons
}
})

3602
front/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -28,67 +28,73 @@
]
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.17",
"@fortawesome/free-regular-svg-icons": "^5.8.2",
"@fortawesome/free-solid-svg-icons": "^5.8.1",
"@fortawesome/fontawesome-svg-core": "^1.2.20",
"@fortawesome/free-regular-svg-icons": "^5.10.0",
"@fortawesome/free-solid-svg-icons": "^5.10.0",
"@fortawesome/vue-fontawesome": "^0.1.6",
"axios": "^0.18.1",
"easymde": "^2.6.0",
"axios": "^0.19.0",
"easymde": "^2.7.0",
"koa": "^2.7.0",
"koa-bodyparser": "^4.2.1",
"koa-mount": "^4.0.0",
"koa-proxy": "^0.9.0",
"koa-static": "^5.0.0",
"lodash": "^4.17.13",
"lodash": "^4.17.15",
"moment": "^2.24.0",
"normalize-url": "^4.3.0",
"nprogress": "^0.2.0",
"vue": "^2.6.10",
"vue-class-component": "^7.0.2",
"vue-mixin-decorator": "^1.1.1",
"vue-property-decorator": "^8.1.0",
"vue-router": "^3.0.4",
"vue-class-component": "^7.1.0",
"vue-mixin-decorator": "^1.2.0",
"vue-property-decorator": "^8.2.1",
"vue-router": "^3.0.7",
"vue-server-renderer": "^2.6.10",
"vuetify": "^1.5.11",
"vuex": "^3.1.0",
"vuetify": "^2.0.3",
"vuex": "^3.1.1",
"vuex-router-sync": "^5.0.0"
},
"devDependencies": {
"@babel/core": "^7.4.3",
"fibers": "^4.0.1",
"@babel/core": "^7.5.5",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.4.3",
"@babel/preset-env": "^7.4.3",
"@babel/plugin-transform-runtime": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/runtime": "^7.5.5",
"@vue/preload-webpack-plugin": "^1.1.0",
"autoprefixer": "^9.5.1",
"babel-loader": "^8.0.5",
"concurrently": "^4.1.0",
"copyfiles": "^2.1.0",
"autoprefixer": "^9.6.1",
"babel-loader": "^8.0.6",
"concurrently": "^4.1.1",
"copyfiles": "^2.1.1",
"cross-env": "^5.2.0",
"css-loader": "^2.1.1",
"file-loader": "^3.0.1",
"fork-ts-checker-webpack-plugin": "^1.0.2",
"css-loader": "^3.1.0",
"deepmerge": "^4.0.0",
"file-loader": "^4.1.0",
"fork-ts-checker-webpack-plugin": "^1.4.3",
"friendly-errors-webpack-plugin": "^1.7.0",
"koa-webpack": "^5.2.2",
"koa-webpack": "^5.2.4",
"memory-fs": "^0.4.1",
"nodemon": "^1.18.11",
"nodemon": "^1.19.1",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.6.0",
"postcss-preset-env": "^6.7.0",
"precss": "^4.0.0",
"rimraf": "^2.6.3",
"testcafe": "^1.1.2",
"sass": "^1.22.9",
"sass-loader": "^7.1.0",
"testcafe": "^1.3.3",
"testcafe-vue-selectors": "^3.1.0",
"ts-loader": "^5.3.3",
"ts-node": "^8.0.3",
"tslint": "^5.15.0",
"typescript": "^3.4.3",
"url-loader": "^1.1.2",
"vue-loader": "^15.7.0",
"ts-loader": "^6.0.4",
"ts-node": "^8.3.0",
"tslint": "^5.18.0",
"typescript": "^3.5.3",
"url-loader": "^2.1.0",
"vue-loader": "^15.7.1",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.30.0",
"webpack-cli": "^3.3.0",
"vuetify-loader": "^1.3.0",
"webpack": "^4.38.0",
"webpack-cli": "^3.3.6",
"webpack-merge": "^4.2.1",
"webpack-node-externals": "^1.7.2",
"webpackbar": "^3.1.5"
"webpackbar": "^3.2.0"
}
}