Browse Source

Front-end shop products images

Vova Tkach 5 years ago
parent
commit
598d24b959

+ 2 - 2
assets/template/header_html_file.go

@@ -41,10 +41,10 @@ var VarHeaderHtmlFile = []byte(`<!doctype html>
 		<link rel="shortcut icon" href="{{$.System.PathIcoFav}}" type="image/x-icon" />
 
 		<!-- Template CSS file from template folder -->
-		<link rel="stylesheet" href="{{$.System.PathThemeStyles}}?v=1">
+		<link rel="stylesheet" href="{{$.System.PathThemeStyles}}?v=2">
 
 		<!-- Template JavaScript file from template folder -->
-		<script src="{{$.System.PathThemeScripts}}?v=1"></script>
+		<script src="{{$.System.PathThemeScripts}}?v=2"></script>
 	</head>
 	<body class="fixed-top-bar1">
 		<div id="wrap">

+ 5 - 1
assets/template/shop_category_html_file.go

@@ -15,7 +15,11 @@ var VarShopCategoryHtmlFile = []byte(`{{template "header.html" .}}
 				<div class="card card-product">
 					<div class="card-img-link">
 						<a href="{{.Permalink}}">
-							<img class="card-img-top" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_16c7e5ac360%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_16c7e5ac360%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22107.0078125%22%20y%3D%2296.234375%22%3E286x180%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="Card image cap">
+							{{if .HaveImages }}
+								<img class="card-img-top" src="{{.Image.Thumbnail1}}" alt="">
+							{{else}}
+								<img class="card-img-top" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_16c7e5ac360%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_16c7e5ac360%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22107.0078125%22%20y%3D%2296.234375%22%3E286x180%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="Card image cap">
+							{{end}}
 						</a>
 					</div>
 					<div class="card-body">

+ 5 - 1
assets/template/shop_html_file.go

@@ -8,7 +8,11 @@ var VarShopHtmlFile = []byte(`{{template "header.html" .}}
 				<div class="card card-product">
 					<div class="card-img-link">
 						<a href="{{.Permalink}}">
-							<img class="card-img-top" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_16c7e5ac360%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_16c7e5ac360%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22107.0078125%22%20y%3D%2296.234375%22%3E286x180%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="Card image cap">
+							{{if .HaveImages }}
+								<img class="card-img-top" src="{{.Image.Thumbnail1}}" alt="">
+							{{else}}
+								<img class="card-img-top" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_16c7e5ac360%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_16c7e5ac360%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22107.0078125%22%20y%3D%2296.234375%22%3E286x180%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="Card image cap">
+							{{end}}
 						</a>
 					</div>
 					<div class="card-body">

+ 10 - 2
assets/template/shop_product_html_file.go

@@ -26,7 +26,11 @@ var VarShopProductHtmlFile = []byte(`{{template "header.html" .}}
 				<div class="row">
 					<div class="col-md-6">
 						<div class="card">
-							<img class="card-img-top" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_16c7e5ac360%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_16c7e5ac360%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22107.0078125%22%20y%3D%2296.234375%22%3E286x180%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="Card image cap">
+							{{if $.Data.Shop.Product.HaveImages }}
+								<img class="card-img-top" src="{{$.Data.Shop.Product.Image.Thumbnail3}}" alt="">
+							{{else}}
+								<img class="card-img-top" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_16c7e5ac360%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_16c7e5ac360%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22107.0078125%22%20y%3D%2296.234375%22%3E286x180%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="Card image cap">
+							{{end}}
 						</div>
 					</div>
 					<div class="col-md-6">
@@ -135,7 +139,11 @@ var VarShopProductHtmlFile = []byte(`{{template "header.html" .}}
 					<div class="col-md-4">
 						<div class="card mt-3 mt-sm-3 mt-md-0 mt-lg-0">
 							<div class="card-body">
-								<img class="card-img-top" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_16c7e5ac360%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_16c7e5ac360%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22107.0078125%22%20y%3D%2296.234375%22%3E286x180%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="Card image cap">
+								{{if $.Data.Shop.Product.HaveImages }}
+									<img class="card-img-top" src="{{$.Data.Shop.Product.Image.Thumbnail2}}" alt="">
+								{{else}}
+									<img class="card-img-top" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_16c7e5ac360%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_16c7e5ac360%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22107.0078125%22%20y%3D%2296.234375%22%3E286x180%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="Card image cap">
+								{{end}}
 							</div>
 						</div>
 						<div class="card mt-3">

+ 0 - 1
assets/template/styles_css_file.go

@@ -156,7 +156,6 @@ footer {
 .grid-products .card-product .card-img-link img {
 	display: block;
 	border-radius: 0;
-	opacity: 0.8;
 }
 
 .grid-products .card-product .card-title {

+ 1 - 1
engine/fetdata/fetdata.go

@@ -47,7 +47,7 @@ func New(wrap *wrapper.Wrapper, drow interface{}, is404 bool) *FERData {
 			}
 		} else if len(wrap.UrlArgs) == 2 && wrap.UrlArgs[0] == "shop" && wrap.UrlArgs[1] != "" {
 			if o, ok := drow.(*utils.MySql_shop_product); ok {
-				d_Shop = &Shop{wrap: wrap, product: &ShopProduct{wrap: wrap, object: o}}
+				d_Shop = &Shop{wrap: wrap, product: (&ShopProduct{wrap: wrap, object: o}).load()}
 			}
 		} else {
 			d_Shop = &Shop{wrap: wrap}

+ 36 - 0
engine/fetdata/shop.go

@@ -184,6 +184,8 @@ func (this *Shop) load() {
 		`
 	}
 
+	product_ids := []string{}
+
 	if err := this.wrap.DB.QueryRow(sql_nums).Scan(&this.productsCount); err == nil {
 		if this.category == nil {
 			this.productsPerPage = (*this.wrap.Config).Shop.Pagination.Index
@@ -232,6 +234,7 @@ func (this *Shop) load() {
 					&ro.A_lft,
 					&ro.A_rgt,
 				); err == nil {
+					product_ids = append(product_ids, utils.IntToStr(rp.A_id))
 					this.products = append(this.products, &ShopProduct{
 						wrap:     this.wrap,
 						object:   &rp,
@@ -244,6 +247,39 @@ func (this *Shop) load() {
 		}
 	}
 
+	// Product images
+	product_images := map[int][]*ShopProductImage{}
+	if len(product_ids) > 0 {
+		if rows, err := this.wrap.DB.Query(
+			`SELECT
+				shop_product_images.product_id,
+				shop_product_images.filename
+			FROM
+				shop_product_images
+			WHERE
+				shop_product_images.product_id IN (` + strings.Join(product_ids, ", ") + `)
+			ORDER BY
+				shop_product_images.filename ASC
+			;`,
+		); err == nil {
+			defer rows.Close()
+			for rows.Next() {
+				img := utils.MySql_shop_product_image{}
+				if err := rows.Scan(
+					&img.A_product_id,
+					&img.A_filename,
+				); err == nil {
+					product_images[img.A_product_id] = append(product_images[img.A_product_id], &ShopProductImage{wrap: this.wrap, object: &img})
+				}
+			}
+		}
+	}
+	for index, product := range this.products {
+		if pimgs, ok := product_images[product.Id()]; ok {
+			this.products[index].images = pimgs
+		}
+	}
+
 	// Build pagination
 	if true {
 		for i := 1; i < this.productsCurrPage; i++ {

+ 67 - 0
engine/fetdata/shop_product.go

@@ -15,6 +15,39 @@ type ShopProduct struct {
 	user     *User
 	currency *Currency
 	category *ShopCategory
+
+	images []*ShopProductImage
+}
+
+func (this *ShopProduct) load() *ShopProduct {
+	if this == nil {
+		return this
+	}
+	if rows, err := this.wrap.DB.Query(
+		`SELECT
+			shop_product_images.product_id,
+			shop_product_images.filename
+		FROM
+			shop_product_images
+		WHERE
+			shop_product_images.product_id = ?
+		ORDER BY
+			shop_product_images.filename ASC
+		;`,
+		this.object.A_id,
+	); err == nil {
+		defer rows.Close()
+		for rows.Next() {
+			img := utils.MySql_shop_product_image{}
+			if err := rows.Scan(
+				&img.A_product_id,
+				&img.A_filename,
+			); err == nil {
+				this.images = append(this.images, &ShopProductImage{wrap: this.wrap, object: &img})
+			}
+		}
+	}
+	return this
 }
 
 func (this *ShopProduct) Id() int {
@@ -143,3 +176,37 @@ func (this *ShopProduct) Permalink() string {
 	}
 	return "/shop/" + this.object.A_alias + "/"
 }
+
+func (this *ShopProduct) HaveImages() bool {
+	if this == nil {
+		return false
+	}
+	if len(this.images) <= 0 {
+		return false
+	}
+	return true
+}
+
+func (this *ShopProduct) Image() *ShopProductImage {
+	if this == nil {
+		return nil
+	}
+	if len(this.images) <= 0 {
+		return nil
+	}
+	return this.images[0]
+}
+
+func (this *ShopProduct) Images() []*ShopProductImage {
+	if this == nil {
+		return []*ShopProductImage{}
+	}
+	return this.images
+}
+
+func (this *ShopProduct) ImagesCount() int {
+	if this == nil {
+		return 0
+	}
+	return len(this.images)
+}

+ 76 - 0
engine/fetdata/shop_product_image.go

@@ -0,0 +1,76 @@
+package fetdata
+
+import (
+	"golang-fave/engine/wrapper"
+	"golang-fave/utils"
+)
+
+type ShopProductImage struct {
+	wrap   *wrapper.Wrapper
+	object *utils.MySql_shop_product_image
+}
+
+func (this *ShopProductImage) ProductId() int {
+	if this == nil {
+		return 0
+	}
+	return this.object.A_product_id
+}
+
+func (this *ShopProductImage) FileName() string {
+	if this == nil {
+		return ""
+	}
+	return this.object.A_filename
+}
+
+func (this *ShopProductImage) FullImage() string {
+	if this == nil {
+		return ""
+	}
+	return "/products/images/" + utils.IntToStr(this.object.A_product_id) + "/" + this.object.A_filename
+}
+
+func (this *ShopProductImage) Thumbnail0() string {
+	if this == nil {
+		return ""
+	}
+	return "/api/product-image/thumb-0/" + utils.IntToStr(this.object.A_product_id) + "/" + this.object.A_filename
+}
+
+func (this *ShopProductImage) Thumbnail1() string {
+	if this == nil {
+		return ""
+	}
+	return "/api/product-image/thumb-1/" + utils.IntToStr(this.object.A_product_id) + "/" + this.object.A_filename
+}
+
+func (this *ShopProductImage) Thumbnail2() string {
+	if this == nil {
+		return ""
+	}
+	return "/api/product-image/thumb-2/" + utils.IntToStr(this.object.A_product_id) + "/" + this.object.A_filename
+}
+
+func (this *ShopProductImage) Thumbnail3() string {
+	if this == nil {
+		return ""
+	}
+	return "/api/product-image/thumb-3/" + utils.IntToStr(this.object.A_product_id) + "/" + this.object.A_filename
+}
+
+func (this *ShopProductImage) ThumbnailSize0() [2]int {
+	return (*this.wrap.Config).Shop.Thumbnails.Thumbnail0
+}
+
+func (this *ShopProductImage) ThumbnailSize1() [2]int {
+	return (*this.wrap.Config).Shop.Thumbnails.Thumbnail1
+}
+
+func (this *ShopProductImage) ThumbnailSize2() [2]int {
+	return (*this.wrap.Config).Shop.Thumbnails.Thumbnail2
+}
+
+func (this *ShopProductImage) ThumbnailSize3() [2]int {
+	return (*this.wrap.Config).Shop.Thumbnails.Thumbnail3
+}

+ 123 - 123
hosts/localhost/template/header.html

@@ -1,124 +1,124 @@
-<!doctype html>
-<html lang="en">
-	<head>
-		<!-- Required meta tags -->
-		<meta charset="utf-8">
-		<meta name="theme-color" content="#205081" />
-		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
-
-		<!-- Bootstrap CSS -->
-		<link rel="stylesheet" href="{{$.System.PathCssBootstrap}}">
-
-		<title>
-			{{if not (eq $.Data.Module "404")}}
-				{{if eq $.Data.Module "index"}}
-					{{$.Data.Page.Name}}
-				{{else if or (eq $.Data.Module "blog") (eq $.Data.Module "blog-post") (eq $.Data.Module "blog-category")}}
-					{{if eq $.Data.Module "blog-category"}}
-						Posts of category "{{$.Data.Blog.Category.Name}}" | Blog
-					{{else if eq $.Data.Module "blog-post"}}
-						{{$.Data.Blog.Post.Name}} | Blog
-					{{else}}
-						Latest posts | Blog
-					{{end}}
-				{{else if or (eq $.Data.Module "shop") (eq $.Data.Module "shop-product") (eq $.Data.Module "shop-category")}}
-					{{if eq $.Data.Module "shop-category"}}
-						Products of category "{{$.Data.Shop.Category.Name}}" | Shop
-					{{else if eq $.Data.Module "shop-product"}}
-						{{$.Data.Shop.Product.Name}} | Shop
-					{{else}}
-						Latest products | Shop
-					{{end}}
-				{{end}}
-			{{else}}
-				Error 404
-			{{end}}
-		</title>
-		<meta name="keywords" content="{{$.Data.Page.MetaKeywords}}" />
-		<meta name="description" content="{{$.Data.Page.MetaDescription}}" />
-		<link rel="shortcut icon" href="{{$.System.PathIcoFav}}" type="image/x-icon" />
-
-		<!-- Template CSS file from template folder -->
-		<link rel="stylesheet" href="{{$.System.PathThemeStyles}}?v=1">
-
-		<!-- Template JavaScript file from template folder -->
-		<script src="{{$.System.PathThemeScripts}}?v=1"></script>
-	</head>
-	<body class="fixed-top-bar1">
-		<div id="wrap">
-			<nav class="navbar navbar-expand-lg navbar-light bg-light">
-				<div class="container">
-					<a class="navbar-brand" href="/">Fave {{$.System.InfoVersion}}</a>
-					<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
-						<span class="navbar-toggler-icon"></span>
-					</button>
-					<div class="collapse navbar-collapse" id="navbarResponsive">
-						<ul class="navbar-nav ml-auto">
-							<li class="nav-item{{if eq $.Data.Page.Alias "/"}} active{{end}}">
-								<a class="nav-link" href="/">Home</a>
-							</li>
-							<li class="nav-item">
-								<a class="nav-link{{if eq $.Data.Page.Alias "/another/"}} active{{end}}" href="/another/">Another</a>
-							</li>
-							<li class="nav-item">
-								<a class="nav-link{{if eq $.Data.Page.Alias "/about/"}} active{{end}}" href="/about/">About</a>
-							</li>
-							<li class="nav-item">
-								<a class="nav-link{{if or (eq $.Data.Module "blog") (eq $.Data.Module "blog-post") (eq $.Data.Module "blog-category")}} active{{end}}" href="/blog/">Blog</a>
-							</li>
-							<li class="nav-item">
-								<a class="nav-link{{if or (eq $.Data.Module "shop") (eq $.Data.Module "shop-product") (eq $.Data.Module "shop-category")}} active{{end}}" href="/shop/">Shop</a>
-							</li>
-							<li class="nav-item">
-								<a class="nav-link{{if eq $.Data.Module "404"}} active{{end}}" href="/not-existent-page/">404</a>
-							</li>
-						</ul>
-					</div>
-				</div>
-			</nav>
-			<div id="main">
-				<div class="bg-fave">
-					<div class="container">
-						<h1 class="text-left text-white m-0 p-0 py-5">
-							{{if not (eq $.Data.Module "404")}}
-								{{if eq $.Data.Module "index"}}
-									{{if eq $.Data.Page.Alias "/"}}
-										Welcome to home page
-									{{else}}
-										Welcome to some another page
-									{{end}}
-								{{else if or (eq $.Data.Module "blog") (eq $.Data.Module "blog-post") (eq $.Data.Module "blog-category")}}
-									{{if eq $.Data.Module "blog-category"}}
-										Blog category
-									{{else if eq $.Data.Module "blog-post"}}
-										Blog post
-									{{else}}
-										Blog
-									{{end}}
-								{{else if or (eq $.Data.Module "shop") (eq $.Data.Module "shop-product") (eq $.Data.Module "shop-category")}}
-									{{if eq $.Data.Module "shop-category"}}
-										Shop category
-									{{else if eq $.Data.Module "shop-product"}}
-										Shop product
-									{{else}}
-										Shop
-									{{end}}
-								{{end}}
-							{{else}}
-								Oops, page is not found...
-							{{end}}
-						</h1>
-					</div>
-				</div>
-				<div class="container clear-top">
-					<div class="row pt-4">
-						{{if or (eq $.Data.Module "shop") (eq $.Data.Module "shop-category")}}
-							<div class="col-sm-5 col-md-4 col-lg-3">
-								{{template "sidebar-left.html" .}}
-							</div>
-						{{end}}
-						{{if or (eq $.Data.Module "shop-product")}}
-							<div class="col-md-12">
-						{{else}}
-							<div class="col-sm-7 col-md-8 col-lg-9">
+<!doctype html>
+<html lang="en">
+	<head>
+		<!-- Required meta tags -->
+		<meta charset="utf-8">
+		<meta name="theme-color" content="#205081" />
+		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+
+		<!-- Bootstrap CSS -->
+		<link rel="stylesheet" href="{{$.System.PathCssBootstrap}}">
+
+		<title>
+			{{if not (eq $.Data.Module "404")}}
+				{{if eq $.Data.Module "index"}}
+					{{$.Data.Page.Name}}
+				{{else if or (eq $.Data.Module "blog") (eq $.Data.Module "blog-post") (eq $.Data.Module "blog-category")}}
+					{{if eq $.Data.Module "blog-category"}}
+						Posts of category "{{$.Data.Blog.Category.Name}}" | Blog
+					{{else if eq $.Data.Module "blog-post"}}
+						{{$.Data.Blog.Post.Name}} | Blog
+					{{else}}
+						Latest posts | Blog
+					{{end}}
+				{{else if or (eq $.Data.Module "shop") (eq $.Data.Module "shop-product") (eq $.Data.Module "shop-category")}}
+					{{if eq $.Data.Module "shop-category"}}
+						Products of category "{{$.Data.Shop.Category.Name}}" | Shop
+					{{else if eq $.Data.Module "shop-product"}}
+						{{$.Data.Shop.Product.Name}} | Shop
+					{{else}}
+						Latest products | Shop
+					{{end}}
+				{{end}}
+			{{else}}
+				Error 404
+			{{end}}
+		</title>
+		<meta name="keywords" content="{{$.Data.Page.MetaKeywords}}" />
+		<meta name="description" content="{{$.Data.Page.MetaDescription}}" />
+		<link rel="shortcut icon" href="{{$.System.PathIcoFav}}" type="image/x-icon" />
+
+		<!-- Template CSS file from template folder -->
+		<link rel="stylesheet" href="{{$.System.PathThemeStyles}}?v=2">
+
+		<!-- Template JavaScript file from template folder -->
+		<script src="{{$.System.PathThemeScripts}}?v=2"></script>
+	</head>
+	<body class="fixed-top-bar1">
+		<div id="wrap">
+			<nav class="navbar navbar-expand-lg navbar-light bg-light">
+				<div class="container">
+					<a class="navbar-brand" href="/">Fave {{$.System.InfoVersion}}</a>
+					<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
+						<span class="navbar-toggler-icon"></span>
+					</button>
+					<div class="collapse navbar-collapse" id="navbarResponsive">
+						<ul class="navbar-nav ml-auto">
+							<li class="nav-item{{if eq $.Data.Page.Alias "/"}} active{{end}}">
+								<a class="nav-link" href="/">Home</a>
+							</li>
+							<li class="nav-item">
+								<a class="nav-link{{if eq $.Data.Page.Alias "/another/"}} active{{end}}" href="/another/">Another</a>
+							</li>
+							<li class="nav-item">
+								<a class="nav-link{{if eq $.Data.Page.Alias "/about/"}} active{{end}}" href="/about/">About</a>
+							</li>
+							<li class="nav-item">
+								<a class="nav-link{{if or (eq $.Data.Module "blog") (eq $.Data.Module "blog-post") (eq $.Data.Module "blog-category")}} active{{end}}" href="/blog/">Blog</a>
+							</li>
+							<li class="nav-item">
+								<a class="nav-link{{if or (eq $.Data.Module "shop") (eq $.Data.Module "shop-product") (eq $.Data.Module "shop-category")}} active{{end}}" href="/shop/">Shop</a>
+							</li>
+							<li class="nav-item">
+								<a class="nav-link{{if eq $.Data.Module "404"}} active{{end}}" href="/not-existent-page/">404</a>
+							</li>
+						</ul>
+					</div>
+				</div>
+			</nav>
+			<div id="main">
+				<div class="bg-fave">
+					<div class="container">
+						<h1 class="text-left text-white m-0 p-0 py-5">
+							{{if not (eq $.Data.Module "404")}}
+								{{if eq $.Data.Module "index"}}
+									{{if eq $.Data.Page.Alias "/"}}
+										Welcome to home page
+									{{else}}
+										Welcome to some another page
+									{{end}}
+								{{else if or (eq $.Data.Module "blog") (eq $.Data.Module "blog-post") (eq $.Data.Module "blog-category")}}
+									{{if eq $.Data.Module "blog-category"}}
+										Blog category
+									{{else if eq $.Data.Module "blog-post"}}
+										Blog post
+									{{else}}
+										Blog
+									{{end}}
+								{{else if or (eq $.Data.Module "shop") (eq $.Data.Module "shop-product") (eq $.Data.Module "shop-category")}}
+									{{if eq $.Data.Module "shop-category"}}
+										Shop category
+									{{else if eq $.Data.Module "shop-product"}}
+										Shop product
+									{{else}}
+										Shop
+									{{end}}
+								{{end}}
+							{{else}}
+								Oops, page is not found...
+							{{end}}
+						</h1>
+					</div>
+				</div>
+				<div class="container clear-top">
+					<div class="row pt-4">
+						{{if or (eq $.Data.Module "shop") (eq $.Data.Module "shop-category")}}
+							<div class="col-sm-5 col-md-4 col-lg-3">
+								{{template "sidebar-left.html" .}}
+							</div>
+						{{end}}
+						{{if or (eq $.Data.Module "shop-product")}}
+							<div class="col-md-12">
+						{{else}}
+							<div class="col-sm-7 col-md-8 col-lg-9">
 						{{end}}

+ 67 - 63
hosts/localhost/template/shop-category.html

@@ -1,64 +1,68 @@
-{{template "header.html" .}}
-<div class="card mb-4">
-	<div class="post">
-		<div class="card-body">
-			<b>Category:</b> {{$.Data.Shop.Category.Name}}
-		</div>
-	</div>
-</div>
-<div class="mb-4">
-	{{if $.Data.Shop.HaveProducts}}
-		<div class="grid-products">
-			{{range $.Data.Shop.Products}}
-				<div class="card card-product">
-					<div class="card-img-link">
-						<a href="{{.Permalink}}">
-							<img class="card-img-top" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_16c7e5ac360%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_16c7e5ac360%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22107.0078125%22%20y%3D%2296.234375%22%3E286x180%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="Card image cap">
-						</a>
-					</div>
-					<div class="card-body">
-						<h5 class="card-title">
-							<a href="{{.Permalink}}">
-								{{.Name}}
-							</a>
-						</h5>
-						<div class="card-text">{{.Briefly}}</div>
-					</div>
-					<div class="card-footer">
-						<span class="price">{{.PriceFormat "%.2f"}} {{.Currency.Code}}</span><a href="{{.Permalink}}" class="btn btn-primary">View</a>
-					</div>
-				</div>
-			{{end}}
-		</div>
-	{{else}}
-		<div class="card">
-			<div class="card-body">
-				Sorry, no products matched your criteria
-			</div>
-		</div>
-	{{end}}
-</div>
-{{if $.Data.Shop.HaveProducts}}
-	{{if gt $.Data.Shop.ProductsMaxPage 1 }}
-		<nav>
-			<ul class="pagination mb-4">
-				{{if $.Data.Shop.PaginationPrev}}
-					<li class="page-item{{if $.Data.Shop.PaginationPrev.Current}} disabled{{end}}">
-						<a class="page-link" href="{{$.Data.Shop.PaginationPrev.Link}}">Previous</a>
-					</li>
-				{{end}}
-				{{range $.Data.Shop.Pagination}}
-					<li class="page-item{{if .Current}} active{{end}}">
-						<a class="page-link" href="{{.Link}}">{{.Num}}</a>
-					</li>
-				{{end}}
-				{{if $.Data.Shop.PaginationNext}}
-					<li class="page-item{{if $.Data.Shop.PaginationNext.Current}} disabled{{end}}">
-						<a class="page-link" href="{{$.Data.Shop.PaginationNext.Link}}">Next</a>
-					</li>
-				{{end}}
-			</ul>
-		</nav>
-	{{end}}
-{{end}}
+{{template "header.html" .}}
+<div class="card mb-4">
+	<div class="post">
+		<div class="card-body">
+			<b>Category:</b> {{$.Data.Shop.Category.Name}}
+		</div>
+	</div>
+</div>
+<div class="mb-4">
+	{{if $.Data.Shop.HaveProducts}}
+		<div class="grid-products">
+			{{range $.Data.Shop.Products}}
+				<div class="card card-product">
+					<div class="card-img-link">
+						<a href="{{.Permalink}}">
+							{{if .HaveImages }}
+								<img class="card-img-top" src="{{.Image.Thumbnail1}}" alt="">
+							{{else}}
+								<img class="card-img-top" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_16c7e5ac360%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_16c7e5ac360%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22107.0078125%22%20y%3D%2296.234375%22%3E286x180%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="Card image cap">
+							{{end}}
+						</a>
+					</div>
+					<div class="card-body">
+						<h5 class="card-title">
+							<a href="{{.Permalink}}">
+								{{.Name}}
+							</a>
+						</h5>
+						<div class="card-text">{{.Briefly}}</div>
+					</div>
+					<div class="card-footer">
+						<span class="price">{{.PriceFormat "%.2f"}} {{.Currency.Code}}</span><a href="{{.Permalink}}" class="btn btn-primary">View</a>
+					</div>
+				</div>
+			{{end}}
+		</div>
+	{{else}}
+		<div class="card">
+			<div class="card-body">
+				Sorry, no products matched your criteria
+			</div>
+		</div>
+	{{end}}
+</div>
+{{if $.Data.Shop.HaveProducts}}
+	{{if gt $.Data.Shop.ProductsMaxPage 1 }}
+		<nav>
+			<ul class="pagination mb-4">
+				{{if $.Data.Shop.PaginationPrev}}
+					<li class="page-item{{if $.Data.Shop.PaginationPrev.Current}} disabled{{end}}">
+						<a class="page-link" href="{{$.Data.Shop.PaginationPrev.Link}}">Previous</a>
+					</li>
+				{{end}}
+				{{range $.Data.Shop.Pagination}}
+					<li class="page-item{{if .Current}} active{{end}}">
+						<a class="page-link" href="{{.Link}}">{{.Num}}</a>
+					</li>
+				{{end}}
+				{{if $.Data.Shop.PaginationNext}}
+					<li class="page-item{{if $.Data.Shop.PaginationNext.Current}} disabled{{end}}">
+						<a class="page-link" href="{{$.Data.Shop.PaginationNext.Link}}">Next</a>
+					</li>
+				{{end}}
+			</ul>
+		</nav>
+	{{end}}
+{{end}}
 {{template "footer.html" .}}

+ 10 - 2
hosts/localhost/template/shop-product.html

@@ -24,7 +24,11 @@
 				<div class="row">
 					<div class="col-md-6">
 						<div class="card">
-							<img class="card-img-top" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_16c7e5ac360%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_16c7e5ac360%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22107.0078125%22%20y%3D%2296.234375%22%3E286x180%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="Card image cap">
+							{{if $.Data.Shop.Product.HaveImages }}
+								<img class="card-img-top" src="{{$.Data.Shop.Product.Image.Thumbnail3}}" alt="">
+							{{else}}
+								<img class="card-img-top" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_16c7e5ac360%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_16c7e5ac360%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22107.0078125%22%20y%3D%2296.234375%22%3E286x180%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="Card image cap">
+							{{end}}
 						</div>
 					</div>
 					<div class="col-md-6">
@@ -133,7 +137,11 @@
 					<div class="col-md-4">
 						<div class="card mt-3 mt-sm-3 mt-md-0 mt-lg-0">
 							<div class="card-body">
-								<img class="card-img-top" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_16c7e5ac360%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_16c7e5ac360%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22107.0078125%22%20y%3D%2296.234375%22%3E286x180%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="Card image cap">
+								{{if $.Data.Shop.Product.HaveImages }}
+									<img class="card-img-top" src="{{$.Data.Shop.Product.Image.Thumbnail2}}" alt="">
+								{{else}}
+									<img class="card-img-top" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_16c7e5ac360%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_16c7e5ac360%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22107.0078125%22%20y%3D%2296.234375%22%3E286x180%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="Card image cap">
+								{{end}}
 							</div>
 						</div>
 						<div class="card mt-3">

+ 64 - 60
hosts/localhost/template/shop.html

@@ -1,61 +1,65 @@
-{{template "header.html" .}}
-<div class="mb-4">
-	{{if $.Data.Shop.HaveProducts}}
-		<div class="grid-products">
-			{{range $.Data.Shop.Products}}
-				<div class="card card-product">
-					<div class="card-img-link">
-						<a href="{{.Permalink}}">
-							<img class="card-img-top" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_16c7e5ac360%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_16c7e5ac360%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22107.0078125%22%20y%3D%2296.234375%22%3E286x180%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="Card image cap">
-						</a>
-					</div>
-					<div class="card-body">
-						<h5 class="card-title">
-							<a href="{{.Permalink}}">
-								{{.Name}}
-							</a>
-						</h5>
-						<div class="card-text">{{.Briefly}}</div>
-					</div>
-					<div class="card-footer">
-						<span class="price">{{.PriceFormat "%.2f"}} {{.Currency.Code}}</span><a href="{{.Permalink}}" class="btn btn-primary">View</a>
-					</div>
-				</div>
-			{{end}}
-		</div>
-	{{else}}
-		<div class="card">
-			<div class="card-body">
-				Sorry, no products matched your criteria
-			</div>
-		</div>
-	{{end}}
-</div>
-{{if $.Data.Shop.HaveProducts}}
-	{{if gt $.Data.Shop.ProductsMaxPage 1 }}
-		<nav>
-			<ul class="pagination mb-4">
-				{{if $.Data.Shop.PaginationPrev}}
-					<li class="page-item{{if $.Data.Shop.PaginationPrev.Current}} disabled{{end}}">
-						<a class="page-link" href="{{$.Data.Shop.PaginationPrev.Link}}">Previous</a>
-					</li>
-				{{end}}
-				{{range $.Data.Shop.Pagination}}
-					{{if .Dots}}
-						<li class="page-item disabled"><a class="page-link" href="">...</a></li>
-					{{else}}
-						<li class="page-item{{if .Current}} active{{end}}">
-							<a class="page-link" href="{{.Link}}">{{.Num}}</a>
-						</li>
-					{{end}}
-				{{end}}
-				{{if $.Data.Shop.PaginationNext}}
-					<li class="page-item{{if $.Data.Shop.PaginationNext.Current}} disabled{{end}}">
-						<a class="page-link" href="{{$.Data.Shop.PaginationNext.Link}}">Next</a>
-					</li>
-				{{end}}
-			</ul>
-		</nav>
-	{{end}}
-{{end}}
+{{template "header.html" .}}
+<div class="mb-4">
+	{{if $.Data.Shop.HaveProducts}}
+		<div class="grid-products">
+			{{range $.Data.Shop.Products}}
+				<div class="card card-product">
+					<div class="card-img-link">
+						<a href="{{.Permalink}}">
+							{{if .HaveImages }}
+								<img class="card-img-top" src="{{.Image.Thumbnail1}}" alt="">
+							{{else}}
+								<img class="card-img-top" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22286%22%20height%3D%22180%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20286%20180%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_16c7e5ac360%20text%20%7B%20fill%3Argba(255%2C255%2C255%2C.75)%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_16c7e5ac360%22%3E%3Crect%20width%3D%22286%22%20height%3D%22180%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22107.0078125%22%20y%3D%2296.234375%22%3E286x180%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" alt="Card image cap">
+							{{end}}
+						</a>
+					</div>
+					<div class="card-body">
+						<h5 class="card-title">
+							<a href="{{.Permalink}}">
+								{{.Name}}
+							</a>
+						</h5>
+						<div class="card-text">{{.Briefly}}</div>
+					</div>
+					<div class="card-footer">
+						<span class="price">{{.PriceFormat "%.2f"}} {{.Currency.Code}}</span><a href="{{.Permalink}}" class="btn btn-primary">View</a>
+					</div>
+				</div>
+			{{end}}
+		</div>
+	{{else}}
+		<div class="card">
+			<div class="card-body">
+				Sorry, no products matched your criteria
+			</div>
+		</div>
+	{{end}}
+</div>
+{{if $.Data.Shop.HaveProducts}}
+	{{if gt $.Data.Shop.ProductsMaxPage 1 }}
+		<nav>
+			<ul class="pagination mb-4">
+				{{if $.Data.Shop.PaginationPrev}}
+					<li class="page-item{{if $.Data.Shop.PaginationPrev.Current}} disabled{{end}}">
+						<a class="page-link" href="{{$.Data.Shop.PaginationPrev.Link}}">Previous</a>
+					</li>
+				{{end}}
+				{{range $.Data.Shop.Pagination}}
+					{{if .Dots}}
+						<li class="page-item disabled"><a class="page-link" href="">...</a></li>
+					{{else}}
+						<li class="page-item{{if .Current}} active{{end}}">
+							<a class="page-link" href="{{.Link}}">{{.Num}}</a>
+						</li>
+					{{end}}
+				{{end}}
+				{{if $.Data.Shop.PaginationNext}}
+					<li class="page-item{{if $.Data.Shop.PaginationNext.Current}} disabled{{end}}">
+						<a class="page-link" href="{{$.Data.Shop.PaginationNext.Link}}">Next</a>
+					</li>
+				{{end}}
+			</ul>
+		</nav>
+	{{end}}
+{{end}}
 {{template "footer.html" .}}

+ 249 - 250
hosts/localhost/template/styles.css

@@ -1,250 +1,249 @@
-/* Fix bootstrap */
-@media (max-width: 991.98px) {
-	.navbar-expand-lg>.container,
-	.navbar-expand-lg>.container-fluid {
-		padding-right: 15px;
-		padding-left: 15px;
-	}
-}
-
-@media (max-width: 575px) {
-	.navbar-expand-lg>.container,
-	.navbar-expand-lg>.container-fluid {
-		padding-right: 0px;
-		padding-left: 0px;
-	}
-}
-
-@media (min-width: 992px) {
-	.navbar-expand-lg .navbar-nav {
-		margin-right: -.5rem;
-	}
-}
-
-/* Set base bootstrap width */
-@media (min-width: 1200px) {
-	.container {
-		 max-width: 1000px; 
-		/*background: red;*/
-	}
-	.navbar-expand-lg .navbar-nav {
-		margin-right: -.5rem;
-	}
-}
-
-/*@media (min-width: 1200px) { .container { max-width: 100%; } }
-@media (min-width: 992px) { .container { max-width: 100%; } }
-@media (min-width: 768px) { .container { max-width: 100%; } }*/
-
-/* Base font and colors */
-body {
-	color: #444;
-	font-size: 1.0rem;
-	font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-}
-
-.h1,
-.h2,
-.h3,
-.h4,
-.h5,
-.h6,
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
-footer {
-	color: #000;
-}
-
-.navbar-brand {
-	font-weight: bold;
-}
-
-h1 {
-	font-size: 250%;
-}
-
-/* Nice sticky footer */
-html,
-body {
-	height: 100%;
-}
-
-#wrap {
-	min-height: 100%;
-}
-
-#main {
-	overflow: auto;
-	padding-bottom: 4.5rem;
-}
-
-footer {
-	position: relative;
-	margin-top: -4.5rem;
-	height: 4.5rem;
-	clear: both;
-}
-
-/* Sticky top nav bar, body class "fixed-top-bar" */
-.fixed-top-bar .navbar {
-	position: fixed;
-	top: 0;
-	right: 0;
-	left: 0;
-	z-index: 1030;
-}
-
-.fixed-top-bar #main {
-	padding-top: 3.5rem;
-}
-
-/* Fave background */
-.bg-fave {
-	background: #205081 url(/assets/sys/bg.png) repeat 0 0;
-}
-
-/* Fix content marging */
-.page-content p:last-child {
-	margin-bottom: 0px;
-}
-
-/* Borders */
-.navbar {
-	border-bottom: 1px solid rgba(0, 0, 0, .125);
-}
-
-footer {
-	border-top: 1px solid rgba(0, 0, 0, .125);
-}
-
-/* Shop */
-.grid-products {
-	display: flex;
-	flex-flow: row wrap;
-	padding-top: 1px;
-	padding-left: 1px;
-	margin-right: -4px;
-}
-
-.grid-products .card-product {
-	width: 100%;
-	border-radius: 0;
-	margin-top: -1px;
-	margin-left: -1px;
-}
-
-@media (min-width: 768px) { .grid-products .card-product { width: 50%; } }
-@media (min-width: 992px) { .grid-products .card-product { width: 33.33333%; } }
-@media (min-width: 1200px) { .grid-products .card-product { width: 33.33333%; } }
-
-.grid-products .card-product:hover {
-	background-color: #f2f4f6;
-}
-
-.grid-products .card-product .card-img-link {
-	display: block;
-	padding: 1.25rem;
-	padding-bottom: 0;
-}
-
-.grid-products .card-product .card-img-link img {
-	display: block;
-	border-radius: 0;
-	opacity: 0.8;
-}
-
-.grid-products .card-product .card-title {
-	font-size: 1rem;
-}
-
-.grid-products .card-product .card-text {
-	font-size: 0.7rem;
-}
-
-.grid-products .card-product .card-footer {
-	padding-top: 0;
-	border-radius: 0;
-	border-top: 0;
-	background: transparent;
-	padding-bottom: 1.25rem;
-}
-
-.grid-products .card-product .price {
-	font-weight: bold;
-}
-
-.grid-products .card-product .btn {
-	float: right;
-}
-
-.product-full .price {
-	display: inline-block;
-	vertical-align: middle;
-}
-
-.product-full .btn-buy {
-	display: inline-block;
-	vertical-align: middle;
-}
-
-.product-full .product-description {
-	font-size: 1rem;
-}
-
-.product-full .product-description p:last-child {
-	margin-bottom: 0px;
-}
-
-.table-specifications .tcol-1,
-.table-specifications .tcol-2 {
-	width: 100%;
-	display: block;
-}
-
-.table-specifications .tcol-1 {
-	font-weight: bold;
-	border-bottom: none;
-	padding-bottom: 0px;
-}
-
-.table-specifications .tcol-2 {
-	border-top: none;
-	padding-top: 0px;
-}
-
-@media (min-width: 768px) {
-	.table-specifications .tcol-1,
-	.table-specifications .tcol-2 {
-		width: 50%;
-		display: table-cell;
-		font-weight: normal;
-		border: 1px solid #dee2e6;
-		padding: .75rem;
-	}
-}
-
-@media (min-width: 992px) {
-	.table-specifications .tcol-1,
-	.table-specifications .tcol-2 {
-		width: 50%;
-		display: table-cell;
-		font-weight: normal;
-		border: 1px solid #dee2e6;
-		padding: .75rem;
-	}
-}
-
-@media (min-width: 1200px) {
-	.table-specifications .tcol-1,
-	.table-specifications .tcol-2 {
-		width: 50%;
-		display: table-cell;
-		font-weight: normal;
-		border: 1px solid #dee2e6;
-		padding: .75rem;
-	}
-}
+/* Fix bootstrap */
+@media (max-width: 991.98px) {
+	.navbar-expand-lg>.container,
+	.navbar-expand-lg>.container-fluid {
+		padding-right: 15px;
+		padding-left: 15px;
+	}
+}
+
+@media (max-width: 575px) {
+	.navbar-expand-lg>.container,
+	.navbar-expand-lg>.container-fluid {
+		padding-right: 0px;
+		padding-left: 0px;
+	}
+}
+
+@media (min-width: 992px) {
+	.navbar-expand-lg .navbar-nav {
+		margin-right: -.5rem;
+	}
+}
+
+/* Set base bootstrap width */
+@media (min-width: 1200px) {
+	.container {
+		 max-width: 1000px; 
+		/*background: red;*/
+	}
+	.navbar-expand-lg .navbar-nav {
+		margin-right: -.5rem;
+	}
+}
+
+/*@media (min-width: 1200px) { .container { max-width: 100%; } }
+@media (min-width: 992px) { .container { max-width: 100%; } }
+@media (min-width: 768px) { .container { max-width: 100%; } }*/
+
+/* Base font and colors */
+body {
+	color: #444;
+	font-size: 1.0rem;
+	font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+.h1,
+.h2,
+.h3,
+.h4,
+.h5,
+.h6,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+footer {
+	color: #000;
+}
+
+.navbar-brand {
+	font-weight: bold;
+}
+
+h1 {
+	font-size: 250%;
+}
+
+/* Nice sticky footer */
+html,
+body {
+	height: 100%;
+}
+
+#wrap {
+	min-height: 100%;
+}
+
+#main {
+	overflow: auto;
+	padding-bottom: 4.5rem;
+}
+
+footer {
+	position: relative;
+	margin-top: -4.5rem;
+	height: 4.5rem;
+	clear: both;
+}
+
+/* Sticky top nav bar, body class "fixed-top-bar" */
+.fixed-top-bar .navbar {
+	position: fixed;
+	top: 0;
+	right: 0;
+	left: 0;
+	z-index: 1030;
+}
+
+.fixed-top-bar #main {
+	padding-top: 3.5rem;
+}
+
+/* Fave background */
+.bg-fave {
+	background: #205081 url(/assets/sys/bg.png) repeat 0 0;
+}
+
+/* Fix content marging */
+.page-content p:last-child {
+	margin-bottom: 0px;
+}
+
+/* Borders */
+.navbar {
+	border-bottom: 1px solid rgba(0, 0, 0, .125);
+}
+
+footer {
+	border-top: 1px solid rgba(0, 0, 0, .125);
+}
+
+/* Shop */
+.grid-products {
+	display: flex;
+	flex-flow: row wrap;
+	padding-top: 1px;
+	padding-left: 1px;
+	margin-right: -4px;
+}
+
+.grid-products .card-product {
+	width: 100%;
+	border-radius: 0;
+	margin-top: -1px;
+	margin-left: -1px;
+}
+
+@media (min-width: 768px) { .grid-products .card-product { width: 50%; } }
+@media (min-width: 992px) { .grid-products .card-product { width: 33.33333%; } }
+@media (min-width: 1200px) { .grid-products .card-product { width: 33.33333%; } }
+
+.grid-products .card-product:hover {
+	background-color: #f2f4f6;
+}
+
+.grid-products .card-product .card-img-link {
+	display: block;
+	padding: 1.25rem;
+	padding-bottom: 0;
+}
+
+.grid-products .card-product .card-img-link img {
+	display: block;
+	border-radius: 0;
+}
+
+.grid-products .card-product .card-title {
+	font-size: 1rem;
+}
+
+.grid-products .card-product .card-text {
+	font-size: 0.7rem;
+}
+
+.grid-products .card-product .card-footer {
+	padding-top: 0;
+	border-radius: 0;
+	border-top: 0;
+	background: transparent;
+	padding-bottom: 1.25rem;
+}
+
+.grid-products .card-product .price {
+	font-weight: bold;
+}
+
+.grid-products .card-product .btn {
+	float: right;
+}
+
+.product-full .price {
+	display: inline-block;
+	vertical-align: middle;
+}
+
+.product-full .btn-buy {
+	display: inline-block;
+	vertical-align: middle;
+}
+
+.product-full .product-description {
+	font-size: 1rem;
+}
+
+.product-full .product-description p:last-child {
+	margin-bottom: 0px;
+}
+
+.table-specifications .tcol-1,
+.table-specifications .tcol-2 {
+	width: 100%;
+	display: block;
+}
+
+.table-specifications .tcol-1 {
+	font-weight: bold;
+	border-bottom: none;
+	padding-bottom: 0px;
+}
+
+.table-specifications .tcol-2 {
+	border-top: none;
+	padding-top: 0px;
+}
+
+@media (min-width: 768px) {
+	.table-specifications .tcol-1,
+	.table-specifications .tcol-2 {
+		width: 50%;
+		display: table-cell;
+		font-weight: normal;
+		border: 1px solid #dee2e6;
+		padding: .75rem;
+	}
+}
+
+@media (min-width: 992px) {
+	.table-specifications .tcol-1,
+	.table-specifications .tcol-2 {
+		width: 50%;
+		display: table-cell;
+		font-weight: normal;
+		border: 1px solid #dee2e6;
+		padding: .75rem;
+	}
+}
+
+@media (min-width: 1200px) {
+	.table-specifications .tcol-1,
+	.table-specifications .tcol-2 {
+		width: 50%;
+		display: table-cell;
+		font-weight: normal;
+		border: 1px solid #dee2e6;
+		padding: .75rem;
+	}
+}

+ 0 - 6
modules/module_api.go

@@ -290,12 +290,6 @@ func (this *Modules) RegisterModule_Api() *Module {
 		Icon:   assets.SysSvgIconPage,
 		Sub:    &[]MISub{},
 	}, func(wrap *wrapper.Wrapper) {
-
-		// http://localhost:8080/api/product-image/thumb-cp/1/1565650500.png/
-		// http://localhost:8080/api/product-image/thumb-1/1/1565650500.png/
-		// http://localhost:8080/api/product-image/thumb-2/1/1565650500.png/
-		// http://localhost:8080/api/product-image/thumb-3/1/1565650500.png/
-
 		if len(wrap.UrlArgs) == 5 && wrap.UrlArgs[0] == "api" && wrap.UrlArgs[1] == "product-image" && (wrap.UrlArgs[2] == "thumb-0" || wrap.UrlArgs[2] == "thumb-1" || wrap.UrlArgs[2] == "thumb-2" || wrap.UrlArgs[2] == "thumb-3") {
 			thumb_type := wrap.UrlArgs[2]
 			product_id := wrap.UrlArgs[3]

+ 6 - 0
utils/mysql_struct_shop_product_image.go

@@ -0,0 +1,6 @@
+package utils
+
+type MySql_shop_product_image struct {
+	A_product_id int
+	A_filename   string
+}