[feature] Advertise rich text formats, support content_type field (#1370)
* Advertise rich text formats, support content_type field * Update JSON in instance patch tests * Replace format with content_type everywhere * update migration to work with both pg and sqlite * regenerate swagger docs * update instance serialization + tests * fix up * learn to code tobi please, i'm begging you --------- Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
This commit is contained in:
parent
baf933cb9f
commit
e6cde25466
|
@ -143,10 +143,10 @@ definitions:
|
||||||
description: Whether new statuses should be marked sensitive by default.
|
description: Whether new statuses should be marked sensitive by default.
|
||||||
type: boolean
|
type: boolean
|
||||||
x-go-name: Sensitive
|
x-go-name: Sensitive
|
||||||
status_format:
|
status_content_type:
|
||||||
description: The default posting format for new statuses.
|
description: The default posting content type for new statuses.
|
||||||
type: string
|
type: string
|
||||||
x-go-name: StatusFormat
|
x-go-name: StatusContentType
|
||||||
title: Source represents display or publishing preferences of user's own account.
|
title: Source represents display or publishing preferences of user's own account.
|
||||||
type: object
|
type: object
|
||||||
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
|
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
|
||||||
|
@ -1240,6 +1240,15 @@ definitions:
|
||||||
format: int64
|
format: int64
|
||||||
type: integer
|
type: integer
|
||||||
x-go-name: MaxMediaAttachments
|
x-go-name: MaxMediaAttachments
|
||||||
|
supported_mime_types:
|
||||||
|
description: List of mime types that it's possible to use for statuses on this instance.
|
||||||
|
example:
|
||||||
|
- text/plain
|
||||||
|
- text/markdown
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
x-go-name: SupportedMimeTypes
|
||||||
title: InstanceConfigurationStatuses models instance status config parameters.
|
title: InstanceConfigurationStatuses models instance status config parameters.
|
||||||
type: object
|
type: object
|
||||||
x-go-name: InstanceConfigurationStatuses
|
x-go-name: InstanceConfigurationStatuses
|
||||||
|
@ -2112,12 +2121,12 @@ definitions:
|
||||||
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
|
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
|
||||||
statusCreateRequest:
|
statusCreateRequest:
|
||||||
properties:
|
properties:
|
||||||
format:
|
content_type:
|
||||||
description: |-
|
description: |-
|
||||||
Format to use when parsing this status.
|
Content type to use when parsing this status.
|
||||||
in: formData
|
in: formData
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Format
|
x-go-name: ContentType
|
||||||
in_reply_to_id:
|
in_reply_to_id:
|
||||||
description: |-
|
description: |-
|
||||||
ID of the status being replied to, if status is a reply.
|
ID of the status being replied to, if status is a reply.
|
||||||
|
@ -2463,10 +2472,10 @@ definitions:
|
||||||
description: Mark authored statuses as sensitive by default.
|
description: Mark authored statuses as sensitive by default.
|
||||||
type: boolean
|
type: boolean
|
||||||
x-go-name: Sensitive
|
x-go-name: Sensitive
|
||||||
status_format:
|
status_content_type:
|
||||||
description: Default format for authored statuses (plain or markdown).
|
description: Default format for authored statuses (text/plain or text/markdown).
|
||||||
type: string
|
type: string
|
||||||
x-go-name: StatusFormat
|
x-go-name: StatusContentType
|
||||||
title: UpdateSource is to be used specifically in an UpdateCredentialsRequest.
|
title: UpdateSource is to be used specifically in an UpdateCredentialsRequest.
|
||||||
type: object
|
type: object
|
||||||
x-go-name: UpdateSource
|
x-go-name: UpdateSource
|
||||||
|
@ -3081,9 +3090,9 @@ paths:
|
||||||
in: formData
|
in: formData
|
||||||
name: source[language]
|
name: source[language]
|
||||||
type: string
|
type: string
|
||||||
- description: Default format to use for authored statuses (plain or markdown).
|
- description: Default content type to use for authored statuses (text/plain or text/markdown).
|
||||||
in: formData
|
in: formData
|
||||||
name: source[status_format]
|
name: source[status_content_type]
|
||||||
type: string
|
type: string
|
||||||
- description: Custom CSS to use when rendering this account's profile or statuses. String must be no more than 5,000 characters (~5kb).
|
- description: Custom CSS to use when rendering this account's profile or statuses. String must be no more than 5,000 characters (~5kb).
|
||||||
in: formData
|
in: formData
|
||||||
|
@ -4874,11 +4883,11 @@ paths:
|
||||||
name: language
|
name: language
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Language
|
x-go-name: Language
|
||||||
- description: Format to use when parsing this status.
|
- description: Content type to use when parsing this status.
|
||||||
in: formData
|
in: formData
|
||||||
name: format
|
name: content_type
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Format
|
x-go-name: ContentType
|
||||||
- description: This status will be federated beyond the local timeline(s).
|
- description: This status will be federated beyond the local timeline(s).
|
||||||
in: query
|
in: query
|
||||||
name: federated
|
name: federated
|
||||||
|
|
|
@ -99,9 +99,9 @@ import (
|
||||||
// description: Default language to use for authored statuses (ISO 6391).
|
// description: Default language to use for authored statuses (ISO 6391).
|
||||||
// type: string
|
// type: string
|
||||||
// -
|
// -
|
||||||
// name: source[status_format]
|
// name: source[status_content_type]
|
||||||
// in: formData
|
// in: formData
|
||||||
// description: Default format to use for authored statuses (plain or markdown).
|
// description: Default content type to use for authored statuses (text/plain or text/markdown).
|
||||||
// type: string
|
// type: string
|
||||||
// -
|
// -
|
||||||
// name: custom_css
|
// name: custom_css
|
||||||
|
@ -190,8 +190,8 @@ func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateCredentialsRequest,
|
||||||
form.Source.Language = &language
|
form.Source.Language = &language
|
||||||
}
|
}
|
||||||
|
|
||||||
if statusFormat, ok := sourceMap["status_format"]; ok {
|
if statusContentType, ok := sourceMap["status_content_type"]; ok {
|
||||||
form.Source.StatusFormat = &statusFormat
|
form.Source.StatusContentType = &statusContentType
|
||||||
}
|
}
|
||||||
|
|
||||||
if form == nil ||
|
if form == nil ||
|
||||||
|
@ -205,7 +205,7 @@ func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateCredentialsRequest,
|
||||||
form.Source.Privacy == nil &&
|
form.Source.Privacy == nil &&
|
||||||
form.Source.Sensitive == nil &&
|
form.Source.Sensitive == nil &&
|
||||||
form.Source.Language == nil &&
|
form.Source.Language == nil &&
|
||||||
form.Source.StatusFormat == nil &&
|
form.Source.StatusContentType == nil &&
|
||||||
form.FieldsAttributes == nil &&
|
form.FieldsAttributes == nil &&
|
||||||
form.CustomCSS == nil &&
|
form.CustomCSS == nil &&
|
||||||
form.EnableRSS == nil) {
|
form.EnableRSS == nil) {
|
||||||
|
|
|
@ -414,13 +414,13 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpd
|
||||||
suite.True(apimodelAccount.Locked)
|
suite.True(apimodelAccount.Locked)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpdateStatusFormatOK() {
|
func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpdateStatusContentTypeOK() {
|
||||||
// set up the request
|
// set up the request
|
||||||
// we're updating the language of zork
|
// we're updating the language of zork
|
||||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||||
"", "",
|
"", "",
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"source[status_format]": "markdown",
|
"source[status_content_type]": "text/markdown",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -450,22 +450,22 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpd
|
||||||
|
|
||||||
// check the returned api model account
|
// check the returned api model account
|
||||||
// fields should be updated
|
// fields should be updated
|
||||||
suite.Equal("markdown", apimodelAccount.Source.StatusFormat)
|
suite.Equal("text/markdown", apimodelAccount.Source.StatusContentType)
|
||||||
|
|
||||||
dbAccount, err := suite.db.GetAccountByID(context.Background(), suite.testAccounts["local_account_1"].ID)
|
dbAccount, err := suite.db.GetAccountByID(context.Background(), suite.testAccounts["local_account_1"].ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
suite.FailNow(err.Error())
|
suite.FailNow(err.Error())
|
||||||
}
|
}
|
||||||
suite.Equal(dbAccount.StatusFormat, "markdown")
|
suite.Equal(dbAccount.StatusContentType, "text/markdown")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpdateStatusFormatBad() {
|
func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpdateStatusContentTypeBad() {
|
||||||
// set up the request
|
// set up the request
|
||||||
// we're updating the language of zork
|
// we're updating the language of zork
|
||||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||||
"", "",
|
"", "",
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"source[status_format]": "peepeepoopoo",
|
"source[status_content_type]": "peepeepoopoo",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -486,7 +486,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpd
|
||||||
b, err := ioutil.ReadAll(result.Body)
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
|
||||||
suite.Equal(`{"error":"Bad Request: status format 'peepeepoopoo' was not recognized, valid options are 'plain', 'markdown'"}`, string(b))
|
suite.Equal(`{"error":"Bad Request: status content type 'peepeepoopoo' was not recognized, valid options are 'text/plain', 'text/markdown'"}`, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccountUpdateTestSuite(t *testing.T) {
|
func TestAccountUpdateTestSuite(t *testing.T) {
|
||||||
|
|
|
@ -90,7 +90,11 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() {
|
||||||
"statuses": {
|
"statuses": {
|
||||||
"max_characters": 5000,
|
"max_characters": 5000,
|
||||||
"max_media_attachments": 6,
|
"max_media_attachments": 6,
|
||||||
"characters_reserved_per_url": 25
|
"characters_reserved_per_url": 25,
|
||||||
|
"supported_mime_types": [
|
||||||
|
"text/plain",
|
||||||
|
"text/markdown"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"media_attachments": {
|
"media_attachments": {
|
||||||
"supported_mime_types": [
|
"supported_mime_types": [
|
||||||
|
@ -188,7 +192,11 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() {
|
||||||
"statuses": {
|
"statuses": {
|
||||||
"max_characters": 5000,
|
"max_characters": 5000,
|
||||||
"max_media_attachments": 6,
|
"max_media_attachments": 6,
|
||||||
"characters_reserved_per_url": 25
|
"characters_reserved_per_url": 25,
|
||||||
|
"supported_mime_types": [
|
||||||
|
"text/plain",
|
||||||
|
"text/markdown"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"media_attachments": {
|
"media_attachments": {
|
||||||
"supported_mime_types": [
|
"supported_mime_types": [
|
||||||
|
@ -286,7 +294,11 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() {
|
||||||
"statuses": {
|
"statuses": {
|
||||||
"max_characters": 5000,
|
"max_characters": 5000,
|
||||||
"max_media_attachments": 6,
|
"max_media_attachments": 6,
|
||||||
"characters_reserved_per_url": 25
|
"characters_reserved_per_url": 25,
|
||||||
|
"supported_mime_types": [
|
||||||
|
"text/plain",
|
||||||
|
"text/markdown"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"media_attachments": {
|
"media_attachments": {
|
||||||
"supported_mime_types": [
|
"supported_mime_types": [
|
||||||
|
@ -435,7 +447,11 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() {
|
||||||
"statuses": {
|
"statuses": {
|
||||||
"max_characters": 5000,
|
"max_characters": 5000,
|
||||||
"max_media_attachments": 6,
|
"max_media_attachments": 6,
|
||||||
"characters_reserved_per_url": 25
|
"characters_reserved_per_url": 25,
|
||||||
|
"supported_mime_types": [
|
||||||
|
"text/plain",
|
||||||
|
"text/markdown"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"media_attachments": {
|
"media_attachments": {
|
||||||
"supported_mime_types": [
|
"supported_mime_types": [
|
||||||
|
@ -554,7 +570,11 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() {
|
||||||
"statuses": {
|
"statuses": {
|
||||||
"max_characters": 5000,
|
"max_characters": 5000,
|
||||||
"max_media_attachments": 6,
|
"max_media_attachments": 6,
|
||||||
"characters_reserved_per_url": 25
|
"characters_reserved_per_url": 25,
|
||||||
|
"supported_mime_types": [
|
||||||
|
"text/plain",
|
||||||
|
"text/markdown"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"media_attachments": {
|
"media_attachments": {
|
||||||
"supported_mime_types": [
|
"supported_mime_types": [
|
||||||
|
@ -689,7 +709,11 @@ func (suite *InstancePatchTestSuite) TestInstancePatch9() {
|
||||||
"statuses": {
|
"statuses": {
|
||||||
"max_characters": 5000,
|
"max_characters": 5000,
|
||||||
"max_media_attachments": 6,
|
"max_media_attachments": 6,
|
||||||
"characters_reserved_per_url": 25
|
"characters_reserved_per_url": 25,
|
||||||
|
"supported_mime_types": [
|
||||||
|
"text/plain",
|
||||||
|
"text/markdown"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"media_attachments": {
|
"media_attachments": {
|
||||||
"supported_mime_types": [
|
"supported_mime_types": [
|
||||||
|
|
|
@ -105,14 +105,14 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() {
|
||||||
func (suite *StatusCreateTestSuite) TestPostNewStatusMarkdown() {
|
func (suite *StatusCreateTestSuite) TestPostNewStatusMarkdown() {
|
||||||
// set default post language of account 1 to markdown
|
// set default post language of account 1 to markdown
|
||||||
testAccount := suite.testAccounts["local_account_1"]
|
testAccount := suite.testAccounts["local_account_1"]
|
||||||
testAccount.StatusFormat = "markdown"
|
testAccount.StatusContentType = "text/markdown"
|
||||||
a := testAccount
|
a := testAccount
|
||||||
|
|
||||||
err := suite.db.UpdateAccount(context.Background(), a)
|
err := suite.db.UpdateAccount(context.Background(), a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
suite.FailNow(err.Error())
|
suite.FailNow(err.Error())
|
||||||
}
|
}
|
||||||
suite.Equal(a.StatusFormat, "markdown")
|
suite.Equal(a.StatusContentType, "text/markdown")
|
||||||
|
|
||||||
t := suite.testTokens["local_account_1"]
|
t := suite.testTokens["local_account_1"]
|
||||||
oauthToken := oauth.DBTokenToToken(t)
|
oauthToken := oauth.DBTokenToToken(t)
|
||||||
|
|
|
@ -174,8 +174,8 @@ type UpdateSource struct {
|
||||||
Sensitive *bool `form:"sensitive" json:"sensitive" xml:"sensitive"`
|
Sensitive *bool `form:"sensitive" json:"sensitive" xml:"sensitive"`
|
||||||
// Default language to use for authored statuses. (ISO 6391)
|
// Default language to use for authored statuses. (ISO 6391)
|
||||||
Language *string `form:"language" json:"language" xml:"language"`
|
Language *string `form:"language" json:"language" xml:"language"`
|
||||||
// Default format for authored statuses (plain or markdown).
|
// Default format for authored statuses (text/plain or text/markdown).
|
||||||
StatusFormat *string `form:"status_format" json:"status_format" xml:"status_format"`
|
StatusContentType *string `form:"status_content_type" json:"status_content_type" xml:"status_content_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateField is to be used specifically in an UpdateCredentialsRequest.
|
// UpdateField is to be used specifically in an UpdateCredentialsRequest.
|
||||||
|
|
|
@ -73,6 +73,10 @@ type InstanceConfigurationStatuses struct {
|
||||||
//
|
//
|
||||||
// example: 25
|
// example: 25
|
||||||
CharactersReservedPerURL int `json:"characters_reserved_per_url"`
|
CharactersReservedPerURL int `json:"characters_reserved_per_url"`
|
||||||
|
// List of mime types that it's possible to use for statuses on this instance.
|
||||||
|
//
|
||||||
|
// example: ["text/plain","text/markdown"]
|
||||||
|
SupportedMimeTypes []string `json:"supported_mime_types,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstanceConfigurationMediaAttachments models instance media attachment config parameters.
|
// InstanceConfigurationMediaAttachments models instance media attachment config parameters.
|
||||||
|
|
|
@ -31,8 +31,8 @@ type Source struct {
|
||||||
Sensitive bool `json:"sensitive"`
|
Sensitive bool `json:"sensitive"`
|
||||||
// The default posting language for new statuses.
|
// The default posting language for new statuses.
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
// The default posting format for new statuses.
|
// The default posting content type for new statuses.
|
||||||
StatusFormat string `json:"status_format"`
|
StatusContentType string `json:"status_content_type"`
|
||||||
// Profile bio.
|
// Profile bio.
|
||||||
Note string `json:"note"`
|
Note string `json:"note"`
|
||||||
// Metadata about the account.
|
// Metadata about the account.
|
||||||
|
|
|
@ -179,9 +179,9 @@ type StatusCreateRequest struct {
|
||||||
// ISO 639 language code for this status.
|
// ISO 639 language code for this status.
|
||||||
// in: formData
|
// in: formData
|
||||||
Language string `form:"language" json:"language" xml:"language"`
|
Language string `form:"language" json:"language" xml:"language"`
|
||||||
// Format to use when parsing this status.
|
// Content type to use when parsing this status.
|
||||||
// in: formData
|
// in: formData
|
||||||
Format StatusFormat `form:"format" json:"format" xml:"format"`
|
ContentType StatusContentType `form:"content_type" json:"content_type" xml:"content_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visibility models the visibility of a status.
|
// Visibility models the visibility of a status.
|
||||||
|
@ -227,16 +227,16 @@ type AdvancedVisibilityFlagsForm struct {
|
||||||
Likeable *bool `form:"likeable" json:"likeable" xml:"likeable"`
|
Likeable *bool `form:"likeable" json:"likeable" xml:"likeable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatusFormat is the format in which to parse the submitted status.
|
// StatusContentType is the content type with which to parse the submitted status.
|
||||||
// Can be either plain or markdown. Empty will default to plain.
|
// Can be either text/plain or text/markdown. Empty will default to text/plain.
|
||||||
//
|
//
|
||||||
// swagger:enum statusFormat
|
// swagger:enum statusContentType
|
||||||
// swagger:type string
|
// swagger:type string
|
||||||
type StatusFormat string
|
type StatusContentType string
|
||||||
|
|
||||||
// Format to use when parsing submitted status into an html-formatted status
|
// Content type to use when parsing submitted status into an html-formatted status
|
||||||
const (
|
const (
|
||||||
StatusFormatPlain StatusFormat = "plain"
|
StatusContentTypePlain StatusContentType = "text/plain"
|
||||||
StatusFormatMarkdown StatusFormat = "markdown"
|
StatusContentTypeMarkdown StatusContentType = "text/markdown"
|
||||||
StatusFormatDefault StatusFormat = StatusFormatPlain
|
StatusContentTypeDefault = StatusContentTypePlain
|
||||||
)
|
)
|
||||||
|
|
|
@ -89,7 +89,7 @@ func (suite *BasicTestSuite) TestPutAccountWithBunDefaultFields() {
|
||||||
suite.Empty(a.Privacy)
|
suite.Empty(a.Privacy)
|
||||||
suite.False(*a.Sensitive)
|
suite.False(*a.Sensitive)
|
||||||
suite.Equal("en", a.Language)
|
suite.Equal("en", a.Language)
|
||||||
suite.Empty(a.StatusFormat)
|
suite.Empty(a.StatusContentType)
|
||||||
suite.Equal(testAccount.URI, a.URI)
|
suite.Equal(testAccount.URI, a.URI)
|
||||||
suite.Equal(testAccount.URL, a.URL)
|
suite.Equal(testAccount.URL, a.URL)
|
||||||
suite.Zero(testAccount.FetchedAt)
|
suite.Zero(testAccount.FetchedAt)
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
GoToSocial
|
||||||
|
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
|
||||||
|
|
||||||
|
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"
|
||||||
|
"github.com/uptrace/bun/dialect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
up := func(ctx context.Context, db *bun.DB) error {
|
||||||
|
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||||
|
// Create a new column for status_content_type.
|
||||||
|
if _, err := tx.ExecContext(ctx, "ALTER TABLE ? ADD COLUMN ? TEXT", bun.Ident("accounts"), bun.Ident("status_content_type")); err != nil &&
|
||||||
|
!(strings.Contains(err.Error(), "already exists") || strings.Contains(err.Error(), "duplicate column name") || strings.Contains(err.Error(), "SQLSTATE 42701")) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Port values into this column from the old status_format column,
|
||||||
|
// prepending 'text/' to the value to derive the mime type.
|
||||||
|
//
|
||||||
|
// Eg, 'plain' status_format becomes 'text/plain' status_content_type.
|
||||||
|
q := tx.
|
||||||
|
NewUpdate().
|
||||||
|
Table("accounts").
|
||||||
|
Where("? IS NOT NULL", bun.Ident("status_format"))
|
||||||
|
|
||||||
|
// We need to switch here because Postgres and SQLite
|
||||||
|
// have different syntaxes for concatenation.
|
||||||
|
switch tx.Dialect().Name() {
|
||||||
|
case dialect.SQLite:
|
||||||
|
q = q.
|
||||||
|
Set("? = ? || ?", bun.Ident("status_content_type"), "text/", bun.Ident("status_format"))
|
||||||
|
case dialect.PG:
|
||||||
|
q = q.
|
||||||
|
Set("? = CONCAT(?, ?)", bun.Ident("status_content_type"), "text/", bun.Ident("status_format"))
|
||||||
|
default:
|
||||||
|
panic("db conn was neither pg not sqlite")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := q.Exec(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop the old status_format column.
|
||||||
|
if _, err := tx.ExecContext(ctx, "ALTER TABLE ? DROP COLUMN ?", bun.Ident("accounts"), bun.Ident("status_format")); err != nil &&
|
||||||
|
!(strings.Contains(err.Error(), "no such column") || strings.Contains(err.Error(), "does not exist") || strings.Contains(err.Error(), "SQLSTATE 42703")) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
down := func(ctx context.Context, db *bun.DB) error {
|
||||||
|
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||||
|
var err error
|
||||||
|
_, err = db.ExecContext(ctx, "ALTER TABLE ? ADD COLUMN ? TEXT", bun.Ident("accounts"), bun.Ident("status_format"))
|
||||||
|
if err != nil && !(strings.Contains(err.Error(), "already exists") || strings.Contains(err.Error(), "duplicate column name") || strings.Contains(err.Error(), "SQLSTATE 42701")) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = db.ExecContext(ctx, "UPDATE ? SET ? = SUBSTR(?, 6) WHERE ? IS NOT NULL", bun.Ident("accounts"), bun.Ident("status_format"), bun.Ident("status_content_type"), bun.Ident("status_content_type"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = db.ExecContext(ctx, "ALTER TABLE ? DROP COLUMN ?", bun.Ident("accounts"), bun.Ident("status_content_type"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Migrations.Register(up, down); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,7 +60,7 @@ type Account struct {
|
||||||
Privacy Visibility `validate:"required_without=Domain,omitempty,oneof=public unlocked followers_only mutuals_only direct" bun:",nullzero"` // Default post privacy for this account
|
Privacy Visibility `validate:"required_without=Domain,omitempty,oneof=public unlocked followers_only mutuals_only direct" bun:",nullzero"` // Default post privacy for this account
|
||||||
Sensitive *bool `validate:"-" bun:",default:false"` // Set posts from this account to sensitive by default?
|
Sensitive *bool `validate:"-" bun:",default:false"` // Set posts from this account to sensitive by default?
|
||||||
Language string `validate:"omitempty,bcp47_language_tag" bun:",nullzero,notnull,default:'en'"` // What language does this account post in?
|
Language string `validate:"omitempty,bcp47_language_tag" bun:",nullzero,notnull,default:'en'"` // What language does this account post in?
|
||||||
StatusFormat string `validate:"required_without=Domain,omitempty,oneof=plain markdown" bun:",nullzero"` // What is the default format for statuses posted by this account (only for local accounts).
|
StatusContentType string `validate:"required_without=Domain,omitempty,oneof=text/plain text/markdown" bun:",nullzero"` // What is the default format for statuses posted by this account (only for local accounts).
|
||||||
CustomCSS string `validate:"-" bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses.
|
CustomCSS string `validate:"-" bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses.
|
||||||
URI string `validate:"required,url" bun:",nullzero,notnull,unique"` // ActivityPub URI for this account.
|
URI string `validate:"required,url" bun:",nullzero,notnull,unique"` // ActivityPub URI for this account.
|
||||||
URL string `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"` // Web URL for this account's profile
|
URL string `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"` // Web URL for this account's profile
|
||||||
|
|
|
@ -79,7 +79,7 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
||||||
|
|
||||||
// Process note to generate a valid HTML representation
|
// Process note to generate a valid HTML representation
|
||||||
var f text.FormatFunc
|
var f text.FormatFunc
|
||||||
if account.StatusFormat == "markdown" {
|
if account.StatusContentType == "text/markdown" {
|
||||||
f = p.formatter.FromMarkdown
|
f = p.formatter.FromMarkdown
|
||||||
} else {
|
} else {
|
||||||
f = p.formatter.FromPlain
|
f = p.formatter.FromPlain
|
||||||
|
@ -144,12 +144,12 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
||||||
account.Privacy = privacy
|
account.Privacy = privacy
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Source.StatusFormat != nil {
|
if form.Source.StatusContentType != nil {
|
||||||
if err := validate.StatusFormat(*form.Source.StatusFormat); err != nil {
|
if err := validate.StatusContentType(*form.Source.StatusContentType); err != nil {
|
||||||
return nil, gtserror.NewErrorBadRequest(err, err.Error())
|
return nil, gtserror.NewErrorBadRequest(err, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
account.StatusFormat = *form.Source.StatusFormat
|
account.StatusContentType = *form.Source.StatusContentType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -122,13 +122,13 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateWithMarkdownNote() {
|
||||||
Note: ¬e,
|
Note: ¬e,
|
||||||
}
|
}
|
||||||
|
|
||||||
// set default post language of account 1 to markdown
|
// set default post content type of account 1 to markdown
|
||||||
testAccount.StatusFormat = "markdown"
|
testAccount.StatusContentType = "text/markdown"
|
||||||
|
|
||||||
// should get no error from the update function, and an api model account returned
|
// should get no error from the update function, and an api model account returned
|
||||||
apiAccount, errWithCode := suite.accountProcessor.Update(context.Background(), testAccount, form)
|
apiAccount, errWithCode := suite.accountProcessor.Update(context.Background(), testAccount, form)
|
||||||
// reset test account to avoid breaking other tests
|
// reset test account to avoid breaking other tests
|
||||||
testAccount.StatusFormat = "plain"
|
testAccount.StatusContentType = "text/plain"
|
||||||
suite.NoError(errWithCode)
|
suite.NoError(errWithCode)
|
||||||
suite.NotNil(apiAccount)
|
suite.NotNil(apiAccount)
|
||||||
|
|
||||||
|
|
|
@ -290,32 +290,32 @@ func processContent(ctx context.Context, dbService db.DB, formatter text.Formatt
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// if format wasn't specified we should try to figure out what format this user prefers
|
// if content type wasn't specified we should try to figure out what content type this user prefers
|
||||||
if form.Format == "" {
|
if form.ContentType == "" {
|
||||||
acct, err := dbService.GetAccountByID(ctx, accountID)
|
acct, err := dbService.GetAccountByID(ctx, accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error processing new content: couldn't retrieve account from db to check post format: %s", err)
|
return fmt.Errorf("error processing new content: couldn't retrieve account from db to check post format: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch acct.StatusFormat {
|
switch acct.StatusContentType {
|
||||||
case "plain":
|
case "text/plain":
|
||||||
form.Format = apimodel.StatusFormatPlain
|
form.ContentType = apimodel.StatusContentTypePlain
|
||||||
case "markdown":
|
case "text/markdown":
|
||||||
form.Format = apimodel.StatusFormatMarkdown
|
form.ContentType = apimodel.StatusContentTypeMarkdown
|
||||||
default:
|
default:
|
||||||
form.Format = apimodel.StatusFormatDefault
|
form.ContentType = apimodel.StatusContentTypeDefault
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse content out of the status depending on what format has been submitted
|
// parse content out of the status depending on what content type has been submitted
|
||||||
var f text.FormatFunc
|
var f text.FormatFunc
|
||||||
switch form.Format {
|
switch form.ContentType {
|
||||||
case apimodel.StatusFormatPlain:
|
case apimodel.StatusContentTypePlain:
|
||||||
f = formatter.FromPlain
|
f = formatter.FromPlain
|
||||||
case apimodel.StatusFormatMarkdown:
|
case apimodel.StatusContentTypeMarkdown:
|
||||||
f = formatter.FromMarkdown
|
f = formatter.FromMarkdown
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("format %s not recognised as a valid status format", form.Format)
|
return fmt.Errorf("format %s not recognised as a valid status format", form.ContentType)
|
||||||
}
|
}
|
||||||
formatted := f(ctx, parseMention, accountID, status.ID, form.Status)
|
formatted := f(ctx, parseMention, accountID, status.ID, form.Status)
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ func (suite *StatusCreateTestSuite) TestProcessContentWarningWithQuotationMarks(
|
||||||
Visibility: apimodel.VisibilityPublic,
|
Visibility: apimodel.VisibilityPublic,
|
||||||
ScheduledAt: "",
|
ScheduledAt: "",
|
||||||
Language: "en",
|
Language: "en",
|
||||||
Format: apimodel.StatusFormatPlain,
|
ContentType: apimodel.StatusContentTypePlain,
|
||||||
},
|
},
|
||||||
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
|
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
|
||||||
Federated: nil,
|
Federated: nil,
|
||||||
|
@ -84,7 +84,7 @@ func (suite *StatusCreateTestSuite) TestProcessContentWarningWithHTMLEscapedQuot
|
||||||
Visibility: apimodel.VisibilityPublic,
|
Visibility: apimodel.VisibilityPublic,
|
||||||
ScheduledAt: "",
|
ScheduledAt: "",
|
||||||
Language: "en",
|
Language: "en",
|
||||||
Format: apimodel.StatusFormatPlain,
|
ContentType: apimodel.StatusContentTypePlain,
|
||||||
},
|
},
|
||||||
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
|
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
|
||||||
Federated: nil,
|
Federated: nil,
|
||||||
|
@ -122,7 +122,7 @@ func (suite *StatusCreateTestSuite) TestProcessStatusMarkdownWithUnderscoreEmoji
|
||||||
Visibility: apimodel.VisibilityPublic,
|
Visibility: apimodel.VisibilityPublic,
|
||||||
ScheduledAt: "",
|
ScheduledAt: "",
|
||||||
Language: "en",
|
Language: "en",
|
||||||
Format: apimodel.StatusFormatMarkdown,
|
ContentType: apimodel.StatusContentTypeMarkdown,
|
||||||
},
|
},
|
||||||
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
|
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
|
||||||
Federated: nil,
|
Federated: nil,
|
||||||
|
@ -156,7 +156,7 @@ func (suite *StatusCreateTestSuite) TestProcessStatusMarkdownWithSpoilerTextEmoj
|
||||||
Visibility: apimodel.VisibilityPublic,
|
Visibility: apimodel.VisibilityPublic,
|
||||||
ScheduledAt: "",
|
ScheduledAt: "",
|
||||||
Language: "en",
|
Language: "en",
|
||||||
Format: apimodel.StatusFormatMarkdown,
|
ContentType: apimodel.StatusContentTypeMarkdown,
|
||||||
},
|
},
|
||||||
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
|
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
|
||||||
Federated: nil,
|
Federated: nil,
|
||||||
|
@ -194,7 +194,7 @@ func (suite *StatusCreateTestSuite) TestProcessMediaDescriptionTooShort() {
|
||||||
Visibility: apimodel.VisibilityPublic,
|
Visibility: apimodel.VisibilityPublic,
|
||||||
ScheduledAt: "",
|
ScheduledAt: "",
|
||||||
Language: "en",
|
Language: "en",
|
||||||
Format: apimodel.StatusFormatPlain,
|
ContentType: apimodel.StatusContentTypePlain,
|
||||||
},
|
},
|
||||||
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
|
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
|
||||||
Federated: nil,
|
Federated: nil,
|
||||||
|
|
|
@ -111,7 +111,7 @@ func (suite *ImportMinimalTestSuite) TestImportMinimalOK() {
|
||||||
suite.Equal(testAccountBefore.Privacy, testAccountAfter.Privacy)
|
suite.Equal(testAccountBefore.Privacy, testAccountAfter.Privacy)
|
||||||
suite.Equal(testAccountBefore.Sensitive, testAccountAfter.Sensitive)
|
suite.Equal(testAccountBefore.Sensitive, testAccountAfter.Sensitive)
|
||||||
suite.Equal(testAccountBefore.Language, testAccountAfter.Language)
|
suite.Equal(testAccountBefore.Language, testAccountAfter.Language)
|
||||||
suite.Equal(testAccountBefore.StatusFormat, testAccountAfter.StatusFormat)
|
suite.Equal(testAccountBefore.StatusContentType, testAccountAfter.StatusContentType)
|
||||||
suite.Equal(testAccountBefore.URI, testAccountAfter.URI)
|
suite.Equal(testAccountBefore.URI, testAccountAfter.URI)
|
||||||
suite.Equal(testAccountBefore.URL, testAccountAfter.URL)
|
suite.Equal(testAccountBefore.URL, testAccountAfter.URL)
|
||||||
suite.Equal(testAccountBefore.InboxURI, testAccountAfter.InboxURI)
|
suite.Equal(testAccountBefore.InboxURI, testAccountAfter.InboxURI)
|
||||||
|
|
|
@ -43,7 +43,7 @@ type Account struct {
|
||||||
Privacy string `json:"privacy,omitempty" bun:",nullzero"`
|
Privacy string `json:"privacy,omitempty" bun:",nullzero"`
|
||||||
Sensitive *bool `json:"sensitive"`
|
Sensitive *bool `json:"sensitive"`
|
||||||
Language string `json:"language,omitempty" bun:",nullzero"`
|
Language string `json:"language,omitempty" bun:",nullzero"`
|
||||||
StatusFormat string `json:"statusFormat,omitempty" bun:",nullzero"`
|
StatusContentType string `json:"statusContentType,omitempty" bun:",nullzero"`
|
||||||
URI string `json:"uri" bun:",nullzero"`
|
URI string `json:"uri" bun:",nullzero"`
|
||||||
URL string `json:"url" bun:",nullzero"`
|
URL string `json:"url" bun:",nullzero"`
|
||||||
InboxURI string `json:"inboxURI" bun:",nullzero"`
|
InboxURI string `json:"inboxURI" bun:",nullzero"`
|
||||||
|
|
|
@ -46,6 +46,11 @@ const (
|
||||||
instanceSourceURL = "https://github.com/superseriousbusiness/gotosocial"
|
instanceSourceURL = "https://github.com/superseriousbusiness/gotosocial"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var instanceStatusesSupportedMimeTypes = []string{
|
||||||
|
string(apimodel.StatusContentTypePlain),
|
||||||
|
string(apimodel.StatusContentTypeMarkdown),
|
||||||
|
}
|
||||||
|
|
||||||
func (c *converter) AccountToAPIAccountSensitive(ctx context.Context, a *gtsmodel.Account) (*apimodel.Account, error) {
|
func (c *converter) AccountToAPIAccountSensitive(ctx context.Context, a *gtsmodel.Account) (*apimodel.Account, error) {
|
||||||
// we can build this sensitive account easily by first getting the public account....
|
// we can build this sensitive account easily by first getting the public account....
|
||||||
apiAccount, err := c.AccountToAPIAccountPublic(ctx, a)
|
apiAccount, err := c.AccountToAPIAccountPublic(ctx, a)
|
||||||
|
@ -67,16 +72,16 @@ func (c *converter) AccountToAPIAccountSensitive(ctx context.Context, a *gtsmode
|
||||||
frc = len(frs)
|
frc = len(frs)
|
||||||
}
|
}
|
||||||
|
|
||||||
statusFormat := string(apimodel.StatusFormatDefault)
|
statusContentType := string(apimodel.StatusContentTypeDefault)
|
||||||
if a.StatusFormat != "" {
|
if a.StatusContentType != "" {
|
||||||
statusFormat = a.StatusFormat
|
statusContentType = a.StatusContentType
|
||||||
}
|
}
|
||||||
|
|
||||||
apiAccount.Source = &apimodel.Source{
|
apiAccount.Source = &apimodel.Source{
|
||||||
Privacy: c.VisToAPIVis(ctx, a.Privacy),
|
Privacy: c.VisToAPIVis(ctx, a.Privacy),
|
||||||
Sensitive: *a.Sensitive,
|
Sensitive: *a.Sensitive,
|
||||||
Language: a.Language,
|
Language: a.Language,
|
||||||
StatusFormat: statusFormat,
|
StatusContentType: statusContentType,
|
||||||
Note: a.NoteRaw,
|
Note: a.NoteRaw,
|
||||||
Fields: apiAccount.Fields,
|
Fields: apiAccount.Fields,
|
||||||
FollowRequestsCount: frc,
|
FollowRequestsCount: frc,
|
||||||
|
@ -695,6 +700,7 @@ func (c *converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins
|
||||||
instance.Configuration.Statuses.MaxCharacters = config.GetStatusesMaxChars()
|
instance.Configuration.Statuses.MaxCharacters = config.GetStatusesMaxChars()
|
||||||
instance.Configuration.Statuses.MaxMediaAttachments = config.GetStatusesMediaMaxFiles()
|
instance.Configuration.Statuses.MaxMediaAttachments = config.GetStatusesMediaMaxFiles()
|
||||||
instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL
|
instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL
|
||||||
|
instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes
|
||||||
instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes
|
instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes
|
||||||
instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaImageMaxSize())
|
instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaImageMaxSize())
|
||||||
instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit
|
instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit
|
||||||
|
@ -820,6 +826,7 @@ func (c *converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins
|
||||||
instance.Configuration.Statuses.MaxCharacters = config.GetStatusesMaxChars()
|
instance.Configuration.Statuses.MaxCharacters = config.GetStatusesMaxChars()
|
||||||
instance.Configuration.Statuses.MaxMediaAttachments = config.GetStatusesMediaMaxFiles()
|
instance.Configuration.Statuses.MaxMediaAttachments = config.GetStatusesMediaMaxFiles()
|
||||||
instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL
|
instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL
|
||||||
|
instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes
|
||||||
instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes
|
instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes
|
||||||
instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaImageMaxSize())
|
instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaImageMaxSize())
|
||||||
instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit
|
instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit
|
||||||
|
|
|
@ -198,7 +198,7 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendSensitive() {
|
||||||
"privacy": "public",
|
"privacy": "public",
|
||||||
"sensitive": false,
|
"sensitive": false,
|
||||||
"language": "en",
|
"language": "en",
|
||||||
"status_format": "plain",
|
"status_content_type": "text/plain",
|
||||||
"note": "hey yo this is my profile!",
|
"note": "hey yo this is my profile!",
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"follow_requests_count": 0
|
"follow_requests_count": 0
|
||||||
|
@ -504,7 +504,11 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV1ToFrontend() {
|
||||||
"statuses": {
|
"statuses": {
|
||||||
"max_characters": 5000,
|
"max_characters": 5000,
|
||||||
"max_media_attachments": 6,
|
"max_media_attachments": 6,
|
||||||
"characters_reserved_per_url": 25
|
"characters_reserved_per_url": 25,
|
||||||
|
"supported_mime_types": [
|
||||||
|
"text/plain",
|
||||||
|
"text/markdown"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"media_attachments": {
|
"media_attachments": {
|
||||||
"supported_mime_types": [
|
"supported_mime_types": [
|
||||||
|
@ -616,7 +620,11 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV2ToFrontend() {
|
||||||
"statuses": {
|
"statuses": {
|
||||||
"max_characters": 5000,
|
"max_characters": 5000,
|
||||||
"max_media_attachments": 6,
|
"max_media_attachments": 6,
|
||||||
"characters_reserved_per_url": 25
|
"characters_reserved_per_url": 25,
|
||||||
|
"supported_mime_types": [
|
||||||
|
"text/plain",
|
||||||
|
"text/markdown"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"media_attachments": {
|
"media_attachments": {
|
||||||
"supported_mime_types": [
|
"supported_mime_types": [
|
||||||
|
|
|
@ -63,7 +63,7 @@ func happyAccount() *gtsmodel.Account {
|
||||||
Privacy: gtsmodel.VisibilityPublic,
|
Privacy: gtsmodel.VisibilityPublic,
|
||||||
Sensitive: testrig.FalseBool(),
|
Sensitive: testrig.FalseBool(),
|
||||||
Language: "en",
|
Language: "en",
|
||||||
StatusFormat: "plain",
|
StatusContentType: "text/plain",
|
||||||
URI: "http://localhost:8080/users/the_mighty_zork",
|
URI: "http://localhost:8080/users/the_mighty_zork",
|
||||||
URL: "http://localhost:8080/@the_mighty_zork",
|
URL: "http://localhost:8080/@the_mighty_zork",
|
||||||
FetchedAt: time.Time{},
|
FetchedAt: time.Time{},
|
||||||
|
|
|
@ -150,16 +150,16 @@ func Privacy(privacy string) error {
|
||||||
return fmt.Errorf("privacy '%s' was not recognized, valid options are 'direct', 'mutuals_only', 'private', 'public', 'unlisted'", privacy)
|
return fmt.Errorf("privacy '%s' was not recognized, valid options are 'direct', 'mutuals_only', 'private', 'public', 'unlisted'", privacy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatusFormat checks that the desired status format setting is valid.
|
// StatusContentType checks that the desired status format setting is valid.
|
||||||
func StatusFormat(statusFormat string) error {
|
func StatusContentType(statusContentType string) error {
|
||||||
if statusFormat == "" {
|
if statusContentType == "" {
|
||||||
return fmt.Errorf("empty string for status format not allowed")
|
return fmt.Errorf("empty string for status format not allowed")
|
||||||
}
|
}
|
||||||
switch apimodel.StatusFormat(statusFormat) {
|
switch apimodel.StatusContentType(statusContentType) {
|
||||||
case apimodel.StatusFormatPlain, apimodel.StatusFormatMarkdown:
|
case apimodel.StatusContentTypePlain, apimodel.StatusContentTypeMarkdown:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("status format '%s' was not recognized, valid options are 'plain', 'markdown'", statusFormat)
|
return fmt.Errorf("status content type '%s' was not recognized, valid options are 'text/plain', 'text/markdown'", statusContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CustomCSS(customCSS string) error {
|
func CustomCSS(customCSS string) error {
|
||||||
|
|
|
@ -53,14 +53,14 @@ function UserSettingsForm({ data }) {
|
||||||
- string source[privacy]
|
- string source[privacy]
|
||||||
- bool source[sensitive]
|
- bool source[sensitive]
|
||||||
- string source[language]
|
- string source[language]
|
||||||
- string source[status_format]
|
- string source[status_content_type]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const form = {
|
const form = {
|
||||||
defaultPrivacy: useTextInput("source[privacy]", { source: data, defaultValue: "unlisted" }),
|
defaultPrivacy: useTextInput("source[privacy]", { source: data, defaultValue: "unlisted" }),
|
||||||
isSensitive: useBoolInput("source[sensitive]", { source: data }),
|
isSensitive: useBoolInput("source[sensitive]", { source: data }),
|
||||||
language: useTextInput("source[language]", { source: data, valueSelector: (s) => s.source.language?.toUpperCase() ?? "EN" }),
|
language: useTextInput("source[language]", { source: data, valueSelector: (s) => s.source.language?.toUpperCase() ?? "EN" }),
|
||||||
format: useTextInput("source[status_format]", { source: data, defaultValue: "plain" }),
|
statusContentType: useTextInput("source[status_content_type]", { source: data, defaultValue: "text/plain" }),
|
||||||
};
|
};
|
||||||
|
|
||||||
const [submitForm, result] = useFormSubmit(form, query.useUpdateCredentialsMutation());
|
const [submitForm, result] = useFormSubmit(form, query.useUpdateCredentialsMutation());
|
||||||
|
@ -82,10 +82,10 @@ function UserSettingsForm({ data }) {
|
||||||
}>
|
}>
|
||||||
<a href="https://docs.gotosocial.org/en/latest/user_guide/posts/#privacy-settings" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about post privacy settings (opens in a new tab)</a>
|
<a href="https://docs.gotosocial.org/en/latest/user_guide/posts/#privacy-settings" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about post privacy settings (opens in a new tab)</a>
|
||||||
</Select>
|
</Select>
|
||||||
<Select field={form.format} label="Default post (and bio) format" options={
|
<Select field={form.statusContentType} label="Default post (and bio) format" options={
|
||||||
<>
|
<>
|
||||||
<option value="plain">Plain (default)</option>
|
<option value="text/plain">Plain (default)</option>
|
||||||
<option value="markdown">Markdown</option>
|
<option value="text/markdown">Markdown</option>
|
||||||
</>
|
</>
|
||||||
}>
|
}>
|
||||||
<a href="https://docs.gotosocial.org/en/latest/user_guide/posts/#input-types" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about post format settings (opens in a new tab)</a>
|
<a href="https://docs.gotosocial.org/en/latest/user_guide/posts/#input-types" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about post format settings (opens in a new tab)</a>
|
||||||
|
|
Loading…
Reference in New Issue