make writefreely translatable for any language

This commit is contained in:
aitzol76 2022-11-23 20:17:34 +01:00
parent 29c898867a
commit 8a74dba40a
59 changed files with 5799 additions and 567 deletions

View File

@ -21,6 +21,7 @@ RUN mkdir /stage && \
/go/src/github.com/writefreely/writefreely/pages \
/go/src/github.com/writefreely/writefreely/keys \
/go/src/github.com/writefreely/writefreely/cmd \
./locales \
/stage
# Final image

139
app.go
View File

@ -25,6 +25,7 @@ import (
"strings"
"syscall"
"time"
"encoding/json"
"github.com/gorilla/mux"
"github.com/gorilla/schema"
@ -41,6 +42,7 @@ import (
"github.com/writefreely/writefreely/migrations"
"github.com/writefreely/writefreely/page"
"golang.org/x/crypto/acme/autocert"
"github.com/leonelquinteros/gotext"
)
const (
@ -73,6 +75,8 @@ type App struct {
sessionStore sessions.Store
formDecoder *schema.Decoder
updates *updatesCache
locales string
tr func (str string, ParamsToTranslate ...interface{}) interface{}
timeline *localTimeline
}
@ -130,11 +134,15 @@ type Apper interface {
LoadKeys() error
LoadLocales() error
ReqLog(r *http.Request, status int, timeSince time.Duration) string
}
// App returns the App
func (app *App) App() *App {
//softwareVer = "0.13.1"
//softwareVer = os.Getenv("VERSION")
return app
}
@ -156,6 +164,126 @@ func (app *App) SaveConfig(c *config.Config) error {
return config.Save(c, app.cfgFile)
}
// LoadLocales reads "json" locales file created from "po" locales.
func (app *App) LoadLocales() error {
var err error
log.Info("Reading %s locales...", app.cfg.App.Lang)
//var setLang = localize(app.cfg.App.Lang)
// ###############################################################################3
type translator interface {}
app.tr = func(str string, ParamsToTranslate ...interface{}) interface{} {
//var str string
n := 1
md := false
var res translator
var output []interface{}
var iString []interface{}
setLang := gotext.NewLocale("./locales", app.cfg.App.Lang);
setLang.AddDomain("base");
for _, item := range ParamsToTranslate {
switch item.(type) {
case int: // n > 1 for plural
n = item.(int)
case bool: // true for apply markdown
md = item.(bool)
case []interface{}: // variables passed for combined translations
var s string
var arr []string
plural := false // true if passed variable needs to be pluralized
for _, vars := range item.([]interface{}) {
switch vars.(type) {
case bool: // true if passed variable needs to be pluralized
plural = vars.(bool)
case int:
iString = append(iString, vars.(int))
case int64:
iString = append(iString, int(vars.(int64)))
case string:
s = vars.(string)
if(strings.Contains(s, ";")){ // links inside translation
var link [] string
for j:= 0; j<=strings.Count(s,";"); j++ {
link = append(link, strings.Split(s, ";")[j])
}
if(plural == true){
link[0] = setLang.GetN(link[0], link[0], 2)
}else{
link[0] = setLang.Get(link[0])
}
iString = append(iString, "[" + link[0] + "](" + link[1] + ")")
}else{ // simple string
if(plural == true){
fmt.Println("PLURAL")
if(len(iString) == 0){
iString = append(iString, setLang.GetN(s, s, 2))
}else{
iString = append(iString, setLang.GetN(s, s, 2))
}
}else{
if(len(iString) == 0){
iString = append(iString, setLang.Get(s))
}else{
iString = append(iString, setLang.Get(s))
}
}
}
case []string: // not used, templates don't support [] string type as function arguments
arr = vars.([]string)
iString = append(iString, "[" + arr[0] + "](" + arr[1] + ")")
}
output = iString
}
default:
fmt.Println("invalid parameters")
}
}
if(output != nil){ // if output for combined translations is not null
if(md == true){
res = template.HTML(applyBasicMarkdown([]byte(setLang.Get(str, output...))))
}else{
res = setLang.Get(str, output...)
}
return res
}
if(md == true){
res = template.HTML(applyBasicMarkdown([]byte(setLang.Get(str))))
}else if(n > 1){
res = setLang.GetN(str, str, n)
}else{
res = setLang.Get(str)
}
return res
}
//inputFile:= "eu_ES.json"
inputFile := "./static/js/"+app.cfg.App.Lang+".json"
file, err := ioutil.ReadFile(inputFile)
if err != nil {
log.Error(err.Error())
os.Exit(1)
}
var mfile map[string]interface{}
err = json.Unmarshal(file, &mfile)
var res []byte
res, err = json.Marshal(mfile[app.cfg.App.Lang])
if err != nil {
log.Error(err.Error())
os.Exit(1)
}
app.locales = string(res)
return nil
}
// LoadKeys reads all needed keys from disk into the App. In order to use the
// configured `Server.KeysParentDir`, you must call initKeyPaths(App) before
// this.
@ -308,8 +436,10 @@ func handleTemplatedPage(app *App, w http.ResponseWriter, r *http.Request, t *te
page.StaticPage
ContentTitle string
Content template.HTML
ExtraContent template.HTML
PlainContent string
Updated string
//BlogStats template.HTML
AboutStats *InstanceStats
}{
@ -335,6 +465,8 @@ func handleTemplatedPage(app *App, w http.ResponseWriter, r *http.Request, t *te
}
p.ContentTitle = c.Title.String
p.Content = template.HTML(applyMarkdown([]byte(c.Content), "", app.cfg))
//p.ExtraContent = template.HTML(applyMarkdown([]byte(c.ExtraContent), "", app.cfg))
//p.BlogStats = template.HTML(applyMarkdown([]byte(c.BlogStats), "", app.cfg))
p.PlainContent = shortPostDescription(stripmd.Strip(c.Content))
if !c.Updated.IsZero() {
p.Updated = c.Updated.Format("January 2, 2006")
@ -354,6 +486,7 @@ func pageForReq(app *App, r *http.Request) page.StaticPage {
AppCfg: app.cfg.App,
Path: r.URL.Path,
Version: "v" + softwareVer,
Tr: app.tr,
}
// Use custom style, if file exists
@ -383,6 +516,8 @@ func pageForReq(app *App, r *http.Request) page.StaticPage {
}
p.CanViewReader = !app.cfg.App.Private || u != nil
p.Locales = app.locales
return p
}
@ -395,6 +530,10 @@ func Initialize(apper Apper, debug bool) (*App, error) {
apper.LoadConfig()
// Generate JSON format locales
apper.App().GenJsonFiles()
apper.LoadLocales()
// Load templates
err := InitTemplates(apper.App().Config())
if err != nil {

View File

@ -124,6 +124,7 @@ type (
SiteName string `ini:"site_name"`
SiteDesc string `ini:"site_description"`
Host string `ini:"host"`
Lang string `ini:"language"`
// Site appearance
Theme string `ini:"theme"`

View File

@ -355,6 +355,7 @@ body {
a {
display: inline-block;
margin-top: 0.8em;
text-transform: lowercase;
.transition-duration(0.1s);
text-decoration: none;
+ a {

44
locales.go Normal file
View File

@ -0,0 +1,44 @@
package writefreely
import (
//"fmt"
"io/ioutil"
"os"
//"encoding/json"
"github.com/writeas/web-core/log"
"git.lainoa.eus/aitzol/po2json"
)
func (app *App) GenJsonFiles(){
lang := app.cfg.App.Lang
log.Info("Generating json %s locales...", lang)
//fmt.Println(lang)
locales := []string {}
domain := "base"
localedir := "./locales"
files,_ := ioutil.ReadDir(localedir)
for _, f := range files {
if f.IsDir() {
//fmt.Println(f.Name())
locales = append(locales, f.Name(),)
if (lang == f.Name()){ //only creates json file for locale set in config.ini
//file, _ := json.MarshalIndent(po2json.PO2JSON([]string{f.Name(),}, domain, localedir), "", " ")
file := po2json.PO2JSON([]string{f.Name(),}, domain, localedir)
//err := os.WriteFile(f.Name()+".json",[]byte(file), 0666)
err := ioutil.WriteFile("static/js/"+f.Name()+".json",[]byte(file), 0666)
if err != nil {
log.Error(err.Error())
os.Exit(1)
}
}
}
}
//fmt.Println(po2json.PO2JSON(locales, domain, localedir))
}

1581
locales/base.pot Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -31,6 +31,9 @@ type StaticPage struct {
CanViewReader bool
IsAdmin bool
CanInvite bool
Locales string
Tr func (str string, ParamsToTranslate ...interface{}) interface{}
}
// SanitizeHost alters the StaticPage to contain a real hostname. This is

View File

@ -1,7 +1,7 @@
{{define "head"}}<title>Page not found &mdash; {{.SiteName}}</title>{{end}}
{{define "head"}}<title>{{call .Tr "Page not found"}} &mdash; {{.SiteName}}</title>{{end}}
{{define "content"}}
<div class="error-page">
<p class="msg">This page is missing.</p>
<p>Are you sure it was ever here?</p>
<p class="msg">{{call .Tr "This page is missing."}}</p>
<p>{{call .Tr "Are you sure it was ever here?"}}</p>
</div>
{{end}}

View File

@ -1,10 +1,11 @@
{{define "head"}}<title>Post not found &mdash; {{.SiteName}}</title>{{end}}
{{define "head"}}<title>{{call .Tr "Post not found"}} &mdash; {{.SiteName}}</title>{{end}}
{{define "content"}}
<div class="error-page" style="max-width:30em">
<p class="msg">Post not found.</p>
<p class="msg">{{call .Tr "Post not found"}}.</p>
{{if and (not .SingleUser) .OpenRegistration}}
<p class="commentary" style="margin-top:2.5em">Why not share a thought of your own?</p>
<p><a href="/">Start a blog</a> and spread your ideas on <strong>{{.SiteName}}</strong>, a simple{{if .Federation}}, federated{{end}} blogging community.</p>
<p class="commentary" style="margin-top:2.5em">{{call .Tr "Why not share a thought of your own?"}}</p>
{{$str := print "a simple blogging community"}}{{if .Federation}}{{$str = print "a simple, federated blogging community"}}{{end}}
<p>{{call .Tr "%s and spread your ideas on **%s**, %s." true (variables "Start a blog;/" .SiteName $str)}}</p>
{{end}}
</div>
{{end}}

View File

@ -1,7 +1,7 @@
{{define "head"}}<title>Unpublished &mdash; {{.SiteName}}</title>{{end}}
{{define "head"}}<title>{{call .Tr "Unpublished"}} &mdash; {{.SiteName}}</title>{{end}}
{{define "content"}}
<div class="error-page">
<p class="msg">{{if .Content}}{{.Content}}{{else}}Post was unpublished by the author.{{end}}</p>
<p class="commentary">It might be back some day.</p>
<p class="msg">{{if .Content}}{{.Content}}{{else}}{{call .Tr "Post was unpublished by the author."}}{{end}}</p>
<p class="commentary">{{call .Tr "It might be back some day."}}</p>
</div>
{{end}}

View File

@ -63,6 +63,9 @@ form dd {
.or {
margin-bottom: 2.5em !important;
}
label{
text-transform: capitalize;
}
</style>
{{end}}
{{define "content"}}
@ -71,7 +74,7 @@ form dd {
<div class="row">
<div class="banner-container">
{{.Banner}}
<p><a href="{{if .Content}}#more{{else}}/about{{end}}">Learn more...</a></p>
<p><a href="{{if .Content}}#more{{else}}/about{{end}}">{{call .Tr "Learn more..."}}</a></p>
</div>
<div{{if not .OpenRegistration}} style="padding: 2em 0;"{{end}}>
@ -86,30 +89,30 @@ form dd {
<form action="/auth/signup" method="POST" id="signup-form" onsubmit="return signup()">
<dl class="billing">
<label>
<dt>Username</dt>
<dt>{{call .Tr "Username"}}</dt>
<dd>
<input type="text" id="alias" name="alias" style="width: 100%; box-sizing: border-box;" tabindex="1" autofocus {{if .ForcedLanding}}disabled{{end}} />
{{if .Federation}}<p id="alias-site" class="demo">@<strong>your-username</strong>@{{.FriendlyHost}}</p>{{else}}<p id="alias-site" class="demo">{{.FriendlyHost}}/<strong>your-username</strong></p>{{end}}
{{if .Federation}}<p id="alias-site" class="demo">@<strong>{{call .Tr "your-username"}}</strong>@{{.FriendlyHost}}</p>{{else}}<p id="alias-site" class="demo">{{.FriendlyHost}}/<strong>{{call .Tr "your-username"}}</strong></p>{{end}}
</dd>
</label>
<label>
<dt>Password</dt>
<dt>{{call .Tr "password"}}</dt>
<dd><input type="password" id="password" name="pass" autocomplete="new-password" placeholder="" tabindex="2" style="width: 100%; box-sizing: border-box;" {{if .ForcedLanding}}disabled{{end}} /></dd>
</label>
<label>
<dt>Email (optional)</dt>
<dt>{{call .Tr "Email"}} ({{call .Tr "optional"}})</dt>
<dd><input type="email" name="email" id="email" style="letter-spacing: 1px; width: 100%; box-sizing: border-box;" placeholder="me@example.com" tabindex="3" {{if .ForcedLanding}}disabled{{end}} /></dd>
</label>
<dt>
<button id="btn-create" type="submit" style="margin-top: 0" {{if .ForcedLanding}}disabled{{end}}>Create blog</button>
<button id="btn-create" type="submit" style="margin-top: 0" {{if .ForcedLanding}}disabled{{end}}>{{call .Tr "Create blog"}}</button>
</dt>
</dl>
</form>
</div>
{{end}}
{{ else }}
<p style="font-size: 1.3em; margin: 1rem 0;">Registration is currently closed.</p>
<p>You can always sign up on <a href="https://writefreely.org/instances">another instance</a>.</p>
<p style="font-size: 1.3em; margin: 1rem 0;">{{call .Tr "Registration is currently closed."}}</p>
<p>{{call .Tr "You can always sign up on %s." true (variables "another instance;https://writefreely.org/instances")}}</p>
{{ end }}
</div>
</div>
@ -191,7 +194,7 @@ var doneTyping = function() {
http.send(JSON.stringify(params));
} else {
$aliasSite.className += ' demo';
$aliasSite.innerHTML = '{{ if .Federation }}@<strong>your-username</strong>@{{.FriendlyHost}}{{ else }}{{.FriendlyHost}}/<strong>your-username</strong>/{{ end }}';
$aliasSite.innerHTML = '{{ if .Federation }}@<strong>{{call .Tr "your-username"}}</strong>@{{.FriendlyHost}}{{ else }}{{.FriendlyHost}}/<strong>{{call .Tr "your-username"}}</strong>/{{ end }}';
}
};
$alias.on('keyup input', function() {

View File

@ -3,11 +3,12 @@
<meta itemprop="description" content="Log in to {{.SiteName}}.">
<style>
input{margin-bottom:0.5em;}
::placeholder {text-transform:capitalize;}
</style>
{{end}}
{{define "content"}}
<div class="tight content-container">
<h1>Log in to {{.SiteName}}</h1>
<h1>{{call .Tr "Log in to %s" (variables .SiteName)}}</h1>
{{if .Flashes}}<ul class="errors">
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}
@ -17,18 +18,18 @@ input{margin-bottom:0.5em;}
{{if not .DisablePasswordAuth}}
<form action="/auth/login" method="post" style="text-align: center;margin-top:1em;" onsubmit="disableSubmit()">
<input type="text" name="alias" placeholder="Username" value="{{.LoginUsername}}" {{if not .LoginUsername}}autofocus{{end}} /><br />
<input type="password" name="pass" placeholder="Password" {{if .LoginUsername}}autofocus{{end}} /><br />
<input type="text" name="alias" placeholder={{call .Tr "Username"}} value="{{.LoginUsername}}" {{if not .LoginUsername}}autofocus{{end}} /><br />
<input type="password" name="pass" placeholder={{call .Tr "password"}} {{if .LoginUsername}}autofocus{{end}} /><br />
{{if .To}}<input type="hidden" name="to" value="{{.To}}" />{{end}}
<input type="submit" id="btn-login" value="Login" />
<input type="submit" id="btn-login" value={{call .Tr "Log in"}} />
</form>
{{if and (not .SingleUser) .OpenRegistration}}<p style="text-align:center;font-size:0.9em;margin:3em auto;max-width:26em;">{{if .Message}}{{.Message}}{{else}}<em>No account yet?</em> <a href="{{.SignupPath}}">Sign up</a> to start a blog.{{end}}</p>{{end}}
{{if and (not .SingleUser) .OpenRegistration}}<p style="text-align:center;font-size:0.9em;margin:3em auto;max-width:26em;">{{if .Message}}{{.Message}}{{else}}{{call .Tr "_No account yet?_ %s to start a blog." true (variables "Sign up; .SignupPath")}}{{end}}</p>{{end}}
<script type="text/javascript">
function disableSubmit() {
var $btn = document.getElementById("btn-login");
$btn.value = "Logging in...";
$btn.value = {{call .Tr "Logging in..."}};
$btn.disabled = true;
}
</script>

View File

@ -3,8 +3,9 @@
{{end}}
{{define "content"}}<div class="content-container snug">
<h1>{{.ContentTitle}}</h1>
<p style="font-style:italic">Last updated {{.Updated}}</p>
<p style="font-style:italic">{{call .Tr "Last updated"}}, <time datetime="{{.Updated}}" content="{{.Updated}}"></time></p>
{{.Content}}
</div>
<script src="/js/localdate.js"></script>
{{end}}

View File

@ -56,7 +56,7 @@ form dd {
{{end}}
{{define "content"}}
<div id="pricing" class="tight content-container">
<h1>Finish creating account</h1>
<h1>{{call .Tr "Finish creating account"}}</h1>
{{if .Flashes}}<ul class="errors">
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}
@ -76,22 +76,22 @@ form dd {
<dl class="billing">
<label>
<dt>Display Name</dt>
<dt>{{call .Tr "Display Name"}}</dt>
<dd>
<input type="text" style="width: 100%; box-sizing: border-box;" name="alias" placeholder="Name"{{ if .Alias }} value="{{.Alias}}"{{ end }} />
</dd>
</label>
<label>
<dt>Username</dt>
<dt>{{call .Tr "Username"}}</dt>
<dd>
<input type="text" id="username" name="username" style="width: 100%; box-sizing: border-box;" placeholder="Username" value="{{.LoginUsername}}" /><br />
{{if .Federation}}<p id="alias-site" class="demo">@<strong>your-username</strong>@{{.FriendlyHost}}</p>{{else}}<p id="alias-site" class="demo">{{.FriendlyHost}}/<strong>your-username</strong></p>{{end}}
<input type="text" id="username" name="username" style="width: 100%; box-sizing: border-box;" placeholder={{call .Tr "Username"}} value="{{.LoginUsername}}" /><br />
{{if .Federation}}<p id="alias-site" class="demo">@<strong>{{call .Tr "your-username"}}</strong>@{{.FriendlyHost}}</p>{{else}}<p id="alias-site" class="demo">{{.FriendlyHost}}/<strong>{{call .Tr "your-username"}}</strong></p>{{end}}
</dd>
</label>
<label>
<dt>Email</dt>
<dt>{{call .Tr "Email"}}</dt>
<dd>
<input type="text" name="email" style="width: 100%; box-sizing: border-box;" placeholder="Email"{{ if .Email }} value="{{.Email}}"{{ end }} />
<input type="text" name="email" style="width: 100%; box-sizing: border-box;" placeholder={{call .Tr "Email"}}{{ if .Email }} value="{{.Email}}"{{ end }} />
</dd>
</label>
<dt>

View File

@ -77,22 +77,22 @@ form dd {
<input type="hidden" name="invite_code" value="{{.Invite}}" />
<dl class="billing">
<label>
<dt>Username</dt>
<dt>{{call .Tr "Username"}}</dt>
<dd>
<input type="text" id="alias" name="alias" style="width: 100%; box-sizing: border-box;" tabindex="1" autofocus />
{{if .Federation}}<p id="alias-site" class="demo">@<strong>your-username</strong>@{{.FriendlyHost}}</p>{{else}}<p id="alias-site" class="demo">{{.FriendlyHost}}/<strong>your-username</strong></p>{{end}}
</dd>
</label>
<label>
<dt>Password</dt>
<dt style="text-transform:capitalize;">{{call .Tr "Password"}}</dt>
<dd><input type="password" id="password" name="pass" autocomplete="new-password" placeholder="" tabindex="2" style="width: 100%; box-sizing: border-box;" /></dd>
</label>
<label>
<dt>Email (optional)</dt>
<dt>{{call .Tr "Email"}} ({{call .Tr "optional"}})</dt>
<dd><input type="email" name="email" id="email" style="letter-spacing: 1px; width: 100%; box-sizing: border-box;" placeholder="me@example.com" tabindex="3" /></dd>
</label>
<dt>
<button id="btn-create" type="submit" style="margin-top: 0">Create blog</button>
<button id="btn-create" type="submit" style="margin-top: 0">{{call .Tr "Create blog"}}</button>
</dt>
</dl>
</form>

View File

@ -1,6 +1,46 @@
function toLocalDate(dateEl, displayEl) {
//localdate.js
//Modified for correctly formating the dates in Basque language(while some errors are fixed on "unicode-org/cldr"
// https://github.com/unicode-org/cldr/blob/main/common/main/eu.xml): 2022/08/16
function toLocalDate(dateEl, displayEl, longFormat) {
var d = new Date(dateEl.getAttribute("datetime"));
displayEl.textContent = d.toLocaleDateString(navigator.language || "en-US", { year: 'numeric', month: 'long', day: 'numeric' });
//displayEl.textContent = d.toLocaleDateString(navigator.language || "en-US", { year: 'numeric', month: 'long', day: 'numeric' });
if(longFormat){
var dateString = d.toLocaleDateString(navigator.language || "en-US", { year: 'numeric', month: 'long', day: 'numeric' , hour: 'numeric', minute: 'numeric'});
}else{
var dateString = d.toLocaleDateString(navigator.language || "en-US", { year: 'numeric', month: 'long', day: 'numeric' });
}
function euFormat(data){
var hk = [
"urtarrila", "otsaila", "martxoa",
"apirila", "maiatza", "ekaina", "uztaila",
"abuztua", "iraila", "urria",
"azaroa", "abendua"
];
var e = data.getDate();
var h = data.getMonth();
var u = data.getFullYear();
var or = data.getHours();
var min = data.getMinutes();
var a = (or >= 12) ? "PM" : "AM";
var lot = ((((Number(u[2])%2 == 0 && u[3] =='1') || (Number(u[2])%2 == 1 && u[3]=='0')) || u[3] == '5')?'e':'')+'ko'
if(longFormat){
return u + lot + ' ' + hk[h] + 'k ' + e + ', ' + or + ':' + min + ' ' + a;
}else{
return u + lot + ' ' + hk[h] + 'k ' + e;
}
}
//if lang eu or eu-ES ...
displayEl.textContent = navigator.language.indexOf('eu') != -1 ? euFormat(d) : dateString
}
// Adjust dates on individual post pages, and on posts in a list *with* an explicit title
@ -12,5 +52,26 @@ for (var i=0; i < $dates.length; i++) {
// Adjust dates on posts in a list without an explicit title, where they act as the header
$dates = document.querySelectorAll("h2.post-title > time");
for (i=0; i < $dates.length; i++) {
toLocalDate($dates[i], $dates[i].querySelector('a'));
}
toLocalDate($dates[i], $dates[i].querySelector('a'), false);
}
// Adjust dates on drafts 2022/08/16
$dates = document.querySelectorAll("h4 > date");
for (var i=0; i < $dates.length; i++) {
$dates[i].setAttribute("datetime", $dates[i].getAttribute("datetime").split(" +")[0])
toLocalDate($dates[i], $dates[i], false);
}
// Adjust date on privacy page 2022/08/17
$dates = document.querySelectorAll("p > time");
for (var i=0; i < $dates.length; i++) {
toLocalDate($dates[i], $dates[i], false);
}
// Adjust date to long format on admin/user-s pages 2022/11/21
if(location.pathname.startsWith("/admin/user")){
$dates = document.querySelectorAll("td > time");
for (var i=0; i < $dates.length; i++) {
toLocalDate($dates[i], $dates[i], true);
}
}

View File

@ -1,10 +1,17 @@
var postActions = function() {
var $container = He.get('moving');
var MultiMove = function(el, id, singleUser) {
var tr = function(term, str){
return term.replace("%s", str);
};
var MultiMove = function(el, id, singleUser, loc) {
var lbl = el.options[el.selectedIndex].textContent;
var loc = JSON.parse(loc);
var collAlias = el.options[el.selectedIndex].value;
var $lbl = He.$('label[for=move-'+id+']')[0];
$lbl.textContent = "moving to "+lbl+"...";
//$lbl.textContent = loc['moving to']+" "+lbl+"...";
$lbl.textContent = tr(loc['moving to %s...'],lbl);
var params;
if (collAlias == '|anonymous|') {
params = [id];
@ -17,7 +24,8 @@ var postActions = function() {
if (code == 200) {
for (var i=0; i<resp.data.length; i++) {
if (resp.data[i].code == 200) {
$lbl.innerHTML = "moved to <strong>"+lbl+"</strong>";
//$lbl.innerHTML = loc["Moved to"]+" <strong>"+lbl+"</strong>";
$lbl.innerHTML = tr(loc["Moved to %s"], " <strong>"+lbl+"</strong>");
var pre = "/"+collAlias;
if (typeof singleUser !== 'undefined' && singleUser) {
pre = "";
@ -35,13 +43,14 @@ var postActions = function() {
if (typeof singleUser !== 'undefined' && singleUser) {
draftPre = "d/";
}
$article.innerHTML = '<p><a href="/'+draftPre+resp.data[i].post.id+'">Unpublished post</a>.</p>';
$article.innerHTML = '<p><a href="/'+draftPre+resp.data[i].post.id+'">'+loc["Unpublished post"]+'</a>.</p>';
} else {
$article.innerHTML = '<p>Moved to <a style="font-weight:bold" href="'+newPostURL+'">'+lbl+'</a>.</p>';
//$article.innerHTML = '<p>'+loc["Moved to"]+' <a style="font-weight:bold" href="'+newPostURL+'">'+lbl+'</a>.</p>';
$article.innerHTML = '<p>'+ tr(loc["Moved to %s"], '<a style="font-weight:bold" href="'+newPostURL+'">'+lbl+'</a>')+'.</p>';
}
}
} else {
$lbl.innerHTML = "unable to move: "+resp.data[i].error_msg;
$lbl.innerHTML = loc['unable to move']+": "+resp.data[i].error_msg;
}
}
}
@ -52,18 +61,28 @@ var postActions = function() {
He.postJSON("/api/collections/"+collAlias+"/collect", params, callback);
}
};
var Move = function(el, id, collAlias, singleUser) {
var Move = function(el, id, collAlias, singleUser, loc) {
var lbl = el.textContent;
var loc = JSON.parse(loc)
/*
try {
var m = lbl.match(/move to (.*)/);
//var m = lbl.match(/move to (.*)/);
var m = lbl.match(RegExp(loc['move to'] + "(.*)"));
lbl = m[1];
} catch (e) {
if (collAlias == '|anonymous|') {
lbl = "draft";
}
}
*/
if (collAlias == '|anonymous|'){
lbl = loc["draft"];
}else{
lbl = collAlias
}
el.textContent = "moving to "+lbl+"...";
el.textContent = tr(loc['moving to %s...'],lbl);
if (collAlias == '|anonymous|') {
params = [id];
} else {
@ -75,7 +94,7 @@ var postActions = function() {
if (code == 200) {
for (var i=0; i<resp.data.length; i++) {
if (resp.data[i].code == 200) {
el.innerHTML = "moved to <strong>"+lbl+"</strong>";
el.innerHTML = tr(loc["Moved to %s"], " <strong>"+lbl+"</strong>");
el.onclick = null;
var pre = "/"+collAlias;
if (typeof singleUser !== 'undefined' && singleUser) {
@ -83,7 +102,7 @@ var postActions = function() {
}
var newPostURL = pre+"/"+resp.data[i].post.slug;
el.href = newPostURL;
el.title = "View on "+lbl;
el.title = tr(loc["View on %s"], lbl)
try {
// Posts page
He.$('#post-'+resp.data[i].post.id+' > h3 > a')[0].href = newPostURL;
@ -96,13 +115,13 @@ var postActions = function() {
if (typeof singleUser !== 'undefined' && singleUser) {
draftPre = "d/";
}
$article.innerHTML = '<p><a href="/'+draftPre+resp.data[i].post.id+'">Unpublished post</a>.</p>';
$article.innerHTML = '<p><a href="/'+draftPre+resp.data[i].post.id+'">'+loc["Unpublished post"]+'</a>.</p>';
} else {
$article.innerHTML = '<p>Moved to <a style="font-weight:bold" href="'+newPostURL+'">'+lbl+'</a>.</p>';
$article.innerHTML = '<p>'+ tr(loc["Moved to %s"], '<a style="font-weight:bold" href="'+newPostURL+'">'+lbl+'</a>')+'.</p>';
}
}
} else {
el.innerHTML = "unable to move: "+resp.data[i].error_msg;
el.innerHTML = loc['unable to move']+": "+resp.data[i].error_msg;
}
}
}
@ -112,6 +131,7 @@ var postActions = function() {
} else {
He.postJSON("/api/collections/"+collAlias+"/collect", params, callback);
}
};
return {

View File

@ -4,6 +4,7 @@
* Dependencies:
* h.js
*/
function toggleTheme() {
var btns;
try {
@ -34,14 +35,15 @@ if (H.get('padTheme', 'light') != 'light') {
}
var deleting = false;
function delPost(e, id, owned) {
function delPost(e, id, owned, loc) {
e.preventDefault();
if (deleting) {
return;
}
var loc = JSON.parse(loc);
// TODO: UNDO!
if (window.confirm('Are you sure you want to delete this post?')) {
if (window.confirm(loc['Are you sure you want to delete this post?'])) {
var token;
for (var i=0; i<posts.length; i++) {
if (posts[i].id == id) {

View File

@ -24,6 +24,7 @@ import (
"github.com/writeas/web-core/l10n"
"github.com/writeas/web-core/log"
"github.com/writefreely/writefreely/config"
"github.com/leonelquinteros/gotext"
)
var (
@ -42,6 +43,9 @@ var (
"hasPrefix": strings.HasPrefix,
"hasSuffix": strings.HasSuffix,
"dict": dict,
"localize": localize,
"variables": variables,
}
)
@ -194,7 +198,7 @@ func isRTL(d string) bool {
func isLTR(d string) bool {
return d == "ltr" || d == "auto"
}
/*
func localStr(term, lang string) string {
s := l10n.Strings(lang)[term]
if s == "" {
@ -202,6 +206,16 @@ func localStr(term, lang string) string {
}
return s
}
*/
func localStr(term, lang string) string {
switch lang {
case "eu": lang = "eu_ES"
case "es": lang = "es_ES"
default: lang = "en_UK"
}
setLang := localize(lang);
return setLang.Get(term)
}
func localHTML(term, lang string) template.HTML {
s := l10n.Strings(lang)[term]
@ -227,3 +241,18 @@ func dict(values ...interface{}) (map[string]interface{}, error) {
}
return dict, nil
}
func localize(lang string) (*gotext.Locale){
var language string = "en_UK"
if lang != "" {
language = lang
}
setLang := gotext.NewLocale("./locales", language);
setLang.AddDomain("base");
return setLang
}
func variables(Vars ...interface{}) interface{}{
res := Vars
return res
}

View File

@ -5,7 +5,6 @@
<title>{{if .Editing}}Editing {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}}{{else}}New Post{{end}} &mdash; {{.SiteName}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="google" value="notranslate">
@ -18,20 +17,20 @@
{{end}}{{.Post.Content}}</textarea>
<div class="alert success hidden" id="edited-elsewhere">This post has been updated elsewhere since you last published! <a href="#" id="erase-edit">Delete draft and reload</a>.</div>
<div class="alert success hidden" id="edited-elsewhere">{{call .Tr "This post has been updated elsewhere since you last published!"}} <a href="#" id="erase-edit">{{call .Tr "Delete draft and reload"}}</a>.</div>
<header id="tools">
<div id="clip">
{{if not .SingleUser}}<h1>{{if .Chorus}}<a href="/" title="Home">{{else}}<a href="/me/c/" title="View blogs">{{end}}{{.SiteName}}</a></h1>{{end}}
<nav id="target" {{if .SingleUser}}style="margin-left:0"{{end}}><ul>
<li>{{if .Blogs}}<a href="{{$c := index .Blogs 0}}{{$c.CanonicalURL}}">My Posts</a>{{else}}<a>Draft</a>{{end}}</li>
<li>{{if .Blogs}}<a href="{{$c := index .Blogs 0}}{{$c.CanonicalURL}}">{{call .Tr "My Posts"}}</a>{{else}}<a>{{call .Tr "Draft"}}</a>{{end}}</li>
</ul></nav>
<span id="wc" class="hidden if-room room-4">0 words</span>
</div>
<noscript style="margin-left: 2em;"><strong>NOTE</strong>: for now, you'll need Javascript enabled to post.</noscript>
<noscript style="margin-left: 2em;"><strong>{{call .Tr "NOTE"}}</strong>: {{call .Tr "for now, you'll need Javascript enabled to post."}}</noscript>
<div id="belt">
{{if .Editing}}<div class="tool hidden if-room"><a href="{{if .EditCollection}}{{.EditCollection.CanonicalURL}}{{.Post.Slug}}/edit/meta{{else}}/{{if .SingleUser}}d/{{end}}{{.Post.Id}}/meta{{end}}" title="Edit post metadata" id="edit-meta"><img class="ic-24dp" src="/img/ic_info_dark@2x.png" /></a></div>{{end}}
<div class="tool"><button title="Publish your writing" id="publish" style="font-weight: bold">Post</button></div>
<div class="tool"><button title="Publish your writing" id="publish" style="font-weight: bold">{{call .Tr "Post"}}</button></div>
</div>
</header>
@ -97,7 +96,7 @@
var publish = function(content, font) {
{{if and (and .Post.Id (not .Post.Slug)) (not .User)}}
if (!token) {
alert("You don't have permission to update this post.");
alert({{call .Tr "You don't have permission to update this post."}});
return;
}
{{end}}

View File

@ -26,34 +26,34 @@
{{if .Username}}
<nav class="dropdown-nav">
<ul><li class="has-submenu"><a>{{.Username}}</a> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /><ul>
{{if .IsAdmin}}<li><a href="/admin">Admin dashboard</a></li>{{end}}
<li><a href="/me/settings">Account settings</a></li>
<li><a href="/me/export">Export</a></li>
{{if .CanInvite}}<li><a href="/me/invites">Invite people</a></li>{{end}}
{{if .IsAdmin}}<li><a href="/admin">{{call .Tr "Admin dashboard"}}</a></li>{{end}}
<li><a href="/me/settings">{{call .Tr "Account settings"}}</a></li>
<li><a href="/me/export">{{call .Tr "Export"}}</a></li>
{{if .CanInvite}}<li><a href="/me/invites">{{call .Tr "Invite people"}}</a></li>{{end}}
<li class="separator"><hr /></li>
<li><a href="/me/logout">Log out</a></li>
<li><a href="/me/logout">{{call .Tr "Log out"}}</a></li>
</ul></li>
</ul>
</nav>
{{end}}
<nav class="tabs">
{{ if and .SimpleNav (not .SingleUser) }}
{{if and (and .LocalTimeline .CanViewReader) .Chorus}}<a href="/"{{if eq .Path "/"}} class="selected"{{end}}>Home</a>{{end}}
{{if and (and .LocalTimeline .CanViewReader) .Chorus}}<a href="/"{{if eq .Path "/"}} class="selected"{{end}}>{{call .Tr "Home"}}</a>{{end}}
{{ end }}
{{if or .Chorus (not .Username)}}<a href="/about"{{if eq .Path "/about"}} class="selected"{{end}}>About</a>{{end}}
{{if or .Chorus (not .Username)}}<a href="/about"{{if eq .Path "/about"}} class="selected"{{end}}>{{call .Tr "About"}}</a>{{end}}
{{ if not .SingleUser }}
{{ if .Username }}
{{if or (not .Chorus) (gt .MaxBlogs 1)}}<a href="/me/c/"{{if eq .Path "/me/c/"}} class="selected"{{end}}>Blogs</a>{{end}}
{{if or (not .Chorus) (gt .MaxBlogs 1)}}<a href="/me/c/"{{if eq .Path "/me/c/"}} class="selected"{{end}}>{{call .Tr "Blog" 2}}</a>{{end}}
{{if and (and .Chorus (eq .MaxBlogs 1)) .Username}}<a href="/{{.Username}}/"{{if eq .Path (printf "/%s/" .Username)}} class="selected"{{end}}>My Posts</a>{{end}}
{{if not .DisableDrafts}}<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>Drafts</a>{{end}}
{{if not .DisableDrafts}}<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>{{call .Tr "Draft" 2}}</a>{{end}}
{{ end }}
{{if and (and .LocalTimeline .CanViewReader) (not .Chorus)}}<a href="/read"{{if eq .Path "/read"}} class="selected"{{end}}>Reader</a>{{end}}
{{if eq .SignupPath "/signup"}}<a href="/signup"{{if eq .Path "/signup"}} class="selected"{{end}}>Sign up</a>{{end}}
{{if and (not .Username) (not .Private)}}<a href="/login"{{if eq .Path "/login"}} class="selected"{{end}}>Log in</a>{{else if .SimpleNav}}<a href="/me/logout">Log out</a>{{end}}
{{if and (and .LocalTimeline .CanViewReader) (not .Chorus)}}<a href="/read"{{if eq .Path "/read"}} class="selected"{{end}}>{{call .Tr "Reader"}}</a>{{end}}
{{if eq .SignupPath "/signup"}}<a href="/signup"{{if eq .Path "/signup"}} class="selected"{{end}}>{{call .Tr "Sign up"}}</a>{{end}}
{{if and (not .Username) (not .Private)}}<a href="/login"{{if eq .Path "/login"}} class="selected"{{end}}>{{call .Tr "Log in"}}</a>{{else if .SimpleNav}}<a href="/me/logout">{{call .Tr "Log out"}}</a>{{end}}
{{ end }}
</nav>
{{if .Chorus}}{{if .Username}}<div class="right-side" style="font-size: 0.86em;">
<a class="simple-btn" href="/new">New Post</a>
<a class="simple-btn" href="/new">{{call .Tr "New Post"}}</a>
</div>{{end}}
</nav>
{{end}}

View File

@ -6,7 +6,6 @@
<title>{{.PlainDisplayTitle}} {{localhtml "title dash" .Language.String}} {{.Collection.DisplayTitle}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<link rel="shortcut icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="canonical" href="{{.CanonicalURL .Host}}" />
@ -58,7 +57,7 @@ body#post header {
{{template "user-navigation" .}}
{{if .Silenced}}
{{template "user-silenced"}}
{{template "user-silenced" (dict "Tr" $.Tr)}}
{{end}}
<article id="post-body" class="{{.Font}} h-entry">{{if .IsScheduled}}<p class="badge">Scheduled</p>{{end}}{{if .Title.String}}<h2 id="title" class="p-name{{if $.Collection.Format.ShowDates}} dated{{end}}">{{.FormattedDisplayTitle}}</h2>{{end}}{{if and $.Collection.Format.ShowDates (not .IsPinned)}}<time class="dt-published" datetime="{{.Created8601}}" pubdate itemprop="datePublished" content="{{.Created}}">{{.DisplayDate}}</time>{{end}}<div class="e-content">{{.HTMLContent}}</div></article>

View File

@ -6,7 +6,6 @@
<title>{{.DisplayTitle}}{{if not .SingleUser}} &mdash; {{.SiteName}}{{end}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="canonical" href="{{.CanonicalURL}}">
{{if gt .CurrentPage 1}}<link rel="prev" href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">{{end}}
@ -64,7 +63,7 @@ body#collection header nav.tabs a:first-child {
{{template "user-navigation" .}}
{{if .Silenced}}
{{template "user-silenced"}}
{{template "user-silenced" (dict "Tr" $.Tr)}}
{{end}}
<header>
<h1 dir="{{.Direction}}" id="blog-title"><a href="/{{if .IsTopLevel}}{{else}}{{.Prefix}}{{.Alias}}/{{end}}" class="h-card p-author u-url" rel="me author">{{.DisplayTitle}}</a></h1>
@ -121,14 +120,16 @@ body#collection header nav.tabs a:first-child {
<script src="/js/postactions.js"></script>
<script type="text/javascript">
var deleting = false;
function delPost(e, id, owned) {
function delPost(e, id, owned, loc) {
e.preventDefault();
if (deleting) {
return;
}
var loc = JSON.parse(loc);
// TODO: UNDO!
if (window.confirm('Are you sure you want to delete this post?')) {
if (window.confirm(loc['Are you sure you want to delete this post?'])) {
// AJAX
deletePost(id, "", function() {
// Remove post from list
@ -169,13 +170,15 @@ var deletePost = function(postID, token, callback) {
};
var pinning = false;
function pinPost(e, postID, slug, title) {
function pinPost(e, postID, slug, title, loc) {
e.preventDefault();
if (pinning) {
return;
}
pinning = true;
var loc = JSON.parse(loc)
var callback = function() {
// Visibly remove post from collection
var $postEl = document.getElementById('post-' + postID);
@ -206,13 +209,14 @@ function pinPost(e, postID, slug, title) {
callback();
} else if (http.status == 409) {
$pinBtn.innerHTML = 'pin';
alert("Post is synced to another account. Delete the post from that account instead.");
alert(loc["Post is synced to another account. Delete the post from that account instead."]);
// TODO: show "remove" button instead of "delete" now
// Persist that state.
// Have it remove the post locally only.
} else {
$pinBtn.innerHTML = 'pin';
alert("Failed to pin." + (http.status>=500?" Please try again.":""));
//alert("Failed to pin." + (http.status>=500?" Please try again.":""));
alert(loc["Failed to pin."] + (http.status>=500?" " + loc["Please try again."]:""));
}
}
}

View File

@ -2,11 +2,10 @@
<html>
<head>
<title>{{if .Editing}}Editing {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}}{{else}}New Post{{end}} &mdash; {{.SiteName}}</title>
<title>{{if .Editing}}Editing {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}}{{else}}{{call .Tr "New Post"}}{{end}} &mdash; {{.SiteName}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" />
<link rel="stylesheet" type="text/css" href="/css/prose.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="google" value="notranslate">
@ -32,37 +31,38 @@
{{if .Editing}}<li>{{if .EditCollection}}<a href="{{.EditCollection.CanonicalURL}}">{{.EditCollection.Title}}</a>{{else}}<a>Draft</a>{{end}}</li>
{{else}}<li><a id="publish-to"><span id="target-name">Draft</span> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /></a>
<ul>
<li class="menu-heading">Publish to...</li>
<li class="menu-heading">{{call .Tr "Publish to..."}}</li>
{{if .Blogs}}{{range $idx, $el := .Blogs}}
<li class="target{{if eq $idx 0}} selected{{end}}" id="blog-{{$el.Alias}}"><a href="#{{$el.Alias}}"><i class="material-icons md-18">public</i> {{if $el.Title}}{{$el.Title}}{{else}}{{$el.Alias}}{{end}}</a></li>
{{end}}{{end}}
<li class="target" id="blog-anonymous"><a href="#anonymous"><i class="material-icons md-18">description</i> <em>Draft</em></a></li>
<li class="target" id="blog-anonymous"><a href="#anonymous"><i class="material-icons md-18">description</i> <em>{{call .Tr "Draft"}}</em></a></li>
<li id="user-separator" class="separator"><hr /></li>
{{ if .SingleUser }}
<li><a href="/"><i class="material-icons md-18">launch</i> View Blog</a></li>
<li><a href="/me/c/{{.Username}}"><i class="material-icons md-18">palette</i> Customize</a></li>
<li><a href="/me/c/{{.Username}}/stats"><i class="material-icons md-18">trending_up</i> Stats</a></li>
<li><a href="/"><i class="material-icons md-18">launch</i> {{call .Tr "View Blog"}}</a></li>
<li><a href="/me/c/{{.Username}}"><i class="material-icons md-18">palette</i> {{call .Tr "Customize"}}</a></li>
<li><a href="/me/c/{{.Username}}/stats"><i class="material-icons md-18">trending_up</i> {{call .Tr "Stats"}}</a></li>
{{ else }}
<li><a href="/me/c/"><i class="material-icons md-18">library_books</i> View Blogs</a></li>
<li><a href="/me/c/"><i class="material-icons md-18">library_books</i> {{call .Tr "View Blog2" 2}}</a></li>
{{ end }}
<li><a href="/me/posts/"><i class="material-icons md-18">view_list</i> View Drafts</a></li>
<li><a href="/me/logout"><i class="material-icons md-18">power_settings_new</i> Log out</a></li>
<li><a href="/me/posts/"><i class="material-icons md-18">view_list</i> {{call .Tr "View Draft" 2}}</a></li>
<li><a href="/me/logout"><i class="material-icons md-18">power_settings_new</i> {{call .Tr "Log out"}}</a></li>
</ul>
</li>{{end}}
</ul></nav>
<nav id="font-picker" class="if-room room-3 hidden" style="margin-left:-1em"><ul>
<li><a href="#" id="" onclick="return false"><img class="ic-24dp" src="/img/ic_font_dark@2x.png" /> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /></a>
<ul style="text-align: center">
<li class="menu-heading">Font</li>
<li class="menu-heading">{{call .Tr "Font"}}</li>
<li class="selected"><a class="font norm" href="#norm">Serif</a></li>
<li><a class="font sans" href="#sans">Sans-serif</a></li>
<li><a class="font wrap" href="#wrap">Monospace</a></li>
</ul>
</li>
</ul></nav>
<span id="wc" class="hidden if-room room-4">0 words</span>
{{ $N := 2 }}{{ if eq .AppCfg.Lang "eu_ES" }}{{ $N = 1}}{{ end }}
<span id="wc" class="hidden if-room room-4">0 {{call .Tr "words" $N}}</span>
</div>
<noscript style="margin-left: 2em;"><strong>NOTE</strong>: for now, you'll need Javascript enabled to post.</noscript>
<noscript style="margin-left: 2em;"><strong>{{call .Tr "NOTE"}}</strong>: {{call .Tr "for now, you'll need Javascript enabled to post."}}</noscript>
<div id="belt">
{{if .Editing}}<div class="tool hidden if-room"><a href="{{if .EditCollection}}{{.EditCollection.CanonicalURL}}{{.Post.Slug}}/edit/meta{{else}}/{{if .SingleUser}}d/{{end}}{{.Post.Id}}/meta{{end}}" title="Edit post metadata" id="edit-meta"><img class="ic-24dp" src="/img/ic_info_dark@2x.png" /></a></div>{{end}}
<div class="tool hidden if-room room-2"><a href="#theme" title="Toggle theme" id="toggle-theme"><img class="ic-24dp" src="/img/ic_brightness_dark@2x.png" /></a></div>
@ -109,7 +109,10 @@
if (val != '') {
words += $title.el.value.trim().replace(/\s+/gi, ' ').split(' ').length;
}
$wc.el.innerText = words + " word" + (words != 1 ? "s" : "");
//$wc.el.innerText = words + " word" + (words != 1 ? "s" : "");
{{ $N := 2 }}{{ if eq .AppCfg.Lang "eu_ES" }}{{ $N = 1}}{{ end }} //don't need to pluralize for the basque language
$wc.el.innerText = words + " " + (words !=1 ? {{call .Tr "word" $N}} : {{call .Tr "word" 1}})
};
var setButtonStates = function() {
if (!canPublish) {
@ -152,12 +155,12 @@
var silenced = {{.Silenced}};
var publish = function(title, content, font) {
if (silenced === true) {
alert("Your account is silenced, so you can't publish or update posts.");
alert({{call .Tr "Your account is silenced, so you can't publish or update posts."}});
return;
}
{{if and (and .Post.Id (not .Post.Slug)) (not .User)}}
if (!token) {
alert("You don't have permission to update this post.");
alert({{call .Tr "You don't have permission to update this post."}});
return;
}
if ($btnPublish.el.className == 'disabled') {
@ -251,7 +254,7 @@
{{end}}
} else {
$btnPublish.el.children[0].textContent = 'send';
alert("Failed to post. Please try again.");
alert({{call .Tr "Failed to post. Please try again."}});
}
}
}

View File

@ -6,7 +6,6 @@
<title>{{.PlainDisplayTitle}} {{localhtml "title dash" .Language.String}} {{.Collection.DisplayTitle}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<link rel="shortcut icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{{ if .IsFound }}
@ -54,17 +53,17 @@
{{if .PinnedPosts}}
{{range .PinnedPosts}}<a class="pinned{{if eq .Slug.String $.Slug.String}} selected{{end}}" href="{{if not $.SingleUser}}/{{$.Collection.Alias}}/{{.Slug.String}}{{else}}{{.CanonicalURL $.Host}}{{end}}">{{.PlainDisplayTitle}}</a>{{end}}
{{end}}
{{ if and .IsOwner .IsFound }}<span class="views" dir="ltr"><strong>{{largeNumFmt .Views}}</strong> {{pluralize "view" "views" .Views}}</span>
<a class="xtra-feature" href="/{{if not .SingleUser}}{{.Collection.Alias}}/{{end}}{{.Slug.String}}/edit" dir="{{.Direction}}">Edit</a>
{{if .IsPinned}}<a class="xtra-feature unpin" href="/{{.Collection.Alias}}/{{.Slug.String}}/unpin" dir="{{.Direction}}" onclick="unpinPost(event, '{{.ID}}')">Unpin</a>{{end}}
{{ if and .IsOwner .IsFound }}{{ $N := .Views }}{{ if eq .AppCfg.Lang "eu_ES" }}{{ $N = 1}}{{ end }}<span class="views" dir="ltr"><strong>{{largeNumFmt .Views}}</strong> {{call .Tr "View" $N}}</span>
<a class="xtra-feature" href="/{{if not .SingleUser}}{{.Collection.Alias}}/{{end}}{{.Slug.String}}/edit" dir="{{.Direction}}">{{call .Tr "Edit"}}</a>
{{if .IsPinned}}<a class="xtra-feature unpin" href="/{{.Collection.Alias}}/{{.Slug.String}}/unpin" dir="{{.Direction}}" onclick="unpinPost(event, '{{.ID}}')">{{call .Tr "Unpin"}}</a>{{end}}
{{ end }}
</nav>
</header>
{{if .Silenced}}
{{template "user-silenced"}}
{{template "user-silenced" (dict "Tr" $.Tr)}}
{{end}}
<article id="post-body" class="{{.Font}} h-entry {{if not .IsFound}}error-page{{end}}">{{if .IsScheduled}}<p class="badge">Scheduled</p>{{end}}{{if .Title.String}}<h2 id="title" class="p-name{{if and $.Collection.Format.ShowDates (not .IsPinned)}} dated{{end}}">{{.FormattedDisplayTitle}}</h2>{{end}}{{if and $.Collection.Format.ShowDates (not .IsPinned) .IsFound}}<time class="dt-published" datetime="{{.Created8601}}" pubdate itemprop="datePublished" content="{{.Created}}">{{.DisplayDate}}</time>{{end}}<div class="e-content">{{.HTMLContent}}</div></article>
<article id="post-body" class="{{.Font}} h-entry {{if not .IsFound}}error-page{{end}}">{{if .IsScheduled}}<p class="badge">{{call .Tr "Scheduled"}}</p>{{end}}{{if .Title.String}}<h2 id="title" class="p-name{{if and $.Collection.Format.ShowDates (not .IsPinned)}} dated{{end}}">{{.FormattedDisplayTitle}}</h2>{{end}}{{if and $.Collection.Format.ShowDates (not .IsPinned) .IsFound}}<time class="dt-published" datetime="{{.Created8601}}" pubdate itemprop="datePublished" content="{{.Created}}">{{.DisplayDate}}</time>{{end}}<div class="e-content">{{.HTMLContent}}</div></article>
{{ if .Collection.ShowFooterBranding }}
<footer dir="ltr"><hr><nav><p style="font-size: 0.9em">{{localhtml "published with write.as" .Language.String}}</p></nav></footer>

View File

@ -6,7 +6,6 @@
<title>{{.Tag}} &mdash; {{.Collection.DisplayTitle}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<link rel="shortcut icon" href="/favicon.ico" />
{{if not .Collection.IsPrivate}}<link rel="alternate" type="application/rss+xml" title="{{.Tag}} posts on {{.DisplayTitle}}" href="{{.CanonicalURL}}tag:{{.Tag}}/feed/" />{{end}}
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@ -56,7 +55,7 @@
</header>
{{if .Silenced}}
{{template "user-silenced"}}
{{template "user-silenced" (dict "Tr" $.Tr)}}
{{end}}
{{if .Posts}}<section id="wrapper" itemscope itemtype="http://schema.org/Blog">{{else}}<div id="wrapper">{{end}}
<h1>{{.Tag}}</h1>
@ -85,16 +84,18 @@
<script type="text/javascript">
{{if .IsOwner}}
var deleting = false;
function delPost(e, id, owned) {
function delPost(e, id, owned, loc) {
e.preventDefault();
if (deleting) {
return;
}
var loc = JSON.parse(loc);
// TODO: UNDO!
if (window.confirm('Are you sure you want to delete this post?')) {
if (window.confirm(loc['Are you sure you want to delete this post?'])) {
// AJAX
deletePost(id, "", function() {
deletePost(id, "", loc, function() {
// Remove post from list
var $postEl = document.getElementById('post-' + id);
$postEl.parentNode.removeChild($postEl);
@ -103,7 +104,7 @@ function delPost(e, id, owned) {
}
}
var deletePost = function(postID, token, callback) {
var deletePost = function(postID, token, loc, callback) {
deleting = true;
var $delBtn = document.getElementById('post-' + postID).getElementsByClassName('delete action')[0];
@ -119,13 +120,13 @@ var deletePost = function(postID, token, callback) {
callback();
} else if (http.status == 409) {
$delBtn.innerHTML = 'delete';
alert("Post is synced to another account. Delete the post from that account instead.");
alert(loc["Post is synced to another account. Delete the post from that account instead."]);
// TODO: show "remove" button instead of "delete" now
// Persist that state.
// Have it remove the post locally only.
} else {
$delBtn.innerHTML = 'delete';
alert("Failed to delete." + (http.status>=500?" Please try again.":""));
alert(loc["Failed to pin."] + (http.status>=500?" " + loc["Please try again."]:""));
}
}
}
@ -133,13 +134,15 @@ var deletePost = function(postID, token, callback) {
};
var pinning = false;
function pinPost(e, postID, slug, title) {
function pinPost(e, postID, slug, title, loc) {
e.preventDefault();
if (pinning) {
return;
}
pinning = true;
var loc = JSON.parse(loc)
var callback = function() {
// Visibly remove post from collection
var $postEl = document.getElementById('post-' + postID);
@ -170,13 +173,13 @@ function pinPost(e, postID, slug, title) {
callback();
} else if (http.status == 409) {
$pinBtn.innerHTML = 'pin';
alert("Post is synced to another account. Delete the post from that account instead.");
alert(loc["Post is synced to another account. Delete the post from that account instead."]);
// TODO: show "remove" button instead of "delete" now
// Persist that state.
// Have it remove the post locally only.
} else {
$pinBtn.innerHTML = 'pin';
alert("Failed to pin." + (http.status>=500?" Please try again.":""));
alert(loc["Failed to pin."] + (http.status>=500?" " + loc["Please try again."]:""));
}
}
}

View File

@ -6,7 +6,6 @@
<title>{{.DisplayTitle}}{{if not .SingleUser}} &mdash; {{.SiteName}}{{end}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="canonical" href="{{.CanonicalURL}}">
{{if gt .CurrentPage 1}}<link rel="prev" href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">{{end}}
@ -51,17 +50,17 @@
{{else}}
<li><a href="/#{{.Alias}}" class="write">{{.SiteName}}</a></li>
{{end}}
{{if .SimpleNav}}<li><a href="/new#{{.Alias}}">New Post</a></li>{{end}}
<li><a href="/me/c/{{.Alias}}">Customize</a></li>
<li><a href="/me/c/{{.Alias}}/stats">Stats</a></li>
{{if .SimpleNav}}<li><a href="/new#{{.Alias}}">{{call .Tr "New Post"}}</a></li>{{end}}
<li><a href="/me/c/{{.Alias}}">{{call .Tr "Customize"}}</a></li>
<li><a href="/me/c/{{.Alias}}/stats">{{call .Tr "Stats"}}</a></li>
<li class="separator"><hr /></li>
{{if not .SingleUser}}<li><a href="/me/c/"><img class="ic-18dp" src="/img/ic_blogs_dark@2x.png" /> View Blogs</a></li>{{end}}
<li><a href="/me/posts/"><img class="ic-18dp" src="/img/ic_list_dark@2x.png" /> View Drafts</a></li>
{{if not .SingleUser}}<li><a href="/me/c/"><img class="ic-18dp" src="/img/ic_blogs_dark@2x.png" /> {{call .Tr "View Blog" 2}}</a></li>{{end}}
<li><a href="/me/posts/"><img class="ic-18dp" src="/img/ic_list_dark@2x.png" /> {{call .Tr "View Draft" 2}}</a></li>
{{ else }}
<li><a href="/login">Log in{{if .IsProtected}} to {{.DisplayTitle}}{{end}}</a></li>
{{if .IsProtected}}
<li class="separator"><hr /></li>
<li><a href="/logout">Log out</a></li>
<li><a href="/logout">{{call .Tr "Log out"}}</a></li>
{{end}}
{{ end }}
</ul>
@ -72,7 +71,7 @@
<li class="has-submenu"><a onclick="void(0)">&#9776; Menu</a>
<ul>
<li class="menu-heading" style="padding: .5rem .75rem; box-sizing: border-box;">{{.DisplayTitle}}</li>
<li><a href="{{.CanonicalURL}}logout">Log out</a></li>
<li><a href="{{.CanonicalURL}}logout">{{call .Tr "Log out"}}</a></li>
</ul>
</li>
</ul></nav>
@ -80,7 +79,7 @@
<header>
{{if .Silenced}}
{{template "user-silenced"}}
{{template "user-silenced" (dict "Tr" $.Tr)}}
{{end}}
<h1 dir="{{.Direction}}" id="blog-title">{{if .Posts}}{{else}}<span class="writeas-prefix"><a href="/">write.as</a></span> {{end}}<a href="/{{if .IsTopLevel}}{{else}}{{.Prefix}}{{.Alias}}/{{end}}" class="h-card p-author u-url" rel="me author">{{.DisplayTitle}}</a></h1>
{{if .Description}}<p class="description p-note">{{.Description}}</p>{{end}}
@ -137,14 +136,16 @@
<script type="text/javascript" src="/js/menu.js"></script>
<script type="text/javascript">
var deleting = false;
function delPost(e, id, owned) {
function delPost(e, id, owned, loc) {
e.preventDefault();
if (deleting) {
return;
}
var loc = JSON.parse(loc);
// TODO: UNDO!
if (window.confirm('Are you sure you want to delete this post?')) {
if (window.confirm(loc['Are you sure you want to delete this post?'])) {
// AJAX
deletePost(id, "", function() {
// Remove post from list
@ -155,7 +156,7 @@ function delPost(e, id, owned) {
}
}
var deletePost = function(postID, token, callback) {
var deletePost = function(postID, token, loc, callback) {
deleting = true;
var $delBtn = document.getElementById('post-' + postID).getElementsByClassName('delete action')[0];
@ -171,13 +172,13 @@ var deletePost = function(postID, token, callback) {
callback();
} else if (http.status == 409) {
$delBtn.innerHTML = 'delete';
alert("Post is synced to another account. Delete the post from that account instead.");
alert(loc["Post is synced to another account. Delete the post from that account instead."]);
// TODO: show "remove" button instead of "delete" now
// Persist that state.
// Have it remove the post locally only.
} else {
$delBtn.innerHTML = 'delete';
alert("Failed to delete." + (http.status>=500?" Please try again.":""));
alert(loc["Failed to delete."] + (http.status>=500?" " + loc["Please try again."]:""));
}
}
}
@ -185,12 +186,14 @@ var deletePost = function(postID, token, callback) {
};
var pinning = false;
function pinPost(e, postID, slug, title) {
function pinPost(e, postID, slug, title, loc) {
e.preventDefault();
if (pinning) {
return;
}
pinning = true;
var loc = JSON.parse(loc)
var callback = function() {
// Visibly remove post from collection
@ -222,13 +225,13 @@ function pinPost(e, postID, slug, title) {
callback();
} else if (http.status == 409) {
$pinBtn.innerHTML = 'pin';
alert("Post is synced to another account. Delete the post from that account instead.");
alert(loc["Post is synced to another account. Delete the post from that account instead."]);
// TODO: show "remove" button instead of "delete" now
// Persist that state.
// Have it remove the post locally only.
} else {
$pinBtn.innerHTML = 'pin';
alert("Failed to pin." + (http.status>=500?" Please try again.":""));
alert(loc["Failed to pin."] + (http.status>=500?" " + loc["Please try again."]:""));
}
}
}

View File

@ -2,7 +2,7 @@
<html>
<head>
<title>Edit metadata: {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}} &mdash; {{.SiteName}}</title>
<title>{{call .Tr "Edit metadata"}}: {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}} &mdash; {{.SiteName}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
@ -39,21 +39,21 @@
<header id="tools">
<div id="clip">
<h1><a href="/me/c/" title="View blogs"><img class="ic-24dp" src="/img/ic_blogs_dark@2x.png" /></a></h1>
<h1><a href="/me/c/" title={{call .Tr "View blog" 2}}><img class="ic-24dp" src="/img/ic_blogs_dark@2x.png" /></a></h1>
<nav id="target" class=""><ul>
<li>{{if .EditCollection}}<a href="{{.EditCollection.CanonicalURL}}">{{.EditCollection.Title}}</a>{{else}}<a>Draft</a>{{end}}</li>
<li>{{if .EditCollection}}<a href="{{.EditCollection.CanonicalURL}}">{{.EditCollection.Title}}</a>{{else}}<a>{{call .Tr "Draft"}}</a>{{end}}</li>
</ul></nav>
</div>
<div id="belt">
<div class="tool if-room"><a href="{{if .EditCollection}}{{.EditCollection.CanonicalURL}}{{.Post.Slug}}/edit{{else}}/{{.Post.Id}}/edit{{end}}" title="Edit post" id="edit"><img class="ic-24dp" src="/img/ic_edit_dark@2x.png" /></a></div>
<div class="tool if-room room-2"><a href="#theme" title="Toggle theme" id="toggle-theme"><img class="ic-24dp" src="/img/ic_brightness_dark@2x.png" /></a></div>
<div class="tool if-room room-1"><a href="/me/posts/" title="View posts" id="view-posts"><img class="ic-24dp" src="/img/ic_list_dark@2x.png" /></a></div>
<div class="tool if-room"><a href="{{if .EditCollection}}{{.EditCollection.CanonicalURL}}{{.Post.Slug}}/edit{{else}}/{{.Post.Id}}/edit{{end}}" title={{call .Tr "Edit post"}} id="edit"><img class="ic-24dp" src="/img/ic_edit_dark@2x.png" /></a></div>
<div class="tool if-room room-2"><a href="#theme" title={{call .Tr "Toggle theme"}} id="toggle-theme"><img class="ic-24dp" src="/img/ic_brightness_dark@2x.png" /></a></div>
<div class="tool if-room room-1"><a href="/me/posts/" title={{call .Tr "View post" 2}} id="view-posts"><img class="ic-24dp" src="/img/ic_list_dark@2x.png" /></a></div>
</div>
</header>
<div class="content-container tight">
<form action="/api/{{if .EditCollection}}collections/{{.EditCollection.Alias}}/{{end}}posts/{{.Post.Id}}" method="post" onsubmit="return updateMeta()">
<h2>Edit metadata: {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}} <a href="/{{if .EditCollection}}{{if not .SingleUser}}{{.EditCollection.Alias}}/{{end}}{{.Post.Slug}}{{else}}{{if .SingleUser}}d/{{end}}{{.Post.Id}}{{end}}">view post</a></h2>
<h2>{{call .Tr "Edit metadata"}}: {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}} <a href="/{{if .EditCollection}}{{if not .SingleUser}}{{.EditCollection.Alias}}/{{end}}{{.Post.Slug}}{{else}}{{if .SingleUser}}d/{{end}}{{.Post.Id}}{{end}}">{{call .Tr "View post"}}</a></h2>
{{if .Flashes}}<ul class="errors">
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}
@ -61,10 +61,10 @@
<dl class="dl-horizontal">
{{if .EditCollection}}
<dt><label for="slug">Slug</label></dt>
<dt><label for="slug">{{call .Tr "Slug"}}</label></dt>
<dd><input type="text" id="slug" name="slug" value="{{.Post.Slug}}" /></dd>
{{end}}
<dt><label for="lang">Language</label></dt>
<dt><label for="lang">{{call .Tr "Language"}}</label></dt>
<dd>
<select name="lang" id="lang" dir="auto" class="inputform">
<option value=""></option>
@ -254,14 +254,14 @@
<option value="zu"{{if eq "zu" .Post.Language.String}} selected="selected"{{end}}>isiZulu</option>
</select>
</dd>
<dt><label for="rtl">Direction</label></dt>
<dd><input type="checkbox" id="rtl" name="rtl" {{if .Post.IsRTL.Bool}}checked="checked"{{end}} /><label for="rtl"> right-to-left</label></dd>
<dt><label for="created">Created</label></dt>
<dt><label for="rtl">{{call .Tr "Direction"}}</label></dt>
<dd><input type="checkbox" id="rtl" name="rtl" {{if .Post.IsRTL.Bool}}checked="checked"{{end}} /><label for="rtl"> {{call .Tr "right-to-left"}}</label></dd>
<dt><label for="created">{{call .Tr "Created"}}</label></dt>
<dd>
<input type="text" id="created" name="created" value="{{.Post.UserFacingCreated}}" data-time="{{.Post.Created8601}}" placeholder="YYYY-MM-DD HH:MM:SS" maxlength="19" /> <span id="tz">UTC</span> <a href="#" id="set-now">now</a>
<input type="text" id="created" name="created" value="{{.Post.UserFacingCreated}}" data-time="{{.Post.Created8601}}" placeholder="YYYY-MM-DD HH:MM:SS" maxlength="19" /> <span id="tz">UTC</span> <a href="#" id="set-now">{{call .Tr "now"}}</a>
<p class="error" id="create-error">Date format should be: <span class="mono"><abbr title="The full year">YYYY</abbr>-<abbr title="The numeric month of the year, where January = 1, with a zero in front if less than 10">MM</abbr>-<abbr title="The day of the month, with a zero in front if less than 10">DD</abbr> <abbr title="The hour (00-23), with a zero in front if less than 10.">HH</abbr>:<abbr title="The minute of the hour (00-59), with a zero in front if less than 10.">MM</abbr>:<abbr title="The seconds (00-59), with a zero in front if less than 10.">SS</abbr></span></p>
</dd>
<dt>&nbsp;</dt><dd><input type="submit" value="Save changes" /></dd>
<dt>&nbsp;</dt><dd><input type="submit" value={{call .Tr "Save changes"}} /></dd>
</dl>
<input type="hidden" name="web" value="true" />
</form>

View File

@ -6,14 +6,14 @@
<a class="home" href="/">{{.SiteName}}</a>
{{if not .SingleUser}}
<a href="/about">about</a>
{{if and .LocalTimeline .CanViewReader}}<a href="/read">reader</a>{{end}}
{{if .Username}}<a href="https://writefreely.org/guide/{{.OfficialVersion}}" target="guide">writer's guide</a>{{end}}
<a href="/privacy">privacy</a>
{{if and .LocalTimeline .CanViewReader}}<a href="/read">{{call .Tr "Reader"}}</a>{{end}}
{{if .Username}}<a href="https://writefreely.org/guide/{{.OfficialVersion}}" target="guide">{{call .Tr "writer's guide"}}</a>{{end}}
<a href="/privacy">{{call .Tr "privacy"}}</a>
<p style="font-size: 0.9em">powered by <a href="https://writefreely.org">writefreely</a></p>
{{else}}
<a href="https://writefreely.org/guide/{{.OfficialVersion}}" target="guide">writer's guide</a>
<a href="https://developers.write.as/" title="Build on WriteFreely with our open developer API.">developers</a>
<a href="https://github.com/writefreely/writefreely">source code</a>
<a href="https://writefreely.org/guide/{{.OfficialVersion}}" target="guide">{{call .Tr "writer's guide"}}</a>
<a href="https://developers.write.as/" title="Build on WriteFreely with our open developer API.">{{call .Tr "developers"}}</a>
<a href="https://github.com/writefreely/writefreely">{{call .Tr "source code"}}</a>
<a href="https://writefreely.org">writefreely {{.Version}}</a>
{{end}}
</nav>
@ -23,17 +23,17 @@
<div class="half">
<h3><a class="home" href="/">{{.SiteName}}</a></h3>
<ul>
<li><a href="/about">about</a></li>
{{if and (and (not .SingleUser) .LocalTimeline) .CanViewReader}}<a href="/read">reader</a>{{end}}
<li><a href="/privacy">privacy</a></li>
<li><a href="/about">{{call .Tr "About"}}</a></li>
{{if and (and (not .SingleUser) .LocalTimeline) .CanViewReader}}<a href="/read">{{call .Tr "Reader"}}</a>{{end}}
<li><a href="/privacy">{{call .Tr "privacy"}}</a></li>
</ul>
</div>
<div class="half">
<h3><a href="https://writefreely.org" style="color:#444;text-transform:lowercase;">WriteFreely</a></h3>
<ul>
<li><a href="https://writefreely.org/guide/{{.OfficialVersion}}" target="guide">writer's guide</a></li>
<li><a href="https://developers.write.as/" title="Build on WriteFreely with our open developer API.">developers</a></li>
<li><a href="https://github.com/writefreely/writefreely">source code</a></li>
<li><a href="https://writefreely.org/guide/{{.OfficialVersion}}" target="guide">{{call .Tr "writer's guide"}}</a></li>
<li><a href="https://developers.write.as/" title="Build on WriteFreely with our open developer API.">{{call .Tr "developers"}}</a></li>
<li><a href="https://github.com/writefreely/writefreely">{{call .Tr "source code"}}</a></li>
<li style="margin-top:0.8em">{{.Version}}</li>
</ul>
</div>

View File

@ -6,30 +6,24 @@
{{ end }}
{{ if .WriteAsEnabled }}
<a class="btn cta loginbtn" id="writeas-login" href="/oauth/write.as">
<img src="/img/mark/writeas-white.png" />
Sign in with <strong>Write.as</strong>
</a>
<img src="/img/mark/writeas-white.png" />{{call .Tr "Sign in with **%s**" true (variables "Write.as")}}</a>
{{ end }}
{{ if .GitLabEnabled }}
<a class="btn cta loginbtn" id="gitlab-login" href="/oauth/gitlab">
<img src="/img/mark/gitlab.png" />
Sign in with <strong>{{.GitLabDisplayName}}</strong>
</a>
<img src="/img/mark/gitlab.png" />{{call .Tr "Sign in with **%s**" true (variables .GitLabDisplayName)}}</a>
{{ end }}
{{ if .GiteaEnabled }}
<a class="btn cta loginbtn" id="gitea-login" href="/oauth/gitea">
<img src="/img/mark/gitea.png" />
Sign in with <strong>{{.GiteaDisplayName}}</strong>
</a>
<a class="btn cta loginbtn" id="gitea-login" href="/oauth/gitea">GitLabDisplayName
<img src="/img/mark/gitea.png" />{{call .Tr "Sign in with **%s**" true (variables .GiteaDisplayName)}}</a>
{{ end }}
{{ if .GenericEnabled }}
<a class="btn cta loginbtn" id="generic-oauth-login" href="/oauth/generic">Sign in with <strong>{{.GenericDisplayName}}</strong></a>
<a class="btn cta loginbtn" id="generic-oauth-login" href="/oauth/generic">{{call .Tr "Sign in with **%s**" true (variables .GenericDisplayName)}}</a>
{{ end }}
</div>
{{if not .DisablePasswordAuth}}
<div class="or">
<p>or</p>
<p>{{call .Tr "or"}}</p>
<hr class="short" />
</div>
{{end}}

View File

@ -1,28 +1,33 @@
{{ define "posts" }}
<style type="text/css">
a.hidden.action{
text-transform: lowercase;
}
</style>
{{ range $el := .Posts }}<article id="post-{{.ID}}" class="{{.Font}} h-entry" itemscope itemtype="http://schema.org/BlogPosting">
{{if .IsScheduled}}<p class="badge">Scheduled</p>{{end}}
{{if .IsScheduled}}<p class="badge">{{call $.Tr "Scheduled"}}</p>{{end}}
{{if .Title.String}}<h2 class="post-title" itemprop="name" class="p-name">
{{- if .HasTitleLink -}}
{{.HTMLTitle}} <a class="user hidden action" href="{{if not $.SingleUser}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}">view{{if .IsPaid}} {{template "paid-badge" .}}{{end}}</a>
{{.HTMLTitle}} <a class="user hidden action" href="{{if not $.SingleUser}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}">{{call $.Tr "View"}}{{if .IsPaid}} {{template "paid-badge" .}}{{end}}</a>
{{- else -}}
{{- if .IsPaid}}{{template "paid-badge" .}}{{end -}}
<a href="{{if not $.SingleUser}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}" itemprop="url" class="u-url">{{.HTMLTitle}}</a>
{{- end}}
{{if $.IsOwner}}
<a class="user hidden action" href="/{{if not $.SingleUser}}{{$.Alias}}/{{end}}{{.Slug.String}}/edit">edit</a>
{{if $.CanPin}}<a class="user hidden pin action" href="/{{$.Alias}}/{{.Slug.String}}/pin" onclick="pinPost(event, '{{.ID}}', '{{.Slug.String}}', '{{.PlainDisplayTitle}}')">pin</a>{{end}}
<a class="user hidden delete action" onclick="delPost(event, '{{.ID}}')" href="/{{$.Alias}}/{{.Slug.String}}/delete">delete</a>
<a class="user hidden action" href="/{{if not $.SingleUser}}{{$.Alias}}/{{end}}{{.Slug.String}}/edit">{{call $.Tr "Edit"}}</a>
{{if $.CanPin}}<a class="user hidden pin action" href="/{{$.Alias}}/{{.Slug.String}}/pin" onclick="pinPost(event, '{{.ID}}', '{{.Slug.String}}', '{{.PlainDisplayTitle}}', '{{$.Locales}}')">{{call $.Tr "Pin"}}</a>{{end}}
<a class="user hidden delete action" onclick="delPost(event, '{{.ID}}', null, '{{$.Locales}}')" href="/{{$.Alias}}/{{.Slug.String}}/delete">{{call $.Tr "Delete"}}</a>
{{if gt (len $.Collections) 1}}<div class="user hidden action flat-select">
<select id="move-{{.ID}}" onchange="postActions.multiMove(this, '{{.ID}}', {{if $.SingleUser}}true{{else}}false{{end}})" title="Move this post to another blog">
<select id="move-{{.ID}}" onchange="postActions.multiMove(this, '{{.ID}}', {{if $.SingleUser}}true{{else}}false{{end}}, '{{$.Locales}}')" title="{{call $.Tr "Move this post to another blog"}}">
<option style="display:none"></option>
<option value="|anonymous|" style="font-style:italic">Draft</option>
<option value="|anonymous|" style="font-style:italic">{{call $.Tr "Draft"}}</option>
{{range $.Collections}}{{if ne .Alias $.Alias}}<option value="{{.Alias}}">{{.DisplayTitle}}</option>{{end}}{{end}}
</select>
<label for="move-{{.ID}}">move to...</label>
<label for="move-{{.ID}}">{{call $.Tr "move to..."}}</label>
<img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" />
</div>{{else}}
{{range $.Collections}}
<a class="user hidden action" href="/{{$el.ID}}" title="Change to a draft" onclick="postActions.move(this, '{{$el.ID}}', '|anonymous|', {{if $.SingleUser}}true{{else}}false{{end}});return false">change to <em>draft</em></a>
<a class="user hidden action" href="/{{$el.ID}}" title="{{call $.Tr "Change to a draft"}}" onclick="postActions.move(this, '{{$el.ID}}', '|anonymous|', {{if $.SingleUser}}true{{else}}false{{end}}, '{{$.Locales}}');return false">{{call $.Tr "change to _%s_" true (variables "Draft")}}</a>
{{end}}
{{end}}
{{end}}
@ -35,30 +40,30 @@
<time class="dt-published" datetime="{{.Created8601}}" pubdate itemprop="datePublished" content="{{.Created}}"><a href="{{if not $.SingleUser}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}" itemprop="url" class="u-url">{{.DisplayDate}}</a></time>
{{- end}}
{{if $.IsOwner}}
{{if not $.Format.ShowDates}}<a class="user hidden action" href="{{if not $.SingleUser}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}">view</a>{{end}}
<a class="user hidden action" href="/{{if not $.SingleUser}}{{$.Alias}}/{{end}}{{.Slug.String}}/edit">edit</a>
{{if $.CanPin}}<a class="user hidden pin action" href="/{{if not $.SingleUser}}{{$.Alias}}/{{end}}{{.Slug.String}}/pin" onclick="pinPost(event, '{{.ID}}', '{{.Slug.String}}', '{{.PlainDisplayTitle}}')">pin</a>{{end}}
<a class="user hidden delete action" onclick="delPost(event, '{{.ID}}')" href="/{{$.Alias}}/{{.Slug.String}}/delete">delete</a>
{{if not $.Format.ShowDates}}<a class="user hidden action" href="{{if not $.SingleUser}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}">{{call $.Tr "View"}}</a>{{end}}
<a class="user hidden action" href="/{{if not $.SingleUser}}{{$.Alias}}/{{end}}{{.Slug.String}}/edit">{{call $.Tr "Edit"}}</a>
{{if $.CanPin}}<a class="user hidden pin action" href="/{{if not $.SingleUser}}{{$.Alias}}/{{end}}{{.Slug.String}}/pin" onclick="pinPost(event, '{{.ID}}', '{{.Slug.String}}', '{{.PlainDisplayTitle}}', '{{$.Locales}}')">{{call $.Tr "Pin"}}</a>{{end}}
<a class="user hidden delete action" onclick="delPost(event, '{{.ID}}', null, '{{$.Locales}}')" href="/{{$.Alias}}/{{.Slug.String}}/delete">{{call $.Tr "Delete"}}</a>
{{if gt (len $.Collections) 1}}<div class="user hidden action flat-select">
<select id="move-{{.ID}}" onchange="postActions.multiMove(this, '{{.ID}}', {{if $.SingleUser}}true{{else}}false{{end}})" title="Move this post to another blog">
<select id="move-{{.ID}}" onchange="postActions.multiMove(this, '{{.ID}}', {{if $.SingleUser}}true{{else}}false{{end}}, '{{$.Locales}}')" title="{{call $.Tr "Move this post to another blog"}}">
<option style="display:none"></option>
<option value="|anonymous|" style="font-style:italic">Draft</option>
<option value="|anonymous|" style="font-style:italic">{{call $.Tr "Draft"}}</option>
{{range $.Collections}}{{if ne .Alias $.Alias}}<option value="{{.Alias}}">{{.DisplayTitle}}</option>{{end}}{{end}}
</select>
<label for="move-{{.ID}}">move to...</label>
<label for="move-{{.ID}}">{{call $.Tr "move to..."}}</label>
<img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" />
</div>{{else}}
{{range $.Collections}}
<a class="user hidden action" href="/{{$el.ID}}" title="Change to a draft" onclick="postActions.move(this, '{{$el.ID}}', '|anonymous|', {{if $.SingleUser}}true{{else}}false{{end}});return false">change to <em>draft</em></a>
<a class="user hidden action" href="/{{$el.ID}}" title="{{call $.Tr "Change to a draft"}}" onclick="postActions.move(this, '{{$el.ID}}', '|anonymous|', {{if $.SingleUser}}true{{else}}false{{end}}, '{{$.Locales}}');return false">{{call $.Tr "change to _%s_" true (variables "Draft")}}</a>
{{end}}
{{end}}
{{end}}
</h2>
{{end}}
{{if .Excerpt}}<div {{if .Language}}lang="{{.Language.String}}"{{end}} dir="{{.Direction}}" class="book p-summary">{{if and (and (not $.IsOwner) (not $.Format.ShowDates)) (not .Title.String)}}<a class="hidden action" href="{{if $.IsOwner}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}">view</a>{{end}}{{.Excerpt}}</div>
{{if .Excerpt}}<div {{if .Language}}lang="{{.Language.String}}"{{end}} dir="{{.Direction}}" class="book p-summary">{{if and (and (not $.IsOwner) (not $.Format.ShowDates)) (not .Title.String)}}<a class="hidden action" href="{{if $.IsOwner}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}">{{call $.Tr "View"}}</a>{{end}}{{.Excerpt}}</div>
<a class="read-more" href="{{$.CanonicalURL}}{{.Slug.String}}">{{localstr "Read more..." .Language.String}}</a>{{else}}<div {{if .Language}}lang="{{.Language.String}}"{{end}} dir="{{.Direction}}" class="book e-content">{{if and (and (not $.IsOwner) (not $.Format.ShowDates)) (not .Title.String)}}<a class="hidden action" href="{{if $.IsOwner}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}">view</a>{{end}}{{.HTMLContent}}</div>{{end}}</article>{{ end }}
<a class="read-more" href="{{$.CanonicalURL}}{{.Slug.String}}">{{localstr "Read more..." .Language.String}}</a>{{else}}<div {{if .Language}}lang="{{.Language.String}}"{{end}} dir="{{.Direction}}" class="book e-content">{{if and (and (not $.IsOwner) (not $.Format.ShowDates)) (not .Title.String)}}<a class="hidden action" href="{{if $.IsOwner}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}">{{call $.Tr "View"}}</a>{{end}}{{.HTMLContent}}</div>{{end}}</article>{{ end }}
{{ end }}
{{define "paid-badge"}}<img class="paid" alt="Paid article" src="/img/paidarticle.svg" /> {{end}}

View File

@ -2,7 +2,7 @@
<html>
<head>
<title>{{if .Editing}}Editing {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}}{{else}}New Post{{end}} &mdash; {{.SiteName}}</title>
<title>{{if .Editing}}Editing {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}}{{else}}{{call .Tr "New Post"}}{{end}} &mdash; {{.SiteName}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
@ -14,55 +14,56 @@
<div id="overlay"></div>
<textarea id="writer" placeholder="Write..." class="{{.Post.Font}}" autofocus>{{if .Post.Title}}# {{.Post.Title}}
<textarea id="writer" placeholder={{call .Tr "Write..."}} class="{{.Post.Font}}" autofocus>{{if .Post.Title}}# {{.Post.Title}}
{{end}}{{.Post.Content}}</textarea>
<div class="alert success hidden" id="edited-elsewhere">This post has been updated elsewhere since you last published! <a href="#" id="erase-edit">Delete draft and reload</a>.</div>
<div class="alert success hidden" id="edited-elsewhere">{{call .Tr "This post has been updated elsewhere since you last published!"}} <a href="#" id="erase-edit">{{call .Tr "Delete draft and reload"}}</a>.</div>
<header id="tools">
<div id="clip">
{{if not .SingleUser}}<h1><a href="/me/c/" title="View blogs"><img class="ic-24dp" src="/img/ic_blogs_dark@2x.png" /></a></h1>{{end}}
{{if not .SingleUser}}<h1><a href="/me/c/" title={{call .Tr "View Blog" 2}}><img class="ic-24dp" src="/img/ic_blogs_dark@2x.png" /></a></h1>{{end}}
<nav id="target" {{if .SingleUser}}style="margin-left:0"{{end}}><ul>
{{if .Editing}}<li>{{if .EditCollection}}<a href="{{.EditCollection.CanonicalURL}}">{{.EditCollection.Title}}</a>{{else}}<a>Draft</a>{{end}}</li>
{{else}}<li class="has-submenu"><a href="#" id="publish-to" onclick="return false"><span id="target-name">Draft</span> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /></a>
{{if .Editing}}<li>{{if .EditCollection}}<a href="{{.EditCollection.CanonicalURL}}">{{.EditCollection.Title}}</a>{{else}}<a>{{call .Tr "Draft"}}</a>{{end}}</li>
{{else}}<li class="has-submenu"><a href="#" id="publish-to" onclick="return false"><span id="target-name">{{call .Tr "Draft"}}</span> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /></a>
<ul>
<li class="menu-heading">Publish to...</li>
<li class="menu-heading">{{call .Tr "Publish to..."}}</li>
{{if .Blogs}}{{range $idx, $el := .Blogs}}
<li class="target{{if eq $idx 0}} selected{{end}}" id="blog-{{$el.Alias}}"><a href="#{{$el.Alias}}"><i class="material-icons md-18">public</i> {{if $el.Title}}{{$el.Title}}{{else}}{{$el.Alias}}{{end}}</a></li>
{{end}}{{end}}
<li class="target" id="blog-anonymous"><a href="#anonymous"><i class="material-icons md-18">description</i> <em>Draft</em></a></li>
<li class="target" id="blog-anonymous"><a href="#anonymous"><i class="material-icons md-18">description</i> <em>{{call .Tr "Draft"}}</em></a></li>
<li id="user-separator" class="separator"><hr /></li>
{{ if .SingleUser }}
<li><a href="/"><i class="material-icons md-18">launch</i> View Blog</a></li>
<li><a href="/me/c/{{.Username}}"><i class="material-icons md-18">palette</i> Customize</a></li>
<li><a href="/me/c/{{.Username}}/stats"><i class="material-icons md-18">trending_up</i> Stats</a></li>
<li><a href="/"><i class="material-icons md-18">launch</i> {{call .Tr "View Blog"}}</a></li>
<li><a href="/me/c/{{.Username}}"><i class="material-icons md-18">palette</i> {{call .Tr "Customize"}}</a></li>
<li><a href="/me/c/{{.Username}}/stats"><i class="material-icons md-18">trending_up</i> {{call .Tr "Stats"}}</a></li>
{{ else }}
<li><a href="/me/c/"><i class="material-icons md-18">library_books</i> View Blogs</a></li>
<li><a href="/me/c/"><i class="material-icons md-18">library_books</i> {{call .Tr "View Blog" 2}}</a></li>
{{ end }}
<li><a href="/me/posts/"><i class="material-icons md-18">view_list</i> View Drafts</a></li>
<li><a href="/me/logout"><i class="material-icons md-18">power_settings_new</i> Log out</a></li>
<li><a href="/me/posts/"><i class="material-icons md-18">view_list</i> {{call .Tr "View Draft" 2}}</a></li>
<li><a href="/me/logout"><i class="material-icons md-18">power_settings_new</i> {{call .Tr "Log out"}}</a></li>
</ul>
</li>{{end}}
</ul></nav>
<nav id="font-picker" class="if-room room-3 hidden" style="margin-left:-1em"><ul>
<li class="has-submenu"><a href="#" id="" onclick="return false"><img class="ic-24dp" src="/img/ic_font_dark@2x.png" /> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /></a>
<ul style="text-align: center">
<li class="menu-heading">Font</li>
<li class="menu-heading">{{call .Tr "Font"}}</li>
<li class="selected"><a class="font norm" href="#norm">Serif</a></li>
<li><a class="font sans" href="#sans">Sans-serif</a></li>
<li><a class="font wrap" href="#wrap">Monospace</a></li>
</ul>
</li>
</ul></nav>
<span id="wc" class="hidden if-room room-4">0 words</span>
{{ $N := 2 }}{{ if eq .AppCfg.Lang "eu_ES" }}{{ $N = 1}}{{ end }}
<span id="wc" class="hidden if-room room-4">0 {{call .Tr "words" $N}}</span>
</div>
<noscript style="margin-left: 2em;"><strong>NOTE</strong>: for now, you'll need Javascript enabled to post.</noscript>
<noscript style="margin-left: 2em;"><strong>{{call .Tr "NOTE"}}</strong>: {{call .Tr "for now, you'll need Javascript enabled to post."}}</noscript>
<div id="belt">
{{if .Editing}}<div class="tool hidden if-room"><a href="{{if .EditCollection}}{{.EditCollection.CanonicalURL}}{{.Post.Slug}}/edit/meta{{else}}/{{if .SingleUser}}d/{{end}}{{.Post.Id}}/meta{{end}}" title="Edit post metadata" id="edit-meta"><img class="ic-24dp" src="/img/ic_info_dark@2x.png" /></a></div>{{end}}
<div class="tool hidden if-room room-2"><a href="#theme" title="Toggle theme" id="toggle-theme"><img class="ic-24dp" src="/img/ic_brightness_dark@2x.png" /></a></div>
<div class="tool if-room room-1"><a href="{{if not .User}}/pad/posts{{else}}/me/posts/{{end}}" title="View posts" id="view-posts"><img class="ic-24dp" src="/img/ic_list_dark@2x.png" /></a></div>
<div class="tool"><a href="#publish" title="Publish" id="publish"><img class="ic-24dp" src="/img/ic_send_dark@2x.png" /></a></div>
{{if .Editing}}<div class="tool hidden if-room"><a href="{{if .EditCollection}}{{.EditCollection.CanonicalURL}}{{.Post.Slug}}/edit/meta{{else}}/{{if .SingleUser}}d/{{end}}{{.Post.Id}}/meta{{end}}" title={{call .Tr "Edit post metadata"}} id="edit-meta"><img class="ic-24dp" src="/img/ic_info_dark@2x.png" /></a></div>{{end}}
<div class="tool hidden if-room room-2"><a href="#theme" title={{call .Tr "Toggle theme"}} id="toggle-theme"><img class="ic-24dp" src="/img/ic_brightness_dark@2x.png" /></a></div>
<div class="tool if-room room-1"><a href="{{if not .User}}/pad/posts{{else}}/me/posts/{{end}}" title={{call .Tr "View post" 2}} id="view-posts"><img class="ic-24dp" src="/img/ic_list_dark@2x.png" /></a></div>
<div class="tool"><a href="#publish" title={{call .Tr "Publish"}} id="publish"><img class="ic-24dp" src="/img/ic_send_dark@2x.png" /></a></div>
</div>
</header>
@ -121,7 +122,11 @@
if (val != '') {
words = $writer.el.value.trim().replace(/\s+/gi, ' ').split(' ').length;
}
$wc.el.innerText = words + " word" + (words != 1 ? "s" : "");
//$wc.el.innerText = words + " word" + (words != 1 ? "s" : "");
{{ $N := 2 }}{{ if eq .AppCfg.Lang "eu_ES" }}{{ $N = 1}}{{ end }} //don't need to pluralize for the basque language
$wc.el.innerText = words + " " + (words !=1 ? {{call .Tr "word" $N}} : {{call .Tr "word" 1}})
};
var setButtonStates = function() {
if (!canPublish) {
@ -170,12 +175,12 @@
var silenced = {{.Silenced}};
var publish = function(content, font) {
if (silenced === true) {
alert("Your account is silenced, so you can't publish or update posts.");
alert({{call .Tr "Your account is silenced, so you can't publish or update posts."}});
return;
}
{{if and (and .Post.Id (not .Post.Slug)) (not .User)}}
if (!token) {
alert("You don't have permission to update this post.");
alert({{call .Tr "You don't have permission to update this post."}});
return;
}
if ($btnPublish.el.className == 'disabled') {
@ -271,7 +276,7 @@
{{end}}
} else {
$btnPublish.el.children[0].textContent = 'send';
alert("Failed to post. Please try again.");
alert({{call .Tr "Failed to post. Please try again."}});
}
}
}

View File

@ -6,7 +6,6 @@
<title>{{.DisplayTitle}}{{if not .SingleUser}} &mdash; {{.SiteName}}{{end}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="canonical" href="{{.CanonicalURL}}">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@ -29,9 +28,9 @@
{{if .SingleUser}}
<nav id="manage">
<ul>
<li class="has-submenu"><a onclick="void(0)">&#9776; Menu</a>
<li class="has-submenu"><a onclick="void(0)">&#9776; {{call .Tr "Menu"}}</a>
<ul>
<li><a href="/login">Log in</a></li>
<li><a href="/login">{{call .Tr "Log in"}}</a></li>
</ul>
</li>
</ul>
@ -49,7 +48,7 @@
{{if .Flashes}}<ul class="errors">
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}
</ul>{{else}}
<h2>This blog requires a password.</h2>
<h2>{{call .Tr "This blog requires a password."}}</h2>
{{end}}
<input type="hidden" name="alias" value="{{.Alias}}" />
<input type="hidden" name="to" value="{{.Next}}" />

View File

@ -9,7 +9,6 @@
<link rel="stylesheet" href="/css/lib/mono-blue.min.css">
{{end}}
<link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="canonical" href="{{.Host}}/{{if .SingleUser}}d/{{end}}{{.ID}}" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@ -39,19 +38,19 @@
<header>
<h1 dir="{{.Direction}}"><a href="/">{{.SiteName}}</a></h1>
<nav>
<span class="views{{if not .IsOwner}} owner-visible{{end}}" dir="ltr"><strong>{{largeNumFmt .Views}}</strong> {{pluralize "view" "views" .Views}}</span>
{{if .IsCode}}<a href="/{{.ID}}.txt" rel="noindex" dir="{{.Direction}}">View raw</a>{{end}}
<span class="views{{if not .IsOwner}} owner-visible{{end}}" dir="ltr"><strong>{{largeNumFmt .Views}}</strong> {{call .Tr "View" 2}}</span>
{{if .IsCode}}<a href="/{{.ID}}.txt" rel="noindex" dir="{{.Direction}}">{{call .Tr "View raw"}}</a>{{end}}
{{ if .Username }}
{{if .IsOwner}}
<a href="/{{if .SingleUser}}d/{{end}}{{.ID}}/edit" dir="{{.Direction}}">Edit</a>
<a href="/{{if .SingleUser}}d/{{end}}{{.ID}}/edit" dir="{{.Direction}}">{{call .Tr "Edit"}}</a>
{{end}}
<a class="xtra-feature dash-nav" href="/me/posts/" dir="{{.Direction}}">Drafts</a>
<a class="xtra-feature dash-nav" href="/me/posts/" dir="{{.Direction}}">{{call .Tr "Draft" 2}}</a>
{{ end }}
</nav>
</header>
{{if .Silenced}}
{{template "user-silenced"}}
{{template "user-silenced" (dict "Tr" $.Tr)}}
{{end}}
<article class="{{.Font}} h-entry">{{if .Title}}<h2 id="title" class="p-name">{{.Title}}</h2>{{end}}{{ if .IsPlainText }}<p id="post-body" class="e-content">{{.Content}}</p>{{ else }}<div id="post-body" class="e-content">{{.HTMLContent}}</div>{{ end }}</article>
@ -89,7 +88,7 @@
var $nav = document.getElementsByTagName('nav')[0];
for (var i=0; i<posts.length; i++) {
if (posts[i].id == "{{.ID}}") {
$nav.innerHTML = $nav.innerHTML + '<a class="xtra-feature" href="/edit/{{.ID}}" dir="{{.Direction}}">Edit</a>';
$nav.innerHTML = $nav.innerHTML + '<a class="xtra-feature" href="/edit/{{.ID}}" dir="{{.Direction}}">{{call .Tr "Edit"}}</a>';
var $ownerVis = document.querySelectorAll('.owner-visible');
for (var i=0; i<$ownerVis.length; i++) {
$ownerVis[i].classList.remove('owner-visible');

View File

@ -102,7 +102,7 @@
<time class="dt-published" datetime="{{.Created8601}}" pubdate itemprop="datePublished" content="{{.Created}}"><a href="{{if .Collection}}{{.Collection.CanonicalURL}}{{.Slug.String}}{{else}}{{.CanonicalURL .Host}}.md{{end}}" itemprop="url" class="u-url">{{.DisplayDate}}</a></time>
</h2>
{{- end}}
<p class="source">{{if .Collection}}from <a href="{{.Collection.CanonicalURL}}">{{.Collection.DisplayTitle}}</a>{{else}}<em>Anonymous</em>{{end}}</p>
<p class="source">{{if .Collection}}{{call $.Tr "from"}} <a href="{{.Collection.CanonicalURL}}">{{.Collection.DisplayTitle}}</a>{{else}}<em>{{call $.Tr "Anonymous"}}</em>{{end}}</p>
{{if .Excerpt}}<div class="p-summary" {{if .Language}}lang="{{.Language.String}}"{{end}} dir="{{.Direction}}">{{.Excerpt}}</div>
<a class="read-more" href="{{if .Collection}}{{.Collection.CanonicalURL}}{{.Slug.String}}{{else}}{{.CanonicalURL .Host}}.md{{end}}">{{localstr "Read more..." .Language.String}}</a>{{else}}<div class="e-content preview" {{if .Language}}lang="{{.Language.String}}"{{end}} dir="{{.Direction}}">{{ if not .HTMLContent }}<p id="post-body" class="e-content preview">{{.Content}}</p>{{ else }}{{.HTMLContent}}{{ end }}<div class="over">&nbsp;</div></div>
@ -117,8 +117,8 @@
{{ end }}
{{if gt .TotalPages 1}}<nav id="paging" class="content-container clearfix">
{{if lt .CurrentPage .TotalPages}}<a href="{{.NextPageURL .CurrentPage}}">&#8672; Older</a>{{end}}
{{if gt .CurrentPage 1}}<a style="float:right;" href="{{.PrevPageURL .CurrentPage}}">Newer &#8674;</a>{{end}}
{{if lt .CurrentPage .TotalPages}}<a href="{{.NextPageURL .CurrentPage}}">&#8672; {{call .Tr "Older"}}</a>{{end}}
{{if gt .CurrentPage 1}}<a style="float:right;" href="{{.PrevPageURL .CurrentPage}}">{{call .Tr "Newer"}} &#8674;</a>{{end}}
</nav>{{end}}
</div>

View File

@ -38,6 +38,7 @@ p.docs {
.stats {
font-size: 1.2em;
margin: 1em 0;
text-transform: lowercase;
}
.num {
font-weight: bold;
@ -51,9 +52,12 @@ p.docs {
{{if .Message}}<p>{{.Message}}</p>{{end}}
<div class="row stats">
<div><span class="num">{{largeNumFmt .UsersCount}}</span> {{pluralize "user" "users" .UsersCount}}</div>
<div><span class="num">{{largeNumFmt .CollectionsCount}}</span> {{pluralize "blog" "blogs" .CollectionsCount}}</div>
<div><span class="num">{{largeNumFmt .PostsCount}}</span> {{pluralize "post" "posts" .PostsCount}}</div>
{{ $N := .UsersCount }}{{ if eq .AppCfg.Lang "eu_ES" }}{{ $N = 1}}{{ end }}
{{ $C := .CollectionsCount }}{{ if eq .AppCfg.Lang "eu_ES" }}{{ $C = 1}}{{ end }}
{{ $P := .PostsCount }}{{ if eq .AppCfg.Lang "eu_ES" }}{{ $P = 1}}{{ end }}
<div><span class="num">{{largeNumFmt .UsersCount}}</span> {{call .Tr "User" $N}}</div>
<div><span class="num">{{largeNumFmt .CollectionsCount}}</span> {{call .Tr "Blog" $C}}</div>
<div><span class="num">{{largeNumFmt .PostsCount}}</span> {{call .Tr "post" $P}}</div>
</div>
</div>

View File

@ -34,136 +34,136 @@ select {
<form action="/admin/update/config" method="post">
<div class="features row">
<div{{if .Config.SingleUser}} class="invisible"{{end}}>
Site Title
<p>Your public site name.</p>
{{call .Tr "Site Title"}}
<p>{{call .Tr "Your public site name."}}</p>
</div>
<div{{if .Config.SingleUser}} class="invisible"{{end}}><input type="text" name="site_name" id="site_name" class="inline" value="{{.Config.SiteName}}" style="width: 14em;"/></div>
</div>
<div class="features row">
<div{{if .Config.SingleUser}} class="invisible"{{end}}>
Site Description
<p>Describe your site &mdash; this shows in your site's metadata.</p>
{{call .Tr "Site Description"}}
<p>{{call .Tr "Describe your site — this shows in your site's metadata."}}</p>
</div>
<div{{if .Config.SingleUser}} class="invisible"{{end}}><input type="text" name="site_desc" id="site_desc" class="inline" value="{{.Config.SiteDesc}}" style="width: 14em;"/></div>
</div>
<div class="features row">
<div>
Host
<p>The public address where users will access your site, starting with <code>http://</code> or <code>https://</code>.</p>
{{call .Tr "Host"}}
<p>{{call .Tr "The public address where users will access your site, starting with `http://` or `https://`." true}}</p>
</div>
<div>{{.Config.Host}}</div>
</div>
<div class="features row">
<div>
Community Mode
<p>Whether your site is made for one person or many.</p>
{{call .Tr "Community Mode"}}
<p>{{call .Tr "Whether your site is made for one person or many."}}</p>
</div>
<div>{{if .Config.SingleUser}}Single user{{else}}Multiple users{{end}}</div>
<div>{{if .Config.SingleUser}}{{call .Tr "Single user"}}{{else}}{{call .Tr "Multiple users"}}{{end}}</div>
</div>
<div class="features row">
<div{{if .Config.SingleUser}} class="invisible"{{end}}>
Landing Page
<p>The page that logged-out visitors will see first. This should be an absolute path like: <code>/read</code></p>
{{call .Tr "Landing Page"}}
<p>{{call .Tr "The page that logged-out visitors will see first. This should be an absolute path like: `/read`." true}}</p>
</div>
<div{{if .Config.SingleUser}} class="invisible"{{end}}><input type="text" name="landing" id="landing" class="inline" value="{{.Config.Landing}}" style="width: 14em;"/></div>
</div>
<div class="features row">
<div{{if .Config.SingleUser}} class="invisible"{{end}}><label for="open_registration">
Open Registrations
<p>Allow anyone who visits the site to create an account.</p>
{{call .Tr "Open Registrations"}}
<p>{{call .Tr "Allow anyone who visits the site to create an account."}}</p>
</label></div>
<div{{if .Config.SingleUser}} class="invisible"{{end}}><input type="checkbox" name="open_registration" id="open_registration" {{if .Config.OpenRegistration}}checked="checked"{{end}} />
</div>
</div>
<div class="features row">
<div{{if .Config.SingleUser}} class="invisible"{{end}}><label for="open_deletion">
Allow account deletion
<p>Allow all users to delete their account. Admins can always delete users.</p>
{{call .Tr "Allow account deletion"}}
<p>{{call .Tr "Allow all users to delete their account. Admins can always delete users."}}</p>
</label></div>
<div{{if .Config.SingleUser}} class="invisible"{{end}}><input type="checkbox" name="open_deletion" id="open_deletion" {{if .Config.OpenDeletion}}checked="checked"{{end}} />
</div>
</div>
<div class="features row">
<div{{if .Config.SingleUser}} class="invisible"{{end}}><label for="user_invites">
Allow invitations from...
<p>Choose who is allowed to invite new people.</p>
{{call .Tr "Allow invitations from..."}}
<p>{{call .Tr "Choose who is allowed to invite new people."}}</p>
</label></div>
<div{{if .Config.SingleUser}} class="invisible"{{end}}>
<select name="user_invites" id="user_invites">
<option value="none" {{if eq .Config.UserInvites ""}}selected="selected"{{end}}>No one</option>
<option value="admin" {{if eq .Config.UserInvites "admin"}}selected="selected"{{end}}>Only Admins</option>
<option value="user" {{if eq .Config.UserInvites "user"}}selected="selected"{{end}}>All Users</option>
<option value="none" {{if eq .Config.UserInvites ""}}selected="selected"{{end}}>{{call .Tr "No one"}}</option>
<option value="admin" {{if eq .Config.UserInvites "admin"}}selected="selected"{{end}}>{{call .Tr "Only Admins"}}</option>
<option value="user" {{if eq .Config.UserInvites "user"}}selected="selected"{{end}}>{{call .Tr "All Users"}}</option>
</select>
</div>
</div>
<div class="features row">
<div><label for="private">
Private Instance
<p>Limit site access to people with an account.</p>
{{call .Tr "Private Instance"}}
<p>{{call .Tr "Limit site access to people with an account."}}</p>
</label></div>
<div><input type="checkbox" name="private" id="private" {{if .Config.Private}}checked="checked"{{end}} /></div>
</div>
<div class="features row">
<div{{if .Config.SingleUser}} class="invisible"{{end}}><label for="local_timeline">
Reader
<p>Show a feed of user posts for anyone who chooses to share there.</p>
{{call .Tr "Reader"}}
<p>{{call .Tr "Show a feed of user posts for anyone who chooses to share there."}}</p>
</label></div>
<div{{if .Config.SingleUser}} class="invisible"{{end}}><input type="checkbox" name="local_timeline" id="local_timeline" {{if .Config.LocalTimeline}}checked="checked"{{end}} /></div>
</div>
<div class="features row">
<div{{if .Config.SingleUser}} class="invisible"{{end}}><label for="default_visibility">
Default blog visibility
<p>The default setting for new accounts and blogs.</p>
{{call .Tr "Default blog visibility"}}
<p>{{call .Tr "The default setting for new accounts and blogs."}}</p>
</label></div>
<div{{if .Config.SingleUser}} class="invisible"{{end}}>
<select name="default_visibility" id="default_visibility">
<option value="unlisted" {{if eq .Config.DefaultVisibility "unlisted"}}selected="selected"{{end}}>Unlisted</option>
<option value="public" {{if eq .Config.DefaultVisibility "public"}}selected="selected"{{end}}>Public</option>
<option value="private" {{if eq .Config.DefaultVisibility "private"}}selected="selected"{{end}}>Private</option>
<option value="unlisted" {{if eq .Config.DefaultVisibility "unlisted"}}selected="selected"{{end}}>{{call .Tr "Unlisted"}}</option>
<option value="public" {{if eq .Config.DefaultVisibility "public"}}selected="selected"{{end}}>{{call .Tr "Public"}}</option>
<option value="private" {{if eq .Config.DefaultVisibility "private"}}selected="selected"{{end}}>{{call .Tr "Private"}}</option>
</select>
</div>
</div>
<div class="features row">
<div{{if .Config.SingleUser}} class="invisible"{{end}}><label for="max_blogs">
Maximum Blogs per User
<p>Keep things simple by setting this to <strong>1</strong>, unlimited by setting to <strong>0</strong>, or pick another amount.</p>
{{call .Tr "Maximum Blogs per User"}}
<p>{{call .Tr "Keep things simple by setting this to **1**, unlimited by setting to **0**, or pick another amount." true}}</p>
</label></div>
<div{{if .Config.SingleUser}} class="invisible"{{end}}><input type="number" name="max_blogs" id="max_blogs" class="inline" min="0" value="{{.Config.MaxBlogs}}"/></div>
</div>
<div class="features row">
<div><label for="federation">
Federation
<p>Enable accounts on this site to propagate their posts via the ActivityPub protocol.</p>
{{call .Tr "Federation"}}
<p>{{call .Tr "Enable accounts on this site to propagate their posts via the ActivityPub protocol."}}</p>
</label></div>
<div><input type="checkbox" name="federation" id="federation" {{if .Config.Federation}}checked="checked"{{end}} /></div>
</div>
<div class="features row">
<div><label for="public_stats">
Public Stats
<p>Publicly display the number of users and posts on your <strong>About</strong> page.</p>
{{call .Tr "Public Stats"}}
<p>{{call .Tr "Erakutsi publikoki erabiltzaile eta post kopurua **%s** orrian." true (variables "About")}}</p>
</label></div>
<div><input type="checkbox" name="public_stats" id="public_stats" {{if .Config.PublicStats}}checked="checked"{{end}} /></div>
</div>
<div class="features row">
<div><label for="monetization">
Monetization
<p>Enable blogs on this site to receive micro&shy;pay&shy;ments from readers via <a target="wm" href="https://webmonetization.org/">Web Monetization</a>.</p>
{{call .Tr "Monetization"}}
<p>{{call .Tr "Enable blogs on this site to receive micropayments from readers via %s." true (variables "Web Monetization;https://webmonetization.org/" )}}</p>
</label></div>
<div><input type="checkbox" name="monetization" id="monetization" {{if .Config.Monetization}}checked="checked"{{end}} /></div>
</div>
<div class="features row">
<div><label for="min_username_len">
Minimum Username Length
<p>The minimum number of characters allowed in a username. (Recommended: 2 or more.)</p>
{{call .Tr "Minimum Username Length"}}
<p>{{call .Tr "The minimum number of characters allowed in a username. (Recommended: 2 or more.)"}}</p>
</label></div>
<div><input type="number" name="min_username_len" id="min_username_len" class="inline" min="1" max="100" value="{{.Config.MinUsernameLen}}"/></div>
</div>
<div class="features row">
<input type="submit" value="Save Settings" />
<input type="submit" value="{{call .Tr "Save Settings"}}" />
</div>
</form>
<p class="docs">Still have questions? Read more details in the <a href="https://writefreely.org/docs/{{.OfficialVersion}}/admin/config">configuration docs</a>.</p>
{{ $link := print "configuration docs;https://writefreely.org/docs/" .OfficialVersion "/admin/config" }}
<p class="docs">{{call .Tr "Still have questions? Read more details in the %s." true (variables $link)}}</p>
</div>
<script>

View File

@ -16,30 +16,31 @@
{{ if .UpdateChecks }}
{{if .CheckFailed}}
<p class="intro"><span class="ex failure">&times;</span> Automated update check failed.</p>
<p>Installed version: <strong>{{.Version}}</strong> (<a href="{{.CurReleaseNotesURL}}" target="changelog-wf">release notes</a>).</p>
<p>Learn about latest releases on the <a href="https://blog.writefreely.org/tag:release" target="changelog-wf">WriteFreely blog</a> or <a href="https://discuss.write.as/c/writefreely/updates" target="forum-wf">forum</a>.</p>
<p class="intro"><span class="ex failure">&times;</span> {{call .Tr "Automated update check failed."}}</p>
<p>{{call .Tr "Installed version: **%s** (%s)." true (variables .Version "release notes; .CurReleaseNotesURL") }}</p>
<p>{{call .Tr "Learn about latest releases on the %s or %s." true (variables "Writefreely blog;https://blog.writefreely.org/tag:release" "forum;https://discuss.write.as/c/writefreely/updates" )}}</p>
{{else if not .UpdateAvailable}}
<p class="intro"><span class="check">&check;</span> WriteFreely is <strong>up to date</strong>.</p>
<p>Installed version: <strong>{{.Version}}</strong> (<a href="{{.LatestReleaseNotesURL}}" target="changelog-wf">release notes</a>).</p>
<p class="intro"><span class="check">&check;</span>{{call .Tr "WriteFreely is **up to date**."}}</p>
<p>{{call .Tr "Installed version: **%s** (%s)." true (variables .Version "release notes; .CurReleaseNotesURL") }}</p>
{{else}}
<p class="intro">A new version of WriteFreely is available! <a href="{{.LatestReleaseURL}}" target="download-wf" style="font-weight: bold;">Get {{.LatestVersion}}</a></p>
<p class="changelog">
<a href="{{.LatestReleaseNotesURL}}" target="changelog-wf">Read the release notes</a> for details on features, bug fixes, and notes on upgrading from your current version, <strong>{{.Version}}</strong>.
</p>
{{ $link := print .LatestVersion ";" .LatestReleaseURL }}
<p class="intro">{{call .Tr "A new version of WriteFreely is available! **%s %s**" true (variables "Get" $link) }}</p>
{{ $link2 := print "release notes;" .LatestReleaseNotesURL }}
<p class="changelog">{{call .Tr "Read the %s for details on features, bug fixes, and notes on upgrading from your current version, **%s**." true (variables $link2 .Version) }}</p>
{{end}}
<p style="font-size: 0.86em;"><em>Last checked</em>: <time class="dt-published" datetime="{{.LastChecked8601}}">{{.LastChecked}}</time>. <a href="/admin/updates?check=now">Check now</a>.</p>
<p style="font-size: 0.86em;"><em>{{call .Tr "Last checked"}}</em>: <time class="dt-published" datetime="{{.LastChecked8601}}">{{.LastChecked}}</time>. <a href="/admin/updates?check=now">{{call .Tr "Check now"}}</a>.</p>
<script>
// Code modified from /js/localdate.js
var displayEl = document.querySelector("time");
var d = new Date(displayEl.getAttribute("datetime"));
displayEl.textContent = d.toLocaleDateString(navigator.language || "en-US", { dateStyle: 'long', timeStyle: 'short' });
//var displayEl = document.querySelector("time");
//var d = new Date(displayEl.getAttribute("datetime"));
//displayEl.textContent = d.toLocaleDateString(navigator.language || "en-US", { dateStyle: 'long', timeStyle: 'short' });
</script>
<script src="/js/localdate.js"></script>
{{ else }}
<p class="intro disabled">Automated update checks are disabled.</p>
<p>Installed version: <strong>{{.Version}}</strong> (<a href="{{.CurReleaseNotesURL}}" target="changelog-wf">release notes</a>).</p>
<p>Learn about latest releases on the <a href="https://blog.writefreely.org/tag:release" target="changelog-wf">WriteFreely blog</a> or <a href="https://discuss.write.as/c/writefreely/updates" target="forum-wf">forum</a>.</p>
<p class="intro disabled">{{call .Tr "Automated update checks are disabled."}}</p>
<p>{{call .Tr "Installed version: **%s** (%s)." true (variables .Version "release notes; .CurReleaseNotesURL") }}</p>
<p>{{call .Tr "Learn about latest releases on the %s or %s." true (variables "Writefreely blog;https://blog.writefreely.org/tag:release" "forum;https://discuss.write.as/c/writefreely/updates" )}}</p>
{{ end }}
{{template "footer" .}}

View File

@ -10,18 +10,18 @@ table.classy.export .disabled, table.classy.export a {
<div class="snug content-container">
{{template "admin-header" .}}
<h2 id="posts-header" style="display: flex; justify-content: space-between;">Pages</h2>
<h2 id="posts-header" style="display: flex; justify-content: space-between;">{{call .Tr "Page" 2}}</h2>
<table class="classy export" style="width:100%">
<tr>
<th>Page</th>
<th>Last Modified</th>
<th>{{call .Tr "Page"}}</th>
<th>{{call .Tr "last modified"}}</th>
</tr>
<tr>
<td colspan="2"><a href="/admin/page/landing">Home</a></td>
<td colspan="2"><a href="/admin/page/landing">{{call .Tr "Home"}}</a></td>
</tr>
{{if .LocalTimeline}}<tr>
<td colspan="2"><a href="/admin/page/reader">Reader</a></td>
<td colspan="2"><a href="/admin/page/reader">{{call .Tr "Reader"}}</a></td>
</tr>{{end}}
{{range .Pages}}
<tr>

View File

@ -11,23 +11,24 @@
</p>
{{end}}
<div class="row admin-actions" style="justify-content: space-between;">
<span style="font-style: italic; font-size: 1.2em">{{.TotalUsers}} {{pluralize "user" "users" .TotalUsers}}</span>
<a class="btn cta" href="/me/invites">+ Invite people</a>
{{ $T := .TotalUsers }}{{ if eq .AppCfg.Lang "eu_ES" }}{{ $T = 1}}{{ end }}
<span style="font-style: italic; font-size: 1.2em">{{.TotalUsers}} {{call .Tr "User" $T}}</span>
<a class="btn cta" href="/me/invites">+ {{call .Tr "Invite people"}}</a>
</div>
<table class="classy export" style="width:100%">
<tr>
<th>User</th>
<th>Joined</th>
<th>Type</th>
<th>Status</th>
<th>{{call .Tr "User"}}{{ if eq .AppCfg.Lang "eu_ES" }}a{{end}}</th>
<th>{{call .Tr "joined"}}</th>
<th>{{call .Tr "type"}}</th>
<th>{{call .Tr "status"}}</th>
</tr>
{{range .Users}}
<tr>
<td><a href="/admin/user/{{.Username}}">{{.Username}}</a></td>
<td>{{.CreatedFriendly}}</td>
<td style="text-align:center">{{if .IsAdmin}}Admin{{else}}User{{end}}</td>
<td style="text-align:center">{{if .IsSilenced}}Silenced{{else}}Active{{end}}</td>
<td><time datetime="{{.CreatedFriendly}}" content="{{.CreatedFriendly}}"></td>
<td style="text-align:center">{{if .IsAdmin}}Admin{{else}}{{call $.Tr "User"}}{{ if eq $.AppCfg.Lang "eu_ES" }}a{{end}}{{end}}</td>
<td style="text-align:center">{{if .IsSilenced}}{{call $.Tr "Silenced"}}{{else}}{{call $.Tr "Active"}}{{end}}</td>
</tr>
{{end}}
</table>
@ -37,6 +38,7 @@
</nav>
</div>
<script src="/js/localdate.js"></script>
{{template "footer" .}}
{{end}}

View File

@ -25,16 +25,16 @@ input[type=text] {
<div class="snug content-container">
{{template "admin-header" .}}
<h2 id="posts-header">{{if eq .Content.ID "landing"}}Home page{{else}}{{.Content.ID}} page{{end}}</h2>
<h2 id="posts-header">{{if eq .Content.ID "landing"}}{{call .Tr "Home"}} {{call .Tr "Page"}}{{else}}{{.Content.Title.String}} {{call .Tr "Page"}}{{end}}</h2>
{{if eq .Content.ID "about"}}
<p class="page-desc content-desc">Describe what your instance is <a href="/about" target="page">about</a>.</p>
<p class="page-desc content-desc">{{call .Tr "Describe what your instance is %s." true (variables "About;/about") }}</p>
{{else if eq .Content.ID "privacy"}}
<p class="page-desc content-desc">Outline your <a href="/privacy" target="page">privacy policy</a>.</p>
<p class="page-desc content-desc">{{call .Tr "Outline your %s." true (variables "Privacy Policy;/privacy") }}</p>
{{else if eq .Content.ID "reader"}}
<p class="page-desc content-desc">Customize your <a href="/read" target="page">Reader</a> page.</p>
<p class="page-desc content-desc">{{call .Tr "Customize your %s page." true (variables "Reader;/read") }}</p>
{{else if eq .Content.ID "landing"}}
<p class="page-desc content-desc">Customize your <a href="/?landing=1" target="page">home page</a>.</p>
<p class="page-desc content-desc">{{call .Tr "Customize your %s page." true (variables "Home;/landing=1") }}</p>
{{end}}
{{if .Message}}<p>{{.Message}}</p>{{end}}
@ -42,25 +42,25 @@ input[type=text] {
<form method="post" action="/admin/update/{{.Content.ID}}" onsubmit="savePage(this)">
{{if .Banner}}
<label for="banner">
Banner
{{call .Tr "Banner"}}
</label>
<textarea id="banner" class="section codable norm edit-page" style="min-height: 5em; height: 5em;" name="banner">{{.Banner.Content}}</textarea>
<p class="content-desc">We suggest a header (e.g. <code># Welcome</code>), optionally followed by a small bit of text. Accepts Markdown and HTML.</p>
<p class="content-desc">{{call .Tr "We suggest a header (e.g. `# Welcome`), optionally followed by a small bit of text. Accepts Markdown and HTML." true}}</p>
{{else}}
<label for="title">
Title
{{call .Tr "Title"}}
</label>
<input type="text" name="title" id="title" value="{{.Content.Title.String}}" />
{{end}}
<label for="content">
{{if .Banner}}Body{{else}}Content{{end}}
{{if .Banner}}{{call .Tr "Body"}}{{else}}{{call .Tr "Content"}}{{end}}
</label>
<textarea id="content" class="section codable norm edit-page" name="content">{{.Content.Content}}</textarea>
<p class="content-desc">Accepts Markdown and HTML.</p>
<p class="content-desc">{{call .Tr "Accepts Markdown and HTML."}}</p>
<input type="submit" value="Save" />
<input type="submit" value="{{call .Tr "Save"}}" />
</form>
</div>
@ -68,7 +68,7 @@ input[type=text] {
<script>
function savePage(el) {
var $btn = el.querySelector('input[type=submit]');
$btn.value = 'Saving...';
$btn.value = '{{call .Tr "Saving..."}}';
$btn.disabled = true;
}
</script>

View File

@ -43,102 +43,102 @@ input.copy-text {
<h2 id="posts-header">{{.User.Username}}</h2>
{{if .NewPassword}}<div class="alert success">
<p>This user's password has been reset to:</p>
<p>{{call .Tr "This user's password has been reset to:"}}</p>
<p><input type="text" class="copy-text" value="{{.NewPassword}}" onfocus="if (this.select) this.select(); else this.setSelectionRange(0, this.value.length);" readonly /></p>
<p>They can use this new password to log in to their account. <strong>This will only be shown once</strong>, so be sure to copy it and send it to them now.</p>
{{if .ClearEmail}}<p>Their email address is: <a href="mailto:{{.ClearEmail}}">{{.ClearEmail}}</a></p>{{end}}
<p>{{call .Tr "They can use this new password to log in to their account. **This will only be shown once**, so be sure to copy it and send it to them now." true}}</p>
{{if .ClearEmail}}<p>{{call .Tr "Their email address is:"}} <a href="mailto:{{.ClearEmail}}">{{.ClearEmail}}</a></p>{{end}}
</div>
{{end}}
<table class="classy export">
<tr>
<th>No.</th>
<th>{{call .Tr "No."}}</th>
<td>{{.User.ID}}</td>
</tr>
<tr>
<th>Type</th>
<td>{{if .User.IsAdmin}}Admin{{else}}User{{end}}</td>
<th>{{call .Tr "type"}}</th>
<td>{{if .User.IsAdmin}}Admin{{else}}{{call .Tr "User"}}{{ if eq .AppCfg.Lang "eu_ES" }}a{{end}}{{end}}</td>
</tr>
<tr>
<th>Username</th>
<th>{{call .Tr "Username"}}</th>
<td>{{.User.Username}}</td>
</tr>
<tr>
<th>Joined</th>
<td>{{.User.CreatedFriendly}}</td>
<th>{{call .Tr "joined"}}</th>
<td><time datetime="{{.User.CreatedFriendly}}" content="{{.User.CreatedFriendly}}"></td>
</tr>
<tr>
<th>Total Posts</th>
<th>{{call .Tr "total posts"}}</th>
<td>{{.TotalPosts}}</td>
</tr>
<tr>
<th>Last Post</th>
<td>{{if .LastPost}}{{.LastPost}}{{else}}Never{{end}}</td>
<th>{{call .Tr "last post"}}</th>
<td>{{if .LastPost}}<time datetime="{{.LastPost}}" content="{{.LastPost}}">{{else}}{{call .Tr "Never"}}{{end}}</td>
</tr>
<tr>
<form action="/admin/user/{{.User.Username}}/status" method="POST" {{if not .User.IsSilenced}}onsubmit="return confirmSilence()"{{end}}>
<th><a id="status"></a>Status</th>
<th><a id="status"></a>{{call .Tr "status"}}</th>
<td class="active-silence">
{{if .User.IsSilenced}}
<p>Silenced</p>
<input type="submit" value="Unsilence"/>
<p>{{call .Tr "Silenced"}}</p>
<input type="submit" value="{{call .Tr "Unsilence"}}"/>
{{else}}
<p>Active</p>
<input class="danger" type="submit" value="Silence" {{if .User.IsAdmin}}disabled{{end}}/>
<p>{{call .Tr "Active"}}</p>
<input class="danger" type="submit" value="{{call .Tr "Silence"}}" {{if .User.IsAdmin}}{{call .Tr "disabled"}}{{end}}/>
{{end}}
</td>
</form>
</tr>
<tr>
<th>Password</th>
<th>{{call .Tr "Password"}}</th>
<td>
{{if ne .Username .User.Username}}
<form id="reset-form" action="/admin/user/{{.User.Username}}/passphrase" method="post" autocomplete="false">
<input type="hidden" name="user" value="{{.User.ID}}"/>
<button type="submit">Reset</button>
<button type="submit">{{call .Tr "Reset"}}</button>
</form>
{{else}}
<a href="/me/settings" title="Go to reset password page">Change your password</a>
<a href="/me/settings" title='{{call .Tr "Go to reset password page"}}'>{{call .Tr "Change your password"}}</a>
{{end}}
</td>
</tr>
</table>
<h2>Blogs</h2>
<h2>{{call .Tr "Blog" 2}}</h2>
{{range .Colls}}
<h3><a href="/{{.Alias}}/">{{.Title}}</a></h3>
<table class="classy export">
<tr>
<th>Alias</th>
<th>{{call $.Tr "Alias"}}</th>
<td>{{.Alias}}</td>
</tr>
<tr>
<th>Title</th>
<th>{{call $.Tr "Title"}}</th>
<td>{{.Title}}</td>
</tr>
<tr>
<th>Description</th>
<th>{{call $.Tr "Description"}}</th>
<td>{{.Description}}</td>
</tr>
<tr>
<th>Visibility</th>
<td>{{.FriendlyVisibility}}</td>
<th>{{call $.Tr "Visibility"}}</th>
<td>{{call $.Tr .FriendlyVisibility}}</td>
</tr>
<tr>
<th>Views</th>
<th>{{call $.Tr "View" 2}}</th>
<td>{{.Views}}</td>
</tr>
<tr>
<th>Posts</th>
<th>{{call $.Tr "Post" 2}}</th>
<td>{{.TotalPosts}}</td>
</tr>
<tr>
<th>Last Post</th>
<td>{{if .LastPost}}{{.LastPost}}{{else}}Never{{end}}</td>
<th>{{call $.Tr "last post"}}</th>
<td>{{if .LastPost}}<time datetime="{{.LastPost}}" content="{{.LastPost}}">{{else}}{{call .Tr "Never"}}{{end}}</td>
</tr>
{{if $.Config.Federation}}
<tr>
<th>Fediverse Followers</th>
<th>{{call $.Tr "Fediverse followers"}}</th>
<td>{{.Followers}}</td>
</tr>
{{end}}
@ -146,38 +146,39 @@ input.copy-text {
{{end}}
{{ if not .User.IsAdmin }}
<h2>Incinerator</h2>
<h2>{{call .Tr "Incinerator"}}</h2>
<div class="alert danger">
<div class="row">
<div>
<h3>Delete this user</h3>
<p>Permanently erase all user data, with no way to recover it.</p>
<h3>{{call .Tr "Delete this user"}}</h3>
<p>{{call .Tr "Permanently erase all user data, with no way to recover it."}}</p>
</div>
<button class="cta danger" onclick="prepareDeleteUser()">Delete this user...</button>
<button class="cta danger" onclick="prepareDeleteUser()">{{call .Tr "Delete this user"}}...</button>
</div>
</div>
{{end}}
</div>
<div id="modal-delete-user" class="modal">
<h2>Are you sure?</h2>
<h2>{{call .Tr "Are you sure?"}}</h2>
<div class="body">
<p style="text-align:left">This action <strong>cannot</strong> be undone. It will permanently erase all traces of this user, <strong>{{.User.Username}}</strong>, including their account information, blogs, and posts.</p>
<p>Please type <strong>{{.User.Username}}</strong> to confirm.</p>
<p style="text-align:left">{{call .Tr "This action **cannot**be undone. It will permanently erase all traces of this user, **%s**, including their account information, blogs, and posts." true (variables .User.Username)}}</p>
<p>{{call .Tr "Please type **%s** to confirm." true (variables .User.Username)}}</p>
<ul id="delete-errors" class="errors"></ul>
<form action="/admin/user/{{.User.Username}}/delete" method="post" onsubmit="confirmDeletion()">
<input id="confirm-text" placeholder="{{.User.Username}}" type="text" class="confirm boxy" name="confirm-username" style="margin-top: 0.5em;" />
<div style="text-align:right; margin-top: 1em;">
<a id="cancel-delete" style="margin-right:2em" href="#">Cancel</a>
<input class="danger" type="submit" id="confirm-delete" value="Delete this user" disabled />
<a id="cancel-delete" style="margin-right:2em" href="#">{{call .Tr "Cancel"}}</a>
<input class="danger" type="submit" id="confirm-delete" value='{{call .Tr "Delete this user"}}' disabled />
</div>
</div>
</div>
<script src="/js/h.js"></script>
<script src="/js/modals.js"></script>
<script src="/js/localdate.js"></script>
<script type="text/javascript">
H.getEl('cancel-delete').on('click', closeModals);
@ -195,17 +196,17 @@ function prepareDeleteUser() {
function confirmDeletion() {
$confirmDelBtn.disabled = true
$confirmDelBtn.value = 'Deleting...'
$confirmDelBtn.value = {{call .Tr "Deleting..."}}
}
function confirmSilence() {
return confirm("Silence this user? They'll still be able to log in and access their posts, but no one else will be able to see them anymore. You can reverse this decision at any time.");
return confirm({{call .Tr "Silence this user? They'll still be able to log in and access their posts, but no one else will be able to see them anymore. You can reverse this decision at any time."}});
}
form = document.getElementById("reset-form");
form.addEventListener('submit', function(e) {
e.preventDefault();
agreed = confirm("Reset this user's password? This will generate a new temporary password that you'll need to share with them, and invalidate their old one.");
agreed = confirm({{call .Tr "Reset this user's password? This will generate a new temporary password that you'll need to share with them, and invalidate their old one."}});
if (agreed === true) {
form.submit();
}

View File

@ -8,6 +8,9 @@
#move-tmpl {
display: none;
}
a.action {
text-transform: lowercase;
}
</style>
<div class="snug content-container">
@ -16,49 +19,50 @@
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}
</ul>{{end}}
{{if .Silenced}}
{{template "user-silenced"}}
{{template "user-silenced" (dict "Tr" $.Tr)}}
{{end}}
<h1 id="posts-header">Drafts</h1>
<h1 id="posts-header">{{call .Tr "Draft" 2}}</h1>
{{ if .AnonymousPosts }}
<p>These are your draft posts. You can share them individually (without a blog) or move them to your blog when you're ready.</p>
<p>{{call .Tr "These are your draft posts. You can share them individually (without a blog) or move them to your blog when you're ready."}}</p>
<div id="anon-posts" class="atoms posts">
{{ range $el := .AnonymousPosts }}<div id="post-{{.ID}}" class="post">
<h3><a href="/{{if $.SingleUser}}d/{{end}}{{.ID}}" itemprop="url">{{.DisplayTitle}}</a></h3>
<h4>
<date datetime="{{.Created}}" pubdate itemprop="datePublished" content="{{.Created}}">{{.DisplayDate}}</date>
<a class="action" href="/{{if $.SingleUser}}d/{{end}}{{.ID}}/edit">edit</a>
<a class="delete action" href="/{{.ID}}" onclick="delPost(event, '{{.ID}}', true)">delete</a>
<a class="action" href="/{{if $.SingleUser}}d/{{end}}{{.ID}}/edit">{{call $.Tr "Edit"}}</a>
<a class="delete action" href="/{{.ID}}" onclick="delPost(event, '{{.ID}}', true, {{$.Locales}})">{{call $.Tr "Delete"}}</a>
{{ if $.Collections }}
{{if gt (len $.Collections) 1}}<div class="action flat-select">
<select id="move-{{.ID}}" onchange="postActions.multiMove(this, '{{.ID}}', {{if $.SingleUser}}true{{else}}false{{end}})" title="Move this post to one of your blogs">
<select id="move-{{.ID}}" onchange="postActions.multiMove(this, '{{.ID}}', {{if $.SingleUser}}true{{else}}false{{end}})" title={{call .Tr "Move this post to one of your blogs"}}>
<option style="display:none"></option>
{{range $.Collections}}<option value="{{.Alias}}">{{.DisplayTitle}}</option>{{end}}
</select>
<label for="move-{{.ID}}">move to...</label>
<label for="move-{{.ID}}">{{call .Tr "move to..."}}</label>
<img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" />
</div>{{else}}
{{range $.Collections}}
<a class="action" href="/{{$el.ID}}" title="Publish this post to your blog '{{.DisplayTitle}}'" onclick="postActions.move(this, '{{$el.ID}}', '{{.Alias}}', {{if $.SingleUser}}true{{else}}false{{end}});return false">move to {{.DisplayTitle}}</a>
<a class="action" href="/{{$el.ID}}" title={{call $.Tr "Publish this post to your blog %s" true (variables .DisplayTitle)}} onclick="postActions.move(this, '{{$el.ID}}', '{{.Alias}}', {{if $.SingleUser}}true{{else}}false{{end}}, {{$.Locales}});return false">{{call $.Tr "move to %s" (variables .DisplayTitle)}}</a>
{{end}}
{{end}}
{{ end }}
</h4>
{{if .Summary}}<p>{{.SummaryHTML}}</p>{{end}}
</div>{{end}}
<script src="/js/localdate.js"></script>
</div>
{{if eq (len .AnonymousPosts) 10}}<p id="load-more-p"><a href="#load">Load more...</a></p>{{end}}
{{if eq (len .AnonymousPosts) 10}}<p id="load-more-p"><a href="#load">{{call .Tr "Load more..."}}</a></p>{{end}}
{{ else }}<div id="no-posts-published">
<p>Your anonymous and draft posts will show up here once you've published some. You'll be able to share them individually (without a blog) or move them to a blog when you're ready.</p>
{{if not .SingleUser}}<p>Alternatively, see your blogs and their posts on your <a href="/me/c/">Blogs</a> page.</p>{{end}}
<p>{{call .Tr "Your anonymous and draft posts will show up here once you've published some. You'll be able to share them individually (without a blog) or move them to a blog when you're ready."}}</p>
{{if not .SingleUser}}<p>{{call .Tr "Alternatively, see your blogs and their posts on your %s page." true (variables true "Blog;/me/c/")}}</p>{{end}}
<p class="text-cta"><a href="{{if .SingleUser}}/me/new{{else}}/{{end}}">Start writing</a></p></div>{{ end }}
<p class="text-cta"><a href="{{if .SingleUser}}/me/new{{else}}/{{end}}">{{call .Tr "Start writing"}}</a></p></div>{{ end }}
<div id="moving"></div>
<h2 id="unsynced-posts-header" style="display: none">unsynced posts</h2>
<h2 id="unsynced-posts-header" style="display: none">{{call .Tr "unsynced posts"}}</h2>
<div id="unsynced-posts-info" style="margin-top: 1em"></div>
<div id="unsynced-posts" class="atoms"></div>
@ -68,16 +72,16 @@
<div id="move-tmpl">
{{if gt (len .Collections) 1}}
<div class="action flat-select">
<select id="move-POST_ID" onchange="postActions.multiMove(this, 'POST_ID', {{if .SingleUser}}true{{else}}false{{end}})" title="Move this post to one of your blogs">
<select id="move-POST_ID" onchange="postActions.multiMove(this, 'POST_ID', {{if .SingleUser}}true{{else}}false{{end}}, {{$.Locales}})" title={{call .Tr "Move this post to one of your blogs"}}>
<option style="display:none"></option>
{{range .Collections}}<option value="{{.Alias}}">{{.DisplayTitle}}</option>{{end}}
</select>
<label for="move-POST_ID">move to...</label>
<label for="move-POST_ID">{{call .Tr "move to..."}}</label>
<img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" />
</div>
{{else}}
{{range .Collections}}
<a class="action" href="/POST_ID" title="Publish this post to your blog '{{.DisplayTitle}}'" onclick="postActions.move(this, 'POST_ID', '{{.Alias}}', {{if $.SingleUser}}true{{else}}false{{end}});return false">move to {{.DisplayTitle}}</a>
<a class="action" href="/POST_ID" title={{call $.Tr "Publish this post to your blog %s" (variables .DisplayTitle)}} onclick="postActions.move(this, 'POST_ID', '{{.Alias}}', {{if $.SingleUser}}true{{else}}false{{end}}, {{$.Locales}});return false">{{call $.Tr "move to %s" (variables .DisplayTitle)}}</a>
{{end}}
{{end}}
</div>

View File

@ -20,14 +20,14 @@ textarea.section.norm {
<div id="overlay"></div>
{{if .Silenced}}
{{template "user-silenced"}}
{{template "user-silenced" (dict "Tr" $.Tr)}}
{{end}}
{{template "collection-breadcrumbs" .}}
<h1>Customize</h1>
<h1>{{(call .Tr "Customize")}}</h1>
{{template "collection-nav" (dict "Alias" .Alias "Path" .Path "SingleUser" .SingleUser)}}
{{template "collection-nav" (dict "Alias" .Alias "Path" .Path "SingleUser" .SingleUser "Tr" $.Tr)}}
{{if .Flashes}}<ul class="errors">
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}
@ -36,14 +36,14 @@ textarea.section.norm {
<form name="customize-form" action="/api/collections/{{.Alias}}" method="post" onsubmit="return disableSubmit()">
<div id="collection-options">
<div style="text-align:center">
<h1><input type="text" name="title" id="title" value="{{.DisplayTitle}}" placeholder="Title" /></h1>
<p><input type="text" name="description" id="description" value="{{.Description}}" placeholder="Description" /></p>
<h1><input type="text" name="title" id="title" value="{{.DisplayTitle}}" placeholder={{call .Tr "Title"}} /></h1>
<p><input type="text" name="description" id="description" value="{{.Description}}" placeholder={{call .Tr "Description"}} /></p>
</div>
<div class="option">
<h2><a name="preferred-url"></a>URL</h2>
<div class="section">
{{if eq .Alias .Username}}<p style="font-size: 0.8em">This blog uses your username in its URL{{if .Federation}} and fediverse handle{{end}}. You can change it in your <a href="/me/settings">Account Settings</a>.</p>{{end}}
{{if eq .Alias .Username}}<p style="font-size: 0.8em">{{if .Federation}}{{call .Tr "This blog uses your username in its URL and fediverse handle."}}{{else}}{{call .Tr "This blog uses your username in its URL."}}{{end}} {{call .Tr "You can change it in your %s." true (variables "Account Settings;/me/settings")}}</p>{{end}}
<ul style="list-style:none">
<li>
{{.FriendlyHost}}/<strong>{{.Alias}}</strong>/
@ -56,34 +56,34 @@ textarea.section.norm {
</div>
<div class="option">
<h2>Publicity</h2>
<h2>{{call .Tr "Publicity"}}</h2>
<div class="section">
<ul style="list-style:none">
<li>
<label><input type="radio" name="visibility" id="visibility-unlisted" value="0" {{if .IsUnlisted}}checked="checked"{{end}} />
Unlisted
{{call .Tr "Unlisted"}}
</label>
<p>This blog is visible to {{if .Private}}any registered user on this instance{{else}}anyone with its link{{end}}.</p>
<p>{{if .Private}}{{call .Tr "This blog is visible to any registered user on this instance."}}{{else}}{{call .Tr "This blog is visible to anyone with its link."}}{{end}}</p>
</li>
<li>
<label class="option-text"><input type="radio" name="visibility" id="visibility-private" value="2" {{if .IsPrivate}}checked="checked"{{end}} />
Private
{{call .Tr "Private"}}
</label>
<p>Only you may read this blog (while you're logged in).</p>
<p>{{call .Tr "Only you may read this blog (while you're logged in)."}}</p>
</li>
<li>
<label class="option-text"><input type="radio" name="visibility" id="visibility-protected" value="4" {{if .IsProtected}}checked="checked"{{end}} />
Password-protected: <input type="password" class="low-profile" name="password" id="collection-pass" autocomplete="new-password" placeholder="{{if .IsProtected}}xxxxxxxxxxxxxxxx{{else}}a memorable password{{end}}" />
{{call .Tr "Password-protected:"}} <input type="password" class="low-profile" name="password" id="collection-pass" autocomplete="new-password" placeholder="{{if .IsProtected}}xxxxxxxxxxxxxxxx{{else}}{{call .Tr "a memorable password"}}{{end}}" />
</label>
<p>A password is required to read this blog.</p>
<p>{{call .Tr "A password is required to read this blog."}}</p>
</li>
{{if not .SingleUser}}
<li>
<label class="option-text{{if not .LocalTimeline}} disabled{{end}}"><input type="radio" name="visibility" id="visibility-public" value="1" {{if .IsPublic}}checked="checked"{{end}} {{if not .LocalTimeline}}disabled="disabled"{{end}} />
Public
{{call .Tr "Public"}}
</label>
{{if .LocalTimeline}}<p>This blog is displayed on the public <a href="/read">reader</a>, and is visible to {{if .Private}}any registered user on this instance{{else}}anyone with its link{{end}}.</p>
{{else}}<p>The public reader is currently turned off for this community.</p>{{end}}
{{if .LocalTimeline}}<p>{{if .Private}}{{call .Tr "This blog is displayed on the public %s, and is visible to any registered user on this instance." true (variables "Reader;/read")}}{{else}}{{call .Tr "This blog is displayed on the public %s, and is visible to any registered user on this instance." true (variables "Reader;/read")}}{{end}}</p>
{{else}}<p>{{call .Tr "The public reader is currently turned off for this community."}}</p>{{end}}
</li>
{{end}}
</ul>
@ -91,37 +91,37 @@ textarea.section.norm {
</div>
<div class="option">
<h2>Display Format</h2>
<h2>{{call .Tr "Display Format"}}</h2>
<div class="section">
<p class="explain">Customize how your posts display on your page.
<p class="explain">{{call .Tr "Customize how your posts display on your page."}}
</p>
<ul style="list-style:none">
<li>
<label><input type="radio" name="format" id="format-blog" value="blog" {{if or (not .Format) (eq .Format "blog")}}checked="checked"{{end}} />
Blog
</label>
<p>Dates are shown. Latest posts listed first.</p>
<p>{{call .Tr "Dates are shown. Latest posts listed first."}}</p>
</li>
<li>
<label class="option-text"><input type="radio" name="format" id="format-novel" value="novel" {{if eq .Format "novel"}}checked="checked"{{end}} />
Novel
</label>
<p>No dates shown. Oldest posts first.</p>
<p>{{call .Tr "No dates shown. Oldest posts first."}}</p>
</li>
<li>
<label class="option-text"><input type="radio" name="format" id="format-notebook" value="notebook" {{if eq .Format "notebook"}}checked="checked"{{end}} />
Notebook
</label>
<p>No dates shown. Latest posts first.</p>
<p>{{call .Tr "No dates shown. Latest posts first."}}</p>
</li>
</ul>
</div>
</div>
<div class="option">
<h2>Text Rendering</h2>
<h2>{{call .Tr "Text Rendering"}}</h2>
<div class="section">
<p class="explain">Customize how plain text renders on your blog.</p>
<p class="explain">{{call .Tr "Customize how plain text renders on your blog."}}</p>
<ul style="list-style:none">
<li>
<label class="option-text disabled"><input type="checkbox" name="markdown" checked="checked" disabled />
@ -138,52 +138,52 @@ textarea.section.norm {
</div>
<div class="option">
<h2>Custom CSS</h2>
<h2>{{call .Tr "Custom CSS"}}</h2>
<div class="section">
<textarea id="css-editor" class="section codable" name="style_sheet">{{.StyleSheet}}</textarea>
<p class="explain">See our guide on <a href="https://guides.write.as/customizing/#custom-css">customization</a>.</p>
<p class="explain">{{call .Tr "See our guide on %s." true (variables "customization;https://guides.write.as/customizing/#custom-css")}}</p>
</div>
</div>
<div class="option">
<h2>Post Signature</h2>
<h2>{{call .Tr "Post Signature"}}</h2>
<div class="section">
<p class="explain">This content will be added to the end of every post on this blog, as if it were part of the post itself. Markdown, HTML, and shortcodes are allowed.</p>
<p class="explain">{{call .Tr "This content will be added to the end of every post on this blog, as if it were part of the post itself. Markdown, HTML, and shortcodes are allowed."}}</p>
<textarea id="signature" class="section norm" name="signature">{{.Signature}}</textarea>
</div>
</div>
{{if .UserPage.StaticPage.AppCfg.Monetization}}
<div class="option">
<h2>Web Monetization</h2>
<h2>{{call .Tr "Web Monetization"}}</h2>
<div class="section">
<p class="explain">Web Monetization enables you to receive micropayments from readers that have a <a href="https://coil.com">Coil membership</a>. Add your payment pointer to enable Web Monetization on your blog.</p>
<p class="explain">{{call .Tr "Web Monetization enables you to receive micropayments from readers that have a %s. Add your payment pointer to enable Web Monetization on your blog." true (variables "Coil membership;https://coil.com")}}</p>
<input type="text" name="monetization_pointer" style="width:100%" value="{{.Collection.Monetization}}" placeholder="$wallet.example.com/alice" />
</div>
</div>
{{end}}
<div class="option" style="text-align: center; margin-top: 4em;">
<input type="submit" id="save-changes" value="Save changes" />
<p><a href="{{if .SingleUser}}/{{else}}/{{.Alias}}/{{end}}">View Blog</a></p>
{{if ne .Alias .Username}}<p><a class="danger" href="#modal-delete" onclick="promptDelete();">Delete Blog...</a></p>{{end}}
<input type="submit" id="save-changes" value="{{call .Tr "Save changes"}}" />
<p><a href="{{if .SingleUser}}/{{else}}/{{.Alias}}/{{end}}">{{call .Tr "View Blog"}}</a></p>
{{if ne .Alias .Username}}<p><a class="danger" href="#modal-delete" onclick="promptDelete();">{{call .Tr "Delete Blog..."}}</a></p>{{end}}
</div>
</div>
</form>
</div>
<div id="modal-delete" class="modal">
<h2>Are you sure you want to delete this blog?</h2>
<h2>{{call .Tr "Are you sure you want to delete this blog?"}}</h2>
<div class="body short">
<p style="text-align:left">This will permanently erase <strong>{{.DisplayTitle}}</strong> ({{.FriendlyHost}}/{{.Alias}}) from the internet. Any posts on this blog will be saved and made into drafts (found on your <a href="/me/posts/">Drafts</a> page).</p>
<p>If you're sure you want to delete this blog, enter its name in the box below and press <strong>Delete</strong>.</p>
<p style="text-align:left">{{call .Tr "This will permanently erase <strong>{{.DisplayTitle}}</strong> ({{.FriendlyHost}}/{{.Alias}}) from the internet. Any posts on this blog will be saved and made into drafts (found on your %s page)." true (variables true "Draft;/me/posts/")}}</p>
<p>{{call .Tr "If you're sure you want to delete this blog, enter its name in the box below and press **%s**." true (variables "Delete")}}</p>
<ul id="delete-errors" class="errors"></ul>
<input id="confirm-text" placeholder="{{.Alias}}" type="text" class="boxy" style="margin-top: 0.5em;" />
<div style="text-align:right; margin-top: 1em;">
<a id="cancel-delete" style="margin-right:2em" href="#">Cancel</a>
<button id="btn-delete" class="danger" onclick="deleteBlog(); return false;">Delete</button>
<a id="cancel-delete" style="margin-right:2em" href="#">{{call .Tr "Cancel"}}</a>
<button id="btn-delete" class="danger" onclick="deleteBlog(); return false;">{{call .Tr "Delete"}}</button>
</div>
</div>
</div>
@ -195,12 +195,12 @@ textarea.section.norm {
H.getEl('cancel-delete').on('click', closeModals);
var deleteBlog = function(e) {
if (document.getElementById('confirm-text').value != '{{.Alias}}') {
document.getElementById('delete-errors').innerHTML = '<li class="urgent">Enter <strong>{{.Alias}}</strong> in the box below.</li>';
document.getElementById('delete-errors').innerHTML = '<li class="urgent">{{call .Tr "Enter **%s** in the box below." true (variables .Alias)}}</li>';
return;
}
// Clear errors
document.getElementById('delete-errors').innerHTML = '';
document.getElementById('btn-delete').innerHTML = 'Deleting...';
document.getElementById('btn-delete').innerHTML = {{call .Tr "Deleting..."}};
var http = new XMLHttpRequest();
var url = "/api/collections/{{.Alias}}?web=1";
@ -213,7 +213,7 @@ var deleteBlog = function(e) {
} else {
var data = JSON.parse(http.responseText);
document.getElementById('delete-errors').innerHTML = '<li class="urgent">'+data.error_msg+'</li>';
document.getElementById('btn-delete').innerHTML = 'Delete';
document.getElementById('btn-delete').innerHTML = {{call .Tr "Delete"}};
}
}
};
@ -231,7 +231,7 @@ function disableSubmit() {
var $form = document.forms['customize-form'];
createHidden($form, 'style_sheet', cssEditor.getSession().getValue());
var $btn = document.getElementById("save-changes");
$btn.value = "Saving changes...";
$btn.value = {{call .Tr "Saving changes..."}};
$btn.disabled = true;
return true;
}

View File

@ -8,9 +8,9 @@
</ul>{{end}}
{{if .Silenced}}
{{template "user-silenced"}}
{{template "user-silenced" (dict "Tr" $.Tr)}}
{{end}}
<h1>Blogs</h1>
<h1>{{call .Tr "Blog" 2}}</h1>
<ul class="atoms collections">
{{range $i, $el := .Collections}}<li class="collection">
<div class="row lineitem">
@ -19,7 +19,7 @@
<a class="title" href="/{{.Alias}}/" >{{if .Title}}{{.Title}}{{else}}{{.Alias}}{{end}}</a>
<span class="electron" {{if .IsPrivate}}style="font-style: italic"{{end}}>{{if .IsPrivate}}private{{else}}{{.DisplayCanonicalURL}}{{end}}</span>
</h3>
{{template "collection-nav" (dict "Alias" .Alias "Path" $.Path "SingleUser" $.SingleUser "CanPost" true )}}
{{template "collection-nav" (dict "Alias" .Alias "Path" $.Path "SingleUser" $.SingleUser "CanPost" true "Tr" $.Tr)}}
{{if .Description}}<p class="description">{{.Description}}</p>{{end}}
</div>
</div>
@ -69,10 +69,10 @@ function createCollection() {
if ($err === null) {
var url = He.create('span');
url.className = "error";
url.innerText = "This name is taken.";
url.innerText = {{call .Tr "This name is taken."}};
$createColl.appendChild(url);
} else {
$err.innerText = "This name is taken.";
$err.innerText = {{call .Tr "This name is taken."}};
}
} else {
if ($err === null) {
@ -81,7 +81,7 @@ function createCollection() {
url.innerText = data.error_msg;
$createColl.appendChild(url);
} else {
$err.innerText = "This name is taken.";
$err.innerText = {{call .Tr "This name is taken."}};
}
}
}

View File

@ -2,21 +2,21 @@
{{template "header" .}}
<div class="snug content-container">
<h1 id="posts-header">Export</h1>
<p>Your data on {{.SiteName}} is always free. Download and back-up your work any time.</p>
<h1 id="posts-header">{{call .Tr "Export"}}</h1>
<p>{{call .Tr "Your data on %s is always free. Download and back-up your work any time." (variables .SiteName)}}</p>
<table class="classy export">
<tr>
<th style="width: 40%">Export</th>
<th colspan="2">Format</th>
<th style="width: 40%">{{call .Tr "Export"}}</th>
<th colspan="2">{{call .Tr "Format"}}</th>
</tr>
<tr>
<th>Posts</th>
<th>{{call .Tr "Post" 2}}</th>
<td><p class="text-cta"><a href="/me/posts/export.csv">CSV</a></p></td>
<td><p class="text-cta"><a href="/me/posts/export.zip">TXT</a></p></td>
</tr>
<tr>
<th>User + Blogs + Posts</th>
<th>{{call .Tr "User"}} + {{call .Tr "Blog" 2}} + {{call .Tr "Post" 2}}</th>
<td><p class="text-cta"><a href="/me/export.json">JSON</a></p></td>
<td><p class="text-cta"><a href="/me/export.json?pretty=1">Prettified</a></p></td>
</tr>

View File

@ -14,7 +14,7 @@
</style>
<div class="snug content-container">
<h1 id="import-header">Import posts</h1>
<h1 id="import-header">{{call .Tr "Import posts"}}</h1>
{{if .Message}}
<div class="alert {{if .InfoMsg}}info{{else}}success{{end}}">
<p>{{.Message}}</p>
@ -25,20 +25,20 @@
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}
</ul>
{{end}}
<p>Publish plain text or Markdown files to your account by uploading them below.</p>
<p>{{call .Tr "Publish plain text or Markdown files to your account by uploading them below."}}</p>
<div class="formContainer">
<form id="importPosts" class="prominent" enctype="multipart/form-data" action="/api/me/import" method="POST">
<label>Select some files to import:
<label>{{call .Tr "Select some files to import:"}}
<input id="fileInput" class="fileInput" name="files" type="file" multiple accept="text/*"/>
</label>
<input id="fileDates" name="fileDates" hidden/>
<label>
Import these posts to:
{{call .Tr "Import these posts to:"}}
<select name="collection">
{{range $i, $el := .Collections}}
<option value="{{.Alias}}" {{if eq $i 0}}selected{{end}}>{{.DisplayTitle}}</option>
{{end}}
<option value="">Drafts</option>
<option value="">{{call .Tr "Draft" 2}}</option>
</select>
</label>
<script>
@ -56,7 +56,7 @@
fileDates.value = JSON.stringify(dateMap);
})
</script>
<input type="submit" value="Import" />
<input type="submit" value={{call .Tr "Import"}} />
</form>
</div>
</div>

View File

@ -8,10 +8,10 @@
<hr />
<nav>
<a class="home" href="/">{{.SiteName}}</a>
{{if not .SingleUser}}<a href="/about">about</a>{{end}}
{{if and (not .SingleUser) .LocalTimeline}}<a href="/read">reader</a>{{end}}
<a href="https://writefreely.org/guide/{{.OfficialVersion}}" target="guide">writer's guide</a>
{{if not .SingleUser}}<a href="/privacy">privacy</a>{{end}}
{{if not .SingleUser}}<a href="/about">{{call .Tr "About"}}</a>{{end}}
{{if and (not .SingleUser) .LocalTimeline}}<a href="/read">{{call .Tr "Reader"}}</a>{{end}}
<a href="https://writefreely.org/guide/{{.OfficialVersion}}" target="guide">{{call .Tr "writer's guide"}}</a>
{{if not .SingleUser}}<a href="/privacy">{{call .Tr "privacy"}}</a>{{end}}
{{if .WFModesty}}
<p style="font-size: 0.9em">powered by <a href="https://writefreely.org">writefreely</a></p>
{{else}}

View File

@ -4,41 +4,41 @@
{{if .SingleUser}}
<nav id="user-nav">
<nav class="dropdown-nav">
<ul><li><a href="/" title="View blog" class="title">{{.SiteName}}</a> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" />
<ul><li><a href="/" title={{call .Tr "View blog"}} class="title">{{.SiteName}}</a> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" />
<ul>
{{if .IsAdmin}}<li><a href="/admin">Admin dashboard</a></li>{{end}}
<li><a href="/me/settings">Account settings</a></li>
<li><a href="/me/import">Import posts</a></li>
<li><a href="/me/export">Export</a></li>
{{if .IsAdmin}}<li><a href="/admin">{{call .Tr "Admin dashboard"}}</a></li>{{end}}
<li><a href="/me/settings">{{call .Tr "Account settings"}}</a></li>
<li><a href="/me/import">{{call .Tr "Import posts"}}</a></li>
<li><a href="/me/export">{{call .Tr "Export"}}</a></li>
<li class="separator"><hr /></li>
<li><a href="/me/logout">Log out</a></li>
<li><a href="/me/logout">{{call .Tr "Log out"}}</a></li>
</ul></li>
</ul>
</nav>
<nav class="tabs">
<a href="/me/c/{{.Username}}" {{if and (hasPrefix .Path "/me/c/") (hasSuffix .Path .Username)}}class="selected"{{end}}>Customize</a>
<a href="/me/c/{{.Username}}/stats" {{if hasSuffix .Path "/stats"}}class="selected"{{end}}>Stats</a>
<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>Drafts</a>
<a href="/me/c/{{.Username}}" {{if and (hasPrefix .Path "/me/c/") (hasSuffix .Path .Username)}}class="selected"{{end}}>{{call .Tr "Customize"}}</a>
<a href="/me/c/{{.Username}}/stats" {{if hasSuffix .Path "/stats"}}class="selected"{{end}}>{{call .Tr "Stats"}}</a>
<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>{{call .Tr "Draft" 2}}</a>
</nav>
</nav>
<div class="right-side">
<a class="simple-btn" href="/me/new">New Post</a>
<a class="simple-btn" href="/me/new">{{call .Tr "New Post"}}</a>
</div>
{{else}}
<div class="left-side">
<h1><a href="/" title="Return to editor">{{.SiteName}}</a></h1>
<h1><a href="/" title={{call .Tr "Return to editor"}}>{{.SiteName}}</a></h1>
</div>
<nav id="user-nav">
{{if .Username}}
<nav class="dropdown-nav">
<ul><li class="has-submenu"><a>{{.Username}}</a> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /><ul>
{{if .IsAdmin}}<li><a href="/admin">Admin dashboard</a></li>{{end}}
<li><a href="/me/settings">Account settings</a></li>
<li><a href="/me/import">Import posts</a></li>
<li><a href="/me/export">Export</a></li>
{{if .CanInvite}}<li><a href="/me/invites">Invite people</a></li>{{end}}
{{if .IsAdmin}}<li><a href="/admin">{{call .Tr "Admin dashboard"}}</a></li>{{end}}
<li><a href="/me/settings">{{call .Tr "Account settings"}}</a></li>
<li><a href="/me/import">{{call .Tr "Import posts"}}</a></li>
<li><a href="/me/export">{{call .Tr "Export"}}</a></li>
{{if .CanInvite}}<li><a href="/me/invites">{{call .Tr "Invite people"}}</a></li>{{end}}
<li class="separator"><hr /></li>
<li><a href="/me/logout">Log out</a></li>
<li><a href="/me/logout">{{call .Tr "Log out"}}</a></li>
</ul></li>
</ul>
</nav>
@ -46,29 +46,29 @@
<nav class="tabs">
{{if .SimpleNav}}
{{ if not .SingleUser }}
{{if and (and .LocalTimeline .CanViewReader) .Chorus}}<a href="/"{{if eq .Path "/"}} class="selected"{{end}}>Home</a>{{end}}
{{if and (and .LocalTimeline .CanViewReader) .Chorus}}<a href="/"{{if eq .Path "/"}} class="selected"{{end}}>{{call .Tr "Home"}}</a>{{end}}
{{ end }}
<a href="/about">About</a>
<a href="/about">{{call .Tr "About"}}</a>
{{ if not .SingleUser }}
{{ if .Username }}
{{if gt .MaxBlogs 1}}<a href="/me/c/"{{if eq .Path "/me/c/"}} class="selected"{{end}}>Blogs</a>{{end}}
{{if and .Chorus (eq .MaxBlogs 1)}}<a href="/{{.Username}}/"{{if eq .Path (printf "/%s/" .Username)}} class="selected"{{end}}>My Posts</a>{{end}}
{{if not .DisableDrafts}}<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>Drafts</a>{{end}}
{{if gt .MaxBlogs 1}}<a href="/me/c/"{{if eq .Path "/me/c/"}} class="selected"{{end}}>{{call .Tr "Blog" 2}}</a>{{end}}
{{if and .Chorus (eq .MaxBlogs 1)}}<a href="/{{.Username}}/"{{if eq .Path (printf "/%s/" .Username)}} class="selected"{{end}}>{{call .Tr "My Posts"}}</a>{{end}}
{{if not .DisableDrafts}}<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>{{call .Tr "Draft" 2}}</a>{{end}}
{{ end }}
{{if and (and .LocalTimeline .CanViewReader) (not .Chorus)}}<a href="/read">Reader</a>{{end}}
{{if and (and (and .Chorus .OpenRegistration) (not .Username)) (or (not .Private) (ne .Landing ""))}}<a href="/signup"{{if eq .Path "/signup"}} class="selected"{{end}}>Sign up</a>{{end}}
{{if .Username}}<a href="/me/logout">Log out</a>{{else}}<a href="/login">Log in</a>{{end}}
{{if and (and .LocalTimeline .CanViewReader) (not .Chorus)}}<a href="/read">{{call .Tr "Reader"}}</a>{{end}}
{{if and (and (and .Chorus .OpenRegistration) (not .Username)) (or (not .Private) (ne .Landing ""))}}<a href="/signup"{{if eq .Path "/signup"}} class="selected"{{end}}>{{call .Tr "Sign up"}}</a>{{end}}
{{if .Username}}<a href="/me/logout">{{call .Tr "Log out"}}</a>{{else}}<a href="/login">{{call .Tr "Log in"}}</a>{{end}}
{{ end }}
{{else}}
<a href="/me/c/"{{if eq .Path "/me/c/"}} class="selected"{{end}}>Blogs</a>
{{if not .DisableDrafts}}<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>Drafts</a>{{end}}
{{if and (and .LocalTimeline .CanViewReader) (not .Chorus)}}<a href="/read">Reader</a>{{end}}
<a href="/me/c/"{{if eq .Path "/me/c/"}} class="selected"{{end}}>{{call .Tr "Blog" 2}}</a>
{{if not .DisableDrafts}}<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>{{call .Tr "Draft" 2}}</a>{{end}}
{{if and (and .LocalTimeline .CanViewReader) (not .Chorus)}}<a href="/read">{{call .Tr "Reader"}}</a>{{end}}
{{end}}
</nav>
</nav>
{{if .Username}}
<div class="right-side">
<a class="simple-btn" href="/{{if .CollAlias}}#{{.CollAlias}}{{end}}">New Post</a>
<a class="simple-btn" href="/{{if .CollAlias}}#{{.CollAlias}}{{end}}">{{call .Tr "New Post"}}</a>
</div>
{{end}}
{{end}}
@ -99,17 +99,17 @@
{{define "admin-header"}}
<header class="admin">
<h1>Admin</h1>
<h1>{{call .Tr "Admin"}}</h1>
<nav id="admin" class="pager">
<a href="/admin" {{if eq .Path "/admin"}}class="selected"{{end}}>Dashboard</a>
<a href="/admin/settings" {{if eq .Path "/admin/settings"}}class="selected"{{end}}>Settings</a>
<a href="/admin" {{if eq .Path "/admin"}}class="selected"{{end}}>{{call .Tr "Dashboard"}}</a>
<a href="/admin/settings" {{if eq .Path "/admin/settings"}}class="selected"{{end}}>{{call .Tr "Settings"}}</a>
{{if not .SingleUser}}
<a href="/admin/users" {{if eq .Path "/admin/users"}}class="selected"{{end}}>Users</a>
<a href="/admin/pages" {{if eq .Path "/admin/pages"}}class="selected"{{end}}>Pages</a>
{{if .UpdateChecks}}<a href="/admin/updates" {{if eq .Path "/admin/updates"}}class="selected"{{end}}>Updates{{if .UpdateAvailable}}<span class="blip">!</span>{{end}}</a>{{end}}
<a href="/admin/users" {{if eq .Path "/admin/users"}}class="selected"{{end}}>{{call .Tr "User" 2}}</a>
<a href="/admin/pages" {{if eq .Path "/admin/pages"}}class="selected"{{end}}>{{call .Tr "Page" 2}}</a>
{{if .UpdateChecks}}<a href="/admin/updates" {{if eq .Path "/admin/updates"}}class="selected"{{end}}>{{call .Tr "Updates"}}{{if .UpdateAvailable}}<span class="blip">!</span>{{end}}</a>{{end}}
{{end}}
{{if not .Forest}}
<a href="/admin/monitor" {{if eq .Path "/admin/monitor"}}class="selected"{{end}}>Monitor</a>
<a href="/admin/monitor" {{if eq .Path "/admin/monitor"}}class="selected"{{end}}>{{call .Tr "Monitor"}}</a>
{{end}}
</nav>
</header>

View File

@ -1,15 +1,15 @@
{{define "collection-breadcrumbs"}}
{{if and .Collection (not .SingleUser)}}<nav id="org-nav"><a href="/me/c/">Blogs</a> / <a class="coll-name" href="/{{.Collection.Alias}}/">{{.Collection.DisplayTitle}}</a></nav>{{end}}
{{if and .Collection (not .SingleUser)}}<nav id="org-nav"><a href="/me/c/">{{call .Tr "Blog" 2}}</a> / <a class="coll-name" href="/{{.Collection.Alias}}/">{{.Collection.DisplayTitle}}</a></nav>{{end}}
{{end}}
{{define "collection-nav"}}
{{if not .SingleUser}}
<header class="admin">
<nav class="pager">
{{if .CanPost}}<a href="{{if .SingleUser}}/me/new{{else}}/#{{.Alias}}{{end}}" class="btn gentlecta">New Post</a>{{end}}
<a href="/me/c/{{.Alias}}" {{if and (hasPrefix .Path "/me/c/") (hasSuffix .Path .Alias)}}class="selected"{{end}}>Customize</a>
<a href="/me/c/{{.Alias}}/stats" {{if hasSuffix .Path "/stats"}}class="selected"{{end}}>Stats</a>
<a href="{{if .SingleUser}}/{{else}}/{{.Alias}}/{{end}}">View Blog &rarr;</a>
{{if .CanPost}}<a href="{{if .SingleUser}}/me/new{{else}}/#{{.Alias}}{{end}}" class="btn gentlecta">{{call .Tr "New Post"}}</a>{{end}}
<a href="/me/c/{{.Alias}}" {{if and (hasPrefix .Path "/me/c/") (hasSuffix .Path .Alias)}}class="selected"{{end}}>{{call .Tr "Customize"}}</a>
<a href="/me/c/{{.Alias}}/stats" {{if hasSuffix .Path "/stats"}}class="selected"{{end}}>{{call .Tr "Stats"}}</a>
<a href="{{if .SingleUser}}/{{else}}/{{.Alias}}/{{end}}">{{call .Tr "View Blog"}} &rarr;</a>
</nav>
</header>
{{end}}

View File

@ -1,5 +1,5 @@
{{define "user-silenced"}}
<div class="alert info">
<p><strong>Your account has been silenced.</strong> You can still access all of your posts and blogs, but no one else can currently see them.</p>
<p><strong>{{call .Tr "Your account has been silenced."}}</strong> {{call .Tr "You can still access all of your posts and blogs, but no one else can currently see them."}}</p>
</div>
{{end}}

View File

@ -10,19 +10,19 @@
}
</style>
<div class="snug content-container">
<h1>Invite to {{.SiteName}}</h1>
<h1>{{call .Tr "Invite to %s" (variables .SiteName)}}</h1>
{{ if .Expired }}
<p style="font-style: italic">This invite link is expired.</p>
<p style="font-style: italic">{{call .Tr "This invite link is expired."}}</p>
{{ else }}
<p>Copy the link below and send it to anyone that you want to join <em>{{ .SiteName }}</em>. You could paste it into an email, instant message, text message, or write it down on paper. Anyone who navigates to this special page will be able to create an account.</p>
<p>{{call .Tr "Copy the link below and send it to anyone that you want to join *%s*. You could paste it into an email, instant message, text message, or write it down on paper. Anyone who navigates to this special page will be able to create an account." true (variables .SiteName)}}</p>
<input class="copy-link" type="text" name="invite-url" value="{{$.Host}}/invite/{{.Invite.ID}}" onfocus="if (this.select) this.select(); else this.setSelectionRange(0, this.value.length);" readonly />
<p>
{{ if gt .Invite.MaxUses.Int64 0 }}
{{if eq .Invite.MaxUses.Int64 1}}Only <strong>one</strong> user{{else}}Up to <strong>{{.Invite.MaxUses.Int64}}</strong> users{{end}} can sign up with this link.
{{if gt .Invite.Uses 0}}So far, <strong>{{.Invite.Uses}}</strong> {{pluralize "person has" "people have" .Invite.Uses}} used it.{{end}}
{{if .Invite.Expires}}It expires on <strong>{{.Invite.ExpiresFriendly}}</strong>.{{end}}
{{if eq .Invite.MaxUses.Int64 1}}{{call .Tr "Only **one** user" true}}{{else}}{{call .Tr "Up to **%s** users" (variables .Invite.MaxUses.Int64)}}{{end}} {{call .Tr "can sign up with this link."}}
{{if gt .Invite.Uses 0}}{{$PLURAL:=false}}{{if gt .Invite.Uses 1}}{{$PLURAL = true}}{{end}}{{call .Tr "So far, **%d** %s used it." true (variables $PLURAL .Invite.Uses "person has")}}{{end}}
{{if .Invite.Expires}}{{call .Tr "It expires on **%s**." true (variables .Invite.ExpiresFriendly)}}{{end}}
{{ else }}
It can be used as many times as you like{{if .Invite.Expires}} before <strong>{{.Invite.ExpiresFriendly}}</strong>, when it expires{{end}}.
{{call .Tr "It can be used as many times as you like"}}{{if .Invite.Expires}} {{call .Tr "before **%s**, when it expires" true (variables .Invite.ExpiresFriendly)}}{{end}}.
{{ end }}
</p>
{{ end }}

View File

@ -21,59 +21,58 @@ table td {
<div class="snug content-container">
{{if .Silenced}}
{{template "user-silenced"}}
{{template "user-silenced" (dict "Tr" $.Tr)}}
{{end}}
<h1>Invite people</h1>
<p>Invite others to join <em>{{.SiteName}}</em> by generating and sharing invite links below.</p>
<h1>{{call .Tr "Invite people"}}</h1>
<p>{{call .Tr "Invite others to join *%s* by generating and sharing invite links below." true (variables .SiteName)}}</p>
<form style="margin: 2em 0" class="prominent" action="/api/me/invites" method="post">
<div class="row">
<div class="half">
<label for="uses">Maximum number of uses:</label>
<label for="uses">{{call .Tr "Maximum number of uses:"}}</label>
<select id="uses" name="uses" {{if .Silenced}}disabled{{end}}>
<option value="0">No limit</option>
<option value="1">1 use</option>
<option value="5">5 uses</option>
<option value="10">10 uses</option>
<option value="25">25 uses</option>
<option value="50">50 uses</option>
<option value="100">100 uses</option>
<option value="0">{{call .Tr "No limit"}}</option>
<option value="1">1 {{call .Tr "use"}}</option>
<option value="5">5 {{call .Tr "use" 2}}</option>
<option value="10">10 {{call .Tr "use" 2}}</option>
<option value="25">25 {{call .Tr "use" 2}}</option>
<option value="50">50 {{call .Tr "use" 2}}</option>
<option value="100">100 {{call .Tr "use" 2}}</option>
</select>
</div>
<div class="half">
<label for="expires">Expire after:</label>
<label for="expires">{{call .Tr "Expire after:"}}</label>
<select id="expires" name="expires" {{if .Silenced}}disabled{{end}}>
<option value="0">Never</option>
<option value="30">30 minutes</option>
<option value="60">1 hour</option>
<option value="360">6 hours</option>
<option value="720">12 hours</option>
<option value="1440">1 day</option>
<option value="4320">3 days</option>
<option value="10080">1 week</option>
<option value="0">{{call .Tr "Never"}}</option>
<option value="30">30 {{call .Tr "minute" 2}}</option>
<option value="60">1 {{call .Tr "hour"}}</option>
<option value="360">6 {{call .Tr "hour" 2}}</option>
<option value="720">12 {{call .Tr "hour" 2}}</option>
<option value="1440">1 {{call .Tr "day"}}</option>
<option value="4320">3 {{call .Tr "day" 2}}</option>
<option value="10080">1 {{call .Tr "week"}}</option>
</select>
</div>
</div>
<div class="row">
<input type="submit" value="Generate" {{if .Silenced}}disabled title="You cannot generate invites while your account is silenced."{{end}} />
<input type="submit" value="{{call .Tr "Generate"}}" {{if .Silenced}}disabled title="{{call .Tr "You cannot generate invites while your account is silenced."}}"{{end}} />
</div>
</form>
<table class="classy export">
<tr>
<th>Link</th>
<th>Uses</th>
<th>Expires</th>
<th>{{call .Tr "Link"}}</th>
<th>{{call .Tr "use" 2}}</th>
<th>{{call .Tr "Expires"}}</th>
</tr>
{{range .Invites}}
<tr>
<td><a href="{{$.Host}}/invite/{{.ID}}">{{$.Host}}/invite/{{.ID}}</a></td>
<td>{{.Uses}}{{if gt .MaxUses.Int64 0}} / {{.MaxUses.Int64}}{{end}}</td>
<td>{{ if .Expires }}{{if .Expired}}Expired{{else}}{{.ExpiresFriendly}}{{end}}{{ else }}&infin;{{ end }}</td>
<td>{{ if .Expires }}{{if .Expired}}{{call .Tr "Expired"}}{{else}}{{.ExpiresFriendly}}{{end}}{{ else }}&infin;{{ end }}</td>
</tr>
{{else}}
<tr>
<td colspan="3">No invites generated yet.</td>
<td colspan="3">{{call .Tr "No invites generated yet."}}</td>
</tr>
{{end}}
</table>

View File

@ -22,28 +22,28 @@ h3 { font-weight: normal; }
<div id="overlay"></div>
{{if .Silenced}}
{{template "user-silenced"}}
{{template "user-silenced" (dict "Tr" $.Tr)}}
{{end}}
<h1>{{if .IsLogOut}}Before you go...{{else}}Account Settings{{end}}</h1>
<h1>{{if .IsLogOut}}{{call .Tr "Before you go..."}}{{else}}{{call .Tr "Account Settings"}}{{end}}</h1>
{{if .Flashes}}<ul class="errors">
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}
</ul>{{end}}
{{ if .IsLogOut }}
<div class="alert info">
<p class="introduction">Please add an <strong>email address</strong> and/or <strong>passphrase</strong> so you can log in again later.</p>
<p class="introduction">{{call .Tr "Please add an **%s** and/or **%s** so you can log in again later." true (variables "email address" "passphrase")}}</p>
</div>
{{ else }}
<div>
<p>Change your account settings here.</p>
<p>{{call .Tr "Change your account settings here."}}</p>
</div>
<form method="post" action="/api/me/self" autocomplete="false">
<div class="option">
<h3>Username</h3>
<h3>{{call .Tr "Username"}}</h3>
<div class="section">
<input type="text" name="username" value="{{.Username}}" tabindex="1" />
<input type="submit" value="Update" style="margin-left: 1em;" />
<input type="submit" value="{{call .Tr "Update"}}" style="margin-left: 1em;" />
</div>
</div>
</form>
@ -53,32 +53,32 @@ h3 { font-weight: normal; }
<form method="post" action="/api/me/self" autocomplete="false">
<input type="hidden" name="logout" value="{{.IsLogOut}}" />
<div class="option">
<h3>Passphrase</h3>
<h3>{{call .Tr "Passphrase"}}</h3>
<div class="section">
{{if and (not .HasPass) (not .IsLogOut)}}<div class="alert info"><p>Add a passphrase to easily log in to your account.</p></div>{{end}}
{{if .HasPass}}<p>Current passphrase</p>
<input type="password" name="current-pass" placeholder="Current passphrase" tabindex="1" /> <input class="show" type="checkbox" id="show-cur-pass" /><label for="show-cur-pass"> Show</label>
<p>New passphrase</p>
{{if and (not .HasPass) (not .IsLogOut)}}<div class="alert info"><p>{{call .Tr "Add a passphrase to easily log in to your account."}}</p></div>{{end}}
{{if .HasPass}}<p>{{call .Tr "Current passphrase"}}</p>
<input type="password" name="current-pass" placeholder="{{call .Tr "Current passphrase"}}" tabindex="1" /> <input class="show" type="checkbox" id="show-cur-pass" /><label for="show-cur-pass"> {{call .Tr "Show"}}</label>
<p>{{call .Tr "New passphrase"}}</p>
{{end}}
{{if .IsLogOut}}<input type="text" value="{{.Username}}" style="display:none" />{{end}}
<input type="password" name="new-pass" autocomplete="new-password" placeholder="New passphrase" tabindex="{{if .IsLogOut}}1{{else}}2{{end}}" /> <input class="show" type="checkbox" id="show-new-pass" /><label for="show-new-pass"> Show</label>
<input type="password" name="new-pass" autocomplete="new-password" placeholder="{{call .Tr "New passphrase"}}" tabindex="{{if .IsLogOut}}1{{else}}2{{end}}" /> <input class="show" type="checkbox" id="show-new-pass" /><label for="show-new-pass"> {{call .Tr "Show"}}</label>
</div>
</div>
<div class="option">
<h3>Email</h3>
<h3>{{call .Tr "Email"}}</h3>
<div class="section">
{{if and (not .Email) (not .IsLogOut)}}<div class="alert info"><p>Add your email to get:</p>
{{if and (not .Email) (not .IsLogOut)}}<div class="alert info"><p>{{call .Tr "Add your email to get:"}}</p>
<ul>
<li>No-passphrase login</li>
<li>Account recovery if you forget your passphrase</li>
<li>{{call .Tr "No-passphrase login"}}</li>
<li>{{call .Tr "Account recovery if you forget your passphrase"}}</li>
</ul></div>{{end}}
<input type="email" name="email" style="letter-spacing: 1px" placeholder="Email address" value="{{.Email}}" size="40" tabindex="{{if .IsLogOut}}2{{else}}3{{end}}" />
<input type="email" name="email" style="letter-spacing: 1px" placeholder="{{call .Tr "Email address"}}" value="{{.Email}}" size="40" tabindex="{{if .IsLogOut}}2{{else}}3{{end}}" />
</div>
</div>
<div class="option" style="text-align: center;">
<input type="submit" value="Save changes" tabindex="4" />
<input type="submit" value="{{call .Tr "Save changes"}}" tabindex="4" />
</div>
</form>
{{end}}
@ -86,8 +86,8 @@ h3 { font-weight: normal; }
{{ if .OauthSection }}
{{ if .OauthAccounts }}
<div class="option">
<h2>Linked Accounts</h2>
<p>These are your linked external accounts.</p>
<h2>{{call .Tr "Linked Accounts"}}</h2>
<p>{{call .Tr "These are your linked external accounts."}}</p>
{{ range $oauth_account := .OauthAccounts }}
<form method="post" action="/api/me/oauth/remove" autocomplete="false">
<input type="hidden" name="provider" value="{{ $oauth_account.Provider }}" />
@ -111,14 +111,14 @@ h3 { font-weight: normal; }
{{ end }}
{{ if or .OauthSlack .OauthWriteAs .OauthGitLab .OauthGeneric .OauthGitea }}
<div class="option">
<h2>Link External Accounts</h2>
<p>Connect additional accounts to enable logging in with those providers, instead of using your username and password.</p>
<h2>{{call .Tr "Link External Accounts"}}</h2>
<p>{{call .Tr "Connect additional accounts to enable logging in with those providers, instead of using your username and password."}}</p>
<div class="row signinbtns">
{{ if .OauthWriteAs }}
<div class="section oauth-provider">
<a class="btn cta loginbtn" id="writeas-login" href="/oauth/write.as?attach=t">
<img src="/img/mark/writeas-white.png" alt="Write.as" />
Link <strong>Write.as</strong>
{{call .Tr "ToLink"}} <strong>Write.as</strong>
</a>
</div>
{{ end }}
@ -126,7 +126,7 @@ h3 { font-weight: normal; }
<div class="section oauth-provider">
<a class="btn cta loginbtn" id="slack-login" href="/oauth/slack?attach=t">
<img src="/img/mark/slack.png" alt="Slack" />
Link <strong>Slack</strong>
{{call .Tr "ToLink"}} <strong>Slack</strong>
</a>
</div>
{{ end }}
@ -134,7 +134,7 @@ h3 { font-weight: normal; }
<div class="section oauth-provider">
<a class="btn cta loginbtn" id="gitlab-login" href="/oauth/gitlab?attach=t">
<img src="/img/mark/gitlab.png" alt="GitLab" />
Link <strong>{{.GitLabDisplayName}}</strong>
{{call .Tr "ToLink"}} <strong>{{.GitLabDisplayName}}</strong>
</a>
</div>
{{ end }}
@ -142,14 +142,14 @@ h3 { font-weight: normal; }
<div class="section oauth-provider">
<a class="btn cta loginbtn" id="gitea-login" href="/oauth/gitea?attach=t">
<img src="/img/mark/gitea.png" alt="Gitea" />
Link <strong>{{.GiteaDisplayName}}</strong>
{{call .Tr "ToLink"}} <strong>{{.GiteaDisplayName}}</strong>
</a>
</div>
{{ end }}
{{ if .OauthGeneric }}
<div class="section oauth-provider">
<a class="btn cta loginbtn" id="generic-oauth-login" href="/oauth/generic?attach=t">
Link <strong>{{ .OauthGenericDisplayName }}</strong>
{{call .Tr "ToLink"}} <strong>{{ .OauthGenericDisplayName }}</strong>
</a>
</div>
{{ end }}
@ -159,24 +159,24 @@ h3 { font-weight: normal; }
{{ end }}
{{ if and .OpenDeletion (not .IsAdmin) }}
<h2>Incinerator</h2>
<h2>{{call .Tr "Incinerator"}}</h2>
<div class="alert danger">
<div class="row">
<div>
<h3>Delete your account</h3>
<p>Permanently erase all your data, with no way to recover it.</p>
<h3>{{call .Tr "Delete your account"}}</h3>
<p>{{call .Tr "Permanently erase all your data, with no way to recover it."}}</p>
</div>
<button class="cta danger" onclick="prepareDeleteUser()">Delete your account...</button>
<button class="cta danger" onclick="prepareDeleteUser()">{{call .Tr "Delete your account"}}...</button>
</div>
</div>
{{end}}
</div>
<div id="modal-delete-user" class="modal">
<h2>Are you sure?</h2>
<h2>{{call .Tr "Are you sure?"}}</h2>
<div class="body">
<p style="text-align:left">This action <strong>cannot</strong> be undone. It will immediately and permanently erase your account, including your blogs and posts. Before continuing, you might want to <a href="/me/export">export your data</a>.</p>
<p>If you're sure, please type <strong>{{.Username}}</strong> to confirm.</p>
<p style="text-align:left">{{call .Tr "This action **cannot** be undone. It will immediately and permanently erase your account, including your blogs and posts. Before continuing, you might want to %s." true (variables "export your data;/me/export")}}</p>
<p>{{call .Tr "If you're sure, please type **%s** to confirm." true (variables .Username)}}</p>
<ul id="delete-errors" class="errors"></ul>
@ -184,8 +184,8 @@ h3 { font-weight: normal; }
{{ .CSRFField }}
<input id="confirm-text" placeholder="{{.Username}}" type="text" class="confirm boxy" name="confirm-username" style="margin-top: 0.5em;" />
<div style="text-align:right; margin-top: 1em;">
<a id="cancel-delete" style="margin-right:2em" href="#">Cancel</a>
<input class="danger" type="submit" id="confirm-delete" value="Delete your account" disabled />
<a id="cancel-delete" style="margin-right:2em" href="#">{{call .Tr "Cancel"}}</a>
<input class="danger" type="submit" id="confirm-delete" value="{{call .Tr "Delete your account"}}" disabled />
</div>
</div>
</div>
@ -222,7 +222,7 @@ for (var i=0; i<showChecks.length; i++) {
function confirmDeletion() {
$confirmDelBtn.disabled = true
$confirmDelBtn.value = 'Deleting...'
$confirmDelBtn.value = '{{call .Tr "Deleting..."}}'
}
{{ end }}
</script>

View File

@ -18,24 +18,24 @@ td.none {
<div class="content-container snug">
{{if .Silenced}}
{{template "user-silenced"}}
{{template "user-silenced" (dict "Tr" $.Tr)}}
{{end}}
{{template "collection-breadcrumbs" .}}
<h1 id="posts-header">Stats</h1>
<h1 id="posts-header">{{call .Tr "Stats"}}</h1>
{{if .Collection}}
{{template "collection-nav" (dict "Alias" .Collection.Alias "Path" .Path "SingleUser" .SingleUser)}}
{{template "collection-nav" (dict "Alias" .Collection.Alias "Path" .Path "SingleUser" .SingleUser "Tr" .Tr)}}
{{end}}
<p>Stats for all time.</p>
<p>{{call .Tr "Stats for all time."}}</p>
{{if .Federation}}
<h3>Fediverse stats</h3>
<h3>{{call .Tr "Fediverse stats"}}</h3>
<table id="fediverse" class="classy export">
<tr>
<th>Followers</th>
<th>{{call .Tr "Followers"}}</th>
</tr>
<tr>
<td>{{.APFollowers}}</td>
@ -43,16 +43,16 @@ td.none {
</table>
{{end}}
<h3>Top {{len .TopPosts}} posts</h3>
<h3>{{call .Tr "Top %d post" (len .TopPosts) (variables (len .TopPosts))}}</h3>
<table class="classy export">
<tr>
<th>Post</th>
{{if not .Collection}}<th>Blog</th>{{end}}
<th class="num">Total Views</th>
<th>{{call .Tr "Post"}}</th>
{{if not .Collection}}<th>{{call .Tr "Blog"}}</th>{{end}}
<th class="num">{{call .Tr "Total Views"}}</th>
</tr>
{{range .TopPosts}}<tr>
<td style="word-break: break-all;"><a href="{{if .Collection}}{{.Collection.CanonicalURL}}{{.Slug.String}}{{else}}/{{.ID}}{{end}}">{{if ne .DisplayTitle ""}}{{.DisplayTitle}}{{else}}<em>{{.ID}}</em>{{end}}</a></td>
{{ if not $.Collection }}<td>{{if .Collection}}<a href="{{.Collection.CanonicalURL}}">{{.Collection.Title}}</a>{{else}}<em>Draft</em>{{end}}</td>{{ end }}
<td style="word-break: break-all;"><a href="{{if .Collection}}{{.Collection.CanonicalURL}}{{.Slug.String}}{{else}}/{{.ID}}{{end}}">{{if ne .Title.String ""}}{{.Title.String}}{{else}}<em>{{.ID}}</em>{{end}}</a></td>
{{ if not $.Collection }}<td>{{if .Collection}}<a href="{{.Collection.CanonicalURL}}">{{.Collection.Title}}</a>{{else}}<em>{{call .Tr "Draft"}}</em>{{end}}</td>{{ end }}
<td class="num">{{.ViewCount}}</td>
</tr>{{end}}
</table>