Ejemplo n.º 1
0
func (this *TopicController) View() {
	this.Data["IsLogin"] = checkAccount(this.Ctx)
	this.Data["IsTopic"] = true
	this.TplNames = "topic_view.html"

	topic, err := models.GetTopic(this.Ctx.Input.Param("0"))
	if err != nil {
		beego.Error(err)
		this.Redirect("/", 302)
		return
	}

	tid := this.Ctx.Input.Param("0")
	this.Data["Tid"] = tid
	this.Data["Tag"] = strings.Split(topic.Tag, ",")
	topic.Content = string(blackfriday.MarkdownCommon([]byte(topic.Content)))
	this.Data["Topic"] = topic

	replies, err := models.GetAllReplies(tid)
	if err != nil {
		beego.Error(err)
		return
	}

	for _, reply := range replies {
		unsafe := blackfriday.MarkdownCommon([]byte(reply.Content))
		reply.Content = string(bluemonday.UGCPolicy().SanitizeBytes(unsafe))
	}

	this.Data["Replies"] = replies
	this.locale()
}
Ejemplo n.º 2
0
func testUpdatePost(t *testing.T, title string, markdown string) {
	// create JSON payload
	var p Post
	p.Title = title
	p.Markdown = markdown
	p.Content = string(blackfriday.MarkdownCommon([]byte(markdown)))
	apiPayload, _ := json.Marshal(p)

	// save changes to global object for further testing comparison
	post.Title = p.Title
	post.Content = string(blackfriday.MarkdownCommon([]byte(markdown)))
	post.Markdown = markdown
	post.Excerpt = excerpt.Make(p.Content, 15)

	testUpdatePostAPI(t, apiPayload)

	// creates form-encoded payload
	p2 := url.Values{}
	p2.Set("title", title)
	p2.Add("markdown", markdown)
	frontendPayload := p2.Encode()

	// save changes to global object for further testing comparison
	post.Title = p2.Get("title")
	post.Markdown = p2.Get("markdown")
	post.Excerpt = excerpt.Make(post.Content, 15)

	testUpdatePostFrontend(t, frontendPayload)
	TestReadPost(t)
}
Ejemplo n.º 3
0
func (m *TopicMgr) DoTopicUpdate(topic *Topic) {
	topic.Content = string(blackfriday.MarkdownCommon([]byte(topic.Content)))
	reg, _ := regexp.Compile(`\</\w{1,3}\>`)
	index := reg.FindAllStringIndex(topic.Content, 8)
	x := index[len(index)-1]
	topic.Preview = string(blackfriday.MarkdownCommon([]byte(topic.Content[:x[len(x)-1]])))

	topic.URL = fmt.Sprintf("/%s/%d.html", topic.CreateTime.Format(helper.Layout_y_m_d), topic.ID)
	topic.Time = topic.CreateTime.Format(helper.Layout_y_m_d)
}
Ejemplo n.º 4
0
Archivo: blog.go Proyecto: vanzswc/blog
func BlogEntry(ren render.Render, db *mgo.Database, args martini.Params) {
	var result dbBlogEntry

	Id, _ := strconv.Atoi(args["Id"])

	// Find Blogentry by Id (should be only one)
	db.C("blogEntries").Find(bson.M{"id": Id}).One(&result)

	fmt.Println(string(blackfriday.MarkdownCommon([]byte(result.Text))))

	result.Text = string(blackfriday.MarkdownCommon([]byte(result.Text)))

	// render the template using the result from the db
	ren.HTML(200, "blogEntry", result)
}
Ejemplo n.º 5
0
func (this *MarkTest) Get() {
	buf, err := ioutil.ReadFile("view/首页.md")
	if err != nil {
	}
	output := blackfriday.MarkdownCommon(buf)
	this.Ctx.WriteByte(output)
}
Ejemplo n.º 6
0
func (self *Visitor) visit(path string, f os.FileInfo, err error) error {
	if f == nil {
		return err
	}
	if f.IsDir() {
		return nil
	} else if (f.Mode() & os.ModeSymlink) > 0 {
		return nil
	} else {
		if strings.HasSuffix(f.Name(), ".md") {
			fmt.Println(f)
			file, err := os.Open(f.Name())
			if err != nil {
				return err
			}
			input, _ := ioutil.ReadAll(file)
			input = regexp.MustCompile("\\[(.*?)\\]\\(<?(.*?)\\.md>?\\)").ReplaceAll(input, []byte("[$1](<$2.html>)"))
			output := blackfriday.MarkdownCommon(input)
			var out *os.File
			if out, err = os.Create(strings.Replace(f.Name(), ".md", ".html", -1)); err != nil {
				fmt.Fprintf(os.Stderr, "Error creating %s: %v", f.Name(), err)
				os.Exit(-1)
			}
			defer out.Close()
			if _, err = out.Write(output); err != nil {
				fmt.Fprintln(os.Stderr, "Error writing output:", err)
				os.Exit(-1)
			}
		}
	}
	return nil
}
Ejemplo n.º 7
0
func articleHandler(w http.ResponseWriter, req *http.Request) {
	uri := req.RequestURI
	name := uri[len("/article/"):]
	var selected Article

	for _, article := range gArticles {
		if name == article.name || name+".md" == article.name {
			selected = article
		}
	}

	if selected.path == "" {
		w.WriteHeader(404)
		fmt.Fprintf(w, "Not found")
		return
	}

	data, err := ioutil.ReadFile(selected.path)
	if err != nil {
		w.WriteHeader(500)
		fmt.Fprint(w, err)
		return
	}

	unsafe := blackfriday.MarkdownCommon(data)
	html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)

	w.Header().Add("Content-Type", "text/html")
	w.WriteHeader(200)
	w.Write(html)
}
Ejemplo n.º 8
0
func (p Property) DescriptionInMarkdown() (template.HTML, error) {
	unsafeMarkdown := blackfriday.MarkdownCommon([]byte(p.Description))
	safeMarkdown := bluemonday.UGCPolicy().SanitizeBytes(unsafeMarkdown)

	// todo sanitized markdown
	return template.HTML(safeMarkdown), nil
}
Ejemplo n.º 9
0
func GetGodos() []model.Godo {
	var godos []model.Godo

	// Load all godos from the database
	rows, err := db.Query("SELECT ID, Title, Content FROM godo ORDER BY Rank")
	if err != nil {
		log.Println(err)
	}

	// Ensure that the database connection is closed
	defer rows.Close()

	for rows.Next() {
		var id int
		var title string
		var content string

		// Fill variables with data from row
		if err := rows.Scan(&id, &title, &content); err != nil {
			log.Println(err)
		}

		// Create a Godo from the extracted data
		godo := model.Godo{
			ID:          id,
			Title:       title,
			ContentMD:   content,
			ContentHTML: template.HTML(string(md.MarkdownCommon([]byte(content))))}

		godos = append(godos, godo)
	}
	return godos
}
Ejemplo n.º 10
0
func GetBlog(w http.ResponseWriter, r *http.Request) {
	u := User{}

	// Check if offset and limit were provided for pagination
	vars := mux.Vars(r)
	offset, err := strconv.Atoi(vars["offset"])
	if err != nil {
		// If var is empty assign default value
		if strings.ContainsAny(err.Error(), "invalid syntax") {
			offset = 0
		} else {
			log.Printf("No offset query parameter: %s", err)
		}
	}
	limit, err := strconv.Atoi(vars["limit"])
	if err != nil {
		// If var is empty assign default value
		if strings.ContainsAny(err.Error(), "invalid syntax") {
			limit = 10
		} else {
			log.Printf("No offset query parameter: %s", err)
		}
	}

	b, err := selectPageBlogEntries(limit, offset)
	if err != nil {
		log.Printf("ERROR: could not retrieve blog entries")
	}

	bFmt := []Post{}
	for _, post := range b {
		blogpost := Post{
			Id:       post.Id,
			Postdate: post.PostDate.Format("15:04:05 Mon Jan 2 2006"),
			Title:    post.Title,
			Tags:     post.Tags,
			Body:     template.HTML(string(blackfriday.MarkdownCommon([]byte(post.Body)))),
		}
		bFmt = append(bFmt, blogpost)
	}

	data := struct {
		Blog []Post
		User User
	}{
		bFmt,
		u,
	}

	t, err := template.ParseFiles("tmpl/base.html",
		"tmpl/header.tmpl",
		"tmpl/navbar.tmpl",
		"tmpl/blog-body.tmpl",
		"tmpl/footer.tmpl",
		"tmpl/js.tmpl")
	if err != nil {
		log.Printf("ERROR: error creating blog HTML template: %s", err)
	}
	t.Execute(w, data)
}
Ejemplo n.º 11
0
//Presentation handles showing page with details about a presentation.
func Presentation(c util.Context) (err error) {
	pk, err := datastore.DecodeKey(c.Vars["id"])
	if err != nil {
		return
	}

	p, err := presentation.GetByKey(pk, c)
	if err != nil {
		return
	}
	as, err := action.GetFor(p, c)
	if err != nil {
		return
	}

	a := prepareActions(as)

	desc := blackfriday.MarkdownCommon(p.Description)

	acts, err := activation.GetForPresentation(pk, c)
	if err != nil {
		return
	}

	util.RenderLayout("presentation.html", "Info o prezentácií", struct {
		P           *presentation.Presentation
		A           map[string][]time.Time
		Desc        template.HTML
		ZeroTime    time.Time
		Domain      string
		Activations []*activation.Activation
		Tz          *time.Location
	}{p, a, template.HTML(desc), time.Date(0001, 01, 01, 00, 00, 00, 00, utc), appengine.DefaultVersionHostname(c), acts, util.Tz}, c, "/static/js/underscore-min.js", "/static/js/presentation.js")
	return
}
Ejemplo n.º 12
0
// Get Загружает markdown-файл и конвертирует его в HTML
// Возвращает объект типа Post
// Если путь не существует или является каталогом, то возвращаем ошибку
func (p *postArray) Get(md string) (post, int, error) {
	info, err := os.Stat(md)
	if err != nil {
		if os.IsNotExist(err) {
			// файл не существует
			return post{}, 404, err
		}
		return post{}, 500, err
	}
	if info.IsDir() {
		// не файл, а папка
		return post{}, 404, fmt.Errorf("dir")
	}
	val, ok := p.Items[md]
	if !ok || (ok && val.ModTime != info.ModTime().UnixNano()) {
		p.RLock()
		defer p.RUnlock()
		fileread, _ := ioutil.ReadFile(md)
		lines := strings.Split(string(fileread), "\n")
		title := string(lines[0])
		body := strings.Join(lines[1:len(lines)], "\n")
		body = string(blackfriday.MarkdownCommon([]byte(body)))
		p.Items[md] = post{title, template.HTML(body), info.ModTime().UnixNano()}
	}
	post := p.Items[md]
	return post, 200, nil
}
Ejemplo n.º 13
0
func readPageAsHtml(docName, PageFilePath string) ([]byte, error) {
	data, err := ioutil.ReadFile(PageFilePath)
	if err != nil {
		return nil, err
	}

	unsafe := blackfriday.MarkdownCommon(data)
	// TODO: It could be possible sanitize content before and after
	// rendering the wiki-text tags. The post wiki-text sanitising would
	// be slightly looser and allow html class attributes.
	unsafe = kaufmann.RenderWikiText(docName, unsafe)

	p := bluemonday.UGCPolicy()
	p.AllowAttrs("class").Matching(bluemonday.SpaceSeparatedTokens).Globally()
	// NOTE: At the moment we are allowing anything to be placed in a data attribute.
	// We could add a regex to limit the value to valid and safe(!) characters.
	// But we will have to write a regex. I can't see any thing suitable in
	// the bluemonday code.
	// Related: http://stackoverflow.com/q/25897910/395461
	p.AllowAttrs("data-pageid").Globally()
	p.AllowAttrs("data-filename").Globally()
	html := p.SanitizeBytes(unsafe)

	return html, nil
}
Ejemplo n.º 14
0
// Reads a file, if the file has the .md extension the contents are parsed and HTML is returned.
func (host *Host) readFile(file string) ([]byte, error) {
	stat, err := os.Stat(file)

	if err != nil {
		return nil, err
	}

	if stat.IsDir() == false {

		fp, _ := os.Open(file)
		defer fp.Close()

		buf := make([]byte, stat.Size())

		_, err := fp.Read(buf)

		if err != nil {
			return nil, err
		}

		if strings.HasSuffix(file, ".md") {
			return md.MarkdownCommon(buf), nil
		} else {
			return buf, nil
		}

	}

	return nil, nil
}
Ejemplo n.º 15
0
// `highlight` pipes the source to Pygments, section by section
// delimited by dividerText, then reads back the highlighted output,
// searches for the delimiters and extracts the HTML version of the code
// and documentation for each `Section`
func highlight(source string, sections *list.List) {
	language := getLanguage(source)
	pygments := exec.Command("pygmentize", "-l", language.name, "-f", "html", "-O", "encoding=utf-8")
	pygmentsInput, _ := pygments.StdinPipe()
	pygmentsOutput, _ := pygments.StdoutPipe()
	// start the process before we start piping data to it
	// otherwise the pipe may block
	pygments.Start()
	for e := sections.Front(); e != nil; e = e.Next() {
		pygmentsInput.Write(e.Value.(*Section).codeText)
		if e.Next() != nil {
			io.WriteString(pygmentsInput, language.dividerText)
		}
	}
	pygmentsInput.Close()

	buf := new(bytes.Buffer)
	io.Copy(buf, pygmentsOutput)

	output := buf.Bytes()
	output = bytes.Replace(output, []byte(highlightStart), nil, -1)
	output = bytes.Replace(output, []byte(highlightEnd), nil, -1)

	for e := sections.Front(); e != nil; e = e.Next() {
		index := language.dividerHTML.FindIndex(output)
		if index == nil {
			index = []int{len(output), len(output)}
		}

		fragment := output[0:index[0]]
		output = output[index[1]:]
		e.Value.(*Section).CodeHTML = bytes.Join([][]byte{[]byte(highlightStart), []byte(highlightEnd)}, fragment)
		e.Value.(*Section).DocsHTML = blackfriday.MarkdownCommon(e.Value.(*Section).docsText)
	}
}
Ejemplo n.º 16
0
func (markdown Markdown) Extract(creativeWork *schema.CreativeWork, path string) error {
	markdownContent, err := ioutil.ReadFile(path)
	if nil != err {
		return err
	}

	html := blackfriday.MarkdownCommon(markdownContent)

	doc, err := goquery.NewDocumentFromReader(bytes.NewReader(html))
	if nil != err {
		return err
	}

	doc.Find("a[href]").Each(func(i int, s *goquery.Selection) {
		link, _ := s.Attr("href")
		url, _ := url.Parse(link)

		if !url.IsAbs() && strings.HasSuffix(link, ".md") {
			s.SetAttr("href", fmt.Sprint(link[:len(link)-3], ".jsonld"))
		}
	})

	creativeWork.Name = doc.Find("h1").Text()
	creativeWork.Text, err = doc.Find("body").Html()
	if nil != err {
		return err
	}

	return nil
}
Ejemplo n.º 17
0
Archivo: page.go Proyecto: cenan/defter
func Search(db *sql.DB, query string) ([]Page, error) {
	var pages []Page
	var id int
	var title []byte
	var input []byte
	var updatedAt int64

	sql := `
		SELECT
			id, title, content, updated_at
		FROM
			pages
		WHERE
			title like ? or content like ?
		ORDER BY updated_at DESC`
	rows, err := db.Query(sql, "%"+query+"%", "%"+query+"%")
	if err != nil {
		return pages, err
	}
	for rows.Next() {
		err = rows.Scan(&id, &title, &input, &updatedAt)
		if err != nil {
			return pages, err
		}
		output := blackfriday.MarkdownCommon(input)
		page := Page{
			ID:          id,
			Title:       string(title),
			HTMLContent: template.HTML(output),
			UpdatedAt:   updatedAt,
		}
		pages = append(pages, page)
	}
	return pages, nil
}
Ejemplo n.º 18
0
// OpenFile Open Article file and store to ArticleObject
func OpenFile() {
	for _, path := range ArticlePath {
		mainBody, _ := ioutil.ReadFile(path)

		First := 0
		//find ---
		for idx := 0; idx < len(mainBody)-3; idx++ {
			if mainBody[idx] == byte(45) && mainBody[idx+1] == byte(45) && mainBody[idx+2] == byte(45) {
				First = idx
				break
			}
		}

		// yml part
		var thisArticle Article
		err := yaml.Unmarshal(mainBody[:First-1], &thisArticle)
		if err != nil {
			fmt.Println("文章配置出错:  Path: "+path, err.Error())
		}
		if thisArticle.Date == "0000-00-00 00:00:00" {
			thisArticle.Date = "1899-11-30 00:00:00"
		}
		thisArticle.URI = URIGen(path, thisArticle.Date)
		html := blackfriday.MarkdownCommon(mainBody[First+4:])
		thisArticle.Body = string(html)
		ArticleObject = append(ArticleObject, thisArticle)
	}
}
Ejemplo n.º 19
0
func PageHandler(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		idKey := r.URL.Path[len("/p/"):]
		fmt.Println(idKey)
		id, _ := strconv.Atoi(idKey)
		page, err := pageCol.Read(id)

		if err != nil {
			customNotFound(w, r)
			return
		}

		t, err := template.ParseFiles("templates/page.html")
		if err != nil {
			panic(err)
		}

		descMd := blackfriday.MarkdownCommon([]byte(page["desc"].(string)))
		t.Execute(w, &Page{
			Title: (page["title"]).(string),
			Desc:  template.HTML(descMd),
			Date:  (page["date"]).(string),
			Bg:    (page["bg"]).(string),
		})

	default:
		http.Error(w, "Methods not supported", 405)
	}
}
Ejemplo n.º 20
0
//Excerpt returns post excerpt, 300 char long. Html tags are stripped.
func (post *Post) Excerpt() template.HTML {
	//you can sanitize, cut it down, add images, etc
	policy := bluemonday.StrictPolicy() //remove all html tags
	sanitized := policy.Sanitize(string(blackfriday.MarkdownCommon([]byte(post.Content))))
	excerpt := template.HTML(truncate(sanitized, 300) + "...")
	return excerpt
}
Ejemplo n.º 21
0
Archivo: init.go Proyecto: pjvds/acvte
func init() {
	// Filters is the default set of global filters.
	revel.Filters = []revel.Filter{
		revel.PanicFilter,             // Recover from panics and display an error page instead.
		revel.RouterFilter,            // Use the routing table to select the right Action
		revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
		revel.ParamsFilter,            // Parse parameters into Controller.Params.
		revel.SessionFilter,           // Restore and write the session cookie.
		revel.FlashFilter,             // Restore and write the flash cookie.
		revel.ValidationFilter,        // Restore kept validation errors and save new ones from cookie.
		revel.I18nFilter,              // Resolve the requested language
		HeaderFilter,                  // Security-based headers
		revel.InterceptorFilter,       // Run interceptors around the action.
		revel.ActionInvoker,           // Invoke the action.
	}

	// revel.FilterController(controllers.Admin{}).
	// 	Add(AuthenticationFilter)
	//auth.AclApply(aclMap)

	// template functions
	revel.TemplateFuncs["markdown"] = func(str string) string {
		output := blackfriday.MarkdownCommon([]byte(str))
		return string(output)
	}
}
Ejemplo n.º 22
0
func getPosts(g *gas.Gas, postId interface{}) []*Post {
	rows, err := QueryPage.Query(postId)

	if err != nil {
		g.ErrorPage(http.StatusServiceUnavailable)
		panic(err)
	}

	posts := []*Post{}
	for rows.Next() {
		// TODO: better way to do this?
		var (
			id    int64
			stamp string
			title string
			body  []byte
			tag   string
		)

		err = rows.Scan(&id, &stamp, &title, &body, &tag)
		if err != nil {
			g.ErrorPage(http.StatusServiceUnavailable)
		}
		timestamp, _ := time.Parse("2006-01-02 15:04:05-07", stamp)
		posts = append(posts, &Post{id, timestamp, title, string(blackfriday.MarkdownCommon(body)), tag})
	}

	return posts
}
Ejemplo n.º 23
0
func GetMarkdown(file string) template.HTML {
	if input, err := ioutil.ReadFile(file); err == nil {
		return template.HTML(blackfriday.MarkdownCommon(input))
	}

	return template.HTML("&nbsp;")
}
func (self *Visitor) visit(path string, f os.FileInfo, err error) error {
	if f == nil {
		return err
	}
	if f.IsDir() {
		return nil
	} else if (f.Mode() & os.ModeSymlink) > 0 {
		return nil
	} else {
		if strings.Contains(f.Name(), ".md") {
			fmt.Println(f)
			file, err := os.Open(f.Name())
			if err != nil {
				return err
			}
			input, _ := ioutil.ReadAll(file)
			output := blackfriday.MarkdownCommon(input)
			var out *os.File
			if out, err = os.Create(f.Name() + ".html"); err != nil {
				fmt.Fprintf(os.Stderr, "Error creating %s: %v", f.Name(), err)
				os.Exit(-1)
			}
			defer out.Close()
			if _, err = out.Write(output); err != nil {
				fmt.Fprintln(os.Stderr, "Error writing output:", err)
				os.Exit(-1)
			}
		}
	}
	return nil
}
Ejemplo n.º 25
0
// parse markdown file and convert to html
func parseSourceFile(srcFilePath string) (*Post, error) {
	post := &Post{}

	post.Name = trimPath(srcFilePath)

	// date
	d, err := parseDate(post.Name)
	if err != nil {
		log.Warning(err)
	}
	post.Date = d

	// read file
	data, err := ioutil.ReadFile(srcFilePath)
	if err != nil {
		return nil, err
	}

	// parse title from first headline
	lines := strings.Split(string(data), "\n")
	for _, line := range lines {
		if s := strings.TrimLeft(line, " "); strings.HasPrefix(s, "#") {
			post.Title = strings.TrimLeft(strings.TrimLeft(s, "#"), " ")
			break
		}
	}

	// convert markdown to html
	content := strings.Join(lines, "\n")
	output := blackfriday.MarkdownCommon([]byte(content))
	post.Content = string(output)

	return post, nil
}
Ejemplo n.º 26
0
// HtmlWriter renders the markdown listing to HTML, writing that to
// a file.
func HtmlWriter(markdown, filename string) (err error) {
	var page struct {
		Filename string
		Date     string
		Markdown template.HTML
	}
	rendered := string(blackfriday.MarkdownCommon([]byte(markdown)))
	page.Markdown = template.HTML(rendered)
	page.Date = time.Now().Format(DateFormat)
	page.Filename = filename

	tmpl, err := template.New(filename).Parse(HtmlTemplate)
	if err != nil {
		return
	}

	htmlBuffer := new(bytes.Buffer)
	err = tmpl.Execute(htmlBuffer, page)
	if err != nil {
		return
	}

	outFile := GetOutFile(filename + ".html")
	err = ioutil.WriteFile(outFile, htmlBuffer.Bytes(), 0644)
	return
}
Ejemplo n.º 27
0
func main() {
	esInput := html.EscapeString(input)
	unsafe := blackfriday.MarkdownCommon([]byte(esInput))
	html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)

	fmt.Println(string(html))
}
Ejemplo n.º 28
0
// FromMarkdown reads a post folder (that follows our special structure) and creates a new
// post structure with fields filled from the file loaded.
func FromMarkdown(pathname string) (*BPost, error) {
	bp := &BPost{}

	var err error

	// extract the date from the filename
	dateStr := filepath.Base(pathname)[:10]
	bp.DateCreated, err = time.Parse("2006-01-02", dateStr)
	if err != nil {
		return nil, err
	}

	// assign the Url to the current pathname but might be overwritten
	bp.UrlPermalink = filepath.Base(pathname)[11:]
	bp.UrlPermalink = bp.UrlPermalink[:len(bp.UrlPermalink)-3]

	markdown, err := ioutil.ReadFile(pathname)
	if err != nil {
		return nil, err
	}
	bytesRead, err := parseFrontMatter(bp, markdown)

	remBytes := markdown[bytesRead:]
	bp.ContentMarkdown = string(remBytes)
	bp.ContentHtml = template.HTML(string(blackfriday.MarkdownCommon(remBytes)))

	return bp, nil
}
Ejemplo n.º 29
0
func ConvertMdToHtml(input []byte) (html []byte) {

	unsafe := blackfriday.MarkdownCommon(input)
	html = bluemonday.UGCPolicy().SanitizeBytes(unsafe)
	return

}
Ejemplo n.º 30
0
func view(prefix string) http.Handler {
	f := func(w http.ResponseWriter, r *http.Request) {
		c := appengine.NewContext(r)
		p := r.URL.Path[len(prefix):]
		if p == "" {
			p = "index"
		}
		s := new(Page)
		k := datastore.NewKey(c, "string", p, 0, nil)
		if item, err := memcache.Get(c, p); err == memcache.ErrCacheMiss {
			datastore.Get(c, k, s)
			err = memcache.Set(c, &memcache.Item{Key: p, Value: []byte(s.Content)})
			if err != nil {
				panic(err)
			}
		} else if err != nil {
			panic(err)
		} else {
			s.Content = string(item.Value)
		}
		output := string(blackfriday.MarkdownCommon([]byte(s.Content)))
		viewTemplate.Execute(w, Foo{p, output, ""})
	}
	return http.HandlerFunc(f)
}