Simplify starting item types and item type selector (#1404)

This commit is contained in:
Leendert de Borst
2025-12-10 13:17:10 +01:00
parent 48d17081dc
commit 38aa6452dc
9 changed files with 630 additions and 275 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
@@ -17,11 +17,11 @@ import { useVaultMutate } from '@/entrypoints/popup/hooks/useVaultMutate';
import { IdentityHelperUtils, CreateIdentityGenerator, convertAgeRangeToBirthdateOptions } from '@/utils/dist/shared/identity-generator';
import type { Item, ItemField, ItemType, FieldType } from '@/utils/dist/shared/models/vault';
import { getSystemFieldsForItemType, isFieldShownByDefault, getOptionalFieldsForItemType } from '@/utils/dist/shared/models/vault';
import { getSystemFieldsForItemType, isFieldShownByDefault } from '@/utils/dist/shared/models/vault';
import { CreatePasswordGenerator } from '@/utils/dist/shared/password-generator';
// Valid item types from the shared model
const VALID_ITEM_TYPES: ItemType[] = ['Login', 'CreditCard', 'Identity', 'Note'];
const VALID_ITEM_TYPES: ItemType[] = ['Login', 'Alias', 'CreditCard', 'Note'];
// Default item type for new items
const DEFAULT_ITEM_TYPE: ItemType = 'Login';
@@ -48,6 +48,15 @@ const ITEM_TYPE_OPTIONS: ItemTypeOption[] = [
</svg>
)
},
{
type: 'Alias',
titleKey: 'itemTypes.alias.title',
iconSvg: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
)
},
{
type: 'CreditCard',
titleKey: 'itemTypes.creditCard.title',
@@ -57,15 +66,6 @@ const ITEM_TYPE_OPTIONS: ItemTypeOption[] = [
</svg>
)
},
{
type: 'Identity',
titleKey: 'itemTypes.identity.title',
iconSvg: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
)
},
{
type: 'Note',
titleKey: 'itemTypes.note.title',
@@ -140,6 +140,26 @@ const ItemAddEdit: React.FC = () => {
// Add menu dropdown state (unified + button)
const [showAddMenu, setShowAddMenu] = useState(false);
// Track if alias was already auto-generated (to avoid re-generating on re-renders)
const aliasGeneratedRef = useRef(false);
// Ref for the item name input field (for auto-focus)
const nameInputRef = useRef<HTMLInputElement>(null);
// Track last generated values to avoid overwriting manual user entries on regenerate
const [lastGeneratedValues, setLastGeneratedValues] = useState<{
username: string | null;
password: string | null;
email: string | null;
}>({
username: null,
password: null,
email: null
});
// Track password field visibility (for showing generated passwords)
const [showPassword, setShowPassword] = useState(false);
/**
* Get all applicable system fields for the current item type.
* These are sorted by DefaultDisplayOrder.
@@ -152,21 +172,10 @@ const ItemAddEdit: React.FC = () => {
}, [item]);
/**
* Get optional fields that are not shown by default for the current item type.
* These can be added via the "+" menu.
*/
const optionalFields = useMemo(() => {
if (!item) {
return [];
}
return getOptionalFieldsForItemType(item.ItemType);
}, [item]);
/**
* The notes field (login.notes) - handled separately for collapsible UI.
* The notes field (metadata.notes) - handled separately for collapsible UI.
*/
const notesField = useMemo(() => {
return applicableSystemFields.find(field => field.FieldKey === 'login.notes');
return applicableSystemFields.find(field => field.FieldKey === 'metadata.notes');
}, [applicableSystemFields]);
/**
@@ -183,9 +192,16 @@ const ItemAddEdit: React.FC = () => {
return isFieldShownByDefault(systemField, item.ItemType);
}, [item, applicableSystemFields]);
/**
* Primary fields (like URL) that should be shown in the name block.
*/
const primaryFields = useMemo(() => {
return applicableSystemFields.filter(field => field.Category === 'Primary');
}, [applicableSystemFields]);
/**
* Group system fields by category for organized rendering.
* Excludes metadata fields (notes) which are handled separately.
* Excludes metadata fields (notes) and header fields which are handled separately.
*/
const groupedSystemFields = useMemo(() => {
const groups: Record<string, typeof applicableSystemFields> = {};
@@ -195,6 +211,10 @@ const ItemAddEdit: React.FC = () => {
if (field.Category === 'Metadata') {
return;
}
// Skip primary fields - rendered in name block
if (field.Category === 'Primary') {
return;
}
const category = field.Category || 'Other';
if (!groups[category]) {
@@ -231,11 +251,16 @@ const ItemAddEdit: React.FC = () => {
// Check if notes should be shown by default for this type
const typeFields = getSystemFieldsForItemType(effectiveType);
const notesFieldDef = typeFields.find(f => f.FieldKey === 'login.notes');
const notesFieldDef = typeFields.find(f => f.FieldKey === 'metadata.notes');
if (notesFieldDef && isFieldShownByDefault(notesFieldDef, effectiveType)) {
setShowNotes(true);
}
// For Alias type, show alias fields by default
if (effectiveType === 'Alias') {
setShowAliasFields(true);
}
// Load folders
if (dbContext?.sqliteClient) {
const allFolders = dbContext.sqliteClient.getAllFolders();
@@ -291,6 +316,26 @@ const ItemAddEdit: React.FC = () => {
}
}, [dbContext?.sqliteClient, id, isEditMode, itemTypeParam, itemNameParam, navigate, setIsInitialLoading]);
/**
* Auto-generate alias when Alias type is selected in create mode.
*/
useEffect(() => {
if (!isEditMode && item?.ItemType === 'Alias' && !localLoading && dbContext?.sqliteClient && !aliasGeneratedRef.current) {
aliasGeneratedRef.current = true;
void handleGenerateAlias();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isEditMode, item?.ItemType, localLoading, dbContext?.sqliteClient]);
/**
* Auto-focus the name input field when in add mode.
*/
useEffect(() => {
if (!isEditMode && !localLoading && nameInputRef.current) {
nameInputRef.current.focus();
}
}, [isEditMode, localLoading]);
/**
* Handle field value change.
*/
@@ -451,11 +496,19 @@ const ItemAddEdit: React.FC = () => {
// Clear field values when changing type (except name)
setFieldValues({});
setCustomFields([]);
setShowAliasFields(false);
// For Alias type, show alias fields by default; otherwise hide
if (newType === 'Alias') {
setShowAliasFields(true);
// Reset the ref so alias will be auto-generated
aliasGeneratedRef.current = false;
} else {
setShowAliasFields(false);
}
// Check if notes should be shown by default for the new type
const newTypeFields = getSystemFieldsForItemType(newType);
const newNotesField = newTypeFields.find(f => f.FieldKey === 'login.notes');
const newNotesField = newTypeFields.find(f => f.FieldKey === 'metadata.notes');
const notesShownByDefault = newNotesField ? isFieldShownByDefault(newNotesField, newType) : false;
setShowNotes(notesShownByDefault);
@@ -488,6 +541,7 @@ const ItemAddEdit: React.FC = () => {
/**
* Generate random alias and populate alias fields.
* This shows the alias fields and fills them with random values.
* Only overwrites username/password/email if they're empty or match the last generated value.
*/
const handleGenerateAlias = useCallback(async () => {
if (!dbContext?.sqliteClient) {
@@ -512,25 +566,55 @@ const ItemAddEdit: React.FC = () => {
const email = defaultEmailDomain ? `${identity.emailPrefix}@${defaultEmailDomain}` : identity.emailPrefix;
// Set field values for alias fields
setFieldValues(prev => ({
...prev,
'alias.email': email,
'alias.first_name': identity.firstName,
'alias.last_name': identity.lastName,
'alias.nickname': identity.nickName,
'alias.gender': identity.gender,
'alias.birthdate': IdentityHelperUtils.normalizeBirthDateForDisplay(identity.birthDate.toISOString()),
// Also set username and password if they're empty
'login.username': prev['login.username'] || identity.nickName,
'login.password': prev['login.password'] || password
}));
setFieldValues(prev => {
const currentUsername = (prev['login.username'] as string) || '';
const currentPassword = (prev['login.password'] as string) || '';
const currentEmail = (prev['alias.email'] as string) || '';
const newValues: Record<string, string | string[]> = {
...prev,
// Always update alias identity fields
'alias.first_name': identity.firstName,
'alias.last_name': identity.lastName,
'alias.nickname': identity.nickName,
'alias.gender': identity.gender,
'alias.birthdate': IdentityHelperUtils.normalizeBirthDateForDisplay(identity.birthDate.toISOString())
};
// Only overwrite email if it's empty or matches the last generated value
if (!currentEmail || currentEmail === lastGeneratedValues.email) {
newValues['alias.email'] = email;
}
// Only overwrite username if it's empty or matches the last generated value
if (!currentUsername || currentUsername === lastGeneratedValues.username) {
newValues['login.username'] = identity.nickName;
}
// Only overwrite password if it's empty or matches the last generated value
if (!currentPassword || currentPassword === lastGeneratedValues.password) {
newValues['login.password'] = password;
}
return newValues;
});
// Update tracking with new generated values
setLastGeneratedValues({
username: identity.nickName,
password: password,
email: email
});
// Show the generated password (it's random so no need to hide)
setShowPassword(true);
// Show alias fields section
setShowAliasFields(true);
} catch (error) {
console.error('Error generating random alias:', error);
}
}, [dbContext.sqliteClient, initializeGenerators]);
}, [dbContext.sqliteClient, initializeGenerators, lastGeneratedValues]);
/**
* Clear all alias field values but keep them visible.
@@ -561,7 +645,7 @@ const ItemAddEdit: React.FC = () => {
const handleRemoveNotesSection = useCallback(() => {
setFieldValues(prev => ({
...prev,
'login.notes': ''
'metadata.notes': ''
}));
setShowNotes(false);
}, []);
@@ -603,7 +687,7 @@ const ItemAddEdit: React.FC = () => {
// Notes option - show when notes field exists, is optional (not shown by default), not yet shown, and has no value
const notesIsOptional = notesField && !shouldShowFieldByDefault(notesField);
if (notesField && notesIsOptional && !showNotes && !fieldValues['login.notes'] && !isEditMode) {
if (notesField && notesIsOptional && !showNotes && !fieldValues['metadata.notes'] && !isEditMode) {
options.push({
key: 'notes',
label: t('credentials.notes'),
@@ -631,19 +715,6 @@ const ItemAddEdit: React.FC = () => {
return options;
}, [showNotes, notesField, fieldValues, isEditMode, t, handleAddNotesFromMenu, handleAddCustomFieldFromMenu, shouldShowFieldByDefault]);
/**
* Whether to show the dedicated "Add alias" button.
* Show when alias fields exist as optional (not shown by default) and not yet added.
*/
const showAddAliasButton = useMemo(() => {
if (!item || isEditMode) {
return false;
}
// Check if there are any alias fields that are optional (not shown by default)
const hasOptionalAliasFields = optionalFields.some(f => f.Category === 'Alias');
return hasOptionalAliasFields && !showAliasFields;
}, [item, optionalFields, showAliasFields, isEditMode]);
// Set header buttons
useEffect(() => {
const headerButtonsJSX = (
@@ -726,6 +797,8 @@ const ItemAddEdit: React.FC = () => {
label={label}
value={stringValue}
onChange={(val) => handleFieldChange(fieldKey, val)}
showPassword={showPassword}
onShowPasswordChange={setShowPassword}
/>
);
@@ -774,39 +847,66 @@ const ItemAddEdit: React.FC = () => {
/>
);
}
}, [fieldValues, handleFieldChange]);
}, [fieldValues, handleFieldChange, showPassword]);
/**
* Handle form submission via Enter key.
*/
const handleFormSubmit = useCallback((e: React.FormEvent) => {
e.preventDefault();
void handleSave();
}, [handleSave]);
if (localLoading || !item) {
return <LoadingSpinner />;
}
return (
<div className="space-y-4">
<form onSubmit={handleFormSubmit} className="space-y-4">
{/* Item Type Selector (create mode only) */}
{!isEditMode && (
<div className="relative">
<button
type="button"
onClick={() => setShowTypeDropdown(!showTypeDropdown)}
className="w-full px-4 py-2 bg-primary-50 dark:bg-primary-900/20 border border-primary-200 dark:border-primary-800 rounded-lg text-left flex items-center justify-between hover:bg-primary-100 dark:hover:bg-primary-900/30 transition-colors"
>
<div className="flex items-center gap-2">
<span className="text-primary-600 dark:text-primary-400">
{selectedTypeOption?.iconSvg}
</span>
<span className="text-primary-700 dark:text-primary-300 font-medium text-sm">
{t('itemTypes.creating')} {selectedTypeOption ? t(selectedTypeOption.titleKey) : ''}
</span>
</div>
<svg
className={`w-4 h-4 text-primary-500 transition-transform ${showTypeDropdown ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
<div className="w-full px-4 py-2 bg-primary-50 dark:bg-primary-900/20 border border-primary-200 dark:border-primary-800 rounded-lg flex items-center gap-2">
<button
type="button"
onClick={() => setShowTypeDropdown(!showTypeDropdown)}
className="flex-1 flex items-center justify-between hover:opacity-80 transition-opacity"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
<div className="flex items-center gap-2">
<span className="text-primary-600 dark:text-primary-400">
{selectedTypeOption?.iconSvg}
</span>
<span className="text-primary-700 dark:text-primary-300 font-medium text-sm">
{t('itemTypes.creating')} {selectedTypeOption ? t(selectedTypeOption.titleKey) : ''}
</span>
</div>
<svg
className={`w-4 h-4 text-primary-500 transition-transform ${showTypeDropdown ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{/* Regenerate alias button - icon only for flexibility */}
{item?.ItemType === 'Alias' && (
<button
type="button"
onClick={(e) => {
e.stopPropagation();
void handleGenerateAlias();
}}
className="flex-shrink-0 p-1.5 text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 hover:bg-primary-100 dark:hover:bg-primary-900/40 rounded transition-colors"
title={t('itemTypes.regenerateAlias')}
>
<svg className='w-4 h-4' viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M23 4v6h-6"/>
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>
</svg>
</button>
)}
</div>
{/* Type Dropdown Menu */}
{showTypeDropdown && (
@@ -846,44 +946,59 @@ const ItemAddEdit: React.FC = () => {
</div>
)}
{/* Item Name - Central prominent field */}
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg border border-gray-200 dark:border-gray-700">
<label htmlFor="itemName" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
{t('credentials.itemName')} <span className="text-red-500">*</span>
</label>
<div className="relative">
<input
id="itemName"
type="text"
value={item.Name || ''}
onChange={(e) => setItem({ ...item, Name: e.target.value })}
placeholder={t('credentials.itemName')}
className={`w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:text-white ${folders.length > 0 ? 'pr-28' : ''}`}
required
/>
{/* Folder Button inside input */}
{folders.length > 0 && (
<button
type="button"
onClick={() => setShowFolderModal(true)}
className={`absolute right-1 top-1/2 -translate-y-1/2 flex items-center gap-1 px-2 py-1 rounded transition-colors text-xs ${
item.FolderId
? 'bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 hover:bg-primary-200 dark:hover:bg-primary-900/50'
: 'text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-600'
}`}
title={item.FolderId ? folders.find(f => f.Id === item.FolderId)?.Name || t('items.folder') : t('items.noFolder')}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
</svg>
{item.FolderId && (
<span className="max-w-16 truncate">
{folders.find(f => f.Id === item.FolderId)?.Name}
</span>
)}
</button>
)}
{/* Item Name and Header fields block */}
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg border border-gray-200 dark:border-gray-700 space-y-4">
<div>
<label htmlFor="itemName" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
{t('credentials.itemName')} <span className="text-red-500">*</span>
</label>
<div className="relative">
<input
ref={nameInputRef}
id="itemName"
type="text"
value={item.Name || ''}
onChange={(e) => setItem({ ...item, Name: e.target.value })}
placeholder={t('credentials.itemName')}
className={`w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:text-white ${folders.length > 0 ? 'pr-28' : ''}`}
required
/>
{/* Folder Button inside input */}
{folders.length > 0 && (
<button
type="button"
onClick={() => setShowFolderModal(true)}
className={`absolute right-1 top-1/2 -translate-y-1/2 flex items-center gap-1 px-2 py-1 rounded transition-colors text-xs ${
item.FolderId
? 'bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 hover:bg-primary-200 dark:hover:bg-primary-900/50'
: 'text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-600'
}`}
title={item.FolderId ? folders.find(f => f.Id === item.FolderId)?.Name || t('items.folder') : t('items.noFolder')}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
</svg>
{item.FolderId && (
<span className="max-w-16 truncate">
{folders.find(f => f.Id === item.FolderId)?.Name}
</span>
)}
</button>
)}
</div>
</div>
{/* Primary fields (like URL) shown below name */}
{primaryFields.map(field => (
<div key={field.FieldKey}>
{renderFieldInput(
field.FieldKey,
field.Label,
field.FieldType,
field.IsHidden,
field.IsMultiValue
)}
</div>
))}
</div>
{/* Render fields grouped by category */}
@@ -986,13 +1101,13 @@ const ItemAddEdit: React.FC = () => {
</div>
)}
{/* Notes Section - Hidden by default in create mode, with remove button */}
{notesField && (showNotes || isEditMode || fieldValues['login.notes']) && (
{/* Notes Section - Hidden by default in create mode, with remove button if optional */}
{notesField && (showNotes || isEditMode || fieldValues['metadata.notes']) && (
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg border border-gray-200 dark:border-gray-700">
<h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white flex items-center justify-between">
<span>{t('credentials.notes')}</span>
{/* Remove button for notes in create mode */}
{!isEditMode && (
{/* Remove button for notes in create mode - only if notes is optional (not shown by default) */}
{!isEditMode && !shouldShowFieldByDefault(notesField) && (
<button
type="button"
onClick={handleRemoveNotesSection}
@@ -1018,25 +1133,6 @@ const ItemAddEdit: React.FC = () => {
</div>
)}
{/* Dedicated "Add Alias" button - highlighted as core feature */}
{showAddAliasButton && (
<button
type="button"
onClick={handleGenerateAlias}
className="w-full px-4 py-3 bg-primary-50 dark:bg-primary-900/20 border-2 border-primary-300 dark:border-primary-700 text-primary-700 dark:text-primary-300 rounded-lg hover:bg-primary-100 dark:hover:bg-primary-900/30 hover:border-primary-400 dark:hover:border-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 transition-colors flex items-center justify-center gap-2 font-medium"
>
<svg className='w-5 h-5' viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
<circle cx="8" cy="8" r="1"/>
<circle cx="16" cy="8" r="1"/>
<circle cx="12" cy="12" r="1"/>
<circle cx="8" cy="16" r="1"/>
<circle cx="16" cy="16" r="1"/>
</svg>
{t('itemTypes.addAlias')}
</button>
)}
{/* Generic + button with dropdown menu for Notes and Custom Fields */}
<div className="relative">
<button
@@ -1253,7 +1349,7 @@ const ItemAddEdit: React.FC = () => {
<p className="text-sm text-gray-700 dark:text-gray-300">{syncStatus}</p>
</div>
)}
</div>
</form>
);
};

View File

@@ -29,6 +29,15 @@ const ITEM_TYPE_OPTIONS: ItemTypeOption[] = [
</svg>
)
},
{
type: 'Alias',
titleKey: 'itemTypes.alias.title',
iconSvg: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
)
},
{
type: 'CreditCard',
titleKey: 'itemTypes.creditCard.title',
@@ -38,15 +47,6 @@ const ITEM_TYPE_OPTIONS: ItemTypeOption[] = [
</svg>
)
},
{
type: 'Identity',
titleKey: 'itemTypes.identity.title',
iconSvg: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
)
},
{
type: 'Note',
titleKey: 'itemTypes.note.title',

View File

@@ -295,6 +295,7 @@
"typeLabel": "Item Type",
"creating": "Creating",
"generateAlias": "Generate random alias",
"regenerateAlias": "Regenerate alias",
"addAlias": "Add alias",
"addNotes": "Add notes",
"addCustomField": "Add custom field",
@@ -302,14 +303,14 @@
"title": "Login",
"description": "Username, password, and website credentials"
},
"alias": {
"title": "Alias",
"description": "Login with auto-generated email alias and identity"
},
"creditCard": {
"title": "Credit Card",
"description": "Credit card information and payment details"
},
"identity": {
"title": "Identity",
"description": "Personal information and contact details"
},
"note": {
"title": "Secure Note",
"description": "Encrypted notes and private information"

View File

@@ -323,8 +323,12 @@ type ItemTag = {
/**
* Item types supported by the vault
* - Login: Username/password credentials with optional notes
* - Alias: Login with pre-filled alias identity fields (email, name, etc.)
* - CreditCard: Payment card information
* - Note: Secure notes
*/
type ItemType = 'Login' | 'CreditCard' | 'Identity' | 'Note';
type ItemType = 'Login' | 'Alias' | 'CreditCard' | 'Note';
/**
* Item type representing vault entries in the new field-based data model.
* Replaces the old Credential type.
@@ -428,8 +432,8 @@ type SystemFieldDefinition = {
ApplicableToTypes: Partial<Record<ItemType, ItemTypeFieldConfig>>;
/** Whether to track field value history */
EnableHistory: boolean;
/** Category for grouping in UI */
Category: 'Login' | 'Alias' | 'Card' | 'Identity' | 'API' | 'Metadata';
/** Category for grouping in UI. 'Primary' fields are shown in the name block. */
Category: 'Primary' | 'Login' | 'Alias' | 'Card' | 'Metadata';
/** Default display order within category (lower = first) */
DefaultDisplayOrder: number;
};
@@ -437,6 +441,11 @@ type SystemFieldDefinition = {
* Registry of all system-defined fields.
* These fields are immutable and their metadata is defined in code.
* DO NOT modify these definitions without careful consideration of backwards compatibility.
*
* Item Types:
* - Login: Username/password credentials (alias fields optional)
* - Alias: Login with pre-filled alias identity fields shown by default
* - CreditCard: Payment card information
*/
declare const SystemFieldRegistry: Record<string, SystemFieldDefinition>;
/**

View File

@@ -225,7 +225,7 @@ function itemToCredential(item) {
// src/vault/SystemFieldRegistry.ts
var SystemFieldRegistry = {
// Login Fields
/* =================== LOGIN FIELDS =================== */
"login.username": {
FieldKey: "login.username",
Label: "Username",
@@ -233,7 +233,8 @@ var SystemFieldRegistry = {
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: true }
Login: { ShowByDefault: true },
Alias: { ShowByDefault: true }
},
EnableHistory: true,
Category: "Login",
@@ -246,7 +247,8 @@ var SystemFieldRegistry = {
IsHidden: true,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: true }
Login: { ShowByDefault: true },
Alias: { ShowByDefault: true }
},
EnableHistory: true,
Category: "Login",
@@ -259,30 +261,14 @@ var SystemFieldRegistry = {
IsHidden: false,
IsMultiValue: true,
ApplicableToTypes: {
Login: { ShowByDefault: true }
Login: { ShowByDefault: true },
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Login",
DefaultDisplayOrder: 30
Category: "Primary",
DefaultDisplayOrder: 5
},
// Metadata Fields
"login.notes": {
FieldKey: "login.notes",
Label: "Notes",
FieldType: "TextArea",
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
CreditCard: { ShowByDefault: false },
Identity: { ShowByDefault: false },
Note: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Metadata",
DefaultDisplayOrder: 100
},
// Alias Fields
/* =================== ALIAS FIELDS =================== */
"alias.email": {
FieldKey: "alias.email",
Label: "Email",
@@ -290,7 +276,8 @@ var SystemFieldRegistry = {
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false }
Login: { ShowByDefault: false },
Alias: { ShowByDefault: true }
},
EnableHistory: true,
Category: "Alias",
@@ -304,7 +291,7 @@ var SystemFieldRegistry = {
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
Identity: { ShowByDefault: true }
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Alias",
@@ -318,7 +305,7 @@ var SystemFieldRegistry = {
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
Identity: { ShowByDefault: true }
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Alias",
@@ -331,7 +318,8 @@ var SystemFieldRegistry = {
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false }
Login: { ShowByDefault: false },
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Alias",
@@ -345,7 +333,7 @@ var SystemFieldRegistry = {
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
Identity: { ShowByDefault: true }
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Alias",
@@ -359,19 +347,108 @@ var SystemFieldRegistry = {
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
Identity: { ShowByDefault: true }
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Alias",
DefaultDisplayOrder: 60
},
/* =================== CREDIT CARD FIELDS =================== */
"card.cardholder_name": {
FieldKey: "card.cardholder_name",
Label: "Cardholder Name",
FieldType: "Text",
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Card",
DefaultDisplayOrder: 10
},
"card.number": {
FieldKey: "card.number",
Label: "Card Number",
FieldType: "Hidden",
IsHidden: true,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Card",
DefaultDisplayOrder: 20
},
"card.expiry_month": {
FieldKey: "card.expiry_month",
Label: "Expiry Month",
FieldType: "Text",
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Card",
DefaultDisplayOrder: 30
},
"card.expiry_year": {
FieldKey: "card.expiry_year",
Label: "Expiry Year",
FieldType: "Text",
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Card",
DefaultDisplayOrder: 40
},
"card.cvv": {
FieldKey: "card.cvv",
Label: "CVV",
FieldType: "Hidden",
IsHidden: true,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Card",
DefaultDisplayOrder: 50
},
"card.pin": {
FieldKey: "card.pin",
Label: "PIN",
FieldType: "Hidden",
IsHidden: true,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: false }
},
EnableHistory: false,
Category: "Card",
DefaultDisplayOrder: 60
},
/* =================== METADATA FIELDS =================== */
"metadata.notes": {
FieldKey: "metadata.notes",
Label: "Notes",
FieldType: "TextArea",
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
Alias: { ShowByDefault: false },
CreditCard: { ShowByDefault: false },
Note: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Metadata",
DefaultDisplayOrder: 100
}
/*
* Note: Card, Identity, and API fields can be added here when those item types are implemented
* Example:
* 'card.number': { ... },
* 'card.cardholder_name': { ... },
* 'identity.phone_number': { ... },
*/
};
function getSystemField(fieldKey) {
return SystemFieldRegistry[fieldKey];

View File

@@ -323,8 +323,12 @@ type ItemTag = {
/**
* Item types supported by the vault
* - Login: Username/password credentials with optional notes
* - Alias: Login with pre-filled alias identity fields (email, name, etc.)
* - CreditCard: Payment card information
* - Note: Secure notes
*/
type ItemType = 'Login' | 'CreditCard' | 'Identity' | 'Note';
type ItemType = 'Login' | 'Alias' | 'CreditCard' | 'Note';
/**
* Item type representing vault entries in the new field-based data model.
* Replaces the old Credential type.
@@ -428,8 +432,8 @@ type SystemFieldDefinition = {
ApplicableToTypes: Partial<Record<ItemType, ItemTypeFieldConfig>>;
/** Whether to track field value history */
EnableHistory: boolean;
/** Category for grouping in UI */
Category: 'Login' | 'Alias' | 'Card' | 'Identity' | 'API' | 'Metadata';
/** Category for grouping in UI. 'Primary' fields are shown in the name block. */
Category: 'Primary' | 'Login' | 'Alias' | 'Card' | 'Metadata';
/** Default display order within category (lower = first) */
DefaultDisplayOrder: number;
};
@@ -437,6 +441,11 @@ type SystemFieldDefinition = {
* Registry of all system-defined fields.
* These fields are immutable and their metadata is defined in code.
* DO NOT modify these definitions without careful consideration of backwards compatibility.
*
* Item Types:
* - Login: Username/password credentials (alias fields optional)
* - Alias: Login with pre-filled alias identity fields shown by default
* - CreditCard: Payment card information
*/
declare const SystemFieldRegistry: Record<string, SystemFieldDefinition>;
/**

View File

@@ -225,7 +225,7 @@ function itemToCredential(item) {
// src/vault/SystemFieldRegistry.ts
var SystemFieldRegistry = {
// Login Fields
/* =================== LOGIN FIELDS =================== */
"login.username": {
FieldKey: "login.username",
Label: "Username",
@@ -233,7 +233,8 @@ var SystemFieldRegistry = {
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: true }
Login: { ShowByDefault: true },
Alias: { ShowByDefault: true }
},
EnableHistory: true,
Category: "Login",
@@ -246,7 +247,8 @@ var SystemFieldRegistry = {
IsHidden: true,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: true }
Login: { ShowByDefault: true },
Alias: { ShowByDefault: true }
},
EnableHistory: true,
Category: "Login",
@@ -259,30 +261,14 @@ var SystemFieldRegistry = {
IsHidden: false,
IsMultiValue: true,
ApplicableToTypes: {
Login: { ShowByDefault: true }
Login: { ShowByDefault: true },
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Login",
DefaultDisplayOrder: 30
Category: "Primary",
DefaultDisplayOrder: 5
},
// Metadata Fields
"login.notes": {
FieldKey: "login.notes",
Label: "Notes",
FieldType: "TextArea",
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
CreditCard: { ShowByDefault: false },
Identity: { ShowByDefault: false },
Note: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Metadata",
DefaultDisplayOrder: 100
},
// Alias Fields
/* =================== ALIAS FIELDS =================== */
"alias.email": {
FieldKey: "alias.email",
Label: "Email",
@@ -290,7 +276,8 @@ var SystemFieldRegistry = {
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false }
Login: { ShowByDefault: false },
Alias: { ShowByDefault: true }
},
EnableHistory: true,
Category: "Alias",
@@ -304,7 +291,7 @@ var SystemFieldRegistry = {
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
Identity: { ShowByDefault: true }
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Alias",
@@ -318,7 +305,7 @@ var SystemFieldRegistry = {
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
Identity: { ShowByDefault: true }
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Alias",
@@ -331,7 +318,8 @@ var SystemFieldRegistry = {
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false }
Login: { ShowByDefault: false },
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Alias",
@@ -345,7 +333,7 @@ var SystemFieldRegistry = {
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
Identity: { ShowByDefault: true }
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Alias",
@@ -359,19 +347,108 @@ var SystemFieldRegistry = {
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
Identity: { ShowByDefault: true }
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Alias",
DefaultDisplayOrder: 60
},
/* =================== CREDIT CARD FIELDS =================== */
"card.cardholder_name": {
FieldKey: "card.cardholder_name",
Label: "Cardholder Name",
FieldType: "Text",
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Card",
DefaultDisplayOrder: 10
},
"card.number": {
FieldKey: "card.number",
Label: "Card Number",
FieldType: "Hidden",
IsHidden: true,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Card",
DefaultDisplayOrder: 20
},
"card.expiry_month": {
FieldKey: "card.expiry_month",
Label: "Expiry Month",
FieldType: "Text",
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Card",
DefaultDisplayOrder: 30
},
"card.expiry_year": {
FieldKey: "card.expiry_year",
Label: "Expiry Year",
FieldType: "Text",
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Card",
DefaultDisplayOrder: 40
},
"card.cvv": {
FieldKey: "card.cvv",
Label: "CVV",
FieldType: "Hidden",
IsHidden: true,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Card",
DefaultDisplayOrder: 50
},
"card.pin": {
FieldKey: "card.pin",
Label: "PIN",
FieldType: "Hidden",
IsHidden: true,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: false }
},
EnableHistory: false,
Category: "Card",
DefaultDisplayOrder: 60
},
/* =================== METADATA FIELDS =================== */
"metadata.notes": {
FieldKey: "metadata.notes",
Label: "Notes",
FieldType: "TextArea",
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
Alias: { ShowByDefault: false },
CreditCard: { ShowByDefault: false },
Note: { ShowByDefault: true }
},
EnableHistory: false,
Category: "Metadata",
DefaultDisplayOrder: 100
}
/*
* Note: Card, Identity, and API fields can be added here when those item types are implemented
* Example:
* 'card.number': { ... },
* 'card.cardholder_name': { ... },
* 'identity.phone_number': { ... },
*/
};
function getSystemField(fieldKey) {
return SystemFieldRegistry[fieldKey];

View File

@@ -1,10 +1,14 @@
/**
* Item types supported by the vault
* - Login: Username/password credentials with optional notes
* - Alias: Login with pre-filled alias identity fields (email, name, etc.)
* - CreditCard: Payment card information
* - Note: Secure notes
*/
export type ItemType =
| 'Login'
| 'Alias'
| 'CreditCard'
| 'Identity'
| 'Note';
/**

View File

@@ -32,8 +32,8 @@ export type SystemFieldDefinition = {
ApplicableToTypes: Partial<Record<ItemType, ItemTypeFieldConfig>>;
/** Whether to track field value history */
EnableHistory: boolean;
/** Category for grouping in UI */
Category: 'Login' | 'Alias' | 'Card' | 'Identity' | 'API' | 'Metadata';
/** Category for grouping in UI. 'Primary' fields are shown in the name block. */
Category: 'Primary' | 'Login' | 'Alias' | 'Card' | 'Metadata';
/** Default display order within category (lower = first) */
DefaultDisplayOrder: number;
};
@@ -42,9 +42,14 @@ export type SystemFieldDefinition = {
* Registry of all system-defined fields.
* These fields are immutable and their metadata is defined in code.
* DO NOT modify these definitions without careful consideration of backwards compatibility.
*
* Item Types:
* - Login: Username/password credentials (alias fields optional)
* - Alias: Login with pre-filled alias identity fields shown by default
* - CreditCard: Payment card information
*/
export const SystemFieldRegistry: Record<string, SystemFieldDefinition> = {
// Login Fields
/* =================== LOGIN FIELDS =================== */
'login.username': {
FieldKey: 'login.username',
Label: 'Username',
@@ -52,7 +57,8 @@ export const SystemFieldRegistry: Record<string, SystemFieldDefinition> = {
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: true }
Login: { ShowByDefault: true },
Alias: { ShowByDefault: true }
},
EnableHistory: true,
Category: 'Login',
@@ -65,7 +71,8 @@ export const SystemFieldRegistry: Record<string, SystemFieldDefinition> = {
IsHidden: true,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: true }
Login: { ShowByDefault: true },
Alias: { ShowByDefault: true }
},
EnableHistory: true,
Category: 'Login',
@@ -78,32 +85,15 @@ export const SystemFieldRegistry: Record<string, SystemFieldDefinition> = {
IsHidden: false,
IsMultiValue: true,
ApplicableToTypes: {
Login: { ShowByDefault: true }
Login: { ShowByDefault: true },
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: 'Login',
DefaultDisplayOrder: 30
Category: 'Primary',
DefaultDisplayOrder: 5
},
// Metadata Fields
'login.notes': {
FieldKey: 'login.notes',
Label: 'Notes',
FieldType: 'TextArea',
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
CreditCard: { ShowByDefault: false },
Identity: { ShowByDefault: false },
Note: { ShowByDefault: true }
},
EnableHistory: false,
Category: 'Metadata',
DefaultDisplayOrder: 100
},
// Alias Fields
/* =================== ALIAS FIELDS =================== */
'alias.email': {
FieldKey: 'alias.email',
Label: 'Email',
@@ -111,7 +101,8 @@ export const SystemFieldRegistry: Record<string, SystemFieldDefinition> = {
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false }
Login: { ShowByDefault: false },
Alias: { ShowByDefault: true }
},
EnableHistory: true,
Category: 'Alias',
@@ -125,7 +116,7 @@ export const SystemFieldRegistry: Record<string, SystemFieldDefinition> = {
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
Identity: { ShowByDefault: true }
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: 'Alias',
@@ -139,7 +130,7 @@ export const SystemFieldRegistry: Record<string, SystemFieldDefinition> = {
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
Identity: { ShowByDefault: true }
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: 'Alias',
@@ -152,7 +143,8 @@ export const SystemFieldRegistry: Record<string, SystemFieldDefinition> = {
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false }
Login: { ShowByDefault: false },
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: 'Alias',
@@ -166,7 +158,7 @@ export const SystemFieldRegistry: Record<string, SystemFieldDefinition> = {
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
Identity: { ShowByDefault: true }
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: 'Alias',
@@ -180,20 +172,110 @@ export const SystemFieldRegistry: Record<string, SystemFieldDefinition> = {
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
Identity: { ShowByDefault: true }
Alias: { ShowByDefault: true }
},
EnableHistory: false,
Category: 'Alias',
DefaultDisplayOrder: 60
},
/*
* Note: Card, Identity, and API fields can be added here when those item types are implemented
* Example:
* 'card.number': { ... },
* 'card.cardholder_name': { ... },
* 'identity.phone_number': { ... },
*/
/* =================== CREDIT CARD FIELDS =================== */
'card.cardholder_name': {
FieldKey: 'card.cardholder_name',
Label: 'Cardholder Name',
FieldType: 'Text',
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: true }
},
EnableHistory: false,
Category: 'Card',
DefaultDisplayOrder: 10
},
'card.number': {
FieldKey: 'card.number',
Label: 'Card Number',
FieldType: 'Hidden',
IsHidden: true,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: true }
},
EnableHistory: false,
Category: 'Card',
DefaultDisplayOrder: 20
},
'card.expiry_month': {
FieldKey: 'card.expiry_month',
Label: 'Expiry Month',
FieldType: 'Text',
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: true }
},
EnableHistory: false,
Category: 'Card',
DefaultDisplayOrder: 30
},
'card.expiry_year': {
FieldKey: 'card.expiry_year',
Label: 'Expiry Year',
FieldType: 'Text',
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: true }
},
EnableHistory: false,
Category: 'Card',
DefaultDisplayOrder: 40
},
'card.cvv': {
FieldKey: 'card.cvv',
Label: 'CVV',
FieldType: 'Hidden',
IsHidden: true,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: true }
},
EnableHistory: false,
Category: 'Card',
DefaultDisplayOrder: 50
},
'card.pin': {
FieldKey: 'card.pin',
Label: 'PIN',
FieldType: 'Hidden',
IsHidden: true,
IsMultiValue: false,
ApplicableToTypes: {
CreditCard: { ShowByDefault: false }
},
EnableHistory: false,
Category: 'Card',
DefaultDisplayOrder: 60
},
/* =================== METADATA FIELDS =================== */
'metadata.notes': {
FieldKey: 'metadata.notes',
Label: 'Notes',
FieldType: 'TextArea',
IsHidden: false,
IsMultiValue: false,
ApplicableToTypes: {
Login: { ShowByDefault: false },
Alias: { ShowByDefault: false },
CreditCard: { ShowByDefault: false },
Note: { ShowByDefault: true }
},
EnableHistory: false,
Category: 'Metadata',
DefaultDisplayOrder: 100
}
};
/**