mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-26 13:46:39 +00:00
Compare commits
4 commits
7a7f8b6112
...
d04a89e3af
Author | SHA1 | Date | |
---|---|---|---|
d04a89e3af | |||
3f7dc10449 | |||
c17abea921 | |||
c8fb4c17f1 |
|
@ -167,3 +167,11 @@ Links to the set contact account and/or email address will appear on the footer
|
||||||
The selected **contact user** must be an active (not suspended) admin and/or moderator on the instance.
|
The selected **contact user** must be an active (not suspended) admin and/or moderator on the instance.
|
||||||
|
|
||||||
If you're on a single-user instance and you give admin privileges to your main account, you can just fill in your own username here; you don't need to make a separate admin account just for this.
|
If you're on a single-user instance and you give admin privileges to your main account, you can just fill in your own username here; you don't need to make a separate admin account just for this.
|
||||||
|
|
||||||
|
### Instance Custom CSS
|
||||||
|
|
||||||
|
custom CSS allows you to further customize the way your instance looks when visited through a browser.
|
||||||
|
|
||||||
|
This custom CSS will be applied to all pages of your instance. Users themes and CSS still take precedence over this customization.
|
||||||
|
|
||||||
|
See the [Custom CSS](./custom_css.md) page for some tips on writing custom CSS for your instance.
|
||||||
|
|
|
@ -1545,6 +1545,10 @@ definitions:
|
||||||
$ref: '#/definitions/instanceV1Configuration'
|
$ref: '#/definitions/instanceV1Configuration'
|
||||||
contact_account:
|
contact_account:
|
||||||
$ref: '#/definitions/account'
|
$ref: '#/definitions/account'
|
||||||
|
custom_css:
|
||||||
|
description: Custom CSS for the instance.
|
||||||
|
type: string
|
||||||
|
x-go-name: CustomCSS
|
||||||
debug:
|
debug:
|
||||||
description: Whether or not instance is running in DEBUG mode. Omitted if false.
|
description: Whether or not instance is running in DEBUG mode. Omitted if false.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
@ -1725,6 +1729,10 @@ definitions:
|
||||||
$ref: '#/definitions/instanceV2Configuration'
|
$ref: '#/definitions/instanceV2Configuration'
|
||||||
contact:
|
contact:
|
||||||
$ref: '#/definitions/instanceV2Contact'
|
$ref: '#/definitions/instanceV2Contact'
|
||||||
|
custom_css:
|
||||||
|
description: Instance Custom Css
|
||||||
|
type: string
|
||||||
|
x-go-name: CustomCSS
|
||||||
debug:
|
debug:
|
||||||
description: Whether or not instance is running in DEBUG mode. Omitted if false.
|
description: Whether or not instance is running in DEBUG mode. Omitted if false.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 229 KiB |
|
@ -8,6 +8,18 @@ In order to make GoToSocial email sending work, you need an smtp-compatible mail
|
||||||
|
|
||||||
To validate your configuration, you can use the "Administration -> Actions -> Email" section of the settings panel to send a test email.
|
To validate your configuration, you can use the "Administration -> Actions -> Email" section of the settings panel to send a test email.
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
Pending an smtp library update, currently only email providers that work with STARTTLS will work with GoToSocial. STARTTLS is generally available over **port 587**.
|
||||||
|
|
||||||
|
For more info, see:
|
||||||
|
|
||||||
|
- [STARTTLS vs SSL vs TLS](https://mailtrap.io/blog/starttls-ssl-tls/)
|
||||||
|
- [Understanding Ports](https://www.mailgun.com/blog/email/which-smtp-port-understanding-ports-25-465-587/)
|
||||||
|
- [Port 587](https://www.mailgun.com/blog/deliverability/smtp-port-587/)
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
For safety reasons, the smtp library used by GoToSocial will refuse to send authentication credentials over an unencrypted connection, unless the mail provider is running on localhost.
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
The configuration options for smtp are as follows:
|
The configuration options for smtp are as follows:
|
||||||
|
@ -26,6 +38,7 @@ The configuration options for smtp are as follows:
|
||||||
smtp-host: ""
|
smtp-host: ""
|
||||||
|
|
||||||
# Int. Port to use to connect to the smtp server.
|
# Int. Port to use to connect to the smtp server.
|
||||||
|
# In the majority of cases, you should use port 587.
|
||||||
# Examples: []
|
# Examples: []
|
||||||
# Default: 0
|
# Default: 0
|
||||||
smtp-port: 0
|
smtp-port: 0
|
||||||
|
@ -63,27 +76,16 @@ smtp-disclose-recipients: false
|
||||||
|
|
||||||
Note that if you don't set `Host`, then email sending via smtp will be disabled, and the other settings will be ignored. GoToSocial will still log (at trace level) emails that *would* have been sent if smtp was enabled.
|
Note that if you don't set `Host`, then email sending via smtp will be disabled, and the other settings will be ignored. GoToSocial will still log (at trace level) emails that *would* have been sent if smtp was enabled.
|
||||||
|
|
||||||
## Behavior
|
## When are emails sent?
|
||||||
|
|
||||||
### SSL
|
|
||||||
|
|
||||||
GoToSocial requires your smtp server to present valid SSL certificates. Most of the big services like Mailgun do this anyway, but if you're running your own mail server without SSL for some reason, and you're trying to connect GoToSocial to it, it will not work.
|
|
||||||
|
|
||||||
The exception to this requirement is if you're running your mail server (or bridge to a mail server) on `localhost`, in which case SSL certs are not required.
|
|
||||||
|
|
||||||
### When are emails sent?
|
|
||||||
|
|
||||||
Currently, emails are sent:
|
Currently, emails are sent:
|
||||||
|
|
||||||
- To the provided email address of a new user to request email confirmation when a new account is created via the API.
|
- To the provided email address of a new user to request email confirmation when a new account is created via the sign up page or API.
|
||||||
|
- To instance admins when a new account is created in this way.
|
||||||
- To all active instance moderators + admins when a new moderation report is received. By default, recipients are Bcc'd, but you can change this behavior with the setting `smtp-disclose-recipients`.
|
- To all active instance moderators + admins when a new moderation report is received. By default, recipients are Bcc'd, but you can change this behavior with the setting `smtp-disclose-recipients`.
|
||||||
- To the creator of a report (on this instance) when the report is closed by a moderator.
|
- To the creator of a report (on this instance) when the report is closed by a moderator.
|
||||||
|
|
||||||
### Can I test if my SMTP configuration is correct?
|
## HTML versus Plaintext
|
||||||
|
|
||||||
Yes, you can use the API to send a test email to yourself. Check the API documentation for the `/api/v1/admin/email/test` endpoint.
|
|
||||||
|
|
||||||
### HTML versus Plaintext
|
|
||||||
|
|
||||||
Emails are sent in plaintext by default. At this point, there is no option to send emails in html, but this is something that might be added later if there's enough demand for it.
|
Emails are sent in plaintext by default. At this point, there is no option to send emails in html, but this is something that might be added later if there's enough demand for it.
|
||||||
|
|
||||||
|
|
|
@ -817,6 +817,7 @@ oidc-admin-groups: []
|
||||||
smtp-host: ""
|
smtp-host: ""
|
||||||
|
|
||||||
# Int. Port to use to connect to the smtp server.
|
# Int. Port to use to connect to the smtp server.
|
||||||
|
# In the majority of cases, you should use port 587.
|
||||||
# Examples: []
|
# Examples: []
|
||||||
# Default: 0
|
# Default: 0
|
||||||
smtp-port: 0
|
smtp-port: 0
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -22,7 +22,7 @@ require (
|
||||||
codeberg.org/gruf/go-runners v1.6.3
|
codeberg.org/gruf/go-runners v1.6.3
|
||||||
codeberg.org/gruf/go-sched v1.2.4
|
codeberg.org/gruf/go-sched v1.2.4
|
||||||
codeberg.org/gruf/go-storage v0.2.0
|
codeberg.org/gruf/go-storage v0.2.0
|
||||||
codeberg.org/gruf/go-structr v0.8.10
|
codeberg.org/gruf/go-structr v0.8.11
|
||||||
codeberg.org/superseriousbusiness/exif-terminator v0.9.0
|
codeberg.org/superseriousbusiness/exif-terminator v0.9.0
|
||||||
github.com/DmitriyVTitov/size v1.5.0
|
github.com/DmitriyVTitov/size v1.5.0
|
||||||
github.com/KimMachineGun/automemlimit v0.6.1
|
github.com/KimMachineGun/automemlimit v0.6.1
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -72,8 +72,8 @@ codeberg.org/gruf/go-sched v1.2.4 h1:ddBB9o0D/2oU8NbQ0ldN5aWxogpXPRBATWi58+p++Hw
|
||||||
codeberg.org/gruf/go-sched v1.2.4/go.mod h1:wad6l+OcYGWMA2TzNLMmLObsrbBDxdJfEy5WvTgBjNk=
|
codeberg.org/gruf/go-sched v1.2.4/go.mod h1:wad6l+OcYGWMA2TzNLMmLObsrbBDxdJfEy5WvTgBjNk=
|
||||||
codeberg.org/gruf/go-storage v0.2.0 h1:mKj3Lx6AavEkuXXtxqPhdq+akW9YwrnP16yQBF7K5ZI=
|
codeberg.org/gruf/go-storage v0.2.0 h1:mKj3Lx6AavEkuXXtxqPhdq+akW9YwrnP16yQBF7K5ZI=
|
||||||
codeberg.org/gruf/go-storage v0.2.0/go.mod h1:o3GzMDE5QNUaRnm/daUzFqvuAaC4utlgXDXYO79sWKU=
|
codeberg.org/gruf/go-storage v0.2.0/go.mod h1:o3GzMDE5QNUaRnm/daUzFqvuAaC4utlgXDXYO79sWKU=
|
||||||
codeberg.org/gruf/go-structr v0.8.10 h1:uSapW97/StRnYEhCtycaM0isCsEMYC+tx/knYr6SiVo=
|
codeberg.org/gruf/go-structr v0.8.11 h1:I3cQCHpK3fQSXWaaUfksAJRN4+efULiuF11Oi/m8c+o=
|
||||||
codeberg.org/gruf/go-structr v0.8.10/go.mod h1:zkoXVrAnKosh8VFAsbP/Hhs8FmLBjbVVy5w/Ngm8ApM=
|
codeberg.org/gruf/go-structr v0.8.11/go.mod h1:zkoXVrAnKosh8VFAsbP/Hhs8FmLBjbVVy5w/Ngm8ApM=
|
||||||
codeberg.org/superseriousbusiness/exif-terminator v0.9.0 h1:/EfyGI6HIrbkhFwgXGSjZ9o1kr/+k8v4mKdfXTH02Go=
|
codeberg.org/superseriousbusiness/exif-terminator v0.9.0 h1:/EfyGI6HIrbkhFwgXGSjZ9o1kr/+k8v4mKdfXTH02Go=
|
||||||
codeberg.org/superseriousbusiness/exif-terminator v0.9.0/go.mod h1:gCWKduudUWFzsnixoMzu0FYVdxHWG+AbXnZ50DqxsUE=
|
codeberg.org/superseriousbusiness/exif-terminator v0.9.0/go.mod h1:gCWKduudUWFzsnixoMzu0FYVdxHWG+AbXnZ50DqxsUE=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
|
|
@ -175,6 +175,7 @@ func validateInstanceUpdate(form *apimodel.InstanceSettingsUpdateRequest) error
|
||||||
form.ContactEmail == nil &&
|
form.ContactEmail == nil &&
|
||||||
form.ShortDescription == nil &&
|
form.ShortDescription == nil &&
|
||||||
form.Description == nil &&
|
form.Description == nil &&
|
||||||
|
form.CustomCSS == nil &&
|
||||||
form.Terms == nil &&
|
form.Terms == nil &&
|
||||||
form.Avatar == nil &&
|
form.Avatar == nil &&
|
||||||
form.AvatarDescription == nil &&
|
form.AvatarDescription == nil &&
|
||||||
|
|
|
@ -33,6 +33,8 @@ type InstanceSettingsUpdateRequest struct {
|
||||||
ShortDescription *string `form:"short_description" json:"short_description" xml:"short_description"`
|
ShortDescription *string `form:"short_description" json:"short_description" xml:"short_description"`
|
||||||
// Longer description of the instance, max 5,000 chars. HTML formatting accepted.
|
// Longer description of the instance, max 5,000 chars. HTML formatting accepted.
|
||||||
Description *string `form:"description" json:"description" xml:"description"`
|
Description *string `form:"description" json:"description" xml:"description"`
|
||||||
|
// Custom CSS for the instance.
|
||||||
|
CustomCSS *string `form:"custom_css" json:"custom_css,omitempty" xml:"custom_css"`
|
||||||
// Terms and conditions of the instance, max 5,000 chars. HTML formatting accepted.
|
// Terms and conditions of the instance, max 5,000 chars. HTML formatting accepted.
|
||||||
Terms *string `form:"terms" json:"terms" xml:"terms"`
|
Terms *string `form:"terms" json:"terms" xml:"terms"`
|
||||||
// Image to use as the instance thumbnail.
|
// Image to use as the instance thumbnail.
|
||||||
|
|
|
@ -38,6 +38,8 @@ type InstanceV1 struct {
|
||||||
//
|
//
|
||||||
// This should be displayed on the 'about' page for an instance.
|
// This should be displayed on the 'about' page for an instance.
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
// Custom CSS for the instance.
|
||||||
|
CustomCSS string `json:"custom_css,omitempty"`
|
||||||
// Raw (unparsed) version of description.
|
// Raw (unparsed) version of description.
|
||||||
DescriptionText string `json:"description_text,omitempty"`
|
DescriptionText string `json:"description_text,omitempty"`
|
||||||
// A shorter description of the instance.
|
// A shorter description of the instance.
|
||||||
|
|
|
@ -53,6 +53,8 @@ type InstanceV2 struct {
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
// Raw (unparsed) version of description.
|
// Raw (unparsed) version of description.
|
||||||
DescriptionText string `json:"description_text,omitempty"`
|
DescriptionText string `json:"description_text,omitempty"`
|
||||||
|
// Instance Custom Css
|
||||||
|
CustomCSS string `json:"custom_css,omitempty"`
|
||||||
// Basic anonymous usage data for this instance.
|
// Basic anonymous usage data for this instance.
|
||||||
Usage InstanceV2Usage `json:"usage"`
|
Usage InstanceV2Usage `json:"usage"`
|
||||||
// An image used to represent this instance.
|
// An image used to represent this instance.
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
// GoToSocial
|
||||||
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/uptrace/bun"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
up := func(ctx context.Context, db *bun.DB) error {
|
||||||
|
_, err := db.ExecContext(ctx, "ALTER TABLE ? ADD COLUMN ? TEXT", bun.Ident("instances"), bun.Ident("custom_css"))
|
||||||
|
if err != nil && !(strings.Contains(err.Error(), "already exists") || strings.Contains(err.Error(), "duplicate column name") || strings.Contains(err.Error(), "SQLSTATE 42701")) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
down := func(ctx context.Context, db *bun.DB) error {
|
||||||
|
_, err := db.ExecContext(ctx, "ALTER TABLE ? DROP COLUMN ?", bun.Ident("instances"), bun.Ident("custom_css"))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Migrations.Register(up, down); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ type Instance struct {
|
||||||
ShortDescriptionText string `bun:""` // Raw text version of short description (before parsing).
|
ShortDescriptionText string `bun:""` // Raw text version of short description (before parsing).
|
||||||
Description string `bun:""` // Longer description of this instance.
|
Description string `bun:""` // Longer description of this instance.
|
||||||
DescriptionText string `bun:""` // Raw text version of long description (before parsing).
|
DescriptionText string `bun:""` // Raw text version of long description (before parsing).
|
||||||
|
CustomCSS string `bun:",nullzero"` // Custom CSS for the instance.
|
||||||
Terms string `bun:""` // Terms and conditions of this instance.
|
Terms string `bun:""` // Terms and conditions of this instance.
|
||||||
TermsText string `bun:""` // Raw text version of terms (before parsing).
|
TermsText string `bun:""` // Raw text version of terms (before parsing).
|
||||||
ContactEmail string `bun:""` // Contact email address for this instance
|
ContactEmail string `bun:""` // Contact email address for this instance
|
||||||
|
|
|
@ -227,6 +227,17 @@ func (p *Processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe
|
||||||
columns = append(columns, []string{"description", "description_text"}...)
|
columns = append(columns, []string{"description", "description_text"}...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validate & update site custom css if it's set on the form
|
||||||
|
if form.CustomCSS != nil {
|
||||||
|
customCSS := *form.CustomCSS
|
||||||
|
if err := validate.InstanceCustomCSS(customCSS); err != nil {
|
||||||
|
return nil, gtserror.NewErrorBadRequest(err, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.CustomCSS = text.SanitizeToPlaintext(customCSS)
|
||||||
|
columns = append(columns, []string{"custom_css"}...)
|
||||||
|
}
|
||||||
|
|
||||||
// Validate & update site
|
// Validate & update site
|
||||||
// terms if set on the form.
|
// terms if set on the form.
|
||||||
if form.Terms != nil {
|
if form.Terms != nil {
|
||||||
|
|
|
@ -1523,6 +1523,7 @@ func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins
|
||||||
Title: i.Title,
|
Title: i.Title,
|
||||||
Description: i.Description,
|
Description: i.Description,
|
||||||
DescriptionText: i.DescriptionText,
|
DescriptionText: i.DescriptionText,
|
||||||
|
CustomCSS: i.CustomCSS,
|
||||||
ShortDescription: i.ShortDescription,
|
ShortDescription: i.ShortDescription,
|
||||||
ShortDescriptionText: i.ShortDescriptionText,
|
ShortDescriptionText: i.ShortDescriptionText,
|
||||||
Email: i.ContactEmail,
|
Email: i.ContactEmail,
|
||||||
|
@ -1644,6 +1645,7 @@ func (c *Converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins
|
||||||
SourceURL: instanceSourceURL,
|
SourceURL: instanceSourceURL,
|
||||||
Description: i.Description,
|
Description: i.Description,
|
||||||
DescriptionText: i.DescriptionText,
|
DescriptionText: i.DescriptionText,
|
||||||
|
CustomCSS: i.CustomCSS,
|
||||||
Usage: apimodel.InstanceV2Usage{}, // todo: not implemented
|
Usage: apimodel.InstanceV2Usage{}, // todo: not implemented
|
||||||
Languages: config.GetInstanceLanguages().TagStrs(),
|
Languages: config.GetInstanceLanguages().TagStrs(),
|
||||||
Rules: c.InstanceRulesToAPIRules(i.Rules),
|
Rules: c.InstanceRulesToAPIRules(i.Rules),
|
||||||
|
|
|
@ -189,6 +189,16 @@ func CustomCSS(customCSS string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InstanceCustomCSS(customCSS string) error {
|
||||||
|
|
||||||
|
maximumCustomCSSLength := config.GetAccountsCustomCSSLength()
|
||||||
|
if length := len([]rune(customCSS)); length > maximumCustomCSSLength {
|
||||||
|
return fmt.Errorf("custom_css must be less than %d characters, but submitted custom_css was %d characters", maximumCustomCSSLength, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// EmojiShortcode just runs the given shortcode through the regular expression
|
// EmojiShortcode just runs the given shortcode through the regular expression
|
||||||
// for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 2-30 characters,
|
// for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 2-30 characters,
|
||||||
// a-zA-Z, numbers, and underscores.
|
// a-zA-Z, numbers, and underscores.
|
||||||
|
|
|
@ -54,7 +54,7 @@ func (m *Module) aboutGETHandler(c *gin.Context) {
|
||||||
Template: "about.tmpl",
|
Template: "about.tmpl",
|
||||||
Instance: instance,
|
Instance: instance,
|
||||||
OGMeta: apiutil.OGBase(instance),
|
OGMeta: apiutil.OGBase(instance),
|
||||||
Stylesheets: []string{cssAbout},
|
Stylesheets: []string{cssAbout, instanceCustomCSSPath},
|
||||||
Extra: map[string]any{
|
Extra: map[string]any{
|
||||||
"showStrap": true,
|
"showStrap": true,
|
||||||
"blocklistExposed": config.GetInstanceExposeSuspendedWeb(),
|
"blocklistExposed": config.GetInstanceExposeSuspendedWeb(),
|
||||||
|
|
|
@ -127,8 +127,9 @@ func (m *Module) confirmEmailPOSTHandler(c *gin.Context) {
|
||||||
// Serve page informing user that their
|
// Serve page informing user that their
|
||||||
// email address is now confirmed.
|
// email address is now confirmed.
|
||||||
page := apiutil.WebPage{
|
page := apiutil.WebPage{
|
||||||
Template: "confirmed_email.tmpl",
|
Template: "confirmed_email.tmpl",
|
||||||
Instance: instance,
|
Instance: instance,
|
||||||
|
Stylesheets: []string{instanceCustomCSSPath},
|
||||||
Extra: map[string]any{
|
Extra: map[string]any{
|
||||||
"email": user.Email,
|
"email": user.Email,
|
||||||
"username": user.Account.Username,
|
"username": user.Account.Username,
|
||||||
|
|
|
@ -55,3 +55,22 @@ func (m *Module) customCSSGETHandler(c *gin.Context) {
|
||||||
c.Header(cacheControlHeader, cacheControlNoCache)
|
c.Header(cacheControlHeader, cacheControlNoCache)
|
||||||
c.Data(http.StatusOK, textCSSUTF8, []byte(customCSS))
|
c.Data(http.StatusOK, textCSSUTF8, []byte(customCSS))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Module) instanceCustomCSSGETHandler(c *gin.Context) {
|
||||||
|
|
||||||
|
if _, err := apiutil.NegotiateAccept(c, apiutil.TextCSS); err != nil {
|
||||||
|
apiutil.WebErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceV1, errWithCode := m.processor.InstanceGetV1(c.Request.Context())
|
||||||
|
if errWithCode != nil {
|
||||||
|
apiutil.WebErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceCustomCSS := instanceV1.CustomCSS
|
||||||
|
|
||||||
|
c.Header(cacheControlHeader, cacheControlNoCache)
|
||||||
|
c.Data(http.StatusOK, textCSSUTF8, []byte(instanceCustomCSS))
|
||||||
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ func (m *Module) domainBlockListGETHandler(c *gin.Context) {
|
||||||
Template: "domain-blocklist.tmpl",
|
Template: "domain-blocklist.tmpl",
|
||||||
Instance: instance,
|
Instance: instance,
|
||||||
OGMeta: apiutil.OGBase(instance),
|
OGMeta: apiutil.OGBase(instance),
|
||||||
Stylesheets: []string{cssFA},
|
Stylesheets: []string{cssFA, instanceCustomCSSPath},
|
||||||
Javascript: []string{jsFrontend},
|
Javascript: []string{jsFrontend},
|
||||||
Extra: map[string]any{"blocklist": domainBlocks},
|
Extra: map[string]any{"blocklist": domainBlocks},
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ func (m *Module) indexHandler(c *gin.Context) {
|
||||||
Template: "index.tmpl",
|
Template: "index.tmpl",
|
||||||
Instance: instance,
|
Instance: instance,
|
||||||
OGMeta: apiutil.OGBase(instance),
|
OGMeta: apiutil.OGBase(instance),
|
||||||
Stylesheets: []string{cssAbout, cssIndex},
|
Stylesheets: []string{cssAbout, cssIndex, instanceCustomCSSPath},
|
||||||
Extra: map[string]any{"showStrap": true},
|
Extra: map[string]any{"showStrap": true},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -132,7 +132,7 @@ func (m *Module) profileGETHandler(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare stylesheets for profile.
|
// Prepare stylesheets for profile.
|
||||||
stylesheets := make([]string, 0, 6)
|
stylesheets := make([]string, 0, 7)
|
||||||
|
|
||||||
// Basic profile stylesheets.
|
// Basic profile stylesheets.
|
||||||
stylesheets = append(
|
stylesheets = append(
|
||||||
|
@ -142,6 +142,7 @@ func (m *Module) profileGETHandler(c *gin.Context) {
|
||||||
cssStatus,
|
cssStatus,
|
||||||
cssThread,
|
cssThread,
|
||||||
cssProfile,
|
cssProfile,
|
||||||
|
instanceCustomCSSPath,
|
||||||
}...,
|
}...,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,7 @@ func (m *Module) SettingsPanelHandler(c *gin.Context) {
|
||||||
cssProfile, // Used for rendering stub/fake profiles.
|
cssProfile, // Used for rendering stub/fake profiles.
|
||||||
cssStatus, // Used for rendering stub/fake statuses.
|
cssStatus, // Used for rendering stub/fake statuses.
|
||||||
cssSettings,
|
cssSettings,
|
||||||
|
instanceCustomCSSPath,
|
||||||
},
|
},
|
||||||
Javascript: []string{jsSettings},
|
Javascript: []string{jsSettings},
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,9 +126,10 @@ func (m *Module) signupPOSTHandler(c *gin.Context) {
|
||||||
// Serve a page informing the
|
// Serve a page informing the
|
||||||
// user that they've signed up.
|
// user that they've signed up.
|
||||||
page := apiutil.WebPage{
|
page := apiutil.WebPage{
|
||||||
Template: "signed-up.tmpl",
|
Template: "signed-up.tmpl",
|
||||||
Instance: instance,
|
Instance: instance,
|
||||||
OGMeta: apiutil.OGBase(instance),
|
Stylesheets: []string{instanceCustomCSSPath},
|
||||||
|
OGMeta: apiutil.OGBase(instance),
|
||||||
Extra: map[string]any{
|
Extra: map[string]any{
|
||||||
"email": user.UnconfirmedEmail,
|
"email": user.UnconfirmedEmail,
|
||||||
"username": user.Account.Username,
|
"username": user.Account.Username,
|
||||||
|
|
|
@ -59,7 +59,7 @@ func (m *Module) tagGETHandler(c *gin.Context) {
|
||||||
Template: "tag.tmpl",
|
Template: "tag.tmpl",
|
||||||
Instance: instance,
|
Instance: instance,
|
||||||
OGMeta: apiutil.OGBase(instance),
|
OGMeta: apiutil.OGBase(instance),
|
||||||
Stylesheets: []string{cssFA, cssThread, cssTag},
|
Stylesheets: []string{cssFA, cssThread, cssTag, instanceCustomCSSPath},
|
||||||
Extra: map[string]any{"tagName": tagName},
|
Extra: map[string]any{"tagName": tagName},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,7 @@ func (m *Module) threadGETHandler(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare stylesheets for thread.
|
// Prepare stylesheets for thread.
|
||||||
stylesheets := make([]string, 0, 5)
|
stylesheets := make([]string, 0, 6)
|
||||||
|
|
||||||
// Basic thread stylesheets.
|
// Basic thread stylesheets.
|
||||||
stylesheets = append(
|
stylesheets = append(
|
||||||
|
@ -131,6 +131,7 @@ func (m *Module) threadGETHandler(c *gin.Context) {
|
||||||
if theme := targetAccount.Theme; theme != "" {
|
if theme := targetAccount.Theme; theme != "" {
|
||||||
stylesheets = append(
|
stylesheets = append(
|
||||||
stylesheets,
|
stylesheets,
|
||||||
|
instanceCustomCSSPath,
|
||||||
themesPathPrefix+"/"+theme,
|
themesPathPrefix+"/"+theme,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,20 +36,21 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
confirmEmailPath = "/" + uris.ConfirmEmailPath
|
confirmEmailPath = "/" + uris.ConfirmEmailPath
|
||||||
profileGroupPath = "/@:username"
|
profileGroupPath = "/@:username"
|
||||||
statusPath = "/statuses/:" + apiutil.WebStatusIDKey // leave out the '/@:username' prefix as this will be served within the profile group
|
statusPath = "/statuses/:" + apiutil.WebStatusIDKey // leave out the '/@:username' prefix as this will be served within the profile group
|
||||||
tagsPath = "/tags/:" + apiutil.TagNameKey
|
tagsPath = "/tags/:" + apiutil.TagNameKey
|
||||||
customCSSPath = profileGroupPath + "/custom.css"
|
customCSSPath = profileGroupPath + "/custom.css"
|
||||||
rssFeedPath = profileGroupPath + "/feed.rss"
|
instanceCustomCSSPath = "/custom.css"
|
||||||
assetsPathPrefix = "/assets"
|
rssFeedPath = profileGroupPath + "/feed.rss"
|
||||||
distPathPrefix = assetsPathPrefix + "/dist"
|
assetsPathPrefix = "/assets"
|
||||||
themesPathPrefix = assetsPathPrefix + "/themes"
|
distPathPrefix = assetsPathPrefix + "/dist"
|
||||||
settingsPathPrefix = "/settings"
|
themesPathPrefix = assetsPathPrefix + "/themes"
|
||||||
settingsPanelGlob = settingsPathPrefix + "/*panel"
|
settingsPathPrefix = "/settings"
|
||||||
userPanelPath = settingsPathPrefix + "/user"
|
settingsPanelGlob = settingsPathPrefix + "/*panel"
|
||||||
adminPanelPath = settingsPathPrefix + "/admin"
|
userPanelPath = settingsPathPrefix + "/user"
|
||||||
signupPath = "/signup"
|
adminPanelPath = settingsPathPrefix + "/admin"
|
||||||
|
signupPath = "/signup"
|
||||||
|
|
||||||
cacheControlHeader = "Cache-Control" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
|
cacheControlHeader = "Cache-Control" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
|
||||||
cacheControlNoCache = "no-cache" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#response_directives
|
cacheControlNoCache = "no-cache" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#response_directives
|
||||||
|
@ -114,6 +115,7 @@ func (m *Module) Route(r *router.Router, mi ...gin.HandlerFunc) {
|
||||||
r.AttachHandler(http.MethodGet, settingsPathPrefix, m.SettingsPanelHandler)
|
r.AttachHandler(http.MethodGet, settingsPathPrefix, m.SettingsPanelHandler)
|
||||||
r.AttachHandler(http.MethodGet, settingsPanelGlob, m.SettingsPanelHandler)
|
r.AttachHandler(http.MethodGet, settingsPanelGlob, m.SettingsPanelHandler)
|
||||||
r.AttachHandler(http.MethodGet, customCSSPath, m.customCSSGETHandler)
|
r.AttachHandler(http.MethodGet, customCSSPath, m.customCSSGETHandler)
|
||||||
|
r.AttachHandler(http.MethodGet, instanceCustomCSSPath, m.instanceCustomCSSGETHandler)
|
||||||
r.AttachHandler(http.MethodGet, rssFeedPath, m.rssFeedGETHandler)
|
r.AttachHandler(http.MethodGet, rssFeedPath, m.rssFeedGETHandler)
|
||||||
r.AttachHandler(http.MethodGet, confirmEmailPath, m.confirmEmailGETHandler)
|
r.AttachHandler(http.MethodGet, confirmEmailPath, m.confirmEmailGETHandler)
|
||||||
r.AttachHandler(http.MethodPost, confirmEmailPath, m.confirmEmailPOSTHandler)
|
r.AttachHandler(http.MethodPost, confirmEmailPath, m.confirmEmailPOSTHandler)
|
||||||
|
|
47
vendor/codeberg.org/gruf/go-structr/cache.go
generated
vendored
47
vendor/codeberg.org/gruf/go-structr/cache.go
generated
vendored
|
@ -119,9 +119,9 @@ func (c *Cache[T]) Init(config CacheConfig[T]) {
|
||||||
|
|
||||||
// Index selects index with given name from cache, else panics.
|
// Index selects index with given name from cache, else panics.
|
||||||
func (c *Cache[T]) Index(name string) *Index {
|
func (c *Cache[T]) Index(name string) *Index {
|
||||||
for i := range c.indices {
|
for i, idx := range c.indices {
|
||||||
if c.indices[i].name == name {
|
if idx.name == name {
|
||||||
return &c.indices[i]
|
return &(c.indices[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic("unknown index: " + name)
|
panic("unknown index: " + name)
|
||||||
|
@ -337,13 +337,16 @@ func (c *Cache[T]) Load(index *Index, keys []Key, load func([]Key) ([]T, error))
|
||||||
panic("not initialized")
|
panic("not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(keys); {
|
// Iterate keys and catch uncached.
|
||||||
|
toLoad := make([]Key, 0, len(keys))
|
||||||
|
for _, key := range keys {
|
||||||
|
|
||||||
// Value length before
|
// Value length before
|
||||||
// any below appends.
|
// any below appends.
|
||||||
before := len(values)
|
before := len(values)
|
||||||
|
|
||||||
// Concatenate all *values* from cached items.
|
// Concatenate all *values* from cached items.
|
||||||
index.get(keys[i].key, func(item *indexed_item) {
|
index.get(key.key, func(item *indexed_item) {
|
||||||
if value, ok := item.data.(T); ok {
|
if value, ok := item.data.(T); ok {
|
||||||
// Append value COPY.
|
// Append value COPY.
|
||||||
value = c.copy(value)
|
value = c.copy(value)
|
||||||
|
@ -358,30 +361,22 @@ func (c *Cache[T]) Load(index *Index, keys []Key, load func([]Key) ([]T, error))
|
||||||
|
|
||||||
// Only if values changed did
|
// Only if values changed did
|
||||||
// we actually find anything.
|
// we actually find anything.
|
||||||
if len(values) != before {
|
if len(values) == before {
|
||||||
|
toLoad = append(toLoad, key)
|
||||||
// We found values at key,
|
|
||||||
// drop key from the slice.
|
|
||||||
copy(keys[i:], keys[i+1:])
|
|
||||||
keys = keys[:len(keys)-1]
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iter
|
|
||||||
i++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Done with
|
// Done with
|
||||||
// the lock.
|
// the lock.
|
||||||
unlock()
|
unlock()
|
||||||
|
|
||||||
if len(keys) == 0 {
|
if len(toLoad) == 0 {
|
||||||
// We loaded everything!
|
// We loaded everything!
|
||||||
return values, nil
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load uncached values.
|
// Load uncached key values.
|
||||||
uncached, err := load(keys)
|
uncached, err := load(toLoad)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -515,8 +510,8 @@ func (c *Cache[T]) Trim(perc float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compact index data stores.
|
// Compact index data stores.
|
||||||
for i := range c.indices {
|
for _, idx := range c.indices {
|
||||||
c.indices[i].data.Compact()
|
(&idx).data.Compact()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Done with lock.
|
// Done with lock.
|
||||||
|
@ -536,17 +531,17 @@ func (c *Cache[T]) Len() int {
|
||||||
|
|
||||||
// Debug returns debug stats about cache.
|
// Debug returns debug stats about cache.
|
||||||
func (c *Cache[T]) Debug() map[string]any {
|
func (c *Cache[T]) Debug() map[string]any {
|
||||||
m := make(map[string]any)
|
m := make(map[string]any, 2)
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
m["lru"] = c.lru.len
|
m["lru"] = c.lru.len
|
||||||
indices := make(map[string]any)
|
indices := make(map[string]any, len(c.indices))
|
||||||
m["indices"] = indices
|
m["indices"] = indices
|
||||||
for i := range c.indices {
|
for _, idx := range c.indices {
|
||||||
var n uint64
|
var n uint64
|
||||||
for _, l := range c.indices[i].data.m {
|
for _, l := range idx.data.m {
|
||||||
n += uint64(l.len)
|
n += uint64(l.len)
|
||||||
}
|
}
|
||||||
indices[c.indices[i].name] = n
|
indices[idx.name] = n
|
||||||
}
|
}
|
||||||
c.mutex.Unlock()
|
c.mutex.Unlock()
|
||||||
return m
|
return m
|
||||||
|
@ -588,7 +583,7 @@ func (c *Cache[T]) store_value(index *Index, key string, value T) {
|
||||||
|
|
||||||
for i := range c.indices {
|
for i := range c.indices {
|
||||||
// Get current index ptr.
|
// Get current index ptr.
|
||||||
idx := &(c.indices[i])
|
idx := (&c.indices[i])
|
||||||
if idx == index {
|
if idx == index {
|
||||||
|
|
||||||
// Already stored under
|
// Already stored under
|
||||||
|
|
42
vendor/codeberg.org/gruf/go-structr/index.go
generated
vendored
42
vendor/codeberg.org/gruf/go-structr/index.go
generated
vendored
|
@ -197,8 +197,13 @@ func (i *Index) get(key string, hook func(*indexed_item)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate all entries in list.
|
// Iterate the list.
|
||||||
l.rangefn(func(elem *list_elem) {
|
for elem := l.head; //
|
||||||
|
elem != nil; //
|
||||||
|
{
|
||||||
|
// Get next before
|
||||||
|
// any modification.
|
||||||
|
next := elem.next
|
||||||
|
|
||||||
// Extract element entry + item.
|
// Extract element entry + item.
|
||||||
entry := (*index_entry)(elem.data)
|
entry := (*index_entry)(elem.data)
|
||||||
|
@ -206,18 +211,21 @@ func (i *Index) get(key string, hook func(*indexed_item)) {
|
||||||
|
|
||||||
// Pass to hook.
|
// Pass to hook.
|
||||||
hook(item)
|
hook(item)
|
||||||
})
|
|
||||||
|
// Set next.
|
||||||
|
elem = next
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// key uses hasher to generate Key{} from given raw parts.
|
// key uses hasher to generate Key{} from given raw parts.
|
||||||
func (i *Index) key(buf *byteutil.Buffer, parts []unsafe.Pointer) string {
|
func (i *Index) key(buf *byteutil.Buffer, parts []unsafe.Pointer) string {
|
||||||
|
buf.B = buf.B[:0]
|
||||||
if len(parts) != len(i.fields) {
|
if len(parts) != len(i.fields) {
|
||||||
panicf("incorrect number key parts: want=%d received=%d",
|
panicf("incorrect number key parts: want=%d received=%d",
|
||||||
len(i.fields),
|
len(i.fields),
|
||||||
len(parts),
|
len(parts),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
buf.B = buf.B[:0]
|
|
||||||
if !allow_zero(i.flags) {
|
if !allow_zero(i.flags) {
|
||||||
for x, field := range i.fields {
|
for x, field := range i.fields {
|
||||||
before := len(buf.B)
|
before := len(buf.B)
|
||||||
|
@ -301,8 +309,13 @@ func (i *Index) delete(key string, hook func(*indexed_item)) {
|
||||||
// Delete at hash.
|
// Delete at hash.
|
||||||
i.data.Delete(key)
|
i.data.Delete(key)
|
||||||
|
|
||||||
// Iterate entries in list.
|
// Iterate the list.
|
||||||
l.rangefn(func(elem *list_elem) {
|
for elem := l.head; //
|
||||||
|
elem != nil; //
|
||||||
|
{
|
||||||
|
// Get next before
|
||||||
|
// any modification.
|
||||||
|
next := elem.next
|
||||||
|
|
||||||
// Remove elem.
|
// Remove elem.
|
||||||
l.remove(elem)
|
l.remove(elem)
|
||||||
|
@ -319,7 +332,10 @@ func (i *Index) delete(key string, hook func(*indexed_item)) {
|
||||||
|
|
||||||
// Pass to hook.
|
// Pass to hook.
|
||||||
hook(item)
|
hook(item)
|
||||||
})
|
|
||||||
|
// Set next.
|
||||||
|
elem = next
|
||||||
|
}
|
||||||
|
|
||||||
// Release list.
|
// Release list.
|
||||||
free_list(l)
|
free_list(l)
|
||||||
|
@ -375,17 +391,21 @@ type index_entry struct {
|
||||||
func new_index_entry() *index_entry {
|
func new_index_entry() *index_entry {
|
||||||
v := index_entry_pool.Get()
|
v := index_entry_pool.Get()
|
||||||
if v == nil {
|
if v == nil {
|
||||||
v = new(index_entry)
|
e := new(index_entry)
|
||||||
|
e.elem.data = unsafe.Pointer(e)
|
||||||
|
v = e
|
||||||
}
|
}
|
||||||
entry := v.(*index_entry)
|
entry := v.(*index_entry)
|
||||||
ptr := unsafe.Pointer(entry)
|
|
||||||
entry.elem.data = ptr
|
|
||||||
return entry
|
return entry
|
||||||
}
|
}
|
||||||
|
|
||||||
// free_index_entry releases the index_entry.
|
// free_index_entry releases the index_entry.
|
||||||
func free_index_entry(entry *index_entry) {
|
func free_index_entry(entry *index_entry) {
|
||||||
entry.elem.data = nil
|
if entry.elem.next != nil ||
|
||||||
|
entry.elem.prev != nil {
|
||||||
|
should_not_reach()
|
||||||
|
return
|
||||||
|
}
|
||||||
entry.key = ""
|
entry.key = ""
|
||||||
entry.index = nil
|
entry.index = nil
|
||||||
entry.item = nil
|
entry.item = nil
|
||||||
|
|
16
vendor/codeberg.org/gruf/go-structr/item.go
generated
vendored
16
vendor/codeberg.org/gruf/go-structr/item.go
generated
vendored
|
@ -24,18 +24,22 @@ type indexed_item struct {
|
||||||
func new_indexed_item() *indexed_item {
|
func new_indexed_item() *indexed_item {
|
||||||
v := indexed_item_pool.Get()
|
v := indexed_item_pool.Get()
|
||||||
if v == nil {
|
if v == nil {
|
||||||
v = new(indexed_item)
|
i := new(indexed_item)
|
||||||
|
i.elem.data = unsafe.Pointer(i)
|
||||||
|
v = i
|
||||||
}
|
}
|
||||||
item := v.(*indexed_item)
|
item := v.(*indexed_item)
|
||||||
ptr := unsafe.Pointer(item)
|
|
||||||
item.elem.data = ptr
|
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
// free_indexed_item releases the indexed_item.
|
// free_indexed_item releases the indexed_item.
|
||||||
func free_indexed_item(item *indexed_item) {
|
func free_indexed_item(item *indexed_item) {
|
||||||
item.elem.data = nil
|
if len(item.indexed) > 0 ||
|
||||||
item.indexed = item.indexed[:0]
|
item.elem.next != nil ||
|
||||||
|
item.elem.prev != nil {
|
||||||
|
should_not_reach()
|
||||||
|
return
|
||||||
|
}
|
||||||
item.data = nil
|
item.data = nil
|
||||||
indexed_item_pool.Put(item)
|
indexed_item_pool.Put(item)
|
||||||
}
|
}
|
||||||
|
@ -50,7 +54,7 @@ func (i *indexed_item) drop_index(entry *index_entry) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move all index entries down + reslice.
|
// Reslice index entries minus 'x'.
|
||||||
_ = copy(i.indexed[x:], i.indexed[x+1:])
|
_ = copy(i.indexed[x:], i.indexed[x+1:])
|
||||||
i.indexed[len(i.indexed)-1] = nil
|
i.indexed[len(i.indexed)-1] = nil
|
||||||
i.indexed = i.indexed[:len(i.indexed)-1]
|
i.indexed = i.indexed[:len(i.indexed)-1]
|
||||||
|
|
52
vendor/codeberg.org/gruf/go-structr/list.go
generated
vendored
52
vendor/codeberg.org/gruf/go-structr/list.go
generated
vendored
|
@ -40,9 +40,12 @@ func new_list() *list {
|
||||||
|
|
||||||
// free_list releases the list.
|
// free_list releases the list.
|
||||||
func free_list(list *list) {
|
func free_list(list *list) {
|
||||||
list.head = nil
|
if list.head != nil ||
|
||||||
list.tail = nil
|
list.tail != nil ||
|
||||||
list.len = 0
|
list.len != 0 {
|
||||||
|
should_not_reach()
|
||||||
|
return
|
||||||
|
}
|
||||||
list_pool.Put(list)
|
list_pool.Put(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,21 +118,28 @@ func (l *list) remove(elem *list_elem) {
|
||||||
elem.prev = nil
|
elem.prev = nil
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
// elem is ONLY one in list.
|
case next == nil:
|
||||||
case next == nil && prev == nil:
|
if prev == nil {
|
||||||
l.head = nil
|
// next == nil && prev == nil
|
||||||
l.tail = nil
|
//
|
||||||
|
// elem is ONLY one in list.
|
||||||
|
l.head = nil
|
||||||
|
l.tail = nil
|
||||||
|
} else {
|
||||||
|
// next == nil && prev != nil
|
||||||
|
//
|
||||||
|
// elem is last in list.
|
||||||
|
l.tail = prev
|
||||||
|
prev.next = nil
|
||||||
|
}
|
||||||
|
|
||||||
// elem is front in list.
|
case prev == nil:
|
||||||
case next != nil && prev == nil:
|
// next != nil && prev == nil
|
||||||
|
//
|
||||||
|
// elem is front in list.
|
||||||
l.head = next
|
l.head = next
|
||||||
next.prev = nil
|
next.prev = nil
|
||||||
|
|
||||||
// elem is last in list.
|
|
||||||
case prev != nil && next == nil:
|
|
||||||
l.tail = prev
|
|
||||||
prev.next = nil
|
|
||||||
|
|
||||||
// elem in middle of list.
|
// elem in middle of list.
|
||||||
default:
|
default:
|
||||||
next.prev = prev
|
next.prev = prev
|
||||||
|
@ -139,17 +149,3 @@ func (l *list) remove(elem *list_elem) {
|
||||||
// Decr count
|
// Decr count
|
||||||
l.len--
|
l.len--
|
||||||
}
|
}
|
||||||
|
|
||||||
// rangefn will range all elems in list, passing each to fn.
|
|
||||||
func (l *list) rangefn(fn func(*list_elem)) {
|
|
||||||
if fn == nil {
|
|
||||||
panic("nil fn")
|
|
||||||
}
|
|
||||||
for e := l.head; //
|
|
||||||
e != nil; //
|
|
||||||
{
|
|
||||||
n := e.next
|
|
||||||
fn(e)
|
|
||||||
e = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
180
vendor/codeberg.org/gruf/go-structr/ordered_list.bak
generated
vendored
Normal file
180
vendor/codeberg.org/gruf/go-structr/ordered_list.bak
generated
vendored
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
package structr
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type Timeline[StructType any, PK comparable] struct {
|
||||||
|
|
||||||
|
// hook functions.
|
||||||
|
pkey func(StructType) PK
|
||||||
|
gte func(PK, PK) bool
|
||||||
|
lte func(PK, PK) bool
|
||||||
|
copy func(StructType) StructType
|
||||||
|
|
||||||
|
// main underlying
|
||||||
|
// ordered item list.
|
||||||
|
list list
|
||||||
|
|
||||||
|
// indices used in storing passed struct
|
||||||
|
// types by user defined sets of fields.
|
||||||
|
indices []Index
|
||||||
|
|
||||||
|
// protective mutex, guards:
|
||||||
|
// - TODO
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timeline[T, PK]) Init(config any) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timeline[T, PK]) Index(name string) *Index {
|
||||||
|
for i := range t.indices {
|
||||||
|
if t.indices[i].name == name {
|
||||||
|
return &t.indices[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("unknown index: " + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timeline[T, PK]) Insert(values ...T) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timeline[T, PK]) LoadTop(min, max PK, length int, load func(min, max PK, length int) ([]T, error)) ([]T, error) {
|
||||||
|
// Allocate expected no. values.
|
||||||
|
values := make([]T, 0, length)
|
||||||
|
|
||||||
|
// Acquire lock.
|
||||||
|
t.mutex.Lock()
|
||||||
|
|
||||||
|
// Wrap unlock to only do once.
|
||||||
|
unlock := once(t.mutex.Unlock)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
// Check init'd.
|
||||||
|
if t.copy == nil {
|
||||||
|
panic("not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through linked list from top (i.e. head).
|
||||||
|
for next := t.list.head; next != nil; next = next.next {
|
||||||
|
|
||||||
|
// Check if we've gathered
|
||||||
|
// enough values from timeline.
|
||||||
|
if len(values) >= length {
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
item := (*indexed_item)(next.data)
|
||||||
|
value := item.data.(T)
|
||||||
|
pkey := t.pkey(value)
|
||||||
|
|
||||||
|
// Check if below min.
|
||||||
|
if t.lte(pkey, min) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update min.
|
||||||
|
min = pkey
|
||||||
|
|
||||||
|
// Check if above max.
|
||||||
|
if t.gte(pkey, max) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append value copy.
|
||||||
|
value = t.copy(value)
|
||||||
|
values = append(values, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timeline[T, PK]) LoadBottom(min, max PK, length int, load func(min, max PK, length int) ([]T, error)) ([]T, error) {
|
||||||
|
// Allocate expected no. values.
|
||||||
|
values := make([]T, 0, length)
|
||||||
|
|
||||||
|
// Acquire lock.
|
||||||
|
t.mutex.Lock()
|
||||||
|
|
||||||
|
// Wrap unlock to only do once.
|
||||||
|
unlock := once(t.mutex.Unlock)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
// Check init'd.
|
||||||
|
if t.copy == nil {
|
||||||
|
panic("not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through linked list from bottom (i.e. tail).
|
||||||
|
for next := t.list.tail; next != nil; next = next.prev {
|
||||||
|
|
||||||
|
// Check if we've gathered
|
||||||
|
// enough values from timeline.
|
||||||
|
if len(values) >= length {
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
item := (*indexed_item)(next.data)
|
||||||
|
value := item.data.(T)
|
||||||
|
pkey := t.pkey(value)
|
||||||
|
|
||||||
|
// Check if above max.
|
||||||
|
if t.gte(pkey, max) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update max.
|
||||||
|
max = pkey
|
||||||
|
|
||||||
|
// Check if below min.
|
||||||
|
if t.lte(pkey, min) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append value copy.
|
||||||
|
value = t.copy(value)
|
||||||
|
values = append(values, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done with
|
||||||
|
// the lock.
|
||||||
|
unlock()
|
||||||
|
|
||||||
|
// Attempt to load values up to given length.
|
||||||
|
next, err := load(min, max, length-len(values))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire lock.
|
||||||
|
t.mutex.Lock()
|
||||||
|
|
||||||
|
// Store uncached values.
|
||||||
|
for i := range next {
|
||||||
|
t.store_value(
|
||||||
|
nil, "",
|
||||||
|
uncached[i],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done with lock.
|
||||||
|
t.mutex.Unlock()
|
||||||
|
|
||||||
|
// Append uncached to return values.
|
||||||
|
values = append(values, next...)
|
||||||
|
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timeline[T, PK]) index(value T) *indexed_item {
|
||||||
|
pk := t.pkey(value)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case t.list.len == 0:
|
||||||
|
|
||||||
|
case pk < t.list.head.data:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timeline[T, PK]) delete(item *indexed_item) {
|
||||||
|
|
||||||
|
}
|
16
vendor/codeberg.org/gruf/go-structr/queue.go
generated
vendored
16
vendor/codeberg.org/gruf/go-structr/queue.go
generated
vendored
|
@ -68,9 +68,9 @@ func (q *Queue[T]) Init(config QueueConfig[T]) {
|
||||||
|
|
||||||
// Index selects index with given name from queue, else panics.
|
// Index selects index with given name from queue, else panics.
|
||||||
func (q *Queue[T]) Index(name string) *Index {
|
func (q *Queue[T]) Index(name string) *Index {
|
||||||
for i := range q.indices {
|
for i, idx := range q.indices {
|
||||||
if q.indices[i].name == name {
|
if idx.name == name {
|
||||||
return &q.indices[i]
|
return &(q.indices[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic("unknown index: " + name)
|
panic("unknown index: " + name)
|
||||||
|
@ -207,17 +207,17 @@ func (q *Queue[T]) Len() int {
|
||||||
|
|
||||||
// Debug returns debug stats about queue.
|
// Debug returns debug stats about queue.
|
||||||
func (q *Queue[T]) Debug() map[string]any {
|
func (q *Queue[T]) Debug() map[string]any {
|
||||||
m := make(map[string]any)
|
m := make(map[string]any, 2)
|
||||||
q.mutex.Lock()
|
q.mutex.Lock()
|
||||||
m["queue"] = q.queue.len
|
m["queue"] = q.queue.len
|
||||||
indices := make(map[string]any)
|
indices := make(map[string]any, len(q.indices))
|
||||||
m["indices"] = indices
|
m["indices"] = indices
|
||||||
for i := range q.indices {
|
for _, idx := range q.indices {
|
||||||
var n uint64
|
var n uint64
|
||||||
for _, l := range q.indices[i].data.m {
|
for _, l := range idx.data.m {
|
||||||
n += uint64(l.len)
|
n += uint64(l.len)
|
||||||
}
|
}
|
||||||
indices[q.indices[i].name] = n
|
indices[idx.name] = n
|
||||||
}
|
}
|
||||||
q.mutex.Unlock()
|
q.mutex.Unlock()
|
||||||
return m
|
return m
|
||||||
|
|
28
vendor/codeberg.org/gruf/go-structr/runtime.go
generated
vendored
28
vendor/codeberg.org/gruf/go-structr/runtime.go
generated
vendored
|
@ -2,7 +2,10 @@
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
@ -182,7 +185,32 @@ func deref(p unsafe.Pointer, n uint) unsafe.Pointer {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eface_data returns the data ptr from an empty interface.
|
||||||
|
func eface_data(a any) unsafe.Pointer {
|
||||||
|
type eface struct{ _, data unsafe.Pointer }
|
||||||
|
return (*eface)(unsafe.Pointer(&a)).data
|
||||||
|
}
|
||||||
|
|
||||||
// panicf provides a panic with string formatting.
|
// panicf provides a panic with string formatting.
|
||||||
func panicf(format string, args ...any) {
|
func panicf(format string, args ...any) {
|
||||||
panic(fmt.Sprintf(format, args...))
|
panic(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// should_not_reach can be called to indicated a
|
||||||
|
// block of code should not be able to be reached,
|
||||||
|
// else it prints callsite info with a BUG report.
|
||||||
|
//
|
||||||
|
//go:noinline
|
||||||
|
func should_not_reach() {
|
||||||
|
pcs := make([]uintptr, 1)
|
||||||
|
_ = runtime.Callers(2, pcs)
|
||||||
|
fn := runtime.FuncForPC(pcs[0])
|
||||||
|
funcname := "go-structr" // by default use just our library name
|
||||||
|
if fn != nil {
|
||||||
|
funcname = fn.Name()
|
||||||
|
if i := strings.LastIndexByte(funcname, '/'); i != -1 {
|
||||||
|
funcname = funcname[i+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.Stderr.WriteString("BUG: assertion failed in " + funcname + "\n")
|
||||||
|
}
|
||||||
|
|
8
vendor/codeberg.org/gruf/go-structr/util.go
generated
vendored
8
vendor/codeberg.org/gruf/go-structr/util.go
generated
vendored
|
@ -1,7 +1,5 @@
|
||||||
package structr
|
package structr
|
||||||
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
// once only executes 'fn' once.
|
// once only executes 'fn' once.
|
||||||
func once(fn func()) func() {
|
func once(fn func()) func() {
|
||||||
var once int32
|
var once int32
|
||||||
|
@ -13,9 +11,3 @@ func once(fn func()) func() {
|
||||||
fn()
|
fn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eface_data returns the data ptr from an empty interface.
|
|
||||||
func eface_data(a any) unsafe.Pointer {
|
|
||||||
type eface struct{ _, data unsafe.Pointer }
|
|
||||||
return (*eface)(unsafe.Pointer(&a)).data
|
|
||||||
}
|
|
||||||
|
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
|
@ -66,7 +66,7 @@ codeberg.org/gruf/go-storage/disk
|
||||||
codeberg.org/gruf/go-storage/internal
|
codeberg.org/gruf/go-storage/internal
|
||||||
codeberg.org/gruf/go-storage/memory
|
codeberg.org/gruf/go-storage/memory
|
||||||
codeberg.org/gruf/go-storage/s3
|
codeberg.org/gruf/go-storage/s3
|
||||||
# codeberg.org/gruf/go-structr v0.8.10
|
# codeberg.org/gruf/go-structr v0.8.11
|
||||||
## explicit; go 1.21
|
## explicit; go 1.21
|
||||||
codeberg.org/gruf/go-structr
|
codeberg.org/gruf/go-structr
|
||||||
# codeberg.org/superseriousbusiness/exif-terminator v0.9.0
|
# codeberg.org/superseriousbusiness/exif-terminator v0.9.0
|
||||||
|
|
|
@ -25,6 +25,7 @@ export interface InstanceV1 {
|
||||||
description_text?: string;
|
description_text?: string;
|
||||||
short_description: string;
|
short_description: string;
|
||||||
short_description_text?: string;
|
short_description_text?: string;
|
||||||
|
custom_css: string;
|
||||||
email: string;
|
email: string;
|
||||||
version: string;
|
version: string;
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
|
|
|
@ -66,6 +66,10 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
|
||||||
valueSelector: (s: InstanceV1) => s.description_text,
|
valueSelector: (s: InstanceV1) => s.description_text,
|
||||||
validator: (val: string) => val.length <= descLimit ? "" : `Instance description is ${val.length} characters; must be ${descLimit} characters or less`
|
validator: (val: string) => val.length <= descLimit ? "" : `Instance description is ${val.length} characters; must be ${descLimit} characters or less`
|
||||||
}),
|
}),
|
||||||
|
customCSS: useTextInput("custom_css", {
|
||||||
|
source: instance,
|
||||||
|
valueSelector: (s: InstanceV1) => s.custom_css
|
||||||
|
}),
|
||||||
terms: useTextInput("terms", {
|
terms: useTextInput("terms", {
|
||||||
source: instance,
|
source: instance,
|
||||||
// Select "raw" text version of parsed field for editing.
|
// Select "raw" text version of parsed field for editing.
|
||||||
|
@ -191,6 +195,15 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
|
||||||
type="email"
|
type="email"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<TextArea
|
||||||
|
field={form.customCSS}
|
||||||
|
label={"Custom CSS"}
|
||||||
|
className="monospace"
|
||||||
|
rows={8}
|
||||||
|
autoCapitalize="none"
|
||||||
|
spellCheck="false"
|
||||||
|
/>
|
||||||
|
|
||||||
<MutationButton label="Save" result={result} disabled={false} />
|
<MutationButton label="Save" result={result} disabled={false} />
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue