mashlib 2.1.4-test.2 → 2.1.4-test.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mashlib.js CHANGED
@@ -13886,40 +13886,38 @@ var ___CSS_LOADER_URL_IMPORT_0___ = new URL(/* asset import */ __nested_webpack_
13886
13886
  var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));
13887
13887
  var ___CSS_LOADER_URL_REPLACEMENT_0___ = _node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2___default()(___CSS_LOADER_URL_IMPORT_0___);
13888
13888
  // Module
13889
- ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessibility */
13890
- .visually-hidden {
13891
- position: absolute !important;
13892
- height: 1px; width: 1px;
13893
- overflow: hidden;
13894
- clip: rect(1px, 1px, 1px, 1px);
13895
- white-space: nowrap;
13896
- border: 0;
13897
- padding: 0;
13898
- margin: -1px;
13899
- }
13900
-
13901
- /* Focus indicator for keyboard navigation */
13902
- .personRow:focus, .dataCell:focus, tr[tabindex="0"]:focus {
13889
+ ___CSS_LOADER_EXPORT___.push([module.id, `/* Focus indicator for keyboard navigation */
13890
+ .contactPane table tr[tabindex="0"]:focus {
13903
13891
  outline: var(--focus-ring-width) solid var(--color-primary);
13904
13892
  outline-offset: 2px;
13905
- background: #e3f2fd;
13893
+ background: var(--color-info-bg);
13906
13894
  }
13907
13895
  /* contactsPane styles — extracted from inline styles in contactsPane.js */
13908
13896
  /* Uses CSS custom properties from the global stylesheet (dev-global.css / mashlib) */
13909
13897
 
13910
13898
  /* ── Layout: Three-column browser ────────────────────────────── */
13911
13899
 
13900
+ .contactPane .peopleSection .selected {
13901
+ background-color: var(--color-info-bg) !important;
13902
+ }
13903
+
13912
13904
  .contactPane .detailSection {
13913
13905
  flex: 1 1 400px;
13914
13906
  min-width: 300px;
13915
- overflow-y: auto;
13916
- background: var(--color-section-bg);
13917
13907
  box-sizing: border-box;
13908
+ background: var(--color-section-bg);
13918
13909
  }
13919
13910
 
13920
13911
  .contactPane .detailsSectionContent {
13921
13912
  min-height: 200px;
13922
13913
  padding: var(--spacing-lg);
13914
+ max-width: 900px;
13915
+ width: 100%;
13916
+ box-sizing: border-box;
13917
+ }
13918
+
13919
+ .contactPane .detailsSectionContent--wide {
13920
+ max-width: 900px;
13923
13921
  }
13924
13922
 
13925
13923
  .contactPane .cardFooter {
@@ -13930,19 +13928,6 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
13930
13928
  margin-top: var(--spacing-md);
13931
13929
  }
13932
13930
 
13933
- .contactPane .groupsHeader {
13934
- min-width: 10em;
13935
- padding-bottom: 0.2em;
13936
- }
13937
-
13938
- .contactPane .peopleHeader {
13939
- min-width: 18em;
13940
- }
13941
-
13942
- .contactPane .peopleMain {
13943
- overflow: scroll;
13944
- }
13945
-
13946
13931
  .contactPane .detailsSectionContent {
13947
13932
  margin: 0;
13948
13933
  }
@@ -13957,28 +13942,23 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
13957
13942
  }
13958
13943
 
13959
13944
  .contactPane .contactTypeChooser h3 {
13960
- margin: 0 0 4px 0;
13961
- font-size: 1.1em;
13945
+ margin: 0 0 var(--spacing-xs) 0;
13946
+ font-size: var(--font-size-lg);
13962
13947
  }
13963
13948
 
13964
13949
  .contactPane .contactTypeSelect {
13965
- height: 38px;
13950
+ height: var(--min-touch-target);
13966
13951
  border: 1px solid var(--color-border-pale);
13967
13952
  border-radius: var(--border-radius-base);
13968
- padding: 0 10px;
13953
+ padding: 0 var(--spacing-sm);
13969
13954
  font-size: var(--font-size-sm);
13970
13955
  background: var(--color-section-bg);
13971
13956
  }
13972
13957
 
13973
- .contactPane .groupsMain,
13974
- .contactPane .groupsFooter {
13975
- padding: 0.1em;
13976
- }
13977
-
13978
13958
  /* ── Search ──────────────────────────────────────────────────── */
13979
13959
 
13980
13960
  .contactPane .allGroupsButton {
13981
- border-radius: 22px !important;
13961
+ border-radius: var(--border-radius-full) !important;
13982
13962
  /* existing styles */
13983
13963
  }
13984
13964
  .contactPane .searchInput {
@@ -13996,12 +13976,6 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
13996
13976
  box-sizing: border-box;
13997
13977
  }
13998
13978
 
13999
- /* ── Data cells ──────────────────────────────────────────────── */
14000
-
14001
- .contactPane .dataCell {
14002
- padding: 0.1em;
14003
- }
14004
-
14005
13979
  /* ── Contact toolbar (top-right link + delete) ──────────────── */
14006
13980
 
14007
13981
  .contactPane .contact-toolbar {
@@ -14018,11 +13992,6 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
14018
13992
  margin: 0;
14019
13993
  }
14020
13994
 
14021
- /* ── Delete button ───────────────────────────────────────────── */
14022
-
14023
- .contactPane .deleteButton {
14024
- }
14025
-
14026
13995
  /* ── "All" groups button ─────────────────────────────────────── */
14027
13996
 
14028
13997
  .contactPane .allGroupsButton {
@@ -14046,7 +14015,6 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
14046
14015
  /* ── Selection & visibility states ───────────────────────────── */
14047
14016
 
14048
14017
  .contactPane .group-loading {
14049
- background-color: #ffe;
14050
14018
  }
14051
14019
 
14052
14020
  .contactPane .hidden {
@@ -14056,45 +14024,55 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
14056
14024
  /* ── Mint new address book ───────────────────────────────────── */
14057
14025
 
14058
14026
  .contactPane .claimSuccess {
14059
- font-size: 140%;
14027
+ font-size: var(--font-size-xl);
14028
+ }
14029
+
14030
+ .contactPane {
14031
+ display: flex;
14032
+ flex-direction: column;
14033
+ min-height: 100vh;
14060
14034
  }
14061
14035
 
14062
14036
  .contactPane .addressBook-grid {
14063
14037
  display: flex;
14064
14038
  flex-wrap: wrap;
14039
+ flex: 1;
14065
14040
  min-width: 50%;
14066
14041
  align-items: stretch;
14042
+ width: 100%;
14043
+ box-sizing: border-box;
14067
14044
  }
14068
14045
 
14069
14046
  .contactPane .addressBookSection {
14070
14047
  flex: 1 1 350px;
14071
- max-width: 450px;
14048
+ max-width: 485px;
14072
14049
  box-sizing: border-box;
14073
- margin-bottom: var(--spacing-md);
14074
14050
  }
14075
14051
 
14076
- @media (min-width: 900px) {
14052
+ @media ((min-width: 500px) and (max-width: 900px)) {
14077
14053
  .contactPane .addressBookSection {
14078
- margin-bottom: 0;
14054
+ max-width: 900px;
14055
+ }
14056
+ .contactPane .addressBookSection section {
14057
+ max-width: 485px;
14079
14058
  }
14080
14059
  }
14081
14060
 
14082
- /* Section Title - primary colored heading */
14083
- .contactPane .section-title {
14084
- font-size: var(--font-size-xl);
14085
- font-weight: 600;
14086
- color: var(--color-primary);
14087
- margin: 0;
14061
+ @media (max-width: 500px) {
14062
+ .contactPane .addressBookSection {
14063
+ max-width: 485px;
14064
+ }
14088
14065
  }
14089
14066
 
14090
14067
  /* Card Section Background */
14091
14068
  .contactPane .section-bg {
14092
14069
  background: var(--color-section-bg);
14093
14070
  padding: var(--spacing-md);
14071
+ box-sizing: border-box;
14094
14072
  }
14095
14073
 
14096
14074
  /* Primary Button */
14097
- .btn-primary {
14075
+ .contactPane .btn-primary {
14098
14076
  min-height: var(--min-touch-target);
14099
14077
  padding: var(--spacing-sm) var(--spacing-md);
14100
14078
  border: 1px solid var(--color-primary);
@@ -14106,23 +14084,23 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
14106
14084
  transition: all var(--animation-duration) ease;
14107
14085
  }
14108
14086
 
14109
- .btn-primary:hover {
14087
+ .contactPane .btn-primary:hover {
14110
14088
  background: color-mix(in srgb, var(--color-primary) 90%, black);
14111
14089
  box-shadow: 0 2px 4px rgba(124, 77, 255, 0.2);
14112
14090
  }
14113
14091
 
14114
- .btn-primary:active {
14092
+ .contactPane .btn-primary:active {
14115
14093
  box-shadow: 0 1px 2px rgba(124, 77, 255, 0.2);
14116
14094
  }
14117
14095
 
14118
- .btn-primary:disabled {
14119
- opacity: 0.6;
14096
+ .contactPane .btn-primary:disabled {
14097
+ opacity: var(--opacity-disabled, 0.6);
14120
14098
  cursor: not-allowed;
14121
14099
  transform: none;
14122
14100
  }
14123
14101
 
14124
14102
  /* Secondary button */
14125
- .btn-secondary {
14103
+ .contactPane .btn-secondary {
14126
14104
  min-height: var(--min-touch-target);
14127
14105
  padding: var(--spacing-sm) var(--spacing-md);
14128
14106
  border: var(--border-width-thin) solid var(--color-secondary);
@@ -14138,18 +14116,18 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
14138
14116
  justify-content: center;
14139
14117
  }
14140
14118
 
14141
- .btn-secondary:hover {
14119
+ .contactPane .btn-secondary:hover {
14142
14120
  background: color-mix(in srgb, var(--color-secondary) 85%, black);
14143
14121
  }
14144
14122
 
14145
- .btn-secondary:disabled {
14123
+ .contactPane .btn-secondary:disabled {
14146
14124
  opacity: var(--opacity-disabled);
14147
14125
  cursor: not-allowed;
14148
14126
  }
14149
14127
 
14150
14128
  /* Action Button Focus - used by ChatWithMe, ProfileCard */
14151
- .action-button-focus:focus,
14152
- .action-button-focus:focus-visible {
14129
+ .contactPane .action-button-focus:focus,
14130
+ .contactPane .action-button-focus:focus-visible {
14153
14131
  outline: 3px solid var(--color-primary) !important;
14154
14132
  outline-offset: 2px !important;
14155
14133
  box-shadow: 0 0 0 2px var(--color-background), 0 0 0 5px rgba(124, 77, 255, 0.25) !important;
@@ -14177,7 +14155,7 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
14177
14155
 
14178
14156
  .contactPane .buttonSection::-webkit-scrollbar-thumb {
14179
14157
  background: var(--color-border-pale);
14180
- border-radius: 3px;
14158
+ border-radius: var(--border-radius-base);
14181
14159
  }
14182
14160
 
14183
14161
  .contactPane .buttonSection::-webkit-scrollbar-track {
@@ -14193,9 +14171,9 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
14193
14171
  }
14194
14172
 
14195
14173
  .contactPane .buttonSection .groupButtonsList {
14196
- margin-left: 4px;
14197
- margin-right: 4px;
14198
- padding-left: 0px;
14174
+ margin-left: var(--spacing-xs);
14175
+ margin-right: var(--spacing-xs);
14176
+ padding-left: 0;
14199
14177
  }
14200
14178
 
14201
14179
  .contactPane .groupButtonsList li {
@@ -14206,77 +14184,73 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
14206
14184
  white-space: nowrap;
14207
14185
  flex-shrink: 0;
14208
14186
  min-width: max-content;
14209
- margin-left: 0px;
14187
+ margin-left: 0;
14210
14188
  }
14211
14189
 
14212
14190
  /* Groups list in details section — flexible 2-column grid */
14213
14191
  .contactPane .detailsSectionContent .groupButtonsList {
14214
14192
  display: grid;
14215
- grid-template-columns: repeat(2, 1fr);
14193
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
14216
14194
  gap: var(--spacing-sm);
14217
14195
  list-style: none;
14218
14196
  padding: 0;
14219
- max-width: 512px;
14197
+ width: 100%;
14198
+ box-sizing: border-box;
14220
14199
  }
14221
14200
 
14222
14201
  .contactPane .detailsSectionContent .groupButtonsList li {
14223
14202
  width: 100%;
14224
14203
  aspect-ratio: 1 / 1;
14225
- max-width: 250px;
14226
14204
  }
14227
14205
 
14228
14206
  .contactPane .detailsSectionContent .groupButtonsList button {
14229
14207
  width: 100%;
14230
14208
  height: 100%;
14231
14209
  text-align: center;
14232
- border-radius: 12px;
14210
+ border-radius: var(--border-radius-base);
14233
14211
  word-wrap: break-word;
14234
14212
  overflow-wrap: break-word;
14235
14213
  }
14236
14214
 
14237
- @media (max-width: 899px) {
14215
+ @media (max-width: 599px) {
14238
14216
  .contactPane .detailsSectionContent .groupButtonsList {
14239
- grid-template-columns: repeat(3, 1fr);
14217
+ grid-template-columns: repeat(2, 1fr);
14240
14218
  gap: var(--spacing-xs);
14241
- max-width: 100%;
14242
- }
14243
-
14244
- .contactPane .detailsSectionContent .groupButtonsList li {
14245
- max-width: 120px;
14246
14219
  }
14247
14220
 
14248
14221
  .contactPane .detailsSectionContent .groupButtonsList button {
14249
- font-size: 0.8rem;
14250
- border-radius: 8px;
14251
- }
14252
-
14253
- .contactPane .detailsSectionContent .newGroupBtn {
14254
- max-width: 100%;
14222
+ font-size: var(--font-size-sm);
14223
+ border-radius: var(--border-radius-base);
14255
14224
  }
14225
+ }
14256
14226
 
14257
- .contactPane .detailsSectionContent .newGroupBtn {
14258
- max-width: 383px !important;
14227
+ @media (min-width: 900px) {
14228
+ .contactPane .detailsSectionContent .groupButtonsList {
14229
+ grid-template-columns: repeat(3, 1fr);
14259
14230
  }
14260
14231
  }
14261
14232
 
14262
14233
  .contactPane .detailsSectionContent .newGroupBtn {
14263
14234
  width: 100%;
14264
- max-width: 512px;
14265
14235
  box-sizing: border-box;
14266
14236
  margin-top: var(--spacing-sm);
14267
14237
  }
14268
14238
 
14269
14239
  .contactPane .detailsSectionContent h3 {
14270
- font-size: 1.5rem;
14240
+ font-size: var(--font-size-xl);
14271
14241
  margin-bottom: var(--spacing-sm);
14242
+ padding-left: 0;
14272
14243
  }
14273
14244
 
14274
- /* Delete confirmation popup — centered overlay in details section */
14245
+ /* Delete confirmation POPUP — centered overlay in details section */
14275
14246
  .contactPane .detailSection {
14276
14247
  position: relative;
14277
14248
  }
14278
14249
 
14279
- .contactPane .detailsSectionContent .groupButtonsList li > div[style*="position: relative"] {
14250
+ .contactPane .webidControl .personaRow--webid td > div[style*="position: relative"],
14251
+ .contactPane .detailsSectionContent .groupButtonsList li > div[style*="position: relative"],
14252
+ .contactPane .detailsSectionContent .contact-toolbar > div[style*="position: relative"],
14253
+ .contactPane .detailsSectionContent .group-membership-toolbar > div[style*="position: relative"] {
14280
14254
  position: absolute !important;
14281
14255
  top: 0 !important;
14282
14256
  left: 0 !important;
@@ -14290,8 +14264,10 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
14290
14264
  background: rgba(0, 0, 0, 0.3);
14291
14265
  z-index: 1000;
14292
14266
  }
14293
-
14294
- .contactPane .detailsSectionContent .groupButtonsList li > div[style*="position: relative"] > div {
14267
+ .contactPane .webidControl .personaRow--webid td > div[style*="position: relative"] > div,
14268
+ .contactPane .detailsSectionContent .groupButtonsList li > div[style*="position: relative"] > div,
14269
+ .contactPane .detailsSectionContent .contact-toolbar > div[style*="position: relative"] > div,
14270
+ .contactPane .detailsSectionContent .group-membership-toolbar > div[style*="position: relative"] > div {
14295
14271
  position: relative !important;
14296
14272
  top: auto !important;
14297
14273
  background: var(--color-background);
@@ -14336,10 +14312,6 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
14336
14312
  margin: 0;
14337
14313
  }
14338
14314
 
14339
- .contactPane .dottedHr--thin {
14340
- border-top-width: 0.5px;
14341
- }
14342
-
14343
14315
  /* ── Search section ─────────────────────────────────────────── */
14344
14316
 
14345
14317
  .contactPane .searchSection {
@@ -14368,7 +14340,7 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
14368
14340
 
14369
14341
  .contactPane .peopleSection li {
14370
14342
  border-top: 1px solid var(--color-border-pale);
14371
- padding: 5px;
14343
+ padding: var(--spacing-xs);
14372
14344
  }
14373
14345
 
14374
14346
  /* ── Person list item (addressBookPresenter) ─────────────────── */
@@ -14405,13 +14377,13 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
14405
14377
 
14406
14378
  .contactPane .personLi-info {
14407
14379
  flex: 1;
14408
- margin-left: 12px;
14380
+ margin-left: var(--spacing-sm);
14409
14381
  overflow: hidden;
14410
14382
  }
14411
14383
 
14412
14384
  .contactPane .personLi-name {
14413
14385
  font-weight: bold;
14414
- font-size: 1rem;
14386
+ font-size: var(--font-size-base);
14415
14387
  white-space: nowrap;
14416
14388
  overflow: hidden;
14417
14389
  text-overflow: ellipsis;
@@ -14421,7 +14393,7 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
14421
14393
  margin-left: auto;
14422
14394
  display: flex;
14423
14395
  align-items: center;
14424
- }`, "",{"version":3,"sources":["webpack://./src/styles/contactsPane.css"],"names":[],"mappings":"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","sourcesContent":["/* Visually hidden utility for accessibility */\n.visually-hidden {\n position: absolute !important;\n height: 1px; width: 1px;\n overflow: hidden;\n clip: rect(1px, 1px, 1px, 1px);\n white-space: nowrap;\n border: 0;\n padding: 0;\n margin: -1px;\n}\n\n/* Focus indicator for keyboard navigation */\n.personRow:focus, .dataCell:focus, tr[tabindex=\"0\"]:focus {\n outline: var(--focus-ring-width) solid var(--color-primary);\n outline-offset: 2px;\n background: #e3f2fd;\n}\n/* contactsPane styles — extracted from inline styles in contactsPane.js */\n/* Uses CSS custom properties from the global stylesheet (dev-global.css / mashlib) */\n\n/* ── Layout: Three-column browser ────────────────────────────── */\n\n.contactPane .detailSection {\n flex: 1 1 400px;\n min-width: 300px;\n overflow-y: auto;\n background: var(--color-section-bg);\n box-sizing: border-box;\n}\n\n.contactPane .detailsSectionContent {\n min-height: 200px;\n padding: var(--spacing-lg);\n}\n\n.contactPane .cardFooter {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-xs);\n padding-top: var(--spacing-md);\n margin-top: var(--spacing-md);\n}\n\n.contactPane .groupsHeader {\n min-width: 10em;\n padding-bottom: 0.2em;\n}\n\n.contactPane .peopleHeader {\n min-width: 18em;\n}\n\n.contactPane .peopleMain {\n overflow: scroll;\n}\n\n.contactPane .detailsSectionContent {\n margin: 0;\n}\n\n/* ── Contact type chooser ───────────────────────────────────── */\n\n.contactPane .contactTypeChooser {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-sm);\n max-width: 360px;\n}\n\n.contactPane .contactTypeChooser h3 {\n margin: 0 0 4px 0;\n font-size: 1.1em;\n}\n\n.contactPane .contactTypeSelect {\n height: 38px;\n border: 1px solid var(--color-border-pale);\n border-radius: var(--border-radius-base);\n padding: 0 10px;\n font-size: var(--font-size-sm);\n background: var(--color-section-bg);\n}\n\n.contactPane .groupsMain,\n.contactPane .groupsFooter {\n padding: 0.1em;\n}\n\n/* ── Search ──────────────────────────────────────────────────── */\n\n.contactPane .allGroupsButton {\n border-radius: 22px !important;\n /* existing styles */\n}\n.contactPane .searchInput {\n height: var(--min-touch-target);\n border: 1px solid var(--color-border-pale);\n background-color: var(--color-section-bg);\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23999' viewBox='0 0 24 24' width='20' height='20'%3E%3Cpath d='M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99c.41.41 1.09.41 1.5 0s.41-1.09 0-1.5l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: 8px center;\n background-size: 20px 20px;\n border-radius: var(--border-radius-base);\n padding: 0 var(--spacing-sm) 0 34px;\n font-size: var(--font-size-base);\n width: 100%;\n box-sizing: border-box;\n}\n\n/* ── Data cells ──────────────────────────────────────────────── */\n\n.contactPane .dataCell {\n padding: 0.1em;\n}\n\n/* ── Contact toolbar (top-right link + delete) ──────────────── */\n\n.contactPane .contact-toolbar {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n gap: var(--spacing-sm);\n padding: var(--spacing-xs) 0;\n}\n\n.contactPane .contact-toolbar a img {\n width: 1.3em;\n height: 1em;\n margin: 0;\n}\n\n/* ── Delete button ───────────────────────────────────────────── */\n\n.contactPane .deleteButton {\n}\n\n/* ── \"All\" groups button ─────────────────────────────────────── */\n\n.contactPane .allGroupsButton {\n margin-left: var(--spacing-md);\n font-size: var(--font-size-base);\n}\n\n.contactPane .allGroupsButton--loading {\n background-color: var(--color-primary);\n}\n\n.contactPane .allGroupsButton--active {\n background-color: var(--color-primary);\n color: var(--color-background);\n}\n\n.contactPane .allGroupsButton--loaded {\n background-color: var(--color-primary);\n}\n\n/* ── Selection & visibility states ───────────────────────────── */\n\n.contactPane .group-loading {\n background-color: #ffe;\n}\n\n.contactPane .hidden {\n display: none;\n}\n\n/* ── Mint new address book ───────────────────────────────────── */\n\n.contactPane .claimSuccess {\n font-size: 140%;\n}\n\n.contactPane .addressBook-grid {\n display: flex;\n flex-wrap: wrap;\n min-width: 50%;\n align-items: stretch;\n}\n\n.contactPane .addressBookSection {\n flex: 1 1 350px;\n max-width: 450px;\n box-sizing: border-box;\n margin-bottom: var(--spacing-md);\n}\n\n@media (min-width: 900px) {\n .contactPane .addressBookSection {\n margin-bottom: 0;\n }\n}\n\n/* Section Title - primary colored heading */\n.contactPane .section-title {\n font-size: var(--font-size-xl);\n font-weight: 600;\n color: var(--color-primary);\n margin: 0;\n}\n\n/* Card Section Background */\n.contactPane .section-bg {\n background: var(--color-section-bg);\n padding: var(--spacing-md);\n}\n\n/* Primary Button */\n.btn-primary {\n min-height: var(--min-touch-target);\n padding: var(--spacing-sm) var(--spacing-md);\n border: 1px solid var(--color-primary);\n border-radius: var(--border-radius-base);\n background: var(--color-primary);\n color: var(--color-background);\n font-weight: 600;\n cursor: pointer;\n transition: all var(--animation-duration) ease;\n}\n\n.btn-primary:hover {\n background: color-mix(in srgb, var(--color-primary) 90%, black);\n box-shadow: 0 2px 4px rgba(124, 77, 255, 0.2);\n}\n\n.btn-primary:active {\n box-shadow: 0 1px 2px rgba(124, 77, 255, 0.2);\n}\n\n.btn-primary:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n transform: none;\n}\n\n/* Secondary button */\n.btn-secondary {\n min-height: var(--min-touch-target);\n padding: var(--spacing-sm) var(--spacing-md);\n border: var(--border-width-thin) solid var(--color-secondary);\n border-radius: var(--border-radius-base);\n background: var(--color-secondary);\n color: var(--color-background);\n font-weight: var(--font-weight-bold);\n cursor: pointer;\n transition: all var(--animation-duration) ease;\n text-decoration: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.btn-secondary:hover {\n background: color-mix(in srgb, var(--color-secondary) 85%, black);\n}\n\n.btn-secondary:disabled {\n opacity: var(--opacity-disabled);\n cursor: not-allowed;\n}\n\n/* Action Button Focus - used by ChatWithMe, ProfileCard */\n.action-button-focus:focus,\n.action-button-focus:focus-visible {\n outline: 3px solid var(--color-primary) !important;\n outline-offset: 2px !important;\n box-shadow: 0 0 0 2px var(--color-background), 0 0 0 5px rgba(124, 77, 255, 0.25) !important;\n z-index: 1;\n}\n\n/* ── Button section: horizontal scrollable row ──────────────── */\n\n.contactPane .buttonSection {\n display: flex;\n flex-wrap: nowrap;\n align-items: center;\n padding: var(--spacing-sm);\n padding-bottom: 0;\n overflow-x: auto;\n overflow-y: hidden;\n -webkit-overflow-scrolling: touch;\n scrollbar-width: thin;\n margin-bottom: 0;\n}\n\n.contactPane .buttonSection::-webkit-scrollbar {\n height: 6px;\n}\n\n.contactPane .buttonSection::-webkit-scrollbar-thumb {\n background: var(--color-border-pale);\n border-radius: 3px;\n}\n\n.contactPane .buttonSection::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.contactPane .groupButtonsList {\n display: flex;\n flex-wrap: nowrap;\n align-items: center;\n gap: var(--spacing-xs);\n list-style: none;\n}\n\n.contactPane .buttonSection .groupButtonsList {\n margin-left: 4px;\n margin-right: 4px;\n padding-left: 0px;\n}\n\n.contactPane .groupButtonsList li {\n flex-shrink: 0;\n}\n\n.contactPane .groupButtonsList button {\n white-space: nowrap;\n flex-shrink: 0;\n min-width: max-content;\n margin-left: 0px;\n}\n\n/* Groups list in details section — flexible 2-column grid */\n.contactPane .detailsSectionContent .groupButtonsList {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: var(--spacing-sm);\n list-style: none;\n padding: 0;\n max-width: 512px;\n}\n\n.contactPane .detailsSectionContent .groupButtonsList li {\n width: 100%;\n aspect-ratio: 1 / 1;\n max-width: 250px;\n}\n\n.contactPane .detailsSectionContent .groupButtonsList button {\n width: 100%;\n height: 100%;\n text-align: center;\n border-radius: 12px;\n word-wrap: break-word;\n overflow-wrap: break-word;\n}\n\n@media (max-width: 899px) {\n .contactPane .detailsSectionContent .groupButtonsList {\n grid-template-columns: repeat(3, 1fr);\n gap: var(--spacing-xs);\n max-width: 100%;\n }\n\n .contactPane .detailsSectionContent .groupButtonsList li {\n max-width: 120px;\n }\n\n .contactPane .detailsSectionContent .groupButtonsList button {\n font-size: 0.8rem;\n border-radius: 8px;\n }\n\n .contactPane .detailsSectionContent .newGroupBtn {\n max-width: 100%;\n }\n\n .contactPane .detailsSectionContent .newGroupBtn {\n max-width: 383px !important;\n }\n}\n\n.contactPane .detailsSectionContent .newGroupBtn {\n width: 100%;\n max-width: 512px;\n box-sizing: border-box;\n margin-top: var(--spacing-sm);\n}\n\n.contactPane .detailsSectionContent h3 {\n font-size: 1.5rem;\n margin-bottom: var(--spacing-sm);\n}\n\n/* Delete confirmation popup — centered overlay in details section */\n.contactPane .detailSection {\n position: relative;\n}\n\n.contactPane .detailsSectionContent .groupButtonsList li > div[style*=\"position: relative\"] {\n position: absolute !important;\n top: 0 !important;\n left: 0 !important;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.3);\n z-index: 1000;\n}\n\n.contactPane .detailsSectionContent .groupButtonsList li > div[style*=\"position: relative\"] > div {\n position: relative !important;\n top: auto !important;\n background: var(--color-background);\n border-radius: var(--border-radius-full);\n padding: var(--spacing-lg);\n box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);\n z-index: 1001;\n}\n\n/* Selected state for All contacts button */\n.contactPane .allGroupsButton--selected {\n background-color: var(--color-primary);\n color: var(--color-background);\n}\n\n/* ── Header section ──────────────────────────────────────────── */\n\n.contactPane .headerSection {\n background: var(--color-background);\n padding: var(--spacing-sm);\n border-top-left-radius: var(--border-radius-full);\n border-top-right-radius: var(--border-radius-full);\n margin-bottom: 0;\n}\n\n.contactPane .headerSection header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 0;\n}\n\n.contactPane .headerSection h2 {\n margin-bottom: 0;\n}\n\n/* ── Dotted horizontal rule ─────────────────────────────────── */\n\n.contactPane .dottedHr {\n border: none;\n border-top: 1px dotted var(--color-text-muted);\n margin: 0;\n}\n\n.contactPane .dottedHr--thin {\n border-top-width: 0.5px;\n}\n\n/* ── Search section ─────────────────────────────────────────── */\n\n.contactPane .searchSection {\n padding: var(--spacing-sm);\n padding-bottom: 0;\n margin-bottom: 0;\n}\n\n/* ── People list section ────────────────────────────────────── */\n\n.contactPane .peopleSection {\n display: flex;\n background: var(--color-background);\n border-top: 1px dotted var(--color-text-muted);\n margin-bottom: 0;\n}\n\n.contactPane .peopleSection ul {\n list-style: none;\n padding: 0;\n margin: 0;\n width: 100%;\n max-height: 70vh;\n overflow-y: auto;\n}\n\n.contactPane .peopleSection li {\n border-top: 1px solid var(--color-border-pale);\n padding: 5px;\n}\n\n/* ── Person list item (addressBookPresenter) ─────────────────── */\n\n.contactPane .personLi-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n.contactPane .personLi-avatar {\n width: 45px;\n height: 45px;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.contactPane .personLi-avatar .avatar-placeholder {\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.contactPane .personLi-avatar img {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n object-fit: cover;\n}\n\n.contactPane .personLi-info {\n flex: 1;\n margin-left: 12px;\n overflow: hidden;\n}\n\n.contactPane .personLi-name {\n font-weight: bold;\n font-size: 1rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.contactPane .personLi-arrow {\n margin-left: auto;\n display: flex;\n align-items: center;\n}"],"sourceRoot":""}]);
14396
+ }`, "",{"version":3,"sources":["webpack://./src/styles/contactsPane.css"],"names":[],"mappings":"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","sourcesContent":["/* Focus indicator for keyboard navigation */\n.contactPane table tr[tabindex=\"0\"]:focus {\n outline: var(--focus-ring-width) solid var(--color-primary);\n outline-offset: 2px;\n background: var(--color-info-bg);\n}\n/* contactsPane styles — extracted from inline styles in contactsPane.js */\n/* Uses CSS custom properties from the global stylesheet (dev-global.css / mashlib) */\n\n/* ── Layout: Three-column browser ────────────────────────────── */\n\n.contactPane .peopleSection .selected {\n background-color: var(--color-info-bg) !important;\n}\n\n.contactPane .detailSection {\n flex: 1 1 400px;\n min-width: 300px;\n box-sizing: border-box;\n background: var(--color-section-bg);\n}\n\n.contactPane .detailsSectionContent {\n min-height: 200px;\n padding: var(--spacing-lg);\n max-width: 900px;\n width: 100%;\n box-sizing: border-box;\n}\n\n.contactPane .detailsSectionContent--wide {\n max-width: 900px;\n}\n\n.contactPane .cardFooter {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-xs);\n padding-top: var(--spacing-md);\n margin-top: var(--spacing-md);\n}\n\n.contactPane .detailsSectionContent {\n margin: 0;\n}\n\n/* ── Contact type chooser ───────────────────────────────────── */\n\n.contactPane .contactTypeChooser {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-sm);\n max-width: 360px;\n}\n\n.contactPane .contactTypeChooser h3 {\n margin: 0 0 var(--spacing-xs) 0;\n font-size: var(--font-size-lg);\n}\n\n.contactPane .contactTypeSelect {\n height: var(--min-touch-target);\n border: 1px solid var(--color-border-pale);\n border-radius: var(--border-radius-base);\n padding: 0 var(--spacing-sm);\n font-size: var(--font-size-sm);\n background: var(--color-section-bg);\n}\n\n/* ── Search ──────────────────────────────────────────────────── */\n\n.contactPane .allGroupsButton {\n border-radius: var(--border-radius-full) !important;\n /* existing styles */\n}\n.contactPane .searchInput {\n height: var(--min-touch-target);\n border: 1px solid var(--color-border-pale);\n background-color: var(--color-section-bg);\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23999' viewBox='0 0 24 24' width='20' height='20'%3E%3Cpath d='M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99c.41.41 1.09.41 1.5 0s.41-1.09 0-1.5l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: 8px center;\n background-size: 20px 20px;\n border-radius: var(--border-radius-base);\n padding: 0 var(--spacing-sm) 0 34px;\n font-size: var(--font-size-base);\n width: 100%;\n box-sizing: border-box;\n}\n\n/* ── Contact toolbar (top-right link + delete) ──────────────── */\n\n.contactPane .contact-toolbar {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n gap: var(--spacing-sm);\n padding: var(--spacing-xs) 0;\n}\n\n.contactPane .contact-toolbar a img {\n width: 1.3em;\n height: 1em;\n margin: 0;\n}\n\n/* ── \"All\" groups button ─────────────────────────────────────── */\n\n.contactPane .allGroupsButton {\n margin-left: var(--spacing-md);\n font-size: var(--font-size-base);\n}\n\n.contactPane .allGroupsButton--loading {\n background-color: var(--color-primary);\n}\n\n.contactPane .allGroupsButton--active {\n background-color: var(--color-primary);\n color: var(--color-background);\n}\n\n.contactPane .allGroupsButton--loaded {\n background-color: var(--color-primary);\n}\n\n/* ── Selection & visibility states ───────────────────────────── */\n\n.contactPane .group-loading {\n}\n\n.contactPane .hidden {\n display: none;\n}\n\n/* ── Mint new address book ───────────────────────────────────── */\n\n.contactPane .claimSuccess {\n font-size: var(--font-size-xl);\n}\n\n.contactPane {\n display: flex;\n flex-direction: column;\n min-height: 100vh;\n}\n\n.contactPane .addressBook-grid {\n display: flex;\n flex-wrap: wrap;\n flex: 1;\n min-width: 50%;\n align-items: stretch;\n width: 100%;\n box-sizing: border-box;\n}\n\n.contactPane .addressBookSection {\n flex: 1 1 350px;\n max-width: 485px;\n box-sizing: border-box;\n}\n\n@media ((min-width: 500px) and (max-width: 900px)) {\n .contactPane .addressBookSection {\n max-width: 900px;\n }\n .contactPane .addressBookSection section {\n max-width: 485px;\n }\n}\n\n@media (max-width: 500px) {\n .contactPane .addressBookSection {\n max-width: 485px;\n }\n}\n\n/* Card Section Background */\n.contactPane .section-bg {\n background: var(--color-section-bg);\n padding: var(--spacing-md);\n box-sizing: border-box;\n}\n\n/* Primary Button */\n.contactPane .btn-primary {\n min-height: var(--min-touch-target);\n padding: var(--spacing-sm) var(--spacing-md);\n border: 1px solid var(--color-primary);\n border-radius: var(--border-radius-base);\n background: var(--color-primary);\n color: var(--color-background);\n font-weight: 600;\n cursor: pointer;\n transition: all var(--animation-duration) ease;\n}\n\n.contactPane .btn-primary:hover {\n background: color-mix(in srgb, var(--color-primary) 90%, black);\n box-shadow: 0 2px 4px rgba(124, 77, 255, 0.2);\n}\n\n.contactPane .btn-primary:active {\n box-shadow: 0 1px 2px rgba(124, 77, 255, 0.2);\n}\n\n.contactPane .btn-primary:disabled {\n opacity: var(--opacity-disabled, 0.6);\n cursor: not-allowed;\n transform: none;\n}\n\n/* Secondary button */\n.contactPane .btn-secondary {\n min-height: var(--min-touch-target);\n padding: var(--spacing-sm) var(--spacing-md);\n border: var(--border-width-thin) solid var(--color-secondary);\n border-radius: var(--border-radius-base);\n background: var(--color-secondary);\n color: var(--color-background);\n font-weight: var(--font-weight-bold);\n cursor: pointer;\n transition: all var(--animation-duration) ease;\n text-decoration: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.contactPane .btn-secondary:hover {\n background: color-mix(in srgb, var(--color-secondary) 85%, black);\n}\n\n.contactPane .btn-secondary:disabled {\n opacity: var(--opacity-disabled);\n cursor: not-allowed;\n}\n\n/* Action Button Focus - used by ChatWithMe, ProfileCard */\n.contactPane .action-button-focus:focus,\n.contactPane .action-button-focus:focus-visible {\n outline: 3px solid var(--color-primary) !important;\n outline-offset: 2px !important;\n box-shadow: 0 0 0 2px var(--color-background), 0 0 0 5px rgba(124, 77, 255, 0.25) !important;\n z-index: 1;\n}\n\n/* ── Button section: horizontal scrollable row ──────────────── */\n\n.contactPane .buttonSection {\n display: flex;\n flex-wrap: nowrap;\n align-items: center;\n padding: var(--spacing-sm);\n padding-bottom: 0;\n overflow-x: auto;\n overflow-y: hidden;\n -webkit-overflow-scrolling: touch;\n scrollbar-width: thin;\n margin-bottom: 0;\n}\n\n.contactPane .buttonSection::-webkit-scrollbar {\n height: 6px;\n}\n\n.contactPane .buttonSection::-webkit-scrollbar-thumb {\n background: var(--color-border-pale);\n border-radius: var(--border-radius-base);\n}\n\n.contactPane .buttonSection::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.contactPane .groupButtonsList {\n display: flex;\n flex-wrap: nowrap;\n align-items: center;\n gap: var(--spacing-xs);\n list-style: none;\n}\n\n.contactPane .buttonSection .groupButtonsList {\n margin-left: var(--spacing-xs);\n margin-right: var(--spacing-xs);\n padding-left: 0;\n}\n\n.contactPane .groupButtonsList li {\n flex-shrink: 0;\n}\n\n.contactPane .groupButtonsList button {\n white-space: nowrap;\n flex-shrink: 0;\n min-width: max-content;\n margin-left: 0;\n}\n\n/* Groups list in details section — flexible 2-column grid */\n.contactPane .detailsSectionContent .groupButtonsList {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n gap: var(--spacing-sm);\n list-style: none;\n padding: 0;\n width: 100%;\n box-sizing: border-box;\n}\n\n.contactPane .detailsSectionContent .groupButtonsList li {\n width: 100%;\n aspect-ratio: 1 / 1;\n}\n\n.contactPane .detailsSectionContent .groupButtonsList button {\n width: 100%;\n height: 100%;\n text-align: center;\n border-radius: var(--border-radius-base);\n word-wrap: break-word;\n overflow-wrap: break-word;\n}\n\n@media (max-width: 599px) {\n .contactPane .detailsSectionContent .groupButtonsList {\n grid-template-columns: repeat(2, 1fr);\n gap: var(--spacing-xs);\n }\n\n .contactPane .detailsSectionContent .groupButtonsList button {\n font-size: var(--font-size-sm);\n border-radius: var(--border-radius-base);\n }\n}\n\n@media (min-width: 900px) {\n .contactPane .detailsSectionContent .groupButtonsList {\n grid-template-columns: repeat(3, 1fr);\n }\n}\n\n.contactPane .detailsSectionContent .newGroupBtn {\n width: 100%;\n box-sizing: border-box;\n margin-top: var(--spacing-sm);\n}\n\n.contactPane .detailsSectionContent h3 {\n font-size: var(--font-size-xl);\n margin-bottom: var(--spacing-sm);\n padding-left: 0;\n}\n\n/* Delete confirmation POPUP — centered overlay in details section */\n.contactPane .detailSection {\n position: relative;\n}\n\n.contactPane .webidControl .personaRow--webid td > div[style*=\"position: relative\"],\n.contactPane .detailsSectionContent .groupButtonsList li > div[style*=\"position: relative\"],\n.contactPane .detailsSectionContent .contact-toolbar > div[style*=\"position: relative\"],\n.contactPane .detailsSectionContent .group-membership-toolbar > div[style*=\"position: relative\"] {\n position: absolute !important;\n top: 0 !important;\n left: 0 !important;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.3);\n z-index: 1000;\n}\n.contactPane .webidControl .personaRow--webid td > div[style*=\"position: relative\"] > div,\n.contactPane .detailsSectionContent .groupButtonsList li > div[style*=\"position: relative\"] > div,\n.contactPane .detailsSectionContent .contact-toolbar > div[style*=\"position: relative\"] > div,\n.contactPane .detailsSectionContent .group-membership-toolbar > div[style*=\"position: relative\"] > div {\n position: relative !important;\n top: auto !important;\n background: var(--color-background);\n border-radius: var(--border-radius-full);\n padding: var(--spacing-lg);\n box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);\n z-index: 1001;\n}\n\n/* Selected state for All contacts button */\n.contactPane .allGroupsButton--selected {\n background-color: var(--color-primary);\n color: var(--color-background);\n}\n\n/* ── Header section ──────────────────────────────────────────── */\n\n.contactPane .headerSection {\n background: var(--color-background);\n padding: var(--spacing-sm);\n border-top-left-radius: var(--border-radius-full);\n border-top-right-radius: var(--border-radius-full);\n margin-bottom: 0;\n}\n\n.contactPane .headerSection header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 0;\n}\n\n.contactPane .headerSection h2 {\n margin-bottom: 0;\n}\n\n/* ── Dotted horizontal rule ─────────────────────────────────── */\n\n.contactPane .dottedHr {\n border: none;\n border-top: 1px dotted var(--color-text-muted);\n margin: 0;\n}\n\n/* ── Search section ─────────────────────────────────────────── */\n\n.contactPane .searchSection {\n padding: var(--spacing-sm);\n padding-bottom: 0;\n margin-bottom: 0;\n}\n\n/* ── People list section ────────────────────────────────────── */\n\n.contactPane .peopleSection {\n display: flex;\n background: var(--color-background);\n border-top: 1px dotted var(--color-text-muted);\n margin-bottom: 0;\n}\n\n.contactPane .peopleSection ul {\n list-style: none;\n padding: 0;\n margin: 0;\n width: 100%;\n max-height: 70vh;\n overflow-y: auto;\n}\n\n.contactPane .peopleSection li {\n border-top: 1px solid var(--color-border-pale);\n padding: var(--spacing-xs);\n}\n\n/* ── Person list item (addressBookPresenter) ─────────────────── */\n\n.contactPane .personLi-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n.contactPane .personLi-avatar {\n width: 45px;\n height: 45px;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.contactPane .personLi-avatar .avatar-placeholder {\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.contactPane .personLi-avatar img {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n object-fit: cover;\n}\n\n.contactPane .personLi-info {\n flex: 1;\n margin-left: var(--spacing-sm);\n overflow: hidden;\n}\n\n.contactPane .personLi-name {\n font-weight: bold;\n font-size: var(--font-size-base);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.contactPane .personLi-arrow {\n margin-left: auto;\n display: flex;\n align-items: center;\n}"],"sourceRoot":""}]);
14425
14397
  // Exports
14426
14398
  /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
14427
14399
 
@@ -14429,15 +14401,15 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Visually hidden utility for accessi
14429
14401
  /***/ },
14430
14402
 
14431
14403
  /***/ 93
14432
- (module, __nested_webpack_exports__, __nested_webpack_require_32000__) {
14404
+ (module, __nested_webpack_exports__, __nested_webpack_require_32495__) {
14433
14405
 
14434
- /* harmony export */ __nested_webpack_require_32000__.d(__nested_webpack_exports__, {
14406
+ /* harmony export */ __nested_webpack_require_32495__.d(__nested_webpack_exports__, {
14435
14407
  /* harmony export */ A: () => (__WEBPACK_DEFAULT_EXPORT__)
14436
14408
  /* harmony export */ });
14437
- /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_32000__(354);
14438
- /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nested_webpack_require_32000__.n(_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
14439
- /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_32000__(314);
14440
- /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__nested_webpack_require_32000__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
14409
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_32495__(354);
14410
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nested_webpack_require_32495__.n(_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
14411
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_32495__(314);
14412
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__nested_webpack_require_32495__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
14441
14413
  // Imports
14442
14414
 
14443
14415
 
@@ -14445,39 +14417,40 @@ var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBP
14445
14417
  // Module
14446
14418
  ___CSS_LOADER_EXPORT___.push([module.id, `/* ── Group Membership Section ──────────────────────────────── */
14447
14419
 
14448
- .group-membership-container {
14420
+ .contactPane .group-membership-container {
14449
14421
  padding: var(--spacing-sm) 0;
14450
14422
  }
14451
14423
 
14452
14424
  /* Grid wrapper — matches detailsSectionContent groupButtonsList */
14453
- .group-pills-wrapper {
14425
+ .contactPane .group-pills-wrapper {
14454
14426
  display: grid;
14455
- grid-template-columns: repeat(2, 1fr);
14427
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
14456
14428
  gap: var(--spacing-sm);
14457
14429
  list-style: none;
14458
14430
  padding: 0;
14459
14431
  margin: 0;
14460
- max-width: 512px;
14432
+ width: 100%;
14461
14433
  }
14462
14434
 
14463
14435
  /* Each group item: button on top, toolbar below */
14464
- .group-membership-item {
14436
+ .contactPane .group-membership-item {
14465
14437
  display: flex;
14466
14438
  flex-direction: column;
14467
14439
  align-items: stretch;
14440
+ max-width: 256px;
14468
14441
  }
14469
14442
 
14470
- .group-membership-item > button {
14443
+ .contactPane .group-membership-item > button {
14471
14444
  width: 100%;
14472
14445
  text-align: center;
14473
- border-radius: 12px;
14446
+ border-radius: var(--border-radius-base);
14474
14447
  word-wrap: break-word;
14475
14448
  overflow-wrap: break-word;
14476
14449
  min-height: var(--min-touch-target);
14477
14450
  }
14478
14451
 
14479
14452
  /* Toolbar with link icon and delete button below each group button */
14480
- .group-membership-toolbar {
14453
+ .contactPane .group-membership-toolbar {
14481
14454
  display: flex;
14482
14455
  justify-content: flex-end;
14483
14456
  align-items: center;
@@ -14485,25 +14458,32 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* ── Group Membership Section ─
14485
14458
  padding: var(--spacing-xs) 0 0 0;
14486
14459
  }
14487
14460
 
14488
- .group-membership-toolbar a img {
14461
+ .contactPane .group-membership-toolbar a img {
14489
14462
  width: 1.3em;
14490
14463
  height: 1em;
14491
14464
  margin: 0;
14492
14465
  }
14493
14466
 
14494
- @media (max-width: 899px) {
14495
- .group-pills-wrapper {
14496
- grid-template-columns: repeat(3, 1fr);
14467
+ @media (max-width: 599px) {
14468
+ .contactPane .group-pills-wrapper {
14469
+ grid-template-columns: repeat(2, 1fr);
14497
14470
  gap: var(--spacing-xs);
14498
14471
  max-width: 100%;
14499
14472
  }
14500
14473
 
14501
- .group-membership-item > button {
14502
- font-size: 0.8rem;
14503
- border-radius: 8px;
14474
+ .contactPane .group-membership-item > button {
14475
+ font-size: var(--font-size-sm);
14476
+ border-radius: var(--border-radius-base);
14504
14477
  }
14505
14478
  }
14506
- `, "",{"version":3,"sources":["webpack://./src/styles/groupMembership.css"],"names":[],"mappings":"AAAA,iEAAiE;;AAEjE;EACE,4BAA4B;AAC9B;;AAEA,kEAAkE;AAClE;EACE,aAAa;EACb,qCAAqC;EACrC,sBAAsB;EACtB,gBAAgB;EAChB,UAAU;EACV,SAAS;EACT,gBAAgB;AAClB;;AAEA,kDAAkD;AAClD;EACE,aAAa;EACb,sBAAsB;EACtB,oBAAoB;AACtB;;AAEA;EACE,WAAW;EACX,kBAAkB;EAClB,mBAAmB;EACnB,qBAAqB;EACrB,yBAAyB;EACzB,mCAAmC;AACrC;;AAEA,qEAAqE;AACrE;EACE,aAAa;EACb,yBAAyB;EACzB,mBAAmB;EACnB,sBAAsB;EACtB,gCAAgC;AAClC;;AAEA;EACE,YAAY;EACZ,WAAW;EACX,SAAS;AACX;;AAEA;EACE;IACE,qCAAqC;IACrC,sBAAsB;IACtB,eAAe;EACjB;;EAEA;IACE,iBAAiB;IACjB,kBAAkB;EACpB;AACF","sourcesContent":["/* ── Group Membership Section ──────────────────────────────── */\n\n.group-membership-container {\n padding: var(--spacing-sm) 0;\n}\n\n/* Grid wrapper — matches detailsSectionContent groupButtonsList */\n.group-pills-wrapper {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: var(--spacing-sm);\n list-style: none;\n padding: 0;\n margin: 0;\n max-width: 512px;\n}\n\n/* Each group item: button on top, toolbar below */\n.group-membership-item {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n}\n\n.group-membership-item > button {\n width: 100%;\n text-align: center;\n border-radius: 12px;\n word-wrap: break-word;\n overflow-wrap: break-word;\n min-height: var(--min-touch-target);\n}\n\n/* Toolbar with link icon and delete button below each group button */\n.group-membership-toolbar {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n gap: var(--spacing-xs);\n padding: var(--spacing-xs) 0 0 0;\n}\n\n.group-membership-toolbar a img {\n width: 1.3em;\n height: 1em;\n margin: 0;\n}\n\n@media (max-width: 899px) {\n .group-pills-wrapper {\n grid-template-columns: repeat(3, 1fr);\n gap: var(--spacing-xs);\n max-width: 100%;\n }\n\n .group-membership-item > button {\n font-size: 0.8rem;\n border-radius: 8px;\n }\n}\n"],"sourceRoot":""}]);
14479
+
14480
+ @media (min-width: 900px) {
14481
+ .contactPane .group-pills-wrapper {
14482
+ grid-template-columns: repeat(3, 1fr);
14483
+ gap: var(--spacing-sm);
14484
+ }
14485
+ }
14486
+ `, "",{"version":3,"sources":["webpack://./src/styles/groupMembership.css"],"names":[],"mappings":"AAAA,iEAAiE;;AAEjE;EACE,4BAA4B;AAC9B;;AAEA,kEAAkE;AAClE;EACE,aAAa;EACb,2DAA2D;EAC3D,sBAAsB;EACtB,gBAAgB;EAChB,UAAU;EACV,SAAS;EACT,WAAW;AACb;;AAEA,kDAAkD;AAClD;EACE,aAAa;EACb,sBAAsB;EACtB,oBAAoB;EACpB,gBAAgB;AAClB;;AAEA;EACE,WAAW;EACX,kBAAkB;EAClB,wCAAwC;EACxC,qBAAqB;EACrB,yBAAyB;EACzB,mCAAmC;AACrC;;AAEA,qEAAqE;AACrE;EACE,aAAa;EACb,yBAAyB;EACzB,mBAAmB;EACnB,sBAAsB;EACtB,gCAAgC;AAClC;;AAEA;EACE,YAAY;EACZ,WAAW;EACX,SAAS;AACX;;AAEA;EACE;IACE,qCAAqC;IACrC,sBAAsB;IACtB,eAAe;EACjB;;EAEA;IACE,8BAA8B;IAC9B,wCAAwC;EAC1C;AACF;;AAEA;EACE;IACE,qCAAqC;IACrC,sBAAsB;EACxB;AACF","sourcesContent":["/* ── Group Membership Section ──────────────────────────────── */\n\n.contactPane .group-membership-container {\n padding: var(--spacing-sm) 0;\n}\n\n/* Grid wrapper — matches detailsSectionContent groupButtonsList */\n.contactPane .group-pills-wrapper {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n gap: var(--spacing-sm);\n list-style: none;\n padding: 0;\n margin: 0;\n width: 100%;\n}\n\n/* Each group item: button on top, toolbar below */\n.contactPane .group-membership-item {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n max-width: 256px;\n}\n\n.contactPane .group-membership-item > button {\n width: 100%;\n text-align: center;\n border-radius: var(--border-radius-base);\n word-wrap: break-word;\n overflow-wrap: break-word;\n min-height: var(--min-touch-target);\n}\n\n/* Toolbar with link icon and delete button below each group button */\n.contactPane .group-membership-toolbar {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n gap: var(--spacing-xs);\n padding: var(--spacing-xs) 0 0 0;\n}\n\n.contactPane .group-membership-toolbar a img {\n width: 1.3em;\n height: 1em;\n margin: 0;\n}\n\n@media (max-width: 599px) {\n .contactPane .group-pills-wrapper {\n grid-template-columns: repeat(2, 1fr);\n gap: var(--spacing-xs);\n max-width: 100%;\n }\n\n .contactPane .group-membership-item > button {\n font-size: var(--font-size-sm);\n border-radius: var(--border-radius-base);\n }\n}\n\n@media (min-width: 900px) {\n .contactPane .group-pills-wrapper {\n grid-template-columns: repeat(3, 1fr);\n gap: var(--spacing-sm);\n }\n}\n"],"sourceRoot":""}]);
14507
14487
  // Exports
14508
14488
  /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
14509
14489
 
@@ -14511,15 +14491,15 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* ── Group Membership Section ─
14511
14491
  /***/ },
14512
14492
 
14513
14493
  /***/ 479
14514
- (module, __nested_webpack_exports__, __nested_webpack_require_36568__) {
14494
+ (module, __nested_webpack_exports__, __nested_webpack_require_37810__) {
14515
14495
 
14516
- /* harmony export */ __nested_webpack_require_36568__.d(__nested_webpack_exports__, {
14496
+ /* harmony export */ __nested_webpack_require_37810__.d(__nested_webpack_exports__, {
14517
14497
  /* harmony export */ A: () => (__WEBPACK_DEFAULT_EXPORT__)
14518
14498
  /* harmony export */ });
14519
- /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_36568__(354);
14520
- /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nested_webpack_require_36568__.n(_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
14521
- /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_36568__(314);
14522
- /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__nested_webpack_require_36568__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
14499
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_37810__(354);
14500
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nested_webpack_require_37810__.n(_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
14501
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_37810__(314);
14502
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__nested_webpack_require_37810__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
14523
14503
  // Imports
14524
14504
 
14525
14505
 
@@ -14539,15 +14519,15 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* individual.js styles — extracted
14539
14519
  /***/ },
14540
14520
 
14541
14521
  /***/ 715
14542
- (module, __nested_webpack_exports__, __nested_webpack_require_38571__) {
14522
+ (module, __nested_webpack_exports__, __nested_webpack_require_39813__) {
14543
14523
 
14544
- /* harmony export */ __nested_webpack_require_38571__.d(__nested_webpack_exports__, {
14524
+ /* harmony export */ __nested_webpack_require_39813__.d(__nested_webpack_exports__, {
14545
14525
  /* harmony export */ A: () => (__WEBPACK_DEFAULT_EXPORT__)
14546
14526
  /* harmony export */ });
14547
- /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_38571__(354);
14548
- /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nested_webpack_require_38571__.n(_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
14549
- /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_38571__(314);
14550
- /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__nested_webpack_require_38571__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
14527
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_39813__(354);
14528
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nested_webpack_require_39813__.n(_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
14529
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_39813__(314);
14530
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__nested_webpack_require_39813__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
14551
14531
  // Imports
14552
14532
 
14553
14533
 
@@ -14571,15 +14551,15 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* mugshotGallery.js styles — extrac
14571
14551
  /***/ },
14572
14552
 
14573
14553
  /***/ 434
14574
- (module, __nested_webpack_exports__, __nested_webpack_require_40819__) {
14554
+ (module, __nested_webpack_exports__, __nested_webpack_require_42061__) {
14575
14555
 
14576
- /* harmony export */ __nested_webpack_require_40819__.d(__nested_webpack_exports__, {
14556
+ /* harmony export */ __nested_webpack_require_42061__.d(__nested_webpack_exports__, {
14577
14557
  /* harmony export */ A: () => (__WEBPACK_DEFAULT_EXPORT__)
14578
14558
  /* harmony export */ });
14579
- /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_40819__(354);
14580
- /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nested_webpack_require_40819__.n(_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
14581
- /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_40819__(314);
14582
- /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__nested_webpack_require_40819__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
14559
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_42061__(354);
14560
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nested_webpack_require_42061__.n(_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
14561
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_42061__(314);
14562
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__nested_webpack_require_42061__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
14583
14563
  // Imports
14584
14564
 
14585
14565
 
@@ -14587,6 +14567,18 @@ var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBP
14587
14567
  // Module
14588
14568
  ___CSS_LOADER_EXPORT___.push([module.id, `/* Solid-UI form */
14589
14569
 
14570
+ /* Vertically center autocomplete input in .formFieldValue */
14571
+ .individualPane .formFieldValue > div[style*="flex-direction: row"],
14572
+ .contactPane .formFieldValue > div[style*="flex-direction: row"] {
14573
+ align-items: center;
14574
+ display: flex;
14575
+ }
14576
+
14577
+ .individualPane .formFieldValue input[data-testid="autocomplete-input"],
14578
+ .contactPane .formFieldValue input[data-testid="autocomplete-input"] {
14579
+ vertical-align: middle;
14580
+ }
14581
+
14590
14582
  .individualPane .hoverControl:has(> img:first-child),
14591
14583
  .contactPane .hoverControl:has(> img:first-child) {
14592
14584
  background-color: transparent !important;
@@ -14720,6 +14712,12 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Solid-UI form */
14720
14712
  .individualPane .formFieldValue td,
14721
14713
  .contactPane .formFieldValue td {
14722
14714
  padding: 0 !important;
14715
+ vertical-align: middle;
14716
+ }
14717
+
14718
+ .individualPane .formFieldValue table[data-testid="autocomplete-table"],
14719
+ .contactPane .formFieldValue table[data-testid="autocomplete-table"] {
14720
+ height: 100%;
14723
14721
  }
14724
14722
 
14725
14723
  .individualPane .formFieldValue input:not([type="color"]),
@@ -14763,6 +14761,13 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Solid-UI form */
14763
14761
  padding: 0 !important;
14764
14762
  }
14765
14763
 
14764
+ /* In contactPane, remove border/padding from all direct child divs. */
14765
+ .individualPane > div,
14766
+ .contactPane > div {
14767
+ border: none !important;
14768
+ padding: 0 !important;
14769
+ }
14770
+
14766
14771
  /* Align schema.org, solid terms, FOAF, vCard, and org field labels with their input values. */
14767
14772
  .individualPane :not(.choiceBox):has(> .formFieldName):has(> .formFieldValue),
14768
14773
  .contactPane :not(.choiceBox):has(> .formFieldName):has(> .formFieldValue) {
@@ -14771,6 +14776,45 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Solid-UI form */
14771
14776
  margin-bottom: var(--spacing-sm);
14772
14777
  }
14773
14778
 
14779
+ /* for the Resume inside corporation choice */
14780
+ /* Add space between classifierBox label and select box */
14781
+ .individualPane .choiceBox .classifierBox-label,
14782
+ .contactPane .choiceBox .classifierBox-label {
14783
+ margin-right: 0;
14784
+ padding-left: 0.3em;
14785
+ }
14786
+
14787
+ .individualPane .choiceBox .choiceBox-selectBox select,
14788
+ .contactPane .choiceBox .choiceBox-selectBox select {
14789
+ margin-left: 2.1em !important;
14790
+ }
14791
+
14792
+ /* for the Resume orga details */
14793
+ /* Add space between classifierBox label and select box */
14794
+ .individualPane .classifierBox .classifierBox-label,
14795
+ .contactPane .classifierBox .classifierBox-label {
14796
+ margin-right: 0;
14797
+ padding-left: 0.3em;
14798
+ width: 8em;
14799
+ padding: 0.3em;
14800
+ vertical-align: middle;
14801
+ }
14802
+
14803
+ .individualPane .classifierBox .classifierBox-selectBox,
14804
+ .contactPane .classifierBox .classifierBox-selectBox {
14805
+ margin-left: 0 !important;
14806
+ }
14807
+
14808
+ .individualPane .classifierBox .classifierBox-selectBox select,
14809
+ .contactPane .classifierBox .classifierBox-selectBox select {
14810
+ margin-left: 0 !important;
14811
+ }
14812
+
14813
+ .individualPane .formFieldValue > span > select,
14814
+ .contactPane .formFieldValue > span > select {
14815
+ margin-left: 0 !important;
14816
+ }
14817
+
14774
14818
  .individualPane :not(.choiceBox):has(> .formFieldName):has(> .formFieldValue) > .formFieldValue,
14775
14819
  .contactPane :not(.choiceBox):has(> .formFieldName):has(> .formFieldValue) > .formFieldValue {
14776
14820
  margin-bottom: 0;
@@ -14814,11 +14858,11 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Solid-UI form */
14814
14858
  .contactPane .formFieldValue textarea {
14815
14859
  appearance: none;
14816
14860
  -webkit-appearance: none;
14817
- border: .05em solid #88c !important;
14861
+ border: 0.05em solid var(--color-secondary) !important;
14818
14862
  border-style: solid !important;
14819
- border-width: .05em !important;
14820
- border-color: #88c !important;
14821
- border-radius: 0.2em !important;
14863
+ border-width: 0.05em !important;
14864
+ border-color: var(--color-secondary) !important;
14865
+ border-radius: var(--border-radius-base) !important;
14822
14866
  width: 100%;
14823
14867
  box-sizing: border-box;
14824
14868
  margin-top: var(--spacing-xs);
@@ -14826,6 +14870,12 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Solid-UI form */
14826
14870
  margin-right: 0 !important;
14827
14871
  }
14828
14872
 
14873
+ /* Add horizontal gap between label and textarea for all label+textarea pairs. */
14874
+ .individualPane div:has(> a) + div:has(textarea),
14875
+ .contactPane div:has(> a) + div:has(textarea) {
14876
+ margin-left: var(--spacing-sm);
14877
+ }
14878
+
14829
14879
  /* Center textarea label vertically in flex rows. */
14830
14880
  .individualPane div[style*="display: flex"][style*="flex-direction: row"]:has(textarea),
14831
14881
  .contactPane div[style*="display: flex"][style*="flex-direction: row"]:has(textarea) {
@@ -14835,6 +14885,7 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Solid-UI form */
14835
14885
  .individualPane div[style*="display: flex"][style*="flex-direction: row"]:has(textarea) > div:has(> a),
14836
14886
  .contactPane div[style*="display: flex"][style*="flex-direction: row"]:has(textarea) > div:has(> a) {
14837
14887
  padding-left: var(--spacing-xs);
14888
+ padding-top: var(--spacing-sm);
14838
14889
  }
14839
14890
 
14840
14891
  /* Keep autocomplete/table-based fields (e.g. Occupation) aligned to label text baseline. */
@@ -14845,7 +14896,7 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Solid-UI form */
14845
14896
 
14846
14897
  .individualPane :not(.choiceBox):has(> .formFieldValue input[data-testid="autocomplete-input"]) > .formFieldName,
14847
14898
  .contactPane :not(.choiceBox):has(> .formFieldValue input[data-testid="autocomplete-input"]) > .formFieldName {
14848
- padding-top: 0.55em !important;
14899
+ padding-top: var(--spacing-xs) !important;
14849
14900
  }
14850
14901
 
14851
14902
  .individualPane .formFieldValue:has(input[data-testid="autocomplete-input"]),
@@ -14899,9 +14950,9 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Solid-UI form */
14899
14950
  padding: var(--spacing-xs) !important;
14900
14951
  }
14901
14952
 
14902
- .webidControl table td div.contactPane.namedPane {
14953
+ .contactPane .webidControl table td div.contactPane.namedPane {
14903
14954
  border: none !important;
14904
- }`, "",{"version":3,"sources":["webpack://./src/styles/rdfFormsEnforced.css"],"names":[],"mappings":"AAAA,kBAAkB;;AAElB;;EAEE,wCAAwC;EACxC,uBAAuB;EACvB,oBAAoB;EACpB,2BAA2B;EAC3B,yBAAyB;EACzB,mCAAmC;EACnC,kCAAkC;EAClC,eAAe;EACf,oBAAoB;EACpB,mBAAmB;EACnB,uBAAuB;AACzB;;AAEA;;EAEE,uBAAuB;EACvB,wBAAwB;EACxB,cAAc;EACd,wBAAwB;EACxB,yBAAyB;EACzB,0BAA0B;EAC1B,+BAA+B;AACjC;;AAEA;;EAEE,oBAAoB;EACpB,mBAAmB;EACnB,kBAAkB;AACpB;;AAEA;;EAEE,oBAAoB;EACpB,mBAAmB;AACrB;;AAEA;;EAEE,kBAAkB;AACpB;;AAEA;;EAEE,wCAAwC;AAC1C;;AAEA;;EAEE,wCAAwC;EACxC,uBAAuB;EACvB,oBAAoB;EACpB,2BAA2B;AAC7B;;AAEA;;EAEE,mCAAmC;EACnC,kCAAkC;AACpC;;AAEA;;;;;;EAME,eAAe;EACf,YAAY;EACZ,uBAAuB;EACvB,aAAa;EACb,wBAAwB;EACxB,iDAAiD;EACjD,0CAA0C;AAC5C;;AAEA;;EAEE,iDAAiD;EACjD,oBAAoB;EACpB,qBAAqB;AACvB;;AAEA;;;;;;;;;;EAUE,mCAAmC;AACrC;;AAEA;;;;;;;;;;EAUE,sEAAsE;EACtE,mBAAmB;EACnB,6CAA6C;AAC/C;;AAEA;;EAEE,WAAW;AACb;;AAEA;;EAEE,YAAY;EACZ,gCAAgC;AAClC;;AAEA;;EAEE,oBAAoB;EACpB,qBAAqB;AACvB;;AAEA;;EAEE,qBAAqB;AACvB;;AAEA;;;;;;EAME,WAAW;EACX,eAAe;AACjB;;AAEA;;EAEE,WAAW;EACX,cAAc;EACd,sBAAsB;EACtB,yBAAyB;EACzB,0BAA0B;AAC5B;;AAEA;;EAEE,yBAAyB;EACzB,sBAAsB;EACtB,oBAAoB;AACtB;;AAEA;;EAEE,yBAAyB;EACzB,0BAA0B;AAC5B;;AAEA,oFAAoF;AACpF;;;;EAIE,uBAAuB;EACvB,qBAAqB;AACvB;;AAEA,8FAA8F;AAC9F;;EAEE,aAAa;EACb,qBAAqB;EACrB,gCAAgC;AAClC;;AAEA;;EAEE,gBAAgB;AAClB;;AAEA;;;;;;;;;;EAUE,oBAAoB;EACpB,mBAAmB;EACnB,sBAAsB;AACxB;;AAEA;;;;;;;;;;EAUE,oBAAoB;EACpB,mBAAmB;EACnB,sBAAsB;EACtB,OAAO;EACP,YAAY;AACd;;AAEA;;;;EAIE,gBAAgB;EAChB,wBAAwB;EACxB,mCAAmC;EACnC,8BAA8B;EAC9B,8BAA8B;EAC9B,6BAA6B;EAC7B,+BAA+B;EAC/B,WAAW;EACX,sBAAsB;EACtB,6BAA6B;EAC7B,yBAAyB;EACzB,0BAA0B;AAC5B;;AAEA,mDAAmD;AACnD;;EAEE,uBAAuB;AACzB;;AAEA;;EAEE,+BAA+B;AACjC;;AAEA,2FAA2F;AAC3F;;EAEE,uBAAuB;AACzB;;AAEA;;EAEE,8BAA8B;AAChC;;AAEA;;EAEE,sBAAsB;AACxB;;AAEA;;;;EAIE,oBAAoB;AACtB;;AAEA;;EAEE,wBAAwB;AAC1B;;AAEA;;EAEE,WAAW;EACX,yBAAyB;EACzB,0BAA0B;AAC5B;;AAEA;;;;;;;;;;;;;;EAcE,oDAAoD;EACpD,mBAAmB;EACnB,aAAa;EACb,uDAAuD;AACzD;;AAEA;;;;EAIE,qCAAqC;AACvC;;AAEA;EACE,uBAAuB;AACzB","sourcesContent":["/* Solid-UI form */\n\n.individualPane .hoverControl:has(> img:first-child),\n.contactPane .hoverControl:has(> img:first-child) {\n background-color: transparent !important;\n border: none !important;\n margin: 0 !important;\n border-radius: 0 !important;\n padding: 0.7em !important;\n min-height: var(--min-touch-target);\n min-width: var(--min-touch-target);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.individualPane .hoverControlHide,\n.contactPane .hoverControlHide {\n width: 1.5em !important;\n height: 1.5em !important;\n display: block;\n margin-top: 0 !important;\n margin-left: 0 !important;\n margin-right: 0 !important;\n margin-bottom: 0.3em !important;\n}\n\n.individualPane .hoverControl:has(> img:first-child) > span,\n.contactPane .hoverControl:has(> img:first-child) > span {\n display: inline-flex;\n align-items: center;\n margin-left: 0.3em;\n}\n\n.individualPane div[style*=\"padding: 0.5em\"]:has(> img),\n.contactPane div[style*=\"padding: 0.5em\"]:has(> img) {\n display: inline-flex;\n align-items: center;\n}\n\n.individualPane div[style*=\"padding: 0.5em\"]:has(> img) > span,\n.contactPane div[style*=\"padding: 0.5em\"]:has(> img) > span {\n margin-left: 0.3em;\n}\n\n.individualPane .hoverControl:has(> img:first-child):hover,\n.contactPane .hoverControl:has(> img:first-child):hover {\n background-color: transparent !important;\n}\n\n.individualPane button:has(> img[src$=\".svg\"]),\n.contactPane button:has(> img[src$=\".svg\"]) {\n background-color: transparent !important;\n border: none !important;\n margin: 0 !important;\n border-radius: 0 !important;\n}\n\n.individualPane button,\n.contactPane button {\n min-height: var(--min-touch-target);\n min-width: var(--min-touch-target);\n}\n\n.individualPane input:not([type=\"color\"]),\n.contactPane input:not([type=\"color\"]),\n.individualPane textarea,\n.contactPane textarea,\n.individualPane select,\n.contactPane select {\n max-width: 100%;\n min-width: 0;\n box-sizing: border-box ;\n font: inherit;\n color: var(--color-text);\n background-color: var(--color-card-bg) !important;\n border: 1px solid var(--color-border-pale);\n}\n\n.individualPane textarea,\n.contactPane textarea {\n border-color: var(--color-border-pale) !important;\n margin: 0 !important;\n padding: 0 !important;\n}\n\n.individualPane input[type=\"date\"],\n.contactPane input[type=\"date\"],\n.individualPane input[type=\"month\"],\n.contactPane input[type=\"month\"],\n.individualPane input[type=\"week\"],\n.contactPane input[type=\"week\"],\n.individualPane input[type=\"time\"],\n.contactPane input[type=\"time\"],\n.individualPane input[type=\"datetime-local\"],\n.contactPane input[type=\"datetime-local\"] {\n min-height: var(--min-touch-target);\n}\n\n.individualPane .hoverControl:has(> img:first-child):focus-visible,\n.contactPane .hoverControl:has(> img:first-child):focus-visible,\n.individualPane button:focus-visible,\n.contactPane button:focus-visible,\n.individualPane input:not([type=\"color\"]):focus-visible,\n.contactPane input:not([type=\"color\"]):focus-visible,\n.individualPane textarea:focus-visible,\n.contactPane textarea:focus-visible,\n.individualPane select:focus-visible,\n.contactPane select:focus-visible {\n outline: var(--focus-ring-width) solid var(--color-primary) !important;\n outline-offset: 2px;\n box-shadow: 0 0 0 1px var(--color-background);\n}\n\n.individualPane input[type=\"url\"],\n.contactPane input[type=\"url\"] {\n width: 100%;\n}\n\n.individualPane .formFieldValue,\n.contactPane .formFieldValue {\n min-width: 0;\n margin-bottom: var(--spacing-sm);\n}\n\n.individualPane .formFieldValue table,\n.contactPane .formFieldValue table {\n margin: 0 !important;\n padding: 0 !important;\n}\n\n.individualPane .formFieldValue td,\n.contactPane .formFieldValue td {\n padding: 0 !important;\n}\n\n.individualPane .formFieldValue input:not([type=\"color\"]),\n.contactPane .formFieldValue input:not([type=\"color\"]),\n.individualPane .formFieldValue textarea,\n.contactPane .formFieldValue textarea,\n.individualPane .formFieldValue select,\n.contactPane .formFieldValue select {\n width: 100%;\n max-width: 100%;\n}\n\n.individualPane select#formSelect,\n.contactPane select#formSelect {\n width: 100%;\n max-width: 97%;\n box-sizing: border-box;\n margin-left: 0 !important;\n margin-right: 0 !important;\n}\n\n.individualPane span select,\n.contactPane span select {\n max-width: 96% !important;\n box-sizing: border-box;\n margin: 0 !important;\n}\n\n.individualPane .formFieldValue span select,\n.contactPane .formFieldValue span select {\n margin-left: 0 !important;\n margin-right: 0 !important;\n}\n\n/* Remove border/padding from the first wrapper div (and its first child wrapper). */\n.individualPane > div:first-of-type,\n.contactPane > div:first-of-type,\n.individualPane > div:first-of-type > div:first-of-type,\n.contactPane > div:first-of-type > div:first-of-type {\n border: none !important;\n padding: 0 !important;\n}\n\n/* Align schema.org, solid terms, FOAF, vCard, and org field labels with their input values. */\n.individualPane :not(.choiceBox):has(> .formFieldName):has(> .formFieldValue),\n.contactPane :not(.choiceBox):has(> .formFieldName):has(> .formFieldValue) {\n display: flex;\n align-items: baseline;\n margin-bottom: var(--spacing-sm);\n}\n\n.individualPane :not(.choiceBox):has(> .formFieldName):has(> .formFieldValue) > .formFieldValue,\n.contactPane :not(.choiceBox):has(> .formFieldName):has(> .formFieldValue) > .formFieldValue {\n margin-bottom: 0;\n}\n\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://schema.org/\"]),\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://schema.org/\"]),\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/ns/solid/terms#\"]),\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/ns/solid/terms#\"]),\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://xmlns.com/foaf/0.1/\"]),\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://xmlns.com/foaf/0.1/\"]),\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/2006/vcard/ns\"]),\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/2006/vcard/ns\"]),\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/ns/org#\"]),\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/ns/org#\"]) {\n display: inline-flex;\n align-items: center;\n vertical-align: middle;\n}\n\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://schema.org/\"]) + .formFieldValue,\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://schema.org/\"]) + .formFieldValue,\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/ns/solid/terms#\"]) + .formFieldValue,\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/ns/solid/terms#\"]) + .formFieldValue,\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://xmlns.com/foaf/0.1/\"]) + .formFieldValue,\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://xmlns.com/foaf/0.1/\"]) + .formFieldValue,\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/2006/vcard/ns\"]) + .formFieldValue,\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/2006/vcard/ns\"]) + .formFieldValue,\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/ns/org#\"]) + .formFieldValue,\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/ns/org#\"]) + .formFieldValue {\n display: inline-flex;\n align-items: center;\n vertical-align: middle;\n flex: 1;\n min-width: 0;\n}\n\n.individualPane textarea,\n.contactPane textarea,\n.individualPane .formFieldValue textarea,\n.contactPane .formFieldValue textarea {\n appearance: none;\n -webkit-appearance: none;\n border: .05em solid #88c !important;\n border-style: solid !important;\n border-width: .05em !important;\n border-color: #88c !important;\n border-radius: 0.2em !important;\n width: 100%;\n box-sizing: border-box;\n margin-top: var(--spacing-xs);\n margin-left: 0 !important;\n margin-right: 0 !important;\n}\n\n/* Center textarea label vertically in flex rows. */\n.individualPane div[style*=\"display: flex\"][style*=\"flex-direction: row\"]:has(textarea),\n.contactPane div[style*=\"display: flex\"][style*=\"flex-direction: row\"]:has(textarea) {\n align-items: flex-start;\n}\n\n.individualPane div[style*=\"display: flex\"][style*=\"flex-direction: row\"]:has(textarea) > div:has(> a),\n.contactPane div[style*=\"display: flex\"][style*=\"flex-direction: row\"]:has(textarea) > div:has(> a) {\n padding-left: var(--spacing-xs);\n}\n\n/* Keep autocomplete/table-based fields (e.g. Occupation) aligned to label text baseline. */\n.individualPane :not(.choiceBox):has(> .formFieldValue input[data-testid=\"autocomplete-input\"]),\n.contactPane :not(.choiceBox):has(> .formFieldValue input[data-testid=\"autocomplete-input\"]) {\n align-items: flex-start;\n}\n\n.individualPane :not(.choiceBox):has(> .formFieldValue input[data-testid=\"autocomplete-input\"]) > .formFieldName,\n.contactPane :not(.choiceBox):has(> .formFieldValue input[data-testid=\"autocomplete-input\"]) > .formFieldName {\n padding-top: 0.55em !important;\n}\n\n.individualPane .formFieldValue:has(input[data-testid=\"autocomplete-input\"]),\n.contactPane .formFieldValue:has(input[data-testid=\"autocomplete-input\"]) {\n align-self: flex-start;\n}\n\n.individualPane .formFieldValue table[data-testid=\"autocomplete-table\"],\n.contactPane .formFieldValue table[data-testid=\"autocomplete-table\"],\n.individualPane .formFieldValue input[data-testid=\"autocomplete-input\"],\n.contactPane .formFieldValue input[data-testid=\"autocomplete-input\"] {\n margin: 0 !important;\n}\n\n.individualPane .formFieldValue table[data-testid=\"autocomplete-table\"],\n.contactPane .formFieldValue table[data-testid=\"autocomplete-table\"] {\n vertical-align: baseline;\n}\n\n.individualPane input:not([type=\"color\"]),\n.contactPane input:not([type=\"color\"]) {\n width: 100%;\n margin-left: 0 !important;\n margin-right: 0 !important;\n}\n\n.individualPane input:disabled,\n.contactPane input:disabled,\n.individualPane textarea:disabled,\n.contactPane textarea:disabled,\n.individualPane select:disabled,\n.contactPane select:disabled,\n.individualPane input[readonly],\n.contactPane input[readonly],\n.individualPane textarea[readonly],\n.contactPane textarea[readonly],\n.individualPane input:read-only,\n.contactPane input:read-only,\n.individualPane textarea:read-only,\n.contactPane textarea:read-only {\n background-color: var(--color-background) !important;\n cursor: not-allowed;\n opacity: 0.75;\n border: 0.05em solid var(--color-background) !important;\n}\n\n.individualPane textarea,\n.contactPane textarea,\n.individualPane .formFieldValue textarea,\n.contactPane .formFieldValue textarea {\n padding: var(--spacing-xs) !important;\n}\n\n.webidControl table td div.contactPane.namedPane {\n border: none !important;\n}"],"sourceRoot":""}]);
14955
+ }`, "",{"version":3,"sources":["webpack://./src/styles/rdfFormsEnforced.css"],"names":[],"mappings":"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","sourcesContent":["/* Solid-UI form */\n\n/* Vertically center autocomplete input in .formFieldValue */\n.individualPane .formFieldValue > div[style*=\"flex-direction: row\"],\n.contactPane .formFieldValue > div[style*=\"flex-direction: row\"] {\n align-items: center;\n display: flex;\n}\n\n.individualPane .formFieldValue input[data-testid=\"autocomplete-input\"],\n.contactPane .formFieldValue input[data-testid=\"autocomplete-input\"] {\n vertical-align: middle;\n}\n\n.individualPane .hoverControl:has(> img:first-child),\n.contactPane .hoverControl:has(> img:first-child) {\n background-color: transparent !important;\n border: none !important;\n margin: 0 !important;\n border-radius: 0 !important;\n padding: 0.7em !important;\n min-height: var(--min-touch-target);\n min-width: var(--min-touch-target);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.individualPane .hoverControlHide,\n.contactPane .hoverControlHide {\n width: 1.5em !important;\n height: 1.5em !important;\n display: block;\n margin-top: 0 !important;\n margin-left: 0 !important;\n margin-right: 0 !important;\n margin-bottom: 0.3em !important;\n}\n\n.individualPane .hoverControl:has(> img:first-child) > span,\n.contactPane .hoverControl:has(> img:first-child) > span {\n display: inline-flex;\n align-items: center;\n margin-left: 0.3em;\n}\n\n.individualPane div[style*=\"padding: 0.5em\"]:has(> img),\n.contactPane div[style*=\"padding: 0.5em\"]:has(> img) {\n display: inline-flex;\n align-items: center;\n}\n\n.individualPane div[style*=\"padding: 0.5em\"]:has(> img) > span,\n.contactPane div[style*=\"padding: 0.5em\"]:has(> img) > span {\n margin-left: 0.3em;\n}\n\n.individualPane .hoverControl:has(> img:first-child):hover,\n.contactPane .hoverControl:has(> img:first-child):hover {\n background-color: transparent !important;\n}\n\n.individualPane button:has(> img[src$=\".svg\"]),\n.contactPane button:has(> img[src$=\".svg\"]) {\n background-color: transparent !important;\n border: none !important;\n margin: 0 !important;\n border-radius: 0 !important;\n}\n\n.individualPane button,\n.contactPane button {\n min-height: var(--min-touch-target);\n min-width: var(--min-touch-target);\n}\n\n.individualPane input:not([type=\"color\"]),\n.contactPane input:not([type=\"color\"]),\n.individualPane textarea,\n.contactPane textarea,\n.individualPane select,\n.contactPane select {\n max-width: 100%;\n min-width: 0;\n box-sizing: border-box ;\n font: inherit;\n color: var(--color-text);\n background-color: var(--color-card-bg) !important;\n border: 1px solid var(--color-border-pale);\n}\n\n.individualPane textarea,\n.contactPane textarea {\n border-color: var(--color-border-pale) !important;\n margin: 0 !important;\n padding: 0 !important;\n}\n\n.individualPane input[type=\"date\"],\n.contactPane input[type=\"date\"],\n.individualPane input[type=\"month\"],\n.contactPane input[type=\"month\"],\n.individualPane input[type=\"week\"],\n.contactPane input[type=\"week\"],\n.individualPane input[type=\"time\"],\n.contactPane input[type=\"time\"],\n.individualPane input[type=\"datetime-local\"],\n.contactPane input[type=\"datetime-local\"] {\n min-height: var(--min-touch-target);\n}\n\n.individualPane .hoverControl:has(> img:first-child):focus-visible,\n.contactPane .hoverControl:has(> img:first-child):focus-visible,\n.individualPane button:focus-visible,\n.contactPane button:focus-visible,\n.individualPane input:not([type=\"color\"]):focus-visible,\n.contactPane input:not([type=\"color\"]):focus-visible,\n.individualPane textarea:focus-visible,\n.contactPane textarea:focus-visible,\n.individualPane select:focus-visible,\n.contactPane select:focus-visible {\n outline: var(--focus-ring-width) solid var(--color-primary) !important;\n outline-offset: 2px;\n box-shadow: 0 0 0 1px var(--color-background);\n}\n\n.individualPane input[type=\"url\"],\n.contactPane input[type=\"url\"] {\n width: 100%;\n}\n\n.individualPane .formFieldValue,\n.contactPane .formFieldValue {\n min-width: 0;\n margin-bottom: var(--spacing-sm);\n}\n\n.individualPane .formFieldValue table,\n.contactPane .formFieldValue table {\n margin: 0 !important;\n padding: 0 !important;\n}\n\n.individualPane .formFieldValue td,\n.contactPane .formFieldValue td {\n padding: 0 !important;\n vertical-align: middle;\n}\n\n.individualPane .formFieldValue table[data-testid=\"autocomplete-table\"],\n.contactPane .formFieldValue table[data-testid=\"autocomplete-table\"] {\n height: 100%;\n}\n\n.individualPane .formFieldValue input:not([type=\"color\"]),\n.contactPane .formFieldValue input:not([type=\"color\"]),\n.individualPane .formFieldValue textarea,\n.contactPane .formFieldValue textarea,\n.individualPane .formFieldValue select,\n.contactPane .formFieldValue select {\n width: 100%;\n max-width: 100%;\n}\n\n.individualPane select#formSelect,\n.contactPane select#formSelect {\n width: 100%;\n max-width: 97%;\n box-sizing: border-box;\n margin-left: 0 !important;\n margin-right: 0 !important;\n}\n\n.individualPane span select,\n.contactPane span select {\n max-width: 96% !important;\n box-sizing: border-box;\n margin: 0 !important;\n}\n\n.individualPane .formFieldValue span select,\n.contactPane .formFieldValue span select {\n margin-left: 0 !important;\n margin-right: 0 !important;\n}\n\n/* Remove border/padding from the first wrapper div (and its first child wrapper). */\n.individualPane > div:first-of-type,\n.contactPane > div:first-of-type,\n.individualPane > div:first-of-type > div:first-of-type,\n.contactPane > div:first-of-type > div:first-of-type {\n border: none !important;\n padding: 0 !important;\n}\n\n/* In contactPane, remove border/padding from all direct child divs. */\n.individualPane > div,\n.contactPane > div {\n border: none !important;\n padding: 0 !important;\n}\n\n/* Align schema.org, solid terms, FOAF, vCard, and org field labels with their input values. */\n.individualPane :not(.choiceBox):has(> .formFieldName):has(> .formFieldValue),\n.contactPane :not(.choiceBox):has(> .formFieldName):has(> .formFieldValue) {\n display: flex;\n align-items: baseline;\n margin-bottom: var(--spacing-sm);\n}\n\n/* for the Resume inside corporation choice */\n/* Add space between classifierBox label and select box */\n.individualPane .choiceBox .classifierBox-label,\n.contactPane .choiceBox .classifierBox-label {\n margin-right: 0;\n padding-left: 0.3em;\n}\n\n.individualPane .choiceBox .choiceBox-selectBox select,\n.contactPane .choiceBox .choiceBox-selectBox select {\n margin-left: 2.1em !important;\n}\n\n/* for the Resume orga details */\n/* Add space between classifierBox label and select box */\n.individualPane .classifierBox .classifierBox-label,\n.contactPane .classifierBox .classifierBox-label {\n margin-right: 0;\n padding-left: 0.3em;\n width: 8em;\n padding: 0.3em;\n vertical-align: middle;\n}\n\n.individualPane .classifierBox .classifierBox-selectBox,\n.contactPane .classifierBox .classifierBox-selectBox {\n margin-left: 0 !important;\n}\n\n.individualPane .classifierBox .classifierBox-selectBox select,\n.contactPane .classifierBox .classifierBox-selectBox select {\n margin-left: 0 !important;\n}\n\n.individualPane .formFieldValue > span > select,\n.contactPane .formFieldValue > span > select {\n margin-left: 0 !important;\n}\n\n.individualPane :not(.choiceBox):has(> .formFieldName):has(> .formFieldValue) > .formFieldValue,\n.contactPane :not(.choiceBox):has(> .formFieldName):has(> .formFieldValue) > .formFieldValue {\n margin-bottom: 0;\n}\n\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://schema.org/\"]),\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://schema.org/\"]),\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/ns/solid/terms#\"]),\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/ns/solid/terms#\"]),\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://xmlns.com/foaf/0.1/\"]),\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://xmlns.com/foaf/0.1/\"]),\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/2006/vcard/ns\"]),\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/2006/vcard/ns\"]),\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/ns/org#\"]),\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/ns/org#\"]) {\n display: inline-flex;\n align-items: center;\n vertical-align: middle;\n}\n\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://schema.org/\"]) + .formFieldValue,\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://schema.org/\"]) + .formFieldValue,\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/ns/solid/terms#\"]) + .formFieldValue,\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/ns/solid/terms#\"]) + .formFieldValue,\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://xmlns.com/foaf/0.1/\"]) + .formFieldValue,\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://xmlns.com/foaf/0.1/\"]) + .formFieldValue,\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/2006/vcard/ns\"]) + .formFieldValue,\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/2006/vcard/ns\"]) + .formFieldValue,\n.individualPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/ns/org#\"]) + .formFieldValue,\n.contactPane :not(.choiceBox) > .formFieldName:has(a[href*=\"http://www.w3.org/ns/org#\"]) + .formFieldValue {\n display: inline-flex;\n align-items: center;\n vertical-align: middle;\n flex: 1;\n min-width: 0;\n}\n\n.individualPane textarea,\n.contactPane textarea,\n.individualPane .formFieldValue textarea,\n.contactPane .formFieldValue textarea {\n appearance: none;\n -webkit-appearance: none;\n border: 0.05em solid var(--color-secondary) !important;\n border-style: solid !important;\n border-width: 0.05em !important;\n border-color: var(--color-secondary) !important;\n border-radius: var(--border-radius-base) !important;\n width: 100%;\n box-sizing: border-box;\n margin-top: var(--spacing-xs);\n margin-left: 0 !important;\n margin-right: 0 !important;\n}\n\n/* Add horizontal gap between label and textarea for all label+textarea pairs. */\n.individualPane div:has(> a) + div:has(textarea),\n.contactPane div:has(> a) + div:has(textarea) {\n margin-left: var(--spacing-sm);\n}\n\n/* Center textarea label vertically in flex rows. */\n.individualPane div[style*=\"display: flex\"][style*=\"flex-direction: row\"]:has(textarea),\n.contactPane div[style*=\"display: flex\"][style*=\"flex-direction: row\"]:has(textarea) {\n align-items: flex-start;\n}\n\n.individualPane div[style*=\"display: flex\"][style*=\"flex-direction: row\"]:has(textarea) > div:has(> a),\n.contactPane div[style*=\"display: flex\"][style*=\"flex-direction: row\"]:has(textarea) > div:has(> a) {\n padding-left: var(--spacing-xs);\n padding-top: var(--spacing-sm);\n}\n\n/* Keep autocomplete/table-based fields (e.g. Occupation) aligned to label text baseline. */\n.individualPane :not(.choiceBox):has(> .formFieldValue input[data-testid=\"autocomplete-input\"]),\n.contactPane :not(.choiceBox):has(> .formFieldValue input[data-testid=\"autocomplete-input\"]) {\n align-items: flex-start;\n}\n\n.individualPane :not(.choiceBox):has(> .formFieldValue input[data-testid=\"autocomplete-input\"]) > .formFieldName,\n.contactPane :not(.choiceBox):has(> .formFieldValue input[data-testid=\"autocomplete-input\"]) > .formFieldName {\n padding-top: var(--spacing-xs) !important;\n}\n\n.individualPane .formFieldValue:has(input[data-testid=\"autocomplete-input\"]),\n.contactPane .formFieldValue:has(input[data-testid=\"autocomplete-input\"]) {\n align-self: flex-start;\n}\n\n.individualPane .formFieldValue table[data-testid=\"autocomplete-table\"],\n.contactPane .formFieldValue table[data-testid=\"autocomplete-table\"],\n.individualPane .formFieldValue input[data-testid=\"autocomplete-input\"],\n.contactPane .formFieldValue input[data-testid=\"autocomplete-input\"] {\n margin: 0 !important;\n}\n\n.individualPane .formFieldValue table[data-testid=\"autocomplete-table\"],\n.contactPane .formFieldValue table[data-testid=\"autocomplete-table\"] {\n vertical-align: baseline;\n}\n\n.individualPane input:not([type=\"color\"]),\n.contactPane input:not([type=\"color\"]) {\n width: 100%;\n margin-left: 0 !important;\n margin-right: 0 !important;\n}\n\n.individualPane input:disabled,\n.contactPane input:disabled,\n.individualPane textarea:disabled,\n.contactPane textarea:disabled,\n.individualPane select:disabled,\n.contactPane select:disabled,\n.individualPane input[readonly],\n.contactPane input[readonly],\n.individualPane textarea[readonly],\n.contactPane textarea[readonly],\n.individualPane input:read-only,\n.contactPane input:read-only,\n.individualPane textarea:read-only,\n.contactPane textarea:read-only {\n background-color: var(--color-background) !important;\n cursor: not-allowed;\n opacity: 0.75;\n border: 0.05em solid var(--color-background) !important;\n}\n\n.individualPane textarea,\n.contactPane textarea,\n.individualPane .formFieldValue textarea,\n.contactPane .formFieldValue textarea {\n padding: var(--spacing-xs) !important;\n}\n\n.contactPane .webidControl table td div.contactPane.namedPane {\n border: none !important;\n}"],"sourceRoot":""}]);
14905
14956
  // Exports
14906
14957
  /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
14907
14958
 
@@ -14909,15 +14960,15 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* Solid-UI form */
14909
14960
  /***/ },
14910
14961
 
14911
14962
  /***/ 295
14912
- (module, __nested_webpack_exports__, __nested_webpack_require_66404__) {
14963
+ (module, __nested_webpack_exports__, __nested_webpack_require_72654__) {
14913
14964
 
14914
- /* harmony export */ __nested_webpack_require_66404__.d(__nested_webpack_exports__, {
14965
+ /* harmony export */ __nested_webpack_require_72654__.d(__nested_webpack_exports__, {
14915
14966
  /* harmony export */ A: () => (__WEBPACK_DEFAULT_EXPORT__)
14916
14967
  /* harmony export */ });
14917
- /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_66404__(354);
14918
- /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nested_webpack_require_66404__.n(_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
14919
- /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_66404__(314);
14920
- /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__nested_webpack_require_66404__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
14968
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_72654__(354);
14969
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nested_webpack_require_72654__.n(_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
14970
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_72654__(314);
14971
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__nested_webpack_require_72654__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
14921
14972
  // Imports
14922
14973
 
14923
14974
 
@@ -14929,7 +14980,7 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* toolsPane.js styles — extracted f
14929
14980
  /* ── Tools pane table ────────────────────────────────────────── */
14930
14981
 
14931
14982
  .contactPane .statsLog {
14932
- font-size: 120%;
14983
+ font-size: var(--font-size-lg);
14933
14984
  margin: var(--spacing-md);
14934
14985
  background-color: var(--color-background);
14935
14986
  }
@@ -14943,22 +14994,6 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* toolsPane.js styles — extracted f
14943
14994
  max-width: 100%;
14944
14995
  }
14945
14996
 
14946
- /* ── Tools header row ────────────────────────────────────────── */
14947
-
14948
- .contactPane .toolsHeader {
14949
- min-width: 20em;
14950
- padding: var(--spacing-md);
14951
- font-size: 150%;
14952
- border-bottom: 0.1em solid var(--color-error);
14953
- margin-bottom: var(--spacing-xl);
14954
- }
14955
-
14956
- /* ── Status block ────────────────────────────────────────────── */
14957
-
14958
- .contactPane .toolsStatusBlock {
14959
- padding: var(--spacing-xl);
14960
- }
14961
-
14962
14997
  /* ── Tools pane layout ────────────────────────────────────────── */
14963
14998
 
14964
14999
  .contactPane .toolsPane {
@@ -14973,28 +15008,19 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* toolsPane.js styles — extracted f
14973
15008
  gap: var(--spacing-xs);
14974
15009
  }
14975
15010
 
14976
- /* ── Tools buttons ───────────────────────────────────────────── */
14977
-
14978
- .contactPane .toolsButton {
14979
- font-size: var(--font-size-base);
14980
- margin: 0.8em;
14981
- padding: var(--spacing-xs);
14982
- }
14983
-
14984
- /* ── Load index button states ────────────────────────────────── */
15011
+ /* ── Load index button states ──────────────────────────────── */
14985
15012
 
14986
15013
  .contactPane .toolsButton--loading {
14987
- background-color: #ffc;
14988
15014
  }
14989
15015
 
14990
15016
  .contactPane .toolsButton--error {
14991
- background-color: #fcc;
15017
+ background-color: var(--color-error);
14992
15018
  }
14993
15019
 
14994
15020
  .contactPane .toolsButton--success {
14995
15021
  background-color: var(--color-primary);
14996
15022
  }
14997
- `, "",{"version":3,"sources":["webpack://./src/styles/toolsPane.css"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,qFAAqF;;AAErF,mEAAmE;;AAEnE;EACE,eAAe;EACf,yBAAyB;EACzB,yCAAyC;AAC3C;;AAEA;EACE,0BAA0B;EAC1B,qBAAqB;EACrB,qBAAqB;EACrB,yBAAyB;EACzB,gBAAgB;EAChB,eAAe;AACjB;;AAEA,mEAAmE;;AAEnE;EACE,eAAe;EACf,0BAA0B;EAC1B,eAAe;EACf,6CAA6C;EAC7C,gCAAgC;AAClC;;AAEA,mEAAmE;;AAEnE;EACE,0BAA0B;AAC5B;;AAEA,oEAAoE;;AAEpE;EACE,aAAa;EACb,sBAAsB;EACtB,sBAAsB;AACxB;;AAEA;EACE,aAAa;EACb,eAAe;EACf,sBAAsB;AACxB;;AAEA,mEAAmE;;AAEnE;EACE,gCAAgC;EAChC,aAAa;EACb,0BAA0B;AAC5B;;AAEA,mEAAmE;;AAEnE;EACE,sBAAsB;AACxB;;AAEA;EACE,sBAAsB;AACxB;;AAEA;EACE,sCAAsC;AACxC","sourcesContent":["/* toolsPane.js styles — extracted from inline styles */\n/* Uses CSS custom properties from the global stylesheet (dev-global.css / mashlib) */\n\n/* ── Tools pane table ────────────────────────────────────────── */\n\n.contactPane .statsLog {\n font-size: 120%;\n margin: var(--spacing-md);\n background-color: var(--color-background);\n}\n\n.contactPane .statsLog pre {\n padding: var(--spacing-md);\n white-space: pre-wrap;\n word-wrap: break-word;\n overflow-wrap: break-word;\n overflow: hidden;\n max-width: 100%;\n}\n\n/* ── Tools header row ────────────────────────────────────────── */\n\n.contactPane .toolsHeader {\n min-width: 20em;\n padding: var(--spacing-md);\n font-size: 150%;\n border-bottom: 0.1em solid var(--color-error);\n margin-bottom: var(--spacing-xl);\n}\n\n/* ── Status block ────────────────────────────────────────────── */\n\n.contactPane .toolsStatusBlock {\n padding: var(--spacing-xl);\n}\n\n/* ── Tools pane layout ────────────────────────────────────────── */\n\n.contactPane .toolsPane {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-xs);\n}\n\n.contactPane .toolsButtonsContainer {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-xs);\n}\n\n/* ── Tools buttons ───────────────────────────────────────────── */\n\n.contactPane .toolsButton {\n font-size: var(--font-size-base);\n margin: 0.8em;\n padding: var(--spacing-xs);\n}\n\n/* ── Load index button states ────────────────────────────────── */\n\n.contactPane .toolsButton--loading {\n background-color: #ffc;\n}\n\n.contactPane .toolsButton--error {\n background-color: #fcc;\n}\n\n.contactPane .toolsButton--success {\n background-color: var(--color-primary);\n}\n"],"sourceRoot":""}]);
15023
+ `, "",{"version":3,"sources":["webpack://./src/styles/toolsPane.css"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,qFAAqF;;AAErF,mEAAmE;;AAEnE;EACE,8BAA8B;EAC9B,yBAAyB;EACzB,yCAAyC;AAC3C;;AAEA;EACE,0BAA0B;EAC1B,qBAAqB;EACrB,qBAAqB;EACrB,yBAAyB;EACzB,gBAAgB;EAChB,eAAe;AACjB;;AAEA,oEAAoE;;AAEpE;EACE,aAAa;EACb,sBAAsB;EACtB,sBAAsB;AACxB;;AAEA;EACE,aAAa;EACb,eAAe;EACf,sBAAsB;AACxB;;AAEA,iEAAiE;;AAEjE;AACA;;AAEA;EACE,oCAAoC;AACtC;;AAEA;EACE,sCAAsC;AACxC","sourcesContent":["/* toolsPane.js styles — extracted from inline styles */\n/* Uses CSS custom properties from the global stylesheet (dev-global.css / mashlib) */\n\n/* ── Tools pane table ────────────────────────────────────────── */\n\n.contactPane .statsLog {\n font-size: var(--font-size-lg);\n margin: var(--spacing-md);\n background-color: var(--color-background);\n}\n\n.contactPane .statsLog pre {\n padding: var(--spacing-md);\n white-space: pre-wrap;\n word-wrap: break-word;\n overflow-wrap: break-word;\n overflow: hidden;\n max-width: 100%;\n}\n\n/* ── Tools pane layout ────────────────────────────────────────── */\n\n.contactPane .toolsPane {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-xs);\n}\n\n.contactPane .toolsButtonsContainer {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-xs);\n}\n\n/* ── Load index button states ──────────────────────────────── */\n\n.contactPane .toolsButton--loading {\n}\n\n.contactPane .toolsButton--error {\n background-color: var(--color-error);\n}\n\n.contactPane .toolsButton--success {\n background-color: var(--color-primary);\n}\n"],"sourceRoot":""}]);
14998
15024
  // Exports
14999
15025
  /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
15000
15026
 
@@ -15002,25 +15028,21 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* toolsPane.js styles — extracted f
15002
15028
  /***/ },
15003
15029
 
15004
15030
  /***/ 886
15005
- (module, __nested_webpack_exports__, __nested_webpack_require_71764__) {
15031
+ (module, __nested_webpack_exports__, __nested_webpack_require_76650__) {
15006
15032
 
15007
- /* harmony export */ __nested_webpack_require_71764__.d(__nested_webpack_exports__, {
15033
+ /* harmony export */ __nested_webpack_require_76650__.d(__nested_webpack_exports__, {
15008
15034
  /* harmony export */ A: () => (__WEBPACK_DEFAULT_EXPORT__)
15009
15035
  /* harmony export */ });
15010
- /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_71764__(354);
15011
- /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nested_webpack_require_71764__.n(_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
15012
- /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_71764__(314);
15013
- /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__nested_webpack_require_71764__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
15036
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __nested_webpack_require_76650__(354);
15037
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nested_webpack_require_76650__.n(_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
15038
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __nested_webpack_require_76650__(314);
15039
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__nested_webpack_require_76650__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
15014
15040
  // Imports
15015
15041
 
15016
15042
 
15017
15043
  var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));
15018
15044
  // Module
15019
- ___CSS_LOADER_EXPORT___.push([module.id, `/* webidControl.js styles extracted from inline styles */
15020
- /* Uses CSS custom properties from the global stylesheet (dev-global.css / mashlib) */
15021
-
15022
- /* ── Named pane (rendered sub-pane) ──────────────────────────── */
15023
-
15045
+ ___CSS_LOADER_EXPORT___.push([module.id, `/* ── Named pane (rendered sub-pane) ──────────────────────────── */
15024
15046
  .contactPane .namedPane {
15025
15047
  border: 0.1em solid var(--color-text-muted);
15026
15048
  border-radius: var(--border-radius-base);
@@ -15029,11 +15051,11 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* webidControl.js styles — extracte
15029
15051
  /* ── Persona row ─────────────────────────────────────────────── */
15030
15052
 
15031
15053
  .contactPane .personaRow {
15032
- padding: 0.2em;
15054
+ padding: var(--spacing-xs);
15033
15055
  }
15034
15056
 
15035
15057
  .contactPane .personaRow--webid {
15036
- background-color: #ffe6ff;
15058
+ background-color: var(--color-info-bg);
15037
15059
  }
15038
15060
 
15039
15061
  /* ── Full-width elements ─────────────────────────────────────── */
@@ -15053,17 +15075,17 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* webidControl.js styles — extracte
15053
15075
  /* ── Section heading ─────────────────────────────────────────── */
15054
15076
 
15055
15077
  .contactPane .webidHeading {
15056
- font-size: 110%;
15078
+ font-size: var(--font-size-lg);
15057
15079
  font-weight: bold;
15058
15080
  color: var(--color-primary);
15059
- padding: 0.2em;
15060
- margin: 0.7em 0;
15081
+ padding: var(--spacing-xs);
15082
+ margin: var(--spacing-sm) 0;
15061
15083
  }
15062
15084
 
15063
15085
  /* ── Prompt text ─────────────────────────────────────────────── */
15064
15086
 
15065
15087
  .contactPane .webidPrompt {
15066
- padding: 0.7em;
15088
+ padding: var(--spacing-sm);
15067
15089
  border: none;
15068
15090
  font-size: var(--font-size-base);
15069
15091
  white-space: pre-wrap;
@@ -15078,7 +15100,7 @@ ___CSS_LOADER_EXPORT___.push([module.id, `/* webidControl.js styles — extracte
15078
15100
  .contactPane .collapsed {
15079
15101
  visibility: collapse;
15080
15102
  }
15081
- `, "",{"version":3,"sources":["webpack://./src/styles/webidControl.css"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,qFAAqF;;AAErF,mEAAmE;;AAEnE;EACE,2CAA2C;EAC3C,wCAAwC;AAC1C;;AAEA,mEAAmE;;AAEnE;EACE,cAAc;AAChB;;AAEA;EACE,yBAAyB;AAC3B;;AAEA,mEAAmE;;AAEnE;EACE,WAAW;AACb;;AAEA,mEAAmE;;AAEnE;EACE,YAAY;EACZ,6BAA6B;EAC7B,YAAY;AACd;;AAEA,mEAAmE;;AAEnE;EACE,eAAe;EACf,iBAAiB;EACjB,2BAA2B;EAC3B,cAAc;EACd,eAAe;AACjB;;AAEA,mEAAmE;;AAEnE;EACE,cAAc;EACd,YAAY;EACZ,gCAAgC;EAChC,qBAAqB;AACvB;;AAEA,mEAAmE;;AAEnE;EACE,aAAa;AACf;;AAEA;EACE,oBAAoB;AACtB","sourcesContent":["/* webidControl.js styles — extracted from inline styles */\n/* Uses CSS custom properties from the global stylesheet (dev-global.css / mashlib) */\n\n/* ── Named pane (rendered sub-pane) ──────────────────────────── */\n\n.contactPane .namedPane {\n border: 0.1em solid var(--color-text-muted);\n border-radius: var(--border-radius-base);\n}\n\n/* ── Persona row ─────────────────────────────────────────────── */\n\n.contactPane .personaRow {\n padding: 0.2em;\n}\n\n.contactPane .personaRow--webid {\n background-color: #ffe6ff;\n}\n\n/* ── Full-width elements ─────────────────────────────────────── */\n\n.contactPane .fullWidth {\n width: 100%;\n}\n\n/* ── Open/close profile button ───────────────────────────────── */\n\n.contactPane .personaOpenButton {\n float: right;\n background-color: transparent;\n border: none;\n}\n\n/* ── Section heading ─────────────────────────────────────────── */\n\n.contactPane .webidHeading {\n font-size: 110%;\n font-weight: bold;\n color: var(--color-primary);\n padding: 0.2em;\n margin: 0.7em 0;\n}\n\n/* ── Prompt text ─────────────────────────────────────────────── */\n\n.contactPane .webidPrompt {\n padding: 0.7em;\n border: none;\n font-size: var(--font-size-base);\n white-space: pre-wrap;\n}\n\n/* ── Visibility / display helpers ────────────────────────────── */\n\n.contactPane .hidden {\n display: none;\n}\n\n.contactPane .collapsed {\n visibility: collapse;\n}\n"],"sourceRoot":""}]);
15103
+ `, "",{"version":3,"sources":["webpack://./src/styles/webidControl.css"],"names":[],"mappings":"AAAA,mEAAmE;AACnE;EACE,2CAA2C;EAC3C,wCAAwC;AAC1C;;AAEA,mEAAmE;;AAEnE;EACE,0BAA0B;AAC5B;;AAEA;EACE,sCAAsC;AACxC;;AAEA,mEAAmE;;AAEnE;EACE,WAAW;AACb;;AAEA,mEAAmE;;AAEnE;EACE,YAAY;EACZ,6BAA6B;EAC7B,YAAY;AACd;;AAEA,mEAAmE;;AAEnE;EACE,8BAA8B;EAC9B,iBAAiB;EACjB,2BAA2B;EAC3B,0BAA0B;EAC1B,2BAA2B;AAC7B;;AAEA,mEAAmE;;AAEnE;EACE,0BAA0B;EAC1B,YAAY;EACZ,gCAAgC;EAChC,qBAAqB;AACvB;;AAEA,mEAAmE;;AAEnE;EACE,aAAa;AACf;;AAEA;EACE,oBAAoB;AACtB","sourcesContent":["/* ── Named pane (rendered sub-pane) ──────────────────────────── */\n.contactPane .namedPane {\n border: 0.1em solid var(--color-text-muted);\n border-radius: var(--border-radius-base);\n}\n\n/* ── Persona row ─────────────────────────────────────────────── */\n\n.contactPane .personaRow {\n padding: var(--spacing-xs);\n}\n\n.contactPane .personaRow--webid {\n background-color: var(--color-info-bg);\n}\n\n/* ── Full-width elements ─────────────────────────────────────── */\n\n.contactPane .fullWidth {\n width: 100%;\n}\n\n/* ── Open/close profile button ───────────────────────────────── */\n\n.contactPane .personaOpenButton {\n float: right;\n background-color: transparent;\n border: none;\n}\n\n/* ── Section heading ─────────────────────────────────────────── */\n\n.contactPane .webidHeading {\n font-size: var(--font-size-lg);\n font-weight: bold;\n color: var(--color-primary);\n padding: var(--spacing-xs);\n margin: var(--spacing-sm) 0;\n}\n\n/* ── Prompt text ─────────────────────────────────────────────── */\n\n.contactPane .webidPrompt {\n padding: var(--spacing-sm);\n border: none;\n font-size: var(--font-size-base);\n white-space: pre-wrap;\n}\n\n/* ── Visibility / display helpers ────────────────────────────── */\n\n.contactPane .hidden {\n display: none;\n}\n\n.contactPane .collapsed {\n visibility: collapse;\n}\n"],"sourceRoot":""}]);
15082
15104
  // Exports
15083
15105
  /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
15084
15106
 
@@ -15377,13 +15399,13 @@ module.exports = insertStyleElement;
15377
15399
  /***/ },
15378
15400
 
15379
15401
  /***/ 56
15380
- (module, __unused_webpack_exports, __nested_webpack_require_83786__) {
15402
+ (module, __unused_webpack_exports, __nested_webpack_require_88507__) {
15381
15403
 
15382
15404
 
15383
15405
 
15384
15406
  /* istanbul ignore next */
15385
15407
  function setAttributesWithoutAttributes(styleElement) {
15386
- var nonce = true ? __nested_webpack_require_83786__.nc : 0;
15408
+ var nonce = true ? __nested_webpack_require_88507__.nc : 0;
15387
15409
  if (nonce) {
15388
15410
  styleElement.setAttribute("nonce", nonce);
15389
15411
  }
@@ -15513,7 +15535,7 @@ module.exports = __WEBPACK_EXTERNAL_MODULE__104__;
15513
15535
  /******/ var __webpack_module_cache__ = {};
15514
15536
  /******/
15515
15537
  /******/ // The require function
15516
- /******/ function __nested_webpack_require_86951__(moduleId) {
15538
+ /******/ function __nested_webpack_require_91672__(moduleId) {
15517
15539
  /******/ // Check if module is in cache
15518
15540
  /******/ var cachedModule = __webpack_module_cache__[moduleId];
15519
15541
  /******/ if (cachedModule !== undefined) {
@@ -15527,24 +15549,24 @@ module.exports = __WEBPACK_EXTERNAL_MODULE__104__;
15527
15549
  /******/ };
15528
15550
  /******/
15529
15551
  /******/ // Execute the module function
15530
- /******/ __webpack_modules__[moduleId](module, module.exports, __nested_webpack_require_86951__);
15552
+ /******/ __webpack_modules__[moduleId](module, module.exports, __nested_webpack_require_91672__);
15531
15553
  /******/
15532
15554
  /******/ // Return the exports of the module
15533
15555
  /******/ return module.exports;
15534
15556
  /******/ }
15535
15557
  /******/
15536
15558
  /******/ // expose the modules object (__webpack_modules__)
15537
- /******/ __nested_webpack_require_86951__.m = __webpack_modules__;
15559
+ /******/ __nested_webpack_require_91672__.m = __webpack_modules__;
15538
15560
  /******/
15539
15561
  /************************************************************************/
15540
15562
  /******/ /* webpack/runtime/compat get default export */
15541
15563
  /******/ (() => {
15542
15564
  /******/ // getDefaultExport function for compatibility with non-harmony modules
15543
- /******/ __nested_webpack_require_86951__.n = (module) => {
15565
+ /******/ __nested_webpack_require_91672__.n = (module) => {
15544
15566
  /******/ var getter = module && module.__esModule ?
15545
15567
  /******/ () => (module['default']) :
15546
15568
  /******/ () => (module);
15547
- /******/ __nested_webpack_require_86951__.d(getter, { a: getter });
15569
+ /******/ __nested_webpack_require_91672__.d(getter, { a: getter });
15548
15570
  /******/ return getter;
15549
15571
  /******/ };
15550
15572
  /******/ })();
@@ -15552,9 +15574,9 @@ module.exports = __WEBPACK_EXTERNAL_MODULE__104__;
15552
15574
  /******/ /* webpack/runtime/define property getters */
15553
15575
  /******/ (() => {
15554
15576
  /******/ // define getter functions for harmony exports
15555
- /******/ __nested_webpack_require_86951__.d = (exports, definition) => {
15577
+ /******/ __nested_webpack_require_91672__.d = (exports, definition) => {
15556
15578
  /******/ for(var key in definition) {
15557
- /******/ if(__nested_webpack_require_86951__.o(definition, key) && !__nested_webpack_require_86951__.o(exports, key)) {
15579
+ /******/ if(__nested_webpack_require_91672__.o(definition, key) && !__nested_webpack_require_91672__.o(exports, key)) {
15558
15580
  /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
15559
15581
  /******/ }
15560
15582
  /******/ }
@@ -15563,12 +15585,12 @@ module.exports = __WEBPACK_EXTERNAL_MODULE__104__;
15563
15585
  /******/
15564
15586
  /******/ /* webpack/runtime/hasOwnProperty shorthand */
15565
15587
  /******/ (() => {
15566
- /******/ __nested_webpack_require_86951__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
15588
+ /******/ __nested_webpack_require_91672__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
15567
15589
  /******/ })();
15568
15590
  /******/
15569
15591
  /******/ /* webpack/runtime/jsonp chunk loading */
15570
15592
  /******/ (() => {
15571
- /******/ __nested_webpack_require_86951__.b = (typeof document !== 'undefined' && document.baseURI) || self.location.href;
15593
+ /******/ __nested_webpack_require_91672__.b = (typeof document !== 'undefined' && document.baseURI) || self.location.href;
15572
15594
  /******/
15573
15595
  /******/ // object to store loaded and loading chunks
15574
15596
  /******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
@@ -15594,43 +15616,43 @@ module.exports = __WEBPACK_EXTERNAL_MODULE__104__;
15594
15616
  /******/
15595
15617
  /******/ /* webpack/runtime/nonce */
15596
15618
  /******/ (() => {
15597
- /******/ __nested_webpack_require_86951__.nc = undefined;
15619
+ /******/ __nested_webpack_require_91672__.nc = undefined;
15598
15620
  /******/ })();
15599
15621
  /******/
15600
15622
  /************************************************************************/
15601
15623
  var __nested_webpack_exports__ = {};
15602
15624
 
15603
15625
  // EXPORTS
15604
- __nested_webpack_require_86951__.d(__nested_webpack_exports__, {
15626
+ __nested_webpack_require_91672__.d(__nested_webpack_exports__, {
15605
15627
  "default": () => (/* binding */ src_contactsPane)
15606
15628
  });
15607
15629
 
15608
15630
  // EXTERNAL MODULE: external {"commonjs":"solid-logic","commonjs2":"solid-logic","amd":"solid-logic","root":"SolidLogic"}
15609
- var external_commonjs_solid_logic_commonjs2_solid_logic_amd_solid_logic_root_SolidLogic_ = __nested_webpack_require_86951__(941);
15631
+ var external_commonjs_solid_logic_commonjs2_solid_logic_amd_solid_logic_root_SolidLogic_ = __nested_webpack_require_91672__(941);
15610
15632
  // EXTERNAL MODULE: external {"commonjs":"solid-ui","commonjs2":"solid-ui","amd":"solid-ui","root":"UI"}
15611
- var external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_ = __nested_webpack_require_86951__(104);
15633
+ var external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_ = __nested_webpack_require_91672__(104);
15612
15634
  // EXTERNAL MODULE: external {"commonjs":"rdflib","commonjs2":"rdflib","amd":"rdflib","root":"$rdf"}
15613
- var external_commonjs_rdflib_commonjs2_rdflib_amd_rdflib_root_$rdf_ = __nested_webpack_require_86951__(53);
15635
+ var external_commonjs_rdflib_commonjs2_rdflib_amd_rdflib_root_$rdf_ = __nested_webpack_require_91672__(53);
15614
15636
  // EXTERNAL MODULE: ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js
15615
- var injectStylesIntoStyleTag = __nested_webpack_require_86951__(72);
15616
- var injectStylesIntoStyleTag_default = /*#__PURE__*/__nested_webpack_require_86951__.n(injectStylesIntoStyleTag);
15637
+ var injectStylesIntoStyleTag = __nested_webpack_require_91672__(72);
15638
+ var injectStylesIntoStyleTag_default = /*#__PURE__*/__nested_webpack_require_91672__.n(injectStylesIntoStyleTag);
15617
15639
  // EXTERNAL MODULE: ./node_modules/style-loader/dist/runtime/styleDomAPI.js
15618
- var styleDomAPI = __nested_webpack_require_86951__(825);
15619
- var styleDomAPI_default = /*#__PURE__*/__nested_webpack_require_86951__.n(styleDomAPI);
15640
+ var styleDomAPI = __nested_webpack_require_91672__(825);
15641
+ var styleDomAPI_default = /*#__PURE__*/__nested_webpack_require_91672__.n(styleDomAPI);
15620
15642
  // EXTERNAL MODULE: ./node_modules/style-loader/dist/runtime/insertBySelector.js
15621
- var insertBySelector = __nested_webpack_require_86951__(659);
15622
- var insertBySelector_default = /*#__PURE__*/__nested_webpack_require_86951__.n(insertBySelector);
15643
+ var insertBySelector = __nested_webpack_require_91672__(659);
15644
+ var insertBySelector_default = /*#__PURE__*/__nested_webpack_require_91672__.n(insertBySelector);
15623
15645
  // EXTERNAL MODULE: ./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js
15624
- var setAttributesWithoutAttributes = __nested_webpack_require_86951__(56);
15625
- var setAttributesWithoutAttributes_default = /*#__PURE__*/__nested_webpack_require_86951__.n(setAttributesWithoutAttributes);
15646
+ var setAttributesWithoutAttributes = __nested_webpack_require_91672__(56);
15647
+ var setAttributesWithoutAttributes_default = /*#__PURE__*/__nested_webpack_require_91672__.n(setAttributesWithoutAttributes);
15626
15648
  // EXTERNAL MODULE: ./node_modules/style-loader/dist/runtime/insertStyleElement.js
15627
- var insertStyleElement = __nested_webpack_require_86951__(540);
15628
- var insertStyleElement_default = /*#__PURE__*/__nested_webpack_require_86951__.n(insertStyleElement);
15649
+ var insertStyleElement = __nested_webpack_require_91672__(540);
15650
+ var insertStyleElement_default = /*#__PURE__*/__nested_webpack_require_91672__.n(insertStyleElement);
15629
15651
  // EXTERNAL MODULE: ./node_modules/style-loader/dist/runtime/styleTagTransform.js
15630
- var styleTagTransform = __nested_webpack_require_86951__(113);
15631
- var styleTagTransform_default = /*#__PURE__*/__nested_webpack_require_86951__.n(styleTagTransform);
15652
+ var styleTagTransform = __nested_webpack_require_91672__(113);
15653
+ var styleTagTransform_default = /*#__PURE__*/__nested_webpack_require_91672__.n(styleTagTransform);
15632
15654
  // EXTERNAL MODULE: ./node_modules/css-loader/dist/cjs.js!./src/styles/webidControl.css
15633
- var webidControl = __nested_webpack_require_86951__(886);
15655
+ var webidControl = __nested_webpack_require_91672__(886);
15634
15656
  ;// ./src/styles/webidControl.css
15635
15657
 
15636
15658
 
@@ -15879,6 +15901,8 @@ async function renderIdControl (person, dataBrowserContext, options) {
15879
15901
  profileIsVisible = !profileIsVisible
15880
15902
  main.classList.toggle('collapsed', !profileIsVisible)
15881
15903
  openButton.children[0].src = profileIsVisible ? UP_ARROW : DOWN_ARROW // @@ fragile
15904
+ openButton.setAttribute('aria-expanded', profileIsVisible ? 'true' : 'false')
15905
+ openButton.setAttribute('aria-label', profileIsVisible ? 'Collapse profile' : 'Expand profile')
15882
15906
  }
15883
15907
  function renderNewRow (webidObject) {
15884
15908
  const webid = new external_commonjs_rdflib_commonjs2_rdflib_amd_rdflib_root_$rdf_.Literal(webidObject.uri)
@@ -15922,6 +15946,8 @@ async function renderIdControl (person, dataBrowserContext, options) {
15922
15946
  const rhs = nav.children[2]
15923
15947
  const openButton = rhs.appendChild(widgets.button(dom, DOWN_ARROW, 'View', profileOpenHandler))
15924
15948
  openButton.classList.add('personaOpenButton')
15949
+ openButton.setAttribute('aria-expanded', 'true')
15950
+ openButton.setAttribute('aria-label', 'Collapse profile')
15925
15951
  const paneName = isOrganization(person) || isOrganization(persona) ? 'profile' : 'profile' // was default for org
15926
15952
 
15927
15953
  widgets.publicData.loadPublicDataThing(kb, person, persona).then(_resp => {
@@ -16004,8 +16030,114 @@ async function renderIdControl (person, dataBrowserContext, options) {
16004
16030
  return div
16005
16031
  }
16006
16032
 
16033
+ ;// ./src/localUtils.js
16034
+
16035
+
16036
+
16037
+
16038
+ const localUtils_kb = external_commonjs_solid_logic_commonjs2_solid_logic_amd_solid_logic_root_SolidLogic_.store
16039
+ const localUtils_ns = external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.ns
16040
+ let dom
16041
+
16042
+ function setDom (d) {
16043
+ dom = d
16044
+ }
16045
+
16046
+ /**
16047
+ * Normalize group URIs to ensure consistent representation.
16048
+ * Groups should be referenced with fragment #this, e.g., ...Group/AnotherGroup.ttl#this
16049
+ * If a group URI ends with .ttl (without #this), add #this
16050
+ * @param {string} uri - The group URI to normalize
16051
+ * @returns {string} The normalized group URI
16052
+ */
16053
+ function normalizeGroupUri (uri) {
16054
+ if (uri && uri.endsWith('.ttl')) {
16055
+ return uri + '#this'
16056
+ }
16057
+ return uri
16058
+ }
16059
+
16060
+ function complain (div, d, message) {
16061
+ error('contactsPane: ' + message)
16062
+ external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.errorMessageBlock(d, message, 'pink')
16063
+ }
16064
+ function complainIfBad (div, dom, ok, body) {
16065
+ if (!ok) {
16066
+ complain(div, dom, 'Error: ' + body)
16067
+ }
16068
+ }
16069
+
16070
+ function localUtils_getSameAs (kb, item, doc) {
16071
+ return kb.each(item, localUtils_ns.owl('sameAs'), null, doc).concat(
16072
+ kb.each(null, localUtils_ns.owl('sameAs'), item, doc))
16073
+ }
16074
+ // For deleting an addressbook sub-folder eg person - use with care!
16075
+ // @@ move to solid-logic
16076
+ function deleteRecursive (kb, folder) {
16077
+ return new Promise(function (resolve, reject) {
16078
+ kb.fetcher.load(folder).then(function () {
16079
+ const promises = kb.each(folder, localUtils_ns.ldp('contains')).map(file => {
16080
+ if (kb.holds(file, localUtils_ns.rdf('type'), localUtils_ns.ldp('BasicContainer'))) {
16081
+ return deleteRecursive(kb, file)
16082
+ } else {
16083
+ log('Recursie delete - we delete file ' + file.uri)
16084
+ return kb.fetcher.webOperation('DELETE', file.uri)
16085
+ }
16086
+ })
16087
+ log('Recursie delete - we delete folder ' + folder.uri)
16088
+ promises.push(kb.fetcher.webOperation('DELETE', folder.uri))
16089
+ Promise.all(promises).then(_res => {
16090
+ resolve()
16091
+ }).catch(reject)
16092
+ }).catch(reject)
16093
+ })
16094
+ }
16095
+
16096
+ // In a LDP work, deletes the whole document describing a thing
16097
+ // plus patch out ALL mentiosn of it! Use with care!
16098
+ // beware of other data picked up from other places being smushed
16099
+ // together and then deleted.
16100
+ async function deleteThingAndDoc (x) {
16101
+ const name = nameFor(x)
16102
+ if (!confirm('Really DELETE ' + name + '?')) {
16103
+ return
16104
+ }
16105
+ log('deleteThingAndDoc - to be deleted ' + x)
16106
+ const ds = localUtils_kb.statementsMatching(x).concat(localUtils_kb.statementsMatching(undefined, undefined, x))
16107
+ try {
16108
+ await localUtils_kb.updater.updateMany(ds)
16109
+ await localUtils_kb.fetcher.delete(x.doc())
16110
+ log('deleteThingAndDoc - deleted')
16111
+ } catch (err) {
16112
+ external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.complain('Error deleting ' + x + ': ' + err)
16113
+ throw err
16114
+ }
16115
+ }
16116
+
16117
+ function compareForSort (self, other) {
16118
+ let s = nameFor(self)
16119
+ let o = nameFor(other)
16120
+ if (s && o) {
16121
+ s = s.toLowerCase()
16122
+ o = o.toLowerCase()
16123
+ if (s > o) return 1
16124
+ if (s < o) return -1
16125
+ }
16126
+ if (self.uri > other.uri) return 1
16127
+ if (self.uri < other.uri) return -1
16128
+ return 0
16129
+ }
16130
+
16131
+ // organization-name is a hack for Mac records with no FN which is mandatory.
16132
+ function nameFor (x) {
16133
+ const name =
16134
+ localUtils_kb.any(x, localUtils_ns.vcard('fn')) ||
16135
+ localUtils_kb.any(x, localUtils_ns.foaf('name')) ||
16136
+ localUtils_kb.any(x, localUtils_ns.vcard('organization-name'))
16137
+ return name ? name.value : '???'
16138
+ }
16139
+
16007
16140
  ;// ./src/contactLogic.js
16008
- // Logic for solid contacts
16009
16141
 
16010
16142
 
16011
16143
 
@@ -16132,6 +16264,7 @@ async function addPersonToGroup (thing, group) {
16132
16264
  try {
16133
16265
  await contactLogic_kb.fetcher.load(toBeFetched)
16134
16266
  } catch (e) {
16267
+ //complain(dom, 'Error loading data for ' + thing + ' or ' + group + ': ' + e)
16135
16268
  throw new Error('addPersonToGroup: ' + e)
16136
16269
  }
16137
16270
 
@@ -16209,18 +16342,13 @@ function isLocal (group, item) {
16209
16342
  return local
16210
16343
  }
16211
16344
 
16212
- function contactLogic_getSameAs (kb, item, doc) {
16213
- return kb.each(item, contactLogic_ns.owl('sameAs'), null, doc).concat(
16214
- kb.each(null, contactLogic_ns.owl('sameAs'), item, doc))
16215
- }
16216
-
16217
16345
  async function getDataModelIssues (groups) {
16218
16346
  const del = []
16219
16347
  const ins = []
16220
16348
  groups.forEach(group => {
16221
16349
  const members = contactLogic_kb.each(group, contactLogic_ns.vcard('hasMember'), null, group.doc())
16222
16350
  members.forEach((member) => {
16223
- const others = contactLogic_getSameAs(contactLogic_kb, member, group.doc())
16351
+ const others = localUtils_getSameAs(contactLogic_kb, member, group.doc())
16224
16352
  if (others.length && isLocal(group, member)) { // Problem: local ID used instead of webID
16225
16353
  for (const other of others) {
16226
16354
  if (!isLocal(group, other)) { // Let's use this one as the immediate member for CSS ACLs'
@@ -16239,96 +16367,6 @@ async function getDataModelIssues (groups) {
16239
16367
 
16240
16368
  // Ends
16241
16369
 
16242
- ;// ./src/localUtils.js
16243
-
16244
-
16245
-
16246
-
16247
- const localUtils_kb = external_commonjs_solid_logic_commonjs2_solid_logic_amd_solid_logic_root_SolidLogic_.store
16248
- const localUtils_ns = external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.ns
16249
-
16250
- function complain (div, dom, message) {
16251
- log('contactsPane: ' + message)
16252
- div.appendChild(external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.errorMessageBlock(dom, message, 'pink'))
16253
- }
16254
- function complainIfBad (div, dom, ok, body) {
16255
- if (!ok) {
16256
- complain(div, dom, 'Error: ' + body)
16257
- }
16258
- }
16259
-
16260
- function localUtils_getSameAs (kb, item, doc) {
16261
- return kb.each(item, localUtils_ns.owl('sameAs'), null, doc).concat(
16262
- kb.each(null, localUtils_ns.owl('sameAs'), item, doc))
16263
- }
16264
- // For deleting an addressbook sub-folder eg person - use with care!
16265
- // @@ move to solid-logic
16266
- function deleteRecursive (kb, folder) {
16267
- return new Promise(function (resolve) {
16268
- kb.fetcher.load(folder).then(function () {
16269
- const promises = kb.each(folder, localUtils_ns.ldp('contains')).map(file => {
16270
- if (kb.holds(file, localUtils_ns.rdf('type'), localUtils_ns.ldp('BasicContainer'))) {
16271
- return deleteRecursive(kb, file)
16272
- } else {
16273
- log('deleteRecursive file: ' + file)
16274
- if (!confirm(' Really DELETE File ' + file)) {
16275
- throw new Error('User aborted delete file')
16276
- }
16277
- return kb.fetcher.webOperation('DELETE', file.uri)
16278
- }
16279
- })
16280
- log('deleteRecirsive folder: ' + folder)
16281
- if (!confirm(' Really DELETE folder ' + folder)) {
16282
- throw new Error('User aborted delete file')
16283
- }
16284
- promises.push(kb.fetcher.webOperation('DELETE', folder.uri))
16285
- Promise.all(promises).then(_res => {
16286
- resolve()
16287
- })
16288
- })
16289
- })
16290
- }
16291
-
16292
- // In a LDP work, deletes the whole document describing a thing
16293
- // plus patch out ALL mentiosn of it! Use with care!
16294
- // beware of other data picked up from other places being smushed
16295
- // together and then deleted.
16296
- async function deleteThingAndDoc (x) {
16297
- log('deleteThingAndDoc: ' + x)
16298
- const ds = localUtils_kb.statementsMatching(x).concat(localUtils_kb.statementsMatching(undefined, undefined, x))
16299
- try {
16300
- await localUtils_kb.updater.updateMany(ds)
16301
- log('Deleting resoure ' + x.doc())
16302
- await localUtils_kb.fetcher.delete(x.doc())
16303
- log('Delete thing ' + x + ': complete.')
16304
- } catch (err) {
16305
- complain('Error deleting thing ' + x + ': ' + err)
16306
- }
16307
- }
16308
-
16309
- function compareForSort (self, other) {
16310
- let s = nameFor(self)
16311
- let o = nameFor(other)
16312
- if (s && o) {
16313
- s = s.toLowerCase()
16314
- o = o.toLowerCase()
16315
- if (s > o) return 1
16316
- if (s < o) return -1
16317
- }
16318
- if (self.uri > other.uri) return 1
16319
- if (self.uri < other.uri) return -1
16320
- return 0
16321
- }
16322
-
16323
- // organization-name is a hack for Mac records with no FN which is mandatory.
16324
- function nameFor (x) {
16325
- const name =
16326
- localUtils_kb.any(x, localUtils_ns.vcard('fn')) ||
16327
- localUtils_kb.any(x, localUtils_ns.foaf('name')) ||
16328
- localUtils_kb.any(x, localUtils_ns.vcard('organization-name'))
16329
- return name ? name.value : '???'
16330
- }
16331
-
16332
16370
  ;// ./src/mintNewAddressBook.js
16333
16371
 
16334
16372
 
@@ -16501,7 +16539,7 @@ function mintNewAddressBook (dataBrowserContext, context) {
16501
16539
  }
16502
16540
 
16503
16541
  // EXTERNAL MODULE: ./node_modules/css-loader/dist/cjs.js!./src/styles/mugshotGallery.css
16504
- var mugshotGallery = __nested_webpack_require_86951__(715);
16542
+ var mugshotGallery = __nested_webpack_require_91672__(715);
16505
16543
  ;// ./src/styles/mugshotGallery.css
16506
16544
 
16507
16545
 
@@ -16748,6 +16786,7 @@ function renderMugshotGallery (dom, subject) {
16748
16786
  function elementForImage (image) {
16749
16787
  const img = dom.createElement('img')
16750
16788
  img.classList.add('mugshotImage')
16789
+ img.setAttribute('alt', image ? 'Contact photo' : 'Drop photo here')
16751
16790
  external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.makeDropTarget(
16752
16791
  img,
16753
16792
  handleURIsDroppedOnMugshot,
@@ -16802,7 +16841,9 @@ function renderMugshotGallery (dom, subject) {
16802
16841
  const button = external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.button(
16803
16842
  dom,
16804
16843
  external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.icons.iconBase + 'noun_925021.svg',
16805
- 'Drag here to delete'
16844
+ 'Drag here to delete',
16845
+ undefined,
16846
+ { 'aria-label': 'Delete photo - drag image here' }
16806
16847
  )
16807
16848
  async function droppedURIHandler (uris) {
16808
16849
  const images = mugshotGallery_kb
@@ -16867,7 +16908,7 @@ function renderMugshotGallery (dom, subject) {
16867
16908
  }
16868
16909
 
16869
16910
  // EXTERNAL MODULE: ./node_modules/css-loader/dist/cjs.js!./src/styles/groupMembership.css
16870
- var groupMembership = __nested_webpack_require_86951__(93);
16911
+ var groupMembership = __nested_webpack_require_91672__(93);
16871
16912
  ;// ./src/styles/groupMembership.css
16872
16913
 
16873
16914
 
@@ -16902,6 +16943,7 @@ var groupMembership_update = injectStylesIntoStyleTag_default()(groupMembership/
16902
16943
 
16903
16944
 
16904
16945
 
16946
+
16905
16947
  const groupMembershipControl_ns = external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.ns
16906
16948
  const groupMembershipControl_kb = external_commonjs_solid_logic_commonjs2_solid_logic_amd_solid_logic_root_SolidLogic_.store
16907
16949
 
@@ -16909,7 +16951,7 @@ const groupMembershipControl_kb = external_commonjs_solid_logic_commonjs2_solid_
16909
16951
  function groupMembershipControl_groupMembership (person) {
16910
16952
  let groups = groupMembershipControl_kb.statementsMatching(null, groupMembershipControl_ns.owl('sameAs'), person).map(st => st.why)
16911
16953
  .concat(groupMembershipControl_kb.each(null, groupMembershipControl_ns.vcard('hasMember'), person))
16912
- const strings = new Set(groups.map(group => group.uri)) // remove dups
16954
+ const strings = new Set(groups.map(group => normalizeGroupUri(group.uri))) // remove dups with normalized URIs
16913
16955
  groups = [...strings].map(uri => groupMembershipControl_kb.sym(uri))
16914
16956
  return groups
16915
16957
  }
@@ -16945,12 +16987,13 @@ async function renderGroupMemberships (person, context) {
16945
16987
  del = del.concat(kb.statementsMatching(undefined, undefined, webid, group.doc()))
16946
16988
  }
16947
16989
  })
16948
- kb.updater.update(del, [], function (uri, ok, err) {
16949
- if (!ok) {
16950
- const message = 'Error removing member from group ' + group + ': ' + err
16951
- container.appendChild(external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.errorMessageBlock(dom, message, 'pink'))
16952
- }
16953
- })
16990
+ try {
16991
+ await kb.updater.update(del, [])
16992
+ } catch (err) {
16993
+ const message = 'Error removing member from group ' + group + ': ' + err
16994
+ container.appendChild(external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.errorMessageBlock(dom, message, 'pink'))
16995
+ return
16996
+ }
16954
16997
  log('Removed ' + pname + ' from group ' + gname)
16955
16998
  // to allow refresh of card groupList
16956
16999
  kb.fetcher.unload(group.doc())
@@ -17052,7 +17095,7 @@ const individualAndOrganizationForm_namespaceObject = "# This turtle file define
17052
17095
  ;// ./src/ontology/vcard.ttl
17053
17096
  const vcard_namespaceObject = "@prefix : <http://www.w3.org/2006/vcard/ns#> .\n@prefix owl: <http://www.w3.org/2002/07/owl#> .\n@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n@prefix xml: <http://www.w3.org/XML/1998/namespace> .\n@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n\n:Acquaintance a owl:Class ;\n rdfs:label \"Acquaintance\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Agent a owl:Class ;\n rdfs:label \"Agent\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:BBS a owl:Class ;\n rdfs:label \"BBS\"@en ;\n rdfs:comment \"This class is deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :TelephoneType ;\n owl:deprecated true .\n\n:Car a owl:Class ;\n rdfs:label \"Car\"@en ;\n rdfs:comment \"This class is deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :TelephoneType ;\n owl:deprecated true .\n\n:Cell a owl:Class ;\n rdfs:label \"Cell\"@en ;\n rdfs:comment \"Also called mobile telephone\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :TelephoneType .\n\n:Child a owl:Class ;\n rdfs:label \"Child\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Colleague a owl:Class ;\n rdfs:label \"Colleague\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Contact a owl:Class ;\n rdfs:label \"Contact\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Coresident a owl:Class ;\n rdfs:label \"Coresident\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Coworker a owl:Class ;\n rdfs:label \"Coworker\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Crush a owl:Class ;\n rdfs:label \"Crush\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Date a owl:Class ;\n rdfs:label \"Date\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Dom a owl:Class ;\n rdfs:label \"Dom\"@en ;\n rdfs:comment \"This class is deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Type ;\n owl:deprecated true .\n\n:Emergency a owl:Class ;\n rdfs:label \"Emergency\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Fax a owl:Class ;\n rdfs:label \"Fax\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :TelephoneType .\n\n:Female a owl:Class ;\n rdfs:label \"Female\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Gender .\n\n:Friend a owl:Class ;\n rdfs:label \"Friend\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Home a owl:Class ;\n rdfs:label \"Home\"@en ;\n rdfs:comment \"This implies that the property is related to an individual's personal life\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Type .\n\n:ISDN a owl:Class ;\n rdfs:label \"ISDN\"@en ;\n rdfs:comment \"This class is deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Type ;\n owl:deprecated true .\n\n:Internet a owl:Class ;\n rdfs:label \"Internet\"@en ;\n rdfs:comment \"This class is deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Type ;\n owl:deprecated true .\n\n:Intl a owl:Class ;\n rdfs:label \"Intl\"@en ;\n rdfs:comment \"This class is deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Type ;\n owl:deprecated true .\n\n:Kin a owl:Class ;\n rdfs:label \"Kin\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Label a owl:Class ;\n rdfs:label \"Label\"@en ;\n rdfs:comment \"This class is deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Type ;\n owl:deprecated true .\n\n:Male a owl:Class ;\n rdfs:label \"Male\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Gender .\n\n:Me a owl:Class ;\n rdfs:label \"Me\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Met a owl:Class ;\n rdfs:label \"Met\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Modem a owl:Class ;\n rdfs:label \"Modem\"@en ;\n rdfs:comment \"This class is deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :TelephoneType ;\n owl:deprecated true .\n\n:Msg a owl:Class ;\n rdfs:label \"Msg\"@en ;\n rdfs:comment \"This class is deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :TelephoneType ;\n owl:deprecated true .\n\n:Muse a owl:Class ;\n rdfs:label \"Muse\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Neighbor a owl:Class ;\n rdfs:label \"Neighbor\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:None a owl:Class ;\n rdfs:label \"None\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Gender .\n\n:Other a owl:Class ;\n rdfs:label \"Other\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Gender .\n\n:PCS a owl:Class ;\n rdfs:label \"PCS\"@en ;\n rdfs:comment \"This class is deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :TelephoneType ;\n owl:deprecated true .\n\n:Pager a owl:Class ;\n rdfs:label \"Pager\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :TelephoneType .\n\n:Parcel a owl:Class ;\n rdfs:label \"Parcel\"@en ;\n rdfs:comment \"This class is deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Type ;\n owl:deprecated true .\n\n:Parent a owl:Class ;\n rdfs:label \"Parent\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Postal a owl:Class ;\n rdfs:label \"Postal\"@en ;\n rdfs:comment \"This class is deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Type ;\n owl:deprecated true .\n\n:Pref a owl:Class ;\n rdfs:label \"Pref\"@en ;\n rdfs:comment \"This class is deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Type ;\n owl:deprecated true .\n\n:Sibling a owl:Class ;\n rdfs:label \"Sibling\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Spouse a owl:Class ;\n rdfs:label \"Spouse\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Sweetheart a owl:Class ;\n rdfs:label \"Sweetheart\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :RelatedType .\n\n:Tel a owl:Class ;\n rdfs:label \"Tel\"@en ;\n rdfs:comment \"This class is deprecated. Use the hasTelephone object property.\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:deprecated true .\n\n:Text a owl:Class ;\n rdfs:label \"Text\"@en ;\n rdfs:comment \"Also called sms telephone\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :TelephoneType .\n\n:TextPhone a owl:Class ;\n rdfs:label \"Text phone\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :TelephoneType .\n\n:Unknown a owl:Class ;\n rdfs:label \"Unknown\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Gender .\n\n:Video a owl:Class ;\n rdfs:label \"Video\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :TelephoneType .\n\n:Voice a owl:Class ;\n rdfs:label \"Voice\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :TelephoneType .\n\n:Work a owl:Class ;\n rdfs:label \"Work\"@en ;\n rdfs:comment \"This implies that the property is related to an individual's work place\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Type .\n\n:X400 a owl:Class ;\n rdfs:label \"X400\"@en ;\n rdfs:comment \"This class is deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Type ;\n owl:deprecated true .\n\n:adr a owl:ObjectProperty ;\n rdfs:label \"address\"@en ;\n rdfs:comment \"This object property has been mapped\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :hasAddress .\n\n:agent a owl:ObjectProperty ;\n rdfs:label \"agent\"@en ;\n rdfs:comment \"This object property has been deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:deprecated true .\n\n:anniversary a owl:DatatypeProperty ;\n rdfs:label \"anniversary\"@en ;\n rdfs:comment \"The date of marriage, or equivalent, of the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range [ a rdfs:Datatype ;\n owl:unionOf ( xsd:dateTime xsd:gYear ) ] .\n\n:bday a owl:DatatypeProperty ;\n rdfs:label \"birth date\"@en ;\n rdfs:comment \"To specify the birth date of the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range [ a rdfs:Datatype ;\n owl:unionOf ( xsd:dateTime xsd:dateTimeStamp xsd:gYear ) ] .\n\n:category a owl:DatatypeProperty ;\n rdfs:label \"category\"@en ;\n rdfs:comment \"The category information about the object, also known as tags\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:class a owl:DatatypeProperty ;\n rdfs:label \"class\"@en ;\n rdfs:comment \"This data property has been deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:deprecated true .\n\n:email a owl:ObjectProperty ;\n rdfs:label \"email\"@en ;\n rdfs:comment \"This object property has been mapped\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :hasEmail .\n\n:extended-address a owl:DatatypeProperty ;\n rdfs:label \"extended address\"@en ;\n rdfs:comment \"This data property has been deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:deprecated true .\n\n:geo a owl:ObjectProperty ;\n rdfs:label \"geo\"@en ;\n rdfs:comment \"This object property has been mapped\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :hasGeo .\n\n:hasAdditionalName a owl:ObjectProperty ;\n rdfs:label \"has additional name\"@en ;\n rdfs:comment \"Used to support property parameters for the additional name data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasCalendarBusy a owl:ObjectProperty ;\n rdfs:label \"has calendar busy\"@en ;\n rdfs:comment \"To specify the busy time associated with the object. (Was called FBURL in RFC6350)\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasCalendarLink a owl:ObjectProperty ;\n rdfs:label \"has calendar link\"@en ;\n rdfs:comment \"To specify the calendar associated with the object. (Was called CALURI in RFC6350)\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasCalendarRequest a owl:ObjectProperty ;\n rdfs:label \"has calendar request\"@en ;\n rdfs:comment \"To specify the calendar user address to which a scheduling request be sent for the object. (Was called CALADRURI in RFC6350)\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasCategory a owl:ObjectProperty ;\n rdfs:label \"has category\"@en ;\n rdfs:comment \"Used to support property parameters for the category data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasCountryName a owl:ObjectProperty ;\n rdfs:label \"has country name\"@en ;\n rdfs:comment \"Used to support property parameters for the country name data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasFN a owl:ObjectProperty ;\n rdfs:label \"has formatted name\"@en ;\n rdfs:comment \"Used to support property parameters for the formatted name data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasFamilyName a owl:ObjectProperty ;\n rdfs:label \"has family name\"@en ;\n rdfs:comment \"Used to support property parameters for the family name data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasGender a owl:ObjectProperty ;\n rdfs:label \"has gender\"@en ;\n rdfs:comment \"To specify the sex or gender identity of the object. URIs are recommended to enable interoperable sex and gender codes to be used.\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasGivenName a owl:ObjectProperty ;\n rdfs:label \"has given name\"@en ;\n rdfs:comment \"Used to support property parameters for the given name data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasHonorificPrefix a owl:ObjectProperty ;\n rdfs:label \"has honorific prefix\"@en ;\n rdfs:comment \"Used to support property parameters for the honorific prefix data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasHonorificSuffix a owl:ObjectProperty ;\n rdfs:label \"has honorific suffix\"@en ;\n rdfs:comment \"Used to support property parameters for the honorific suffix data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasInstantMessage a owl:ObjectProperty ;\n rdfs:label \"has messaging\"@en ;\n rdfs:comment \"To specify the instant messaging and presence protocol communications with the object. (Was called IMPP in RFC6350)\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasLanguage a owl:ObjectProperty ;\n rdfs:label \"has language\"@en ;\n rdfs:comment \"Used to support property parameters for the language data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasLocality a owl:ObjectProperty ;\n rdfs:label \"has locality\"@en ;\n rdfs:comment \"Used to support property parameters for the locality data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasNickname a owl:ObjectProperty ;\n rdfs:label \"has nickname\"@en ;\n rdfs:comment \"Used to support property parameters for the nickname data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:seeAlso :nickname .\n\n:hasNote a owl:ObjectProperty ;\n rdfs:label \"has note\"@en ;\n rdfs:comment \"Used to support property parameters for the note data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasOrganizationName a owl:ObjectProperty ;\n rdfs:label \"has organization name\"@en ;\n rdfs:comment \"Used to support property parameters for the organization name data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasOrganizationUnit a owl:ObjectProperty ;\n rdfs:label \"has organization unit name\"@en ;\n rdfs:comment \"Used to support property parameters for the organization unit name data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasPostalCode a owl:ObjectProperty ;\n rdfs:label \"has postal code\"@en ;\n rdfs:comment \"Used to support property parameters for the postal code data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasRegion a owl:ObjectProperty ;\n rdfs:label \"has region\"@en ;\n rdfs:comment \"Used to support property parameters for the region data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasRelated a owl:ObjectProperty ;\n rdfs:label \"has related\"@en ;\n rdfs:comment \"To specify a relationship between another entity and the entity represented by this object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasRole a owl:ObjectProperty ;\n rdfs:label \"has role\"@en ;\n rdfs:comment \"Used to support property parameters for the role data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasSource a owl:ObjectProperty ;\n rdfs:label \"has source\"@en ;\n rdfs:comment \"To identify the source of directory information of the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasStreetAddress a owl:ObjectProperty ;\n rdfs:label \"has street address\"@en ;\n rdfs:comment \"Used to support property parameters for the street address data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasTitle a owl:ObjectProperty ;\n rdfs:label \"has title\"@en ;\n rdfs:comment \"Used to support property parameters for the title data property\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasUID a owl:ObjectProperty ;\n rdfs:label \"has uid\"@en ;\n rdfs:comment \"To specify a value that represents a globally unique identifier corresponding to the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasValue a owl:ObjectProperty ;\n rdfs:label \"has value\"@en ;\n rdfs:comment \"Used to indicate the resource value of an object property that requires property parameters\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:label a owl:DatatypeProperty ;\n rdfs:label \"label\"@en ;\n rdfs:comment \"This data property has been deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:deprecated true .\n\n:language a owl:DatatypeProperty ;\n rdfs:label \"language\"@en ;\n rdfs:comment \"To specify the language that may be used for contacting the object. May also be used as a property parameter.\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:latitude a owl:DatatypeProperty ;\n rdfs:label \"latitude\"@en ;\n rdfs:comment \"This data property has been deprecated. See hasGeo\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:deprecated true .\n\n:longitude a owl:DatatypeProperty ;\n rdfs:label \"longitude\"@en ;\n rdfs:comment \"This data property has been deprecated. See hasGeo\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:deprecated true .\n\n:mailer a owl:DatatypeProperty ;\n rdfs:label \"mailer\"@en ;\n rdfs:comment \"This data property has been deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:deprecated true .\n\n:note a owl:DatatypeProperty ;\n rdfs:label \"note\"@en ;\n rdfs:comment \"A note associated with the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:org a owl:ObjectProperty ;\n rdfs:label \"organization\"@en ;\n rdfs:comment \"This object property has been mapped. Use the organization-name data property.\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :organization-name .\n\n:organization-unit a owl:DatatypeProperty ;\n rdfs:label \"organizational unit name\"@en ;\n rdfs:comment \"To specify the organizational unit name associated with the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string ;\n rdfs:subPropertyOf :organization-name .\n\n:post-office-box a owl:DatatypeProperty ;\n rdfs:label \"post office box\"@en ;\n rdfs:comment \"This data property has been deprecated\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:deprecated true .\n\n:prodid a owl:DatatypeProperty ;\n rdfs:label \"product id\"@en ;\n rdfs:comment \"To specify the identifier for the product that created the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:rev a owl:DatatypeProperty ;\n rdfs:label \"revision\"@en ;\n rdfs:comment \"To specify revision information about the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:dateTime .\n\n:role a owl:DatatypeProperty ;\n rdfs:label \"role\"@en ;\n rdfs:comment \"To specify the function or part played in a particular situation by the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:sort-string a owl:DatatypeProperty ;\n rdfs:label \"sort as\"@en ;\n rdfs:comment \"To specify the string to be used for national-language-specific sorting. Used as a property parameter only.\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:title a owl:DatatypeProperty ;\n rdfs:label \"title\"@en ;\n rdfs:comment \"To specify the position or job of the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:tz a owl:DatatypeProperty ;\n rdfs:label \"time zone\"@en ;\n rdfs:comment \"To indicate time zone information that is specific to the object. May also be used as a property parameter.\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:value a owl:DatatypeProperty ;\n rdfs:label \"value\"@en ;\n rdfs:comment \"Used to indicate the literal value of a data property that requires property parameters\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:Address a owl:Class ;\n rdfs:label \"Address\"@en ;\n rdfs:comment \"To specify the components of the delivery address for the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentClass [ a owl:Class ;\n owl:unionOf ( [ a owl:Class ;\n owl:intersectionOf ( [ a owl:Restriction ;\n owl:onProperty :country-name ;\n owl:someValuesFrom xsd:string ] [ a owl:Restriction ;\n owl:maxCardinality \"1\"^^xsd:nonNegativeInteger ;\n owl:onProperty :country-name ] ) ] [ a owl:Class ;\n owl:intersectionOf ( [ a owl:Restriction ;\n owl:onProperty :locality ;\n owl:someValuesFrom xsd:string ] [ a owl:Restriction ;\n owl:maxCardinality \"1\"^^xsd:nonNegativeInteger ;\n owl:onProperty :locality ] ) ] [ a owl:Class ;\n owl:intersectionOf ( [ a owl:Restriction ;\n owl:onProperty :postal-code ;\n owl:someValuesFrom xsd:string ] [ a owl:Restriction ;\n owl:maxCardinality \"1\"^^xsd:nonNegativeInteger ;\n owl:onProperty :postal-code ] ) ] [ a owl:Class ;\n owl:intersectionOf ( [ a owl:Restriction ;\n owl:onProperty :region ;\n owl:someValuesFrom xsd:string ] [ a owl:Restriction ;\n owl:maxCardinality \"1\"^^xsd:nonNegativeInteger ;\n owl:onProperty :region ] ) ] [ a owl:Class ;\n owl:intersectionOf ( [ a owl:Restriction ;\n owl:onProperty :street-address ;\n owl:someValuesFrom xsd:string ] [ a owl:Restriction ;\n owl:maxCardinality \"1\"^^xsd:nonNegativeInteger ;\n owl:onProperty :street-address ] ) ] ) ] .\n\n:Email a owl:Class ;\n rdfs:label \"Email\"@en ;\n rdfs:comment \"To specify the electronic mail address for communication with the object the vCard represents. Use the hasEmail object property.\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:deprecated true .\n\n:Group a owl:Class ;\n rdfs:label \"Group\"@en ;\n rdfs:comment \"Object representing a group of persons or entities. A group object will usually contain hasMember properties to specify the members of the group.\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Kind ;\n owl:disjointWith :Individual,\n :Location,\n :Organization ;\n owl:equivalentClass [ a owl:Class ;\n owl:intersectionOf ( [ a owl:Restriction ;\n owl:onProperty :hasMember ;\n owl:someValuesFrom :Kind ] [ a owl:Restriction ;\n owl:minQualifiedCardinality \"1\"^^xsd:nonNegativeInteger ;\n owl:onClass :Kind ;\n owl:onProperty :hasMember ] ) ] .\n\n:Individual a owl:Class ;\n rdfs:label \"Individual\"@en ;\n rdfs:comment \"An object representing a single person or entity\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Kind ;\n owl:disjointWith :Location,\n :Organization .\n\n:Name a owl:Class ;\n rdfs:label \"Name\"@en ;\n rdfs:comment \"To specify the components of the name of the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentClass [ a owl:Class ;\n owl:unionOf ( [ a owl:Class ;\n owl:intersectionOf ( [ a owl:Restriction ;\n owl:onProperty :additional-name ;\n owl:someValuesFrom xsd:string ] [ a owl:Restriction ;\n owl:minCardinality \"0\"^^xsd:nonNegativeInteger ;\n owl:onProperty :additional-name ] ) ] [ a owl:Class ;\n owl:intersectionOf ( [ a owl:Restriction ;\n owl:onProperty :family-name ;\n owl:someValuesFrom xsd:string ] [ a owl:Restriction ;\n owl:maxCardinality \"1\"^^xsd:nonNegativeInteger ;\n owl:onProperty :family-name ] ) ] [ a owl:Class ;\n owl:intersectionOf ( [ a owl:Restriction ;\n owl:onProperty :given-name ;\n owl:someValuesFrom xsd:string ] [ a owl:Restriction ;\n owl:maxCardinality \"1\"^^xsd:nonNegativeInteger ;\n owl:onProperty :given-name ] ) ] [ a owl:Class ;\n owl:intersectionOf ( [ a owl:Restriction ;\n owl:onProperty :honorific-prefix ;\n owl:someValuesFrom xsd:string ] [ a owl:Restriction ;\n owl:minCardinality \"0\"^^xsd:nonNegativeInteger ;\n owl:onProperty :honorific-prefix ] ) ] [ a owl:Class ;\n owl:intersectionOf ( [ a owl:Restriction ;\n owl:onProperty :honorific-suffix ;\n owl:someValuesFrom xsd:string ] [ a owl:Restriction ;\n owl:minCardinality \"0\"^^xsd:nonNegativeInteger ;\n owl:onProperty :honorific-suffix ] ) ] ) ] .\n\n:VCard a owl:Class ;\n rdfs:label \"VCard\"@en ;\n rdfs:comment \"The vCard class is equivalent to the new Kind class, which is the parent for the four explicit types of vCards (Individual, Organization, Location, Group)\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentClass :Kind .\n\n:fn a owl:DatatypeProperty ;\n rdfs:label \"formatted name\"@en ;\n rdfs:comment \"The formatted text corresponding to the name of the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:hasAddress a owl:ObjectProperty ;\n rdfs:label \"has address\"@en ;\n rdfs:comment \"To specify the components of the delivery address for the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range :Address .\n\n:hasEmail a owl:ObjectProperty ;\n rdfs:label \"has email\"@en ;\n rdfs:comment \"To specify the electronic mail address for communication with the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range :Email .\n\n:hasGeo a owl:ObjectProperty ;\n rdfs:label \"has geo\"@en ;\n rdfs:comment \"To specify information related to the global positioning of the object. May also be used as a property parameter.\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:hasKey a owl:ObjectProperty ;\n rdfs:label \"has key\"@en ;\n rdfs:comment \"To specify a public key or authentication certificate associated with the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :key .\n\n:hasLogo a owl:ObjectProperty ;\n rdfs:label \"has logo\"@en ;\n rdfs:comment \"To specify a graphic image of a logo associated with the object \"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :logo .\n\n:hasName a owl:ObjectProperty ;\n rdfs:label \"has name\"@en ;\n rdfs:comment \"To specify the components of the name of the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range :Name ;\n owl:equivalentProperty :n .\n\n:hasPhoto a owl:ObjectProperty ;\n rdfs:label \"has photo\"@en ;\n rdfs:comment \"To specify an image or photograph information that annotates some aspect of the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :photo .\n\n:hasSound a owl:ObjectProperty ;\n rdfs:label \"has sound\"@en ;\n rdfs:comment \"To specify a digital sound content information that annotates some aspect of the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :sound .\n\n:hasTelephone a owl:ObjectProperty ;\n rdfs:label \"has telephone\"@en ;\n rdfs:comment \"To specify the telephone number for telephony communication with the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :tel .\n\n:hasURL a owl:ObjectProperty ;\n rdfs:label \"has url\"@en ;\n rdfs:comment \"To specify a uniform resource locator associated with the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :url .\n\n:key a owl:ObjectProperty ;\n rdfs:label \"key\"@en ;\n rdfs:comment \"This object property has been mapped\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :hasKey .\n\n:logo a owl:ObjectProperty ;\n rdfs:label \"logo\"@en ;\n rdfs:comment \"This object property has been mapped\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :hasLogo .\n\n:n a owl:ObjectProperty ;\n rdfs:label \"name\"@en ;\n rdfs:comment \"This object property has been mapped\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :hasName .\n\n:nickname a owl:DatatypeProperty ;\n rdfs:label \"nickname\"@en ;\n rdfs:comment \"The nick name associated with the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:photo a owl:ObjectProperty ;\n rdfs:label \"photo\"@en ;\n rdfs:comment \"This object property has been mapped\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :hasPhoto .\n\n:sound a owl:ObjectProperty ;\n rdfs:label \"sound\"@en ;\n rdfs:comment \"This object property has been mapped\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :hasSound .\n\n:tel a owl:ObjectProperty ;\n rdfs:label \"telephone\"@en ;\n rdfs:comment \"This object property has been mapped\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :hasTelephone .\n\n:url a owl:ObjectProperty ;\n rdfs:label \"url\"@en ;\n rdfs:comment \"This object property has been mapped\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentProperty :hasURL .\n\n:Location a owl:Class ;\n rdfs:label \"Location\"@en ;\n rdfs:comment \"An object representing a named geographical place\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Kind ;\n owl:disjointWith :Organization .\n\n:additional-name a owl:DatatypeProperty ;\n rdfs:label \"additional name\"@en ;\n rdfs:comment \"The additional name associated with the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:country-name a owl:DatatypeProperty ;\n rdfs:label \"country name\"@en ;\n rdfs:comment \"The country name associated with the address of the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:family-name a owl:DatatypeProperty ;\n rdfs:label \"family name\"@en ;\n rdfs:comment \"The family name associated with the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:given-name a owl:DatatypeProperty ;\n rdfs:label \"given name\"@en ;\n rdfs:comment \"The given name associated with the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:hasMember a owl:ObjectProperty ;\n rdfs:label \"has member\"@en ;\n rdfs:comment \"To include a member in the group this object represents. (This property can only be used by Group individuals)\"@en ;\n rdfs:domain :Group ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range :Kind .\n\n:honorific-prefix a owl:DatatypeProperty ;\n rdfs:label \"honorific prefix\"@en ;\n rdfs:comment \"The honorific prefix of the name associated with the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:honorific-suffix a owl:DatatypeProperty ;\n rdfs:label \"honorific suffix\"@en ;\n rdfs:comment \"The honorific suffix of the name associated with the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:locality a owl:DatatypeProperty ;\n rdfs:label \"locality\"@en ;\n rdfs:comment \"The locality (e.g. city or town) associated with the address of the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:organization-name a owl:DatatypeProperty ;\n rdfs:label \"organization name\"@en ;\n rdfs:comment \"To specify the organizational name associated with the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:postal-code a owl:DatatypeProperty ;\n rdfs:label \"postal code\"@en ;\n rdfs:comment \"The postal code associated with the address of the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:region a owl:DatatypeProperty ;\n rdfs:label \"region\"@en ;\n rdfs:comment \"The region (e.g. state or province) associated with the address of the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:street-address a owl:DatatypeProperty ;\n rdfs:label \"street address\"@en ;\n rdfs:comment \"The street address associated with the address of the object\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:range xsd:string .\n\n:Organization a owl:Class ;\n rdfs:label \"Organization\"@en ;\n rdfs:comment \"\"\"An object representing an organization. An organization is a single entity, and might represent a business or government, a department or division within a business or government, a club, an association, or the like.\n\"\"\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n rdfs:subClassOf :Kind .\n\n:Gender a owl:Class ;\n rdfs:label \"Gender\"@en ;\n rdfs:comment \"Used for gender codes. The URI of the gender code must be used as the value for Gender.\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:Kind a owl:Class ;\n rdfs:label \"Kind\"@en ;\n rdfs:comment \"The parent class for all objects\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> ;\n owl:equivalentClass [ a owl:Restriction ;\n owl:minQualifiedCardinality \"1\"^^xsd:nonNegativeInteger ;\n owl:onDataRange xsd:string ;\n owl:onProperty :fn ],\n :VCard .\n\n:Type a owl:Class ;\n rdfs:label \"Type\"@en ;\n rdfs:comment \"Used for type codes. The URI of the type code must be used as the value for Type.\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:TelephoneType a owl:Class ;\n rdfs:label \"Phone\"@en ;\n rdfs:comment \"Used for telephone type codes. The URI of the telephone type code must be used as the value for the Telephone Type.\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n:RelatedType a owl:Class ;\n rdfs:label \"Relation Type\"@en ;\n rdfs:comment \"Used for relation type codes. The URI of the relation type code must be used as the value for the Relation Type.\"@en ;\n rdfs:isDefinedBy <http://www.w3.org/2006/vcard/ns> .\n\n<http://www.w3.org/2006/vcard/ns> a owl:Ontology ;\n rdfs:label \"Ontology for vCard\"@en ;\n rdfs:comment \"Ontology for vCard based on RFC6350\"@en ;\n owl:versionInfo \"Final\"@en .\n\n\n";
17054
17097
  // EXTERNAL MODULE: ./node_modules/css-loader/dist/cjs.js!./src/styles/individual.css
17055
- var individual = __nested_webpack_require_86951__(479);
17098
+ var individual = __nested_webpack_require_91672__(479);
17056
17099
  ;// ./src/styles/individual.css
17057
17100
 
17058
17101
 
@@ -17081,7 +17124,7 @@ var individual_update = injectStylesIntoStyleTag_default()(individual/* default
17081
17124
  /* harmony default export */ const styles_individual = (individual/* default */.A && individual/* default */.A.locals ? individual/* default */.A.locals : undefined);
17082
17125
 
17083
17126
  // EXTERNAL MODULE: ./node_modules/css-loader/dist/cjs.js!./src/styles/rdfFormsEnforced.css
17084
- var rdfFormsEnforced = __nested_webpack_require_86951__(434);
17127
+ var rdfFormsEnforced = __nested_webpack_require_91672__(434);
17085
17128
  ;// ./src/styles/rdfFormsEnforced.css
17086
17129
 
17087
17130
 
@@ -17213,16 +17256,18 @@ async function renderIndividual (dom, div, subject, dataBrowserContext) {
17213
17256
 
17214
17257
  div.appendChild(await renderGroupMemberships(subject, dataBrowserContext))
17215
17258
 
17216
- // Allow to attach documents etc to the contact card
17217
- const h3 = div.appendChild(dom.createElement('h3'))
17218
- h3.textContent = 'Attach any document to this contact'
17219
- h3.classList.add('webidHeading')
17259
+ if ( external_commonjs_solid_logic_commonjs2_solid_logic_amd_solid_logic_root_SolidLogic_.authn.currentUser()) {
17260
+ // Allow to attach documents etc to the contact card
17261
+ const h3 = div.appendChild(dom.createElement('h3'))
17262
+ h3.textContent = 'Attach any document to this contact'
17263
+ h3.classList.add('webidHeading')
17220
17264
 
17221
- external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.attachmentList(dom, subject, div, {
17222
- modify: editable
17223
- // promptIcon: UI.icons.iconBase + 'noun_681601.svg',
17224
- // predicate: UI.ns.vcard('url') // @@@@@@@@@ ,--- no, the vcard ontology structure uses a bnode.
17225
- })
17265
+ external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.attachmentList(dom, subject, div, {
17266
+ modify: editable
17267
+ // promptIcon: UI.icons.iconBase + 'noun_681601.svg',
17268
+ // predicate: UI.ns.vcard('url') // @@@@@@@@@ ,--- no, the vcard ontology structure uses a bnode.
17269
+ })
17270
+ }
17226
17271
 
17227
17272
  if (isOrganization) {
17228
17273
  div.appendChild(await renderPublicIdControl(subject, dataBrowserContext))
@@ -17235,7 +17280,7 @@ async function renderIndividual (dom, div, subject, dataBrowserContext) {
17235
17280
  } // renderIndividual
17236
17281
 
17237
17282
  // EXTERNAL MODULE: ./node_modules/css-loader/dist/cjs.js!./src/styles/toolsPane.css
17238
- var toolsPane = __nested_webpack_require_86951__(295);
17283
+ var toolsPane = __nested_webpack_require_91672__(295);
17239
17284
  ;// ./src/styles/toolsPane.css
17240
17285
 
17241
17286
 
@@ -17280,6 +17325,7 @@ const VCARD = toolsPane_ns.vcard
17280
17325
  let book
17281
17326
  let selectedGroups
17282
17327
  let logSpace
17328
+ let refreshGroupsFn
17283
17329
 
17284
17330
  function toolsPane_toolsPane (
17285
17331
  selectAllGroups,
@@ -17287,10 +17333,12 @@ function toolsPane_toolsPane (
17287
17333
  groupsMainTable,
17288
17334
  bookParam,
17289
17335
  dataBrowserContext,
17290
- me
17336
+ me,
17337
+ refreshGroups
17291
17338
  ) {
17292
17339
  book = bookParam
17293
17340
  selectedGroups = selectedGroupsParam
17341
+ refreshGroupsFn = refreshGroups
17294
17342
  const dom = dataBrowserContext.dom
17295
17343
 
17296
17344
  const pane = dom.createElement('div')
@@ -17917,7 +17965,7 @@ function stats (logSpace) {
17917
17965
  const totalContacts = toolsPane_kb.each(undefined, VCARD('inAddressBook'), book).length
17918
17966
  toolsPane_log(logSpace, '' + totalContacts + ' contacts loaded. ')
17919
17967
  let groups = toolsPane_kb.each(book, VCARD('includesGroup'))
17920
- const strings = new Set(groups.map(group => group.uri)) // remove dups
17968
+ const strings = new Set(groups.map(group => normalizeGroupUri(group.uri))) // remove dups with normalized URIs
17921
17969
  groups = [...strings].map(uri => toolsPane_kb.sym(uri))
17922
17970
  toolsPane_log(logSpace, '' + groups.length + ' total groups. ')
17923
17971
  const gg = []
@@ -17954,7 +18002,7 @@ async function fixGroupless (book) {
17954
18002
  return new Promise(function (resolve) {
17955
18003
  const msg = dom.createElement('p')
17956
18004
  msg.textContent = `Add the ${groupless.length} contacts without groups to a 'No group' group?`
17957
- logSpace.parentNode.appendChild(msg)
18005
+ logSpace.appendChild(msg)
17958
18006
  const confirmButton = external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.continueButton(dom, async function () {
17959
18007
  msg.remove()
17960
18008
  confirmButton.remove()
@@ -17963,9 +18011,10 @@ async function fixGroupless (book) {
17963
18011
  await addPersonToGroup(person, groupOfUngrouped)
17964
18012
  }
17965
18013
  toolsPane_log(logSpace, 'People moved to group.')
18014
+ if (refreshGroupsFn) refreshGroupsFn()
17966
18015
  resolve()
17967
18016
  })
17968
- logSpace.parentNode.appendChild(confirmButton)
18017
+ logSpace.appendChild(confirmButton)
17969
18018
  })
17970
18019
  }
17971
18020
 
@@ -17983,7 +18032,7 @@ async function getGroupless (book) {
17983
18032
  const reverseIndex = {}
17984
18033
  const groupless = []
17985
18034
  let groups = toolsPane_kb.each(book, VCARD('includesGroup'))
17986
- const strings = new Set(groups.map(group => group.uri)) // remove dups
18035
+ const strings = new Set(groups.map(group => normalizeGroupUri(group.uri))) // remove dups with normalized URIs
17987
18036
  groups = [...strings].map(uri => toolsPane_kb.sym(uri))
17988
18037
  toolsPane_log(logSpace, '' + groups.length + ' total groups. ')
17989
18038
 
@@ -18039,7 +18088,7 @@ async function fixToOldDataModel (book) {
18039
18088
  return new Promise(function (resolve) {
18040
18089
  const msg = dom.createElement('p')
18041
18090
  msg.textContent = 'Groups can be updated to old data model?'
18042
- logSpace.parentNode.appendChild(msg)
18091
+ logSpace.appendChild(msg)
18043
18092
  const confirmButton = external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.continueButton(dom, async function () {
18044
18093
  msg.remove()
18045
18094
  confirmButton.remove()
@@ -18047,20 +18096,20 @@ async function fixToOldDataModel (book) {
18047
18096
  toolsPane_log(logSpace, 'Update done')
18048
18097
  resolve()
18049
18098
  })
18050
- logSpace.parentNode.appendChild(confirmButton)
18099
+ logSpace.appendChild(confirmButton)
18051
18100
  })
18052
18101
  } else {
18053
18102
  toolsPane_log(logSpace, 'Nothing to update.\nAll groups already use the old data model.')
18054
18103
  }
18055
18104
  }
18056
18105
  let groups = toolsPane_kb.each(book, VCARD('includesGroup'))
18057
- const strings = new Set(groups.map(group => group.uri)) // remove dups
18106
+ const strings = new Set(groups.map(group => normalizeGroupUri(group.uri))) // remove dups with normalized URIs
18058
18107
  groups = [...strings].map(uri => toolsPane_kb.sym(uri))
18059
18108
  updateToOldDataModel(groups)
18060
18109
  }
18061
18110
 
18062
18111
  // EXTERNAL MODULE: ./node_modules/css-loader/dist/cjs.js!./src/styles/contactsPane.css
18063
- var contactsPane = __nested_webpack_require_86951__(903);
18112
+ var contactsPane = __nested_webpack_require_91672__(903);
18064
18113
  ;// ./src/styles/contactsPane.css
18065
18114
 
18066
18115
 
@@ -18099,7 +18148,7 @@ var contactsPane_update = injectStylesIntoStyleTag_default()(contactsPane/* defa
18099
18148
  const addressBookPresenter_ns = external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.ns
18100
18149
  const addressBookPresenter_utils = external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.utils
18101
18150
  const addressBookPresenter_kb = external_commonjs_solid_logic_commonjs2_solid_logic_amd_solid_logic_root_SolidLogic_.store
18102
- let dom
18151
+ let addressBookPresenter_dom
18103
18152
  let addressBookPresenter_selectedGroups = {}
18104
18153
  let selectedPeople = {}
18105
18154
  let ulPeople = null
@@ -18125,7 +18174,7 @@ function setActiveGroupButton (groupsUl, activeBtn) {
18125
18174
  }
18126
18175
 
18127
18176
  function renderGroupButtons (currentBook, groupsUl, options, domElement, groupsSelected, peopleUl, searchEl, cardMainEl, divEl, context, groupClickCallback) {
18128
- dom = domElement
18177
+ addressBookPresenter_dom = domElement
18129
18178
  addressBookPresenter_selectedGroups = groupsSelected || {}
18130
18179
  if (peopleUl) ulPeople = peopleUl
18131
18180
  if (searchEl) searchInput = searchEl
@@ -18139,18 +18188,37 @@ function renderGroupButtons (currentBook, groupsUl, options, domElement, groupsS
18139
18188
  addressBookPresenter_utils.syncTableToArrayReOrdered(ulGroups, groups, renderGroupLi)
18140
18189
  }
18141
18190
 
18191
+ /** Create the common DOM structure for a group list item (li + button).
18192
+ * Returns { groupLi, groupButton, name } so callers can attach their own handlers.
18193
+ */
18194
+ function createGroupLi (group) {
18195
+ const name = addressBookPresenter_kb.any(group, addressBookPresenter_ns.vcard('fn'))
18196
+ const groupLi = addressBookPresenter_dom.createElement('li')
18197
+ groupLi.setAttribute('role', 'listitem')
18198
+ groupLi.setAttribute('aria-label', name ? name.value : 'Some group')
18199
+ groupLi.subject = group
18200
+ external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.makeDraggable(groupLi, group)
18201
+
18202
+ const groupButton = groupLi.appendChild(addressBookPresenter_dom.createElement('button'))
18203
+ groupButton.setAttribute('type', 'button')
18204
+ groupButton.innerHTML = name ? name.value : 'Some group'
18205
+ groupButton.classList.add('allGroupsButton', 'actionButton', 'btn-secondary', 'action-button-focus')
18206
+
18207
+ return { groupLi, groupButton, name }
18208
+ }
18209
+
18142
18210
  function renderGroupLi (group) {
18143
18211
  async function handleURIsDroppedOnGroup (uris) {
18144
- uris.forEach(function (u) {
18212
+ for (const u of uris) {
18145
18213
  log('Dropped on group: ' + u)
18146
18214
  const thing = addressBookPresenter_kb.sym(u)
18147
18215
  try {
18148
- addPersonToGroup(thing, group)
18216
+ await addPersonToGroup(thing, group)
18149
18217
  } catch (e) {
18150
18218
  complain(e)
18151
18219
  }
18152
18220
  refreshNames(ulPeople)
18153
- })
18221
+ }
18154
18222
  }
18155
18223
  function groupLiClickListener (event) {
18156
18224
  event.preventDefault()
@@ -18169,24 +18237,9 @@ function renderGroupLi (group) {
18169
18237
  })
18170
18238
  }
18171
18239
 
18172
- // Body of renderGroupUl
18173
- const name = addressBookPresenter_kb.any(group, addressBookPresenter_ns.vcard('fn'))
18174
- const groupLi = dom.createElement('li')
18175
- groupLi.setAttribute('role', 'listitem')
18176
- groupLi.setAttribute('tabindex', '0')
18177
- groupLi.setAttribute('aria-label', name ? name.value : 'Some group')
18178
- groupLi.subject = group
18179
- external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.makeDraggable(groupLi, group)
18180
-
18181
- const groupButton = groupLi.appendChild(dom.createElement('button'))
18182
- groupButton.setAttribute('type', 'button')
18183
- groupButton.innerHTML = name ? name.value : 'Some group'
18184
- groupButton.classList.add('allGroupsButton', 'actionButton', 'btn-secondary', 'action-button-focus')
18185
- groupButton.addEventListener(
18186
- 'click', groupLiClickListener,
18187
- false
18188
- )
18240
+ const { groupLi, groupButton } = createGroupLi(group)
18189
18241
 
18242
+ groupButton.addEventListener('click', groupLiClickListener, false)
18190
18243
  external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.makeDropTarget(groupLi, handleURIsDroppedOnGroup)
18191
18244
  groupLi.addEventListener('click', groupLiClickListener, true)
18192
18245
  return groupLi
@@ -18199,6 +18252,7 @@ function selectAllGroups (
18199
18252
  ) {
18200
18253
  function fetchGroupAndSelect (group, groupLi) {
18201
18254
  groupLi.classList.add('group-loading')
18255
+ groupLi.setAttribute('aria-busy', 'true')
18202
18256
  addressBookPresenter_kb.fetcher.nowOrWhenFetched(group.doc(), undefined, function (
18203
18257
  ok,
18204
18258
  message
@@ -18206,9 +18260,11 @@ function selectAllGroups (
18206
18260
  if (!ok) {
18207
18261
  const msg = 'Can\'t load group file: ' + group + ': ' + message
18208
18262
  badness.push(msg)
18209
- return complainIfBad(div, dom, ok, msg)
18263
+ groupLi.setAttribute('aria-busy', 'false')
18264
+ return complainIfBad(div, addressBookPresenter_dom, ok, msg)
18210
18265
  }
18211
18266
  groupLi.classList.remove('group-loading')
18267
+ groupLi.setAttribute('aria-busy', 'false')
18212
18268
  groupLi.classList.add('selected')
18213
18269
  selectedGroups[group.uri] = true
18214
18270
  refreshThingsSelected(ulGroups, selectedGroups)
@@ -18220,8 +18276,8 @@ function selectAllGroups (
18220
18276
  })
18221
18277
  }
18222
18278
 
18279
+ const badness = []
18223
18280
  let todo = 0
18224
- var badness = [] /* eslint-disable-line no-var */
18225
18281
  for (let k = 0; k < ulGroups.children.length; k++) {
18226
18282
  const groupLi = ulGroups.children[k]
18227
18283
  const group = groupLi.subject
@@ -18242,7 +18298,7 @@ function refreshThingsSelected (ul, selectionArray) {
18242
18298
  }
18243
18299
 
18244
18300
  function syncGroupUl (book, options, groupsUl, domElement, groupsSelected, peopleUl, searchEl) {
18245
- dom = domElement
18301
+ addressBookPresenter_dom = domElement
18246
18302
  if (groupsSelected) addressBookPresenter_selectedGroups = groupsSelected
18247
18303
  if (peopleUl) ulPeople = peopleUl
18248
18304
  if (searchEl) searchInput = searchEl
@@ -18278,10 +18334,12 @@ function groupsInOrder (book, options) {
18278
18334
 
18279
18335
  async function loadAllGroups (book) {
18280
18336
  const groupIndex = addressBookPresenter_kb.any(book, addressBookPresenter_ns.vcard('groupIndex'))
18281
- await addressBookPresenter_kb.fetcher.load(groupIndex)
18282
- const gs = book ? addressBookPresenter_kb.each(book, addressBookPresenter_ns.vcard('includesGroup'), null, groupIndex) : []
18283
- await addressBookPresenter_kb.fetcher.load(gs)
18284
- return gs
18337
+ if (groupIndex) {
18338
+ await addressBookPresenter_kb.fetcher.load(groupIndex)
18339
+ const gs = book ? addressBookPresenter_kb.each(book, addressBookPresenter_ns.vcard('includesGroup'), null, groupIndex) : []
18340
+ await addressBookPresenter_kb.fetcher.load(gs)
18341
+ return gs
18342
+ } else return
18285
18343
  }
18286
18344
 
18287
18345
  // The book could be the main subject, or linked from a group we are dealing with
@@ -18305,9 +18363,16 @@ function findBookFromGroups (book) {
18305
18363
  /** Refresh the list of names */
18306
18364
  function refreshNames (ulPeople, detailsView, autoSelect = true) {
18307
18365
  function setPersonListener (personLi, person) {
18308
- personLi.addEventListener('click', function (event) {
18366
+ function handleSelect (event) {
18309
18367
  event.preventDefault()
18310
18368
  selectPerson(ulPeople, person, cardMain)
18369
+ }
18370
+ personLi.addEventListener('click', handleSelect)
18371
+ personLi.addEventListener('keydown', function (event) {
18372
+ if (event.key === 'Enter' || event.key === ' ') {
18373
+ event.preventDefault()
18374
+ handleSelect(event)
18375
+ }
18311
18376
  })
18312
18377
  }
18313
18378
 
@@ -18328,7 +18393,7 @@ function refreshNames (ulPeople, detailsView, autoSelect = true) {
18328
18393
  }
18329
18394
 
18330
18395
  function renderNameInGroupList (person, ulPeople) {
18331
- const personLi = dom.createElement('li')
18396
+ const personLi = addressBookPresenter_dom.createElement('li')
18332
18397
  personLi.setAttribute('role', 'listitem')
18333
18398
  personLi.setAttribute('tabindex', '0')
18334
18399
  personLi.classList.add('personLi')
@@ -18336,25 +18401,28 @@ function refreshNames (ulPeople, detailsView, autoSelect = true) {
18336
18401
  external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.makeDraggable(personLi, person)
18337
18402
 
18338
18403
  // Container for the row
18339
- const rowDiv = dom.createElement('div')
18404
+ const rowDiv = addressBookPresenter_dom.createElement('div')
18340
18405
  rowDiv.classList.add('personLi-row')
18341
18406
 
18342
18407
  // Left: Avatar
18343
- const avatarDiv = dom.createElement('div')
18408
+ const avatarDiv = addressBookPresenter_dom.createElement('div')
18344
18409
  avatarDiv.classList.add('personLi-avatar')
18345
18410
  // Placeholder avatar (shown initially while person doc loads)
18346
- const placeholderEl = dom.createElement('div')
18411
+ const placeholderEl = addressBookPresenter_dom.createElement('div')
18347
18412
  placeholderEl.classList.add('avatar-placeholder')
18348
- placeholderEl.innerHTML = '<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="18" cy="18" r="18" fill="#e0e0e0"/><text x="50%" y="58%" text-anchor="middle" fill="#888" font-size="16" font-family="Arial" dy=".3em">?</text></svg>'
18413
+ placeholderEl.innerHTML = '<svg aria-hidden="true" width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="18" cy="18" r="18" fill="#e0e0e0"/><text x="50%" y="58%" text-anchor="middle" fill="#595959" font-size="16" font-family="Arial" dy=".3em">?</text></svg>'
18349
18414
  avatarDiv.appendChild(placeholderEl)
18350
18415
 
18416
+ // Get name early so it can be used in trySetAvatar
18417
+ const name = nameFor(person) || 'Unknown Name'
18418
+
18351
18419
  // Try to set avatar from already-loaded data, or fetch the person's doc
18352
18420
  function trySetAvatar () {
18353
18421
  const avatarUrl = addressBookPresenter_kb.any(person, addressBookPresenter_ns.vcard('hasPhoto'))
18354
18422
  if (avatarUrl && avatarUrl.value) {
18355
- const img = dom.createElement('img')
18423
+ const img = addressBookPresenter_dom.createElement('img')
18356
18424
  img.src = avatarUrl.value
18357
- img.alt = 'Avatar'
18425
+ img.alt = name + ' avatar'
18358
18426
  avatarDiv.replaceChild(img, avatarDiv.firstChild)
18359
18427
  }
18360
18428
  }
@@ -18365,21 +18433,20 @@ function refreshNames (ulPeople, detailsView, autoSelect = true) {
18365
18433
  })
18366
18434
 
18367
18435
  // Center: Name
18368
- const infoDiv = dom.createElement('div')
18436
+ const infoDiv = addressBookPresenter_dom.createElement('div')
18369
18437
  infoDiv.classList.add('personLi-info')
18370
18438
 
18371
- const name = nameFor(person) || 'Unknown Name'
18372
18439
  personLi.setAttribute('aria-label', name)
18373
- const nameDiv = dom.createElement('div')
18440
+ const nameDiv = addressBookPresenter_dom.createElement('div')
18374
18441
  nameDiv.classList.add('personLi-name')
18375
18442
  nameDiv.textContent = name
18376
18443
 
18377
18444
  infoDiv.appendChild(nameDiv)
18378
18445
 
18379
18446
  // Right: Arrow icon
18380
- const arrowDiv = dom.createElement('div')
18447
+ const arrowDiv = addressBookPresenter_dom.createElement('div')
18381
18448
  arrowDiv.classList.add('personLi-arrow')
18382
- arrowDiv.innerHTML = '<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6 4.5L11.25 9L6 13.5" stroke="#888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>'
18449
+ arrowDiv.innerHTML = '<svg aria-hidden="true" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6 4.5L11.25 9L6 13.5" stroke="#595959" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>'
18383
18450
 
18384
18451
  // Assemble
18385
18452
  rowDiv.appendChild(avatarDiv)
@@ -18399,6 +18466,8 @@ function selectPerson (ulPeople, person, detailsView) {
18399
18466
  if (!detailsView) return
18400
18467
  if (detailsView.parentNode) detailsView.parentNode.classList.remove('hidden')
18401
18468
  detailsView.innerHTML = 'Loading...'
18469
+ detailsView.setAttribute('aria-busy', 'true')
18470
+ detailsView.classList.add('detailsSectionContent--wide')
18402
18471
  selectedPeople = {}
18403
18472
  selectedPeople[person.uri] = true
18404
18473
  refreshFilteredPeople(ulPeople, false, detailsView) // Color to remember which one you picked
@@ -18408,68 +18477,90 @@ function selectPerson (ulPeople, person, detailsView) {
18408
18477
  message
18409
18478
  ) {
18410
18479
  detailsView.innerHTML = ''
18480
+ detailsView.setAttribute('aria-busy', 'false')
18411
18481
  if (!ok) {
18412
- return complainIfBad(div, dom, ok, 'Can\'t load card: ' + local + ': ' + message)
18482
+ return complainIfBad(div, addressBookPresenter_dom, ok, 'Can\'t load card: ' + local + ': ' + message)
18413
18483
  }
18414
18484
  // debug.log("Loaded card " + local + '\n')
18415
18485
 
18416
18486
  // Top-right toolbar with link icon and delete button
18417
- const toolbar = dom.createElement('div')
18487
+ const toolbar = addressBookPresenter_dom.createElement('div')
18418
18488
  toolbar.classList.add('contact-toolbar')
18419
- const linkEl = external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.linkIcon(dom, local)
18489
+ const linkEl = external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.linkIcon(addressBookPresenter_dom, local)
18420
18490
  linkEl.setAttribute('title', 'Uri of contact')
18421
18491
  toolbar.appendChild(linkEl)
18422
18492
 
18423
18493
  // Add in a delete button to delete from AB
18424
18494
  const deleteButton = external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.deleteButtonWithCheck(
18425
- dom,
18426
- detailsView,
18495
+ addressBookPresenter_dom,
18496
+ toolbar, // appends it to toolbar.appendChild(deleteButton)
18427
18497
  'contact',
18428
18498
  async function () {
18429
18499
  const container = person.dir() // ASSUMPTION THAT CARD IS IN ITS OWN DIRECTORY
18430
- // alert('Container to delete is ' + container)
18500
+
18431
18501
  const pname = addressBookPresenter_kb.any(person, addressBookPresenter_ns.vcard('fn'))
18432
- if (confirm('Delete contact ' + pname + ' completely?? ' + container)) {
18433
- log('Deleting a contact ' + pname)
18434
- await loadAllGroups() // need to wait for all groups to be loaded in case they have a link to this person
18435
- // load people.ttl
18436
- const nameEmailIndex = addressBookPresenter_kb.any(addressBookPresenter_book, addressBookPresenter_ns.vcard('nameEmailIndex'))
18437
- await addressBookPresenter_kb.fetcher.load(nameEmailIndex)
18438
-
18439
- // - delete person's WebID's in each Group
18440
- // - delete the references to it in group files and save them back
18441
- // - delete the reference in people.ttl and save it back
18442
-
18443
- // find all Groups
18444
- const groups = groupMembershipControl_groupMembership(person)
18445
- let removeFromGroups = []
18446
- // find person WebID's
18447
- groups.forEach(group => {
18448
- const webids = localUtils_getSameAs(addressBookPresenter_kb, person, group.doc())
18449
- // for each check in each Group that it is not used by an other person then delete
18450
- webids.forEach(webid => {
18451
- if (localUtils_getSameAs(addressBookPresenter_kb, webid, group.doc()).length === 1) {
18452
- removeFromGroups = removeFromGroups.concat(addressBookPresenter_kb.statementsMatching(group, addressBookPresenter_ns.vcard('hasMember'), webid, group.doc()))
18453
- }
18454
- })
18455
- })
18456
- // debug.log(removeFromGroups)
18457
- await addressBookPresenter_kb.updater.updateMany(removeFromGroups)
18458
- await deleteThingAndDoc(person)
18459
- await deleteRecursive(addressBookPresenter_kb, container)
18460
- refreshNames(ulPeople, person) // "Doesn't work" -- maybe does now with waiting for async
18461
- detailsView.innerHTML = 'Contact data deleted.'
18502
+ log('We are about to delete the contact ' + pname)
18503
+ await loadAllGroups() // need to wait for all groups to be loaded in case they have a link to this person
18504
+ // load people.ttl
18505
+ let nameEmailIndex = addressBookPresenter_kb.any(addressBookPresenter_book, addressBookPresenter_ns.vcard('nameEmailIndex'))
18506
+ await addressBookPresenter_kb.fetcher.load(nameEmailIndex)
18507
+
18508
+ // - delete person's WebID's in each Group
18509
+ // - delete the references to it in group files and save them back
18510
+ // - delete the reference in people.ttl and save it back
18511
+
18512
+ // find all Groups
18513
+ const groups = groupMembershipControl_groupMembership(person)
18514
+ let removeFromGroups = []
18515
+ // find person WebID's
18516
+ groups.forEach(group => {
18517
+ const webids = localUtils_getSameAs(addressBookPresenter_kb, person, group.doc())
18518
+ // for each check in each Group that it is not used by an other person then delete
18519
+ webids.forEach(webid => {
18520
+ if (localUtils_getSameAs(addressBookPresenter_kb, webid, group.doc()).length === 1) {
18521
+ removeFromGroups = removeFromGroups.concat(addressBookPresenter_kb.statementsMatching(group, addressBookPresenter_ns.vcard('hasMember'), webid, group.doc()))
18522
+ }
18523
+ })
18524
+ })
18525
+
18526
+ // Only if folder deletion succeeds, proceed with person deletion
18527
+ await addressBookPresenter_kb.updater.updateMany(removeFromGroups)
18528
+
18529
+ try {
18530
+ await deleteThingAndDoc(person)
18531
+ } catch (err) {
18532
+ log('Contact deletion cancelled or failed: ' + err.message)
18533
+ return // Stop - don't run deleteRecursive
18462
18534
  }
18535
+
18536
+ try {
18537
+ await deleteRecursive(addressBookPresenter_kb, container, toolbar, addressBookPresenter_dom)
18538
+ } catch (err) {
18539
+ log('Deletion cancelled: ' + err.message)
18540
+ return // Exit without continuing with other deletions
18541
+ }
18542
+ complain(div, addressBookPresenter_dom, "Contact data deleted.")
18543
+ refreshNames(ulPeople, detailsView)
18544
+ detailsView.innerHTML = 'Contact data deleted.'
18463
18545
  }
18464
18546
  )
18465
18547
  deleteButton.classList.add('deleteButton')
18466
- toolbar.appendChild(deleteButton)
18467
18548
  detailsView.appendChild(toolbar)
18468
18549
 
18550
+ detailsView.classList.add('detailsSectionContent--wide')
18469
18551
  detailsView.appendChild(renderPane(local, 'contact'))
18470
18552
  })
18471
18553
  }
18472
18554
 
18555
+ function deselectAllPeople (ulPeople) {
18556
+ selectedPeople = {}
18557
+ if (ulPeople) {
18558
+ for (let i = 0; i < ulPeople.children.length; i++) {
18559
+ ulPeople.children[i].classList.remove('selected')
18560
+ }
18561
+ }
18562
+ }
18563
+
18473
18564
  function refreshFilteredPeople (ulPeople, active, detailsView) {
18474
18565
  let count = 0
18475
18566
  let lastRow = null
@@ -18519,16 +18610,25 @@ function localNode (person) {
18519
18610
  }
18520
18611
 
18521
18612
  // Check every group is in the list and add it if not.
18522
- async function checkDataModel (book) {
18613
+ async function checkDataModel (book, detailsSectionContent) {
18523
18614
  // await kb.fetcher.load(groups) // asssume loaded already
18524
18615
  const groups = await loadAllGroups(book)
18525
18616
 
18526
- const { del, ins } = await getDataModelIssues(groups)
18617
+ if (groups && groups.length > 0) {
18527
18618
 
18528
- if (del.length && confirm(`Groups data model need to be updated? (${del.length})`)) {
18529
- await addressBookPresenter_kb.updater.updateMany(del, ins)
18530
- alert('Update done')
18531
- }
18619
+ const { del, ins } = await getDataModelIssues(groups)
18620
+
18621
+ if (del.length) {
18622
+ external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.deleteButtonWithCheck(
18623
+ addressBookPresenter_dom,
18624
+ detailsSectionContent, // where it appends it to
18625
+ 'contact',
18626
+ async function () {
18627
+ await addressBookPresenter_kb.updater.updateMany(del, ins)
18628
+ log('Deleted ' + del.length + ' bad statements from groups')
18629
+ })
18630
+ }
18631
+ }
18532
18632
  }
18533
18633
 
18534
18634
  // Prepare book data once so askName forms load instantly
@@ -18613,6 +18713,7 @@ const contactsPane_utils = external_commonjs_solid_ui_commonjs2_solid_ui_amd_sol
18613
18713
  const dom = dataBrowserContext.dom
18614
18714
  const kb = dataBrowserContext.session.store
18615
18715
  const div = dom.createElement('div')
18716
+ setDom(dom) // set dom for ana error handling in other modules
18616
18717
 
18617
18718
  external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.aclControl.preventBrowserDropEvents(dom) // protect drag and drop
18618
18719
 
@@ -18629,15 +18730,49 @@ const contactsPane_utils = external_commonjs_solid_ui_commonjs2_solid_ui_amd_sol
18629
18730
 
18630
18731
  const t = kb.findTypeURIs(subject)
18631
18732
 
18632
- let me = external_commonjs_solid_logic_commonjs2_solid_logic_amd_solid_logic_root_SolidLogic_.authn.currentUser()
18733
+ // Render a single contact Individual
18734
+ if (
18735
+ t[contactsPane_ns.vcard('Individual').uri] ||
18736
+ t[contactsPane_ns.foaf('Person').uri] ||
18737
+ t[contactsPane_ns.schema('Person').uri] ||
18738
+ t[contactsPane_ns.vcard('Organization').uri] ||
18739
+ t[contactsPane_ns.schema('Organization').uri]
18740
+ ) {
18741
+ renderIndividual(dom, div, subject, dataBrowserContext).then(() => log('(individual rendered)'))
18742
+ /*
18743
+ // Render a Group instance
18744
+ }
18745
+ else if (t[ns.vcard('Group').uri]) {
18746
+ // If we have a main address book, then render this group as a guest group within it
18747
+ UI.login
18748
+ .findAppInstances(context, ns.vcard('AddressBook'))
18749
+ .then(function (context) {
18750
+ const addressBooks = context.instances
18751
+ const options = { foreignGroup: subject }
18752
+ if (addressBooks.length > 0) {
18753
+ // const book = addressBooks[0]
18754
+ renderAddressBook(addressBooks, options)
18755
+ } else {
18756
+ renderAddressBook([], options)
18757
+ // @@ button to Make a new addressBook
18758
+ }
18759
+ })
18760
+ .catch(function (e) {
18761
+ complain(div, dom, '' + e)
18762
+ })
18763
+ */
18764
+ // Render a AddressBook instance
18765
+ } else if (t[contactsPane_ns.vcard('AddressBook').uri]) {
18766
+ renderAddressBook([subject], {})
18767
+ } else {
18768
+ log(
18769
+ 'Error: Contact pane: No evidence that ' +
18770
+ subject +
18771
+ ' is anything to do with contacts.'
18772
+ )
18773
+ }
18633
18774
 
18634
- const context = {
18635
- target: subject,
18636
- me,
18637
- noun: 'address book',
18638
- div,
18639
- dom
18640
- } // missing: statusRegion
18775
+ let me = external_commonjs_solid_logic_commonjs2_solid_logic_amd_solid_logic_root_SolidLogic_.authn.currentUser()
18641
18776
 
18642
18777
  // Render AddressBook instance
18643
18778
  function renderAddressBook (books, options) {
@@ -18654,13 +18789,23 @@ const contactsPane_utils = external_commonjs_solid_ui_commonjs2_solid_ui_amd_sol
18654
18789
  function renderAddressBookDetails (books, options) {
18655
18790
  const classLabel = contactsPane_utils.label(contactsPane_ns.vcard('AddressBook'))
18656
18791
 
18657
- const book = books[0] // for now
18792
+ let book = options.foreignGroup // in case we have only a Grouo
18793
+ let title = ''
18794
+ if (books && books.length > 0) {
18795
+ book = books[0] // if we have an Address Book, we prefer this
18796
+ title = contactsPane_utils.label(book.dir())
18797
+ } else {
18798
+ kb.any(book, contactsPane_ns.dc('title')) || kb.any(book, contactsPane_ns.vcard('fn'))
18799
+ if (paneOptions.solo && title && typeof document !== 'undefined') {
18800
+ document.title = title.value // @@ only when the outermmost pane
18801
+ }
18802
+ title = title ? title.value : classLabel
18803
+ }
18804
+
18658
18805
  const groupIndex = kb.any(book, contactsPane_ns.vcard('groupIndex'))
18659
18806
  const selectedGroups = {}
18660
18807
  let selectedPeople = {} // Actually prob max 1
18661
18808
 
18662
- const target = options.foreignGroup || book
18663
-
18664
18809
  let allGroupsLi = null
18665
18810
  let newGroupLi = null
18666
18811
 
@@ -18677,616 +18822,627 @@ const contactsPane_utils = external_commonjs_solid_ui_commonjs2_solid_ui_amd_sol
18677
18822
  }
18678
18823
  }
18679
18824
 
18680
- // the title of a adddress book has a default value, reason why if we take the name from the document
18681
- let title = ''
18682
- if (book) {
18683
- title = contactsPane_utils.label(book.dir())
18684
- } else {
18685
- contactsPane_utils.label(book.dir()) || kb.any(target, contactsPane_ns.dc('title')) || kb.any(target, contactsPane_ns.vcard('fn'))
18686
- if (paneOptions.solo && title && typeof document !== 'undefined') {
18687
- document.title = title.value // @@ only when the outermmost pane
18688
- }
18689
- title = title ? title.value : classLabel
18825
+ // Shared context passed to all builder functions
18826
+ const ctx = {
18827
+ dom, kb, ns: contactsPane_ns, book, options, title, groupIndex,
18828
+ selectedGroups, get selectedPeople () { return selectedPeople },
18829
+ set selectedPeople (v) { selectedPeople = v },
18830
+ get allGroupsLi () { return allGroupsLi },
18831
+ set allGroupsLi (v) { allGroupsLi = v },
18832
+ get newGroupLi () { return newGroupLi },
18833
+ set newGroupLi (v) { newGroupLi = v },
18834
+ actionButtons, setActiveActionButton,
18835
+ dataBrowserContext, div, me,
18836
+ setMe (v) { me = v },
18837
+ paneOptions,
18690
18838
  }
18691
18839
 
18692
- // Click on New Group button
18693
- async function newGroupClickHandler (_event) {
18694
- showDetailsSection()
18695
- detailsSectionContent.innerHTML = ''
18696
- const groupIndex = kb.any(book, contactsPane_ns.vcard('groupIndex'))
18697
- try {
18698
- await kb.fetcher.load(groupIndex)
18699
- } catch (e) {
18700
- log('Error: Group index NOT loaded:' + e + '\n')
18701
- }
18702
- log(' Group index has been loaded\n')
18703
-
18704
- const name = await external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.askName(
18705
- dom, kb, detailsSectionContent, external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.ns.foaf('name'), contactsPane_ns.vcard('Group'), 'group')
18706
- if (!name) return // cancelled by user
18707
- let group
18708
- try {
18709
- group = await saveNewGroup(book, name)
18710
- } catch (err) {
18711
- log('Error: can\'t save new group:' + err)
18712
- detailsSectionContent.innerHTML = 'Failed to save group' + err
18713
- return
18714
- }
18715
- for (const key in selectedGroups) delete selectedGroups[key]
18716
- selectedGroups[group.uri] = true
18717
-
18718
- // Refresh the group buttons list
18719
- if (allGroupsLi.parentNode) allGroupsLi.parentNode.removeChild(allGroupsLi)
18720
- if (newGroupLi.parentNode) newGroupLi.parentNode.removeChild(newGroupLi)
18721
- syncGroupUl(book, options, ulGroups, dom, selectedGroups, ulPeople, searchInput)
18722
- ulGroups.insertBefore(allGroupsLi, ulGroups.firstChild)
18723
- ulGroups.appendChild(newGroupLi)
18724
- refreshThingsSelected(ulGroups, selectedGroups)
18725
- // Highlight the new group button in ulGroups and show empty people list
18726
- const matchingLi = Array.from(ulGroups.children).find(li => li.subject && li.subject.uri === group.uri)
18727
- setActiveGroupButton(ulGroups, matchingLi ? matchingLi.querySelector('button') : null)
18728
- refreshNames(ulPeople, null, false)
18729
-
18730
- detailsSectionContent.innerHTML = ''
18731
- detailsSectionContent.appendChild(external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.aclControl.ACLControlBox5(
18732
- group.doc(), dataBrowserContext, 'group', kb,
18733
- function (ok, body) {
18734
- if (!ok) {
18735
- detailsSectionContent.innerHTML =
18736
- 'Group sharing setup failed: ' + body
18737
- }
18738
- }))
18739
- } // newGroupClickHandler
18740
-
18741
- // Render the askName form for the given klass into formContainer
18742
- function createNewPersonOrOrganization (formContainer, klass) {
18743
- formContainer.innerHTML = ''
18744
- external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets
18745
- .askName(dom, kb, formContainer, external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.ns.foaf('name'), klass)
18746
- .then(async (name) => {
18747
- if (!name) return // cancelled by user
18748
- detailsSectionContent.innerHTML = 'Indexing...'
18749
- let person
18750
- try {
18751
- person = await saveNewContact(book, name, selectedGroups, klass)
18752
- } catch (err) {
18753
- const msg = 'Error: can\'t save new contact: ' + err
18754
- log(msg)
18755
- alert(msg)
18756
- return
18757
- }
18758
- selectedPeople = {}
18759
- selectedPeople[person.uri] = true
18760
- refreshNames(ulPeople, null) // Add name to list of group
18761
- detailsSectionContent.innerHTML = '' // Clear 'indexing'
18762
- const contactPane = dataBrowserContext.session.paneRegistry.byName('contact')
18763
- const paneDiv = contactPane.render(person, dataBrowserContext)
18764
- paneDiv.classList.add('renderPane')
18765
- detailsSectionContent.appendChild(paneDiv)
18766
- })
18767
- }
18768
-
18769
- // //////////////////////////// Contact Browser - Body
18770
- // Main wrapper for accessibility and style
18771
- const main = dom.createElement('main')
18772
- main.id = 'main-content'
18773
- main.classList.add('addressBook-grid')
18774
- main.setAttribute('role', 'main')
18775
- main.setAttribute('aria-label', 'Address Book')
18776
- main.setAttribute('tabindex', '-1')
18840
+ // ── Build layout ────────────────────────────────────────────
18841
+ const { main, addressBookSection, detailsSection } = buildMainLayout(ctx)
18777
18842
  div.appendChild(main)
18778
18843
 
18779
- // Section wrapper for accessibility and style
18780
- const section = dom.createElement('section')
18781
- section.setAttribute('aria-labelledby', 'addressBook-section')
18782
- section.classList.add('addressBookSection', 'section-bg')
18783
- section.setAttribute('role', 'region')
18784
- section.setAttribute('tabindex', '-1')
18785
- main.appendChild(section)
18786
-
18787
- // Create a section for the header with white background
18788
- const headerSection = dom.createElement('section')
18789
- headerSection.classList.add('headerSection')
18790
-
18791
- const header = dom.createElement('header')
18792
- header.classList.add('mb-md')
18793
- const h2 = dom.createElement('h2')
18794
- h2.id = 'addressBook-heading'
18795
- h2.setAttribute('tabindex', '-1')
18796
- h2.textContent = title
18797
-
18798
- // New Contact button
18799
- const newContactButton = dom.createElement('button')
18800
- const container = dom.createElement('div')
18801
- newContactButton.setAttribute('type', 'button')
18802
- if (!me) newContactButton.setAttribute('disabled', 'true')
18803
- external_commonjs_solid_logic_commonjs2_solid_logic_amd_solid_logic_root_SolidLogic_.authn.checkUser().then(webId => {
18804
- if (webId) {
18805
- me = webId
18806
- newContactButton.removeAttribute('disabled')
18807
- }
18808
- })
18809
- container.appendChild(newContactButton)
18810
- newContactButton.innerHTML = '+ New contact'
18811
- newContactButton.classList.add('actionButton', 'btn-primary', 'action-button-focus')
18812
- let newContactClickGeneration = 0
18813
- newContactButton.addEventListener('click', async function (_event) {
18814
- setActiveActionButton(null)
18815
- const thisGeneration = ++newContactClickGeneration
18816
- showDetailsSection()
18817
- detailsSectionContent.innerHTML = ''
18818
- await ensureBookLoaded()
18819
- // Bail out if a newer click has taken over
18820
- if (thisGeneration !== newContactClickGeneration) return
18821
- detailsSectionContent.innerHTML = ''
18822
-
18823
- const chooserDiv = dom.createElement('div')
18824
- chooserDiv.classList.add('contactTypeChooser')
18825
-
18826
- const selectLabel = dom.createElement('label')
18827
- selectLabel.textContent = 'Contact type: '
18828
- selectLabel.setAttribute('for', 'contactTypeSelect')
18829
- chooserDiv.appendChild(selectLabel)
18830
-
18831
- const select = dom.createElement('select')
18832
- select.id = 'contactTypeSelect'
18833
- select.classList.add('contactTypeSelect')
18834
- const optIndividual = dom.createElement('option')
18835
- optIndividual.value = 'Individual'
18836
- optIndividual.textContent = 'New person'
18837
- select.appendChild(optIndividual)
18838
- const optOrganization = dom.createElement('option')
18839
- optOrganization.value = 'Organization'
18840
- optOrganization.textContent = 'New organization'
18841
- select.appendChild(optOrganization)
18842
- chooserDiv.appendChild(select)
18843
-
18844
- detailsSectionContent.appendChild(chooserDiv)
18845
-
18846
- const remark = dom.createElement('p')
18847
- remark.classList.add('contactCreationRemark')
18848
- remark.textContent = 'The new contact is added to the already selected group.'
18849
- detailsSectionContent.appendChild(remark)
18850
-
18851
- // Container for the askName form, placed below the select
18852
- const formContainer = dom.createElement('div')
18853
- formContainer.classList.add('contactFormContainer')
18854
- detailsSectionContent.appendChild(formContainer)
18855
-
18856
- function currentKlass () {
18857
- return select.value === 'Organization'
18858
- ? contactsPane_ns.vcard('Organization')
18859
- : contactsPane_ns.vcard('Individual')
18860
- }
18861
-
18862
- // Render person form immediately as default
18863
- createNewPersonOrOrganization(formContainer, currentKlass())
18864
-
18865
- // Switch form when dropdown changes
18866
- select.addEventListener('change', function () {
18867
- createNewPersonOrOrganization(formContainer, currentKlass())
18868
- })
18869
- }, false)
18870
-
18871
- // TODO we should also add if it is public or private
18872
- header.appendChild(h2)
18873
- header.appendChild(container)
18874
- headerSection.appendChild(header)
18875
- section.appendChild(headerSection)
18876
-
18877
- // Add a dotted horizontal rule after the header section
18878
- const dottedHr = dom.createElement('hr')
18879
- dottedHr.classList.add('dottedHr')
18880
- section.appendChild(dottedHr)
18881
-
18882
- // Search input
18883
- const searchSection = dom.createElement('section')
18884
- searchSection.classList.add('searchSection')
18885
- const searchDiv = dom.createElement('div')
18886
- searchDiv.classList.add('searchDiv')
18887
- searchSection.appendChild(searchDiv)
18888
- const searchInput = dom.createElement('input')
18889
- searchInput.setAttribute('type', 'text')
18890
- searchInput.setAttribute('aria-label', 'Search contacts')
18891
- searchInput.classList.add('searchInput')
18892
- searchInput.setAttribute('placeholder', 'Search by name')
18893
- searchDiv.appendChild(searchInput)
18894
- searchInput.addEventListener('input', function (_event) {
18895
- refreshFilteredPeople(ulPeople, true, detailsSectionContent)
18896
- })
18897
- section.appendChild(searchSection)
18898
-
18899
- // Create a section for the buttons
18900
- const buttonSection = dom.createElement('section')
18901
- buttonSection.classList.add('buttonSection')
18844
+ function showDetailsSection () {
18845
+ detailsSection.classList.remove('hidden')
18846
+ }
18847
+ ctx.showDetailsSection = showDetailsSection
18848
+ ctx.detailsSection = detailsSection
18902
18849
 
18903
- // People list (created early so it can be passed to presenter functions)
18850
+ // Create shared DOM elements needed by multiple builders
18904
18851
  const ulPeople = dom.createElement('ul')
18905
18852
  ulPeople.setAttribute('role', 'list')
18906
18853
  ulPeople.setAttribute('aria-label', 'People list')
18854
+ ctx.ulPeople = ulPeople
18907
18855
 
18908
- // Card main (created early so it can be passed to presenter functions)
18909
18856
  const detailsSectionContent = dom.createElement('div')
18910
18857
  detailsSectionContent.classList.add('detailsSectionContent')
18911
18858
  detailsSectionContent.setAttribute('role', 'region')
18912
18859
  detailsSectionContent.setAttribute('aria-labelledby', 'detailsSectionContent')
18860
+ detailsSectionContent.setAttribute('aria-live', 'polite')
18861
+ ctx.detailsSectionContent = detailsSectionContent
18913
18862
 
18914
- if (options.foreignGroup) {
18915
- selectedGroups[options.foreignGroup.uri] = true
18916
- }
18917
-
18918
- // Groups list (all buttons inside ul > li)
18919
- const ulGroups = dom.createElement('ul')
18920
- ulGroups.classList.add('groupButtonsList')
18921
- ulGroups.setAttribute('role', 'list')
18922
- ulGroups.setAttribute('aria-label', 'Groups list')
18923
-
18924
- if (book) {
18925
- // All groups button — leftmost, initially selected
18926
- allGroupsLi = dom.createElement('li')
18927
- const allGroupsButton = dom.createElement('button')
18928
- allGroupsButton.textContent = 'All groups'
18929
- allGroupsButton.classList.add('allGroupsButton', 'actionButton', 'btn-primary', 'action-button-focus', 'allGroupsButton--selected')
18930
- allGroupsButton.addEventListener('click', function (_event) {
18931
- setActiveGroupButton(ulGroups, allGroupsButton)
18932
- setActiveActionButton(null)
18933
- // Check if all groups are currently selected
18934
- const allSelected = Array.from(ulGroups.children).every(function (li) {
18935
- if (!li.subject) return true // skip non-group items (All groups, New group)
18936
- return !!selectedGroups[li.subject.uri]
18937
- })
18863
+ // ── Header (title + New Contact button) ─────────────────────
18864
+ const headerSection = buildHeaderSection(ctx)
18865
+ addressBookSection.appendChild(headerSection)
18938
18866
 
18939
- if (!allSelected) {
18940
- allGroupsButton.classList.add('allGroupsButton--loading')
18941
- selectAllGroups(selectedGroups, ulGroups, function (
18942
- ok,
18943
- message
18944
- ) {
18945
- if (!ok) return complain(div, dom, message)
18946
- allGroupsButton.classList.remove('allGroupsButton--loading')
18947
- allGroupsButton.classList.add('allGroupsButton--active')
18948
- refreshThingsSelected(ulGroups, selectedGroups)
18949
- refreshNames(ulPeople, null)
18950
- })
18951
- } else {
18952
- allGroupsButton.classList.remove('allGroupsButton--loading', 'allGroupsButton--active')
18953
- allGroupsButton.classList.add('allGroupsButton--loaded') // pale green hint groups loaded
18954
- for (const key in selectedGroups) delete selectedGroups[key]
18955
- refreshThingsSelected(ulGroups, selectedGroups)
18956
- refreshNames(ulPeople, null)
18957
- }
18958
- }) // on button click
18959
- allGroupsLi.appendChild(allGroupsButton)
18960
- ulGroups.appendChild(allGroupsLi) // First item in the list
18961
-
18962
- // New group button — rightmost (appended after group buttons are rendered)
18963
- newGroupLi = dom.createElement('li')
18964
- const newGroupButton = dom.createElement('button')
18965
- newGroupButton.setAttribute('type', 'button')
18966
- newGroupButton.innerHTML = '+ New group'
18967
- newGroupButton.classList.add('allGroupsButton', 'actionButton', 'btn-secondary', 'action-button-focus')
18968
- actionButtons.push(newGroupButton)
18969
- newGroupButton.addEventListener(
18970
- 'click', function (event) {
18971
- setActiveGroupButton(ulGroups, newGroupButton)
18972
- setActiveActionButton(null)
18973
- newGroupClickHandler(event)
18974
- },
18975
- false
18976
- )
18977
- newGroupLi.appendChild(newGroupButton)
18978
-
18979
- // Append ulGroups to buttonSection, then add New group at the end
18980
- buttonSection.appendChild(ulGroups)
18981
-
18982
- kb.fetcher.nowOrWhenFetched(groupIndex.uri, book, function (ok, body) {
18983
- if (!ok) return complain(div, dom, 'Cannot load group index: ' + body)
18984
- // Remove special items before sync (syncTableToArrayReOrdered expects .subject on all children)
18985
- if (allGroupsLi.parentNode) allGroupsLi.parentNode.removeChild(allGroupsLi)
18986
- if (newGroupLi.parentNode) newGroupLi.parentNode.removeChild(newGroupLi)
18987
- syncGroupUl(book, options, ulGroups, dom, selectedGroups, ulPeople, searchInput) // Refresh list of groups
18988
- ulGroups.insertBefore(allGroupsLi, ulGroups.firstChild) // Keep All contacts first
18989
- ulGroups.appendChild(newGroupLi) // Keep New group last
18990
-
18991
- // Auto-select all groups and display all contacts on load
18992
- allGroupsButton.classList.add('allGroupsButton--loading')
18993
- selectAllGroups(selectedGroups, ulGroups, function (loadOk, message) {
18994
- if (!loadOk) return complain(div, dom, message)
18995
- allGroupsButton.classList.remove('allGroupsButton--loading')
18996
- allGroupsButton.classList.add('allGroupsButton--active')
18997
- refreshThingsSelected(ulGroups, selectedGroups)
18998
- refreshNames(ulPeople, null)
18999
- })
19000
- })
18867
+ const dottedHr = dom.createElement('hr')
18868
+ dottedHr.classList.add('dottedHr')
18869
+ addressBookSection.appendChild(dottedHr)
19001
18870
 
19002
- // Remove special items before initial render too
19003
- if (allGroupsLi.parentNode) allGroupsLi.parentNode.removeChild(allGroupsLi)
19004
- if (newGroupLi.parentNode) newGroupLi.parentNode.removeChild(newGroupLi)
19005
- renderGroupButtons(book, ulGroups, options, dom, selectedGroups, ulPeople, searchInput, detailsSectionContent, div, dataBrowserContext, function () {
19006
- setActiveActionButton(null)
19007
- // Keep the New contact form open when switching groups
19008
- if (!detailsSectionContent.querySelector('.contactTypeChooser, .contactFormContainer')) {
19009
- detailsSectionContent.innerHTML = ''
19010
- detailsSection.classList.add('hidden')
19011
- }
19012
- })
19013
- ulGroups.insertBefore(allGroupsLi, ulGroups.firstChild) // Keep All contacts first
19014
- ulGroups.appendChild(newGroupLi) // Ensure New group is last after initial render
19015
- } else {
19016
- syncGroupUl(book, options, ulGroups, dom, selectedGroups, ulPeople, searchInput) // Refresh list of groups (will be empty)
19017
- refreshNames(ulPeople, null)
19018
- log('No book, only one group -> hide list of groups')
19019
- } // if not book
18871
+ // ── Search ──────────────────────────────────────────────────
18872
+ const { searchSection, searchInput } = buildSearchSection(ctx)
18873
+ ctx.searchInput = searchInput
18874
+ addressBookSection.appendChild(searchSection)
19020
18875
 
19021
- section.appendChild(buttonSection)
18876
+ // ── Group bar ───────────────────────────────────────────────
18877
+ const { buttonSection, ulGroups } = buildGroupBar(ctx)
18878
+ ctx.ulGroups = ulGroups
18879
+ addressBookSection.appendChild(buttonSection)
19022
18880
 
19023
- // List of contacts
18881
+ // ── People list ─────────────────────────────────────────────
19024
18882
  const peopleListSection = dom.createElement('section')
19025
18883
  peopleListSection.classList.add('peopleSection')
19026
- section.appendChild(peopleListSection)
19027
-
18884
+ addressBookSection.appendChild(peopleListSection)
19028
18885
  peopleListSection.appendChild(ulPeople)
19029
18886
 
19030
- // Accessible table structure rendered beside the addressBook section
19031
- const detailsSection = dom.createElement('section')
19032
- detailsSection.classList.add('detailSection')
19033
- detailsSection.setAttribute('role', 'region')
19034
- detailsSection.setAttribute('aria-label', 'Details section')
19035
- detailsSection.classList.add('hidden')
19036
- main.appendChild(detailsSection)
18887
+ // ── Details content section ─────────────────────────────────
18888
+ detailsSection.appendChild(detailsSectionContent)
19037
18889
 
19038
- function showDetailsSection () {
19039
- detailsSection.classList.remove('hidden')
19040
- }
18890
+ // ── Footer buttons ──────────────────────────────────────────
18891
+ const cardFooter = buildFooterButtons(ctx)
18892
+ addressBookSection.appendChild(cardFooter)
19041
18893
 
19042
- detailsSection.appendChild(detailsSectionContent)
18894
+ checkDataModel(book, detailsSectionContent).then(() => { log('async checkDataModel done.') })
18895
+ }
19043
18896
 
19044
- // Footer area for action buttons
19045
- const cardFooter = dom.createElement('div')
19046
- cardFooter.classList.add('cardFooter')
19047
- section.appendChild(cardFooter)
19048
-
19049
- if (book) {
19050
- // Groups button
19051
- const groupsButton = cardFooter.appendChild(dom.createElement('button'))
19052
- groupsButton.setAttribute('type', 'button')
19053
- groupsButton.innerHTML = 'Groups'
19054
- groupsButton.classList.add('actionButton', 'btn-secondary', 'action-button-focus')
19055
- actionButtons.push(groupsButton)
19056
- groupsButton.addEventListener('click', async function (_event) {
19057
- setActiveActionButton(groupsButton)
19058
- showDetailsSection()
19059
- detailsSectionContent.innerHTML = ''
19060
-
19061
- // Header
19062
- const groupsHeader = dom.createElement('h3')
19063
- groupsHeader.textContent = 'Your groups'
19064
- detailsSectionContent.appendChild(groupsHeader)
19065
-
19066
- const groupRemark = dom.createElement('p')
19067
- groupRemark.classList.add('contactCreationRemark')
19068
- groupRemark.textContent = 'When deleting a group, all its contacts are deleted too.'
19069
- detailsSectionContent.appendChild(groupRemark)
19070
-
19071
- // Load all groups and display them in a list
19072
- let groups
19073
- try {
19074
- groups = await loadAllGroups(book)
19075
- } catch (err) {
19076
- detailsSectionContent.appendChild(dom.createTextNode('Failed to load groups: ' + err))
19077
- return
19078
- }
18897
+ // /////////////// Fix user when testing on a plane
19079
18898
 
19080
- const groupsList = dom.createElement('ul')
19081
- groupsList.setAttribute('role', 'list')
19082
- groupsList.setAttribute('aria-label', 'All groups')
19083
- groupsList.classList.add('groupButtonsList')
18899
+ if (
18900
+ typeof document !== 'undefined' &&
18901
+ document.location &&
18902
+ ('' + document.location).slice(0, 16) === 'http://localhost'
18903
+ ) {
18904
+ me = kb.any(subject, external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.ns.acl('owner')) // when testing on plane with no webid
18905
+ log('Assuming user is ' + me)
18906
+ }
19084
18907
 
19085
- // Sort groups by name
19086
- groups.sort((a, b) => {
19087
- const nameA = (kb.any(a, contactsPane_ns.vcard('fn')) || '').toString().toLowerCase()
19088
- const nameB = (kb.any(b, contactsPane_ns.vcard('fn')) || '').toString().toLowerCase()
19089
- return nameA < nameB ? -1 : nameA > nameB ? 1 : 0
19090
- })
18908
+ return div
18909
+ } // asyncRender
18910
+ } // render function
18911
+ }); // pane object
19091
18912
 
19092
- groups.forEach(function (group) {
19093
- const name = kb.any(group, contactsPane_ns.vcard('fn'))
19094
- const groupLi = dom.createElement('li')
19095
- groupLi.setAttribute('role', 'listitem')
19096
- groupLi.setAttribute('tabindex', '0')
19097
- groupLi.setAttribute('aria-label', name ? name.value : 'Some group')
19098
- groupLi.subject = group
19099
-
19100
- const groupBtn = groupLi.appendChild(dom.createElement('button'))
19101
- groupBtn.setAttribute('type', 'button')
19102
- groupBtn.innerHTML = name ? name.value : 'Some group'
19103
- groupBtn.classList.add('allGroupsButton', 'actionButton', 'btn-secondary', 'action-button-focus')
19104
- groupBtn.addEventListener('click', function (event) {
19105
- event.preventDefault()
19106
- if (!event.metaKey) {
19107
- for (const key in selectedGroups) delete selectedGroups[key]
19108
- }
19109
- selectedGroups[group.uri] = !selectedGroups[group.uri]
19110
- refreshThingsSelected(ulGroups, selectedGroups)
19111
- // Highlight the matching group button in the sidebar ulGroups
19112
- const matchingLi = Array.from(ulGroups.children).find(li => li.subject && li.subject.uri === group.uri)
19113
- setActiveGroupButton(ulGroups, matchingLi ? matchingLi.querySelector('button') : null)
19114
- kb.fetcher.nowOrWhenFetched(group.doc(), undefined, function (ok, _message) {
19115
- if (ok) {
19116
- refreshNames(ulPeople, null, false)
19117
- }
19118
- })
19119
- }, false)
18913
+ // ── Helper: handle "New group" button click ──────────────────────────
18914
+ async function handleNewGroupClick (ctx) {
18915
+ const { dom, kb, ns, book, options, selectedGroups, dataBrowserContext, div } = ctx
18916
+ ctx.showDetailsSection()
18917
+ ctx.detailsSectionContent.innerHTML = ''
18918
+ const groupIndex = kb.any(book, ns.vcard('groupIndex'))
18919
+ try {
18920
+ await kb.fetcher.load(groupIndex)
18921
+ } catch (e) {
18922
+ log('Error: Group index NOT loaded:' + e + '\n')
18923
+ }
18924
+ log(' Group index has been loaded\n')
19120
18925
 
19121
- external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.makeDraggable(groupLi, group)
19122
- external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.deleteButtonWithCheck(
19123
- dom,
19124
- groupLi,
19125
- 'group ' + name,
19126
- async function () {
19127
- await deleteThingAndDoc(group)
19128
- delete selectedGroups[group.uri]
19129
- // Refresh the group buttons list
19130
- if (allGroupsLi.parentNode) allGroupsLi.parentNode.removeChild(allGroupsLi)
19131
- if (newGroupLi.parentNode) newGroupLi.parentNode.removeChild(newGroupLi)
19132
- syncGroupUl(book, options, ulGroups, dom, selectedGroups, ulPeople, searchInput)
19133
- ulGroups.insertBefore(allGroupsLi, ulGroups.firstChild)
19134
- ulGroups.appendChild(newGroupLi)
19135
- refreshThingsSelected(ulGroups, selectedGroups)
19136
- // Refresh the people list to reflect the deleted group
19137
- refreshNames(ulPeople, null, false)
19138
- // Refresh the groups detail view
19139
- groupsButton.click()
19140
- }
19141
- )
18926
+ const name = await external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.askName(
18927
+ dom, kb, ctx.detailsSectionContent, external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.ns.foaf('name'), ns.vcard('Group'), 'group')
18928
+ if (!name) return // cancelled by user
18929
+ let group
18930
+ try {
18931
+ group = await saveNewGroup(book, name)
18932
+ } catch (err) {
18933
+ log('Error: can\'t save new group:' + err)
18934
+ ctx.detailsSectionContent.innerHTML = 'Failed to save group' + err
18935
+ return
18936
+ }
18937
+ for (const key in selectedGroups) delete selectedGroups[key]
18938
+ selectedGroups[group.uri] = true
18939
+
18940
+ // Refresh the group buttons list
18941
+ const allGroupsLi = ctx.allGroupsLi
18942
+ const newGroupLi = ctx.newGroupLi
18943
+ if (allGroupsLi.parentNode) allGroupsLi.parentNode.removeChild(allGroupsLi)
18944
+ if (newGroupLi.parentNode) newGroupLi.parentNode.removeChild(newGroupLi)
18945
+ syncGroupUl(book, options, ctx.ulGroups, dom, selectedGroups, ctx.ulPeople, ctx.searchInput)
18946
+ ctx.ulGroups.insertBefore(allGroupsLi, ctx.ulGroups.firstChild)
18947
+ ctx.ulGroups.appendChild(newGroupLi)
18948
+ refreshThingsSelected(ctx.ulGroups, selectedGroups)
18949
+ // Highlight the new group button in ulGroups and show empty people list
18950
+ const matchingLi = Array.from(ctx.ulGroups.children).find(li => li.subject && li.subject.uri === group.uri)
18951
+ setActiveGroupButton(ctx.ulGroups, matchingLi ? matchingLi.querySelector('button') : null)
18952
+ refreshNames(ctx.ulPeople, null, false)
18953
+
18954
+ ctx.detailsSectionContent.innerHTML = ''
18955
+ ctx.detailsSectionContent.appendChild(external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.aclControl.ACLControlBox5(
18956
+ group.doc(), dataBrowserContext, 'group', kb,
18957
+ function (ok, body) {
18958
+ if (!ok) {
18959
+ ctx.detailsSectionContent.innerHTML =
18960
+ 'Group sharing setup failed: ' + body
18961
+ }
18962
+ }))
18963
+ }
19142
18964
 
19143
- groupsList.appendChild(groupLi)
19144
- })
18965
+ // ── Helper: render askName form for person or organization ───────────
18966
+ function createNewPersonOrOrganization (ctx, formContainer, klass) {
18967
+ const { dom, kb, ns, book, selectedGroups, dataBrowserContext } = ctx
18968
+ formContainer.innerHTML = ''
18969
+ external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets
18970
+ .askName(dom, kb, formContainer, external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.ns.foaf('name'), klass)
18971
+ .then(async (name) => {
18972
+ if (!name) return // cancelled by user
18973
+ ctx.detailsSectionContent.innerHTML = 'Indexing...'
18974
+ let person
18975
+ try {
18976
+ person = await saveNewContact(book, name, selectedGroups, klass)
18977
+ } catch (err) {
18978
+ const msg = 'Error: can\'t save new contact: ' + err
18979
+ log(msg)
18980
+ alert(msg)
18981
+ return
18982
+ }
18983
+ ctx.selectedPeople = {}
18984
+ ctx.selectedPeople[person.uri] = true
18985
+ refreshNames(ctx.ulPeople, null) // Add name to list of group
18986
+ ctx.detailsSectionContent.innerHTML = '' // Clear 'indexing'
18987
+ ctx.detailsSectionContent.classList.add('detailsSectionContent--wide')
18988
+ const contactPane = dataBrowserContext.session.paneRegistry.byName('contact')
18989
+ const paneDiv = contactPane.render(person, dataBrowserContext)
18990
+ paneDiv.classList.add('renderPane')
18991
+ ctx.detailsSectionContent.appendChild(paneDiv)
18992
+ })
18993
+ }
19145
18994
 
19146
- detailsSectionContent.appendChild(groupsList)
18995
+ // ── Builder: main layout skeleton ────────────────────────────────────
18996
+ function buildMainLayout (ctx) {
18997
+ const { dom } = ctx
18998
+ const main = dom.createElement('main')
18999
+ main.id = 'main-content'
19000
+ main.classList.add('addressBook-grid')
19001
+ main.setAttribute('role', 'main')
19002
+ main.setAttribute('aria-label', 'Address Book')
19003
+ main.setAttribute('tabindex', '-1')
19004
+
19005
+ const addressBookSection = dom.createElement('section')
19006
+ addressBookSection.setAttribute('aria-labelledby', 'addressBook-section')
19007
+ addressBookSection.classList.add('addressBookSection', 'section-bg')
19008
+ addressBookSection.setAttribute('role', 'region')
19009
+ addressBookSection.setAttribute('tabindex', '-1')
19010
+ main.appendChild(addressBookSection)
19011
+
19012
+ const detailsSection = dom.createElement('section')
19013
+ detailsSection.classList.add('detailSection')
19014
+ detailsSection.setAttribute('role', 'region')
19015
+ detailsSection.setAttribute('aria-label', 'Details section')
19016
+ detailsSection.classList.add('hidden')
19017
+ main.appendChild(detailsSection)
19018
+
19019
+ return { main, addressBookSection, detailsSection }
19020
+ }
19021
+
19022
+ // ── Builder: header with title and New Contact button ────────────────
19023
+ function buildHeaderSection (ctx) {
19024
+ const { dom, ns, kb, title, me, setMe, setActiveActionButton, paneOptions } = ctx
19025
+
19026
+ const headerSection = dom.createElement('section')
19027
+ headerSection.classList.add('headerSection')
19028
+
19029
+ const header = dom.createElement('header')
19030
+ header.classList.add('mb-md')
19031
+ const h2 = dom.createElement('h2')
19032
+ h2.id = 'addressBook-heading'
19033
+ h2.setAttribute('tabindex', '-1')
19034
+ h2.textContent = title
19035
+
19036
+ // New Contact button
19037
+ const newContactButton = dom.createElement('button')
19038
+ const container = dom.createElement('div')
19039
+ newContactButton.setAttribute('type', 'button')
19040
+ if (!me) newContactButton.setAttribute('disabled', 'true')
19041
+ external_commonjs_solid_logic_commonjs2_solid_logic_amd_solid_logic_root_SolidLogic_.authn.checkUser().then(webId => {
19042
+ if (webId) {
19043
+ setMe(webId)
19044
+ newContactButton.removeAttribute('disabled')
19045
+ }
19046
+ })
19047
+ container.appendChild(newContactButton)
19048
+ newContactButton.innerHTML = '+ New contact'
19049
+ newContactButton.classList.add('actionButton', 'btn-primary', 'action-button-focus')
19050
+ let newContactClickGeneration = 0
19051
+ newContactButton.addEventListener('click', async function (_event) {
19052
+ setActiveActionButton(null)
19053
+ deselectAllPeople(ctx.ulPeople)
19054
+ const thisGeneration = ++newContactClickGeneration
19055
+ ctx.showDetailsSection()
19056
+ ctx.detailsSectionContent.innerHTML = ''
19057
+ ctx.detailsSectionContent.classList.remove('detailsSectionContent--wide')
19058
+ await ensureBookLoaded()
19059
+ // Bail out if a newer click has taken over
19060
+ if (thisGeneration !== newContactClickGeneration) return
19061
+ ctx.detailsSectionContent.innerHTML = ''
19062
+
19063
+ const chooserDiv = dom.createElement('div')
19064
+ chooserDiv.classList.add('contactTypeChooser')
19065
+
19066
+ const selectLabel = dom.createElement('label')
19067
+ selectLabel.textContent = 'Contact type: '
19068
+ selectLabel.setAttribute('for', 'contactTypeSelect')
19069
+ chooserDiv.appendChild(selectLabel)
19070
+
19071
+ const select = dom.createElement('select')
19072
+ select.id = 'contactTypeSelect'
19073
+ select.classList.add('contactTypeSelect')
19074
+ const optIndividual = dom.createElement('option')
19075
+ optIndividual.value = 'Individual'
19076
+ optIndividual.textContent = 'New person'
19077
+ select.appendChild(optIndividual)
19078
+ const optOrganization = dom.createElement('option')
19079
+ optOrganization.value = 'Organization'
19080
+ optOrganization.textContent = 'New organization'
19081
+ select.appendChild(optOrganization)
19082
+ chooserDiv.appendChild(select)
19083
+
19084
+ ctx.detailsSectionContent.appendChild(chooserDiv)
19085
+
19086
+ const remark = dom.createElement('p')
19087
+ remark.classList.add('contactCreationRemark')
19088
+ remark.textContent = 'The new contact is added to the already selected group.'
19089
+ ctx.detailsSectionContent.appendChild(remark)
19090
+
19091
+ // Container for the askName form, placed below the select
19092
+ const formContainer = dom.createElement('div')
19093
+ formContainer.classList.add('contactFormContainer')
19094
+ ctx.detailsSectionContent.appendChild(formContainer)
19095
+
19096
+ function currentKlass () {
19097
+ return select.value === 'Organization'
19098
+ ? ns.vcard('Organization')
19099
+ : ns.vcard('Individual')
19100
+ }
19101
+
19102
+ // Render person form immediately as default
19103
+ createNewPersonOrOrganization(ctx, formContainer, currentKlass())
19104
+
19105
+ // Switch form when dropdown changes
19106
+ select.addEventListener('change', function () {
19107
+ createNewPersonOrOrganization(ctx, formContainer, currentKlass())
19108
+ })
19109
+ }, false)
19147
19110
 
19148
- // New group button at the bottom
19149
- const newGroupBtn = dom.createElement('button')
19150
- newGroupBtn.setAttribute('type', 'button')
19151
- newGroupBtn.innerHTML = '+ New group'
19152
- newGroupBtn.classList.add('actionButton', 'btn-primary', 'action-button-focus', 'newGroupBtn')
19153
- newGroupBtn.addEventListener('click', newGroupClickHandler, false)
19154
- detailsSectionContent.appendChild(newGroupBtn)
19155
- })
19111
+ // TODO we should also add if it is public or private
19112
+ header.appendChild(h2)
19113
+ header.appendChild(container)
19114
+ headerSection.appendChild(header)
19115
+ return headerSection
19116
+ }
19117
+
19118
+ // ── Builder: search input section ────────────────────────────────────
19119
+ function buildSearchSection (ctx) {
19120
+ const { dom } = ctx
19121
+ const searchSection = dom.createElement('section')
19122
+ searchSection.classList.add('searchSection')
19123
+ const searchDiv = dom.createElement('div')
19124
+ searchDiv.classList.add('searchDiv')
19125
+ searchSection.appendChild(searchDiv)
19126
+ const searchInput = dom.createElement('input')
19127
+ searchInput.setAttribute('type', 'text')
19128
+ searchInput.setAttribute('aria-label', 'Search contacts')
19129
+ searchInput.classList.add('searchInput')
19130
+ searchInput.setAttribute('placeholder', 'Search by name')
19131
+ searchDiv.appendChild(searchInput)
19132
+ searchInput.addEventListener('input', function (_event) {
19133
+ refreshFilteredPeople(ctx.ulPeople, true, ctx.detailsSectionContent)
19134
+ })
19135
+ return { searchSection, searchInput }
19136
+ }
19156
19137
 
19157
- // Sharing button
19158
- const sharingButton = cardFooter.appendChild(dom.createElement('button'))
19159
- sharingButton.setAttribute('type', 'button')
19160
- sharingButton.innerHTML = 'Sharing'
19161
- sharingButton.classList.add('actionButton', 'btn-secondary', 'action-button-focus')
19162
- actionButtons.push(sharingButton)
19163
- sharingButton.addEventListener('click', function (_event) {
19164
- setActiveActionButton(sharingButton)
19165
- showDetailsSection()
19166
- detailsSectionContent.innerHTML = ''
19167
-
19168
- const sharingHeader = dom.createElement('h3')
19169
- sharingHeader.textContent = 'Sharing'
19170
- detailsSectionContent.appendChild(sharingHeader)
19171
-
19172
- detailsSectionContent.appendChild(
19173
- external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.aclControl.ACLControlBox5(
19174
- book.dir(),
19175
- dataBrowserContext,
19176
- 'book',
19177
- kb,
19178
- function (ok, body) {
19179
- if (!ok) detailsSectionContent.innerHTML = 'ACL control box Failed: ' + body
19180
- }
19181
- )
19182
- )
19138
+ // ── Builder: group buttons bar ───────────────────────────────────────
19139
+ function buildGroupBar (ctx) {
19140
+ const { dom, kb, ns, book, options, groupIndex, selectedGroups,
19141
+ actionButtons, setActiveActionButton, div } = ctx
19183
19142
 
19184
- const sharingContext = {
19185
- target: book,
19186
- me,
19187
- noun: 'address book',
19188
- div: detailsSectionContent,
19189
- dom,
19190
- statusRegion: div
19191
- }
19192
- external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.login.registrationControl(sharingContext, book, contactsPane_ns.vcard('AddressBook'))
19193
- .then(() => log('Registration control finished.'))
19194
- .catch(e => external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.complain(sharingContext, 'registrationControl: ' + e))
19195
- })
19143
+ const buttonSection = dom.createElement('section')
19144
+ buttonSection.classList.add('buttonSection')
19196
19145
 
19197
- // Settings button
19198
- const toolsButton = cardFooter.appendChild(dom.createElement('button'))
19199
- toolsButton.setAttribute('type', 'button')
19200
- toolsButton.innerHTML = 'Tools'
19201
- toolsButton.classList.add('actionButton', 'btn-secondary', 'action-button-focus')
19202
- actionButtons.push(toolsButton)
19203
- toolsButton.addEventListener('click', function (_event) {
19204
- setActiveActionButton(toolsButton)
19205
- showDetailsSection()
19206
- detailsSectionContent.innerHTML = ''
19207
- detailsSectionContent.appendChild(
19208
- toolsPane_toolsPane(
19209
- selectAllGroups,
19210
- selectedGroups,
19211
- ulGroups,
19212
- book,
19213
- dataBrowserContext,
19214
- me
19215
- )
19216
- )
19217
- })
19218
- } // if book
19146
+ const ulGroups = dom.createElement('ul')
19147
+ ulGroups.classList.add('groupButtonsList')
19148
+ ulGroups.setAttribute('role', 'list')
19149
+ ulGroups.setAttribute('aria-label', 'Groups list')
19219
19150
 
19220
- /*
19221
- const newBookBtn = newAddressBookButton(book)
19222
- if (newBookBtn && newBookBtn.classList) {
19223
- newBookBtn.classList.add('actionButton', 'btn-secondary', 'action-button-focus')
19224
- }
19225
- cardFooter.appendChild(newBookBtn)
19226
- */
19151
+ if (options.foreignGroup) {
19152
+ selectedGroups[options.foreignGroup.uri] = true
19153
+ }
19227
19154
 
19228
- checkDataModel(book).then(() => { log('async checkDataModel done.') })
19155
+ if (book) {
19156
+ // All groups button — leftmost, initially selected
19157
+ ctx.allGroupsLi = dom.createElement('li')
19158
+ const allGroupsButton = dom.createElement('button')
19159
+ allGroupsButton.textContent = 'All groups'
19160
+ allGroupsButton.classList.add('allGroupsButton', 'actionButton', 'btn-primary', 'action-button-focus', 'allGroupsButton--selected')
19161
+ allGroupsButton.addEventListener('click', function (_event) {
19162
+ setActiveGroupButton(ulGroups, allGroupsButton)
19163
+ setActiveActionButton(null)
19164
+ // Check if all groups are currently selected
19165
+ const allSelected = Array.from(ulGroups.children).every(function (li) {
19166
+ if (!li.subject) return true // skip non-group items (All groups, New group)
19167
+ return !!selectedGroups[li.subject.uri]
19168
+ })
19169
+
19170
+ if (!allSelected) {
19171
+ allGroupsButton.classList.add('allGroupsButton--loading')
19172
+ allGroupsButton.setAttribute('aria-busy', 'true')
19173
+ selectAllGroups(selectedGroups, ulGroups, function (ok, message) {
19174
+ if (!ok) return complain(div, dom, message)
19175
+ allGroupsButton.classList.remove('allGroupsButton--loading')
19176
+ allGroupsButton.setAttribute('aria-busy', 'false')
19177
+ allGroupsButton.classList.add('allGroupsButton--active')
19178
+ refreshThingsSelected(ulGroups, selectedGroups)
19179
+ refreshNames(ctx.ulPeople, null)
19180
+ })
19181
+ } else {
19182
+ allGroupsButton.classList.remove('allGroupsButton--loading', 'allGroupsButton--active')
19183
+ allGroupsButton.setAttribute('aria-busy', 'false')
19184
+ allGroupsButton.classList.add('allGroupsButton--loaded') // pale green hint groups loaded
19185
+ for (const key in selectedGroups) delete selectedGroups[key]
19186
+ refreshThingsSelected(ulGroups, selectedGroups)
19187
+ refreshNames(ctx.ulPeople, null)
19188
+ }
19189
+ }) // on button click
19190
+ ctx.allGroupsLi.appendChild(allGroupsButton)
19191
+ ulGroups.appendChild(ctx.allGroupsLi) // First item in the list
19192
+
19193
+ // New group button — rightmost (appended after group buttons are rendered)
19194
+ ctx.newGroupLi = dom.createElement('li')
19195
+ const newGroupButton = dom.createElement('button')
19196
+ newGroupButton.setAttribute('type', 'button')
19197
+ newGroupButton.innerHTML = '+ New group'
19198
+ newGroupButton.classList.add('allGroupsButton', 'actionButton', 'btn-secondary', 'action-button-focus')
19199
+ actionButtons.push(newGroupButton)
19200
+ newGroupButton.addEventListener(
19201
+ 'click', function (event) {
19202
+ setActiveGroupButton(ulGroups, newGroupButton)
19203
+ setActiveActionButton(null)
19204
+ deselectAllPeople(ctx.ulPeople)
19205
+ handleNewGroupClick(ctx)
19206
+ },
19207
+ false
19208
+ )
19209
+ ctx.newGroupLi.appendChild(newGroupButton)
19210
+
19211
+ // Append ulGroups to buttonSection, then add New group at the end
19212
+ buttonSection.appendChild(ulGroups)
19213
+
19214
+ if (groupIndex) {
19215
+ kb.fetcher.nowOrWhenFetched(groupIndex.uri, book, function (ok, body) {
19216
+ if (!ok) return complain(div, dom, 'Cannot load group index: ' + body)
19217
+ // Remove special items before sync (syncTableToArrayReOrdered expects .subject on all children)
19218
+ if (ctx.allGroupsLi.parentNode) ctx.allGroupsLi.parentNode.removeChild(ctx.allGroupsLi)
19219
+ if (ctx.newGroupLi.parentNode) ctx.newGroupLi.parentNode.removeChild(ctx.newGroupLi)
19220
+ syncGroupUl(book, options, ulGroups, dom, selectedGroups, ctx.ulPeople, ctx.searchInput) // Refresh list of groups
19221
+ ulGroups.insertBefore(ctx.allGroupsLi, ulGroups.firstChild) // Keep All contacts first
19222
+ ulGroups.appendChild(ctx.newGroupLi) // Keep New group last
19223
+
19224
+ // Auto-select all groups and display all contacts on load
19225
+ allGroupsButton.classList.add('allGroupsButton--loading')
19226
+ allGroupsButton.setAttribute('aria-busy', 'true')
19227
+ selectAllGroups(selectedGroups, ulGroups, function (loadOk, message) {
19228
+ if (!loadOk) return complain(div, dom, message)
19229
+ allGroupsButton.classList.remove('allGroupsButton--loading')
19230
+ allGroupsButton.setAttribute('aria-busy', 'false')
19231
+ allGroupsButton.classList.add('allGroupsButton--active')
19232
+ refreshThingsSelected(ulGroups, selectedGroups)
19233
+ refreshNames(ctx.ulPeople, null)
19234
+ })
19235
+ })
19236
+ }
19237
+
19238
+ // Remove special items before initial render too
19239
+ if (ctx.allGroupsLi.parentNode) ctx.allGroupsLi.parentNode.removeChild(ctx.allGroupsLi)
19240
+ if (ctx.newGroupLi.parentNode) ctx.newGroupLi.parentNode.removeChild(ctx.newGroupLi)
19241
+ renderGroupButtons(book, ulGroups, options, dom, selectedGroups, ctx.ulPeople, ctx.searchInput, ctx.detailsSectionContent, div, ctx.dataBrowserContext, function () {
19242
+ setActiveActionButton(null)
19243
+ // Keep the details section open when a contact or New contact form is showing
19244
+ if (!ctx.detailsSectionContent.querySelector('.contactTypeChooser, .contactFormContainer, .renderPane')) {
19245
+ ctx.detailsSectionContent.innerHTML = ''
19246
+ ctx.detailsSection.classList.add('hidden')
19229
19247
  }
19248
+ })
19249
+ ulGroups.insertBefore(ctx.allGroupsLi, ulGroups.firstChild) // Keep All contacts first
19250
+ ulGroups.appendChild(ctx.newGroupLi) // Ensure New group is last after initial render
19251
+ } else {
19252
+ syncGroupUl(book, options, ulGroups, dom, selectedGroups, ctx.ulPeople, ctx.searchInput) // Refresh list of groups (will be empty)
19253
+ refreshNames(ctx.ulPeople, null)
19254
+ log('No book, only one group -> hide list of groups')
19255
+ } // if not book
19230
19256
 
19231
- // ///////////////////////////////////////////////////////////////////////////////////
19257
+ return { buttonSection, ulGroups }
19258
+ }
19232
19259
 
19233
- // Render a single contact Individual
19260
+ // ── Builder: footer action buttons (Groups / Sharing / Tools) ────────
19261
+ function buildFooterButtons (ctx) {
19262
+ const { dom, kb, ns, book, options, selectedGroups,
19263
+ actionButtons, setActiveActionButton, dataBrowserContext, div, me } = ctx
19234
19264
 
19235
- if (
19236
- t[contactsPane_ns.vcard('Individual').uri] ||
19237
- t[contactsPane_ns.foaf('Person').uri] ||
19238
- t[contactsPane_ns.schema('Person').uri] ||
19239
- t[contactsPane_ns.vcard('Organization').uri] ||
19240
- t[contactsPane_ns.schema('Organization').uri]
19241
- ) {
19242
- renderIndividual(dom, div, subject, dataBrowserContext).then(() => log('(individual rendered)'))
19265
+ const cardFooter = dom.createElement('div')
19266
+ cardFooter.classList.add('cardFooter')
19243
19267
 
19244
- // Render a Group instance
19245
- } else if (t[contactsPane_ns.vcard('Group').uri]) {
19246
- // If we have a main address book, then render this group as a guest group within it
19247
- external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.login
19248
- .findAppInstances(context, contactsPane_ns.vcard('AddressBook'))
19249
- .then(function (context) {
19250
- const addressBooks = context.instances
19251
- const options = { foreignGroup: subject }
19252
- if (addressBooks.length > 0) {
19253
- // const book = addressBooks[0]
19254
- renderAddressBook(addressBooks, options)
19255
- } else {
19256
- renderAddressBook([], options)
19257
- // @@ button to Make a new addressBook
19268
+ if (book) {
19269
+ // Groups button
19270
+ const groupsButton = cardFooter.appendChild(dom.createElement('button'))
19271
+ groupsButton.setAttribute('type', 'button')
19272
+ groupsButton.innerHTML = 'Groups'
19273
+ groupsButton.classList.add('actionButton', 'btn-secondary', 'action-button-focus')
19274
+ actionButtons.push(groupsButton)
19275
+ groupsButton.addEventListener('click', async function (_event) {
19276
+ setActiveActionButton(groupsButton)
19277
+ deselectAllPeople(ctx.ulPeople)
19278
+ ctx.showDetailsSection()
19279
+ ctx.detailsSectionContent.innerHTML = ''
19280
+ ctx.detailsSectionContent.classList.remove('detailsSectionContent--wide')
19281
+
19282
+ // Header
19283
+ const groupsHeader = dom.createElement('h3')
19284
+ groupsHeader.textContent = 'Your groups'
19285
+ ctx.detailsSectionContent.appendChild(groupsHeader)
19286
+
19287
+ const groupRemark = dom.createElement('p')
19288
+ groupRemark.textContent = 'When you delete a group it can happen that some contacts end up groupless.'
19289
+ ctx.detailsSectionContent.appendChild(groupRemark)
19290
+
19291
+ // Load all groups and display them in a list
19292
+ let groups
19293
+ try {
19294
+ groups = await loadAllGroups(book)
19295
+ } catch (err) {
19296
+ ctx.detailsSectionContent.appendChild(dom.createTextNode('Failed to load groups: ' + err))
19297
+ return
19298
+ }
19299
+
19300
+ const groupsList = dom.createElement('ul')
19301
+ groupsList.setAttribute('role', 'list')
19302
+ groupsList.setAttribute('aria-label', 'All groups')
19303
+ groupsList.classList.add('groupButtonsList')
19304
+
19305
+ // Sort groups by name
19306
+ if (groups) {
19307
+ groups.sort((a, b) => {
19308
+ const nameA = (kb.any(a, ns.vcard('fn')) || '').toString().toLowerCase()
19309
+ const nameB = (kb.any(b, ns.vcard('fn')) || '').toString().toLowerCase()
19310
+ return nameA < nameB ? -1 : nameA > nameB ? 1 : 0
19311
+ })
19312
+ groups.forEach(function (group) {
19313
+ const { groupLi, groupButton: groupBtn, name } = createGroupLi(group)
19314
+ groupBtn.addEventListener('click', function (event) {
19315
+ event.preventDefault()
19316
+ if (!event.metaKey) {
19317
+ for (const key in selectedGroups) delete selectedGroups[key]
19258
19318
  }
19259
- })
19260
- .catch(function (e) {
19261
- complain(div, dom, '' + e)
19262
- })
19319
+ selectedGroups[group.uri] = !selectedGroups[group.uri]
19320
+ refreshThingsSelected(ctx.ulGroups, selectedGroups)
19321
+ // Highlight the matching group button in the sidebar ulGroups
19322
+ const matchingLi = Array.from(ctx.ulGroups.children).find(li => li.subject && li.subject.uri === group.uri)
19323
+ setActiveGroupButton(ctx.ulGroups, matchingLi ? matchingLi.querySelector('button') : null)
19324
+ kb.fetcher.nowOrWhenFetched(group.doc(), undefined, function (ok, _message) {
19325
+ if (ok) {
19326
+ refreshNames(ctx.ulPeople, null, false)
19327
+ }
19328
+ })
19329
+ }, false)
19330
+
19331
+ external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.deleteButtonWithCheck(
19332
+ dom,
19333
+ groupLi,
19334
+ 'group ' + name,
19335
+ async function () {
19336
+ await deleteThingAndDoc(group)
19337
+ delete selectedGroups[group.uri]
19338
+ // Refresh the group buttons list
19339
+ const allGroupsLi = ctx.allGroupsLi
19340
+ const newGroupLi = ctx.newGroupLi
19341
+ if (allGroupsLi.parentNode) allGroupsLi.parentNode.removeChild(allGroupsLi)
19342
+ if (newGroupLi.parentNode) newGroupLi.parentNode.removeChild(newGroupLi)
19343
+ syncGroupUl(book, options, ctx.ulGroups, dom, selectedGroups, ctx.ulPeople, ctx.searchInput)
19344
+ ctx.ulGroups.insertBefore(allGroupsLi, ctx.ulGroups.firstChild)
19345
+ ctx.ulGroups.appendChild(newGroupLi)
19346
+ refreshThingsSelected(ctx.ulGroups, selectedGroups)
19347
+ // Refresh the people list to reflect the deleted group
19348
+ refreshNames(ctx.ulPeople, null, false)
19349
+ // Refresh the groups detail view
19350
+ groupsButton.click()
19351
+ }
19352
+ )
19263
19353
 
19264
- // Render a AddressBook instance
19265
- } else if (t[contactsPane_ns.vcard('AddressBook').uri]) {
19266
- renderAddressBook([subject], {})
19267
- } else {
19268
- log(
19269
- 'Error: Contact pane: No evidence that ' +
19270
- subject +
19271
- ' is anything to do with contacts.'
19272
- )
19354
+ groupsList.appendChild(groupLi)
19355
+ })
19273
19356
  }
19274
19357
 
19275
- // /////////////// Fix user when testing on a plane
19358
+ ctx.detailsSectionContent.appendChild(groupsList)
19276
19359
 
19277
- if (
19278
- typeof document !== 'undefined' &&
19279
- document.location &&
19280
- ('' + document.location).slice(0, 16) === 'http://localhost'
19281
- ) {
19282
- me = kb.any(subject, external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.ns.acl('owner')) // when testing on plane with no webid
19283
- log('Assuming user is ' + me)
19360
+ // New group button at the bottom
19361
+ const newGroupBtn = dom.createElement('button')
19362
+ newGroupBtn.setAttribute('type', 'button')
19363
+ newGroupBtn.innerHTML = '+ New group'
19364
+ newGroupBtn.classList.add('actionButton', 'btn-primary', 'action-button-focus', 'newGroupBtn')
19365
+ newGroupBtn.addEventListener('click', function () { handleNewGroupClick(ctx) }, false)
19366
+ ctx.detailsSectionContent.appendChild(newGroupBtn)
19367
+ })
19368
+
19369
+ // Sharing button
19370
+ const sharingButton = cardFooter.appendChild(dom.createElement('button'))
19371
+ sharingButton.setAttribute('type', 'button')
19372
+ sharingButton.innerHTML = 'Sharing'
19373
+ sharingButton.classList.add('actionButton', 'btn-secondary', 'action-button-focus')
19374
+ actionButtons.push(sharingButton)
19375
+ sharingButton.addEventListener('click', function (_event) {
19376
+ setActiveActionButton(sharingButton)
19377
+ deselectAllPeople(ctx.ulPeople)
19378
+ ctx.showDetailsSection()
19379
+ ctx.detailsSectionContent.innerHTML = ''
19380
+ ctx.detailsSectionContent.classList.remove('detailsSectionContent--wide')
19381
+
19382
+ const sharingHeader = dom.createElement('h3')
19383
+ sharingHeader.textContent = 'Sharing'
19384
+ ctx.detailsSectionContent.appendChild(sharingHeader)
19385
+
19386
+ ctx.detailsSectionContent.appendChild(
19387
+ external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.aclControl.ACLControlBox5(
19388
+ book.dir(),
19389
+ dataBrowserContext,
19390
+ 'book',
19391
+ kb,
19392
+ function (ok, body) {
19393
+ if (!ok) ctx.detailsSectionContent.innerHTML = 'ACL control box Failed: ' + body
19394
+ }
19395
+ )
19396
+ )
19397
+
19398
+ const sharingContext = {
19399
+ target: book,
19400
+ me,
19401
+ noun: 'address book',
19402
+ div: ctx.detailsSectionContent,
19403
+ dom,
19404
+ statusRegion: div
19284
19405
  }
19285
- return div
19286
- } // asyncRender
19287
- } // render function
19288
- }); // pane object
19289
- // ends
19406
+ external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.login.registrationControl(sharingContext, book, ns.vcard('AddressBook'))
19407
+ .then(() => log('Registration control finished.'))
19408
+ .catch(e => external_commonjs_solid_ui_commonjs2_solid_ui_amd_solid_ui_root_UI_.widgets.complain(sharingContext, 'registrationControl: ' + e))
19409
+ })
19410
+
19411
+ // Settings button
19412
+ const toolsButton = cardFooter.appendChild(dom.createElement('button'))
19413
+ toolsButton.setAttribute('type', 'button')
19414
+ toolsButton.innerHTML = 'Tools'
19415
+ toolsButton.classList.add('actionButton', 'btn-secondary', 'action-button-focus')
19416
+ actionButtons.push(toolsButton)
19417
+ toolsButton.addEventListener('click', function (_event) {
19418
+ setActiveActionButton(toolsButton)
19419
+ deselectAllPeople(ctx.ulPeople)
19420
+ ctx.showDetailsSection()
19421
+ ctx.detailsSectionContent.innerHTML = ''
19422
+ ctx.detailsSectionContent.classList.add('detailsSectionContent--wide')
19423
+ ctx.detailsSectionContent.appendChild(
19424
+ toolsPane_toolsPane(
19425
+ selectAllGroups,
19426
+ selectedGroups,
19427
+ ctx.ulGroups,
19428
+ book,
19429
+ dataBrowserContext,
19430
+ me,
19431
+ function refreshGroups () {
19432
+ if (ctx.allGroupsLi.parentNode) ctx.allGroupsLi.parentNode.removeChild(ctx.allGroupsLi)
19433
+ if (ctx.newGroupLi.parentNode) ctx.newGroupLi.parentNode.removeChild(ctx.newGroupLi)
19434
+ syncGroupUl(book, options, ctx.ulGroups, dom, selectedGroups, ctx.ulPeople, ctx.searchInput)
19435
+ ctx.ulGroups.insertBefore(ctx.allGroupsLi, ctx.ulGroups.firstChild)
19436
+ ctx.ulGroups.appendChild(ctx.newGroupLi)
19437
+ refreshThingsSelected(ctx.ulGroups, selectedGroups)
19438
+ }
19439
+ )
19440
+ )
19441
+ })
19442
+ } // if book
19443
+
19444
+ return cardFooter
19445
+ }
19290
19446
 
19291
19447
  __nested_webpack_exports__ = __nested_webpack_exports__["default"];
19292
19448
  /******/ return __nested_webpack_exports__;
@@ -82283,7 +82439,7 @@ Object.defineProperty(exports, "__esModule", ({
82283
82439
  }));
82284
82440
  exports["default"] = void 0;
82285
82441
  var _default = exports["default"] = {
82286
- buildTime: '2026-03-05T17:53:35Z',
82442
+ buildTime: '2026-03-07T11:48:05Z',
82287
82443
  commit: 'dfd8ed6d211c4df3ce43c2bd2b220ba2dab03a61',
82288
82444
  npmInfo: {
82289
82445
  'solid-panes': '4.2.3',
@@ -116320,7 +116476,7 @@ var dist = __webpack_require__(7523);
116320
116476
  var solid_logic_esm = __webpack_require__(9332);
116321
116477
  ;// ./src/versionInfo.ts
116322
116478
  /* harmony default export */ const versionInfo = ({
116323
- buildTime: '2026-03-05T17:54:36Z',
116479
+ buildTime: '2026-03-07T11:49:14Z',
116324
116480
  commit: 'e1941a6329a0d2666cfebf1fca9a73fdd04fd20e',
116325
116481
  npmInfo: {
116326
116482
  'mashlib': '2.1.3',