Compare commits
No commits in common. "ts" and "master" have entirely different histories.
43 changed files with 1318 additions and 18773 deletions
|
@ -1,3 +0,0 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not ie <= 8
|
27
.gitignore
vendored
27
.gitignore
vendored
|
@ -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.
|
||||
|
|
72
api/api.go
Normal file
72
api/api.go
Normal file
|
@ -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'
|
||||
]
|
||||
}
|
5
components/footer.gohtml
Normal file
5
components/footer.gohtml
Normal file
|
@ -0,0 +1,5 @@
|
|||
{{ define "footer" }}
|
||||
<script src="/static/js/menu.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
{{ end }}
|
260
components/header.gohtml
Normal file
260
components/header.gohtml
Normal file
|
@ -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 }}
|
79
main.go
Normal file
79
main.go
Normal file
|
@ -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))
|
||||
}
|
||||
}
|
10590
package-lock.json
generated
10590
package-lock.json
generated
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>
|
74
sass/animeList.scss
Normal file
74
sass/animeList.scss
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
217
sass/base.scss
Normal file
217
sass/base.scss
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
159
sass/menu.scss
Normal file
159
sass/menu.scss
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
sass/variables.scss
Normal file
1
sass/variables.scss
Normal file
|
@ -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),
|
||||
}
|
38
src/types/shims-vue.d.ts
vendored
38
src/types/shims-vue.d.ts
vendored
|
@ -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>
|
BIN
static/fonts/material-icons.woff2
Normal file
BIN
static/fonts/material-icons.woff2
Normal file
Binary file not shown.
BIN
static/fonts/roboto.woff2
Normal file
BIN
static/fonts/roboto.woff2
Normal file
Binary file not shown.
362
static/img/bg.svg
Normal file
362
static/img/bg.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 130 KiB |
BIN
static/img/construction.png
Normal file
BIN
static/img/construction.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 533 KiB |
BIN
static/img/construction1.png
Normal file
BIN
static/img/construction1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 107 KiB |
BIN
static/img/construction2.png
Normal file
BIN
static/img/construction2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 390 KiB |
37
static/js/menu.js
Normal file
37
static/js/menu.js
Normal file
|
@ -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);
|
||||
}
|
24
templates/anime.gohtml
Normal file
24
templates/anime.gohtml
Normal file
|
@ -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" }}
|
2
templates/home.gohtml
Normal file
2
templates/home.gohtml
Normal file
|
@ -0,0 +1,2 @@
|
|||
{{ template "header" .}}
|
||||
{{ template "footer" }}
|
13
templates/index.gohtml
Normal file
13
templates/index.gohtml
Normal file
|
@ -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…
Add table
Add a link
Reference in a new issue