From e6cde25466e03ec864cd9defed96957e741b5e7c Mon Sep 17 00:00:00 2001 From: Vyr Cossont Date: Thu, 2 Mar 2023 03:06:40 -0800 Subject: [PATCH] [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 --- docs/api/swagger.yaml | 37 ++++--- internal/api/client/accounts/accountupdate.go | 10 +- .../api/client/accounts/accountupdate_test.go | 14 +-- .../api/client/instance/instancepatch_test.go | 36 +++++-- .../api/client/statuses/statuscreate_test.go | 4 +- internal/api/model/account.go | 4 +- internal/api/model/instance.go | 4 + internal/api/model/source.go | 4 +- internal/api/model/status.go | 20 ++-- internal/db/bundb/basic_test.go | 2 +- .../20230126170719_format_to_content_type.go | 96 +++++++++++++++++++ internal/gtsmodel/account.go | 2 +- internal/processing/account/update.go | 8 +- internal/processing/account/update_test.go | 6 +- internal/processing/status/create.go | 26 ++--- internal/processing/status/create_test.go | 10 +- internal/trans/import_test.go | 2 +- internal/trans/model/account.go | 2 +- internal/typeutils/internaltofrontend.go | 15 ++- internal/typeutils/internaltofrontend_test.go | 14 ++- internal/validate/account_test.go | 2 +- internal/validate/formvalidation.go | 12 +-- web/source/settings/user/settings.js | 10 +- 23 files changed, 244 insertions(+), 96 deletions(-) create mode 100644 internal/db/bundb/migrations/20230126170719_format_to_content_type.go diff --git a/docs/api/swagger.yaml b/docs/api/swagger.yaml index 601c3a2b9..783e1147e 100644 --- a/docs/api/swagger.yaml +++ b/docs/api/swagger.yaml @@ -143,10 +143,10 @@ definitions: description: Whether new statuses should be marked sensitive by default. type: boolean x-go-name: Sensitive - status_format: - description: The default posting format for new statuses. + status_content_type: + description: The default posting content type for new statuses. type: string - x-go-name: StatusFormat + x-go-name: StatusContentType title: Source represents display or publishing preferences of user's own account. type: object x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model @@ -1240,6 +1240,15 @@ definitions: format: int64 type: integer 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. type: object x-go-name: InstanceConfigurationStatuses @@ -2112,12 +2121,12 @@ definitions: x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model statusCreateRequest: properties: - format: + content_type: description: |- - Format to use when parsing this status. + Content type to use when parsing this status. in: formData type: string - x-go-name: Format + x-go-name: ContentType in_reply_to_id: description: |- 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. type: boolean x-go-name: Sensitive - status_format: - description: Default format for authored statuses (plain or markdown). + status_content_type: + description: Default format for authored statuses (text/plain or text/markdown). type: string - x-go-name: StatusFormat + x-go-name: StatusContentType title: UpdateSource is to be used specifically in an UpdateCredentialsRequest. type: object x-go-name: UpdateSource @@ -3081,9 +3090,9 @@ paths: in: formData name: source[language] 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 - name: source[status_format] + name: source[status_content_type] 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). in: formData @@ -4874,11 +4883,11 @@ paths: name: language type: string x-go-name: Language - - description: Format to use when parsing this status. + - description: Content type to use when parsing this status. in: formData - name: format + name: content_type type: string - x-go-name: Format + x-go-name: ContentType - description: This status will be federated beyond the local timeline(s). in: query name: federated diff --git a/internal/api/client/accounts/accountupdate.go b/internal/api/client/accounts/accountupdate.go index 3983fe8e1..b5d0dd5f9 100644 --- a/internal/api/client/accounts/accountupdate.go +++ b/internal/api/client/accounts/accountupdate.go @@ -99,9 +99,9 @@ import ( // description: Default language to use for authored statuses (ISO 6391). // type: string // - -// name: source[status_format] +// name: source[status_content_type] // 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 // - // name: custom_css @@ -190,8 +190,8 @@ func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateCredentialsRequest, form.Source.Language = &language } - if statusFormat, ok := sourceMap["status_format"]; ok { - form.Source.StatusFormat = &statusFormat + if statusContentType, ok := sourceMap["status_content_type"]; ok { + form.Source.StatusContentType = &statusContentType } if form == nil || @@ -205,7 +205,7 @@ func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateCredentialsRequest, form.Source.Privacy == nil && form.Source.Sensitive == nil && form.Source.Language == nil && - form.Source.StatusFormat == nil && + form.Source.StatusContentType == nil && form.FieldsAttributes == nil && form.CustomCSS == nil && form.EnableRSS == nil) { diff --git a/internal/api/client/accounts/accountupdate_test.go b/internal/api/client/accounts/accountupdate_test.go index 95387f3d7..f898e64da 100644 --- a/internal/api/client/accounts/accountupdate_test.go +++ b/internal/api/client/accounts/accountupdate_test.go @@ -414,13 +414,13 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpd suite.True(apimodelAccount.Locked) } -func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpdateStatusFormatOK() { +func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpdateStatusContentTypeOK() { // set up the request // we're updating the language of zork requestBody, w, err := testrig.CreateMultipartFormData( "", "", map[string]string{ - "source[status_format]": "markdown", + "source[status_content_type]": "text/markdown", }) if err != nil { panic(err) @@ -450,22 +450,22 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpd // check the returned api model account // 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) if err != nil { 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 // we're updating the language of zork requestBody, w, err := testrig.CreateMultipartFormData( "", "", map[string]string{ - "source[status_format]": "peepeepoopoo", + "source[status_content_type]": "peepeepoopoo", }) if err != nil { panic(err) @@ -486,7 +486,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpd b, err := ioutil.ReadAll(result.Body) 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) { diff --git a/internal/api/client/instance/instancepatch_test.go b/internal/api/client/instance/instancepatch_test.go index 0a508083c..6fb9b0d03 100644 --- a/internal/api/client/instance/instancepatch_test.go +++ b/internal/api/client/instance/instancepatch_test.go @@ -90,7 +90,11 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() { "statuses": { "max_characters": 5000, "max_media_attachments": 6, - "characters_reserved_per_url": 25 + "characters_reserved_per_url": 25, + "supported_mime_types": [ + "text/plain", + "text/markdown" + ] }, "media_attachments": { "supported_mime_types": [ @@ -188,7 +192,11 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() { "statuses": { "max_characters": 5000, "max_media_attachments": 6, - "characters_reserved_per_url": 25 + "characters_reserved_per_url": 25, + "supported_mime_types": [ + "text/plain", + "text/markdown" + ] }, "media_attachments": { "supported_mime_types": [ @@ -286,7 +294,11 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() { "statuses": { "max_characters": 5000, "max_media_attachments": 6, - "characters_reserved_per_url": 25 + "characters_reserved_per_url": 25, + "supported_mime_types": [ + "text/plain", + "text/markdown" + ] }, "media_attachments": { "supported_mime_types": [ @@ -435,7 +447,11 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() { "statuses": { "max_characters": 5000, "max_media_attachments": 6, - "characters_reserved_per_url": 25 + "characters_reserved_per_url": 25, + "supported_mime_types": [ + "text/plain", + "text/markdown" + ] }, "media_attachments": { "supported_mime_types": [ @@ -554,7 +570,11 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() { "statuses": { "max_characters": 5000, "max_media_attachments": 6, - "characters_reserved_per_url": 25 + "characters_reserved_per_url": 25, + "supported_mime_types": [ + "text/plain", + "text/markdown" + ] }, "media_attachments": { "supported_mime_types": [ @@ -689,7 +709,11 @@ func (suite *InstancePatchTestSuite) TestInstancePatch9() { "statuses": { "max_characters": 5000, "max_media_attachments": 6, - "characters_reserved_per_url": 25 + "characters_reserved_per_url": 25, + "supported_mime_types": [ + "text/plain", + "text/markdown" + ] }, "media_attachments": { "supported_mime_types": [ diff --git a/internal/api/client/statuses/statuscreate_test.go b/internal/api/client/statuses/statuscreate_test.go index 6802558ec..3c2120105 100644 --- a/internal/api/client/statuses/statuscreate_test.go +++ b/internal/api/client/statuses/statuscreate_test.go @@ -105,14 +105,14 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() { func (suite *StatusCreateTestSuite) TestPostNewStatusMarkdown() { // set default post language of account 1 to markdown testAccount := suite.testAccounts["local_account_1"] - testAccount.StatusFormat = "markdown" + testAccount.StatusContentType = "text/markdown" a := testAccount err := suite.db.UpdateAccount(context.Background(), a) if err != nil { suite.FailNow(err.Error()) } - suite.Equal(a.StatusFormat, "markdown") + suite.Equal(a.StatusContentType, "text/markdown") t := suite.testTokens["local_account_1"] oauthToken := oauth.DBTokenToToken(t) diff --git a/internal/api/model/account.go b/internal/api/model/account.go index 238def59d..c275c8155 100644 --- a/internal/api/model/account.go +++ b/internal/api/model/account.go @@ -174,8 +174,8 @@ type UpdateSource struct { Sensitive *bool `form:"sensitive" json:"sensitive" xml:"sensitive"` // Default language to use for authored statuses. (ISO 6391) Language *string `form:"language" json:"language" xml:"language"` - // Default format for authored statuses (plain or markdown). - StatusFormat *string `form:"status_format" json:"status_format" xml:"status_format"` + // Default format for authored statuses (text/plain or text/markdown). + StatusContentType *string `form:"status_content_type" json:"status_content_type" xml:"status_content_type"` } // UpdateField is to be used specifically in an UpdateCredentialsRequest. diff --git a/internal/api/model/instance.go b/internal/api/model/instance.go index c1eec1d54..84e4bab8f 100644 --- a/internal/api/model/instance.go +++ b/internal/api/model/instance.go @@ -73,6 +73,10 @@ type InstanceConfigurationStatuses struct { // // example: 25 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. diff --git a/internal/api/model/source.go b/internal/api/model/source.go index 9e2eafa0a..4f32399cc 100644 --- a/internal/api/model/source.go +++ b/internal/api/model/source.go @@ -31,8 +31,8 @@ type Source struct { Sensitive bool `json:"sensitive"` // The default posting language for new statuses. Language string `json:"language"` - // The default posting format for new statuses. - StatusFormat string `json:"status_format"` + // The default posting content type for new statuses. + StatusContentType string `json:"status_content_type"` // Profile bio. Note string `json:"note"` // Metadata about the account. diff --git a/internal/api/model/status.go b/internal/api/model/status.go index 92741edfe..57f5f6c63 100644 --- a/internal/api/model/status.go +++ b/internal/api/model/status.go @@ -179,9 +179,9 @@ type StatusCreateRequest struct { // ISO 639 language code for this status. // in: formData 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 - 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. @@ -227,16 +227,16 @@ type AdvancedVisibilityFlagsForm struct { Likeable *bool `form:"likeable" json:"likeable" xml:"likeable"` } -// StatusFormat is the format in which to parse the submitted status. -// Can be either plain or markdown. Empty will default to plain. +// StatusContentType is the content type with which to parse the submitted status. +// Can be either text/plain or text/markdown. Empty will default to text/plain. // -// swagger:enum statusFormat +// swagger:enum statusContentType // 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 ( - StatusFormatPlain StatusFormat = "plain" - StatusFormatMarkdown StatusFormat = "markdown" - StatusFormatDefault StatusFormat = StatusFormatPlain + StatusContentTypePlain StatusContentType = "text/plain" + StatusContentTypeMarkdown StatusContentType = "text/markdown" + StatusContentTypeDefault = StatusContentTypePlain ) diff --git a/internal/db/bundb/basic_test.go b/internal/db/bundb/basic_test.go index 86a9995fc..13fa9f8c7 100644 --- a/internal/db/bundb/basic_test.go +++ b/internal/db/bundb/basic_test.go @@ -89,7 +89,7 @@ func (suite *BasicTestSuite) TestPutAccountWithBunDefaultFields() { suite.Empty(a.Privacy) suite.False(*a.Sensitive) suite.Equal("en", a.Language) - suite.Empty(a.StatusFormat) + suite.Empty(a.StatusContentType) suite.Equal(testAccount.URI, a.URI) suite.Equal(testAccount.URL, a.URL) suite.Zero(testAccount.FetchedAt) diff --git a/internal/db/bundb/migrations/20230126170719_format_to_content_type.go b/internal/db/bundb/migrations/20230126170719_format_to_content_type.go new file mode 100644 index 000000000..f8ffb9087 --- /dev/null +++ b/internal/db/bundb/migrations/20230126170719_format_to_content_type.go @@ -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 . +*/ + +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) + } +} diff --git a/internal/gtsmodel/account.go b/internal/gtsmodel/account.go index 076bd7a9a..5c5a60373 100644 --- a/internal/gtsmodel/account.go +++ b/internal/gtsmodel/account.go @@ -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 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? - 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. 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 diff --git a/internal/processing/account/update.go b/internal/processing/account/update.go index 537857cee..a96c17eeb 100644 --- a/internal/processing/account/update.go +++ b/internal/processing/account/update.go @@ -79,7 +79,7 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form // Process note to generate a valid HTML representation var f text.FormatFunc - if account.StatusFormat == "markdown" { + if account.StatusContentType == "text/markdown" { f = p.formatter.FromMarkdown } else { f = p.formatter.FromPlain @@ -144,12 +144,12 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form account.Privacy = privacy } - if form.Source.StatusFormat != nil { - if err := validate.StatusFormat(*form.Source.StatusFormat); err != nil { + if form.Source.StatusContentType != nil { + if err := validate.StatusContentType(*form.Source.StatusContentType); err != nil { return nil, gtserror.NewErrorBadRequest(err, err.Error()) } - account.StatusFormat = *form.Source.StatusFormat + account.StatusContentType = *form.Source.StatusContentType } } diff --git a/internal/processing/account/update_test.go b/internal/processing/account/update_test.go index 8ebce7888..486848549 100644 --- a/internal/processing/account/update_test.go +++ b/internal/processing/account/update_test.go @@ -122,13 +122,13 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateWithMarkdownNote() { Note: ¬e, } - // set default post language of account 1 to markdown - testAccount.StatusFormat = "markdown" + // set default post content type of account 1 to markdown + testAccount.StatusContentType = "text/markdown" // should get no error from the update function, and an api model account returned apiAccount, errWithCode := suite.accountProcessor.Update(context.Background(), testAccount, form) // reset test account to avoid breaking other tests - testAccount.StatusFormat = "plain" + testAccount.StatusContentType = "text/plain" suite.NoError(errWithCode) suite.NotNil(apiAccount) diff --git a/internal/processing/status/create.go b/internal/processing/status/create.go index 4e5399469..35c36a29b 100644 --- a/internal/processing/status/create.go +++ b/internal/processing/status/create.go @@ -290,32 +290,32 @@ func processContent(ctx context.Context, dbService db.DB, formatter text.Formatt return nil } - // if format wasn't specified we should try to figure out what format this user prefers - if form.Format == "" { + // if content type wasn't specified we should try to figure out what content type this user prefers + if form.ContentType == "" { acct, err := dbService.GetAccountByID(ctx, accountID) if err != nil { return fmt.Errorf("error processing new content: couldn't retrieve account from db to check post format: %s", err) } - switch acct.StatusFormat { - case "plain": - form.Format = apimodel.StatusFormatPlain - case "markdown": - form.Format = apimodel.StatusFormatMarkdown + switch acct.StatusContentType { + case "text/plain": + form.ContentType = apimodel.StatusContentTypePlain + case "text/markdown": + form.ContentType = apimodel.StatusContentTypeMarkdown 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 - switch form.Format { - case apimodel.StatusFormatPlain: + switch form.ContentType { + case apimodel.StatusContentTypePlain: f = formatter.FromPlain - case apimodel.StatusFormatMarkdown: + case apimodel.StatusContentTypeMarkdown: f = formatter.FromMarkdown 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) diff --git a/internal/processing/status/create_test.go b/internal/processing/status/create_test.go index 84adbf32f..717843ce1 100644 --- a/internal/processing/status/create_test.go +++ b/internal/processing/status/create_test.go @@ -50,7 +50,7 @@ func (suite *StatusCreateTestSuite) TestProcessContentWarningWithQuotationMarks( Visibility: apimodel.VisibilityPublic, ScheduledAt: "", Language: "en", - Format: apimodel.StatusFormatPlain, + ContentType: apimodel.StatusContentTypePlain, }, AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{ Federated: nil, @@ -84,7 +84,7 @@ func (suite *StatusCreateTestSuite) TestProcessContentWarningWithHTMLEscapedQuot Visibility: apimodel.VisibilityPublic, ScheduledAt: "", Language: "en", - Format: apimodel.StatusFormatPlain, + ContentType: apimodel.StatusContentTypePlain, }, AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{ Federated: nil, @@ -122,7 +122,7 @@ func (suite *StatusCreateTestSuite) TestProcessStatusMarkdownWithUnderscoreEmoji Visibility: apimodel.VisibilityPublic, ScheduledAt: "", Language: "en", - Format: apimodel.StatusFormatMarkdown, + ContentType: apimodel.StatusContentTypeMarkdown, }, AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{ Federated: nil, @@ -156,7 +156,7 @@ func (suite *StatusCreateTestSuite) TestProcessStatusMarkdownWithSpoilerTextEmoj Visibility: apimodel.VisibilityPublic, ScheduledAt: "", Language: "en", - Format: apimodel.StatusFormatMarkdown, + ContentType: apimodel.StatusContentTypeMarkdown, }, AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{ Federated: nil, @@ -194,7 +194,7 @@ func (suite *StatusCreateTestSuite) TestProcessMediaDescriptionTooShort() { Visibility: apimodel.VisibilityPublic, ScheduledAt: "", Language: "en", - Format: apimodel.StatusFormatPlain, + ContentType: apimodel.StatusContentTypePlain, }, AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{ Federated: nil, diff --git a/internal/trans/import_test.go b/internal/trans/import_test.go index a53305c79..e980d090b 100644 --- a/internal/trans/import_test.go +++ b/internal/trans/import_test.go @@ -111,7 +111,7 @@ func (suite *ImportMinimalTestSuite) TestImportMinimalOK() { suite.Equal(testAccountBefore.Privacy, testAccountAfter.Privacy) suite.Equal(testAccountBefore.Sensitive, testAccountAfter.Sensitive) 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.URL, testAccountAfter.URL) suite.Equal(testAccountBefore.InboxURI, testAccountAfter.InboxURI) diff --git a/internal/trans/model/account.go b/internal/trans/model/account.go index e29293fdd..cc0ed0f95 100644 --- a/internal/trans/model/account.go +++ b/internal/trans/model/account.go @@ -43,7 +43,7 @@ type Account struct { Privacy string `json:"privacy,omitempty" bun:",nullzero"` Sensitive *bool `json:"sensitive"` 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"` URL string `json:"url" bun:",nullzero"` InboxURI string `json:"inboxURI" bun:",nullzero"` diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go index ebea023cf..aa9352272 100644 --- a/internal/typeutils/internaltofrontend.go +++ b/internal/typeutils/internaltofrontend.go @@ -46,6 +46,11 @@ const ( 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) { // we can build this sensitive account easily by first getting the public account.... apiAccount, err := c.AccountToAPIAccountPublic(ctx, a) @@ -67,16 +72,16 @@ func (c *converter) AccountToAPIAccountSensitive(ctx context.Context, a *gtsmode frc = len(frs) } - statusFormat := string(apimodel.StatusFormatDefault) - if a.StatusFormat != "" { - statusFormat = a.StatusFormat + statusContentType := string(apimodel.StatusContentTypeDefault) + if a.StatusContentType != "" { + statusContentType = a.StatusContentType } apiAccount.Source = &apimodel.Source{ Privacy: c.VisToAPIVis(ctx, a.Privacy), Sensitive: *a.Sensitive, Language: a.Language, - StatusFormat: statusFormat, + StatusContentType: statusContentType, Note: a.NoteRaw, Fields: apiAccount.Fields, 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.MaxMediaAttachments = config.GetStatusesMediaMaxFiles() instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL + instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaImageMaxSize()) 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.MaxMediaAttachments = config.GetStatusesMediaMaxFiles() instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL + instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaImageMaxSize()) instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit diff --git a/internal/typeutils/internaltofrontend_test.go b/internal/typeutils/internaltofrontend_test.go index f82fd8cb5..30241892e 100644 --- a/internal/typeutils/internaltofrontend_test.go +++ b/internal/typeutils/internaltofrontend_test.go @@ -198,7 +198,7 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendSensitive() { "privacy": "public", "sensitive": false, "language": "en", - "status_format": "plain", + "status_content_type": "text/plain", "note": "hey yo this is my profile!", "fields": [], "follow_requests_count": 0 @@ -504,7 +504,11 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV1ToFrontend() { "statuses": { "max_characters": 5000, "max_media_attachments": 6, - "characters_reserved_per_url": 25 + "characters_reserved_per_url": 25, + "supported_mime_types": [ + "text/plain", + "text/markdown" + ] }, "media_attachments": { "supported_mime_types": [ @@ -616,7 +620,11 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV2ToFrontend() { "statuses": { "max_characters": 5000, "max_media_attachments": 6, - "characters_reserved_per_url": 25 + "characters_reserved_per_url": 25, + "supported_mime_types": [ + "text/plain", + "text/markdown" + ] }, "media_attachments": { "supported_mime_types": [ diff --git a/internal/validate/account_test.go b/internal/validate/account_test.go index 3c918e1fc..0e23a2409 100644 --- a/internal/validate/account_test.go +++ b/internal/validate/account_test.go @@ -63,7 +63,7 @@ func happyAccount() *gtsmodel.Account { Privacy: gtsmodel.VisibilityPublic, Sensitive: testrig.FalseBool(), Language: "en", - StatusFormat: "plain", + StatusContentType: "text/plain", URI: "http://localhost:8080/users/the_mighty_zork", URL: "http://localhost:8080/@the_mighty_zork", FetchedAt: time.Time{}, diff --git a/internal/validate/formvalidation.go b/internal/validate/formvalidation.go index 48e0b7015..32aa1fd1e 100644 --- a/internal/validate/formvalidation.go +++ b/internal/validate/formvalidation.go @@ -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) } -// StatusFormat checks that the desired status format setting is valid. -func StatusFormat(statusFormat string) error { - if statusFormat == "" { +// StatusContentType checks that the desired status format setting is valid. +func StatusContentType(statusContentType string) error { + if statusContentType == "" { return fmt.Errorf("empty string for status format not allowed") } - switch apimodel.StatusFormat(statusFormat) { - case apimodel.StatusFormatPlain, apimodel.StatusFormatMarkdown: + switch apimodel.StatusContentType(statusContentType) { + case apimodel.StatusContentTypePlain, apimodel.StatusContentTypeMarkdown: 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 { diff --git a/web/source/settings/user/settings.js b/web/source/settings/user/settings.js index 6b6b3c09f..e997beeb6 100644 --- a/web/source/settings/user/settings.js +++ b/web/source/settings/user/settings.js @@ -53,14 +53,14 @@ function UserSettingsForm({ data }) { - string source[privacy] - bool source[sensitive] - string source[language] - - string source[status_format] + - string source[status_content_type] */ const form = { defaultPrivacy: useTextInput("source[privacy]", { source: data, defaultValue: "unlisted" }), isSensitive: useBoolInput("source[sensitive]", { source: data }), 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()); @@ -82,10 +82,10 @@ function UserSettingsForm({ data }) { }> Learn more about post privacy settings (opens in a new tab) -