forked from trent_larson/crowd-funder-for-time-pwa
allow blocking another person's content from this user (with iViewContent contact field)
This commit is contained in:
@@ -34,7 +34,6 @@ const secretBase64 = arrayBufferToBase64(randomBytes);
|
||||
const MIGRATIONS = [
|
||||
{
|
||||
name: "001_initial",
|
||||
// see ../db/tables files for explanations of the fields
|
||||
sql: `
|
||||
CREATE TABLE IF NOT EXISTS accounts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -119,6 +118,12 @@ const MIGRATIONS = [
|
||||
);
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "002_add_iViewContent_to_contacts",
|
||||
sql: `
|
||||
ALTER TABLE contacts ADD COLUMN iViewContent BOOLEAN DEFAULT TRUE;
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
export interface ContactMethod {
|
||||
export type ContactMethod = {
|
||||
label: string;
|
||||
type: string; // eg. "EMAIL", "SMS", "WHATSAPP", maybe someday "GOOGLE-CONTACT-API"
|
||||
value: string;
|
||||
}
|
||||
};
|
||||
|
||||
export interface Contact {
|
||||
export type Contact = {
|
||||
//
|
||||
// When adding a property, consider whether it should be added when exporting & sharing contacts.
|
||||
// When adding a property, consider whether it should be added when exporting & sharing contacts, eg. DataExportSection
|
||||
|
||||
did: string;
|
||||
contactMethods?: Array<ContactMethod>;
|
||||
iViewContent?: boolean;
|
||||
name?: string;
|
||||
nextPubKeyHashB64?: string; // base64-encoded SHA256 hash of next public key
|
||||
notes?: string;
|
||||
@@ -17,9 +18,15 @@ export interface Contact {
|
||||
publicKeyBase64?: string;
|
||||
seesMe?: boolean; // cached value of the server setting
|
||||
registered?: boolean; // cached value of the server setting
|
||||
}
|
||||
};
|
||||
|
||||
export type ContactWithJsonStrings = Contact & {
|
||||
/**
|
||||
* This is for those cases (eg. with a DB) where every field is a primitive (and not an object).
|
||||
*
|
||||
* This is so that we can reuse most of the type and don't have to maintain another copy.
|
||||
* Another approach uses typescript conditionals: https://chatgpt.com/share/6855cdc3-ab5c-8007-8525-726612016eb2
|
||||
*/
|
||||
export type ContactWithJsonStrings = Omit<Contact, "contactMethods"> & {
|
||||
contactMethods?: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
faArrowLeft,
|
||||
faArrowRight,
|
||||
faArrowRotateBackward,
|
||||
faArrowUpRightFromSquare,
|
||||
faArrowUp,
|
||||
faArrowUpRightFromSquare,
|
||||
faBan,
|
||||
faBitcoinSign,
|
||||
faBurst,
|
||||
@@ -92,8 +92,8 @@ library.add(
|
||||
faArrowLeft,
|
||||
faArrowRight,
|
||||
faArrowRotateBackward,
|
||||
faArrowUpRightFromSquare,
|
||||
faArrowUp,
|
||||
faArrowUpRightFromSquare,
|
||||
faBan,
|
||||
faBitcoinSign,
|
||||
faBurst,
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
updateDefaultSettings,
|
||||
} from "../db/index";
|
||||
import { Account, AccountEncrypted } from "../db/tables/accounts";
|
||||
import { Contact } from "../db/tables/contacts";
|
||||
import { Contact, ContactWithJsonStrings } from "../db/tables/contacts";
|
||||
import * as databaseUtil from "../db/databaseUtil";
|
||||
import { DEFAULT_PASSKEY_EXPIRATION_MINUTES } from "../db/tables/settings";
|
||||
import {
|
||||
@@ -974,19 +974,16 @@ export interface DatabaseExport {
|
||||
*/
|
||||
export const contactsToExportJson = (contacts: Contact[]): DatabaseExport => {
|
||||
// Convert each contact to a plain object and ensure all fields are included
|
||||
const rows = contacts.map((contact) => ({
|
||||
did: contact.did,
|
||||
name: contact.name || null,
|
||||
contactMethods: contact.contactMethods
|
||||
? JSON.stringify(parseJsonField(contact.contactMethods, []))
|
||||
: null,
|
||||
nextPubKeyHashB64: contact.nextPubKeyHashB64 || null,
|
||||
notes: contact.notes || null,
|
||||
profileImageUrl: contact.profileImageUrl || null,
|
||||
publicKeyBase64: contact.publicKeyBase64 || null,
|
||||
seesMe: contact.seesMe || false,
|
||||
registered: contact.registered || false,
|
||||
}));
|
||||
const rows = contacts.map((contact) => {
|
||||
const exContact: ContactWithJsonStrings = R.omit(
|
||||
["contactMethods"],
|
||||
contact,
|
||||
);
|
||||
exContact.contactMethods = contact.contactMethods
|
||||
? JSON.stringify(contact.contactMethods, [])
|
||||
: undefined;
|
||||
return exContact;
|
||||
});
|
||||
|
||||
return {
|
||||
data: {
|
||||
|
||||
@@ -39,7 +39,6 @@ import {
|
||||
generateUpdateStatement,
|
||||
generateInsertStatement,
|
||||
} from "../db/databaseUtil";
|
||||
import { updateDefaultSettings } from "../db/databaseUtil";
|
||||
import { importFromMnemonic } from "../libs/util";
|
||||
|
||||
/**
|
||||
@@ -156,11 +155,14 @@ export async function getDexieContacts(): Promise<Contact[]> {
|
||||
await db.open();
|
||||
const contacts = await db.contacts.toArray();
|
||||
logger.info(
|
||||
`[MigrationService] Retrieved ${contacts.length} contacts from Dexie`,
|
||||
`[IndexedDBMigrationService] Retrieved ${contacts.length} contacts from Dexie`,
|
||||
);
|
||||
return contacts;
|
||||
} catch (error) {
|
||||
logger.error("[MigrationService] Error retrieving Dexie contacts:", error);
|
||||
logger.error(
|
||||
"[IndexedDBMigrationService] Error retrieving Dexie contacts:",
|
||||
error,
|
||||
);
|
||||
throw new Error(`Failed to retrieve Dexie contacts: ${error}`);
|
||||
}
|
||||
}
|
||||
@@ -214,11 +216,14 @@ export async function getSqliteContacts(): Promise<Contact[]> {
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`[MigrationService] Retrieved ${contacts.length} contacts from SQLite`,
|
||||
`[IndexedDBMigrationService] Retrieved ${contacts.length} contacts from SQLite`,
|
||||
);
|
||||
return contacts;
|
||||
} catch (error) {
|
||||
logger.error("[MigrationService] Error retrieving SQLite contacts:", error);
|
||||
logger.error(
|
||||
"[IndexedDBMigrationService] Error retrieving SQLite contacts:",
|
||||
error,
|
||||
);
|
||||
throw new Error(`Failed to retrieve SQLite contacts: ${error}`);
|
||||
}
|
||||
}
|
||||
@@ -251,11 +256,14 @@ export async function getDexieSettings(): Promise<Settings[]> {
|
||||
await db.open();
|
||||
const settings = await db.settings.toArray();
|
||||
logger.info(
|
||||
`[MigrationService] Retrieved ${settings.length} settings from Dexie`,
|
||||
`[IndexedDBMigrationService] Retrieved ${settings.length} settings from Dexie`,
|
||||
);
|
||||
return settings;
|
||||
} catch (error) {
|
||||
logger.error("[MigrationService] Error retrieving Dexie settings:", error);
|
||||
logger.error(
|
||||
"[IndexedDBMigrationService] Error retrieving Dexie settings:",
|
||||
error,
|
||||
);
|
||||
throw new Error(`Failed to retrieve Dexie settings: ${error}`);
|
||||
}
|
||||
}
|
||||
@@ -309,11 +317,14 @@ export async function getSqliteSettings(): Promise<Settings[]> {
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`[MigrationService] Retrieved ${settings.length} settings from SQLite`,
|
||||
`[IndexedDBMigrationService] Retrieved ${settings.length} settings from SQLite`,
|
||||
);
|
||||
return settings;
|
||||
} catch (error) {
|
||||
logger.error("[MigrationService] Error retrieving SQLite settings:", error);
|
||||
logger.error(
|
||||
"[IndexedDBMigrationService] Error retrieving SQLite settings:",
|
||||
error,
|
||||
);
|
||||
throw new Error(`Failed to retrieve SQLite settings: ${error}`);
|
||||
}
|
||||
}
|
||||
@@ -353,11 +364,14 @@ export async function getSqliteAccounts(): Promise<string[]> {
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`[MigrationService] Retrieved ${dids.length} accounts from SQLite`,
|
||||
`[IndexedDBMigrationService] Retrieved ${dids.length} accounts from SQLite`,
|
||||
);
|
||||
return dids;
|
||||
} catch (error) {
|
||||
logger.error("[MigrationService] Error retrieving SQLite accounts:", error);
|
||||
logger.error(
|
||||
"[IndexedDBMigrationService] Error retrieving SQLite accounts:",
|
||||
error,
|
||||
);
|
||||
throw new Error(`Failed to retrieve SQLite accounts: ${error}`);
|
||||
}
|
||||
}
|
||||
@@ -391,11 +405,14 @@ export async function getDexieAccounts(): Promise<Account[]> {
|
||||
await accountsDB.open();
|
||||
const accounts = await accountsDB.accounts.toArray();
|
||||
logger.info(
|
||||
`[MigrationService] Retrieved ${accounts.length} accounts from Dexie`,
|
||||
`[IndexedDBMigrationService] Retrieved ${accounts.length} accounts from Dexie`,
|
||||
);
|
||||
return accounts;
|
||||
} catch (error) {
|
||||
logger.error("[MigrationService] Error retrieving Dexie accounts:", error);
|
||||
logger.error(
|
||||
"[IndexedDBMigrationService] Error retrieving Dexie accounts:",
|
||||
error,
|
||||
);
|
||||
throw new Error(`Failed to retrieve Dexie accounts: ${error}`);
|
||||
}
|
||||
}
|
||||
@@ -429,7 +446,7 @@ export async function getDexieAccounts(): Promise<Account[]> {
|
||||
* ```
|
||||
*/
|
||||
export async function compareDatabases(): Promise<DataComparison> {
|
||||
logger.info("[MigrationService] Starting database comparison");
|
||||
logger.info("[IndexedDBMigrationService] Starting database comparison");
|
||||
|
||||
const [
|
||||
dexieContacts,
|
||||
@@ -470,7 +487,7 @@ export async function compareDatabases(): Promise<DataComparison> {
|
||||
},
|
||||
};
|
||||
|
||||
logger.info("[MigrationService] Database comparison completed", {
|
||||
logger.info("[IndexedDBMigrationService] Database comparison completed", {
|
||||
dexieContacts: dexieContacts.length,
|
||||
sqliteContacts: sqliteContacts.length,
|
||||
dexieSettings: dexieSettings.length,
|
||||
@@ -679,6 +696,7 @@ function compareAccounts(dexieAccounts: Account[], sqliteDids: string[]) {
|
||||
* ```
|
||||
*/
|
||||
function contactsEqual(contact1: Contact, contact2: Contact): boolean {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const ifEmpty = (arg: any, def: any) => (arg ? arg : def);
|
||||
const contact1Methods =
|
||||
contact1.contactMethods &&
|
||||
@@ -954,7 +972,7 @@ export function generateComparisonYaml(comparison: DataComparison): string {
|
||||
export async function migrateContacts(
|
||||
overwriteExisting: boolean = false,
|
||||
): Promise<MigrationResult> {
|
||||
logger.info("[MigrationService] Starting contact migration", {
|
||||
logger.info("[IndexedDBMigrationService] Starting contact migration", {
|
||||
overwriteExisting,
|
||||
});
|
||||
|
||||
@@ -990,7 +1008,7 @@ export async function migrateContacts(
|
||||
);
|
||||
await platformService.dbExec(sql, params);
|
||||
result.contactsMigrated++;
|
||||
logger.info(`[MigrationService] Updated contact: ${contact.did}`);
|
||||
logger.info(`[IndexedDBMigrationService] Updated contact: ${contact.did}`);
|
||||
} else {
|
||||
result.warnings.push(
|
||||
`Contact ${contact.did} already exists, skipping`,
|
||||
@@ -1004,17 +1022,17 @@ export async function migrateContacts(
|
||||
);
|
||||
await platformService.dbExec(sql, params);
|
||||
result.contactsMigrated++;
|
||||
logger.info(`[MigrationService] Added contact: ${contact.did}`);
|
||||
logger.info(`[IndexedDBMigrationService] Added contact: ${contact.did}`);
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMsg = `Failed to migrate contact ${contact.did}: ${error}`;
|
||||
logger.error("[MigrationService]", errorMsg);
|
||||
logger.error("[IndexedDBMigrationService]", errorMsg);
|
||||
result.errors.push(errorMsg);
|
||||
result.success = false;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("[MigrationService] Contact migration completed", {
|
||||
logger.info("[IndexedDBMigrationService] Contact migration completed", {
|
||||
contactsMigrated: result.contactsMigrated,
|
||||
errors: result.errors.length,
|
||||
warnings: result.warnings.length,
|
||||
@@ -1023,7 +1041,7 @@ export async function migrateContacts(
|
||||
return result;
|
||||
} catch (error) {
|
||||
const errorMsg = `Contact migration failed: ${error}`;
|
||||
logger.error("[MigrationService]", errorMsg);
|
||||
logger.error("[IndexedDBMigrationService]", errorMsg);
|
||||
result.errors.push(errorMsg);
|
||||
result.success = false;
|
||||
return result;
|
||||
@@ -1063,7 +1081,7 @@ export async function migrateContacts(
|
||||
* ```
|
||||
*/
|
||||
export async function migrateSettings(): Promise<MigrationResult> {
|
||||
logger.info("[MigrationService] Starting settings migration");
|
||||
logger.info("[IndexedDBMigrationService] Starting settings migration");
|
||||
|
||||
const result: MigrationResult = {
|
||||
success: true,
|
||||
@@ -1076,17 +1094,17 @@ export async function migrateSettings(): Promise<MigrationResult> {
|
||||
|
||||
try {
|
||||
const dexieSettings = await getDexieSettings();
|
||||
logger.info("[MigrationService] Migrating settings", {
|
||||
logger.info("[IndexedDBMigrationService] Migrating settings", {
|
||||
dexieSettings: dexieSettings.length,
|
||||
});
|
||||
const platformService = PlatformServiceFactory.getInstance();
|
||||
|
||||
// Create an array of promises for all settings migrations
|
||||
const migrationPromises = dexieSettings.map(async (setting) => {
|
||||
logger.info("[MigrationService] Starting to migrate settings", setting);
|
||||
let sqliteSettingRaw:
|
||||
| { columns: string[]; values: unknown[][] }
|
||||
| undefined;
|
||||
logger.info(
|
||||
"[IndexedDBMigrationService] Starting to migrate settings",
|
||||
setting,
|
||||
);
|
||||
|
||||
// adjust SQL based on the accountDid key, maybe null
|
||||
let conditional: string;
|
||||
@@ -1098,15 +1116,18 @@ export async function migrateSettings(): Promise<MigrationResult> {
|
||||
conditional = "accountDid = ?";
|
||||
preparams = [setting.accountDid];
|
||||
}
|
||||
sqliteSettingRaw = await platformService.dbQuery(
|
||||
const sqliteSettingRaw = await platformService.dbQuery(
|
||||
"SELECT * FROM settings WHERE " + conditional,
|
||||
preparams,
|
||||
);
|
||||
|
||||
logger.info("[MigrationService] Migrating one set of settings:", {
|
||||
setting,
|
||||
sqliteSettingRaw,
|
||||
});
|
||||
logger.info(
|
||||
"[IndexedDBMigrationService] Migrating one set of settings:",
|
||||
{
|
||||
setting,
|
||||
sqliteSettingRaw,
|
||||
},
|
||||
);
|
||||
if (sqliteSettingRaw?.values?.length) {
|
||||
// should cover the master settings, where accountDid is null
|
||||
delete setting.id; // don't conflict with the id in the sqlite database
|
||||
@@ -1117,7 +1138,7 @@ export async function migrateSettings(): Promise<MigrationResult> {
|
||||
conditional,
|
||||
preparams,
|
||||
);
|
||||
logger.info("[MigrationService] Updating settings", {
|
||||
logger.info("[IndexedDBMigrationService] Updating settings", {
|
||||
sql,
|
||||
params,
|
||||
});
|
||||
@@ -1127,10 +1148,7 @@ export async function migrateSettings(): Promise<MigrationResult> {
|
||||
// insert new setting
|
||||
delete setting.id; // don't conflict with the id in the sqlite database
|
||||
delete setting.activeDid; // ensure we don't set the activeDid (since master settings are an update and don't hit this case)
|
||||
const { sql, params } = generateInsertStatement(
|
||||
setting,
|
||||
"settings",
|
||||
);
|
||||
const { sql, params } = generateInsertStatement(setting, "settings");
|
||||
await platformService.dbExec(sql, params);
|
||||
result.settingsMigrated++;
|
||||
}
|
||||
@@ -1140,7 +1158,7 @@ export async function migrateSettings(): Promise<MigrationResult> {
|
||||
const updatedSettings = await Promise.all(migrationPromises);
|
||||
|
||||
logger.info(
|
||||
"[MigrationService] Finished migrating settings",
|
||||
"[IndexedDBMigrationService] Finished migrating settings",
|
||||
updatedSettings,
|
||||
result,
|
||||
);
|
||||
@@ -1148,7 +1166,7 @@ export async function migrateSettings(): Promise<MigrationResult> {
|
||||
return result;
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
"[MigrationService] Complete settings migration failed:",
|
||||
"[IndexedDBMigrationService] Complete settings migration failed:",
|
||||
error,
|
||||
);
|
||||
const errorMessage = `Settings migration failed: ${error}`;
|
||||
@@ -1192,7 +1210,7 @@ export async function migrateSettings(): Promise<MigrationResult> {
|
||||
* ```
|
||||
*/
|
||||
export async function migrateAccounts(): Promise<MigrationResult> {
|
||||
logger.info("[MigrationService] Starting account migration");
|
||||
logger.info("[IndexedDBMigrationService] Starting account migration");
|
||||
|
||||
const result: MigrationResult = {
|
||||
success: true,
|
||||
@@ -1248,14 +1266,17 @@ export async function migrateAccounts(): Promise<MigrationResult> {
|
||||
);
|
||||
}
|
||||
|
||||
logger.info("[MigrationService] Successfully migrated account", {
|
||||
did,
|
||||
dateCreated: account.dateCreated,
|
||||
});
|
||||
logger.info(
|
||||
"[IndexedDBMigrationService] Successfully migrated account",
|
||||
{
|
||||
did,
|
||||
dateCreated: account.dateCreated,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
const errorMessage = `Failed to migrate account ${did}: ${error}`;
|
||||
result.errors.push(errorMessage);
|
||||
logger.error("[MigrationService] Account migration failed:", {
|
||||
logger.error("[IndexedDBMigrationService] Account migration failed:", {
|
||||
error,
|
||||
did,
|
||||
});
|
||||
@@ -1272,7 +1293,7 @@ export async function migrateAccounts(): Promise<MigrationResult> {
|
||||
result.errors.push(errorMessage);
|
||||
result.success = false;
|
||||
logger.error(
|
||||
"[MigrationService] Complete account migration failed:",
|
||||
"[IndexedDBMigrationService] Complete account migration failed:",
|
||||
error,
|
||||
);
|
||||
return result;
|
||||
@@ -1306,11 +1327,11 @@ export async function migrateAll(): Promise<MigrationResult> {
|
||||
|
||||
try {
|
||||
logger.info(
|
||||
"[MigrationService] Starting complete migration from Dexie to SQLite",
|
||||
"[IndexedDBMigrationService] Starting complete migration from Dexie to SQLite",
|
||||
);
|
||||
|
||||
// Step 1: Migrate Accounts (foundational)
|
||||
logger.info("[MigrationService] Step 1: Migrating accounts...");
|
||||
logger.info("[IndexedDBMigrationService] Step 1: Migrating accounts...");
|
||||
const accountsResult = await migrateAccounts();
|
||||
if (!accountsResult.success) {
|
||||
result.errors.push(
|
||||
@@ -1322,7 +1343,7 @@ export async function migrateAll(): Promise<MigrationResult> {
|
||||
result.warnings.push(...accountsResult.warnings);
|
||||
|
||||
// Step 2: Migrate Settings (depends on accounts)
|
||||
logger.info("[MigrationService] Step 2: Migrating settings...");
|
||||
logger.info("[IndexedDBMigrationService] Step 2: Migrating settings...");
|
||||
const settingsResult = await migrateSettings();
|
||||
if (!settingsResult.success) {
|
||||
result.errors.push(
|
||||
@@ -1335,7 +1356,7 @@ export async function migrateAll(): Promise<MigrationResult> {
|
||||
|
||||
// Step 4: Migrate Contacts (independent, but after accounts for consistency)
|
||||
// ... but which is better done through the contact import view
|
||||
// logger.info("[MigrationService] Step 4: Migrating contacts...");
|
||||
// logger.info("[IndexedDBMigrationService] Step 4: Migrating contacts...");
|
||||
// const contactsResult = await migrateContacts();
|
||||
// if (!contactsResult.success) {
|
||||
// result.errors.push(
|
||||
@@ -1354,7 +1375,7 @@ export async function migrateAll(): Promise<MigrationResult> {
|
||||
result.contactsMigrated;
|
||||
|
||||
logger.info(
|
||||
`[MigrationService] Complete migration successful: ${totalMigrated} total records migrated`,
|
||||
`[IndexedDBMigrationService] Complete migration successful: ${totalMigrated} total records migrated`,
|
||||
{
|
||||
accounts: result.accountsMigrated,
|
||||
settings: result.settingsMigrated,
|
||||
@@ -1367,7 +1388,10 @@ export async function migrateAll(): Promise<MigrationResult> {
|
||||
} catch (error) {
|
||||
const errorMessage = `Complete migration failed: ${error}`;
|
||||
result.errors.push(errorMessage);
|
||||
logger.error("[MigrationService] Complete migration failed:", error);
|
||||
logger.error(
|
||||
"[IndexedDBMigrationService] Complete migration failed:",
|
||||
error,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,8 +349,9 @@
|
||||
</div>
|
||||
<div v-if="includeUserProfileLocation" class="mb-4 aspect-video">
|
||||
<p class="text-sm mb-2 text-slate-500">
|
||||
The location you choose will be shared with the world until you remove this checkbox.
|
||||
For your security, choose a location nearby but not exactly at your true location, like at your town center.
|
||||
The location you choose will be shared with the world until you remove
|
||||
this checkbox. For your security, choose a location nearby but not
|
||||
exactly at your true location, like at your town center.
|
||||
</p>
|
||||
|
||||
<l-map
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
@click="confirmSetVisibility(contactFromDid, false)"
|
||||
>
|
||||
<font-awesome icon="eye" class="fa-fw" />
|
||||
<font-awesome icon="arrow-up" class="fa-fw" />
|
||||
</button>
|
||||
<button
|
||||
v-else-if="
|
||||
@@ -87,6 +88,32 @@
|
||||
@click="confirmSetVisibility(contactFromDid, true)"
|
||||
>
|
||||
<font-awesome icon="eye-slash" class="fa-fw" />
|
||||
<font-awesome icon="arrow-up" class="fa-fw" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="
|
||||
contactFromDid?.iViewContent &&
|
||||
contactFromDid.did !== activeDid
|
||||
"
|
||||
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
||||
title="I view their content"
|
||||
@click="confirmViewContent(contactFromDid, false)"
|
||||
>
|
||||
<font-awesome icon="eye" class="fa-fw" />
|
||||
<font-awesome icon="arrow-down" class="fa-fw" />
|
||||
</button>
|
||||
<button
|
||||
v-else-if="
|
||||
!contactFromDid?.iViewContent &&
|
||||
contactFromDid?.did !== activeDid
|
||||
"
|
||||
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
||||
title="I do not view their content"
|
||||
@click="confirmViewContent(contactFromDid, true)"
|
||||
>
|
||||
<font-awesome icon="eye-slash" class="fa-fw" />
|
||||
<font-awesome icon="arrow-down" class="fa-fw" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -825,9 +852,9 @@ export default class DIDView extends Vue {
|
||||
title: "Visibility Refreshed",
|
||||
text:
|
||||
libsUtil.nameForContact(contact, true) +
|
||||
" can " +
|
||||
(visibility ? "" : "not ") +
|
||||
"see your activity.",
|
||||
" can" +
|
||||
(visibility ? "" : " not") +
|
||||
" see your activity.",
|
||||
},
|
||||
3000,
|
||||
);
|
||||
@@ -857,6 +884,64 @@ export default class DIDView extends Vue {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm whether the user want to see/hide the other's content, then execute it
|
||||
*
|
||||
* @param contact Contact content to show/hide from user
|
||||
* @param view whether user wants to view this contact
|
||||
*/
|
||||
async confirmViewContent(contact: Contact, view: boolean) {
|
||||
const contentVisibilityPrompt = view
|
||||
? "Are you sure you want to see their content?"
|
||||
: "Are you sure you want to hide their content from you?";
|
||||
this.$notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: "Set Content Visibility",
|
||||
text: contentVisibilityPrompt,
|
||||
onYes: async () => {
|
||||
const success = await this.setViewContent(contact, view);
|
||||
if (success) {
|
||||
contact.iViewContent = view; // see visibility note about not working inside setVisibility
|
||||
}
|
||||
},
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates contact content visibility for this device
|
||||
*
|
||||
* @param contact - Contact to update content visibility for
|
||||
* @param visibility - New content visibility state
|
||||
* @returns Boolean indicating success
|
||||
*/
|
||||
async setViewContent(contact: Contact, visibility: boolean) {
|
||||
const platformService = PlatformServiceFactory.getInstance();
|
||||
await platformService.dbExec(
|
||||
"UPDATE contacts SET iViewContent = ? WHERE did = ?",
|
||||
[visibility, contact.did],
|
||||
);
|
||||
if (USE_DEXIE_DB) {
|
||||
db.contacts.update(contact.did, { iViewContent: visibility });
|
||||
}
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Visibility Set",
|
||||
text:
|
||||
"You will" +
|
||||
(visibility ? "" : " not") +
|
||||
` see ${contact.name}'s activity.`,
|
||||
},
|
||||
3000,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1122,6 +1122,7 @@ export default class DatabaseMigration extends Vue {
|
||||
private loadingMessage = "";
|
||||
private error = "";
|
||||
private warning = "";
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private exportedData: Record<string, any> | null = null;
|
||||
private successMessage = "";
|
||||
|
||||
@@ -1134,6 +1135,7 @@ export default class DatabaseMigration extends Vue {
|
||||
* @param {any} setting - The setting object
|
||||
* @returns {string} The display name for the setting
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
getSettingDisplayName(setting: any): string {
|
||||
// Handle exported JSON format (has 'type' and 'did' fields)
|
||||
if (setting.type && setting.did) {
|
||||
@@ -1153,6 +1155,7 @@ export default class DatabaseMigration extends Vue {
|
||||
* @param {any} account - The account object
|
||||
* @returns {boolean} True if account has identity
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
getAccountHasIdentity(account: any): boolean {
|
||||
// Handle exported JSON format (has 'hasIdentity' field)
|
||||
if (account.hasIdentity !== undefined) {
|
||||
@@ -1170,6 +1173,7 @@ export default class DatabaseMigration extends Vue {
|
||||
* @param {any} account - The account object
|
||||
* @returns {boolean} True if account has mnemonic
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
getAccountHasMnemonic(account: any): boolean {
|
||||
// Handle exported JSON format (has 'hasMnemonic' field)
|
||||
if (account.hasMnemonic !== undefined) {
|
||||
|
||||
@@ -448,6 +448,7 @@ export default class HomeView extends Vue {
|
||||
allContacts: Array<Contact> = [];
|
||||
allMyDids: Array<string> = [];
|
||||
apiServer = "";
|
||||
blockedContactDids: Array<string> = [];
|
||||
feedData: GiveRecordWithContactInfo[] = [];
|
||||
feedPreviousOldestId?: string;
|
||||
feedLastViewedClaimId?: string;
|
||||
@@ -567,22 +568,14 @@ export default class HomeView extends Vue {
|
||||
|
||||
// Load contacts with graceful fallback
|
||||
try {
|
||||
const platformService = PlatformServiceFactory.getInstance();
|
||||
const dbContacts = await platformService.dbQuery(
|
||||
"SELECT * FROM contacts",
|
||||
);
|
||||
this.allContacts = databaseUtil.mapQueryResultToValues(
|
||||
dbContacts,
|
||||
) as Contact[];
|
||||
if (USE_DEXIE_DB) {
|
||||
this.allContacts = await db.contacts.toArray();
|
||||
}
|
||||
this.loadContacts();
|
||||
} catch (error) {
|
||||
logConsoleAndDb(
|
||||
`[HomeView] Failed to retrieve contacts: ${error}`,
|
||||
true,
|
||||
);
|
||||
this.allContacts = []; // Ensure we have a valid empty array
|
||||
this.blockedContactDids = [];
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -746,6 +739,9 @@ export default class HomeView extends Vue {
|
||||
if (USE_DEXIE_DB) {
|
||||
this.allContacts = await db.contacts.toArray();
|
||||
}
|
||||
this.blockedContactDids = this.allContacts
|
||||
.filter((c) => !c.iViewContent)
|
||||
.map((c) => c.did);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1013,6 +1009,7 @@ export default class HomeView extends Vue {
|
||||
);
|
||||
if (results.data.length > 0) {
|
||||
endOfResults = false;
|
||||
// gather any contacts that user has blocked from view
|
||||
await this.processFeedResults(results.data);
|
||||
await this.updateFeedLastViewedId(results.data);
|
||||
}
|
||||
@@ -1200,7 +1197,7 @@ export default class HomeView extends Vue {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if record should be included based on filters
|
||||
* Checks if record should be included based on filters & preferences
|
||||
*
|
||||
* @internal
|
||||
* @callGraph
|
||||
@@ -1226,6 +1223,10 @@ export default class HomeView extends Vue {
|
||||
record: GiveSummaryRecord,
|
||||
fulfillsPlan?: FulfillsPlan,
|
||||
): boolean {
|
||||
if (this.blockedContactDids.includes(record.issuerDid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.isAnyFeedFilterOn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user