├── .gitignore
├── README.md
├── conf
├── app.conf
├── docTree.json
├── locale_en-US.ini
└── locale_zh-CN.ini
├── controllers
├── controller.go
├── default.go
└── docs.go
├── main.go
├── models
├── github.go
├── http.go
├── press.go
└── product.go
├── static
├── css
│ ├── admin.css
│ ├── amazeui.css
│ ├── amazeui.flat.css
│ ├── amazeui.flat.min.css
│ ├── amazeui.min.css
│ ├── app.css
│ └── index.css
├── favicon.ico
├── fonts
│ ├── FontAwesome.otf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.svg
│ ├── fontawesome-webfont.ttf
│ └── fontawesome-webfont.woff
├── images
│ ├── InfoQ.jpg
│ ├── bearychat.png
│ ├── bowen.png
│ ├── coding.png
│ ├── csdn.png
│ ├── daocloud.jpg
│ ├── daocloud.png
│ ├── dreamlab.png
│ ├── fequan.jpg
│ ├── gitcafe.png
│ ├── gophercomplex1.jpg
│ ├── gophercomplex2.jpg
│ ├── gopherconf.png
│ ├── huawei.png
│ ├── imooc.png
│ ├── jd.png
│ ├── oschina.png
│ ├── osforce.jpg
│ ├── pusoft.jpg
│ ├── pusoft1.jpg
│ ├── pusoft2.jpg
│ ├── pusoft_map.jpg
│ ├── qiniu.jpg
│ ├── segmentfault.png
│ ├── techparty.jpg
│ ├── tuling.jpg
│ └── xd.png
├── img
│ ├── chenmingda.png
│ ├── gonghaohua.jpg
│ ├── lisibao.jpg
│ ├── liuqi.png
│ ├── maquanyi.jpg
│ ├── qleelulu.jpg
│ ├── xushiwei.jpg
│ └── zhouyang.jpg
└── js
│ ├── amazeui.js
│ ├── amazeui.legacy.js
│ ├── amazeui.legacy.min.js
│ ├── amazeui.min.js
│ ├── amazeui.widgets.helper.js
│ ├── amazeui.widgets.helper.min.js
│ ├── app.js
│ ├── handlebars.min.js
│ ├── jquery.min.js
│ └── polyfill
│ ├── rem.min.js
│ └── respond.min.js
└── views
├── base
├── footer.html
└── header.html
├── detail.tpl
├── index.tpl
├── register.tpl
├── speaker.tpl
├── sponsors.tpl
└── venue.tpl
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | *~
3 | .DS_Store
4 | *.tmp
5 | *.tmp
6 | *.pprof
7 | *.pdf
8 |
9 | /docs
10 | /tests
11 | website
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Gopher China Website
2 | ===================
3 |
4 | Gopher China Website
5 |
6 |
--------------------------------------------------------------------------------
/conf/app.conf:
--------------------------------------------------------------------------------
1 | appname = website
2 | httpport = 8080
3 | runmode = dev
4 |
5 |
6 | [lang]
7 | types=en-US|zh-CN
8 | names=English|简体中文
9 |
--------------------------------------------------------------------------------
/conf/docTree.json:
--------------------------------------------------------------------------------
1 | {"Tree":[{"Sha":"51473709537ae2a9b5342c9f2eff1fc3e7c2dc76","Path":"en-US/accommodation","Type":""},{"Sha":"1be9000125ff8f011ad246ac56ca5d02383760e7","Path":"en-US/images/chenmingda.png","Type":""},{"Sha":"88fc793c5105724ecb35347b04009a42c1ce693b","Path":"en-US/images/go.jpg","Type":""},{"Sha":"130d26e57274a3b4d600e44c98d4a032647458d2","Path":"en-US/images/gonghaohua.jpg","Type":""},{"Sha":"8dbe97e3180290accf9d49c3bb8b11639ce823c3","Path":"en-US/images/lisibao.jpg","Type":""},{"Sha":"fac54daa97dabd5b85a8191e610002c5a579c468","Path":"en-US/images/liuqi.png","Type":""},{"Sha":"adfd75aa20e709a4c0fd094b92a933c2f220bae8","Path":"en-US/images/maojian.jpg","Type":""},{"Sha":"3faef7d49392ef908c73761e8ee43ccb720eed83","Path":"en-US/images/maquanyi.jpg","Type":""},{"Sha":"7a6c39d25cd988548ddb95e917e2643d1dc60a70","Path":"en-US/images/qleelulu.jpg","Type":""},{"Sha":"494ce5157c460328780857ca1b88c59a01ad7a04","Path":"en-US/images/robert.jpeg","Type":""},{"Sha":"fe70f4e2a370b0c4213d3fa521cd4717c4a59207","Path":"en-US/images/wangyang.jpg","Type":""},{"Sha":"cdf63d4f2de23db6f654bcc4ca47c4a4a86dec9a","Path":"en-US/images/xiabin.jpg","Type":""},{"Sha":"b17b52a4cf5868dec1c757d6a3dccba468b510a5","Path":"en-US/images/xudaoli.jpg","Type":""},{"Sha":"9e6f61c82bc2bf6ea9ec8eb87feaab9acc8ae308","Path":"en-US/images/xushiwei.jpg","Type":""},{"Sha":"ba7cdd8352823e623f251e78a1bf839a3648fcf8","Path":"en-US/images/yuhen.jpg","Type":""},{"Sha":"a12cf783c59e26e8829eb59f5107a669f239ec54","Path":"en-US/images/yujun.jpg","Type":""},{"Sha":"3e90c26b9eae4a160960b12a1eaf1b9ba30d9a2f","Path":"en-US/images/zhangchengyuan.png","Type":""},{"Sha":"8ab03335f4a33ea213eabae1a2edc62c62d43571","Path":"en-US/images/zhouyang.jpg","Type":""},{"Sha":"4ab3647fe248f808f2285fcb7d15a0c6b74b5c34","Path":"en-US/travel","Type":""},{"Sha":"1b7d5e0ce1b12ad78060c84aadf535c28fae0a89","Path":"en-US/user/chenmingda","Type":""},{"Sha":"8bf61ccee740c3e1396553535a667d926c47575c","Path":"en-US/user/core","Type":""},{"Sha":"f7d86f80f81e6d9ade94c32fc5f50d3ce8c94127","Path":"en-US/user/gonghaohua","Type":""},{"Sha":"3c4d0e445e82d8d6857e2970d07830daf4c297fc","Path":"en-US/user/lisibao","Type":""},{"Sha":"599dc761fe2a83be1f4fc5acfeb3394960ec87c8","Path":"en-US/user/liuqi","Type":""},{"Sha":"04108c1b7f332754bb86545e8f2283f625a2e57b","Path":"en-US/user/maojian","Type":""},{"Sha":"8f4d7c497ebeb29f876888b32109f7c0d17840e1","Path":"en-US/user/maquanyi","Type":""},{"Sha":"b27e1ece5c5c2cd0ac475c9d7e98662094eed497","Path":"en-US/user/qleelulu","Type":""},{"Sha":"13c15fb7fad042fe668c3a154d6723aeeb4a98c9","Path":"en-US/user/robert","Type":""},{"Sha":"8000c3011558fff240b25f7096cdd61ffdb42711","Path":"en-US/user/wangyang","Type":""},{"Sha":"f6617d7688463101c3e95f1ba72c8572b6327ca6","Path":"en-US/user/xiabin","Type":""},{"Sha":"bf84b5454a9db45e55a9f54800df900c8184f9a3","Path":"en-US/user/xudaoli","Type":""},{"Sha":"ac1b2a4e7a8b5a7c7113d3515a15b5175acee1bb","Path":"en-US/user/xushiwei","Type":""},{"Sha":"ddce203cf99cb32349fb7b13aa1eb0af3273bac6","Path":"en-US/user/yuhen","Type":""},{"Sha":"0826eae155bdc50bd169fe7c7a305d08ab732af4","Path":"en-US/user/yujun","Type":""},{"Sha":"e4636cd67e14f051b650c45e73552b7a9221b76e","Path":"en-US/user/zhangchengyuan","Type":""},{"Sha":"4040e6496153a1013d0d60e1d5712dfa6a187275","Path":"en-US/user/zhouyang","Type":""},{"Sha":"6e8d9a8612bf158a956493909a028bae0dc73435","Path":"zh-CN/accommodation","Type":""},{"Sha":"1be9000125ff8f011ad246ac56ca5d02383760e7","Path":"zh-CN/images/chenmingda.png","Type":""},{"Sha":"88fc793c5105724ecb35347b04009a42c1ce693b","Path":"zh-CN/images/go.jpg","Type":""},{"Sha":"130d26e57274a3b4d600e44c98d4a032647458d2","Path":"zh-CN/images/gonghaohua.jpg","Type":""},{"Sha":"8dbe97e3180290accf9d49c3bb8b11639ce823c3","Path":"zh-CN/images/lisibao.jpg","Type":""},{"Sha":"fac54daa97dabd5b85a8191e610002c5a579c468","Path":"zh-CN/images/liuqi.png","Type":""},{"Sha":"adfd75aa20e709a4c0fd094b92a933c2f220bae8","Path":"zh-CN/images/maojian.jpg","Type":""},{"Sha":"3faef7d49392ef908c73761e8ee43ccb720eed83","Path":"zh-CN/images/maquanyi.jpg","Type":""},{"Sha":"7a6c39d25cd988548ddb95e917e2643d1dc60a70","Path":"zh-CN/images/qleelulu.jpg","Type":""},{"Sha":"494ce5157c460328780857ca1b88c59a01ad7a04","Path":"zh-CN/images/robert.jpeg","Type":""},{"Sha":"fe70f4e2a370b0c4213d3fa521cd4717c4a59207","Path":"zh-CN/images/wangyang.jpg","Type":""},{"Sha":"cdf63d4f2de23db6f654bcc4ca47c4a4a86dec9a","Path":"zh-CN/images/xiabin.jpg","Type":""},{"Sha":"b17b52a4cf5868dec1c757d6a3dccba468b510a5","Path":"zh-CN/images/xudaoli.jpg","Type":""},{"Sha":"9e6f61c82bc2bf6ea9ec8eb87feaab9acc8ae308","Path":"zh-CN/images/xushiwei.jpg","Type":""},{"Sha":"ba7cdd8352823e623f251e78a1bf839a3648fcf8","Path":"zh-CN/images/yuhen.jpg","Type":""},{"Sha":"a12cf783c59e26e8829eb59f5107a669f239ec54","Path":"zh-CN/images/yujun.jpg","Type":""},{"Sha":"3e90c26b9eae4a160960b12a1eaf1b9ba30d9a2f","Path":"zh-CN/images/zhangchengyuan.png","Type":""},{"Sha":"8ab03335f4a33ea213eabae1a2edc62c62d43571","Path":"zh-CN/images/zhouyang.jpg","Type":""},{"Sha":"8d89079ca22b52c59fae3e5915c11598c829cae4","Path":"zh-CN/travel","Type":""},{"Sha":"1b7d5e0ce1b12ad78060c84aadf535c28fae0a89","Path":"zh-CN/user/chenmingda","Type":""},{"Sha":"8bf61ccee740c3e1396553535a667d926c47575c","Path":"zh-CN/user/core","Type":""},{"Sha":"f7d86f80f81e6d9ade94c32fc5f50d3ce8c94127","Path":"zh-CN/user/gonghaohua","Type":""},{"Sha":"3c4d0e445e82d8d6857e2970d07830daf4c297fc","Path":"zh-CN/user/lisibao","Type":""},{"Sha":"599dc761fe2a83be1f4fc5acfeb3394960ec87c8","Path":"zh-CN/user/liuqi","Type":""},{"Sha":"8abda267248aa0e5d751dcf480702c62e0bbaa91","Path":"zh-CN/user/maojian","Type":""},{"Sha":"11497c9801656c1c9c29e174a61da178a4e6251a","Path":"zh-CN/user/maquanyi","Type":""},{"Sha":"b27e1ece5c5c2cd0ac475c9d7e98662094eed497","Path":"zh-CN/user/qleelulu","Type":""},{"Sha":"13c15fb7fad042fe668c3a154d6723aeeb4a98c9","Path":"zh-CN/user/robert","Type":""},{"Sha":"8000c3011558fff240b25f7096cdd61ffdb42711","Path":"zh-CN/user/wangyang","Type":""},{"Sha":"f6617d7688463101c3e95f1ba72c8572b6327ca6","Path":"zh-CN/user/xiabin","Type":""},{"Sha":"bf84b5454a9db45e55a9f54800df900c8184f9a3","Path":"zh-CN/user/xudaoli","Type":""},{"Sha":"ac1b2a4e7a8b5a7c7113d3515a15b5175acee1bb","Path":"zh-CN/user/xushiwei","Type":""},{"Sha":"373f978531cbce5cad22e94466648b33409ed4d4","Path":"zh-CN/user/yuhen","Type":""},{"Sha":"0826eae155bdc50bd169fe7c7a305d08ab732af4","Path":"zh-CN/user/yujun","Type":""},{"Sha":"e4636cd67e14f051b650c45e73552b7a9221b76e","Path":"zh-CN/user/zhangchengyuan","Type":""},{"Sha":"4040e6496153a1013d0d60e1d5712dfa6a187275","Path":"zh-CN/user/zhouyang","Type":""}]}
2 |
--------------------------------------------------------------------------------
/conf/locale_en-US.ini:
--------------------------------------------------------------------------------
1 | zh-CN = 简体中文
2 | en-US = English
3 |
4 | home = Home
5 | speaker = Speaker
6 | register = Register
7 | venue = Venue
8 | venuemap = Venue Map
9 | travel = Router Navigation
10 | accommodation = Hotel & Food
11 | sponsor = Sponsor
12 | supporters = Supporters
13 |
--------------------------------------------------------------------------------
/conf/locale_zh-CN.ini:
--------------------------------------------------------------------------------
1 | zh-CN = 简体中文
2 | en-US = English
3 |
4 | home = 首页
5 | speaker = 分享嘉宾
6 | register = 注册报名
7 | venue = 会场相关
8 | venuemap = 会场信息
9 | travel = 参会路线
10 | accommodation = 食宿
11 | sponsor = 赞助商
12 | supporters = 合作媒体
13 |
--------------------------------------------------------------------------------
/controllers/controller.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "strings"
5 | "time"
6 |
7 | "github.com/astaxie/beego"
8 | "github.com/astaxie/beego/config"
9 | "github.com/beego/i18n"
10 | )
11 |
12 | type baseController struct {
13 | beego.Controller
14 | i18n.Locale
15 | }
16 |
17 | var (
18 | AppVer string
19 | IsPro bool
20 | )
21 |
22 | var langTypes []*langType // Languages are supported.
23 |
24 | // langType represents a language type.
25 | type langType struct {
26 | Lang, Name string
27 | }
28 |
29 | // Prepare implemented Prepare method for baseController.
30 | func (this *baseController) Prepare() {
31 | // Setting properties.
32 | this.Data["AppVer"] = AppVer
33 | this.Data["IsPro"] = IsPro
34 |
35 | this.Data["PageStartTime"] = time.Now()
36 |
37 | // Redirect to make URL clean.
38 | if this.setLangVer() {
39 | i := strings.Index(this.Ctx.Request.RequestURI, "?")
40 | this.Redirect(this.Ctx.Request.RequestURI[:i], 302)
41 | return
42 | }
43 | }
44 |
45 | // setLangVer sets site language version.
46 | func (this *baseController) setLangVer() bool {
47 | isNeedRedir := false
48 | hasCookie := false
49 |
50 | // 1. Check URL arguments.
51 | lang := this.Input().Get("lang")
52 |
53 | // 2. Get language information from cookies.
54 | if len(lang) == 0 {
55 | lang = this.Ctx.GetCookie("lang")
56 | hasCookie = true
57 | } else {
58 | isNeedRedir = true
59 | }
60 |
61 | // Check again in case someone modify by purpose.
62 | if !i18n.IsExist(lang) {
63 | lang = ""
64 | isNeedRedir = false
65 | hasCookie = false
66 | }
67 |
68 | // 3. Get language information from 'Accept-Language'.
69 | if len(lang) == 0 {
70 | al := this.Ctx.Request.Header.Get("Accept-Language")
71 | if len(al) > 4 {
72 | al = al[:5] // Only compare first 5 letters.
73 | if i18n.IsExist(al) {
74 | lang = al
75 | }
76 | }
77 | }
78 |
79 | // 4. Default language is (Chinese) English.
80 | if len(lang) == 0 {
81 | lang = "zh-CN" //"en-US"
82 | isNeedRedir = false
83 | }
84 |
85 | curLang := langType{
86 | Lang: lang,
87 | }
88 |
89 | // Save language information in cookies.
90 | if !hasCookie {
91 | this.Ctx.SetCookie("lang", curLang.Lang, 1<<31-1, "/")
92 | }
93 |
94 | restLangs := make([]*langType, 0, len(langTypes)-1)
95 | for _, v := range langTypes {
96 | restLangs = append(restLangs, v)
97 | }
98 |
99 | // Set language properties.
100 | this.Lang = lang
101 | this.Data["Lang"] = curLang.Lang
102 | this.Data["CurLang"] = curLang.Name
103 | this.Data["RestLangs"] = restLangs
104 | return isNeedRedir
105 | }
106 |
107 | func InitNav() {
108 | nav, err := config.NewConfig("json", "conf/nav.json")
109 | if err != nil {
110 | beego.Error(err)
111 | }
112 | diy, err := nav.DIY("nav")
113 | beego.Info(diy)
114 | }
115 |
116 | func InitLocales() {
117 | // Initialized language type list.
118 | langs := strings.Split(beego.AppConfig.String("lang::types"), "|")
119 | names := strings.Split(beego.AppConfig.String("lang::names"), "|")
120 | langTypes = make([]*langType, 0, len(langs))
121 | for i, v := range langs {
122 | langTypes = append(langTypes, &langType{
123 | Lang: v,
124 | Name: names[i],
125 | })
126 | }
127 | for _, lang := range langs {
128 | beego.Trace("Loading language: " + lang)
129 | if err := i18n.SetMessage(lang, "conf/"+"locale_"+lang+".ini"); err != nil {
130 | beego.Error("Fail to set message file: " + err.Error())
131 | return
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/controllers/default.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/gopherchina/website/models"
7 | )
8 |
9 | type MainController struct {
10 | baseController
11 | }
12 |
13 | func (this *MainController) Get() {
14 | name := this.Ctx.Input.Param(":name")
15 | if name == "" {
16 | this.Data["Title"] = "GopherChina"
17 | this.Data["indexActive"] = true
18 | this.TplNames = "index.tpl"
19 | } else if name == "speaker" {
20 | this.Data["Title"] = "分享嘉宾 - GopherChina"
21 | this.Data["userActive"] = true
22 | this.TplNames = "speaker.tpl"
23 | } else if name == "venue" {
24 | this.Data["Title"] = "会场信息 - GopherChina"
25 | this.TplNames = "venue.tpl"
26 | } else if name == "register" {
27 | this.Data["regActive"] = true
28 | this.Data["Title"] = "注册报名 - GopherChina"
29 | this.TplNames = "register.tpl"
30 | } else {
31 | df := models.GetDoc(name, this.Lang)
32 | this.Data[fmt.Sprintf("%sActive", name)] = true
33 | this.Data["Section"] = name
34 | this.Data["Title"] = df.Title + " - GopherChina"
35 | this.Data["title"] = df.Title
36 | this.Data["Data"] = string(df.Data)
37 | this.TplNames = "detail.tpl"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/controllers/docs.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 |
8 | "github.com/gopherchina/website/models"
9 |
10 | "github.com/astaxie/beego/context"
11 | "github.com/beego/i18n"
12 | )
13 |
14 | type DocsController struct {
15 | baseController
16 | }
17 |
18 | // Get implemented Get method for DocsController.
19 | func (this *DocsController) Get() {
20 | name := this.Ctx.Input.Param(":name")
21 | id := this.Ctx.Input.Param(":id")
22 | if name == "" {
23 | this.Data["indexActive"] = true
24 | this.Data["Title"] = "GopherChina"
25 | this.TplNames = "index.tpl"
26 | } else {
27 | filename := name
28 | if id != "" {
29 | filename = name + "/" + id
30 | }
31 | df := models.GetDoc(filename, this.Lang)
32 | if df == nil {
33 | this.Abort("404")
34 | }
35 | this.Data[fmt.Sprintf("%sActive", name)] = true
36 | this.Data["Section"] = name
37 |
38 | this.Data["Title"] = df.Title + " - GopherChina"
39 | this.Data["title"] = df.Title
40 | this.Data["Data"] = string(df.Data)
41 | this.TplNames = "detail.tpl"
42 | }
43 | }
44 |
45 | func DocsStatic(ctx *context.Context) {
46 | if uri := ctx.Input.Params[":all"]; len(uri) > 0 {
47 | lang := ctx.GetCookie("lang")
48 | if !i18n.IsExist(lang) {
49 | lang = "en-US"
50 | }
51 |
52 | f, err := os.Open("docs/" + lang + "/" + "images/" + uri)
53 | if err != nil {
54 | ctx.WriteString(err.Error())
55 | return
56 | }
57 | defer f.Close()
58 |
59 | _, err = io.Copy(ctx.ResponseWriter, f)
60 | if err != nil {
61 | ctx.WriteString(err.Error())
62 | return
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/gopherchina/website/controllers"
5 | "github.com/gopherchina/website/models"
6 |
7 | "github.com/astaxie/beego"
8 | "github.com/beego/i18n"
9 | )
10 |
11 | func main() {
12 |
13 | beego.Router("/", &controllers.MainController{})
14 | beego.Router("/:name", &controllers.MainController{})
15 | beego.Router("/:name/:id", &controllers.DocsController{})
16 | beego.InsertFilter("/images/:all", beego.BeforeRouter, controllers.DocsStatic)
17 | controllers.InitLocales()
18 | models.InitModels()
19 | beego.AddFuncMap("i18n", i18n.Tr)
20 | beego.Run()
21 | }
22 |
--------------------------------------------------------------------------------
/models/github.go:
--------------------------------------------------------------------------------
1 | // Copyright 2013 Beego Web authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may
4 | // not use this file except in compliance with the License. You may obtain
5 | // a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 | // License for the specific language governing permissions and limitations
13 | // under the License.
14 |
15 | // Package models is for loading and updating documentation files.
16 | package models
17 |
18 | import (
19 | "encoding/json"
20 | "errors"
21 | "os"
22 | "path"
23 | "strconv"
24 | "strings"
25 | "sync"
26 | "time"
27 |
28 | "github.com/astaxie/beego"
29 | "github.com/astaxie/beego/toolbox"
30 | "github.com/astaxie/beego/utils"
31 | "github.com/slene/blackfriday"
32 | )
33 |
34 | var docs = make(map[string]*DocRoot)
35 |
36 | type oldDocNode struct {
37 | Sha string
38 | Path string
39 | Type string
40 | }
41 |
42 | // docTree descriables a documentation file structure tree.
43 | var docTree struct {
44 | Tree []oldDocNode
45 | }
46 |
47 | var blogTree struct {
48 | Tree []oldDocNode
49 | }
50 |
51 | var productTree struct {
52 | Tree []oldDocNode
53 | }
54 |
55 | type docFile struct {
56 | Title string
57 | Data []byte
58 | }
59 |
60 | var (
61 | docLock *sync.RWMutex
62 | blogLock *sync.RWMutex
63 | docMap map[string]*docFile
64 | blogMap map[string]*docFile
65 | )
66 |
67 | var githubCred string
68 |
69 | func setGithubCredentials(id, secret string) {
70 | githubCred = "client_id=" + id + "&client_secret=" + secret
71 | }
72 |
73 | func GetDocByLocale(lang string) *DocRoot {
74 | return docs[lang]
75 | }
76 |
77 | func InitModels() {
78 |
79 | setGithubCredentials(beego.AppConfig.String("github::client_id"),
80 | beego.AppConfig.String("github::client_secret"))
81 |
82 | docLock = new(sync.RWMutex)
83 | blogLock = new(sync.RWMutex)
84 |
85 | parseDocs()
86 | initMaps()
87 | //initProuctCase()
88 |
89 | updateTask := toolbox.NewTask("check file update", "0 */5 * * * *", checkFileUpdates)
90 |
91 | if needCheckUpdate() {
92 | if err := updateTask.Run(); err != nil {
93 | beego.Error(err)
94 | }
95 |
96 | beego.AppConfig.Set("app::update_check_time", strconv.Itoa(int(time.Now().Unix())))
97 | }
98 |
99 | // ATTENTION: you'd better comment following code when developing.
100 | toolbox.AddTask("check file update", updateTask)
101 | toolbox.StartTask()
102 | }
103 |
104 | func parseDocs() {
105 | root, err := ParseDocs("docs/zh-CN")
106 | if err != nil {
107 | beego.Error(err)
108 | }
109 |
110 | if root != nil {
111 | docs["zh-CN"] = root
112 | }
113 |
114 | root, err = ParseDocs("docs/en-US")
115 | if err != nil {
116 | beego.Error(err)
117 | }
118 |
119 | if root != nil {
120 | docs["en-US"] = root
121 | }
122 | }
123 |
124 | func needCheckUpdate() bool {
125 | // Does not have record for check update.
126 | stamp, err := beego.AppConfig.Int64("app::update_check_time")
127 | if err != nil {
128 | return true
129 | }
130 |
131 | if !utils.FileExists("conf/docTree.json") || !utils.FileExists("conf/blogTree.json") ||
132 | !utils.FileExists("conf/productTree.json") {
133 | return true
134 | }
135 |
136 | return time.Unix(stamp, 0).Add(5 * time.Minute).Before(time.Now())
137 | }
138 |
139 | func initDocMap() {
140 | // Documentation names.
141 | docNames := make([]string, 0, 20)
142 | docNames = append(docNames, strings.Split(
143 | beego.AppConfig.String("app::doc_names"), "|")...)
144 |
145 | isConfExist := utils.FileExists("conf/docTree.json")
146 | if isConfExist {
147 | f, err := os.Open("conf/docTree.json")
148 | if err != nil {
149 | beego.Error("models.initDocMap -> load data:", err.Error())
150 | return
151 | }
152 | defer f.Close()
153 |
154 | d := json.NewDecoder(f)
155 | if err = d.Decode(&docTree); err != nil {
156 | beego.Error("models.initDocMap -> decode data:", err.Error())
157 | return
158 | }
159 | } else {
160 | // Generate 'docTree'.
161 | for _, v := range docNames {
162 | docTree.Tree = append(docTree.Tree, oldDocNode{Path: v})
163 | }
164 | }
165 |
166 | docLock.Lock()
167 | defer docLock.Unlock()
168 |
169 | docMap = make(map[string]*docFile)
170 | langs := strings.Split(beego.AppConfig.String("lang::types"), "|")
171 |
172 | os.Mkdir("docs", os.ModePerm)
173 | for _, l := range langs {
174 | os.Mkdir("docs/"+l, os.ModePerm)
175 | for _, v := range docTree.Tree {
176 | var fullName string
177 | if isConfExist {
178 | fullName = v.Path
179 | } else {
180 | fullName = l + "/" + v.Path
181 | }
182 | docMap[fullName] = getFile("docs/" + fullName)
183 | }
184 | }
185 | }
186 |
187 | func initMaps() {
188 | initDocMap()
189 | }
190 |
191 | // loadFile returns []byte of file data by given path.
192 | func loadFile(filePath string) ([]byte, error) {
193 | f, err := os.Open(filePath)
194 | if err != nil {
195 | return []byte(""), errors.New("Fail to open file: " + err.Error())
196 | }
197 |
198 | fi, err := f.Stat()
199 | if err != nil {
200 | return []byte(""), errors.New("Fail to get file information: " + err.Error())
201 | }
202 |
203 | d := make([]byte, fi.Size())
204 | f.Read(d)
205 | return d, nil
206 | }
207 |
208 | func markdown(raw []byte) []byte {
209 | htmlFlags := 0
210 | htmlFlags |= blackfriday.HTML_USE_XHTML
211 | htmlFlags |= blackfriday.HTML_USE_SMARTYPANTS
212 | htmlFlags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS
213 | htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
214 | htmlFlags |= blackfriday.HTML_GITHUB_BLOCKCODE
215 | htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
216 | htmlFlags |= blackfriday.HTML_COMPLETE_PAGE
217 | renderer := blackfriday.HtmlRenderer(htmlFlags, "", "")
218 |
219 | // set up the parser
220 | extensions := 0
221 | extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
222 | extensions |= blackfriday.EXTENSION_TABLES
223 | extensions |= blackfriday.EXTENSION_FENCED_CODE
224 | extensions |= blackfriday.EXTENSION_AUTOLINK
225 | extensions |= blackfriday.EXTENSION_STRIKETHROUGH
226 | extensions |= blackfriday.EXTENSION_HARD_LINE_BREAK
227 | extensions |= blackfriday.EXTENSION_SPACE_HEADERS
228 | extensions |= blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK
229 |
230 | body := blackfriday.Markdown(raw, renderer, extensions)
231 | return body
232 | }
233 |
234 | func getFile(filePath string) *docFile {
235 | if strings.Contains(filePath, "images") {
236 | return nil
237 | }
238 | df := &docFile{}
239 | p, err := loadFile(filePath + ".md")
240 | if err != nil {
241 | beego.Error("models.getFile -> ", err)
242 | return nil
243 | }
244 |
245 | // Parse and render.
246 | s := string(p)
247 | i := strings.Index(s, "\n")
248 | if i > -1 {
249 | // Has title.
250 | df.Title = strings.TrimSpace(
251 | strings.Replace(s[:i+1], "#", "", -1))
252 | if len(s) >= i+2 {
253 | df.Data = []byte(strings.TrimSpace(s[i+2:]))
254 | }
255 | } else {
256 | df.Data = p
257 | }
258 | df.Data = markdown(df.Data)
259 | return df
260 | }
261 |
262 | // GetDoc returns 'docFile' by given name and language version.
263 | func GetDoc(fullName, lang string) *docFile {
264 | filePath := "docs/" + lang + "/" + fullName
265 |
266 | if beego.RunMode == "dev" {
267 | return getFile(filePath)
268 | }
269 |
270 | docLock.RLock()
271 | defer docLock.RUnlock()
272 | return docMap[lang+"/"+fullName]
273 | }
274 |
275 | var checkTicker *time.Ticker
276 |
277 | func checkTickerTimer(checkChan <-chan time.Time) {
278 | for {
279 | <-checkChan
280 | checkFileUpdates()
281 | }
282 | }
283 |
284 | type rawFile struct {
285 | name string
286 | rawURL string
287 | data []byte
288 | }
289 |
290 | func (rf *rawFile) Name() string {
291 | return rf.name
292 | }
293 |
294 | func (rf *rawFile) RawUrl() string {
295 | return rf.rawURL
296 | }
297 |
298 | func (rf *rawFile) Data() []byte {
299 | return rf.data
300 | }
301 |
302 | func (rf *rawFile) SetData(p []byte) {
303 | rf.data = p
304 | }
305 |
306 | func checkFileUpdates() error {
307 | beego.Trace("Checking file updates")
308 |
309 | type tree struct {
310 | ApiUrl, RawUrl, TreeName, Prefix string
311 | }
312 |
313 | var trees = []*tree{
314 | {
315 | ApiUrl: "https://api.github.com/repos/gopherchina/docs/git/trees/master?recursive=1&" + githubCred,
316 | RawUrl: "https://raw.github.com/gopherchina/docs/master/",
317 | TreeName: "conf/docTree.json",
318 | Prefix: "docs/",
319 | },
320 | }
321 |
322 | for _, tree := range trees {
323 | var tmpTree struct {
324 | Tree []*oldDocNode
325 | }
326 | err := getHttpJson(tree.ApiUrl, &tmpTree)
327 | if err != nil {
328 | return errors.New("models.checkFileUpdates -> get trees: " + err.Error())
329 | }
330 |
331 | var saveTree struct {
332 | Tree []*oldDocNode
333 | }
334 | saveTree.Tree = make([]*oldDocNode, 0, len(tmpTree.Tree))
335 |
336 | // Compare SHA.
337 | files := make([]*rawFile, 0, len(tmpTree.Tree))
338 | for _, node := range tmpTree.Tree {
339 | // Skip non-md files and "README.md".
340 | if node.Type != "blob" || (!strings.HasSuffix(node.Path, ".md") &&
341 | !strings.Contains(node.Path, "images") &&
342 | !strings.HasSuffix(node.Path, ".json")) ||
343 | strings.HasPrefix(strings.ToLower(node.Path), "readme") {
344 | continue
345 | }
346 |
347 | name := strings.TrimSuffix(node.Path, ".md")
348 |
349 | if checkSHA(name, node.Sha, tree.Prefix) {
350 | beego.Info("Need to update:", name)
351 | files = append(files, &rawFile{
352 | name: name,
353 | rawURL: tree.RawUrl + node.Path,
354 | })
355 | }
356 |
357 | saveTree.Tree = append(saveTree.Tree, &oldDocNode{
358 | Path: name,
359 | Sha: node.Sha,
360 | })
361 | // For save purpose, reset name.
362 | node.Path = name + ".md"
363 | }
364 |
365 | // Fetch files.
366 | if err := getFiles(files); err != nil {
367 | return errors.New("models.checkFileUpdates -> fetch files: " + err.Error())
368 | //beego.Info(err)
369 | }
370 |
371 | // Update data.
372 | for _, f := range files {
373 | os.MkdirAll(path.Join(tree.Prefix, path.Dir(f.name)), os.ModePerm)
374 | suf := ".md"
375 | if strings.Contains(f.name, "images") ||
376 | strings.HasSuffix(f.name, ".json") {
377 | suf = ""
378 | }
379 | fw, err := os.Create(tree.Prefix + f.name + suf)
380 | if err != nil {
381 | beego.Error("models.checkFileUpdates -> open file:", err.Error())
382 | continue
383 | }
384 |
385 | _, err = fw.Write(f.data)
386 | fw.Close()
387 | if err != nil {
388 | beego.Error("models.checkFileUpdates -> write data:", err.Error())
389 | continue
390 | }
391 | }
392 |
393 | // Save documentation information.
394 | f, err := os.Create(tree.TreeName)
395 | if err != nil {
396 | return errors.New("models.checkFileUpdates -> save data: " + err.Error())
397 | }
398 |
399 | e := json.NewEncoder(f)
400 | err = e.Encode(&saveTree)
401 | if err != nil {
402 | return errors.New("models.checkFileUpdates -> encode data: " + err.Error())
403 | }
404 | f.Close()
405 | }
406 |
407 | beego.Trace("Finish check file updates")
408 | parseDocs()
409 | initMaps()
410 | return nil
411 | }
412 |
413 | // checkSHA returns true if the documentation file need to update.
414 | func checkSHA(name, sha, prefix string) bool {
415 | var tree struct {
416 | Tree []oldDocNode
417 | }
418 |
419 | switch prefix {
420 | case "docs/":
421 | tree = docTree
422 | case "blog/":
423 | tree = blogTree
424 | default:
425 | tree = productTree
426 | }
427 |
428 | for _, v := range tree.Tree {
429 | if v.Path == name {
430 | // Found.
431 | if v.Sha != sha {
432 | // Need to update.
433 | return true
434 | }
435 | return false
436 | }
437 | }
438 | // Not found.
439 | return true
440 | }
441 |
--------------------------------------------------------------------------------
/models/http.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 Gary Burd
2 | // Copyright 2013 Beego Web authors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may
5 | // not use this file except in compliance with the License. You may obtain
6 | // a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | // License for the specific language governing permissions and limitations
14 | // under the License.
15 |
16 | package models
17 |
18 | import (
19 | "encoding/json"
20 | "errors"
21 | "io/ioutil"
22 | "net"
23 | "net/http"
24 | "time"
25 |
26 | "github.com/astaxie/beego"
27 | )
28 |
29 | var userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36"
30 |
31 | var (
32 | dialTimeout = 5 * time.Second //flag.Duration("dial_timeout", 10*time.Second, "Timeout for dialing an HTTP connection.")
33 | requestTimeout = 120 * time.Second //flag.Duration("request_timeout", 20*time.Second, "Time out for roundtripping an HTTP request.")
34 | )
35 |
36 | func timeoutDial(network, addr string) (net.Conn, error) {
37 | return net.DialTimeout(network, addr, dialTimeout)
38 | }
39 |
40 | type transport struct {
41 | t http.Transport
42 | }
43 |
44 | func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
45 | timer := time.AfterFunc(requestTimeout, func() {
46 | t.t.CancelRequest(req)
47 | beego.Warn("Canceled request for %s", req.URL)
48 | })
49 | defer timer.Stop()
50 | resp, err := t.t.RoundTrip(req)
51 | return resp, err
52 | }
53 |
54 | var (
55 | httpTransport = &transport{t: http.Transport{Dial: timeoutDial, ResponseHeaderTimeout: requestTimeout / 2}}
56 | httpClient = &http.Client{Transport: httpTransport}
57 | )
58 |
59 | func getHttpJson(url string, v interface{}) error {
60 | req, err := http.NewRequest("GET", url, nil)
61 | if err != nil {
62 | return err
63 | }
64 | req.Header.Set("User-Agent", userAgent)
65 |
66 | resp, err := httpClient.Do(req)
67 | if err != nil {
68 | return err
69 | }
70 | defer resp.Body.Close()
71 | if resp.StatusCode == 200 {
72 | err = json.NewDecoder(resp.Body).Decode(v)
73 | if _, ok := err.(*json.SyntaxError); ok {
74 | return errors.New("JSON syntax error at " + url)
75 | }
76 | return nil
77 | }
78 | return errors.New("can't get infomation")
79 | }
80 |
81 | func getFiles(files []*rawFile) error {
82 | ch := make(chan error, len(files))
83 | for i := range files {
84 | go func(i int) {
85 | req, err := http.NewRequest("GET", files[i].rawURL, nil)
86 | if err != nil {
87 | ch <- err
88 | return
89 | }
90 | req.Header.Set("User-Agent", userAgent)
91 | resp, err := httpClient.Do(req)
92 | if err != nil {
93 | ch <- err
94 | return
95 | }
96 | time.Sleep(500)
97 | defer resp.Body.Close()
98 | if resp.StatusCode == 200 {
99 | p, err := ioutil.ReadAll(resp.Body)
100 | if err != nil {
101 | ch <- err
102 | return
103 | }
104 | files[i].data = p
105 | }
106 | ch <- nil
107 | }(i)
108 | }
109 | for _ = range files {
110 | if err := <-ch; err != nil {
111 | return err
112 | }
113 | }
114 | return nil
115 | }
116 |
--------------------------------------------------------------------------------
/models/press.go:
--------------------------------------------------------------------------------
1 | // Copyright 2013 Beego Web authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may
4 | // not use this file except in compliance with the License. You may obtain
5 | // a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 | // License for the specific language governing permissions and limitations
13 | // under the License.
14 |
15 | package models
16 |
17 | import (
18 | "bufio"
19 | "bytes"
20 | "fmt"
21 | "io"
22 | "io/ioutil"
23 | "os"
24 | "path/filepath"
25 | "sort"
26 | "strconv"
27 | "strings"
28 | "time"
29 |
30 | "github.com/astaxie/beego"
31 | )
32 |
33 | type DocList []*DocNode
34 |
35 | func (s DocList) Len() int { return len(s) }
36 | func (s DocList) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
37 | func (s DocList) Less(i, j int) bool { return s[i].Sort < s[j].Sort }
38 |
39 | type DocNode struct {
40 | root bool
41 | IsDir bool
42 | Path string
43 | RelPath string
44 | FileRelPath string
45 | FilePath string
46 | Date time.Time
47 | Name string
48 | Sort int
49 | Link string
50 | Docs DocList
51 | dirs map[string]*DocNode
52 | Root *DocRoot
53 | Parent *DocNode
54 | }
55 |
56 | func (d *DocNode) SortDocs() {
57 | sort.Sort(d.Docs)
58 | }
59 |
60 | func (d *DocNode) HasContent() bool {
61 | return len(d.FilePath) > 0
62 | }
63 |
64 | func (d *DocNode) GetContent() string {
65 | if !d.HasContent() {
66 | return ""
67 | }
68 |
69 | body, err := ioutil.ReadFile(d.FilePath)
70 | if err != nil {
71 | return ""
72 | }
73 |
74 | if i := bytes.Index(body, []byte("---")); i != -1 {
75 | body = body[i+3:]
76 | if i = bytes.Index(body, []byte("---")); i != -1 {
77 | body = body[i+3:]
78 | i = 0
79 | m := 0
80 | mFor:
81 | for {
82 | if len(body) > 0 {
83 | if body[0] == ' ' || body[0] == '\n' {
84 | if body[0] == '\n' {
85 | m += 1
86 | }
87 | if m == 2 {
88 | break mFor
89 | }
90 | } else {
91 | break mFor
92 | }
93 | body = body[1:]
94 | } else {
95 | break mFor
96 | }
97 | }
98 |
99 | return string(markdown(body))
100 | }
101 | }
102 |
103 | return ""
104 | }
105 |
106 | type DocRoot struct {
107 | Wd string
108 | Path string
109 | Doc *DocNode
110 | links map[string]*DocNode
111 | }
112 |
113 | func (d *DocRoot) GetNodeByLink(link string) (*DocNode, bool) {
114 | n, ok := d.links[link]
115 | return n, ok
116 | }
117 |
118 | func (d *DocRoot) walkParse() error {
119 | var err error
120 | if d.Path, err = filepath.Abs(d.Path); err != nil {
121 | return err
122 | }
123 |
124 | defer func() {
125 | if err == nil {
126 | d.sortAll(d.Doc)
127 | }
128 | }()
129 |
130 | err = filepath.Walk(d.Path, d.walk)
131 | return err
132 | }
133 |
134 | func (d *DocRoot) sortAll(node *DocNode) {
135 | for _, n := range node.Docs {
136 | if n.IsDir {
137 | d.sortAll(n)
138 | }
139 | }
140 | node.SortDocs()
141 | }
142 |
143 | func (d *DocRoot) makeDirNode(path string) error {
144 | relPath, _ := filepath.Rel(d.Path, path)
145 |
146 | var docDir *DocNode
147 |
148 | if d.Doc == nil {
149 | d.Doc = new(DocNode)
150 | d.Doc.dirs = make(map[string]*DocNode)
151 | docDir = d.Doc
152 |
153 | } else {
154 | list := strings.Split(relPath, string(filepath.Separator))
155 | node := d.Doc
156 | for _, p := range list {
157 | if n, ok := node.dirs[p]; ok {
158 | node = n
159 | } else {
160 | n = new(DocNode)
161 | n.dirs = make(map[string]*DocNode)
162 | n.Parent = node
163 | node.Docs = append(node.Docs, n)
164 | node.dirs[p] = n
165 | node = n
166 | }
167 | }
168 |
169 | docDir = node
170 | }
171 |
172 | docDir.Root = d
173 | docDir.Path = path
174 | docDir.RelPath = relPath
175 | docDir.IsDir = true
176 |
177 | return nil
178 | }
179 |
180 | func (d *DocRoot) getDirNode(path string) *DocNode {
181 | node := d.Doc
182 | list := strings.Split(path, string(filepath.Separator))
183 | for _, p := range list {
184 | if n, ok := node.dirs[p]; ok {
185 | node = n
186 | }
187 | }
188 | return node
189 | }
190 |
191 | func (d *DocRoot) makeFileNode(path string) error {
192 | file, err := os.Open(path)
193 | if err != nil {
194 | return err
195 | }
196 | defer file.Close()
197 |
198 | relPath, _ := filepath.Rel(d.Path, path)
199 | relPath = strings.Replace(relPath, "\\", "/", -1)
200 |
201 | docDir := d.getDirNode(filepath.Dir(relPath))
202 |
203 | var bingo bool
204 | var doc *DocNode
205 | rd := bufio.NewReader(file)
206 | no := 0
207 | for {
208 | line, _, err := rd.ReadLine()
209 | if err == io.EOF {
210 | break
211 | }
212 |
213 | if no > 3 && !bingo {
214 | break
215 | }
216 |
217 | if no > 20 && bingo {
218 | return fmt.Errorf("document %s not contained ended tag `---`", path)
219 | }
220 |
221 | data := string(bytes.TrimSpace(line))
222 |
223 | if len(data) == 3 && data == "---" {
224 |
225 | if bingo {
226 | if doc.root {
227 | if len(docDir.FilePath) > 0 {
228 | return fmt.Errorf("node %s has a document %s, can not replicate by %s",
229 | docDir.Path, docDir.FilePath, path)
230 | }
231 |
232 | docDir.Name = doc.Name
233 | docDir.Date = doc.Date
234 | docDir.Link = doc.Link
235 | docDir.Sort = doc.Sort
236 |
237 | mFor:
238 | for {
239 | l, _, er := rd.ReadLine()
240 | if er != nil {
241 | break mFor
242 | }
243 | if len(bytes.TrimSpace(l)) > 0 {
244 | docDir.FilePath = path
245 | break mFor
246 | }
247 | }
248 |
249 | if len(docDir.Link) == 0 {
250 | docDir.Link = docDir.RelPath + "/"
251 | }
252 |
253 | docDir.FileRelPath = relPath
254 |
255 | doc = docDir
256 | } else {
257 | doc.RelPath = relPath
258 | doc.FilePath = path
259 | if len(doc.Link) == 0 {
260 | doc.Link = doc.RelPath
261 | // doc.Link = strings.TrimSuffix(doc.RelPath, filepath.Ext(doc.RelPath))
262 | }
263 |
264 | docDir.Docs = append(docDir.Docs, doc)
265 | }
266 |
267 | if dc, ok := d.links[doc.Link]; ok {
268 | return fmt.Errorf("document %s's link %s is already used by %s", path, doc.Link, dc.Path)
269 | }
270 |
271 | d.links[doc.Link] = doc
272 |
273 | break
274 | }
275 |
276 | doc = new(DocNode)
277 | doc.Path = path
278 | doc.Root = d
279 | doc.Parent = docDir
280 |
281 | bingo = true
282 | }
283 |
284 | if bingo {
285 | parts := strings.SplitN(data, ":", 2)
286 | if len(parts) == 2 {
287 | name := strings.TrimSpace(parts[0])
288 | value := strings.TrimSpace(parts[1])
289 | switch name {
290 | case "root":
291 | doc.root, _ = strconv.ParseBool(value)
292 | case "name":
293 | doc.Name = value
294 | case "date":
295 | doc.Date, err = beego.DateParse(value, "Y-m-d H:i")
296 | if err != nil {
297 | return err
298 | }
299 | case "link":
300 | doc.Link = value
301 | case "sort":
302 | n, _ := strconv.ParseInt(value, 10, 64)
303 | doc.Sort = int(n)
304 | }
305 | }
306 | }
307 | }
308 |
309 | return nil
310 | }
311 |
312 | func (d *DocRoot) walk(path string, info os.FileInfo, err error) error {
313 | if err != nil {
314 | return filepath.SkipDir
315 | }
316 |
317 | if !info.IsDir() && info.Size() == 0 {
318 | return nil
319 | }
320 |
321 | if info.IsDir() {
322 | if err := d.makeDirNode(path); err != nil {
323 | return err
324 | }
325 | } else {
326 | return d.makeFileNode(path)
327 | }
328 |
329 | return nil
330 | }
331 |
332 | func ParseDocs(path string) (*DocRoot, error) {
333 | root := new(DocRoot)
334 | root.Path = path
335 | root.links = make(map[string]*DocNode)
336 |
337 | if err := root.walkParse(); err == nil {
338 | return root, err
339 | } else {
340 | return nil, err
341 | }
342 | }
343 |
--------------------------------------------------------------------------------
/models/product.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "encoding/json"
5 | "os"
6 |
7 | "github.com/astaxie/beego"
8 | "github.com/astaxie/beego/utils"
9 | )
10 |
11 | type products struct {
12 | Projects []*Project
13 | }
14 |
15 | type Project struct {
16 | Name string
17 | Thumb string
18 | Desc string
19 | Url string
20 | Src string
21 | Submitter string
22 | Date string
23 | }
24 |
25 | var Products = new(products)
26 |
27 | func initProuctCase() {
28 | if !utils.FileExists("conf/productTree.json") {
29 | beego.Error("models.initBlogMap -> conf/productTree.json does not exist")
30 | return
31 | }
32 |
33 | f, err := os.Open("conf/productTree.json")
34 | if err != nil {
35 | beego.Error("models.initBlogMap -> load data:", err.Error())
36 | return
37 | }
38 | defer f.Close()
39 |
40 | d := json.NewDecoder(f)
41 | err = d.Decode(&productTree)
42 | if err != nil {
43 | beego.Error("models.initBlogMap -> decode data:", err.Error())
44 | return
45 | }
46 |
47 | fileName := "products/projects.json"
48 |
49 | aProducts := *Products
50 |
51 | var file *os.File
52 |
53 | if file, err = os.Open(fileName); err != nil {
54 | beego.Error("open %s, %s", fileName, err.Error())
55 | return
56 | }
57 |
58 | d = json.NewDecoder(file)
59 | if err = d.Decode(&aProducts); err != nil {
60 | beego.Error("open %s, %s", fileName, err.Error())
61 | return
62 | }
63 |
64 | for i, j := 0, len(aProducts.Projects)-1; i < j; i, j = i+1, j-1 {
65 | aProducts.Projects[i], aProducts.Projects[j] = aProducts.Projects[j], aProducts.Projects[i]
66 | }
67 |
68 | *Products = aProducts
69 | }
70 |
--------------------------------------------------------------------------------
/static/css/admin.css:
--------------------------------------------------------------------------------
1 | /**
2 | * admin.css
3 | */
4 |
5 | ul {
6 | margin-top: 0;
7 | }
8 |
9 | .admin-icon-yellow {
10 | color: #ffbe40;
11 | }
12 |
13 | .admin-header {
14 | font-size: 1.4rem;
15 | margin-bottom: 0;
16 | }
17 |
18 | .admin-header-list a:hover :after {
19 | content: none;
20 | }
21 |
22 | .admin-main {
23 | background: #f3f3f3;
24 | }
25 |
26 | .admin-sidebar {
27 | width: 260px;
28 | min-height: 100%;
29 | float: left;
30 | border-right: 1px solid #cecece;
31 | }
32 |
33 | .admin-sidebar-list {
34 | margin-bottom: 0;
35 | }
36 |
37 | .admin-sidebar-list li a {
38 | color: #5c5c5c;
39 | padding-left: 24px;
40 | }
41 |
42 | .admin-sidebar-list li:first-child {
43 | border-top: none;
44 | }
45 |
46 | .admin-sidebar-sub {
47 | margin-top: 0;
48 | margin-bottom: 0;
49 | box-shadow: 0 16px 8px -15px #e2e2e2 inset;
50 | background: #ececec;
51 | padding-left: 24px;
52 | }
53 |
54 | .admin-sidebar-sub li:first-child {
55 | border-top: 1px solid #dedede;
56 | }
57 |
58 | .admin-sidebar-panel {
59 | margin: 10px;
60 | }
61 |
62 | .admin-content {
63 | width: auto;
64 | overflow: hidden;
65 | height: 100%;
66 | background: #fff;
67 | }
68 |
69 | .admin-content-list {
70 | border: 1px solid #e9ecf1;
71 | margin-top: 0;
72 | }
73 |
74 | .admin-content-list li {
75 | border: 1px solid #e9ecf1;
76 | border-width: 0 1px;
77 | margin-left: -1px;
78 | }
79 |
80 | .admin-content-list li:first-child {
81 | border-left: none;
82 | }
83 |
84 | .admin-content-list li:last-child {
85 | border-right: none;
86 | }
87 |
88 | .admin-content-table a {
89 | color: #535353;
90 | }
91 | .admin-content-file {
92 | margin-bottom: 0;
93 | color: #666;
94 | }
95 |
96 | .admin-content-file p {
97 | margin: 0 0 5px 0;
98 | font-size: 1.4rem;
99 | }
100 |
101 | .admin-content-file li {
102 | padding: 10px 0;
103 | }
104 |
105 | .admin-content-file li:first-child {
106 | border-top: none;
107 | }
108 |
109 | .admin-content-file li:last-child {
110 | border-bottom: none;
111 | }
112 |
113 | .admin-content-file li .am-progress {
114 | margin-bottom: 4px;
115 | }
116 |
117 | .admin-content-file li .am-progress-bar {
118 | line-height: 14px;
119 | }
120 |
121 | .admin-content-task {
122 | margin-bottom: 0;
123 | }
124 |
125 | .admin-content-task li {
126 | padding: 5px 0;
127 | border-color: #eee;
128 | }
129 |
130 | .admin-content-task li:first-child {
131 | border-top: none;
132 | }
133 |
134 | .admin-content-task li:last-child {
135 | border-bottom: none;
136 | }
137 |
138 | .admin-task-meta {
139 | font-size: 1.2rem;
140 | color: #999;
141 | }
142 |
143 | .admin-task-bd {
144 | font-size: 1.4rem;
145 | margin-bottom: 5px;
146 | }
147 |
148 | .admin-content-comment {
149 | margin-bottom: 0;
150 | }
151 |
152 | .admin-content-comment .am-comment-bd {
153 | font-size: 1.4rem;
154 | }
155 |
156 | .admin-content-pagination {
157 | margin-bottom: 0;
158 | }
159 | .admin-content-pagination li a {
160 | padding: 4px 8px;
161 | }
162 |
163 | /*
164 | * user.html css
165 | */
166 | .user-info {
167 | margin-bottom: 15px;
168 | }
169 |
170 | .user-info .am-progress {
171 | margin-bottom: 4px;
172 | }
173 |
174 | .user-info p {
175 | margin: 5px;
176 | }
177 |
178 | .user-info-order {
179 | font-size: 1.4rem;
180 | }
181 |
182 | /*
183 | * errorLog.html css
184 | */
185 |
186 | .error-log .am-pre-scrollable {
187 | max-height: 40rem;
188 | }
189 |
190 | /*
191 | * table.html css
192 | */
193 |
194 | .table-main {
195 | font-size: 1.4rem;
196 | padding: .5rem;
197 | }
198 |
199 | .table-main button {
200 | background: #fff;
201 | }
202 |
203 | .table-check {
204 | width: 30px;
205 | }
206 |
207 | .table-id {
208 | width: 50px;
209 | }
210 |
211 | /*
212 | gallery.html css
213 | */
214 |
215 | .gallery-list li {
216 | padding: 10px;
217 | }
218 |
219 | .gallery-list a {
220 | color: #666;
221 | }
222 |
223 | .gallery-list a:hover {
224 | color: #3bb4f2;
225 | }
226 |
227 | .gallery-title {
228 | margin-top: 6px;
229 | font-size: 1.4rem;
230 | }
231 |
232 | .gallery-desc {
233 | font-size: 1.2rem;
234 | margin-top: 4px;
235 | }
236 |
237 | /*
238 | 404.html css
239 | */
240 |
241 | .page-404 {
242 | background: #fff;
243 | border: none;
244 | width: 200px;
245 | margin: 0 auto;
246 | }
247 |
--------------------------------------------------------------------------------
/static/css/app.css:
--------------------------------------------------------------------------------
1 | /* Write your styles */
--------------------------------------------------------------------------------
/static/css/index.css:
--------------------------------------------------------------------------------
1 | .m120{width: 120px;}
2 | .detail-span span{margin: 0;padding: .8rem;}
3 | .get {
4 | background: #1E5B94;
5 | color: #fff;
6 | text-align: center;
7 | padding: 50px 0;
8 | }
9 |
10 | .get-title {
11 | font-size: 200%;
12 | }
13 |
14 | .get-btn {
15 | background: #fff;
16 | }
17 | .center{ text-align: center;}
18 | .detail {
19 | background: #fff;
20 | }
21 |
22 | .detail-h2 {
23 | text-align: center;
24 | font-size: 150%;
25 | margin: 40px 0;
26 | }
27 |
28 | .detail-h3 {
29 | color: #1f8dd6;
30 | }
31 |
32 | .detail-p {
33 | color: #7f8c8d;
34 |
35 | }
36 | .detail-span {
37 | color: #7f8c8d;
38 | }
39 | .detail-mb {
40 | margin-bottom: 30px;
41 | }
42 | .detail-mt {
43 | margin-top: 10px;
44 | }
45 | .hope {
46 | background: #0bb59b;
47 | padding: 50px 0;
48 | }
49 |
50 | .hope-img {
51 | text-align: center;
52 | }
53 |
54 | .hope-hr {
55 | border-color: #149C88;
56 | }
57 |
58 | .hope-title {
59 | font-size: 140%;
60 | }
61 |
62 | .about {
63 | background: #fff;
64 | padding: 40px 0;
65 | color: #7f8c8d;
66 | }
67 |
68 | .about-color {
69 | color: #34495e;
70 | }
71 |
72 | .about-title {
73 | font-size: 180%;
74 | padding: 30px 0 50px 0;
75 | text-align: center;
76 | }
77 | .partner{
78 | padding:0 0 0 25px;
79 | /*border-right: 1px solid #E0E0E0;*/
80 | }
81 | .partner img {
82 | width: 125px;
83 | height: auto;
84 | margin: 10px 50px 0px 0px;
85 | }
86 |
87 | .footer p {
88 | color: #7f8c8d;
89 | margin: 0;
90 | padding: 15px 0;
91 | text-align: center;
92 | background: #2d3e50;
93 | }
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/favicon.ico
--------------------------------------------------------------------------------
/static/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/static/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/static/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/static/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/static/images/InfoQ.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/InfoQ.jpg
--------------------------------------------------------------------------------
/static/images/bearychat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/bearychat.png
--------------------------------------------------------------------------------
/static/images/bowen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/bowen.png
--------------------------------------------------------------------------------
/static/images/coding.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/coding.png
--------------------------------------------------------------------------------
/static/images/csdn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/csdn.png
--------------------------------------------------------------------------------
/static/images/daocloud.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/daocloud.jpg
--------------------------------------------------------------------------------
/static/images/daocloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/daocloud.png
--------------------------------------------------------------------------------
/static/images/dreamlab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/dreamlab.png
--------------------------------------------------------------------------------
/static/images/fequan.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/fequan.jpg
--------------------------------------------------------------------------------
/static/images/gitcafe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/gitcafe.png
--------------------------------------------------------------------------------
/static/images/gophercomplex1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/gophercomplex1.jpg
--------------------------------------------------------------------------------
/static/images/gophercomplex2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/gophercomplex2.jpg
--------------------------------------------------------------------------------
/static/images/gopherconf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/gopherconf.png
--------------------------------------------------------------------------------
/static/images/huawei.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/huawei.png
--------------------------------------------------------------------------------
/static/images/imooc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/imooc.png
--------------------------------------------------------------------------------
/static/images/jd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/jd.png
--------------------------------------------------------------------------------
/static/images/oschina.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/oschina.png
--------------------------------------------------------------------------------
/static/images/osforce.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/osforce.jpg
--------------------------------------------------------------------------------
/static/images/pusoft.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/pusoft.jpg
--------------------------------------------------------------------------------
/static/images/pusoft1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/pusoft1.jpg
--------------------------------------------------------------------------------
/static/images/pusoft2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/pusoft2.jpg
--------------------------------------------------------------------------------
/static/images/pusoft_map.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/pusoft_map.jpg
--------------------------------------------------------------------------------
/static/images/qiniu.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/qiniu.jpg
--------------------------------------------------------------------------------
/static/images/segmentfault.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/segmentfault.png
--------------------------------------------------------------------------------
/static/images/techparty.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/techparty.jpg
--------------------------------------------------------------------------------
/static/images/tuling.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/tuling.jpg
--------------------------------------------------------------------------------
/static/images/xd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/images/xd.png
--------------------------------------------------------------------------------
/static/img/chenmingda.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/img/chenmingda.png
--------------------------------------------------------------------------------
/static/img/gonghaohua.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/img/gonghaohua.jpg
--------------------------------------------------------------------------------
/static/img/lisibao.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/img/lisibao.jpg
--------------------------------------------------------------------------------
/static/img/liuqi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/img/liuqi.png
--------------------------------------------------------------------------------
/static/img/maquanyi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/img/maquanyi.jpg
--------------------------------------------------------------------------------
/static/img/qleelulu.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/img/qleelulu.jpg
--------------------------------------------------------------------------------
/static/img/xushiwei.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/img/xushiwei.jpg
--------------------------------------------------------------------------------
/static/img/zhouyang.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopherchina/website/a17ef0e1b67a3c6c9fe5dfff2f24d634adb5ad92/static/img/zhouyang.jpg
--------------------------------------------------------------------------------
/static/js/amazeui.widgets.helper.js:
--------------------------------------------------------------------------------
1 | /*! Amaze UI v2.1.0-beta1 ~ helper | by Amaze UI Team | (c) 2015 AllMobilize, Inc. | Licensed under MIT | 2015-01-04T05:01:12 UTC */
2 | (function(undefined) {
3 | 'use strict';
4 |
5 | var registerIfCondHelper = function(hbs) {
6 | hbs.registerHelper('ifCond', function(v1, operator, v2, options) {
7 | switch (operator) {
8 | case '==':
9 | return (v1 == v2) ? options.fn(this) : options.inverse(this);
10 | break;
11 | case '===':
12 | return (v1 === v2) ? options.fn(this) : options.inverse(this);
13 | break;
14 | case '<':
15 | return (v1 < v2) ? options.fn(this) : options.inverse(this);
16 | break;
17 | case '<=':
18 | return (v1 <= v2) ? options.fn(this) : options.inverse(this);
19 | break;
20 | case '>':
21 | return (v1 > v2) ? options.fn(this) : options.inverse(this);
22 | break;
23 | case '>=':
24 | return (v1 >= v2) ? options.fn(this) : options.inverse(this);
25 | break;
26 | default:
27 | return options.inverse(this);
28 | break;
29 | }
30 | return options.inverse(this);
31 | });
32 | };
33 |
34 | if (typeof module !== 'undefined' && module.exports) {
35 | module.exports = registerIfCondHelper;
36 | }
37 |
38 | this.Handlebars && registerIfCondHelper(this.Handlebars);
39 | }).call(this);
40 |
41 | (function(undefined){
42 | 'use strict';
43 |
44 | var registerAMUIPartials = function(hbs) {
45 | hbs.registerPartial('accordion', "{{#this}}\n \n
\n {{/each}}\n
© 2015 Gopher China组委会
4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 31 |