miniflux/generate.go
Carl Helmertz 15a11c3da9 Unsubscribe from feed through link or "#"
After importing old OPML files, you may discover that many feeds are
obsolete or uninteresting. You list the feeds entries and determine that
you want to unsubscribe. This needs three clicks (edit feed, delete,
confirm) and requires moving the mouse to hit the different targets.

This quickly becomes tiring, if you are up to possibly deleting hundreds
of feeds. One mediation, introduced in this commit, is to add an
unsubscribe link to each feed's entry listing view, and also adding a
keyboard shortcut.

The keyboard shortcut "#" is:
* longer than one keystroke (requires shift)
* hard to type by accident
* used in Google products (thanks for the hint @fguillot)

In an effort to try to reduce the number of accidental feed
unsubscriptions.
2018-10-19 20:05:26 -07:00

227 lines
5.7 KiB
Go

// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// +build ignore
package main
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"text/template"
"github.com/tdewolff/minify"
"github.com/tdewolff/minify/css"
"github.com/tdewolff/minify/js"
)
const tpl = `// Code generated by go generate; DO NOT EDIT.
package {{ .Package }} // import "miniflux.app/{{ .ImportPath }}"
var {{ .Map }} = map[string]string{
{{ range $constant, $content := .Files }}` + "\t" + `"{{ $constant }}": ` + "`{{ $content }}`" + `,
{{ end }}}
var {{ .Map }}Checksums = map[string]string{
{{ range $constant, $content := .Checksums }}` + "\t" + `"{{ $constant }}": "{{ $content }}",
{{ end }}}
`
var bundleTpl = template.Must(template.New("").Parse(tpl))
type Bundle struct {
Package string
Map string
ImportPath string
Files map[string]string
Checksums map[string]string
}
func (b *Bundle) Write(filename string) {
f, err := os.Create(filename)
if err != nil {
panic(err)
}
defer f.Close()
bundleTpl.Execute(f, b)
}
func NewBundle(pkg, mapName, importPath string) *Bundle {
return &Bundle{
Package: pkg,
Map: mapName,
ImportPath: importPath,
Files: make(map[string]string),
Checksums: make(map[string]string),
}
}
func readFile(filename string) []byte {
data, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
return data
}
func checksum(data []byte) string {
return fmt.Sprintf("%x", sha256.Sum256(data))
}
func basename(filename string) string {
return path.Base(filename)
}
func stripExtension(filename string) string {
filename = strings.TrimSuffix(filename, path.Ext(filename))
return strings.Replace(filename, " ", "_", -1)
}
func glob(pattern string) []string {
// There is no Glob function in path package, so we have to use filepath and replace in case of Windows
files, _ := filepath.Glob(pattern)
for i := range files {
if strings.Contains(files[i], "\\") {
files[i] = strings.Replace(files[i], "\\", "/", -1)
}
}
return files
}
func concat(files []string) string {
var b strings.Builder
for _, file := range files {
b.Write(readFile(file))
}
return b.String()
}
func generateJSBundle(bundleFile string, bundleFiles map[string][]string, prefixes, suffixes map[string]string) {
bundle := NewBundle("static", "Javascripts", "ui/static")
m := minify.New()
m.AddFunc("text/javascript", js.Minify)
for name, srcFiles := range bundleFiles {
var b strings.Builder
if prefix, found := prefixes[name]; found {
b.WriteString(prefix)
}
b.WriteString(concat(srcFiles))
if suffix, found := suffixes[name]; found {
b.WriteString(suffix)
}
minifiedData, err := m.String("text/javascript", b.String())
if err != nil {
panic(err)
}
bundle.Files[name] = minifiedData
bundle.Checksums[name] = checksum([]byte(minifiedData))
}
bundle.Write(bundleFile)
}
func generateCSSBundle(bundleFile string, themes map[string][]string) {
bundle := NewBundle("static", "Stylesheets", "ui/static")
m := minify.New()
m.AddFunc("text/css", css.Minify)
for theme, srcFiles := range themes {
data := concat(srcFiles)
minifiedData, err := m.String("text/css", data)
if err != nil {
panic(err)
}
bundle.Files[theme] = minifiedData
bundle.Checksums[theme] = checksum([]byte(minifiedData))
}
bundle.Write(bundleFile)
}
func generateBinaryBundle(bundleFile string, srcFiles []string) {
bundle := NewBundle("static", "Binaries", "ui/static")
for _, srcFile := range srcFiles {
data := readFile(srcFile)
filename := basename(srcFile)
encodedData := base64.StdEncoding.EncodeToString(data)
bundle.Files[filename] = string(encodedData)
bundle.Checksums[filename] = checksum(data)
}
bundle.Write(bundleFile)
}
func generateBundle(bundleFile, pkg, mapName string, srcFiles []string) {
bundle := NewBundle(pkg, mapName, pkg)
for _, srcFile := range srcFiles {
data := readFile(srcFile)
filename := stripExtension(basename(srcFile))
bundle.Files[filename] = string(data)
bundle.Checksums[filename] = checksum(data)
}
bundle.Write(bundleFile)
}
func main() {
generateJSBundle("ui/static/js.go", map[string][]string{
"app": []string{
"ui/static/js/dom_helper.js",
"ui/static/js/touch_handler.js",
"ui/static/js/keyboard_handler.js",
"ui/static/js/mouse_handler.js",
"ui/static/js/form_handler.js",
"ui/static/js/request_builder.js",
"ui/static/js/unread_counter_handler.js",
"ui/static/js/entry_handler.js",
"ui/static/js/feed_handler.js",
"ui/static/js/confirm_handler.js",
"ui/static/js/menu_handler.js",
"ui/static/js/modal_handler.js",
"ui/static/js/nav_handler.js",
"ui/static/js/link_state_handler.js",
"ui/static/js/bootstrap.js",
},
"sw": []string{
"ui/static/js/sw.js",
},
}, map[string]string{
"app": "(function(){'use strict';",
"sw": "'use strict';",
}, map[string]string{
"app": "})();",
})
generateCSSBundle("ui/static/css.go", map[string][]string{
"default": []string{"ui/static/css/common.css"},
"black": []string{"ui/static/css/common.css", "ui/static/css/black.css"},
"sansserif": []string{"ui/static/css/common.css", "ui/static/css/sansserif.css"},
})
generateBinaryBundle("ui/static/bin.go", glob("ui/static/bin/*"))
generateBundle("database/sql.go", "database", "SqlMap", glob("database/sql/*.sql"))
generateBundle("template/views.go", "template", "templateViewsMap", glob("template/html/*.html"))
generateBundle("template/common.go", "template", "templateCommonMap", glob("template/html/common/*.html"))
generateBundle("locale/translations.go", "locale", "translations", glob("locale/translations/*.json"))
}