[x/blog] go.blog: use godoc design, use blog package from go.tools

Also delete go.blog/pkg/blog and unused static files.

R=golang-dev, dsymonds
https://golang.org/cl/13754043
X-Blog-Commit: a7251a3023bb3d78672ef1620be8de4151b3b41b
This commit is contained in:
Andrew Gerrand 2013-09-18 15:16:48 +10:00
Родитель 4d4e41fa2d
Коммит ae8dc04163
10 изменённых файлов: 98 добавлений и 1135 удалений

Просмотреть файл

@ -7,15 +7,45 @@
package main
import (
"code.google.com/p/go.blog/pkg/blog"
"net/http"
"strings"
"time"
"code.google.com/p/go.tools/godoc/blog"
"code.google.com/p/go.tools/godoc/static"
_ "code.google.com/p/go.tools/godoc/playground"
)
const hostname = "blog.golang.org" // default hostname for blog server
var config = &blog.Config{
var config = blog.Config{
Hostname: hostname,
BaseURL: "http://" + hostname,
GodocURL: "http://golang.org",
HomeArticles: 5, // articles to display on the home page
FeedArticles: 10, // articles to include in Atom and JSON feeds
PlayEnabled: true,
}
func init() {
// Redirect "/blog/" to "/", because the menu bar link is to "/blog/"
// but we're serving from the root.
redirect := func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusFound)
}
http.HandleFunc("/blog", redirect)
http.HandleFunc("/blog/", redirect)
http.Handle("/lib/godoc/", http.StripPrefix("/lib/godoc/", http.HandlerFunc(staticHandler)))
}
func staticHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Path
b, ok := static.Files[name]
if !ok {
http.NotFound(w, r)
return
}
http.ServeContent(w, r, name, time.Time{}, strings.NewReader(b))
}

Просмотреть файл

@ -1,420 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package blog implements a web server for articles written in present format.
package blog
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"html/template"
"log"
"net/http"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
"code.google.com/p/go.blog/pkg/atom"
"code.google.com/p/go.talks/pkg/present"
_ "code.google.com/p/go.tools/godoc/playground"
)
var validJSONPFunc = regexp.MustCompile(`(?i)^[a-z_][a-z0-9_.]*$`)
// Config specifies Server configuration values.
type Config struct {
ContentPath string // Relative or absolute location of article files and related content.
TemplatePath string // Relative or absolute location of template files.
BaseURL string // Absolute base URL (for permalinks; no trailing slash).
BasePath string // Base URL path relative to server root (no trailing slash).
HomeArticles int // Articles to display on the home page.
Hostname string // Server host name, used for rendering ATOM feeds.
FeedArticles int // Articles to include in Atom and JSON feeds.
}
// Doc represents an article adorned with presentation data.
type Doc struct {
*present.Doc
Permalink string // Canonical URL for this document.
Path string // Path relative to server root (including base).
HTML template.HTML // rendered article
Related []*Doc
Newer, Older *Doc
}
// Server implements an http.Handler that serves blog articles.
type Server struct {
cfg *Config
docs []*Doc
tags []string
docPaths map[string]*Doc // key is path without BasePath.
docTags map[string][]*Doc
template struct {
home, index, article, doc *template.Template
}
atomFeed []byte // pre-rendered Atom feed
jsonFeed []byte // pre-rendered JSON feed
content http.Handler
}
// NewServer constructs a new Server using the specified config.
func NewServer(cfg *Config) (*Server, error) {
present.PlayEnabled = true
root := filepath.Join(cfg.TemplatePath, "root.tmpl")
parse := func(name string) (*template.Template, error) {
t := template.New("").Funcs(funcMap)
return t.ParseFiles(root, filepath.Join(cfg.TemplatePath, name))
}
s := &Server{cfg: cfg}
// Parse templates.
var err error
s.template.home, err = parse("home.tmpl")
if err != nil {
return nil, err
}
s.template.index, err = parse("index.tmpl")
if err != nil {
return nil, err
}
s.template.article, err = parse("article.tmpl")
if err != nil {
return nil, err
}
p := present.Template().Funcs(funcMap)
s.template.doc, err = p.ParseFiles(filepath.Join(cfg.TemplatePath, "doc.tmpl"))
if err != nil {
return nil, err
}
// Load content.
err = s.loadDocs(filepath.Clean(cfg.ContentPath))
if err != nil {
return nil, err
}
err = s.renderAtomFeed()
if err != nil {
return nil, err
}
err = s.renderJSONFeed()
if err != nil {
return nil, err
}
// Set up content file server.
s.content = http.FileServer(http.Dir(cfg.ContentPath))
return s, nil
}
var funcMap = template.FuncMap{
"sectioned": sectioned,
"authors": authors,
}
// sectioned returns true if the provided Doc contains more than one section.
// This is used to control whether to display the table of contents and headings.
func sectioned(d *present.Doc) bool {
return len(d.Sections) > 1
}
// authors returns a comma-separated list of author names.
func authors(authors []present.Author) string {
var b bytes.Buffer
last := len(authors) - 1
for i, a := range authors {
if i > 0 {
if i == last {
b.WriteString(" and ")
} else {
b.WriteString(", ")
}
}
b.WriteString(authorName(a))
}
return b.String()
}
// authorName returns the first line of the Author text: the author's name.
func authorName(a present.Author) string {
el := a.TextElem()
if len(el) == 0 {
return ""
}
text, ok := el[0].(present.Text)
if !ok || len(text.Lines) == 0 {
return ""
}
return text.Lines[0]
}
// loadDocs reads all content from the provided file system root, renders all
// the articles it finds, adds them to the Server's docs field, computes the
// denormalized docPaths, docTags, and tags fields, and populates the various
// helper fields (Next, Previous, Related) for each Doc.
func (s *Server) loadDocs(root string) error {
// Read content into docs field.
const ext = ".article"
fn := func(p string, info os.FileInfo, err error) error {
if filepath.Ext(p) != ext {
return nil
}
f, err := os.Open(p)
if err != nil {
return err
}
defer f.Close()
d, err := present.Parse(f, p, 0)
if err != nil {
return err
}
html := new(bytes.Buffer)
err = d.Render(html, s.template.doc)
if err != nil {
return err
}
p = p[len(root) : len(p)-len(ext)] // trim root and extension
s.docs = append(s.docs, &Doc{
Doc: d,
Path: s.cfg.BasePath + p,
Permalink: s.cfg.BaseURL + p,
HTML: template.HTML(html.String()),
})
return nil
}
err := filepath.Walk(root, fn)
if err != nil {
return err
}
sort.Sort(docsByTime(s.docs))
// Pull out doc paths and tags and put in reverse-associating maps.
s.docPaths = make(map[string]*Doc)
s.docTags = make(map[string][]*Doc)
for _, d := range s.docs {
s.docPaths[strings.TrimPrefix(d.Path, s.cfg.BasePath)] = d
for _, t := range d.Tags {
s.docTags[t] = append(s.docTags[t], d)
}
}
// Pull out unique sorted list of tags.
for t := range s.docTags {
s.tags = append(s.tags, t)
}
sort.Strings(s.tags)
// Set up presentation-related fields, Newer, Older, and Related.
for _, doc := range s.docs {
// Newer, Older: docs adjacent to doc
for i := range s.docs {
if s.docs[i] != doc {
continue
}
if i > 0 {
doc.Newer = s.docs[i-1]
}
if i+1 < len(s.docs) {
doc.Older = s.docs[i+1]
}
break
}
// Related: all docs that share tags with doc.
related := make(map[*Doc]bool)
for _, t := range doc.Tags {
for _, d := range s.docTags[t] {
if d != doc {
related[d] = true
}
}
}
for d := range related {
doc.Related = append(doc.Related, d)
}
sort.Sort(docsByTime(doc.Related))
}
return nil
}
// renderAtomFeed generates an XML Atom feed and stores it in the Server's
// atomFeed field.
func (s *Server) renderAtomFeed() error {
var updated time.Time
if len(s.docs) > 1 {
updated = s.docs[0].Time
}
feed := atom.Feed{
Title: "The Go Programming Language Blog",
ID: "tag:" + s.cfg.Hostname + ",2013:" + s.cfg.Hostname,
Updated: atom.Time(updated),
Link: []atom.Link{{
Rel: "self",
Href: s.cfg.BaseURL + "/feed.atom",
}},
}
for i, doc := range s.docs {
if i >= s.cfg.FeedArticles {
break
}
e := &atom.Entry{
Title: doc.Title,
ID: feed.ID + doc.Path,
Link: []atom.Link{{
Rel: "alternate",
Href: doc.Permalink,
}},
Published: atom.Time(doc.Time),
Updated: atom.Time(doc.Time),
Summary: &atom.Text{
Type: "html",
Body: summary(doc),
},
Content: &atom.Text{
Type: "html",
Body: string(doc.HTML),
},
Author: &atom.Person{
Name: authors(doc.Authors),
},
}
feed.Entry = append(feed.Entry, e)
}
data, err := xml.Marshal(&feed)
if err != nil {
return err
}
s.atomFeed = data
return nil
}
type jsonItem struct {
Title string
Link string
Time time.Time
Summary string
Content string
Author string
}
// renderJSONFeed generates a JSON feed and stores it in the Server's jsonFeed
// field.
func (s *Server) renderJSONFeed() error {
var feed []jsonItem
for i, doc := range s.docs {
if i >= s.cfg.FeedArticles {
break
}
item := jsonItem{
Title: doc.Title,
Link: doc.Permalink,
Time: doc.Time,
Summary: summary(doc),
Content: string(doc.HTML),
Author: authors(doc.Authors),
}
feed = append(feed, item)
}
data, err := json.Marshal(feed)
if err != nil {
return err
}
s.jsonFeed = data
return nil
}
// summary returns the first paragraph of text from the provided Doc.
func summary(d *Doc) string {
if len(d.Sections) == 0 {
return ""
}
for _, elem := range d.Sections[0].Elem {
text, ok := elem.(present.Text)
if !ok || text.Pre {
// skip everything but non-text elements
continue
}
var buf bytes.Buffer
for _, s := range text.Lines {
buf.WriteString(string(present.Style(s)))
buf.WriteByte('\n')
}
return buf.String()
}
return ""
}
// rootData encapsulates data destined for the root template.
type rootData struct {
Doc *Doc
BasePath string
Data interface{}
}
// ServeHTTP serves the front, index, and article pages
// as well as the ATOM and JSON feeds.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var (
d = rootData{BasePath: s.cfg.BasePath}
t *template.Template
)
switch p := strings.TrimPrefix(r.URL.Path, s.cfg.BasePath); p {
case "/":
d.Data = s.docs
if len(s.docs) > s.cfg.HomeArticles {
d.Data = s.docs[:s.cfg.HomeArticles]
}
t = s.template.home
case "/index":
d.Data = s.docs
t = s.template.index
case "/feed.atom", "/feeds/posts/default":
w.Header().Set("Content-type", "application/atom+xml; charset=utf-8")
w.Write(s.atomFeed)
return
case "/.json":
if p := r.FormValue("jsonp"); validJSONPFunc.MatchString(p) {
w.Header().Set("Content-type", "application/javascript; charset=utf-8")
fmt.Fprintf(w, "%v(%s)", p, s.jsonFeed)
return
}
w.Header().Set("Content-type", "application/json; charset=utf-8")
w.Write(s.jsonFeed)
return
default:
doc, ok := s.docPaths[p]
if !ok {
// Not a doc; try to just serve static content.
s.content.ServeHTTP(w, r)
return
}
d.Doc = doc
t = s.template.article
}
err := t.ExecuteTemplate(w, "root", d)
if err != nil {
log.Println(err)
}
}
// docsByTime implements sort.Interface, sorting Docs by their Time field.
type docsByTime []*Doc
func (s docsByTime) Len() int { return len(s) }
func (s docsByTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s docsByTime) Less(i, j int) bool { return s[i].Time.After(s[j].Time) }

2
blog/static/jquery.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Двоичные данные
blog/static/logo.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 3.3 KiB

Просмотреть файл

@ -1,2 +0,0 @@
These files are copies of those in code.google.com/p/go.talks/present/js.
Please keep them in sync.

Просмотреть файл

@ -1,99 +0,0 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
function initPlayground(transport) {
"use strict";
function text(node) {
var s = "";
for (var i = 0; i < node.childNodes.length; i++) {
var n = node.childNodes[i];
if (n.nodeType === 1 && n.tagName === "SPAN" && n.className != "number") {
var innerText = n.innerText === undefined ? "textContent" : "innerText";
s += n[innerText] + "\n";
continue;
}
if (n.nodeType === 1 && n.tagName !== "BUTTON") {
s += text(n);
}
}
return s;
}
function init(code) {
var output = document.createElement('div');
var outpre = document.createElement('pre');
var running;
if ($ && $(output).resizable) {
$(output).resizable({
handles: "n,w,nw",
minHeight: 27,
minWidth: 135,
maxHeight: 608,
maxWidth: 990
});
}
function onKill() {
if (running) running.Kill();
}
function onRun(e) {
onKill();
output.style.display = "block";
outpre.innerHTML = "";
run1.style.display = "none";
var options = {Race: e.shiftKey};
running = transport.Run(text(code), PlaygroundOutput(outpre), options);
}
function onClose() {
onKill();
output.style.display = "none";
run1.style.display = "inline-block";
}
var run1 = document.createElement('button');
run1.innerHTML = 'Run';
run1.className = 'run';
run1.addEventListener("click", onRun, false);
var run2 = document.createElement('button');
run2.className = 'run';
run2.innerHTML = 'Run';
run2.addEventListener("click", onRun, false);
var kill = document.createElement('button');
kill.className = 'kill';
kill.innerHTML = 'Kill';
kill.addEventListener("click", onKill, false);
var close = document.createElement('button');
close.className = 'close';
close.innerHTML = 'Close';
close.addEventListener("click", onClose, false);
var button = document.createElement('div');
button.classList.add('buttons');
button.appendChild(run1);
// Hack to simulate insertAfter
code.parentNode.insertBefore(button, code.nextSibling);
var buttons = document.createElement('div');
buttons.classList.add('buttons');
buttons.appendChild(run2);
buttons.appendChild(kill);
buttons.appendChild(close);
output.classList.add('output');
output.appendChild(buttons);
output.appendChild(outpre);
output.style.display = "none";
code.parentNode.insertBefore(output, button.nextSibling);
}
var play = document.querySelectorAll('div.playground');
for (var i = 0; i < play.length; i++) {
init(play[i]);
}
}

Просмотреть файл

@ -1,425 +0,0 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
In the absence of any formal way to specify interfaces in JavaScript,
here's a skeleton implementation of a playground transport.
function Transport() {
// Set up any transport state (eg, make a websocket connnection).
return {
Run: function(body, output, options) {
// Compile and run the program 'body' with 'options'.
// Call the 'output' callback to display program output.
return {
Kill: function() {
// Kill the running program.
}
};
}
};
}
// The output callback is called multiple times, and each time it is
// passed an object of this form.
var write = {
Kind: 'string', // 'start', 'stdout', 'stderr', 'end'
Body: 'string' // content of write or end status message
}
// The first call must be of Kind 'start' with no body.
// Subsequent calls may be of Kind 'stdout' or 'stderr'
// and must have a non-null Body string.
// The final call should be of Kind 'end' with an optional
// Body string, signifying a failure ("killed", for example).
// The output callback must be of this form.
// See PlaygroundOutput (below) for an implementation.
function outputCallback(write) {
// Append writes to
}
*/
function HTTPTransport() {
'use strict';
// TODO(adg): support stderr
function playback(output, events) {
var timeout;
output({Kind: 'start'});
function next() {
if (events.length === 0) {
output({Kind: 'end'});
return;
}
var e = events.shift();
if (e.Delay === 0) {
output({Kind: 'stdout', Body: e.Message});
next();
return;
}
timeout = setTimeout(function() {
output({Kind: 'stdout', Body: e.Message});
next();
}, e.Delay / 1000000);
}
next();
return {
Stop: function() {
clearTimeout(timeout);
}
}
}
function error(output, msg) {
output({Kind: 'start'});
output({Kind: 'stderr', Body: msg});
output({Kind: 'end'});
}
var seq = 0;
return {
Run: function(body, output, options) {
seq++;
var cur = seq;
var playing;
$.ajax('/compile', {
type: 'POST',
data: {'version': 2, 'body': body},
dataType: 'json',
success: function(data) {
if (seq != cur) return;
if (!data) return;
if (playing != null) playing.Stop();
if (data.Errors) {
error(output, data.Errors);
return;
}
playing = playback(output, data.Events);
},
error: function() {
error(output, 'Error communicating with remote server.');
}
});
return {
Kill: function() {
if (playing != null) playing.Stop();
output({Kind: 'end', Body: 'killed'});
}
};
}
};
}
function SocketTransport() {
'use strict';
var id = 0;
var outputs = {};
var started = {};
var websocket = new WebSocket('ws://' + window.location.host + '/socket');
websocket.onclose = function() {
console.log('websocket connection closed');
}
websocket.onmessage = function(e) {
var m = JSON.parse(e.data);
var output = outputs[m.Id];
if (output === null)
return;
if (!started[m.Id]) {
output({Kind: 'start'});
started[m.Id] = true;
}
output({Kind: m.Kind, Body: m.Body});
}
function send(m) {
websocket.send(JSON.stringify(m));
}
return {
Run: function(body, output, options) {
var thisID = id+'';
id++;
outputs[thisID] = output;
send({Id: thisID, Kind: 'run', Body: body, Options: options});
return {
Kill: function() {
send({Id: thisID, Kind: 'kill'});
}
};
}
};
}
function PlaygroundOutput(el) {
'use strict';
return function(write) {
if (write.Kind == 'start') {
el.innerHTML = '';
return;
}
var cl = 'system';
if (write.Kind == 'stdout' || write.Kind == 'stderr')
cl = write.Kind;
var m = write.Body;
if (write.Kind == 'end')
m = '\nProgram exited' + (m?(': '+m):'.');
if (m.indexOf('IMAGE:') === 0) {
// TODO(adg): buffer all writes before creating image
var url = 'data:image/png;base64,' + m.substr(6);
var img = document.createElement('img');
img.src = url;
el.appendChild(img);
return;
}
// ^L clears the screen.
var s = m.split('\x0c');
if (s.length > 1) {
el.innerHTML = '';
m = s.pop();
}
m = m.replace(/&/g, '&amp;');
m = m.replace(/</g, '&lt;');
m = m.replace(/>/g, '&gt;');
var needScroll = (el.scrollTop + el.offsetHeight) == el.scrollHeight;
var span = document.createElement('span');
span.className = cl;
span.innerHTML = m;
el.appendChild(span);
if (needScroll)
el.scrollTop = el.scrollHeight - el.offsetHeight;
}
}
(function() {
function lineHighlight(error) {
var regex = /prog.go:([0-9]+)/g;
var r = regex.exec(error);
while (r) {
$(".lines div").eq(r[1]-1).addClass("lineerror");
r = regex.exec(error);
}
}
function highlightOutput(wrappedOutput) {
return function(write) {
if (write.Body) lineHighlight(write.Body);
wrappedOutput(write);
}
}
function lineClear() {
$(".lineerror").removeClass("lineerror");
}
// opts is an object with these keys
// codeEl - code editor element
// outputEl - program output element
// runEl - run button element
// fmtEl - fmt button element (optional)
// shareEl - share button element (optional)
// shareURLEl - share URL text input element (optional)
// shareRedirect - base URL to redirect to on share (optional)
// toysEl - toys select element (optional)
// enableHistory - enable using HTML5 history API (optional)
// transport - playground transport to use (default is HTTPTransport)
function playground(opts) {
var code = $(opts.codeEl);
var transport = opts['transport'] || new HTTPTransport();
var running;
// autoindent helpers.
function insertTabs(n) {
// find the selection start and end
var start = code[0].selectionStart;
var end = code[0].selectionEnd;
// split the textarea content into two, and insert n tabs
var v = code[0].value;
var u = v.substr(0, start);
for (var i=0; i<n; i++) {
u += "\t";
}
u += v.substr(end);
// set revised content
code[0].value = u;
// reset caret position after inserted tabs
code[0].selectionStart = start+n;
code[0].selectionEnd = start+n;
}
function autoindent(el) {
var curpos = el.selectionStart;
var tabs = 0;
while (curpos > 0) {
curpos--;
if (el.value[curpos] == "\t") {
tabs++;
} else if (tabs > 0 || el.value[curpos] == "\n") {
break;
}
}
setTimeout(function() {
insertTabs(tabs);
}, 1);
}
function keyHandler(e) {
if (e.keyCode == 9) { // tab
insertTabs(1);
e.preventDefault();
return false;
}
if (e.keyCode == 13) { // enter
if (e.shiftKey) { // +shift
run();
e.preventDefault();
return false;
} else {
autoindent(e.target);
}
}
return true;
}
code.unbind('keydown').bind('keydown', keyHandler);
var outdiv = $(opts.outputEl).empty();
var output = $('<pre/>').appendTo(outdiv);
function body() {
return $(opts.codeEl).val();
}
function setBody(text) {
$(opts.codeEl).val(text);
}
function origin(href) {
return (""+href).split("/").slice(0, 3).join("/");
}
var pushedEmpty = (window.location.pathname == "/");
function inputChanged() {
if (pushedEmpty) {
return;
}
pushedEmpty = true;
$(opts.shareURLEl).hide();
window.history.pushState(null, "", "/");
}
function popState(e) {
if (e === null) {
return;
}
if (e && e.state && e.state.code) {
setBody(e.state.code);
}
}
var rewriteHistory = false;
if (window.history && window.history.pushState && window.addEventListener && opts.enableHistory) {
rewriteHistory = true;
code[0].addEventListener('input', inputChanged);
window.addEventListener('popstate', popState);
}
function setError(error) {
if (running) running.Kill();
lineClear();
lineHighlight(error);
output.empty().addClass("error").text(error);
}
function loading() {
lineClear();
if (running) running.Kill();
output.removeClass("error").text('Waiting for remote server...');
}
function run() {
loading();
running = transport.Run(body(), highlightOutput(PlaygroundOutput(output[0])));
}
function fmt() {
loading();
$.ajax("/fmt", {
data: {"body": body()},
type: "POST",
dataType: "json",
success: function(data) {
if (data.Error) {
setError(data.Error);
} else {
setBody(data.Body);
setError("");
}
}
});
}
$(opts.runEl).click(run);
$(opts.fmtEl).click(fmt);
if (opts.shareEl !== null && (opts.shareURLEl !== null || opts.shareRedirect !== null)) {
var shareURL;
if (opts.shareURLEl) {
shareURL = $(opts.shareURLEl).hide();
}
var sharing = false;
$(opts.shareEl).click(function() {
if (sharing) return;
sharing = true;
var sharingData = body();
$.ajax("/share", {
processData: false,
data: sharingData,
type: "POST",
complete: function(xhr) {
sharing = false;
if (xhr.status != 200) {
alert("Server error; try again.");
return;
}
if (opts.shareRedirect) {
window.location = opts.shareRedirect + xhr.responseText;
}
if (shareURL) {
var path = "/p/" + xhr.responseText;
var url = origin(window.location) + path;
shareURL.show().val(url).focus().select();
if (rewriteHistory) {
var historyData = {"code": sharingData};
window.history.pushState(historyData, "", path);
pushedEmpty = false;
}
}
}
});
});
}
if (opts.toysEl !== null) {
$(opts.toysEl).bind('change', function() {
var toy = $(this).val();
$.ajax("/doc/play/"+toy, {
processData: false,
type: "GET",
complete: function(xhr) {
if (xhr.status != 200) {
alert("Server error; try again.");
return;
}
setBody(xhr.responseText);
}
});
});
}
}
window.playground = playground;
})();

Просмотреть файл

@ -1,167 +0,0 @@
/* Global */
body {
background: white;
color: black;
font-family: Arial, sans-serif;
}
h1, h2, h3, h4, h5 {
font-weight: normal;
}
p, li {
font-size: 10pt;
}
p {
line-height: 1.6em;
}
li, pre {
line-height: 1.3em;
}
pre, code {
font-size: 10pt;
font-family: Consolas, monospace;
}
a {
color: #5588aa;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* Layout */
body {
text-align: center;
}
#container {
margin-left: auto;
margin-right: auto;
text-align: left;
width: 800px;
}
#content {
width: 550px;
}
#sidebar {
width: 220px;
float: right;
}
#footer {
clear: both;
padding-top: 20px;
text-align: center;
}
/* Heading */
#heading {
margin: 30px 0;
font-size: 150%;
font-weight: normal;
}
#heading a {
color: black;
}
#heading img {
margin-left: -30px;
margin-right: 10px;
margin-bottom: -15px;
}
/* Sidebar */
#sidebar h1 {
font-size: 100%;
}
#sidebar li {
list-style-type: none;
}
#sidebar ul {
list-style-type: none;
padding: 0;
}
/* Content */
#content h1.title {
font-size: 130%;
}
#content h1.title a {
color: inherit;
text-decoration: none;
}
#content h1.title a:hover {
color: #5588aa;
text-decoration: underline;
}
#content h1, #content h2, #content h3, #content h4, #content h4 {
font-size: 100%;
}
#content .date {
text-transform: uppercase;
color: #999;
letter-spacing: .2em;
font-size: 8pt;
}
#content .author {
font-style: italic;
}
#content .article {
margin-bottom: 50px;
}
#content img {
max-width: 550px;
}
/* Code and playground */
.code pre, .playground pre, .output pre {
margin: 0;
padding: 0;
background: none;
border: none;
width: 100%;
height: 100%;
overflow: auto;
}
.playground .number {
color: #999;
}
.code, .playground, .output {
margin: 20px 0;
padding: 10px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.code, .playground {
background: #e9e9e9;
}
.output {
background: #202020;
}
.output .stdout, .output pre {
color: #e6e6e6;
}
.output .stderr, .output .error {
color: rgb(244, 74, 63);
}
.output .system, .output .exit {
color: rgb(255, 209, 77)
}
.buttons {
position: relative;
float: right;
top: -50px;
right: 10px;
}
.output .buttons {
top: -60px;
right: 0;
height: 0;
}
.buttons .kill {
display: none;
visibility: hidden;
}
/* Index */
.tags {
color: #ccc;
font-size: smaller;
}

Просмотреть файл

@ -31,7 +31,7 @@
{{end}}
{{define "section"}}
<h{{len .Number}} id="TOC_{{.FormattedNumber}}">{{.Title}}</h{{len .Number}}>
<h4 id="TOC_{{.FormattedNumber}}">{{.Title}}</h4>
{{range .Elem}}{{elem $.Template .}}{{end}}
{{end}}

Просмотреть файл

@ -6,32 +6,78 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>{{template "title" .}}</title>
<link type="text/css" rel="stylesheet" href="/static/style.css">
<link type="text/css" rel="stylesheet" href="/lib/godoc/style.css">
<link rel="alternate" type="application/atom+xml" title="blog.golang.org - Atom Feed" href="http://blog.golang.org/feed.atom" />
<script type="text/javascript">window.initFuncs = [];</script>
<style>
#sidebar {
float: right;
padding-left: 20px;
width: 250px;
background: white;
}
#sidebar p, #sidebar ul {
margin: 20px 5px;
}
#sidebar ul {
padding: 0;
}
#sidebar li {
list-style-type: none;
}
#content .author {
font-style: italic;
}
#content .article {
margin-bottom: 50px;
}
#content .date {
color: #999;
}
#content .tags {
color: #999;
font-size: smaller;
}
#content .iframe, #content .image {
margin: 20px;
}
</style>
</head>
<body>
<div id="container">
<div id="topbar"><div class="container">
<div id="heading">
<a href="http://golang.org/"><img src="/static/logo.png"></a>
<a href="{{.BasePath}}/">The Go Programming Language Blog</a>
</div><!-- #heading -->
<form method="GET" action="{{.GodocURL}}/search">
<div id="menu">
<a href="{{.GodocURL}}/doc/">Documents</a>
<a href="{{.GodocURL}}/pkg/">Packages</a>
<a href="{{.GodocURL}}/project/">The Project</a>
<a href="{{.GodocURL}}/help/">Help</a>
<a href="{{.GodocURL}}/blog/">Blog</a>
<input type="text" id="search" name="q" class="inactive" value="Search" placeholder="Search">
</div>
<div id="heading"><a href="{{.GodocURL}}/">The Go Programming Language</a></div>
</form>
</div></div>
<div id="page">
<div class="container">
<div id="sidebar">
{{with .Doc}}
{{with .Newer}}
<h1>Next article</h1>
<h4>Next article</h4>
<p><a href="{{.Path}}">{{.Title}}</a></p>
{{end}}
{{with .Older}}
<h1>Previous article</h1>
<h4>Previous article</h4>
<p><a href="{{.Path}}">{{.Title}}</a></p>
{{end}}
{{with .Related}}
<h1>Related articles</h1>
<h4>Related articles</h4>
<ul>
{{range .}}
<li><a href="{{.Path}}">{{.Title}}</a></li>
@ -40,7 +86,7 @@
{{end}}
{{end}}
<h1>Links</h1>
<h4>Links</h4>
<ul>
<li><a href='http://golang.org/'>golang.org</a></li>
<li><a href='http://golang.org/doc/install.html'>Install Go</a></li>
@ -52,7 +98,7 @@
<li><a href='http://twitter.com/go_nuts'>Go on Twitter</a></li>
</ul>
<h1>Keep up with Google Developers</h1>
<h4>Keep up with Google Developers</h4>
<ul>
<li><a href='https://plus.google.com/+GoogleDevelopers/posts'>Google Developers +Page</a></li>
<li><a href='https://developers.google.com/live/'>Google Developers Live</a></li>
@ -60,7 +106,7 @@
<li><a href='http://googledevelopers.blogspot.com/'>Google Developers Blog</a></li>
</ul>
<h1>Blog Archive</h1>
<h4>Blog Archive</h4>
<p><a href="{{.BasePath}}/index">Article index</a></p>
</div><!-- #sidebar -->
@ -80,12 +126,14 @@
</p>
</div><!-- #footer -->
</div><!-- #container -->
</div><!-- .container -->
</div><!-- #page -->
</body>
<script src="/static/jquery.js"></script>
<script src="/static/play/playground.js"></script>
<script src="/static/play/play.js"></script>
<script src="/lib/godoc/jquery.js"></script>
<script src="/lib/godoc/playground.js"></script>
<script src="/lib/godoc/play.js"></script>
<script src="/lib/godoc/godocs.js"></script>
<script>
$(function() {
// Insert line numbers for all playground elements.
@ -120,8 +168,8 @@ $(function() {
{{define "doc"}}
<div class="article">
<p class="date">{{.Time.Format "2 January 2006"}}</p>
<h1 class="title"><a href="{{.Path}}">{{.Title}}</a></h1>
<p class="date">{{.Time.Format "2 January 2006"}}</p>
{{.HTML}}
{{with .Authors}}
<p class="author">By {{authors .}}</p>