1
1
mirror of https://github.com/aelve/guide.git synced 2024-11-23 12:15:06 +03:00

feat/category item info edit (#250)

* added search results nothing found text

* removed external spaces and lines

* Item info update
This commit is contained in:
avele 2018-12-13 16:02:37 +04:00 committed by GitHub
parent c32852ab77
commit 845a7a671e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 299 additions and 157 deletions

View File

@ -10,7 +10,7 @@
wrap
>
<span class="mr-3">
made by
made by
<a-link
openInNewTab
url="https://aelve.com/"

View File

@ -1,8 +1,8 @@
<template>
<a
class="link"
:target="openInNewTab ? '_blank': ''"
:rel="openInNewTab ? 'noopener noreferrer': ''"
:target="openInNewTab ? '_blank': ''"
:rel="openInNewTab ? 'noopener noreferrer': ''"
:href="url"
>
<slot />
@ -17,5 +17,4 @@ export default class ALink extends Vue {
@Prop(Boolean) openInNewTab!: boolean
@Prop(String) url!: string
}
</script>

View File

@ -5,7 +5,7 @@
max-width="500px"
>
<slot slot="activator" />
<v-card>
<v-card-text>
<v-form
@ -78,7 +78,6 @@ export default class AddItemDialog extends Vue {
category: this.categoryId,
name: this.itemName
})
this.close()
}
}

View File

@ -32,22 +32,21 @@
<div v-html="categoryDescription" />
</div>
<template v-if="category">
<div
v-for="(value, index) in category.items"
:key="index"
>
<article-content
:kind="value.name"
:group="value.group"
:itemDescription="value.description.html"
:pros="value.pros"
:cons="value.cons"
:ecosystem="value.ecosystem.html"
:tocArray="value.toc"
:notes="value.notes.html"
:itemUid="value.uid"
/>
</div>
<category-item
v-for="value in category.items"
:key="value.uid"
:itemUid="value.uid"
:link="value.link"
:name="value.name"
:group="value.group"
:itemDescription="value.description.html"
:pros="value.pros"
:cons="value.cons"
:ecosystem="value.ecosystem.html"
:tocArray="value.toc"
:notes="value.notes.html"
:kind="value.kind"
/>
</template>
<v-btn
flat
@ -58,7 +57,7 @@
<v-icon class="mr-1" left>add</v-icon>
Add new item
</v-btn>
<add-item-dialog
<add-item-dialog
v-model="isDialogOpen"
:categoryId="categoryId"
/>
@ -70,14 +69,14 @@
import _toKebabCase from 'lodash/kebabCase'
import _get from 'lodash/get'
import { Vue, Component, Prop } from 'vue-property-decorator'
import ArticleContent from 'client/components/ArticleContent.vue'
import CategoryItem from 'client/components/CategoryItem.vue'
import AddItemDialog from 'client/components/AddItemDialog.vue'
import category from 'client/store/modules/category'
@Component({
name: 'article-component',
components: {
ArticleContent,
CategoryItem,
AddItemDialog
}
})

View File

@ -15,13 +15,13 @@
xl1
v-for="(groupCategories, groupName, index) in groups"
:key="index"
>
>
<div class="category-group">
<h4 class="mb-2 display-1 font-weight-black category-group-name">
{{ groupName }}
</h4>
<router-link
<router-link
class="category-title"
v-for="category in groupCategories[CategoryStatus.finished]"
:key="category.uid"

View File

@ -1,36 +1,28 @@
<template>
<div class="article-item">
<div class="article-header">
<p class="article-hd-textlg">{{ kind }}</p>
<a-link
openInNewTab :url="`http://hackage.haskell.org/package/${kind}`"
class="article-header-link">
(Hackage)
</a-link>
<p class="article-hd-textsm">{{ group }}</p>
<div class="article-header-icons">
<i class="fas fa-arrow-up"></i>
<i class="fas fa-arrow-down"></i>
<div class="header-func-icons">
<i class="fas fa-cogs"></i>
<button @click="openConfirmDialog">
<i class="fas fa-times item-del-btn"></i>
</button>
</div>
</div>
</div>
<div class="article-content">
<category-item-toolbar
:itemUid="itemUid"
:itemName="name"
:itemLink="link"
:itemGroup="group"
:itemKind="kind"
/>
<div class="category-item-body">
<p class="article-section-title">Summary</p>
<div
class="article-description"
v-html="itemDescription"/>
<div
class="article-description"
v-html="itemDescription"
/>
<div class="flex-wrapper article-section pros-cons-box">
<div class="width-50">
<p class="article-section-title">Pros</p>
<ul
v-if="pros"
v-for="(value, index) in pros"
:key="index">
<ul
v-if="pros"
v-for="(value, index) in pros"
:key="index"
>
<li v-html="value.content.html"/>
</ul>
</div>
@ -53,53 +45,51 @@
<button class="notes-settings-btn" @click="collapseNotes">collapse notes</button>
<button class="notes-settings-btn" style="display: none;">edit notes</button>
</div>
<div
v-for="(value, index) in tocArray"
:key="index">
<!-- TODO refactor v-for from ul to li -->
<ul
v-for="(value, index) in value"
:key="index">
<li
class="notes-toc-item"
v-if="value.content">
<a
:href="`#${value.slug}`"
@click="expandNotes">
<div
v-for="(value, index) in tocArray"
:key="index"
>
<ul>
<li
class="notes-toc-item"
:key="index"
v-for="(value, index) in value"
v-if="value.content"
>
<a
:href="`#${value.slug}`"
@click="expandNotes"
>
<p>{{value.content.html}}</p>
</a>
</li>
</ul>
</div>
<!-- TODO lookslike transition not working -->
<transition name="slidedown">
<div
class="notes-content"
v-show="isNoteExpanded"
v-html="notes">
</div>
v-show="isNoteExpanded"
v-html="notes"
/>
</transition>
</div>
</div>
<confirm-dialog
confirmationText="delete this item"
v-model="isDeleteItemDialogOpen"
@confirmed="deleteItem"
/>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator'
import ConfirmDialog from 'client/components/ConfirmDialog.vue'
import CategoryItemToolbar from 'client/components/CategoryItemToolbar.vue'
import { ICategoryItem } from 'client/service/CategoryItem.ts'
@Component({
components: {
ConfirmDialog
CategoryItemToolbar
}
})
export default class ArticleContent extends Vue {
@Prop(String) kind!: string
export default class CategoryItem extends Vue {
@Prop(String) name!: string
@Prop(String) group!: string
@Prop(String) itemDescription!: string
@Prop(Array) pros!: [any]
@ -109,9 +99,10 @@ export default class ArticleContent extends Vue {
@Prop(Object) tocItemContent!: object
@Prop(String) notes!: string
@Prop(String) itemUid!: string
@Prop(String) link!: string
@Prop(Object) kind!: object
isNoteExpanded: boolean = false
isDeleteItemDialogOpen: boolean = false
expandNotes () {
this.isNoteExpanded = true
@ -120,24 +111,20 @@ export default class ArticleContent extends Vue {
collapseNotes () {
this.isNoteExpanded = false
}
async deleteItem (): Promise<void> {
await this.$store.dispatch('categoryItem/deleteItemById', this.itemUid)
}
openConfirmDialog () {
this.isDeleteItemDialogOpen = true
}
}
</script>
<style scoped>
.article-content >>> p {
.category-item-body {
padding: 15px 20px 25px;
}
.category-item-body >>> p {
font-size: 16px;
margin: 0 0 10px;
}
.article-content >>> li {
.category-item-body >>> li {
font-size: 16px;
}
@ -177,18 +164,9 @@ export default class ArticleContent extends Vue {
.article-item {
background: #e5e5e5;
padding: 15px 20px 25px;
margin: 0 0 80px;
}
.article-header {
display: flex;
align-items: center;
padding: 10px 15px;
margin: -15px -20px 15px;
background: #c8c8c8;
}
.flex-wrapper {
display: flex;
}
@ -210,46 +188,6 @@ export default class ArticleContent extends Vue {
font-weight: 600;
}
.article-hd-textlg {
font-size: 22px;
}
.article-hd-textsm {
font-size: 18px;
}
.article-header-link {
font-size: 22px;
padding: 0 32px 0 8px;
}
.article-header-icons {
display: flex;
align-items: center;
justify-content: flex-end;
flex: 1;
}
.article-header-icons >>> i {
margin-right: 5px;
font-size: 18px;
color: #979797;
cursor: pointer;
transition: all ease-in-out 0.25s;
}
.article-header-icons >>> i:nth-last-child(1) {
margin: 0;
}
.article-header-icons >>> i:hover {
color: #000;
}
.header-func-icons {
padding-left: 20px;
}
.notes-toc-item >>> p {
margin: 0;
}
@ -298,21 +236,11 @@ export default class ArticleContent extends Vue {
}
@media screend and (max-width: 768px) {
.article-content {
.category-item-body {
width: 100%;
}
.article-item {
margin: 0 0 30px;
}
.article-hd-textlg {
font-size: 20px;
}
.article-hd-textsm {
font-size: 16px;
}
.article-header-link {
font-size: 20px;
padding: 0 32px 0 8px;
}
}
</style>

View File

@ -0,0 +1,195 @@
<template>
<v-expansion-panel class="category-item-toolbar-wrapper">
<v-expansion-panel-content
hide-actions
lazy
:value="isEditItemInfoMenuOpen"
>
<v-toolbar
slot="header"
color="#d6d6d6"
class="category-item-toolbar"
@click.stop=""
>
<v-toolbar-title class="headline">
<a-link
v-if="itemLink"
:url="itemLink"
openInNewTab
>
{{ itemName }}
</a-link>
<span v-else> {{ itemName }} </span>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items class="category-item-toolbar-btns-wrapper">
<v-btn
flat
icon
title="move item down"
>
<v-icon>fas fa-arrow-up</v-icon>
</v-btn>
<v-btn
flat
icon
title="move item down"
>
<v-icon>fas fa-arrow-down</v-icon>
</v-btn>
<v-btn
flat
icon
title="edit item info"
@click="toggleEditItemInfoMenu"
>
<v-icon>fas fa-cog</v-icon>
<v-icon
v-if="isItemInfoEdited"
class="edit-item-info-changed-icon"
color="#6495ed"
size="8"
>
fas fa-circle
</v-icon>
</v-btn>
<v-btn
flat
icon
title="delete item"
@click="openConfirmDeleteDialog"
>
<v-icon>fas fa-times</v-icon>
</v-btn>
</v-toolbar-items>
</v-toolbar>
<v-layout column class="pa-3">
<v-flex>
<v-text-field
v-model="itemNameEdit"
label="Name"
/>
<v-text-field
v-model="itemLinkEdit"
label="Site (optional)"
/>
</v-flex>
<v-flex align-self-end>
<v-btn
class="mr-0"
:disabled="!isItemInfoEdited"
@click="saveItemInfo"
>
Save
</v-btn>
</v-flex>
</v-layout>
</v-expansion-panel-content>
<confirm-dialog
confirmationText="delete this item"
v-model="isDeleteItemDialogOpen"
@confirmed="deleteItem"
/>
</v-expansion-panel>
</template>
<script lang="ts">
import { Vue, Component, Prop, Watch } from 'vue-property-decorator'
import ConfirmDialog from 'client/components/ConfirmDialog.vue'
@Component({
components: {
ConfirmDialog
}
})
export default class CategoryItemToolbar extends Vue {
@Prop(String) itemUid!: string
@Prop(String) itemName!: string
@Prop(String) itemLink!: string
@Prop(String) itemGroup!: string
@Prop(Object) itemKind!: object
isEditItemInfoMenuOpen: boolean = false
isDeleteItemDialogOpen: boolean = false
itemNameEdit: string = this.itemName
itemLinkEdit: string = this.itemLink
@Watch('itemName')
onItemNameChange (newVal: string) {
this.itemNameEdit = newVal
}
@Watch('itemLink')
onItemLinkChange (newVal: string) {
this.itemLinkEdit = newVal
}
toggleEditItemInfoMenu () {
this.isEditItemInfoMenuOpen = !this.isEditItemInfoMenuOpen
}
openConfirmDeleteDialog () {
this.isDeleteItemDialogOpen = true
}
get isItemInfoEdited () {
return this.itemName !== this.itemNameEdit || this.itemLink !== this.itemLinkEdit
}
async saveItemInfo (): Promise<void> {
await this.$store.dispatch('categoryItem/updateItemInfo', {
id: this.itemUid,
body: {
name: this.itemNameEdit,
link: this.itemLinkEdit,
// TODO remove next two lines after changing API of editing item info
// https://www.notion.so/aelve/Tasks-a157d6e3f22241ae83cf624fec3aaad5?p=fe081421dcf844e79e8877d9f4a103ad
created: '2016-07-22T00:00:00Z',
uid: this.itemUid,
group: this.itemGroup,
kind: this.itemKind
}
})
await this.$store.dispatch('category/reloadCategory')
this.toggleEditItemInfoMenu()
}
async deleteItem (): Promise<void> {
await this.$store.dispatch('categoryItem/deleteItemById', this.itemUid)
await this.$store.dispatch('category/reloadCategory')
}
}
</script>
<style scoped>
.edit-item-info-changed-icon {
position: absolute;
bottom: 0;
right: 0;
}
.category-item-toolbar >>> .v-toolbar__title {
overflow: visible;
}
.category-item-toolbar-wrapper {
display: flex;
box-shadow: none;
/* background: #c8c8c8; */
}
.category-item-toolbar-wrapper >>> .v-expansion-panel__header {
padding: 0;
align-items: center;
cursor: unset;
/* background: #d6d6d6; */
}
.category-item-toolbar-wrapper >>> .v-expansion-panel__body {
background: #d6d6d6;
}
.category-item-toolbar-btns-wrapper > * {
margin: 0 2px;
color: #979797;
}
</style>

View File

@ -14,7 +14,7 @@
<v-divider />
<v-card-actions>
<v-spacer />
<v-btn
<v-btn
flat
color="primary"
class="confirm-btn"

View File

@ -1,5 +1,5 @@
<template>
<article-item
<article-item
:categoryId="categoryId"
/>
</template>

View File

@ -41,7 +41,7 @@
:url="`http://aelve.com:4801/haskell/${result.contents.category.uid}#item-${result.contents.info.uid}`"
>
<!-- Do not format next line to separate lines cause it adds extra space after </a-link>. -->
{{ result.contents.info.name }}</a-link>'s ecosystem
{{ result.contents.info.name }}</a-link>'s ecosystem
</span>
</span>
</v-card-title>

View File

@ -2,6 +2,7 @@ import axios from 'axios'
import { ICategoryFull } from './Category'
class CategoryItemService {
// TODO replace all axios api request to axios instance to remove duplication of 'api/*'
async createItem ({ category, name }: ICreateCategoryItem) {
const { data } = await axios.post(`api/item/${category}`, null, {
params: {
@ -13,6 +14,9 @@ class CategoryItemService {
async deleteItemById (id: ICategoryItem['uid']): Promise<void> {
await axios.delete(`api/item/${id}`)
}
async updateItemInfo (id: ICategoryItem['uid'], body: ICategoryItemInfo): Promise<void> {
await axios.put(`api/item/${id}/info`, body)
}
}
export interface ICreateCategoryItem {
@ -35,6 +39,15 @@ export interface ICategoryItem {
}
export interface ICategoryItemInfo {
uid?: string
name?: string
created?: string
link?: string
kind?: object
}
export interface ITrait {
uid: string
content: object

View File

@ -1,6 +1,10 @@
import { ActionTree, GetterTree, MutationTree, ActionContext, Module } from 'vuex'
import { ICategoryItem, CategoryItemService, ICreateCategoryItem } from 'client/service/CategoryItem'
import { CategoryService, ICategoryFull } from 'client/service/Category'
import {
ICategoryItem,
CategoryItemService,
ICreateCategoryItem,
ICategoryItemInfo
} from 'client/service/CategoryItem'
interface ICategoryItemState {
categoryItemList: ICategoryItem[]
@ -24,9 +28,14 @@ const actions: ActionTree<ICategoryItemState, any> = {
dispatch('category/reloadCategory', null, { root: true })
return createdId
},
async deleteItemById ({ dispatch }: ActionContext<ICategoryItemState, any>, id: ICategoryItem['uid']) {
async deleteItemById (context, id: ICategoryItem['uid']) {
await CategoryItemService.deleteItemById(id)
dispatch('category/reloadCategory', null, { root: true })
},
async updateItemInfo (
context: ActionContext<ICategoryItemState, any>,
{ id, body }: { id: ICategoryItem['uid'], body: ICategoryItemInfo }
): Promise<void> {
await CategoryItemService.updateItemInfo(id, body)
}
}