merge: Hellion Chat 0.5.1 — Backlog Sweep
This commit is contained in:
commit
46b63ffdd1
16 files changed
+202
-55
No files matched your search
@@ -4,7 +4,7 @@
|
|||||||
0.1.0 is our bootstrap release; the underlying Chat 2 base is
|
0.1.0 is our bootstrap release; the underlying Chat 2 base is
|
||||||
called out in the yaml changelog so users can see what it
|
called out in the yaml changelog so users can see what it
|
||||||
derives from. -->
|
derives from. -->
|
||||||
<Version>0.5.0</Version>
|
<Version>0.5.1</Version>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<!-- HellionChat fork: assembly is renamed so Dalamud uses
|
<!-- HellionChat fork: assembly is renamed so Dalamud uses
|
||||||
pluginConfigs/HellionChat instead of pluginConfigs/ChatTwo,
|
pluginConfigs/HellionChat instead of pluginConfigs/ChatTwo,
|
||||||
|
|||||||
@@ -35,20 +35,20 @@ public static class EmoteCache
|
|||||||
public Emote Emote { get; set; }
|
public Emote Emote { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("id")]
|
[JsonPropertyName("id")]
|
||||||
public string Id { get; set; }
|
public required string Id { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public struct Emote()
|
public struct Emote()
|
||||||
{
|
{
|
||||||
[JsonPropertyName("id")]
|
[JsonPropertyName("id")]
|
||||||
public string Id { get; set; }
|
public required string Id { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("code")]
|
[JsonPropertyName("code")]
|
||||||
public string Code { get; set; }
|
public required string Code { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("imageType")]
|
[JsonPropertyName("imageType")]
|
||||||
public string ImageType { get; set; }
|
public required string ImageType { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum LoadingState
|
public enum LoadingState
|
||||||
@@ -66,7 +66,7 @@ public static class EmoteCache
|
|||||||
|
|
||||||
public static string[] SortedCodeArray = [];
|
public static string[] SortedCodeArray = [];
|
||||||
|
|
||||||
public static async void LoadData()
|
public static async Task LoadData()
|
||||||
{
|
{
|
||||||
if (State is not LoadingState.Unloaded)
|
if (State is not LoadingState.Unloaded)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -40,6 +40,36 @@ tags:
|
|||||||
- Replacement
|
- Replacement
|
||||||
- Privacy
|
- Privacy
|
||||||
changelog: |-
|
changelog: |-
|
||||||
|
**Hellion Chat 0.5.1 — Backlog Sweep**
|
||||||
|
|
||||||
|
Pure hardening and polish. No new features. Eight backlog items
|
||||||
|
from the v0.5.0 codebase review collected into one patch:
|
||||||
|
|
||||||
|
- Cleanup preview now flags itself as out-of-date when the user
|
||||||
|
edits the whitelist after the last refresh, and the refresh
|
||||||
|
button is visually emphasised in that state
|
||||||
|
- Greeted Auto-Tell-Tabs now also dim their selection and hover
|
||||||
|
backgrounds in the sidebar, not just the text
|
||||||
|
- Performance section in the General tab moves to the standard
|
||||||
|
HelpMarker tooltip pattern instead of a wall-of-text description
|
||||||
|
- Tabs and Database settings tabs pull their display name from
|
||||||
|
HellionStrings instead of the upstream Language bundle, so all
|
||||||
|
eight tabs share one i18n source
|
||||||
|
- FontChooser results are now marshalled onto the framework thread
|
||||||
|
via Plugin.Framework.Run instead of being written to settings
|
||||||
|
state directly from the threadpool
|
||||||
|
- EmoteCache.LoadData drops async void and the four CS8618 build
|
||||||
|
warnings the build has been carrying since v0.4.0
|
||||||
|
- All MessageStore SQL paths that fed dynamic value lists into
|
||||||
|
interpolated SQL now use named parameter bindings via a new
|
||||||
|
BindIntList helper. Same behaviour, defence against future
|
||||||
|
user-input regressions
|
||||||
|
|
||||||
|
Configuration version is unchanged at 10. No migration. Existing
|
||||||
|
installs upgrade silently.
|
||||||
|
|
||||||
|
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||||
|
|
||||||
**Hellion Chat 0.5.0 — Settings UX polish**
|
**Hellion Chat 0.5.0 — Settings UX polish**
|
||||||
|
|
||||||
The settings window has been pulled apart and rebuilt around eight
|
The settings window has been pulled apart and rebuilt around eight
|
||||||
|
|||||||
+71
-31
@@ -239,6 +239,9 @@ internal class MessageStore : IDisposable
|
|||||||
|
|
||||||
private bool ColumnExists(string table, string column)
|
private bool ColumnExists(string table, string column)
|
||||||
{
|
{
|
||||||
|
// PRAGMA does not accept SQLite parameter bindings. The table name is
|
||||||
|
// a compile-time constant fed in from internal call sites, so the
|
||||||
|
// interpolation cannot be reached from any user-controlled path.
|
||||||
using var cmd = Connection.CreateCommand();
|
using var cmd = Connection.CreateCommand();
|
||||||
cmd.CommandText = $"PRAGMA table_info({table});";
|
cmd.CommandText = $"PRAGMA table_info({table});";
|
||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
@@ -298,8 +301,10 @@ internal class MessageStore : IDisposable
|
|||||||
{
|
{
|
||||||
Plugin.Log.Information($"Setting version {version}");
|
Plugin.Log.Information($"Setting version {version}");
|
||||||
using var cmd = Connection.CreateCommand();
|
using var cmd = Connection.CreateCommand();
|
||||||
// Parameters aren't supported for PRAGMA queries, and you can't set the
|
// PRAGMA does not accept SQLite parameter bindings, and there is no
|
||||||
// version with a pragma_ function.
|
// pragma_ function variant that can set the version either. The
|
||||||
|
// version is a compile-time int from the migration sequence, never
|
||||||
|
// user input.
|
||||||
cmd.CommandText = $"PRAGMA user_version = {version};";
|
cmd.CommandText = $"PRAGMA user_version = {version};";
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
@@ -346,31 +351,44 @@ internal class MessageStore : IDisposable
|
|||||||
throw new ArgumentOutOfRangeException(nameof(chatTypeDaysMap), "Negative retention is not allowed.");
|
throw new ArgumentOutOfRangeException(nameof(chatTypeDaysMap), "Negative retention is not allowed.");
|
||||||
|
|
||||||
var nowMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
var nowMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||||
var clauses = new List<string>();
|
|
||||||
foreach (var (type, days) in chatTypeDaysMap)
|
|
||||||
{
|
|
||||||
var cutoff = nowMs - days * 86400000L;
|
|
||||||
clauses.Add($"(ChatType = {type} AND Date < {cutoff})");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Catch-all for channels without an explicit override. "0" is treated
|
if (chatTypeDaysMap.Count == 0 && defaultDays <= 0)
|
||||||
// as "do not delete by default" — without an explicit user override,
|
|
||||||
// unmapped channels stay forever instead of getting wiped immediately.
|
|
||||||
if (defaultDays > 0)
|
|
||||||
{
|
|
||||||
var cutoff = nowMs - defaultDays * 86400000L;
|
|
||||||
var explicitTypes = chatTypeDaysMap.Count > 0
|
|
||||||
? string.Join(",", chatTypeDaysMap.Keys)
|
|
||||||
: "-1"; // empty list would produce invalid SQL
|
|
||||||
clauses.Add($"(ChatType NOT IN ({explicitTypes}) AND Date < {cutoff})");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clauses.Count == 0)
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
long deleted;
|
long deleted;
|
||||||
using (var cmd = Connection.CreateCommand())
|
using (var cmd = Connection.CreateCommand())
|
||||||
{
|
{
|
||||||
|
var clauses = new List<string>();
|
||||||
|
var index = 0;
|
||||||
|
foreach (var (type, days) in chatTypeDaysMap)
|
||||||
|
{
|
||||||
|
var cutoff = nowMs - days * 86400000L;
|
||||||
|
var typeParam = $"$type{index}";
|
||||||
|
var cutoffParam = $"$cutoff{index}";
|
||||||
|
cmd.Parameters.AddWithValue(typeParam, type);
|
||||||
|
cmd.Parameters.AddWithValue(cutoffParam, cutoff);
|
||||||
|
clauses.Add($"(ChatType = {typeParam} AND Date < {cutoffParam})");
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catch-all for channels without an explicit override. "0" is
|
||||||
|
// treated as "do not delete by default" — without an explicit
|
||||||
|
// user override, unmapped channels stay forever instead of
|
||||||
|
// getting wiped immediately.
|
||||||
|
if (defaultDays > 0)
|
||||||
|
{
|
||||||
|
var defaultCutoff = nowMs - defaultDays * 86400000L;
|
||||||
|
cmd.Parameters.AddWithValue("$defaultCutoff", defaultCutoff);
|
||||||
|
|
||||||
|
var explicitPlaceholders = chatTypeDaysMap.Count > 0
|
||||||
|
? BindIntList(cmd, "explicit", chatTypeDaysMap.Keys)
|
||||||
|
: "-1"; // empty list would produce invalid SQL
|
||||||
|
clauses.Add($"(ChatType NOT IN ({explicitPlaceholders}) AND Date < $defaultCutoff)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clauses.Count == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
cmd.CommandText = $"DELETE FROM messages WHERE {string.Join(" OR ", clauses)};";
|
cmd.CommandText = $"DELETE FROM messages WHERE {string.Join(" OR ", clauses)};";
|
||||||
cmd.CommandTimeout = 600;
|
cmd.CommandTimeout = 600;
|
||||||
deleted = cmd.ExecuteNonQuery();
|
deleted = cmd.ExecuteNonQuery();
|
||||||
@@ -395,11 +413,11 @@ internal class MessageStore : IDisposable
|
|||||||
throw new InvalidOperationException("CleanupRetainOnly requires at least one allowed ChatType. Use ClearMessages for a full wipe.");
|
throw new InvalidOperationException("CleanupRetainOnly requires at least one allowed ChatType. Use ClearMessages for a full wipe.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var inList = string.Join(",", allowedTypes);
|
|
||||||
long deleted;
|
long deleted;
|
||||||
using (var cmd = Connection.CreateCommand())
|
using (var cmd = Connection.CreateCommand())
|
||||||
{
|
{
|
||||||
cmd.CommandText = $"DELETE FROM messages WHERE ChatType NOT IN ({inList});";
|
var placeholders = BindIntList(cmd, "ct", allowedTypes);
|
||||||
|
cmd.CommandText = $"DELETE FROM messages WHERE ChatType NOT IN ({placeholders});";
|
||||||
cmd.CommandTimeout = 600;
|
cmd.CommandTimeout = 600;
|
||||||
deleted = cmd.ExecuteNonQuery();
|
deleted = cmd.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
@@ -512,15 +530,16 @@ internal class MessageStore : IDisposable
|
|||||||
DateTimeOffset? from,
|
DateTimeOffset? from,
|
||||||
DateTimeOffset? to)
|
DateTimeOffset? to)
|
||||||
{
|
{
|
||||||
|
var cmd = Connection.CreateCommand();
|
||||||
|
|
||||||
var clauses = new List<string> { "deleted = false" };
|
var clauses = new List<string> { "deleted = false" };
|
||||||
if (chatTypes is { Count: > 0 })
|
if (chatTypes is { Count: > 0 })
|
||||||
clauses.Add($"ChatType IN ({string.Join(",", chatTypes)})");
|
clauses.Add($"ChatType IN ({BindIntList(cmd, "exct", chatTypes)})");
|
||||||
if (from is not null)
|
if (from is not null)
|
||||||
clauses.Add("Date >= $From");
|
clauses.Add("Date >= $From");
|
||||||
if (to is not null)
|
if (to is not null)
|
||||||
clauses.Add("Date <= $To");
|
clauses.Add("Date <= $To");
|
||||||
|
|
||||||
var cmd = Connection.CreateCommand();
|
|
||||||
cmd.CommandText = @"
|
cmd.CommandText = @"
|
||||||
SELECT
|
SELECT
|
||||||
Id,
|
Id,
|
||||||
@@ -693,16 +712,17 @@ internal class MessageStore : IDisposable
|
|||||||
|
|
||||||
internal long CountDateRange(DateTime after, DateTime before, IEnumerable<byte> channels, ulong? receiver = null)
|
internal long CountDateRange(DateTime after, DateTime before, IEnumerable<byte> channels, ulong? receiver = null)
|
||||||
{
|
{
|
||||||
|
using var cmd = Connection.CreateCommand();
|
||||||
|
|
||||||
List<string> whereClauses = ["deleted = false"];
|
List<string> whereClauses = ["deleted = false"];
|
||||||
if (receiver != null)
|
if (receiver != null)
|
||||||
whereClauses.Add("Receiver = $Receiver");
|
whereClauses.Add("Receiver = $Receiver");
|
||||||
|
|
||||||
whereClauses.Add("Date BETWEEN $After AND $Before");
|
whereClauses.Add("Date BETWEEN $After AND $Before");
|
||||||
whereClauses.Add($"ChatType IN ({string.Join(", ", channels)})");
|
whereClauses.Add($"ChatType IN ({BindIntList(cmd, "cdr", channels.Select(c => (int)c))})");
|
||||||
|
|
||||||
var whereClause = "WHERE " + string.Join(" AND ", whereClauses);
|
var whereClause = "WHERE " + string.Join(" AND ", whereClauses);
|
||||||
|
|
||||||
using var cmd = Connection.CreateCommand();
|
|
||||||
// Select last N messages by date DESC, but reverse the order to get
|
// Select last N messages by date DESC, but reverse the order to get
|
||||||
// them in ascending order.
|
// them in ascending order.
|
||||||
cmd.CommandText = @"
|
cmd.CommandText = @"
|
||||||
@@ -722,16 +742,17 @@ internal class MessageStore : IDisposable
|
|||||||
|
|
||||||
internal MessageEnumerator GetDateRange(DateTime after, DateTime before, IEnumerable<byte> channels, ulong? receiver = null)
|
internal MessageEnumerator GetDateRange(DateTime after, DateTime before, IEnumerable<byte> channels, ulong? receiver = null)
|
||||||
{
|
{
|
||||||
|
var cmd = Connection.CreateCommand();
|
||||||
|
|
||||||
List<string> whereClauses = ["deleted = false"];
|
List<string> whereClauses = ["deleted = false"];
|
||||||
if (receiver != null)
|
if (receiver != null)
|
||||||
whereClauses.Add("Receiver = $Receiver");
|
whereClauses.Add("Receiver = $Receiver");
|
||||||
|
|
||||||
whereClauses.Add("Date BETWEEN $After AND $Before");
|
whereClauses.Add("Date BETWEEN $After AND $Before");
|
||||||
whereClauses.Add($"ChatType IN ({string.Join(", ", channels)})");
|
whereClauses.Add($"ChatType IN ({BindIntList(cmd, "gdr", channels.Select(c => (int)c))})");
|
||||||
|
|
||||||
var whereClause = $"WHERE {string.Join(" AND ", whereClauses)}";
|
var whereClause = $"WHERE {string.Join(" AND ", whereClauses)}";
|
||||||
|
|
||||||
var cmd = Connection.CreateCommand();
|
|
||||||
// Select last N messages by date DESC, but reverse the order to get
|
// Select last N messages by date DESC, but reverse the order to get
|
||||||
// them in ascending order.
|
// them in ascending order.
|
||||||
cmd.CommandText = @"
|
cmd.CommandText = @"
|
||||||
@@ -763,16 +784,17 @@ internal class MessageStore : IDisposable
|
|||||||
|
|
||||||
internal MessageEnumerator GetPagedDateRange(DateTime after, DateTime before, IEnumerable<byte> channels, ulong? receiver = null, int page = 0)
|
internal MessageEnumerator GetPagedDateRange(DateTime after, DateTime before, IEnumerable<byte> channels, ulong? receiver = null, int page = 0)
|
||||||
{
|
{
|
||||||
|
var cmd = Connection.CreateCommand();
|
||||||
|
|
||||||
List<string> whereClauses = ["deleted = false"];
|
List<string> whereClauses = ["deleted = false"];
|
||||||
if (receiver != null)
|
if (receiver != null)
|
||||||
whereClauses.Add("Receiver = $Receiver");
|
whereClauses.Add("Receiver = $Receiver");
|
||||||
|
|
||||||
whereClauses.Add("Date BETWEEN $After AND $Before");
|
whereClauses.Add("Date BETWEEN $After AND $Before");
|
||||||
whereClauses.Add($"ChatType IN ({string.Join(", ", channels)})");
|
whereClauses.Add($"ChatType IN ({BindIntList(cmd, "pdr", channels.Select(c => (int)c))})");
|
||||||
|
|
||||||
var whereClause = $"WHERE {string.Join(" AND ", whereClauses)}";
|
var whereClause = $"WHERE {string.Join(" AND ", whereClauses)}";
|
||||||
|
|
||||||
var cmd = Connection.CreateCommand();
|
|
||||||
// Select last N messages by date DESC, but reverse the order to get
|
// Select last N messages by date DESC, but reverse the order to get
|
||||||
// them in ascending order.
|
// them in ascending order.
|
||||||
cmd.CommandText = @"
|
cmd.CommandText = @"
|
||||||
@@ -806,6 +828,24 @@ internal class MessageStore : IDisposable
|
|||||||
|
|
||||||
return new MessageEnumerator(cmd.ExecuteReader());
|
return new MessageEnumerator(cmd.ExecuteReader());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build "$prefix0,$prefix1,..." placeholder list and bind values to
|
||||||
|
// the command. SQLite has no native array parameter, so we generate
|
||||||
|
// the list at runtime and bind each entry under its own name. Used
|
||||||
|
// for IN-clauses and similar dynamic-arity SQL fragments.
|
||||||
|
private static string BindIntList(SqliteCommand cmd, string prefix, IEnumerable<int> values)
|
||||||
|
{
|
||||||
|
var names = new List<string>();
|
||||||
|
var index = 0;
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
var name = $"${prefix}{index}";
|
||||||
|
cmd.Parameters.AddWithValue(name, value);
|
||||||
|
names.Add(name);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return string.Join(",", names);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class MessageEnumerator(DbDataReader reader) : IEnumerable<Message>, IDisposable, IAsyncDisposable
|
internal class MessageEnumerator(DbDataReader reader) : IEnumerable<Message>, IDisposable, IAsyncDisposable
|
||||||
|
|||||||
+1
-1
@@ -241,7 +241,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
Interface.UiBuilder.OpenMainUi += OpenMainUi;
|
Interface.UiBuilder.OpenMainUi += OpenMainUi;
|
||||||
|
|
||||||
if (Config.ShowEmotes)
|
if (Config.ShowEmotes)
|
||||||
Task.Run(EmoteCache.LoadData);
|
_ = EmoteCache.LoadData(); // Fire-and-forget intentional, exceptions are caught inside
|
||||||
|
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
// Avoid 300ms hitch when sending first message by preloading the
|
// Avoid 300ms hitch when sending first message by preloading the
|
||||||
|
|||||||
+3
@@ -64,6 +64,7 @@ internal class HellionStrings
|
|||||||
internal static string Cleanup_Heading => Get(nameof(Cleanup_Heading));
|
internal static string Cleanup_Heading => Get(nameof(Cleanup_Heading));
|
||||||
internal static string Cleanup_Help_Intro => Get(nameof(Cleanup_Help_Intro));
|
internal static string Cleanup_Help_Intro => Get(nameof(Cleanup_Help_Intro));
|
||||||
internal static string Cleanup_Help_SavedNote => Get(nameof(Cleanup_Help_SavedNote));
|
internal static string Cleanup_Help_SavedNote => Get(nameof(Cleanup_Help_SavedNote));
|
||||||
|
internal static string Cleanup_Preview_Stale => Get(nameof(Cleanup_Preview_Stale));
|
||||||
internal static string Retention_Help_SavedNote => Get(nameof(Retention_Help_SavedNote));
|
internal static string Retention_Help_SavedNote => Get(nameof(Retention_Help_SavedNote));
|
||||||
internal static string Cleanup_RefreshPreview => Get(nameof(Cleanup_RefreshPreview));
|
internal static string Cleanup_RefreshPreview => Get(nameof(Cleanup_RefreshPreview));
|
||||||
internal static string Cleanup_NoPreview => Get(nameof(Cleanup_NoPreview));
|
internal static string Cleanup_NoPreview => Get(nameof(Cleanup_NoPreview));
|
||||||
@@ -196,6 +197,8 @@ internal class HellionStrings
|
|||||||
internal static string Settings_Tab_Appearance => Get(nameof(Settings_Tab_Appearance));
|
internal static string Settings_Tab_Appearance => Get(nameof(Settings_Tab_Appearance));
|
||||||
internal static string Settings_Tab_Window => Get(nameof(Settings_Tab_Window));
|
internal static string Settings_Tab_Window => Get(nameof(Settings_Tab_Window));
|
||||||
internal static string Settings_Tab_Chat => Get(nameof(Settings_Tab_Chat));
|
internal static string Settings_Tab_Chat => Get(nameof(Settings_Tab_Chat));
|
||||||
|
internal static string Settings_Tab_Tabs => Get(nameof(Settings_Tab_Tabs));
|
||||||
|
internal static string Settings_Tab_Database => Get(nameof(Settings_Tab_Database));
|
||||||
internal static string Settings_Tab_Information => Get(nameof(Settings_Tab_Information));
|
internal static string Settings_Tab_Information => Get(nameof(Settings_Tab_Information));
|
||||||
|
|
||||||
// Hellion Chat — General-Tab section headings
|
// Hellion Chat — General-Tab section headings
|
||||||
|
|||||||
@@ -81,6 +81,9 @@
|
|||||||
<data name="Retention_Help_SavedNote" xml:space="preserve">
|
<data name="Retention_Help_SavedNote" xml:space="preserve">
|
||||||
<value>Der manuelle Lauf nutzt deine GESPEICHERTE Retention-Policy, nicht die Slider-Werte oben. Klicke zuerst Speichern, wenn der Lauf deine aktuellen Änderungen anwenden soll.</value>
|
<value>Der manuelle Lauf nutzt deine GESPEICHERTE Retention-Policy, nicht die Slider-Werte oben. Klicke zuerst Speichern, wenn der Lauf deine aktuellen Änderungen anwenden soll.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Cleanup_Preview_Stale" xml:space="preserve">
|
||||||
|
<value>Vorschau veraltet, deine Whitelist hat sich seit dem letzten Aktualisieren geändert. Klicke Aktualisieren, um neu zu berechnen.</value>
|
||||||
|
</data>
|
||||||
<data name="Cleanup_RefreshPreview" xml:space="preserve">
|
<data name="Cleanup_RefreshPreview" xml:space="preserve">
|
||||||
<value>Vorschau aktualisieren</value>
|
<value>Vorschau aktualisieren</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -445,6 +448,12 @@
|
|||||||
<data name="Settings_Tab_Chat" xml:space="preserve">
|
<data name="Settings_Tab_Chat" xml:space="preserve">
|
||||||
<value>Chat</value>
|
<value>Chat</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Settings_Tab_Tabs" xml:space="preserve">
|
||||||
|
<value>Kanäle</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Tab_Database" xml:space="preserve">
|
||||||
|
<value>Datenbank</value>
|
||||||
|
</data>
|
||||||
<data name="Settings_Tab_Information" xml:space="preserve">
|
<data name="Settings_Tab_Information" xml:space="preserve">
|
||||||
<value>Über</value>
|
<value>Über</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -81,6 +81,9 @@
|
|||||||
<data name="Retention_Help_SavedNote" xml:space="preserve">
|
<data name="Retention_Help_SavedNote" xml:space="preserve">
|
||||||
<value>The manual sweep uses your SAVED retention policy, not the slider values above. Click Save first if you want the run to apply your current edits.</value>
|
<value>The manual sweep uses your SAVED retention policy, not the slider values above. Click Save first if you want the run to apply your current edits.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Cleanup_Preview_Stale" xml:space="preserve">
|
||||||
|
<value>Preview is out of date — your whitelist has changed since the last refresh. Click Refresh to recalculate.</value>
|
||||||
|
</data>
|
||||||
<data name="Cleanup_RefreshPreview" xml:space="preserve">
|
<data name="Cleanup_RefreshPreview" xml:space="preserve">
|
||||||
<value>Refresh preview</value>
|
<value>Refresh preview</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -445,6 +448,12 @@
|
|||||||
<data name="Settings_Tab_Chat" xml:space="preserve">
|
<data name="Settings_Tab_Chat" xml:space="preserve">
|
||||||
<value>Chat</value>
|
<value>Chat</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Settings_Tab_Tabs" xml:space="preserve">
|
||||||
|
<value>Tabs</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Tab_Database" xml:space="preserve">
|
||||||
|
<value>Database</value>
|
||||||
|
</data>
|
||||||
<data name="Settings_Tab_Information" xml:space="preserve">
|
<data name="Settings_Tab_Information" xml:space="preserve">
|
||||||
<value>Information</value>
|
<value>Information</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -1374,8 +1374,18 @@ public sealed class ChatLogWindow : Window
|
|||||||
{
|
{
|
||||||
// Dim the tab name once the user marked the partner
|
// Dim the tab name once the user marked the partner
|
||||||
// as greeted, so a glance at the sidebar tells them
|
// as greeted, so a glance at the sidebar tells them
|
||||||
// who still needs attention.
|
// who still needs attention. Selectable has no idle
|
||||||
|
// background slot in ImGui, so the dim only applies
|
||||||
|
// to the selected and hovered states — the text dim
|
||||||
|
// alone signals greeted in the idle state.
|
||||||
|
var headerBase = ImGui.GetColorU32(ImGuiCol.Header);
|
||||||
|
var hoverBase = ImGui.GetColorU32(ImGuiCol.HeaderHovered);
|
||||||
|
var dimHeader = (headerBase & 0xFF000000u) | ((headerBase & 0x00FEFEFEu) >> 1);
|
||||||
|
var dimHover = (hoverBase & 0xFF000000u) | ((hoverBase & 0x00FEFEFEu) >> 1);
|
||||||
|
|
||||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled)))
|
using (ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled)))
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.Header, dimHeader))
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.HeaderHovered, dimHover))
|
||||||
{
|
{
|
||||||
clicked = ImGui.Selectable(selectableLabel, isCurrentTab);
|
clicked = ImGui.Selectable(selectableLabel, isCurrentTab);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
|
|||||||
GameFunctions.GameFunctions.SetChatInteractable(true);
|
GameFunctions.GameFunctions.SetChatInteractable(true);
|
||||||
|
|
||||||
if (Plugin.Config.ShowEmotes)
|
if (Plugin.Config.ShowEmotes)
|
||||||
Task.Run(EmoteCache.LoadData);
|
_ = EmoteCache.LoadData(); // Fire-and-forget intentional, exceptions are caught inside
|
||||||
|
|
||||||
Initialise();
|
Initialise();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,7 +147,9 @@ internal sealed class Appearance : ISettingsTab
|
|||||||
globalChooser?.ResultTask.ContinueWith(r =>
|
globalChooser?.ResultTask.ContinueWith(r =>
|
||||||
{
|
{
|
||||||
if (r.IsCompletedSuccessfully)
|
if (r.IsCompletedSuccessfully)
|
||||||
Mutable.GlobalFontV2 = r.Result;
|
{
|
||||||
|
Plugin.Framework.Run(() => Mutable.GlobalFontV2 = r.Result);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button("Reset##global"))
|
if (ImGui.Button("Reset##global"))
|
||||||
@@ -164,7 +166,9 @@ internal sealed class Appearance : ISettingsTab
|
|||||||
japaneseChooser?.ResultTask.ContinueWith(r =>
|
japaneseChooser?.ResultTask.ContinueWith(r =>
|
||||||
{
|
{
|
||||||
if (r.IsCompletedSuccessfully)
|
if (r.IsCompletedSuccessfully)
|
||||||
Mutable.JapaneseFontV2 = r.Result;
|
{
|
||||||
|
Plugin.Framework.Run(() => Mutable.JapaneseFontV2 = r.Result);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button("Reset##japanese"))
|
if (ImGui.Button("Reset##japanese"))
|
||||||
@@ -179,7 +183,9 @@ internal sealed class Appearance : ISettingsTab
|
|||||||
italicChooser?.ResultTask.ContinueWith(r =>
|
italicChooser?.ResultTask.ContinueWith(r =>
|
||||||
{
|
{
|
||||||
if (r.IsCompletedSuccessfully)
|
if (r.IsCompletedSuccessfully)
|
||||||
Mutable.ItalicFontV2 = r.Result;
|
{
|
||||||
|
Plugin.Framework.Run(() => Mutable.ItalicFontV2 = r.Result);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button("Reset##italic"))
|
if (ImGui.Button("Reset##italic"))
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ internal sealed class Database : ISettingsTab
|
|||||||
private Plugin Plugin { get; }
|
private Plugin Plugin { get; }
|
||||||
private Configuration Mutable { get; }
|
private Configuration Mutable { get; }
|
||||||
|
|
||||||
public string Name => Language.Options_Database_Tab + "###tabs-database";
|
public string Name => HellionStrings.Settings_Tab_Database + "###tabs-database";
|
||||||
|
|
||||||
internal Database(Plugin plugin, Configuration mutable)
|
internal Database(Plugin plugin, Configuration mutable)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using ChatTwo.Resources;
|
using ChatTwo.Resources;
|
||||||
using ChatTwo.Util;
|
using ChatTwo.Util;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
|
|
||||||
@@ -81,10 +82,12 @@ internal sealed class General : ISettingsTab
|
|||||||
|
|
||||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||||
{
|
{
|
||||||
if (ImGuiUtil.InputIntVertical(Language.Options_MaxLinesToShow_Name, Language.Options_MaxLinesToShow_Description, ref Mutable.MaxLinesToRender))
|
ImGui.SetNextItemWidth(200f * ImGuiHelpers.GlobalScale);
|
||||||
|
if (ImGui.InputInt(Language.Options_MaxLinesToShow_Name, ref Mutable.MaxLinesToRender))
|
||||||
{
|
{
|
||||||
Mutable.MaxLinesToRender = Math.Clamp(Mutable.MaxLinesToRender, 1, 10_000);
|
Mutable.MaxLinesToRender = Math.Clamp(Mutable.MaxLinesToRender, 1, 10_000);
|
||||||
}
|
}
|
||||||
|
ImGuiUtil.HelpMarker(Language.Options_MaxLinesToShow_Description);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using ChatTwo.Export;
|
|||||||
using ChatTwo.Privacy;
|
using ChatTwo.Privacy;
|
||||||
using ChatTwo.Resources;
|
using ChatTwo.Resources;
|
||||||
using ChatTwo.Util;
|
using ChatTwo.Util;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.ImGuiNotification;
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
@@ -55,6 +56,8 @@ internal sealed class Privacy : ISettingsTab
|
|||||||
private long CleanupKeepCount;
|
private long CleanupKeepCount;
|
||||||
private long CleanupDeleteCount;
|
private long CleanupDeleteCount;
|
||||||
private bool CleanupRunning;
|
private bool CleanupRunning;
|
||||||
|
private bool CleanupPreviewStale;
|
||||||
|
private HashSet<ChatType>? CleanupPreviewSnapshot;
|
||||||
|
|
||||||
// The retention-running state lives on Plugin so the auto-sweep and
|
// The retention-running state lives on Plugin so the auto-sweep and
|
||||||
// this manual button see the same flag. UI reads stay lock-free
|
// this manual button see the same flag. UI reads stay lock-free
|
||||||
@@ -484,6 +487,21 @@ internal sealed class Privacy : ISettingsTab
|
|||||||
|
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
|
|
||||||
|
// Drift-detection between the snapshot taken at last refresh
|
||||||
|
// and the current Mutable whitelist. Cleanup itself runs on
|
||||||
|
// the SAVED policy (Cleanup_Help_SavedNote covers that), but
|
||||||
|
// the user usually expects "the preview reflects what I just
|
||||||
|
// ticked" — so we surface the divergence instead of silently
|
||||||
|
// showing stale numbers.
|
||||||
|
if (CleanupPreviewSnapshot is not null
|
||||||
|
&& !CleanupPreviewSnapshot.SetEquals(Mutable.PrivacyPersistChannels))
|
||||||
|
{
|
||||||
|
CleanupPreviewStale = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var emphasis = CleanupPreviewStale
|
||||||
|
? ImRaii.PushColor(ImGuiCol.Button, ImGuiColors.HealerGreen with { W = 0.6f })
|
||||||
|
: null)
|
||||||
using (ImRaii.Disabled(CleanupRunning))
|
using (ImRaii.Disabled(CleanupRunning))
|
||||||
{
|
{
|
||||||
if (ImGui.Button(HellionStrings.Cleanup_RefreshPreview))
|
if (ImGui.Button(HellionStrings.Cleanup_RefreshPreview))
|
||||||
@@ -496,10 +514,22 @@ internal sealed class Privacy : ISettingsTab
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CleanupPreviewStale)
|
||||||
|
{
|
||||||
|
ImGui.Spacing();
|
||||||
|
ImGuiUtil.HelpText(HellionStrings.Cleanup_Preview_Stale);
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_TotalStored, CleanupKeepCount + CleanupDeleteCount));
|
|
||||||
ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_WillKeep, CleanupKeepCount));
|
using (var staleColor = CleanupPreviewStale
|
||||||
ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_WillDelete, CleanupDeleteCount));
|
? ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey)
|
||||||
|
: null)
|
||||||
|
{
|
||||||
|
ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_TotalStored, CleanupKeepCount + CleanupDeleteCount));
|
||||||
|
ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_WillKeep, CleanupKeepCount));
|
||||||
|
ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_WillDelete, CleanupDeleteCount));
|
||||||
|
}
|
||||||
|
|
||||||
using (var tree = ImRaii.TreeNode(HellionStrings.Cleanup_Breakdown))
|
using (var tree = ImRaii.TreeNode(HellionStrings.Cleanup_Breakdown))
|
||||||
{
|
{
|
||||||
@@ -555,6 +585,13 @@ internal sealed class Privacy : ISettingsTab
|
|||||||
else
|
else
|
||||||
CleanupDeleteCount += count;
|
CleanupDeleteCount += count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Snapshot the whitelist as it stood at preview-time so the
|
||||||
|
// render pass can flag the user about subsequent edits. Only
|
||||||
|
// updated on success — if the preview throws, the previous
|
||||||
|
// snapshot stays in place so stale-detection keeps working.
|
||||||
|
CleanupPreviewSnapshot = new HashSet<ChatType>(Mutable.PrivacyPersistChannels);
|
||||||
|
CleanupPreviewStale = false;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ internal sealed class Tabs : ISettingsTab
|
|||||||
private Plugin Plugin { get; }
|
private Plugin Plugin { get; }
|
||||||
private Configuration Mutable { get; }
|
private Configuration Mutable { get; }
|
||||||
|
|
||||||
public string Name => Language.Options_Tabs_Tab + "###tabs-tabs";
|
public string Name => HellionStrings.Settings_Tab_Tabs + "###tabs-tabs";
|
||||||
|
|
||||||
private int ToOpen = -2;
|
private int ToOpen = -2;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"Author": "JonKazama-Hellion",
|
"Author": "JonKazama-Hellion",
|
||||||
"Name": "Hellion Chat",
|
"Name": "Hellion Chat",
|
||||||
"InternalName": "HellionChat",
|
"InternalName": "HellionChat",
|
||||||
"AssemblyVersion": "0.5.0.0",
|
"AssemblyVersion": "0.5.1.0",
|
||||||
"Description": "Hellion Chat is built on top of Chat 2 with one removal and a stack\nof privacy controls on top. Tabs, channel filters, RGB colours,\nemotes, screenshot mode, IPC integration and the chat replacement\nwindow itself work the same. The optional webinterface that Chat 2\nships is intentionally not part of this fork because it serves a\ndifferent use case from the smaller default footprint Hellion Chat\nis built around.\n\nOn top of that, Hellion Chat adds privacy and data-handling controls\ndesigned to align with the modern data protection rules that apply\nacross the EU, the United States and Japan. By default only your own\nconversations are stored; messages from strangers, NPCs and system\nspam stay out of the database. Retention windows are configurable per\nchannel, history can be wiped retroactively, and stored data can be\nexported on demand.\n\nKey additions on top of Chat 2:\n\n- Channel whitelist with a Privacy-First default\n- Per-channel retention with a daily background sweep\n- Retroactive cleanup with a Ctrl+Shift confirm\n- Export to Markdown, JSON or CSV\n- First-run wizard with three preset profiles (Privacy-First, Casual,\n Full History)\n- Bilingual UI (English and German) with live language switching\n- Independent plugin state — own config file and database directory,\n so Hellion Chat does not share state with the upstream plugin\n\nBased on Chat 2 by Infi and Anna, licensed under EUPL-1.2.",
|
"Description": "Hellion Chat is built on top of Chat 2 with one removal and a stack\nof privacy controls on top. Tabs, channel filters, RGB colours,\nemotes, screenshot mode, IPC integration and the chat replacement\nwindow itself work the same. The optional webinterface that Chat 2\nships is intentionally not part of this fork because it serves a\ndifferent use case from the smaller default footprint Hellion Chat\nis built around.\n\nOn top of that, Hellion Chat adds privacy and data-handling controls\ndesigned to align with the modern data protection rules that apply\nacross the EU, the United States and Japan. By default only your own\nconversations are stored; messages from strangers, NPCs and system\nspam stay out of the database. Retention windows are configurable per\nchannel, history can be wiped retroactively, and stored data can be\nexported on demand.\n\nKey additions on top of Chat 2:\n\n- Channel whitelist with a Privacy-First default\n- Per-channel retention with a daily background sweep\n- Retroactive cleanup with a Ctrl+Shift confirm\n- Export to Markdown, JSON or CSV\n- First-run wizard with three preset profiles (Privacy-First, Casual,\n Full History)\n- Bilingual UI (English and German) with live language switching\n- Independent plugin state — own config file and database directory,\n so Hellion Chat does not share state with the upstream plugin\n\nBased on Chat 2 by Infi and Anna, licensed under EUPL-1.2.",
|
||||||
"ApplicableVersion": "any",
|
"ApplicableVersion": "any",
|
||||||
"RepoUrl": "https://github.com/JonKazama-Hellion/HellionChat",
|
"RepoUrl": "https://github.com/JonKazama-Hellion/HellionChat",
|
||||||
@@ -20,12 +20,12 @@
|
|||||||
"CanUnloadAsync": false,
|
"CanUnloadAsync": false,
|
||||||
"LoadPriority": 0,
|
"LoadPriority": 0,
|
||||||
"Punchline": "Chat 2 with privacy controls aligned to EU, US and JP rules",
|
"Punchline": "Chat 2 with privacy controls aligned to EU, US and JP rules",
|
||||||
"Changelog": "**Hellion Chat 0.5.0 — Settings UX polish**\n\nThe settings window has been pulled apart and rebuilt around eight\nthemed tabs instead of the twelve organic ones it grew into.\nSettings now sit where they belong and the wall-of-text descriptions\nhave been replaced with hover help markers across every section.\n\nWhat changed in this release:\n\n- Twelve tabs collapsed into eight: General, Appearance, Window,\n Chat, Tabs, Privacy, Database and Information\n- Theme and font controls moved out of the Privacy tab into\n Appearance where they belong\n- Auto-Tell-Tabs settings, message preview and emote controls now\n live under one Chat tab with collapsible sections\n- About and Changelog merged into a single Information tab\n- Disabled settings keep their tooltip help marker visible so you\n can still read why an option is greyed out\n- Section headings start collapsed by default, the same pattern\n used for the Auto-Tell-Tabs preload section in 0.4.0\n\nConfiguration version bumps from 9 to 10 as a wipe migration. The\nold config file is copied to HellionChat.json.pre-v10-backup before\nthe new defaults are written, so you can restore your previous\nsetup by hand if anything looks off. A one-shot notification on\nfirst start explains the reset.\n\nNo changes to message storage, retention sweep, the privacy filter\nor the export pipeline. Tabs and chat history are untouched by the\nmigration.\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n**Hellion Chat 0.4.0 — Auto-Tell-Tabs**\n\nAuto-Tell-Tabs lets you turn each /tell into a session-only tab\ndedicated to that conversation partner. The original use case is\nthe FFXIV club greeter who has to track 5–15 parallel \"hi, welcome\"\nexchanges; everyone else can disable the feature in one click and\ngo back to a single Tell Exclusive tab.\n\nWhat lands in this release:\n\n- Auto-spawn temp tab \"Name@World\" on /tell (incoming and outgoing)\n- Tab limit (default 15, range 1–50) with LRU drop that prefers\n greeted tabs first, then sorts by last activity\n- History preload from the local message store (default 20 tells,\n range 0–100) with a \"— Earlier conversations —\" separator above\n the live tell that triggered the spawn\n- Optional \"mark as greeted\" toggle button (off by default,\n greeter-specific) that dims the tab name and lets you flip the\n status\n- Section header \"Active Tells (n)\" or compact-mode separator in\n the sidebar between persistent tabs and the temp tabs\n- Settings UI under Chat (toggle / limit / compact / greeted-toggle)\n and Privacy (history preload count), with hover-tooltip help\n markers replacing the previous wall-of-text descriptions for the\n new sections\n- Save and load filters strip temp tabs from the on-disk config so\n a crash or a sidebar-mode toggle never persists or wipes them\n\nCompatibility note: if XIV Messanger or another plugin is\nsuppressing direct messages, disable its \"Suppress DMs\" option so\nHellion Chat can receive tells and open the auto tabs.\n\nConfiguration version bumps from 8 to 9. Existing users get a one-\nshot notification on the first start, defaults are seeded by\nproperty initializers, persistent tabs are untouched.\n\nThe vertical sidebar tab view becomes the default for fresh\ninstalls; existing users keep their saved preference.\n\nInspired by the per-sender tab pattern in XIV InstantMessenger\n(Limiana, AGPL-3.0). No code was ported across the licence\nboundary; only the architectural concept influenced this design.\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n**Hellion Chat 0.3.1 — Upstream emote regression fix**\n\nCherry-picks Infi's upstream commit ff899ff \"Fix a regression\nfrom API 15 updates\" which changes the BetterTTV emote DTOs\n(Emote and Top100) from public fields to public properties.\nSystem.Text.Json under the API 15 toolchain only honours the\n[JsonPropertyName] attribute on properties, so the previous\nfield-based version deserialised every fetched emote into empty\ndefault values. Result: BetterTTV emotes were silently broken\non fresh installs. The fix is six lines and applies cleanly on\ntop of our defensive null-check from earlier; the EmoteCache\npath-traversal hardening from 0.3.0 stays as it is.\n\nAuthorship of the fix is preserved with git cherry-pick -x, so\nInfi shows up as the author on the commit. Thanks to him for\ncatching it in the upstream codebase.\n\n**Hellion Chat 0.3.0 — Audit hardening, brand sweep and rebrand of slash commands**\n\nThis release closes the remaining audit follow-ups from the\n0.2.0 cleanup and finishes turning Hellion Chat into a properly\nbranded fork rather than a Chat 2 with a different name.\n\nSlash commands have been renamed across the board so they no\nlonger collide with the upstream plugin and tell you which\nplugin owns them at a glance:\n\n- /chat2 becomes /hellion\n- /chat2Viewer becomes /hellionView\n- /cleLine truncated
|
"Changelog": "**Hellion Chat 0.5.1 — Backlog Sweep**\n\nPure hardening and polish. No new features. Eight backlog items\nfrom the v0.5.0 codebase review collected into one patch:\n\n- Cleanup preview now flags itself as out-of-date when the user\n edits the whitelist after the last refresh, and the refresh\n button is visually emphasised in that state\n- Greeted Auto-Tell-Tabs now also dim their selection and hover\n backgrounds in the sidebar, not just the text\n- Performance section in the General tab moves to the standard\n HelpMarker tooltip pattern instead of a wall-of-text description\n- Tabs and Database settings tabs pull their display name from\n HellionStrings instead of the upstream Language bundle, so all\n eight tabs share one i18n source\n- FontChooser results are now marshalled onto the framework thread\n via Plugin.Framework.Run instead of being written to settings\n state directly from the threadpool\n- EmoteCache.LoadData drops async void and the four CS8618 build\n warnings the build has been carrying since v0.4.0\n- All MessageStore SQL paths that fed dynamic value lists into\n interpolated SQL now use named parameter bindings via a new\n BindIntList helper. Same behaviour, defence against future\n user-input regressions\n\nConfiguration version is unchanged at 10. No migration. Existing\ninstalls upgrade silently.\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n**Hellion Chat 0.5.0 — Settings UX polish**\n\nThe settings window has been pulled apart and rebuilt around eight\nthemed tabs instead of the twelve organic ones it grew into.\nSettings now sit where they belong and the wall-of-text descriptions\nhave been replaced with hover help markers across every section.\n\nWhat changed in this release:\n\n- Twelve tabs collapsed into eight: General, Appearance, Window,\n Chat, Tabs, Privacy, Database and Information\n- Theme and font controls moved out of the Privacy tab into\n Appearance where they belong\n- Auto-Tell-Tabs settings, message preview and emote controls now\n live under one Chat tab with collapsible sections\n- About and Changelog merged into a single Information tab\n- Disabled settings keep their tooltip help marker visible so you\n can still read why an option is greyed out\n- Section headings start collapsed by default, the same pattern\n used for the Auto-Tell-Tabs preload section in 0.4.0\n\nConfiguration version bumps from 9 to 10 as a wipe migration. The\nold config file is copied to HellionChat.json.pre-v10-backup before\nthe new defaults are written, so you can restore your previous\nsetup by hand if anything looks off. A one-shot notification on\nfirst start explains the reset.\n\nNo changes to message storage, retention sweep, the privacy filter\nor the export pipeline. Tabs and chat history are untouched by the\nmigration.\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n**Hellion Chat 0.4.0 — Auto-Tell-Tabs**\n\nAuto-Tell-Tabs lets you turn each /tell into a session-only tab\ndedicated to that conversation partner. The original use case is\nthe FFXIV club greeter who has to track 5–15 parallel \"hi, welcome\"\nexchanges; everyone else can disable the feature in one click and\ngo back to a single Tell Exclusive tab.\n\nWhat lands in this release:\n\n- Auto-spawn temp tab \"Name@World\" on /tell (incoming and outgoing)\n- Tab limit (default 15, range 1–50) with LRU drop that prefers\n greeted tabs first, then sorts by last activity\n- History preload from the local message store (default 20 tells,\n range 0–100) with a \"— Earlier conversations —\" separator above\n the live tell that triggered the spawn\n- Optional \"mark as greeted\" toggle button (off by default,\n greeter-specific) that dims the tab name and lets you flip the\n status\n- Section header \"Active Tells (n)\" or compact-mode separator in\n the sidebar between persistent tabs and the temp tabs\n- Settings UI under Chat (toggle / limit / compact / greeted-toggle)\n and Privacy (history preload count), with hover-tooltip help\n markers replacing the previous wall-of-text descriptions for the\n new sections\n- Save and load filters strip temp tabs from the on-disk config so\n a crash or a sidebar-mode toggle never persists or wipes them\n\nCompatibility note: if XIV Messanger or another plugin is\nsuppressing direct messages, disable its \"Suppress DMs\" option so\nHellion Chat can receive tells and open the auto tabs.\n\nConfiguration version bumps from 8 to 9. Existing users get a one-\nshot notification on the first start, defaults are seeded by\nproperty initializers, persistent tabs are untouched.\n\nThe vertical sidebar tab view becomes the default for fresh\ninstalls; existing users keep their saved preference.\n\nInspired by the per-sender tab pattern in XIV InstantMessenger\n(Limiana, AGPL-3.0). No code was ported across the licence\nboundary; only the architectural concept influenced this design.\nLine truncated
|
||||||
"AcceptsFeedback": true,
|
"AcceptsFeedback": true,
|
||||||
"DownloadLinkInstall": "https://github.com/JonKazama-Hellion/HellionChat/releases/download/v0.5.0/latest.zip",
|
"DownloadLinkInstall": "https://github.com/JonKazama-Hellion/HellionChat/releases/download/v0.5.1/latest.zip",
|
||||||
"DownloadLinkUpdate": "https://github.com/JonKazama-Hellion/HellionChat/releases/download/v0.5.0/latest.zip",
|
"DownloadLinkUpdate": "https://github.com/JonKazama-Hellion/HellionChat/releases/download/v0.5.1/latest.zip",
|
||||||
"DownloadLinkTesting": "https://github.com/JonKazama-Hellion/HellionChat/releases/download/v0.5.0/latest.zip",
|
"DownloadLinkTesting": "https://github.com/JonKazama-Hellion/HellionChat/releases/download/v0.5.1/latest.zip",
|
||||||
"TestingAssemblyVersion": "0.5.0.0",
|
"TestingAssemblyVersion": "0.5.1.0",
|
||||||
"IconUrl": "https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/ChatTwo/images/icon.png",
|
"IconUrl": "https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/ChatTwo/images/icon.png",
|
||||||
"ImageUrls": [],
|
"ImageUrls": [],
|
||||||
"DownloadCount": 0,
|
"DownloadCount": 0,
|
||||||
|
|||||||
Reference in new issue
Block a user