Compare commits

...

59 Commits
0.7.4 ... 0.8.0

Author SHA1 Message Date
James Mills 80999abfa6
Update CHANGELOG for 0.8.0 2 months ago
James Mills 039f6a84ef
Fix CHANGELOG template (too noisey) 2 months ago
James Mills bbbec0c027
Update CHANGELOG for 0.8.0 2 months ago
James Mills 7a021e6990
Fix loading pod settings with invalid features (Fixes #556) 2 months ago
I am Fastidious f5b67c3321 Give inline images some slight round corners (#558) 2 months ago
I am Fastidious 8b7f2783e9 Make article target border more subtle (#557) 2 months ago
James Mills 551a1ae2da
Update CHANGELOG for 0.8.0 2 months ago
James Mills 1565ef9305
Add graceful AJAX support for Follow/UnFollow in Following and Followers views (Fixes #547) 2 months ago
James Mills 48246538b7
Add support for Service Workers to be hosted at /js/ (/static/js/ in themes) 2 months ago
James Mills 698be3b102
Update CHANGELOG template and config 2 months ago
James Mills 27105029cd
Add MagicLinkAuth, StripTwtSubjectHashes and ShowTwtContext as promoted features 2 months ago
James Mills 1a1ca73c31
Remove unused references to RegisterDisabledMessage 2 months ago
James Mills 57953653e3
Cleanup context 2 months ago
James Mills b163718c13
Fix IsAdmin contexxt variable 2 months ago
I am Fastidious 373a92691a Small re-organisation (#553) 2 months ago
I am Fastidious a75b1faa30 Properly align the "Next" and "Prev" navigation (#554) 2 months ago
James Mills b0e927f552
Fix a potential panic in NewContext() 2 months ago
James Mills 72de7c96ed xuu/show-fork-unauthenticated (#552) 2 months ago
xuu 6432478b8f fix: show fork when unauthenticated (#551) 2 months ago
xuu 767185ddff fix: link media is modifying cache (#550) 2 months ago
xuu 8b55e367b9 feat: parse image title for markdown (#548) 2 months ago
James Mills 696bf5019b
Disable sending webmentions until is is either fixed, rewritten or replaced 2 months ago
James Mills f99a1f8b56
Fix bug with --disable-ffmpeg that also disable incorrectly uploading of photos/pictures 2 months ago
James Mills adda47bb84
Add graceful AJAX for Follow/UnFollow and Mute/UnMute for externalProfile template too 2 months ago
I am Fastidious 7d5602dc3d Default text colour on dark mode (#545) 2 months ago
I am Fastidious 34e2c3dc79 Avatar border (#544) 2 months ago
I am Fastidious ad602f5174 Adjust class to match nick (#543) 2 months ago
I am Fastidious 21d1203b51 Time stamp font (#542) 2 months ago
James Mills 692ddbc429
Fix fd socket leak in webmention handling 2 months ago
I am Fastidious e1a3e089e0 User var on colour, and reduce size of name (#541) 2 months ago
I am Fastidious d3e29a5721 Avoid breaking nick@domain on twt (#540) 2 months ago
James Mills cc64de168a
Revert "Add (re-add) MergeStore daily job" 2 months ago
xuu 4a981e4280 feat: add fork link/length for template (#537) 2 months ago
I am Fastidious 3fe736c8fc Inline to match fenced code (#538) 2 months ago
I am Fastidious 70990a8248 Fixes too much space while on mobile web (#536) 2 months ago
I am Fastidious 63bab74f35 Fixes lack of space between summary and actual twt (#535) 2 months ago
James Mills 27eaebe5c2
Fix the margin of the Yarn count badge 2 months ago
I am Fastidious b56b449a6a Update 'internal/theme/static/css/99-yarn.css' (#534) 2 months ago
I am Fastidious 101764eb9f Update 'internal/theme/static/css/99-yarn.css' (#533) 2 months ago
James Mills 22879adf4e
Fix a few bugs with twt context rendering (aparently you can't nest a tags :O) 2 months ago
James Mills 7c2b6689b3 Fix twt context to use proper Markdown rendering + HTML sanitizer (#532) 2 months ago
James Mills 3afc0c13f1
Revert "better styling for in-reply-to and coversation badge" 2 months ago
James Mills 799e4564e9 Add a bunch of missed defer f.Close() -- none of which I believe could lea file descriptors though from open files (#531) 2 months ago
I am Fastidious f59b8ec926 Change textarea background colour (#530) 2 months ago
sorenpeter 33165c350b
better styling for in-reply-to and coversation badge 2 months ago
James Mills 1677e5f0df
Update default pod logo as per abb9c01 2 months ago
I am Fastidious b6245d1225 See #528 (#529) 2 months ago
I am Fastidious d2effe7246 Add a class to the twt navigation (#528) 2 months ago
I am Fastidious 68b5c16d95 Background colour for inline pre to match code fences (#527) 2 months ago
I am Fastidious abb9c01446 Change logo font (#526) 2 months ago
James Mills 339e1b8efc
Only show Twt Context in Timeline view 2 months ago
James Mills 1b9c8fe722
Use <small> instead of <em> as per @darch's feedback re show_twt_context feature 2 months ago
James Mills bfab60ed78
Add getRootTwt template function 2 months ago
James Mills 2814fa3832
Fix css for show_twt_context so ellipsis works 2 months ago
James Mills 281857d7cf
Tweak the show_twt_context template and css 2 months ago
James Mills 2e16728ddf
Increase default maxTwtContextLength to 140 2 months ago
James Mills e678205a8a
Add experimental Twt Context feature enabled with show_twt_context 2 months ago
James Mills 0d0ba52435
Add support for displaying list of features with ./yarnd --enable-feature list 2 months ago
James Mills d711509bdd
Fix display of Root conv button for unauthenticated users 2 months ago
  1. 2
      .chglog/CHANGELOG.tpl.md
  2. 9
      .chglog/config.yml
  3. 43
      CHANGELOG.md
  4. 1
      client/config.go
  5. 9
      cmd/yarnd/main.go
  6. 1
      internal/cache.go
  7. 1
      internal/config.go
  8. 82
      internal/context.go
  9. 17
      internal/features.go
  10. 37
      internal/handlers.go
  11. 30
      internal/jobs.go
  12. 1
      internal/langs/active.en.toml
  13. 4
      internal/langs/active.zh-CN.toml
  14. 4
      internal/langs/active.zh-TW.toml
  15. 14
      internal/media_handlers.go
  16. 3
      internal/options.go
  17. 16
      internal/router.go
  18. 8
      internal/server.go
  19. 4
      internal/templates.go
  20. 14
      internal/theme/static/css/01-pico.css
  21. 81
      internal/theme/static/css/99-yarn.css
  22. 12
      internal/theme/static/js/99-yarn.js
  23. 8
      internal/theme/static/logo.svg
  24. 34
      internal/theme/templates/externalProfile.html
  25. 19
      internal/theme/templates/followers.html
  26. 21
      internal/theme/templates/following.html
  27. 31
      internal/theme/templates/login.html
  28. 8
      internal/theme/templates/login_email.html
  29. 47
      internal/theme/templates/partials.html
  30. 6
      internal/theme/templates/profile.html
  31. 1
      internal/twt.go
  32. 204
      internal/utils.go
  33. 29
      internal/utils_test.go
  34. 4
      internal/webmention/webmention.go
  35. 37
      types/lextwt/ast.go
  36. 15
      types/lextwt/lexer.go
  37. 102
      types/lextwt/lextwt_test.go
  38. 36
      types/lextwt/parser.go
  39. 3
      types/twt_test.go

2
.chglog/CHANGELOG.tpl.md

@ -19,4 +19,4 @@
{{ end }}
{{ end -}}
{{ end -}}
{{ end -}}
{{ end -}}

9
.chglog/config.yml

@ -23,6 +23,15 @@ options:
pattern_maps:
- Subject
- Type
refs:
actions:
- Closes
- Fixes
reverts:
pattern: "^Revert \"([\\s\\S]*)\"$"
pattern_maps:
- Header
notes:
keywords:
- NOTE
- BREAKING CHANGE

43
CHANGELOG.md

@ -1,4 +1,43 @@
<a name="0.8.0"></a>
## [0.8.0](https://git.mills.io/yarnsocial/yarn/compare/0.7.4...0.8.0) (2021-11-21)
### Bug Fixes
* Fix CHANGELOG template (too noisey)
* Fix loading pod settings with invalid features (Fixes #556)
* Fix IsAdmin contexxt variable
* Fix a potential panic in NewContext()
* Fix bug with --disable-ffmpeg that also disable incorrectly uploading of photos/pictures
* Fix fd socket leak in webmention handling
* Fix the margin of the Yarn count badge
* Fix a few bugs with twt context rendering (aparently you can't nest a tags :O)
* Fix twt context to use proper Markdown rendering + HTML sanitizer (#532)
* Fix css for show_twt_context so ellipsis works
* Fix display of Root conv button for unauthenticated users
### Features
* Add graceful AJAX support for Follow/UnFollow in Following and Followers views (Fixes #547)
* Add support for Service Workers to be hosted at /js/ (/static/js/ in themes)
* Add MagicLinkAuth, StripTwtSubjectHashes and ShowTwtContext as promoted features
* Add graceful AJAX for Follow/UnFollow and Mute/UnMute for externalProfile template too
* Add a bunch of missed defer f.Close() -- none of which I believe could lea file descriptors though from open files (#531)
* Add a class to the twt navigation (#528)
* Add getRootTwt template function
* Add experimental Twt Context feature enabled with show_twt_context
* Add support for displaying list of features with ./yarnd --enable-feature list
### Updates
* Update CHANGELOG for 0.8.0
* Update CHANGELOG for 0.8.0
* Update CHANGELOG template and config
* Update 'internal/theme/static/css/99-yarn.css' (#534)
* Update 'internal/theme/static/css/99-yarn.css' (#533)
* Update default pod logo as per abb9c01
<a name="0.7.4"></a>
## [0.7.4](https://git.mills.io/yarnsocial/yarn/compare/0.7.3...0.7.4) (2021-11-15)
@ -7,6 +46,10 @@
* Fix dupe Twt bug in User views by returning a copy of the view's slice
* Fix broken logic for archived root twts
### Updates
* Update CHANGELOG for 0.7.4
<a name="0.7.3"></a>
## [0.7.3](https://git.mills.io/yarnsocial/yarn/compare/0.7.2...0.7.3) (2021-11-15)

1
client/config.go

@ -35,6 +35,7 @@ func (c *Config) Save(path string) error {
if err != nil {
return err
}
defer f.Close()
data, err := yaml.Marshal(c)
if err != nil {

9
cmd/yarnd/main.go

@ -35,6 +35,15 @@ func (f *flagSliceOfFeatureType) Type() string {
}
func (f *flagSliceOfFeatureType) Set(value string) error {
if strings.ToLower(value) == "list" {
fmt.Println("Available Features:")
for _, feature := range internal.AvailableFeatures() {
fmt.Printf(" - %s\n", feature)
}
fmt.Println()
os.Exit(0)
}
feature, err := internal.FeatureFromString(value)
if err != nil {
log.Warnf("invalid feature %s", value)

1
internal/cache.go

@ -184,7 +184,6 @@ func (cache *Cache) Store(conf *Config) error {
log.WithError(err).Error("error opening cache file for writing")
return err
}
defer f.Close()
if _, err = f.Write(b.Bytes()); err != nil {

1
internal/config.go

@ -298,6 +298,7 @@ func (s *Settings) Save(path string) error {
if err != nil {
return err
}
defer f.Close()
data, err := yaml.MarshalWithOptions(s, yaml.Indent(4))
if err != nil {

82
internal/context.go

@ -43,32 +43,28 @@ type Meta struct {
}
type Context struct {
Config string
Debug bool
Logo template.HTML
BaseURL string
InstanceName string
SoftwareVersion SoftwareConfig
TwtsPerPage int
TwtPrompt string
MaxTwtLength int
RegisterDisabled bool
OpenProfiles bool
DisableMedia bool
DisableFfmpeg bool
RegisterDisabledMessage string
WhitelistedImages []string
BlacklistedFeeds []string
EnabledFeatures []string
Logo template.HTML
BaseURL string
InstanceName string
SoftwareVersion SoftwareConfig
TwtsPerPage int
TwtPrompt string
MaxTwtLength int
RegisterDisabled bool
OpenProfiles bool
DisableMedia bool
DisableFfmpeg bool
WhitelistedImages []string
BlacklistedFeeds []string
EnabledFeatures []string
Timezones []*timezones.Zoneinfo
Reply string
Username string
User *User
Tokens []*Token
LastTwt types.Twt
Profile types.Profile
Authenticated bool
@ -130,7 +126,7 @@ func NewContext(s *Server, req *http.Request) *Context {
logo, err := RenderLogo(conf.Logo, conf.Name)
if err != nil {
log.WithError(err).Error("error rendering logo")
logo = template.HTML("")
logo = template.HTML(DefaultLogo)
}
// context
@ -179,42 +175,36 @@ func NewContext(s *Server, req *http.Request) *Context {
URL: fmt.Sprintf("%s/atom.xml", conf.BaseURL),
},
},
}
ctx.CSRFToken = nosurf.Token(req)
// Assume all users are anonymous (overridden below if Authenticated)
User: &User{
DisplayDatesInTimezone: conf.DisplayDatesInTimezone,
DisplayTimePreference: conf.DisplayTimePreference,
OpenLinksInPreference: conf.OpenLinksInPreference,
},
Twter: types.Twter{},
CSRFToken: nosurf.Token(req),
}
if sess := req.Context().Value(session.SessionKey); sess != nil {
if username, ok := sess.(*session.Session).Get("username"); ok {
ctx.Authenticated = true
ctx.Username = username
user, err := db.GetUser(ctx.Username)
if err != nil {
log.WithError(err).Warnf("error loading user object for %s", ctx.Username)
} else {
ctx.Twter = types.Twter{
Nick: user.Username,
URL: URLForUser(conf.BaseURL, user.Username),
}
ctx.User = user
ctx.IsAdmin = strings.EqualFold(username, conf.AdminUser)
}
}
}
if ctx.Authenticated && ctx.Username != "" {
user, err := db.GetUser(ctx.Username)
if err != nil {
log.WithError(err).Warnf("error loading user object for %s", ctx.Username)
}
ctx.Twter = types.Twter{
Nick: user.Username,
URL: URLForUser(conf.BaseURL, user.Username),
}
ctx.User = user
} else {
ctx.User = &User{
DisplayDatesInTimezone: conf.DisplayDatesInTimezone,
DisplayTimePreference: conf.DisplayTimePreference,
OpenLinksInPreference: conf.OpenLinksInPreference,
}
ctx.Twter = types.Twter{}
}
if ctx.Username == conf.AdminUser {
ctx.IsAdmin = true
}
// Set the theme based on user preferences
theme := strings.ToLower(ctx.User.Theme)
switch theme {

17
internal/features.go

@ -6,6 +6,7 @@ import (
"strings"
sync "github.com/sasha-s/go-deadlock"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
)
@ -15,8 +16,6 @@ const (
// FeatureInvalid is the invalid feature (0)
FeatureInvalid FeatureType = iota
FeatureFoo
FeatureMagicLinkAuth
FeatureStripConvSubjectHashes
)
// Interface guards
@ -31,10 +30,6 @@ func (f FeatureType) String() string {
switch f {
case FeatureFoo:
return "foo"
case FeatureMagicLinkAuth:
return "magic_link_auth"
case FeatureStripConvSubjectHashes:
return "strip_conv_subject_hashes"
default:
return "invalid_feature"
}
@ -65,10 +60,6 @@ func FeatureFromString(s string) (FeatureType, error) {
switch s {
case "foo":
return FeatureFoo, nil
case "magic_link_auth":
return FeatureMagicLinkAuth, nil
case "strip_conv_subject_hashes":
return FeatureStripConvSubjectHashes, nil
default:
fs := fmt.Sprintf("available features: %s", strings.Join(AvailableFeatures(), " "))
return FeatureInvalid, fmt.Errorf("Error unrecognised feature: %s (%s)", s, fs)
@ -183,7 +174,8 @@ func (f *FeatureFlags) UnmarshalJSON(b []byte) error {
features, err := FeaturesFromStrings(vs)
if err != nil {
return err
log.WithError(err).Warnf("error parsing features: %#s", vs)
return nil
}
f.Lock()
@ -216,7 +208,8 @@ func (f *FeatureFlags) UnmarshalYAML(unmarshal func(interface{}) error) error {
features, err := FeaturesFromStrings(vs)
if err != nil {
return err
log.WithError(err).Warnf("error parsing features: %#s", vs)
return nil
}
f.Lock()

37
internal/handlers.go

@ -302,7 +302,7 @@ func (s *Server) AvatarHandler() httprouter.Handle {
// PostHandler ...
func (s *Server) PostHandler() httprouter.Handle {
isLocalURL := IsLocalURLFactory(s.config)
//isLocalURL := IsLocalURLFactory(s.config)
return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
ctx := NewContext(s, r)
@ -377,16 +377,16 @@ func (s *Server) PostHandler() httprouter.Handle {
}
var sources types.Feeds
var twt types.Twt = types.NilTwt
//var twt types.Twt = types.NilTwt
switch postas {
case "", user.Username:
sources = user.Source()
if hash != "" && lastTwt.Hash() == hash {
twt, err = AppendTwt(s.config, s.db, user, text, lastTwt.Created())
_, err = AppendTwt(s.config, s.db, user, text, lastTwt.Created())
} else {
twt, err = AppendTwt(s.config, s.db, user, text)
_, err = AppendTwt(s.config, s.db, user, text)
}
default:
if user.OwnsFeed(postas) {
@ -401,9 +401,9 @@ func (s *Server) PostHandler() httprouter.Handle {
}
if hash != "" && lastTwt.Hash() == hash {
twt, err = AppendSpecial(s.config, s.db, postas, text, lastTwt.Created)
_, err = AppendSpecial(s.config, s.db, postas, text, lastTwt.Created)
} else {
twt, err = AppendSpecial(s.config, s.db, postas, text)
_, err = AppendSpecial(s.config, s.db, postas, text)
}
} else {
err = ErrFeedImposter
@ -426,19 +426,24 @@ func (s *Server) PostHandler() httprouter.Handle {
// WebMentions ...
// TODO: Use a queue here instead?
if _, err := s.tasks.Dispatch(NewFuncTask(func() error {
for _, m := range twt.Mentions() {
twter := m.Twter()
if !isLocalURL(twter.URL) {
if err := WebMention(twter.URL, URLForTwt(s.config.BaseURL, twt.Hash())); err != nil {
log.WithError(err).Warnf("error sending webmention to %s", twter.URL)
// TODO: Fix Webmentions
// TODO: https://git.mills.io/yarnsocial/yarn/issues/438
// TODO: https://git.mills.io/yarnsocial/yarn/issues/515
/*
if _, err := s.tasks.Dispatch(NewFuncTask(func() error {
for _, m := range twt.Mentions() {
twter := m.Twter()
if !isLocalURL(twter.URL) {
if err := WebMention(twter.URL, URLForTwt(s.config.BaseURL, twt.Hash())); err != nil {
log.WithError(err).Warnf("error sending webmention to %s", twter.URL)
}
}
}
return nil
})); err != nil {
log.WithError(err).Warn("error submitting task for webmentions")
}
return nil
})); err != nil {
log.WithError(err).Warn("error submitting task for webmentions")
}
*/
http.Redirect(w, r, RedirectRefererURL(r, s.config, "/"), http.StatusFound)
}

30
internal/jobs.go

@ -30,19 +30,13 @@ var (
func InitJobs(conf *Config) {
Jobs = map[string]JobSpec{
// Continious Jobs
"SyncStore": NewJobSpec("@every 1m", NewSyncStoreJob),
"UpdateFeeds": NewJobSpec(conf.FetchInterval, NewUpdateFeedsJob),
"UpdateFeedSources": NewJobSpec("@every 15m", NewUpdateFeedSourcesJob),
// Hourly Jobs
"DeleteOldSessions": NewJobSpec("@hourly", NewDeleteOldSessionsJob),
// Daily Jobs
"Stats": NewJobSpec("0 0 0 * * *", NewStatsJob),
"MergeStore": NewJobSpec("0 0 1 * * *", NewMergeStoreJob),
// Weekly Jobs
"Stats": NewJobSpec("@daily", NewStatsJob),
"RotateFeeds": NewJobSpec("0 0 2 * * 0", NewRotateFeedsJob),
"PruneUsers": NewJobSpec("0 0 3 * * 0", NewPruneUsersJob),
@ -63,28 +57,6 @@ func InitJobs(conf *Config) {
type JobFactory func(conf *Config, cache *Cache, archive Archiver, store Store) cron.Job
type MergeStoreJob struct {
conf *Config
cache *Cache
archive Archiver
db Store
}
func NewMergeStoreJob(conf *Config, cache *Cache, archive Archiver, db Store) cron.Job {
return &MergeStoreJob{conf: conf, cache: cache, archive: archive, db: db}
}
func (job *MergeStoreJob) Run() {
log.Info("merging store...")
// Merge store
if err := job.db.Merge(); err != nil {
log.WithError(err).Error("error merging store")
}
log.Info("merged store")
}
type SyncStoreJob struct {
conf *Config
cache *Cache

1
internal/langs/active.en.toml

@ -231,7 +231,6 @@ ProfileTwtxtLinkTitle = "Twtxt"
ProfileUnmuteLinkTitle = "Unmute"
RecentTwtsSummary = "Recent twts from {{ .Username }}"
RecentTwtsTitle = "Recent Twts"
RegisterDisabledMessage = "Registrations are disabled on this instance. Please contact the operator."
EmailAddress = "Email address"
RegisterFormEmailSummary = "NOTE: We DO NOT actually store this! If you forget or lose access to your Email account provided here, it will be impossible to recover your account!"
RegisterFormLogin = "Already have an account? <a href='/login'>/login</a> instead."

4
internal/langs/active.zh-CN.toml

@ -898,10 +898,6 @@ other = "来自 {{ .Username }} 的最近推文"
hash = "sha1-5471aee8e78958c108a802ab9385f2a13f184d2b"
other = "最近推文"
[RegisterDisabledMessage]
hash = "sha1-14d26e1e7754b30ed7402bbaf188baa629d8a79b"
other = "当前实例禁止注册, 请联系管理员"
[EmailAddress]
hash = "sha1-c94d3175a6560565410511df2cebab9cda96027e"
other = "电子邮件"

4
internal/langs/active.zh-TW.toml

@ -898,10 +898,6 @@ other = "來自 {{ .Username }} 的最新推文"
hash = "sha1-5471aee8e78958c108a802ab9385f2a13f184d2b"
other = "最新推文"
[RegisterDisabledMessage]
hash = "sha1-14d26e1e7754b30ed7402bbaf188baa629d8a79b"
other = "當前實例禁止註冊, 請聯繫管理員"
[EmailAddress]
hash = "sha1-c94d3175a6560565410511df2cebab9cda96027e"
other = "電子郵件"

14
internal/media_handlers.go

@ -130,12 +130,12 @@ func (s *Server) UploadMediaHandler() httprouter.Handle {
uri.Path = URLForTask(s.config.BaseURL, uuid)
}
if s.config.DisableFfmpeg {
http.Error(w, "FFMpeg support disabled", http.StatusNotFound)
return
}
if strings.HasPrefix(ctype, "audio/") {
if s.config.DisableFfmpeg {
http.Error(w, "FFMpeg support disabled", http.StatusNotFound)
return
}
fn, err := ReceiveAudio(mfile)
if err != nil {
log.WithError(err).Error("error writing uploaded audio")
@ -154,6 +154,10 @@ func (s *Server) UploadMediaHandler() httprouter.Handle {
}
if strings.HasPrefix(ctype, "video/") {
if s.config.DisableFfmpeg {
http.Error(w, "FFMpeg support disabled", http.StatusNotFound)
return
}
fn, err := ReceiveVideo(mfile)
if err != nil {
log.WithError(err).Error("error writing uploaded video")

3
internal/options.go

@ -38,7 +38,7 @@ const (
DefaultName = "yarn.local"
// DefaultLogo is the default logo (SVG)
DefaultLogo = `<svg aria-hidden="true" width="196.26" viewBox="100 50 294.389 108.746" height="70.739" xmlns="http://www.w3.org/2000/svg"><g transform="translate(1.302 -35.908)"><path fill="currentCOlor" stroke="null" d="M178.103 112.703c-7.338-7.366-17.095-11.423-27.472-11.423-10.378 0-20.135 4.057-27.473 11.423-6.965 6.992-10.957 16.17-11.345 25.993a1.23 1.23 0 0 0-.018.476c-.01.369-.016.738-.016 1.108 0 10.418 4.041 20.211 11.38 27.578a39.217 39.217 0 0 0 6.546 5.296 118.789 118.789 0 0 1-3.974-1.632c-4.94-2.1-8.84-3.76-14.312-2.468a1.222 1.222 0 0 0 .557 2.378c4.697-1.11 8.098.337 12.805 2.34 5.77 2.454 12.951 5.508 25.85 5.508 10.377 0 20.134-4.056 27.472-11.422 7.338-7.367 11.38-17.16 11.38-27.578 0-10.417-4.042-20.21-11.38-27.577zm.683 50.77-31.174 13.24a36.296 36.296 0 0 1-6.456-1.12l41.321-17.549a36.468 36.468 0 0 1-3.69 5.429zm-4.402 4.522a36.015 36.015 0 0 1-20.475 8.695zm9.721-13.273a1.21 1.21 0 0 0-.207.066l-46.282 19.656a35.902 35.902 0 0 1-4.352-2.013l52.447-22.274a36.187 36.187 0 0 1-1.606 4.565zm2.292-7.508-55.717 23.662a36.484 36.484 0 0 1-3.303-2.459l59.564-25.296a36.852 36.852 0 0 1-.544 4.093zm-67.155 11.63 66.521-28.25c.316 1.16.574 2.34.774 3.533l-65.309 27.736a36.41 36.41 0 0 1-1.986-3.018zm-2.61-5.404 34.466-14.638.012-.005 32.602-13.845a36.113 36.113 0 0 1 1.326 3.298l-66.97 28.442a36.16 36.16 0 0 1-1.436-3.252zm.286-27.036 16.984 17.05-4.15 1.762a1.214 1.214 0 0 0-.323-.588l-13.941-13.994c.39-1.44.867-2.853 1.43-4.23zm3.124-5.982 20.244 20.32-3.966 1.684-18.33-18.4a36.282 36.282 0 0 1 2.052-3.604zm7.476 25.742-3.965 1.684-9.299-9.334c.08-1.715.28-3.408.592-5.07zm-13.267-4.199 6.884 6.91-5.3 2.251a36.692 36.692 0 0 1-1.584-9.16zm11.504-28.382a36.775 36.775 0 0 1 4.192-3.401v7.61zm6.626-4.954a35.903 35.903 0 0 1 4.055-2.034v17.71l-4.055-4.071zm6.489-2.964a35.975 35.975 0 0 1 4.055-1.125v26.278l-4.055-4.07zm6.488-1.563a36.665 36.665 0 0 1 4.056-.357v33.12l-.327.14-3.729-3.744v-29.159zm30.011 21.74-4.055 1.723v-17.383a36.739 36.739 0 0 1 4.055 3.273zm-6.488 2.756-4.056 1.723v-23.726c1.391.59 2.745 1.27 4.056 2.034zm-6.49 2.756-4.055 1.722v-28.535c1.377.296 2.73.671 4.056 1.124zm-6.488 2.756-4.056 1.722v-32.087c1.367.045 2.72.164 4.056.357zm26.711-11.344-4.811 2.043v-8.868a36.567 36.567 0 0 1 4.811 6.825zm-58.58-7.456 22.635 22.721-3.965 1.684-21.246-21.327a36.935 36.935 0 0 1 2.576-3.078zm-1.242 48.54 64.08-27.213c.117 1.203.177 2.419.177 3.643l-.002.144-61.673 26.192c-.166-.16-.332-.321-.496-.485a37.253 37.253 0 0 1-2.086-2.28z"/><text font-family="Helvetica" xml:space="preserve" text-anchor="middle" text-rendering="geometricPrecision" transform="matrix(.7694 0 0 1 86.303 52.164)" font-size="32" x="220.051" y="85" fill="currentColor" stroke="null">{{ .PodName }}</text><text xml:space="preserve" font-family="'Noto Sans JP'" stroke-width="0" fill="currentColor" stroke="null" font-size="22" y="167.245" x="198.372">a Yarn.social pod</text></g></svg>`
DefaultLogo = `<svg aria-hidden="true" width="196.26" viewBox="100 50 294.389 108.746" height="70.739" xmlns="http://www.w3.org/2000/svg"><g transform="translate(1.302 -35.908)"><path fill="currentCOlor" stroke="null" d="M178.103 112.703c-7.338-7.366-17.095-11.423-27.472-11.423-10.378 0-20.135 4.057-27.473 11.423-6.965 6.992-10.957 16.17-11.345 25.993a1.23 1.23 0 0 0-.018.476c-.01.369-.016.738-.016 1.108 0 10.418 4.041 20.211 11.38 27.578a39.217 39.217 0 0 0 6.546 5.296 118.789 118.789 0 0 1-3.974-1.632c-4.94-2.1-8.84-3.76-14.312-2.468a1.222 1.222 0 0 0 .557 2.378c4.697-1.11 8.098.337 12.805 2.34 5.77 2.454 12.951 5.508 25.85 5.508 10.377 0 20.134-4.056 27.472-11.422 7.338-7.367 11.38-17.16 11.38-27.578 0-10.417-4.042-20.21-11.38-27.577zm.683 50.77-31.174 13.24a36.296 36.296 0 0 1-6.456-1.12l41.321-17.549a36.468 36.468 0 0 1-3.69 5.429zm-4.402 4.522a36.015 36.015 0 0 1-20.475 8.695zm9.721-13.273a1.21 1.21 0 0 0-.207.066l-46.282 19.656a35.902 35.902 0 0 1-4.352-2.013l52.447-22.274a36.187 36.187 0 0 1-1.606 4.565zm2.292-7.508-55.717 23.662a36.484 36.484 0 0 1-3.303-2.459l59.564-25.296a36.852 36.852 0 0 1-.544 4.093zm-67.155 11.63 66.521-28.25c.316 1.16.574 2.34.774 3.533l-65.309 27.736a36.41 36.41 0 0 1-1.986-3.018zm-2.61-5.404 34.466-14.638.012-.005 32.602-13.845a36.113 36.113 0 0 1 1.326 3.298l-66.97 28.442a36.16 36.16 0 0 1-1.436-3.252zm.286-27.036 16.984 17.05-4.15 1.762a1.214 1.214 0 0 0-.323-.588l-13.941-13.994c.39-1.44.867-2.853 1.43-4.23zm3.124-5.982 20.244 20.32-3.966 1.684-18.33-18.4a36.282 36.282 0 0 1 2.052-3.604zm7.476 25.742-3.965 1.684-9.299-9.334c.08-1.715.28-3.408.592-5.07zm-13.267-4.199 6.884 6.91-5.3 2.251a36.692 36.692 0 0 1-1.584-9.16zm11.504-28.382a36.775 36.775 0 0 1 4.192-3.401v7.61zm6.626-4.954a35.903 35.903 0 0 1 4.055-2.034v17.71l-4.055-4.071zm6.489-2.964a35.975 35.975 0 0 1 4.055-1.125v26.278l-4.055-4.07zm6.488-1.563a36.665 36.665 0 0 1 4.056-.357v33.12l-.327.14-3.729-3.744v-29.159zm30.011 21.74-4.055 1.723v-17.383a36.739 36.739 0 0 1 4.055 3.273zm-6.488 2.756-4.056 1.723v-23.726c1.391.59 2.745 1.27 4.056 2.034zm-6.49 2.756-4.055 1.722v-28.535c1.377.296 2.73.671 4.056 1.124zm-6.488 2.756-4.056 1.722v-32.087c1.367.045 2.72.164 4.056.357zm26.711-11.344-4.811 2.043v-8.868a36.567 36.567 0 0 1 4.811 6.825zm-58.58-7.456 22.635 22.721-3.965 1.684-21.246-21.327a36.935 36.935 0 0 1 2.576-3.078zm-1.242 48.54 64.08-27.213c.117 1.203.177 2.419.177 3.643l-.002.144-61.673 26.192c-.166-.16-.332-.321-.496-.485a37.253 37.253 0 0 1-2.086-2.28z" /><text letter-spacing="1px" font-weight="bolder" font-family="-apple-system, BlinkMacSystemFont, 'egoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'" xml:space="preserve" text-anchor="middle" text-rendering="geometricPrecision" transform="matrix(.7694 0 0 1 86.303 52.164)" font-size="35" x="220.051" y="85" fill="currentColor" stroke="null">{{ .PodName }}</text><text xml:space="preserve" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'" stroke-width="0" fill="currentColor" stroke="null" font-size="22" y="167.245" x="198.372">a Yarn.Social pod.</text></g></svg>`
// DefaultMetaxxx are the default set of <meta> tags used on non-specific views
DefaultMetaTitle = ""
@ -201,6 +201,7 @@ func NewConfig() *Config {
DisableGzip: DefaultDisableGzip,
DisableLogger: DefaultDisableLogger,
DisableFfmpeg: DefaultDisableFfmpeg,
DisableMedia: DefaultDisableMedia,
Features: NewFeatureFlags(),
DisplayDatesInTimezone: DefaultDisplayDatesInTimezone,
DisplayTimePreference: DefaultDisplayTimePreference,

16
internal/router.go

@ -136,6 +136,21 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.Router.ServeHTTP(w, req)
}
// ServeFiles ...
func (r *Router) ServeFiles(path string, root http.FileSystem) {
if len(path) < 10 || path[len(path)-10:] != "/*filepath" {
panic("path must end with /*filepath in path '" + path + "'")
}
fileServer := http.FileServer(root)
r.GET(path, func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
w.Header().Set("Service-Worker-Allowed", "/")
req.URL.Path = ps.ByName("filepath")
fileServer.ServeHTTP(w, req)
})
}
// ServeFilesWithCacheControl ...
func (r *Router) ServeFilesWithCacheControl(path string, root fs.FS) {
if len(path) < 10 || path[len(path)-10:] != "/*filepath" {
@ -147,6 +162,7 @@ func (r *Router) ServeFilesWithCacheControl(path string, root fs.FS) {
r.GET(path, func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
w.Header().Set("Vary", "Accept-Encoding")
w.Header().Set("Cache-Control", "public, max-age=7776000")
w.Header().Set("Service-Worker-Allowed", "/")
req.URL.Path = ps.ByName("filepath")
fileServer.ServeHTTP(w, req)
})

8
internal/server.go

@ -601,11 +601,9 @@ func (s *Server) initRoutes() {
s.router.GET("/login", httproutermiddleware.Handler("login", s.am.HasAuth(s.LoginHandler()), mdlw))
s.router.POST("/login", httproutermiddleware.Handler("login", s.LoginHandler(), mdlw))
if s.config.Features.IsEnabled(FeatureMagicLinkAuth) {
s.router.GET("/login/email", httproutermiddleware.Handler("login_email", s.am.HasAuth(s.LoginEmailHandler()), mdlw))
s.router.POST("/login/email", httproutermiddleware.Handler("login_email", s.LoginEmailHandler(), mdlw))
s.router.GET("/magiclinkauth", httproutermiddleware.Handler("magiclinkauth", s.MagicLinkAuthHandler(), mdlw))
}
s.router.GET("/login/email", httproutermiddleware.Handler("login_email", s.am.HasAuth(s.LoginEmailHandler()), mdlw))
s.router.POST("/login/email", httproutermiddleware.Handler("login_email", s.LoginEmailHandler(), mdlw))
s.router.GET("/magiclinkauth", httproutermiddleware.Handler("magiclinkauth", s.MagicLinkAuthHandler(), mdlw))
s.router.GET("/logout", httproutermiddleware.Handler("logout", s.LogoutHandler(), mdlw))
s.router.POST("/logout", httproutermiddleware.Handler("logout", s.LogoutHandler(), mdlw))

4
internal/templates.go

@ -71,10 +71,14 @@ func NewTemplateManager(conf *Config, translator *Translator, cache *Cache, arch
funcMap["isLocalURL"] = IsLocalURLFactory(conf)
funcMap["formatTwt"] = FormatTwtFactory(conf, cache, archive)
funcMap["unparseTwt"] = UnparseTwtFactory(conf)
funcMap["formatTwtContext"] = FormatTwtContextFactory(conf, cache, archive)
funcMap["getRootTwt"] = GetRootTwtFactory(conf, cache, archive)
funcMap["formatForDateTime"] = FormatForDateTime
funcMap["urlForConv"] = URLForConvFactory(conf, cache, archive)
funcMap["urlForFork"] = URLForForkFactory(conf, cache, archive)
funcMap["urlForRootConv"] = URLForRootConvFactory(conf, cache, archive)
funcMap["getConvLength"] = GetConvLength(conf, cache, archive)
funcMap["getForkLength"] = GetForkLength(conf, cache, archive)
funcMap["isAdminUser"] = IsAdminUserFactory(conf)
funcMap["isSpecialFeed"] = IsSpecialFeed
funcMap["twtType"] = func(twt types.Twt) string { return fmt.Sprintf("%T", twt) }

14
internal/theme/static/css/01-pico.css

@ -53,7 +53,7 @@
@media only screen and (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--background: #000000;
--text: #a2afb9;
--text: #ffffff;
--h1: #edf0f3;
--h2: #d5dce2;
--h3: #bbc6ce;
@ -73,7 +73,7 @@
--contrast-focus: rgba(89, 107, 120, 0.25);
--contrast-border: rgba(255, 223, 128, 0.33);
--contrast-inverse: #10181e;
--input-background: #10181e;
--input-background: #181818;
--input-border: #374956;
--valid: #1f7a5c;
--invalid: #943838;
@ -86,7 +86,7 @@
--card-sections: #141d24;
--card-shadow: 0 0.125rem 1rem rgba(0, 0, 0, 0.08), 0 0.125rem 2rem rgba(0, 0, 0, 0.04), 0 0 0 0.0625rem rgba(0, 0, 0, 0.1);
--code-background: #2f2f2f;
--code-inlined: rgba(65, 84, 98, 0.25);
--code-inlined: #050505;
--code-color-1: #73828c;
--code-color-2: #a65980;
--code-color-3: #599fa6;
@ -97,7 +97,7 @@
[data-theme="dark"] {
--background: #000000;
--text: #a2afb9;
--text: #ffffff;
--h1: #edf0f3;
--h2: #d5dce2;
--h3: #bbc6ce;
@ -117,7 +117,7 @@
--contrast-focus: rgba(89, 107, 120, 0.25);
--contrast-border: rgba(255, 223, 128, 0.33);
--contrast-inverse: #10181e;
--input-background: #10181e;
--input-background: #181818;
--input-border: #374956;
--valid: #1f7a5c;
--invalid: #943838;
@ -130,7 +130,7 @@
--card-sections: #141d24;
--card-shadow: 0 0.125rem 1rem rgba(0, 0, 0, 0.08), 0 0.125rem 2rem rgba(0, 0, 0, 0.04), 0 0 0 0.0625rem rgba(0, 0, 0, 0.1);
--code-background: #2f2f2f;
--code-inlined: rgba(65, 84, 98, 0.25);
--code-inlined: #050505;
--code-color-1: #73828c;
--code-color-2: #a65980;
--code-color-3: #599fa6;
@ -922,7 +922,7 @@
margin-bottom: 1rem;
padding: 0.5rem 0.5rem;
overflow-x: auto;
background: #2f2f2f; }
background: #050505; }
pre > code {
display: block;
padding: 0;

81
internal/theme/static/css/99-yarn.css

@ -5,6 +5,9 @@ body, html {
article p img {
display: block;
margin-top: 0.5rem;
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
}
article hgroup h3 {
@ -20,28 +23,18 @@ article nav li {
}
article:target {
border: 1px solid var(--primary);
border: 1px solid var(--input-border);
}
.logo {
height: 56px;
}
.h-entry {
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
-ms-hyphens: auto;
-moz-hyphens: auto;
-webkit-hyphens: auto;
hyphens: auto;
}
.avatar {
width: 60px;
height: 60px;
object-fit: cover;
border: 3px solid var(--h1);
border: 2px solid var(--h1);
border-radius: 50%;
-webkit-border-radius: 60px;;
-moz-border-radius: 60px;
@ -61,26 +54,35 @@ article:target {
margin: -1rem -1rem 0 1rem;
}
.badge {
.yarn-count-badge {
background-color: var(--primary);
color: white;
border-radius: 100px;
padding: 0px 4px;
font-size: 9px;
margin-left: -3px;
position: absolute;
}
.nav {
z-index: 1050;
}
nav.pagination-nav a {
display: block;
margin: -1rem -0.5rem -1rem 0rem;
padding: 1rem 0rem;
border-radius: .25rem;
}
nav.toolbar-nav ul{
margin-left: 0px;
margin-right: 0px;
}
.toolbar-form-button {
width: 40px;
}
.toolbar-form-button label {
cursor: pointer
}
@ -266,12 +268,13 @@ video {
.p-name {
left: 0px !important;
font-size: 34px;
color: #00bfff;
font-size: 25px;
color: var(--primary);
}
.p-org {
left: 0px !important;
font-size: 26px;
font-size: 20px;
font-weight: 100;
color: var(--muted-text);
margin-top: 18px;
@ -285,6 +288,7 @@ video {
.publish-time {
display: flex;
flex-direction: row;
font-size: 80%;
}
[data-tooltip]:not(a):not(button):not(input){
border-bottom: none;
@ -312,6 +316,20 @@ video {
border-radius: 0.25rem !important;
}
article .p-summary {
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
-ms-hyphens: auto;
-moz-hyphens: auto;
-webkit-hyphens: auto;
hyphens: auto;
}
article .p-summary p {
margin: 0.75rem 0;
}
/* Video Summary Content Styling */
article .p-summary p video,
article .p-summary p img{
@ -340,6 +358,35 @@ em.twt-hash {
padding-top: 0.25rem;
}
/* Twt Context */
article .p-summary a small, small.twt-context {
color: darkgray;
font-style: normal;
font-weight: 100;
color: var(--muted-text);
}
small.twt-context {
display: block;
font-size: small;
padding-top: 0.25rem;
padding: 0.25rem 0;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
border-bottom: 1px dashed var(--muted-border);
}
article .p-summary p {
margin: 0.75rem 0;
}
/* Twt Nav */
.twt-nav a {
font-size: 85%;
}
/* Footer Style */
footer {
border-top: 1px solid var(--primary);

12
internal/theme/static/js/99-yarn.js

@ -267,28 +267,28 @@ u(".unbookmarkBtn").on("click", function (e) {
});
});
u("#followBtn").on("click", function (e) {
u(".followBtn").on("click", function (e) {
e.preventDefault();
Twix.ajax({
type: "GET",
url: u(e.target).attr("href"),
success: function(data) {
u(e.target).attr("style", "display: none;");
u("#unfollowBtn").attr("style", "display: inline;");
u(e.target).closest("a.followBtn").attr("style", "display: none;");
u(e.target).parent().find("a.unfollowBtn").attr("style", "display: inline;");
},
});
});
u("#unfollowBtn").on("click", function (e) {
u(".unfollowBtn").on("click", function (e) {
e.preventDefault();
Twix.ajax({
type: "GET",
url: u(e.target).attr("href"),
success: function(data) {
u(e.target).attr("style", "display: none;");
u("#followBtn").attr("style", "display: inline;");
u(e.target).closest("a.unfollowBtn").attr("style", "display: none;");
u(e.target).parent().find("a.followBtn").attr("style", "display: inline;");
},
});
});

8
internal/theme/static/logo.svg

@ -1,11 +1,13 @@
<svg aria-hidden="true" width="196.26" viewBox="100 50 294.389 108.746" height="70.739" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(1.302 -35.908)">
<path fill="currentCOlor" stroke="null" d="M178.103 112.703c-7.338-7.366-17.095-11.423-27.472-11.423-10.378 0-20.135 4.057-27.473 11.423-6.965 6.992-10.957 16.17-11.345 25.993a1.23 1.23 0 0 0-.018.476c-.01.369-.016.738-.016 1.108 0 10.418 4.041 20.211 11.38 27.578a39.217 39.217 0 0 0 6.546 5.296 118.789 118.789 0 0 1-3.974-1.632c-4.94-2.1-8.84-3.76-14.312-2.468a1.222 1.222 0 0 0 .557 2.378c4.697-1.11 8.098.337 12.805 2.34 5.77 2.454 12.951 5.508 25.85 5.508 10.377 0 20.134-4.056 27.472-11.422 7.338-7.367 11.38-17.16 11.38-27.578 0-10.417-4.042-20.21-11.38-27.577zm.683 50.77-31.174 13.24a36.296 36.296 0 0 1-6.456-1.12l41.321-17.549a36.468 36.468 0 0 1-3.69 5.429zm-4.402 4.522a36.015 36.015 0 0 1-20.475 8.695zm9.721-13.273a1.21 1.21 0 0 0-.207.066l-46.282 19.656a35.902 35.902 0 0 1-4.352-2.013l52.447-22.274a36.187 36.187 0 0 1-1.606 4.565zm2.292-7.508-55.717 23.662a36.484 36.484 0 0 1-3.303-2.459l59.564-25.296a36.852 36.852 0 0 1-.544 4.093zm-67.155 11.63 66.521-28.25c.316 1.16.574 2.34.774 3.533l-65.309 27.736a36.41 36.41 0 0 1-1.986-3.018zm-2.61-5.404 34.466-14.638.012-.005 32.602-13.845a36.113 36.113 0 0 1 1.326 3.298l-66.97 28.442a36.16 36.16 0 0 1-1.436-3.252zm.286-27.036 16.984 17.05-4.15 1.762a1.214 1.214 0 0 0-.323-.588l-13.941-13.994c.39-1.44.867-2.853 1.43-4.23zm3.124-5.982 20.244 20.32-3.966 1.684-18.33-18.4a36.282 36.282 0 0 1 2.052-3.604zm7.476 25.742-3.965 1.684-9.299-9.334c.08-1.715.28-3.408.592-5.07zm-13.267-4.199 6.884 6.91-5.3 2.251a36.692 36.692 0 0 1-1.584-9.16zm11.504-28.382a36.775 36.775 0 0 1 4.192-3.401v7.61zm6.626-4.954a35.903 35.903 0 0 1 4.055-2.034v17.71l-4.055-4.071zm6.489-2.964a35.975 35.975 0 0 1 4.055-1.125v26.278l-4.055-4.07zm6.488-1.563a36.665 36.665 0 0 1 4.056-.357v33.12l-.327.14-3.729-3.744v-29.159zm30.011 21.74-4.055 1.723v-17.383a36.739 36.739 0 0 1 4.055 3.273zm-6.488 2.756-4.056 1.723v-23.726c1.391.59 2.745 1.27 4.056 2.034zm-6.49 2.756-4.055 1.722v-28.535c1.377.296 2.73.671 4.056 1.124zm-6.488 2.756-4.056 1.722v-32.087c1.367.045 2.72.164 4.056.357zm26.711-11.344-4.811 2.043v-8.868a36.567 36.567 0 0 1 4.811 6.825zm-58.58-7.456 22.635 22.721-3.965 1.684-21.246-21.327a36.935 36.935 0 0 1 2.576-3.078zm-1.242 48.54 64.08-27.213c.117 1.203.177 2.419.177 3.643l-.002.144-61.673 26.192c-.166-.16-.332-.321-.496-.485a37.253 37.253 0 0 1-2.086-2.28z" />
<text font-family="Helvetica" xml:space="preserve" text-anchor="middle" text-rendering="geometricPrecision" transform="matrix(.7694 0 0 1 86.303 52.164)" font-size="32" x="220.051" y="85" fill="currentColor" stroke="null">
<text letter-spacing="1px" font-weight="bolder" font-family="-apple-system, BlinkMacSystemFont, 'egoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe
UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'" xml:space="preserve" text-anchor="middle" text-rendering="geometricPrecision" transform="matrix(.7694 0 0 1 86.303 52.164)" font-size="35" x="220.051" y="85" fill="currentColor" stroke="null">
{{ .PodName }}
</text>
<text xml:space="preserve" font-family="'Noto Sans JP'" stroke-width="0" fill="currentColor" stroke="null" font-size="22" y="167.245" x="198.372">
a Yarn.social pod
<text xml:space="preserve" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe
UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'" stroke-width="0" fill="currentColor" stroke="null" font-size="22" y="167.245" x="198.372">
a Yarn.Social pod.
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

34
internal/theme/templates/externalProfile.html

@ -13,17 +13,14 @@
</a><span class="p-org">@{{ .Profile.URL | hostnameFromURL }}</span>
</h2>
<h3>
{{ if .Profile.Follows }}
<a href="/unfollow?nick={{ .Profile.Username }}">
<i class="icss-minus"></i>
{{ tr . "UnfollowLinkTitle" }}
</a>
{{ else }}
<a href="/follow?nick={{ .Profile.Username }}&url={{ .Profile.URL }}">
<a class="followBtn" style="display: {{ if not .Profile.Follows }}inline{{ else }}none{{ end }};" href="/follow?nick={{ .Profile.Username }}&url={{ .Profile.URL }}">
<i class="icss-plus"></i>
{{ tr . "FollowLinkTitle" }}
</a>
{{ end }}
</a>
<a class="unfollowBtn" style="display: {{ if $.Profile.Follows }}inline{{ else }}none{{ end }};" href="/unfollow?nick={{ .Profile.Username }}">
<i class="icss-minus"></i>
{{ tr . "UnfollowLinkTitle" }}
</a>
</h3>
<p><i>{{ .Profile.Tagline }}</i></p>
<details>
@ -31,17 +28,14 @@
{{ (tr . "ProfileBlockUserContent" (dict "InstanceName" .InstanceName)) | html }}
<ul>
<li>
{{ if $.Profile.Muted }}
<a href="/unmute?nick={{ .Profile.Username }}">
<i class="icss-sound-3"></i>
{{ tr . "ProfileUnmuteLinkTitle" }}
</a>
{{ else }}
<a href="/mute?nick={{ .Profile.Username }}&url={{ .Profile.URL }}">
<i class="icss-sound-0"></i>
{{ tr . "ProfileMuteLinkTitle" }}
</a>
{{ end }}
<a id="muteBtn" style="display: {{ if not $.Profile.Muted }}inline{{ else }}none{{ end }};" href="/mute?nick={{ .Profile.Username }}&url={{ .Profile.URL }}">
<i class="icss-sound-0"></i>
{{ tr . "ProfileMuteLinkTitle" }}
</a>
<a id="unmuteBtn" style="display: {{ if .Profile.Muted }}inline{{ else }}none{{ end }};" href="/unmute?nick={{ .Profile.Username }}">
<i class="icss-sound-3"></i>
{{ tr . "ProfileUnmuteLinkTitle" }}
</a>
</li>
<li>
<a href="/report?nick={{ .Profile.Username }}&url={{ .Profile.URL }}">

19
internal/theme/templates/followers.html

@ -5,9 +5,9 @@
<h2>{{ tr . "FollowersTitle" }}</h2>
<h3>
{{ if $.User.Is .Profile.URL }}
{{ tr . "FollowersFollowingYou" }}
{{ tr . "FollowersFollowingYou" }}
{{ else }}
{{ (tr . "FollowersFollowingUser" (dict "Username" .Profile.Username)) | html }}
{{ (tr . "FollowersFollowingUser" (dict "Username" .Profile.Username)) | html }}
{{ end }}
</h3>
</hgroup>
@ -26,13 +26,14 @@
{{ end }}
(<a href="{{ $URL }}">{{ $URL }}</a>)
{{ if $.Authenticated }}
{{ if not ($.User.Is $URL) }}
{{ if $.User.Follows $URL }}
[<a href="/unfollow?nick={{ $Nick }}">{{ tr $ctx "UnfollowLinkTitle" }}</a>]
{{ else }}
[<a href="/follow?nick={{ $Nick }}&url={{ $URL }}">{{ tr $ctx "FollowLinkTitle" }}</a>]
{{ end }}
{{ end }}
<a class="followBtn" style="display: {{ if not ($.User.Follows $URL) }}inline{{ else }}none{{ end }};" href="/follow?nick={{ $Nick }}&url={{ $URL }}">
<i class="icss-plus"></i>
{{ tr $ctx "FollowLinkTitle" }}
</a>
<a class="unfollowBtn" style="display: {{ if $.User.Follows $URL }}inline{{ else }}none{{ end }};" href="/unfollow?nick={{ $Nick }}">
<i class="icss-minus"></i>
{{ tr $ctx "UnfollowLinkTitle" }}
</a>
{{ end }}
{{ end }}
</li>

21
internal/theme/templates/following.html

@ -5,9 +5,9 @@
<h2>{{ tr . "FollowingTitle" }}</h2>
<h3>
{{ if $.User.Is .Profile.URL }}
{{ tr . "FollowingFollowingYou" }}
{{ tr . "FollowingFollowingYou" }}
{{ else }}
{{ (tr . "FollowingFollowingUser" (dict "Username" .Profile.Username)) | html }}
{{ (tr . "FollowingFollowingUser" (dict "Username" .Profile.Username)) | html }}
{{ end }}
</h3>
</hgroup>
@ -28,13 +28,14 @@
{{ end }}
</a>
{{ if $.Authenticated }}
{{ if not ($.User.Is $URL) }}
{{ if $.User.Follows $URL }}
[<a href="/unfollow?nick={{ $Nick }}">{{ tr $ctx "UnfollowLinkTitle" }}</a>]
{{ else }}
[<a href="/follow?nick={{ $Nick }}&url={{ $URL }}">{{ tr $ctx "FollowLinkTitle" }}</a>]
{{ end }}
{{ end }}
<a class="followBtn" style="display: {{ if not ($.User.Follows $URL) }}inline{{ else }}none{{ end }};" href="/follow?nick={{ $Nick }}&url={{ $URL }}">
<i class="icss-plus"></i>
{{ tr $ctx "FollowLinkTitle" }}
</a>
<a class="unfollowBtn" style="display: {{ if $.User.Follows $URL }}inline{{ else }}none{{ end }};" href="/unfollow?nick={{ $Nick }}">
<i class="icss-minus"></i>
{{ tr $ctx "UnfollowLinkTitle" }}
</a>
{{ end }}
</li>
{{ end }}
@ -42,7 +43,7 @@
{{ else }}
<small>
{{ if $.User.Is .Profile.URL }}
{{ (tr . "FollowingNoFollowingSummary" (dict "InstanceName" .InstanceName)) | html }}
{{ (tr . "FollowingNoFollowingSummary" (dict "InstanceName" .InstanceName)) | html }}
{{ else }}
<b>{{ .Profile.Username }}</b> {{ tr . "FollowingNoFollowing" }}
{{ end }}

31
internal/theme/templates/login.html

@ -18,19 +18,13 @@
<button type="submit">{{ tr . "LoginFormLogin" }}</button>
<p>
{{ tr . "LoginNoAccountTitle" }}
{{ if .RegisterDisabled }}
<a href="#" title="{{ with .RegisterDisabledMessage }}{{ .RegisterDisabledMessage }}{{ else }}{{ tr . "RegisterDisabledMessage" }}{{ end }}">{{ tr . "RegisterLinkTitle" }}</a>
{{ else }}
<a href="/register">{{ tr . "RegisterLinkTitle" }}</a>
{{ if not .RegisterDisabled }}
<a href="/register">{{ tr . "RegisterLinkTitle" }}</a>
{{ tr . "Instead" }}
{{ end }}
{{ tr . "Instead" }}
</p>
<p>
<a href="/resetPassword">{{ tr . "ResetPasswordLinkTitle" }}</a>
</p>
{{ if isFeatureEnabled "magic_link_auth" }}
<p><a href="/login/email">{{ tr . "LoginViaEmailAddress" }}</a></p>
{{ end }}
<p><a href="/resetPassword">{{ tr . "ResetPasswordLinkTitle" }}</a></p>
<p><a href="/login/email">{{ tr . "LoginViaEmailAddress" }}</a></p>
</form>
</div>
<div>
@ -38,21 +32,14 @@
<h2>{{ tr . "LoginHowToTitle" }}</h2>
</hgroup>
{{ (tr . "LoginHowToContent" (dict "InstanceName" $.InstanceName)) | html }}
{{ if .RegisterDisabled }}
<p>
{{ tr . "RegisterDisabledMessage" }}
{{ (tr . "ForgottenPasswordContent") | html }}
</p>
{{end}}
<p>
{{ (tr . "ForgottenPasswordContent") | html }}
You may also login via your Email account by simply supplying your Username and Email Address.
If the Username and Email Address match a valid account, an email will be sent to you with a link
that you can click on to automatically log you in without requiring a password.
</p>
{{ if isFeatureEnabled "magic_link_auth" }}
<p>
You may also login via your Email account by simply supplying your Username and Email Address.
If the Username and Email Address match a valid account, an email will be sent to you with a link
that you can click on to automatically log you in without requiring a password.
</p>
{{ end }}
</div>
</article>
{{ end }}

8
internal/theme/templates/login_email.html

@ -12,12 +12,10 @@
<button type="submit">{{ tr . "LoginFormEmailLogin" }}</button>
<p>
{{ tr . "LoginNoAccountTitle" }}
{{ if .RegisterDisabled }}
<a href="#" title="{{ with .RegisterDisabledMessage }}{{ .RegisterDisabledMessage }}{{ else }}{{ tr . "RegisterDisabledMessage" }}{{ end }}">{{ tr . "RegisterLinkTitle" }}</a>
{{ else }}
<a href="/register">{{ tr . "RegisterLinkTitle" }}</a>
{{ if not .RegisterDisabled }}
<a href="/register">{{ tr . "RegisterLinkTitle" }}</a>
{{ tr . "Instead" }}
{{ end }}
{{ tr . "Instead" }}
</p>
<p><a href="/login">{{ tr . "LoginViaUsernamePassword" }}</a></p>
</form>

47
internal/theme/templates/partials.html

@ -115,7 +115,7 @@
{{ dateInZone (formatForDateTime $.Twt.Created $.User.DisplayTimeFormat) $.Twt.Created $.User.DisplayDatesInTimezone }}
</time>
</a>
<span>({{ $.Twt.Created | time }})</span>
<span>&nbsp;({{ $.Twt.Created | time }})</span>
{{ if $.Authenticated }}
<span>
<a class="bookmarkBtn" style="display: {{ if not ($.User.Bookmarked $.Twt.Hash) }}inline{{ else }}none{{ end }};" href="/bookmark/{{ $.Twt.Hash }}" title="{{tr $.Ctx "BookmarkAddTwt"}}">
@ -130,11 +130,33 @@
</div>
</div>
<div class="p-summary">
{{ if not (eq $.view "conv") }}
{{ with urlForRootConv $.Twt }}
{{ $rootTwt := getRootTwt $.Twt $.User }}
<small class="twt-context">
&rdsh;
<a href="{{ urlForRootConv $.Twt }}#{{ $.Twt.Hash }}" title="Show conversation for #{{ $rootTwt.Hash }}">
{{ if $.User.Is $rootTwt.Twter.URL }}
{{ tr $.Ctx "MeLinkTitle" }}
{{ else }}
{{ if isLocalURL $rootTwt.Twter.URL }}
{{ $rootTwt.Twter.Nick }}
{{ else }}
{{ $rootTwt.Twter.Nick }}&commat;{{ $rootTwt.Twter.URL | hostnameFromURL }}
{{ end }}
{{ end }}
</a>
&gt;
{{ formatTwtContext $.Twt $.User }}
</small>
<!--</a>-->
{{- end -}}
{{- end -}}
{{ formatTwt $.Twt $.User }}
</div>
<hr />
<a href="/search?tag={{ $.Twt.Hash }}" title="Search for this twt hash"><em class="twt-hash">#{{ $.Twt.Hash }}</em></a>
<nav>
<nav class="twt-nav">
<ul>
{{ if $.Authenticated }}