Release v0.6.53: synchronize chat language profiles
This commit is contained in:
1 parent
ede4ce47c0
commit
727dc96908
11 files changed
+115
-22
No files matched your search
+7
-3
@@ -219,8 +219,7 @@ Bytes sharedSyncSettings(const Settings &s)
|
||||
return a.at("layoutId").get<std::string>() < b.at("layoutId").get<std::string>();
|
||||
});
|
||||
for (auto key : {"startWithSystem", "autostartAsAdministrator", "tesseractPath", "useTesseract",
|
||||
"historyEnabled", "maxItems", "maxTotalMegabytes", "maxImageMegabytes", "writeLog", "preventScreenLock",
|
||||
"chatCycles"}) j.erase(key); // Chat names and matching rules stay on this PC.
|
||||
"historyEnabled", "maxItems", "maxTotalMegabytes", "maxImageMegabytes", "writeLog", "preventScreenLock"}) j.erase(key);
|
||||
return bytes(canonicalProfile(j));
|
||||
}
|
||||
Settings restoreSyncSettings(const Bytes &shared, const Settings &local, const std::vector<Layout> &layouts, bool keepLocalLayouts)
|
||||
@@ -230,6 +229,9 @@ Settings restoreSyncSettings(const Bytes &shared, const Settings &local, const s
|
||||
if (!j.contains("autoCorrectReplace")) j["autoCorrectReplace"] = false;
|
||||
if (!j.contains("autoCorrectTypos")) j["autoCorrectTypos"] = false;
|
||||
if (!j.contains("autoCorrectReplacements")) j["autoCorrectReplacements"] = Json::object();
|
||||
// Profiles uploaded before v0.6.53 do not participate in chat synchronization.
|
||||
// An explicit empty object from a newer client does mean remove all profiles.
|
||||
if (!j.contains("chatCycles")) j["chatCycles"] = original.at("chatCycles");
|
||||
auto allowed = parse(sharedSyncSettings(local));
|
||||
if (!j.is_object() || j.value("format", "") != "kswitcher-settings" || j.value("schemaVersion", 0) != 1)
|
||||
throw std::runtime_error("Unsupported synchronized settings format.");
|
||||
@@ -295,7 +297,9 @@ SyncResult synchronize(SyncProvider &provider, const Bytes &base, const Bytes &l
|
||||
if (action == SyncAction::Automatic) merged = mergeSyncProfile(mergeBase, mergeLocal, remote.settings, choices);
|
||||
else
|
||||
{
|
||||
auto selected = bytes(normalizeProfile(parse(action == SyncAction::Download ? remote.settings : mergeLocal)));
|
||||
auto profile = parse(action == SyncAction::Download ? remote.settings : mergeLocal);
|
||||
if (!profile.contains("chatCycles")) profile["chatCycles"] = parse(local).value("chatCycles", Json::object());
|
||||
auto selected = bytes(normalizeProfile(profile));
|
||||
merged = {selected, selected, selected, {}};
|
||||
}
|
||||
if (validate) { validate(merged.local); validate(merged.remote); }
|
||||
|
||||
+17
-1
@@ -8,6 +8,7 @@ Json normalizeProfile(Json j)
|
||||
if (!j.contains("autoCorrectTypos")) j["autoCorrectTypos"] = false;
|
||||
if (!j.contains("autoCorrectReplacements")) j["autoCorrectReplacements"] = Json::object();
|
||||
if (!j.contains("snippetTombstones")) j["snippetTombstones"] = Json::array();
|
||||
if (!j.contains("chatCycles")) j["chatCycles"] = Json::object();
|
||||
for (auto &sn : j.at("snippets"))
|
||||
if (!sn.contains("id")) sn["id"] = utf8(snippetId({wide(sn.at("trigger").get<std::string>()), L""}));
|
||||
return j;
|
||||
@@ -53,6 +54,10 @@ Json flatProfile(Json j)
|
||||
for (auto it = replacements.begin(); it != replacements.end();)
|
||||
if (it.value().is_null()) it = replacements.erase(it);
|
||||
else ++it;
|
||||
auto &chats = j["chatCycles"];
|
||||
for (auto it = chats.begin(); it != chats.end();)
|
||||
if (it.value().is_null()) it = chats.erase(it);
|
||||
else ++it;
|
||||
Json snippets = Json::array(), deleted = Json::array();
|
||||
for (const auto &[id, item] : j.at("snippets").items())
|
||||
{
|
||||
@@ -94,6 +99,7 @@ std::wstring fieldLabel(const std::string &key)
|
||||
{"translitHotkey", L"Transliteration shortcut"}, {"undoHotkey", L"Undo shortcut"},
|
||||
{"clipboardHotkey", L"Clipboard shortcut"}, {"screenshotHotkey", L"Screenshot shortcut"},
|
||||
{"layouts", L"Layout cycle order"}, {"layoutHotkeys", L"Layout shortcuts"},
|
||||
{"chatCycles", L"Chat languages"},
|
||||
{"convertLastWord", L"Convert recent word"}, {"switchSystemLayout", L"Switch system layout"},
|
||||
{"autoCorrectMinLength", L"Minimum word length"}, {"autoCorrectMaxRank", L"Maximum word rank"},
|
||||
{"autoCorrectLayout", L"Automatic layout correction"}, {"autoCorrectLanguages", L"Dictionary languages"},
|
||||
@@ -141,6 +147,11 @@ MergeNode mergeNode(const Json &b, const Json &l, const Json &r, const std::stri
|
||||
auto item = get(l); if (item.is_null() || tombstone(item)) item = get(r);
|
||||
childLabel = L"Snippet: " + wide(item.is_object() ? item.value("trigger", key) : key);
|
||||
}
|
||||
if (path == "/chatCycles") {
|
||||
auto name = key;
|
||||
if (auto separator = name.find('\n'); separator != name.npos) name.replace(separator, 1, " / ");
|
||||
childLabel = L"Chat languages: " + wide(name);
|
||||
}
|
||||
auto child = mergeNode(get(b), get(l), get(r), path + "/" + pointerKey(key), childLabel, choices, conflicts, forced);
|
||||
// Keep nulls in the ancestor so deletion conflicts survive restarts.
|
||||
out.local[key] = child.local; out.remote[key] = child.remote; out.base[key] = child.base;
|
||||
@@ -181,7 +192,12 @@ void collisionPaths(const Json &j, std::set<std::string> &paths)
|
||||
}
|
||||
SyncMerge mergeSyncProfile(const Bytes &base, const Bytes &local, const Bytes &remote, const std::vector<SyncChoice> &choices)
|
||||
{
|
||||
auto l = treeProfile(parse(local)), r = treeProfile(parse(remote));
|
||||
auto localProfile = parse(local), remoteProfile = parse(remote);
|
||||
// Absence in an older client is not a deletion. Preserve the participating
|
||||
// client's chat profiles until both copies understand this field.
|
||||
if (!remoteProfile.contains("chatCycles")) remoteProfile["chatCycles"] = localProfile.value("chatCycles", Json::object());
|
||||
if (!localProfile.contains("chatCycles")) localProfile["chatCycles"] = remoteProfile.at("chatCycles");
|
||||
auto l = treeProfile(localProfile), r = treeProfile(remoteProfile);
|
||||
// With no common history, union independent entries; differing scalar values
|
||||
// remain on their respective devices until the user resolves the conflict.
|
||||
auto b = base.empty() ? Json::object() : treeProfile(parse(base));
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
#include "platform.h"
|
||||
namespace ks
|
||||
{
|
||||
inline constexpr wchar_t AppVersion[] = L"0.6.52";
|
||||
inline constexpr wchar_t AppVersion[] = L"0.6.53";
|
||||
inline constexpr UINT UpdateMessage = WM_APP + 7;
|
||||
inline constexpr UINT UpdateCheckIntervalMs = 6 * 60 * 60 * 1000;
|
||||
inline constexpr UINT UpdateNotificationRetryMs = 5 * 60 * 1000;
|
||||
|
||||
Reference in new issue
Block a user