Compare commits
No commits in common. "ts" and "master" have entirely different histories.
|
@ -1,3 +0,0 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not ie <= 8
|
|
@ -1,21 +1,8 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw*
|
||||
gulpfile.js
|
||||
sftp-config.json
|
||||
build
|
||||
meikan
|
||||
static/css
|
||||
compile.bat
|
||||
meikan.sublime-workspace
|
30
README.md
30
README.md
|
@ -1,29 +1 @@
|
|||
# meikan
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Run your tests
|
||||
```
|
||||
npm run test
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
A [Meikan](https://git.fuyu.moe/Fuyu/meikan) client with a twist. Lets hope it will be beautiful.
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
type Genre struct {
|
||||
Name string `json:"name"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
type Anime struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Type string `json:"type"`
|
||||
Episodes int `json:"episodes"`
|
||||
State string `json:"state"`
|
||||
Rating string `json:"rating"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
Genres []Genre `json:"genres"`
|
||||
AverageDuration int `json:"average_duration"`
|
||||
AnidbID int `json:"anidb_id"`
|
||||
MyanimelistID int `json:"myanimelist_id"`
|
||||
}
|
||||
|
||||
type AnimeSearch struct {
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
var baseURL = "https://api.meikan.moe/v1/"
|
||||
|
||||
func getJSON(url string, target interface{}) error {
|
||||
r, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
return json.NewDecoder(r.Body).Decode(&target)
|
||||
}
|
||||
|
||||
func postJSON(url string, form AnimeSearch, target interface{}) error {
|
||||
b := new(bytes.Buffer)
|
||||
json.NewEncoder(b).Encode(form)
|
||||
|
||||
r, err := http.Post(url, "application/json; charset=utf-8", b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
return json.NewDecoder(r.Body).Decode(target)
|
||||
}
|
||||
|
||||
func AnimeByID(id string) (Anime, error) {
|
||||
anime := Anime{}
|
||||
url := baseURL + path.Join("anime", id)
|
||||
err := getJSON(url, &anime)
|
||||
return anime, err
|
||||
}
|
||||
|
||||
func SearchAnime(title string) ([]Anime, error) {
|
||||
anime := []Anime{}
|
||||
|
||||
form := AnimeSearch{Title: title}
|
||||
url := baseURL + "anime"
|
||||
err := postJSON(url, form, &anime)
|
||||
return anime, err
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{{ define "footer" }}
|
||||
<script src="/static/js/menu.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
{{ end }}
|
|
@ -0,0 +1,260 @@
|
|||
{{ define "header" }}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Meikan - Home</title>
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/base.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu">
|
||||
<div class="container">
|
||||
<a class="logo" href="/">
|
||||
<i class="material-icons">local_library</i><span>Meikan</span>
|
||||
</a>
|
||||
<a data-group="anime" href="/anime">
|
||||
anime
|
||||
</a>
|
||||
<a data-group="manga" href="/manga">
|
||||
manga
|
||||
</a>
|
||||
<a data-group="vn" href="/vn">
|
||||
vn
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="subMenu" class="submenu">
|
||||
<div class="container">
|
||||
<div data-group="anime" class="group">
|
||||
<div class="column">
|
||||
<div class="groupTitle">Top Anime</div>
|
||||
<ul>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="groupTitle">Top Genre</div>
|
||||
<ul>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="groupTitle">Random</div>
|
||||
<ul>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div data-group="manga" class="group">
|
||||
<div class="column">
|
||||
<div class="groupTitle">Top Manga</div>
|
||||
<ul>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="groupTitle">Top Genre</div>
|
||||
<ul>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="groupTitle">Random</div>
|
||||
<ul>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div data-group="vn" class="group">
|
||||
<div class="column">
|
||||
<div class="groupTitle">Top VN</div>
|
||||
<ul>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="groupTitle">Top Genre</div>
|
||||
<ul>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="groupTitle">Random</div>
|
||||
<ul>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title">Something</div>
|
||||
<div class="info">TV | 10 eps | Finished</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
|
@ -0,0 +1,79 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"./api"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
const sockPath = "/srv/kumori.moe/sock/meikan.sock"
|
||||
|
||||
var tmpl = template.Must(template.ParseGlob("templates/*.gohtml"))
|
||||
|
||||
func init() {
|
||||
l, _ := ioutil.ReadDir("components")
|
||||
for _, v := range l {
|
||||
c, _ := ioutil.ReadFile(`components/` + v.Name())
|
||||
tmpl, _ = tmpl.Parse(string(c))
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
os.Remove(sockPath)
|
||||
sock, err := net.Listen("unix", sockPath)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
return
|
||||
}
|
||||
|
||||
router := httprouter.New()
|
||||
|
||||
router.GET("/", getIndex)
|
||||
router.GET("/anime", getAnime)
|
||||
router.GET("/reload", reload)
|
||||
|
||||
router.ServeFiles("/static/*filepath", http.Dir("static/"))
|
||||
|
||||
err = os.Chmod(sockPath, 0770)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
log.Fatal(http.Serve(sock, router))
|
||||
}
|
||||
|
||||
func getIndex(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
err := tmpl.ExecuteTemplate(w, "home.gohtml", nil)
|
||||
if err != nil {
|
||||
println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func getAnime(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
animeList, err := api.SearchAnime("tsukaima")
|
||||
if err != nil {
|
||||
fmt.Fprint(w, err)
|
||||
return
|
||||
}
|
||||
err = tmpl.ExecuteTemplate(w, "anime.gohtml", &animeList)
|
||||
if err != nil {
|
||||
println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func reload(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
tmpl = template.Must(template.ParseGlob("templates/*.gohtml"))
|
||||
l, _ := ioutil.ReadDir("components")
|
||||
for _, v := range l {
|
||||
c, _ := ioutil.ReadFile(`components/` + v.Name())
|
||||
tmpl, _ = tmpl.Parse(string(c))
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
47
package.json
47
package.json
|
@ -1,46 +1,9 @@
|
|||
{
|
||||
"name": "meikan",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash": "^4.14.119",
|
||||
"lodash": "^4.17.11",
|
||||
"material-design-icons-iconfont": "^4.0.3",
|
||||
"vue": "^2.5.17",
|
||||
"vue-class-component": "^6.0.0",
|
||||
"vue-material": "^1.0.0-beta-10.2",
|
||||
"vue-property-decorator": "^7.0.0",
|
||||
"vue-router": "3.0.1",
|
||||
"vuex": "^3.0.1",
|
||||
"vuex-persist": "^2.0.0",
|
||||
"vuex-typex": "^3.0.1",
|
||||
"vuex-xhr-state": "^0.2.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/vue-material": "git+https://github.com/calebsander/vue-material-types.git",
|
||||
"@vue/cli-plugin-babel": "^3.2.0",
|
||||
"@vue/cli-plugin-typescript": "^3.2.0",
|
||||
"@vue/cli-service": "^3.2.0",
|
||||
"lint-staged": "^7.2.2",
|
||||
"typescript": "^3.0.0",
|
||||
"vue-template-compiler": "^2.5.17"
|
||||
},
|
||||
"gitHooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts": [
|
||||
"vue-cli-service lint",
|
||||
"git add"
|
||||
],
|
||||
"*.vue": [
|
||||
"vue-cli-service lint",
|
||||
"git add"
|
||||
]
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-sass": "^3.1.0",
|
||||
"gulp-sftp-with-callbacks": "^0.1.8",
|
||||
"request": "^2.81.0",
|
||||
"scp2": "^0.5.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,20 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic|Material+Icons">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
|
||||
<title>meikan</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but meikan doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,74 @@
|
|||
@import 'variables';
|
||||
.search {
|
||||
display: flex;
|
||||
.filters {
|
||||
width: 300px;
|
||||
}
|
||||
.cardList {
|
||||
padding: 20px 0;
|
||||
|
||||
flex: 1;
|
||||
.card {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
|
||||
border-radius: 2px;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,.12), 0 1px 2px rgba(0,0,0,.24);
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
img {
|
||||
height: 200px;
|
||||
}
|
||||
.body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
max-height: 200px;
|
||||
|
||||
flex: 1;
|
||||
.info {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
|
||||
padding: 16px 16px 0;
|
||||
|
||||
flex: 1;
|
||||
.title {
|
||||
font-size: 25px;
|
||||
|
||||
color: $pink;
|
||||
}
|
||||
.synopsis {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
|
||||
padding: 16px 0 0 0;
|
||||
|
||||
color: rgba(0,0,0,.52);
|
||||
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
.action {
|
||||
padding: 16px;
|
||||
|
||||
border-top: 1px rgba(0,0,0,.12) solid;
|
||||
button {
|
||||
font-size: 16px;
|
||||
|
||||
color: #448aff;
|
||||
border: none;
|
||||
background: none;
|
||||
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(/static/fonts/roboto.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
|
||||
src: local('Material Icons'), local('MaterialIcons-Regular'), url(/static/fonts/material-icons.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@import 'variables';
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
&:after {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
z-index: -1;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
content: '';
|
||||
|
||||
opacity: .05;
|
||||
background: url('/static/img/bg.svg');
|
||||
background-size: cover;
|
||||
}
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-size: 24px;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
line-height: 1;
|
||||
|
||||
display: inline-block;
|
||||
|
||||
white-space: nowrap;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
word-wrap: normal;
|
||||
|
||||
direction: ltr;
|
||||
-webkit-font-feature-settings: 'liga';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
.container {
|
||||
position: relative;
|
||||
|
||||
width: 1240px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.menu {
|
||||
font-size: 0;
|
||||
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
height: 65px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
background: #fff;
|
||||
box-shadow: 0 3px 6px rgba(0,0,0,.23), 0 3px 6px rgba(0,0,0,.16);
|
||||
a {
|
||||
font-size: 22px;
|
||||
font-variant: small-caps;
|
||||
line-height: 65px;
|
||||
|
||||
display: inline-block;
|
||||
|
||||
height: 100%;
|
||||
padding: 0 30px;
|
||||
|
||||
transition: background .1s, color .1s;
|
||||
vertical-align: middle;
|
||||
text-decoration: none;
|
||||
|
||||
color: rgba(0,0,0,.52);
|
||||
&:first-child {
|
||||
font-size: 30px;
|
||||
font-variant: normal;
|
||||
|
||||
padding-left: 0;
|
||||
|
||||
color: $pink;
|
||||
> i {
|
||||
font-size: 30px;
|
||||
line-height: inherit;
|
||||
|
||||
height: inherit;
|
||||
|
||||
vertical-align: top;
|
||||
}
|
||||
> span {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
&:not(:first-child):hover,
|
||||
&:not(:first-child).open {
|
||||
color: #fff;
|
||||
background: $pink;
|
||||
}
|
||||
}
|
||||
}
|
||||
.submenu {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 65px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
height: 0;
|
||||
|
||||
transition: height .5s;
|
||||
|
||||
background: #212121;
|
||||
box-shadow: inset 0 3px 6px rgba(0,0,0,.23);
|
||||
|
||||
will-change: height;
|
||||
&.open {
|
||||
height: 350px;
|
||||
}
|
||||
.container {
|
||||
height: 350px;
|
||||
.group {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
display: flex;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
opacity: 0;
|
||||
&.open {
|
||||
opacity: 1;
|
||||
}
|
||||
.column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
margin: 20px 10px;
|
||||
|
||||
color: #fff;
|
||||
|
||||
flex: 1;
|
||||
.groupTitle {
|
||||
line-height: 20px;
|
||||
|
||||
max-height: 20px;
|
||||
}
|
||||
> * {
|
||||
display: flex;
|
||||
|
||||
height: 100%;
|
||||
}
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
margin: 20px 0 0 10px;
|
||||
padding: 10px 20px;
|
||||
|
||||
list-style: none;
|
||||
|
||||
border-left: 2px rgba(255,255,255,.1) solid;
|
||||
li {
|
||||
position: relative;
|
||||
|
||||
flex: 1;
|
||||
.title {
|
||||
font-size: 16px;
|
||||
|
||||
display: block;
|
||||
|
||||
margin-bottom: 3px;
|
||||
|
||||
color: #ff80ab;
|
||||
}
|
||||
.info {
|
||||
font-size: .8em;
|
||||
font-weight: light;
|
||||
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
.menu
|
||||
{
|
||||
font-size: 0;
|
||||
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
height: 65px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
background: #fff;
|
||||
box-shadow: 0 3px 6px rgba(0,0,0,.23), 0 3px 6px rgba(0,0,0,.16);
|
||||
a
|
||||
{
|
||||
font-size: 22px;
|
||||
font-variant: small-caps;
|
||||
line-height: 65px;
|
||||
|
||||
display: inline-block;
|
||||
|
||||
height: 100%;
|
||||
padding: 0 30px;
|
||||
|
||||
transition: background .1s, color .1s;
|
||||
vertical-align: middle;
|
||||
text-decoration: none;
|
||||
|
||||
color: rgba(0,0,0,.52);
|
||||
&:first-child
|
||||
{
|
||||
font-size: 30px;
|
||||
font-variant: normal;
|
||||
|
||||
padding-left: 0;
|
||||
|
||||
color: #ff4081;
|
||||
> i {
|
||||
height: inherit;
|
||||
line-height: inherit;
|
||||
vertical-align: top;
|
||||
font-size: 30px;
|
||||
}
|
||||
> span
|
||||
{
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
&:not(:first-child):hover,
|
||||
&:not(:first-child).open
|
||||
{
|
||||
color: #fff;
|
||||
background: #ff4081;
|
||||
}
|
||||
}
|
||||
}
|
||||
.submenu
|
||||
{
|
||||
position: absolute;
|
||||
top: 65px;
|
||||
z-index: 1;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
height: 0;
|
||||
|
||||
transition: height .5s;
|
||||
|
||||
background: #212121;
|
||||
box-shadow: inset 0 3px 6px rgba(0,0,0,.23);
|
||||
|
||||
will-change: height;
|
||||
&.open
|
||||
{
|
||||
height: 350px;
|
||||
}
|
||||
.container
|
||||
{
|
||||
height: 350px;
|
||||
.group
|
||||
{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
display: flex;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
opacity: 0;
|
||||
&.open
|
||||
{
|
||||
opacity: 1;
|
||||
}
|
||||
.column
|
||||
{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
margin: 20px 10px;
|
||||
|
||||
color: #fff;
|
||||
|
||||
flex: 1;
|
||||
.groupTitle
|
||||
{
|
||||
line-height: 20px;
|
||||
|
||||
max-height: 20px;
|
||||
}
|
||||
> *
|
||||
{
|
||||
display: flex;
|
||||
|
||||
height: 100%;
|
||||
}
|
||||
ul
|
||||
{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
margin: 20px 0 0 10px;
|
||||
padding: 10px 20px;
|
||||
|
||||
list-style: none;
|
||||
|
||||
border-left: 2px rgba(255,255,255,.1) solid;
|
||||
li
|
||||
{
|
||||
position: relative;
|
||||
|
||||
flex: 1;
|
||||
.title
|
||||
{
|
||||
font-size: 16px;
|
||||
|
||||
display: block;
|
||||
|
||||
margin-bottom: 3px;
|
||||
|
||||
color: #ff80ab;
|
||||
}
|
||||
.info
|
||||
{
|
||||
font-size: .8em;
|
||||
font-weight: light;
|
||||
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
$pink: #ff4081;
|
|
@ -1,5 +0,0 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB |
|
@ -1,19 +0,0 @@
|
|||
export const GetUserAnime = async (userID: number): Promise<Meikan.UserAnime[]> => {
|
||||
const res = await fetch(`https://api.meikan.moe/v1/users/${userID}/anime`)
|
||||
const anime: Meikan.UserAnime[] = await res.json()
|
||||
|
||||
return anime
|
||||
}
|
||||
|
||||
export const FindUsers = async (name: string): Promise<Meikan.User[]> => {
|
||||
const res = await fetch('https://api.meikan.moe/v1/users', {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
}),
|
||||
})
|
||||
const users: Meikan.User[] = await res.json()
|
||||
|
||||
return users
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<md-toolbar class="md-primary" md-elevation="0">
|
||||
<h3 class="md-title">Meikan</h3>
|
||||
<div class="md-toolbar-section-end">
|
||||
<h3 v-if="currentUser" class="md-subheading">
|
||||
{{ currentUser.name }}
|
||||
</h3>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<md-tabs class="md-primary" md-sync-route>
|
||||
<md-tab id="tab-user" md-label="Users" to="/"></md-tab>
|
||||
<md-tab v-if="currentUser" id="tab-anime" md-label="Anime" to="/anime"></md-tab>
|
||||
</md-tabs>
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import { namespace } from 'vuex-class'
|
||||
|
||||
const user = namespace('user')
|
||||
|
||||
@Component({})
|
||||
export default class Base extends Vue {
|
||||
@user.Getter('currentUser') private currentUser!: Meikan.User | null
|
||||
}
|
||||
</script>
|
18
src/main.ts
18
src/main.ts
|
@ -1,18 +0,0 @@
|
|||
import Vue from 'vue'
|
||||
|
||||
import VueMaterial from 'vue-material'
|
||||
import 'vue-material/dist/theme/default-dark.css'
|
||||
import 'vue-material/dist/vue-material.min.css'
|
||||
Vue.use(VueMaterial)
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: (h) => h(App),
|
||||
}).$mount('#app')
|
|
@ -1,32 +0,0 @@
|
|||
import Store from '@/store'
|
||||
import Vue from 'vue'
|
||||
|
||||
import Router from 'vue-router'
|
||||
Vue.use(Router)
|
||||
|
||||
import Base from './layouts/Base.vue'
|
||||
import UserAnime from './views/UserAnime.vue'
|
||||
import UserSelect from './views/UserSelect.vue'
|
||||
|
||||
export default new Router({
|
||||
mode: 'history',
|
||||
base: process.env.BASE_URL,
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
component: Base,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'userSelect',
|
||||
component: UserSelect,
|
||||
},
|
||||
{
|
||||
path: 'anime',
|
||||
name: 'userAnime',
|
||||
component: UserAnime,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
|
@ -1,27 +0,0 @@
|
|||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import VuexPersistence from 'vuex-persist'
|
||||
import { getStoreBuilder } from 'vuex-typex'
|
||||
|
||||
import './module/user'
|
||||
import { UserState } from './module/user'
|
||||
|
||||
import './module/userAnime'
|
||||
import { UserAnimeState } from './module/userAnime'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export interface RootState {
|
||||
user: UserState
|
||||
userAnime: UserAnimeState
|
||||
}
|
||||
|
||||
const vuexLocal = new VuexPersistence({
|
||||
storage: window.localStorage,
|
||||
})
|
||||
|
||||
const store = getStoreBuilder<RootState>().vuexStore({
|
||||
plugins: [vuexLocal.plugin],
|
||||
})
|
||||
|
||||
export default store
|
|
@ -1,58 +0,0 @@
|
|||
import { FindUsers } from '@/domain'
|
||||
import { RootState } from '@/store'
|
||||
import { BareActionContext, getStoreBuilder } from 'vuex-typex'
|
||||
|
||||
export interface UserState {
|
||||
currentUser: Meikan.User | null
|
||||
foundUsers: Meikan.User[]
|
||||
}
|
||||
|
||||
const initialState: UserState = {
|
||||
currentUser: null,
|
||||
foundUsers: [],
|
||||
}
|
||||
const builder = getStoreBuilder<RootState>().module('user', initialState)
|
||||
|
||||
// Getters
|
||||
const getterUserList = builder.read(function foundUsers(state: UserState) {
|
||||
return state.foundUsers
|
||||
})
|
||||
|
||||
const getterCurrentUser = builder.read(function currentUser(state: UserState) {
|
||||
return state.currentUser
|
||||
})
|
||||
|
||||
// Mutations
|
||||
function setUserList(state: UserState, foundUsers: Meikan.User[]) {
|
||||
state.foundUsers = foundUsers
|
||||
}
|
||||
|
||||
function setCurrentUser(state: UserState, currentUser: Meikan.User) {
|
||||
state.currentUser = currentUser
|
||||
}
|
||||
|
||||
// Action
|
||||
async function findUsers(context: BareActionContext<UserState, RootState>, name: string) {
|
||||
const list = await FindUsers(name)
|
||||
|
||||
user.setUserList(list || [])
|
||||
}
|
||||
|
||||
function resetFoundUsers(context: BareActionContext<UserState, RootState>) {
|
||||
user.setUserList([])
|
||||
}
|
||||
|
||||
export const user = {
|
||||
get foundUsers(): Meikan.User[] {
|
||||
return getterUserList()
|
||||
},
|
||||
|
||||
get currentUser(): Meikan.User | null {
|
||||
return getterCurrentUser()
|
||||
},
|
||||
|
||||
setCurrentUser: builder.commit(setCurrentUser),
|
||||
setUserList: builder.commit(setUserList),
|
||||
resetFoundUsers: builder.dispatch(resetFoundUsers),
|
||||
findUsers: builder.dispatch(findUsers),
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
import { GetUserAnime } from '@/domain'
|
||||
import { RootState } from '@/store'
|
||||
import { BareActionContext, getStoreBuilder } from 'vuex-typex'
|
||||
|
||||
export interface UserAnimeState {
|
||||
userAnime: Meikan.UserAnime[]
|
||||
}
|
||||
|
||||
const initialState: UserAnimeState = {
|
||||
userAnime: [],
|
||||
}
|
||||
const builder = getStoreBuilder<RootState>().module('userAnime', initialState)
|
||||
|
||||
// Getters
|
||||
const getterUserAnime = builder.read(function anime(state: UserAnimeState) {
|
||||
return state.userAnime
|
||||
})
|
||||
|
||||
// Mutations
|
||||
function setAnime(state: UserAnimeState, animeList: Meikan.UserAnime[]) {
|
||||
state.userAnime = animeList
|
||||
}
|
||||
|
||||
// Action
|
||||
async function fetchUserAnime(context: BareActionContext<UserAnimeState, RootState>, user: Meikan.User) {
|
||||
const list = await GetUserAnime(user.id)
|
||||
|
||||
userAnimeState.setAnime(list)
|
||||
}
|
||||
|
||||
function resetUserAnime(context: BareActionContext<UserAnimeState, RootState>) {
|
||||
userAnimeState.setAnime([])
|
||||
}
|
||||
|
||||
export const userAnimeState = {
|
||||
get anime(): Meikan.UserAnime[] {
|
||||
return getterUserAnime()
|
||||
},
|
||||
|
||||
setAnime: builder.commit(setAnime),
|
||||
fetchUserAnime: builder.dispatch(fetchUserAnime),
|
||||
resetUserAnime: builder.dispatch(resetUserAnime),
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
declare module '*.vue' {
|
||||
import Vue from 'vue'
|
||||
export default Vue
|
||||
}
|
||||
|
||||
declare namespace Meikan {
|
||||
export interface User {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface Anime {
|
||||
id: number
|
||||
title: string
|
||||
type: string
|
||||
episodes: number
|
||||
state: string
|
||||
rating: string
|
||||
start_date: string
|
||||
}
|
||||
|
||||
export interface DetailedAnime extends Anime {
|
||||
end_date: string
|
||||
genres: string[]
|
||||
average_duration: number
|
||||
anidb_id: number
|
||||
myanimelist_id: number
|
||||
}
|
||||
|
||||
export interface UserAnime {
|
||||
anime: Anime
|
||||
state: string
|
||||
episode: number
|
||||
rating?: number
|
||||
hidden: boolean
|
||||
recommend: boolean
|
||||
}
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
<template>
|
||||
<md-table v-model="searched" md-card md-sort="id" md-sort-order="asc">
|
||||
<md-table-toolbar>
|
||||
<h1 class="md-title md-toolbar-section-start">{{ currentUser.name }}'s anime</h1>
|
||||
<div class="md-toolbar-section-end">
|
||||
<md-field md-clearable>
|
||||
<md-input placeholder="Search by name..." v-model="search" @input="searchOnTable" />
|
||||
</md-field>
|
||||
<md-button
|
||||
class="md-icon-button md-primary"
|
||||
@click="refresh"
|
||||
>
|
||||
<md-icon>refresh</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-table-toolbar>
|
||||
|
||||
<md-table-empty-state v-if="loading">
|
||||
<md-progress-spinner class="md-accent" md-mode="indeterminate"></md-progress-spinner>
|
||||
</md-table-empty-state>
|
||||
<md-table-empty-state
|
||||
v-else
|
||||
md-label="No results"
|
||||
md-description="It's a void"
|
||||
/>
|
||||
|
||||
<md-table-row slot="md-table-row" slot-scope="{ item }">
|
||||
<md-table-cell md-label="#" md-numeric md-sort-by="anime.id">{{ item.anime.id }}</md-table-cell>
|
||||
<md-table-cell md-label="Title" md-sort-by="anime.title">{{ item.anime.title }}</md-table-cell>
|
||||
</md-table-row>
|
||||
</md-table>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import { namespace } from 'vuex-class'
|
||||
|
||||
const user = namespace('user')
|
||||
const userAnime = namespace('userAnime')
|
||||
|
||||
@Component({})
|
||||
export default class Home extends Vue {
|
||||
public loading: boolean = false
|
||||
|
||||
public search: string = ''
|
||||
public searched: Meikan.UserAnime[] = []
|
||||
|
||||
@user.Getter
|
||||
private currentUser!: Meikan.User
|
||||
|
||||
@userAnime.Action
|
||||
private resetUserAnime!: () => void
|
||||
|
||||
@userAnime.Action
|
||||
private fetchUserAnime!: (user: Meikan.User) => Promise<Meikan.UserAnime[]>
|
||||
|
||||
@userAnime.Getter
|
||||
private anime!: Meikan.UserAnime[]
|
||||
|
||||
private created() {
|
||||
if (this.anime.length !== 0) {
|
||||
this.searched = this.anime
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
this.fetchUserAnime(this.currentUser)
|
||||
.then(() => {
|
||||
this.loading = false
|
||||
this.searched = this.anime
|
||||
})
|
||||
}
|
||||
|
||||
private refresh() {
|
||||
this.resetUserAnime()
|
||||
|
||||
this.loading = true
|
||||
this.fetchUserAnime(this.currentUser)
|
||||
.then(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
private searchOnTable() {
|
||||
if (this.search.length === 0) {
|
||||
this.searched = this.anime
|
||||
}
|
||||
|
||||
this.searched = this.anime.filter((item) => {
|
||||
return item.anime.title
|
||||
.toLowerCase()
|
||||
.includes(this.search.toLowerCase())
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,67 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<md-table v-model="foundUsers" @md-selected="onSelect">
|
||||
<md-table-toolbar>
|
||||
<div class="md-toolbar-section-start">
|
||||
<h1 class="md-title">Users</h1>
|
||||
</div>
|
||||
|
||||
<md-field md-clearable class="md-toolbar-section-end">
|
||||
<md-input placeholder="Search users" @input="onInput" />
|
||||
</md-field>
|
||||
</md-table-toolbar>
|
||||
|
||||
<md-table-row slot="md-table-row" slot-scope="{ item }" md-selectable="single">
|
||||
<md-table-cell md-label="#" md-numeric width="100">{{ item.id }}</md-table-cell>
|
||||
<md-table-cell md-label="Title" md-sort-by="title">{{ item.name }}</md-table-cell>
|
||||
</md-table-row>
|
||||
</md-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import _ from 'lodash'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import { namespace } from 'vuex-class'
|
||||
|
||||
const userStore = namespace('user')
|
||||
const userAnimeStore = namespace('userAnime')
|
||||
|
||||
@Component({})
|
||||
export default class Home extends Vue {
|
||||
@userStore.Mutation('setCurrentUser')
|
||||
private setCurrentUser!: (user: Meikan.User) => void
|
||||
|
||||
@userStore.Action('findUsers')
|
||||
private findUsers!: (name: string) => Promise<Meikan.User[]>
|
||||
|
||||
@userStore.Action('resetFoundUsers')
|
||||
private resetFoundUsers!: () => void
|
||||
|
||||
@userStore.Getter('foundUsers')
|
||||
private foundUsers!: Meikan.User[]
|
||||
|
||||
@userAnimeStore.Action
|
||||
private resetUserAnime!: () => void
|
||||
|
||||
private onInput = _.debounce((name: string) => {
|
||||
if (name === '') {
|
||||
this.resetFoundUsers()
|
||||
return
|
||||
}
|
||||
|
||||
this.findUsers(name)
|
||||
}, 200)
|
||||
|
||||
private onSelect(user: Meikan.User) {
|
||||
this.setCurrentUser(user)
|
||||
this.resetUserAnime()
|
||||
|
||||
this.$router.push({ name: 'userAnime' })
|
||||
}
|
||||
|
||||
private created() {
|
||||
this.resetFoundUsers()
|
||||
}
|
||||
}
|
||||
</script>
|
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 130 KiB |
Binary file not shown.
After Width: | Height: | Size: 533 KiB |
Binary file not shown.
After Width: | Height: | Size: 107 KiB |
Binary file not shown.
After Width: | Height: | Size: 390 KiB |
|
@ -0,0 +1,37 @@
|
|||
let menus = document.querySelectorAll('a[data-group]'),
|
||||
subMenu = document.getElementById("subMenu"),
|
||||
activeSubMenuGroup = [
|
||||
document.querySelector(`div.group[data-group]`),
|
||||
document.querySelector(`a[data-group]`)
|
||||
];
|
||||
subMenuGroups = [];
|
||||
|
||||
function openMenu(event){
|
||||
activeSubMenuGroup[0].classList.remove("open");
|
||||
activeSubMenuGroup[1].classList.remove("open");
|
||||
activeSubMenuGroup[0] = subMenuGroups[this.dataset.group];
|
||||
activeSubMenuGroup[1] = this;
|
||||
|
||||
this.classList.add("open");
|
||||
subMenu.classList.add("open");
|
||||
activeSubMenuGroup[0].classList.add("open");
|
||||
}
|
||||
|
||||
function keepMenuOpen(event){
|
||||
subMenu.classList.add("open");
|
||||
activeSubMenuGroup[1].classList.add("open")
|
||||
}
|
||||
|
||||
function closeMenu(event){
|
||||
subMenu.classList.remove("open");
|
||||
activeSubMenuGroup[1].classList.remove("open");
|
||||
}
|
||||
|
||||
subMenu.addEventListener("mouseenter", keepMenuOpen);
|
||||
subMenu.addEventListener("mouseleave", closeMenu);
|
||||
|
||||
for(let m of menus){
|
||||
subMenuGroups[m.dataset.group] = document.querySelector(`div.group[data-group="${m.dataset.group}"]`);
|
||||
m.addEventListener("mouseenter", openMenu);
|
||||
m.addEventListener("mouseleave", closeMenu);
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{{ template "header" }}
|
||||
<link rel="stylesheet" href="/static/css/animeList.css">
|
||||
<div class="container">
|
||||
<div class="search">
|
||||
<div class="filters"></div>
|
||||
<div class="cardList">
|
||||
{{ range . }}
|
||||
<div class="card">
|
||||
<img src="http://s1.narvii.com/image/d67vdzixlgx6jmy35vg2dgomjfhalqtf_hq.jpg">
|
||||
<div class="body">
|
||||
<div class="info">
|
||||
<div class="title">{{ .Title }}</div>
|
||||
<div class="synopsis">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Inventore iure deleniti iste harum, nobis nesciunt aliquid temporibus aspernatur corrupti est. Non fugiat illum ullam rem harum tempore neque, magni architecto. Non natus est officia dolor, obcaecati autem odio molestias assumenda. Minus quae ducimus aut reprehenderit commodi, voluptatum quis nulla autem.</div>
|
||||
</div>
|
||||
<div class="action">
|
||||
<button>read more</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ template "footer" }}
|
|
@ -0,0 +1,2 @@
|
|||
{{ template "header" .}}
|
||||
{{ template "footer" }}
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html style="height: 100%">
|
||||
<head>
|
||||
<title>Meikan</title>
|
||||
<!-- <link rel="stylesheet" type="text/css" href="css/base.css"> -->
|
||||
</head>
|
||||
<body style="height: 100%">
|
||||
<div style="position: relative; top: 50%; text-align: center; transform: translateY(-50%);">
|
||||
<img src="/img/construction.png" style="width: 500px; vertical-align: middle;">
|
||||
<div style="margin-top: 50px; font-family: Arial; font-weight: bold; font-size: 40px; opacity: .87;">Under construction</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,40 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
"noImplicitAny": true,
|
||||
"baseUrl": ".",
|
||||
"types": [
|
||||
"webpack-env"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"tests/**/*.ts",
|
||||
"tests/**/*.tsx",
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
]
|
||||
}
|
21
tslint.json
21
tslint.json
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"defaultSeverity": "warning",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"linterOptions": {
|
||||
"exclude": [
|
||||
"node_modules/**"
|
||||
]
|
||||
},
|
||||
"rules": {
|
||||
"quotemark": [true, "single"],
|
||||
"indent": [true, "tabs", 4],
|
||||
"interface-name": false,
|
||||
"ordered-imports": true,
|
||||
"object-literal-sort-keys": false,
|
||||
"no-consecutive-blank-lines": true,
|
||||
"no-namespace": false,
|
||||
"semicolon": [true, "never"]
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = {
|
||||
lintOnSave: false
|
||||
}
|
Loading…
Reference in New Issue