1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-07-25 19:05:03 +02:00
This commit is contained in:
syuilo
2026-07-25 16:37:05 +09:00
parent 3ead8d840e
commit 4b82eab304
4 changed files with 45 additions and 24 deletions

View File

@@ -1312,6 +1312,7 @@ noteOfThisUser: "このユーザーのノート一覧"
clipNoteLimitExceeded: "これ以上このクリップにノートを追加できません。"
performance: "パフォーマンス"
modified: "変更あり"
modifiedAt: "変更日時"
discard: "破棄"
thereAreNChanges: "{n}件の変更があります"
signinWithPasskey: "パスキーでログイン"

View File

@@ -36,11 +36,12 @@ const io: StorageProvider = {
const cloudData = await misskeyApi('i/registry/get', {
scope: ['client', 'preferences', 'sync'],
key: syncGroup + ':' + ctx.key,
}) as [any, any][];
}) as [any, any, any][];
const target = cloudData.find(([scope]) => isSameScope(scope, ctx.scope));
if (target == null) return null;
return {
value: target[1],
meta: target[2],
};
} catch (err: any) {
if (err.code === 'NO_SUCH_KEY') { // TODO: いちいちエラーキャッチするのは面倒なのでキーが無くてもエラーにならない maybe-get のようなエンドポイントをバックエンドに実装する
@@ -52,12 +53,12 @@ const io: StorageProvider = {
},
cloudSet: async (ctx) => {
let cloudData: [any, any][] = [];
let cloudData: [any, any, any][] = [];
try {
cloudData = await misskeyApi('i/registry/get', {
scope: ['client', 'preferences', 'sync'],
key: syncGroup + ':' + ctx.key,
}) as [any, any][];
}) as [any, any, any][];
} catch (err: any) {
if (err.code === 'NO_SUCH_KEY') { // TODO: いちいちエラーキャッチするのは面倒なのでキーが無くてもエラーにならない maybe-get のようなエンドポイントをバックエンドに実装する
cloudData = [];
@@ -69,9 +70,9 @@ const io: StorageProvider = {
const i = cloudData.findIndex(([scope]) => isSameScope(scope, ctx.scope));
if (i === -1) {
cloudData.push([ctx.scope, ctx.value]);
cloudData.push([ctx.scope, ctx.value, ctx.meta]);
} else {
cloudData[i] = [ctx.scope, ctx.value];
cloudData[i] = [ctx.scope, ctx.value, ctx.meta];
}
await misskeyApi('i/registry/set', {
@@ -86,10 +87,10 @@ const io: StorageProvider = {
const fetchings = ctx.needs.map(need => io.cloudGet(need).then(res => [need.key, res] as const));
const cloudDatas = await Promise.all(fetchings);
const res = {} as Partial<Record<string, any>>;
const res = {} as Partial<Record<string, { value: any; meta: any; }>>;
for (const cloudData of cloudDatas) {
if (cloudData[1] != null) {
res[cloudData[0]] = cloudData[1].value;
res[cloudData[0]] = cloudData[1];
}
}

View File

@@ -36,7 +36,7 @@ type Scope = Partial<{
type ValueMeta = Partial<{
sync: boolean;
// TODO: modifiedAtをここでも(Record個別に)持っておいた方が値の新旧比較に使えて便利そう
modifiedAt?: number;
}>;
type PrefRecord<K extends keyof PREF> = [scope: Scope, value: ValueOf<K>, meta: ValueMeta];
@@ -89,9 +89,9 @@ export type PossiblyNonNormalizedPreferencesProfile = Omit<PreferencesProfile, '
export type StorageProvider = {
load: () => PossiblyNonNormalizedPreferencesProfile | null;
save: (ctx: { profile: PreferencesProfile; }) => void;
cloudGetBulk: <K extends keyof PREF>(ctx: { needs: { key: K; scope: Scope; }[] }) => Promise<Partial<Record<K, ValueOf<K>>>>;
cloudGet: <K extends keyof PREF>(ctx: { key: K; scope: Scope; }) => Promise<{ value: ValueOf<K>; } | null>;
cloudSet: <K extends keyof PREF>(ctx: { key: K; scope: Scope; value: ValueOf<K>; }) => Promise<void>;
cloudGetBulk: <K extends keyof PREF>(ctx: { needs: { key: K; scope: Scope; }[] }) => Promise<Partial<Record<K, { value: ValueOf<K>; meta: { modifiedAt: ValueMeta['modifiedAt'] }; }>>>;
cloudGet: <K extends keyof PREF>(ctx: { key: K; scope: Scope; }) => Promise<{ value: ValueOf<K>; meta: { modifiedAt: ValueMeta['modifiedAt'] }; } | null>;
cloudSet: <K extends keyof PREF>(ctx: { key: K; scope: Scope; value: ValueOf<K>; meta: { modifiedAt: ValueMeta['modifiedAt'] }; }) => Promise<void>;
};
type PreferencesDefinitionRecord<Default, T = Default extends (...args: any) => infer R ? R : Default> = {
@@ -244,13 +244,13 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
}
// TODO: desync対策 cloudの値のfetchが正常に完了していない状態でcommitすると多分値が上書きされる
public commit<K extends keyof PREF>(key: K, value: ValueOf<K>) {
public commit<K extends keyof PREF>(key: K, value: ValueOf<K>): PrefRecord<K> | null {
const currentAccount = this.currentAccount; // TSを黙らせるため
const v = JSON.parse(JSON.stringify(value)); // deep copy 兼 vueのプロキシ解除
if (deepEqual(this.s[key], v)) {
if (_DEV_) console.log('(skip) prefer:commit', key, v);
return;
return null;
}
if (_DEV_) console.log('prefer:commit', key, v);
@@ -260,30 +260,39 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
const record = this.getMatchedRecordOf(key);
if (parseScope(record[0]).account == null && isAccountDependentKey(key) && currentAccount != null) {
this.profile.preferences[key].push([makeScope({
const newRecord = [makeScope({
server: host,
account: currentAccount.id,
}), v, {}]);
}), v, {
modifiedAt: Date.now(),
}] as PrefRecord<K>;
this.profile.preferences[key].push(newRecord);
this.save();
return;
return newRecord;
}
if (parseScope(record[0]).server == null && isServerDependentKey(key)) {
this.profile.preferences[key].push([makeScope({
const newRecord = [makeScope({
server: host,
}), v, {}]);
}), v, {
modifiedAt: Date.now(),
}] as PrefRecord<K>;
this.profile.preferences[key].push(newRecord);
this.save();
return;
return newRecord;
}
record[1] = v;
record[2].modifiedAt = Date.now();
this.save();
if (record[2].sync) {
// awaitの必要なし
// TODO: リクエストを間引く
this.io.cloudSet({ key, scope: record[0], value: record[1] });
this.io.cloudSet({ key, scope: record[0], value: record[1], meta: { modifiedAt: record[2].modifiedAt } });
}
return record;
}
/**
@@ -360,8 +369,9 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
if (record[2].sync && Object.hasOwn(cloudValues, key) && cloudValues[key] !== undefined) {
const cloudValue = cloudValues[key];
if (!deepEqual(cloudValue, record[1])) {
this.rewriteRawState(key, cloudValue);
record[1] = cloudValue;
this.rewriteRawState(key, cloudValue.value);
record[1] = cloudValue.value;
record[2].modifiedAt = cloudValue.meta.modifiedAt;
modified = true;
if (_DEV_) console.log('cloud fetched', key, cloudValue);
}
@@ -503,12 +513,12 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
newValue = resolvedValue;
}
this.commit(key, newValue);
const commitedRecord = this.commit(key, newValue);
const done = os.waiting();
try {
await this.io.cloudSet({ key, scope: record[0], value: newValue });
await this.io.cloudSet({ key, scope: record[0], value: newValue, meta: { modifiedAt: record[2].modifiedAt } });
} catch (err) {
done();
@@ -607,6 +617,11 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
icon: 'ti ti-cloud-cog',
text: i18n.ts.syncBetweenDevices,
ref: sync,
}, {
type: 'divider',
}, {
type: 'label',
text: i18n.ts.modifiedAt + ': ' + (this.getMatchedRecordOf(key)[2].modifiedAt ? new Date(this.getMatchedRecordOf(key)[2].modifiedAt!).toLocaleString() : '-'),
}];
}
}

View File

@@ -5260,6 +5260,10 @@ export interface Locale extends ILocale {
* 変更あり
*/
"modified": string;
/**
* 変更日時
*/
"modifiedAt": string;
/**
* 破棄
*/