Files
decePubClient/Models/ProfileForm.cs
T
thepraandClaude Opus 5.5 fd2f2e7c65
Build / Build (push) Successful in 51s
R1 cleanup: collAnon and dead code gone, no IndexedDB, honest PWA files
Starts the completion program (SocialPub docs/ROADMAP.md, "Owner decisions on finishing everything", 2026-10-17).

- The localizer's namespace is decePubClient.Services; collAnon is gone from the repo.
- Dead code: PagesBase, VConstants, SettingsOptions, ViewLanguage, unused APIs routes, InstanceService.Self,
  RequestStats.LastOf/AverageMs, PostAnon/DeleteTotallyAnon, unused AppStatusService methods and their main.js helpers
  (the app badge stays for Web Push), ExtensionMethods/SUtility leftovers (Markdig and BlazorZXingJs packages go),
  unused PublicCacheData/PageSettings/AppConfiguration fields, commented-out code.
- The client log in IndexedDB, which nothing ever read, goes with the whole IndexedDB layer (DnetIndexedDb, IStorage):
  logs go to the browser console; main.js deletes the old "data" database once.
- The solution names the real ClientModels project. Unused vendor assets (fontawesome, solid, open-iconic, rxjs) go.
- robots.txt disallows everything; manifest.json and index.html say decePub; the service worker's cache name follows
  each publish's assets version, so old caches are purged.
- MessageType is Visibility (member names kept: they are resx keys); ExpandMessage.razor is Thread.razor (same route).
- i18n: dead keys removed, Italian "camera" is "punto di vista", DInputBase/DCalendar validation messages come from
  ErrorMessages, DataTransfer's spinner has its label.
- CLAUDE.md follows: client storage, JS interop, PWA, the D fields' messages.

Verified: dotnet build decePubClient.csproj and decePubClient.sln -c Release (0 errors, no new warnings); a Pasture
publish served by the pasture's decepub peer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WpVCoDeM1qUTRp4fXQdfof
2026-10-17 02:56:57 +02:00

106 lines
4.1 KiB
C#

using decePubClient.Models.Mastodon;
using decePubClient.Models.Types;
namespace decePubClient.Models
{
/// <summary>
/// The persona's profile as Settings edits it, which is what update_credentials takes: the words as written (its
/// account's Source), the flags, the posting defaults, and a new avatar or header when one was picked. Fields always
/// holds FieldCount rows; a row without a name is dropped.
/// </summary>
public class ProfileForm
{
public const int FieldCount = 4;
public const int DisplayNameLength = 30;
public const int NoteLength = 500;
public const int FieldLength = 255;
public string DisplayName { get; set; }
public string Note { get; set; }
public bool Locked { get; set; }
public bool Bot { get; set; }
public bool Discoverable { get; set; }
public bool Indexable { get; set; }
public Visibility Privacy { get; set; } = Visibility.Public;
public bool Sensitive { get; set; }
public string Language { get; set; }
public QuotePolicy QuotePolicy { get; set; } = QuotePolicy.Public;
public List<AccountField> Fields { get; set; } = [];
public UploadMedia Avatar { get; set; }
public UploadMedia Header { get; set; }
public static ProfileForm Of(Account account)
{
var source = account.Source ?? new();
var fields = source.Fields.Count > 0 ? source.Fields : account.Fields;
return new()
{
DisplayName = account.DisplayName,
Note = source.Note,
Locked = account.Locked,
Bot = account.Bot,
Discoverable = account.Discoverable == true,
Indexable = account.Indexable == true,
Privacy = source.Privacy switch
{
"private" => Visibility.FollowersOnly,
"unlisted" => Visibility.Unlisted,
_ => Visibility.Public
},
Sensitive = source.Sensitive,
Language = source.Language,
QuotePolicy = source.QuotePolicy switch
{
"followers" => QuotePolicy.Followers,
"nobody" => QuotePolicy.Nobody,
_ => QuotePolicy.Public
},
Fields = [.. fields.Take(FieldCount).Select(f => new AccountField { Name = f.Name, Value = f.Value }),
.. Enumerable.Range(0, Math.Max(0, FieldCount - fields.Count)).Select(_ => new AccountField())]
};
}
/// <summary>The form as update_credentials reads it, with the pictures as files.</summary>
public MultipartFormDataContent Content()
{
var content = new MultipartFormDataContent
{
{ new StringContent(DisplayName?.Trim() ?? ""), "display_name" },
{ new StringContent(Note ?? ""), "note" },
{ new StringContent(Flag(Locked)), "locked" },
{ new StringContent(Flag(Bot)), "bot" },
{ new StringContent(Flag(Discoverable)), "discoverable" },
{ new StringContent(Flag(Indexable)), "indexable" },
{ new StringContent(Privacy switch { Visibility.FollowersOnly => "private", Visibility.Unlisted => "unlisted", _ => "public" }), "source[privacy]" },
{ new StringContent(Flag(Sensitive)), "source[sensitive]" },
{ new StringContent(Language ?? ""), "source[language]" },
{ new StringContent(QuotePolicy switch { QuotePolicy.Followers => "followers", QuotePolicy.Nobody => "nobody", _ => "public" }), "source[quote_policy]" }
};
// every row is sent, so a row emptied here is removed there
for (var i = 0; i < Fields.Count; i++)
{
content.Add(new StringContent(Fields[i].Name?.Trim() ?? ""), $"fields_attributes[{i}][name]");
content.Add(new StringContent(Fields[i].Value?.Trim() ?? ""), $"fields_attributes[{i}][value]");
}
if (Avatar is not null)
content.Add(Picture(Avatar), "avatar", Avatar.FileName);
if (Header is not null)
content.Add(Picture(Header), "header", Header.FileName);
return content;
}
static string Flag(bool value) => value ? "true" : "false";
// PrivaPub keeps pictures up to this size and resizes them
public const long MaximumPictureSize = 20 * 1024 * 1024;
// streamed from the browser's file, read again for each attempt
static StreamContent Picture(UploadMedia media)
{
var picture = new StreamContent(media.File.OpenReadStream(MaximumPictureSize));
picture.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(media.ContentType);
return picture;
}
}
}