@asd20/ui-next 2.37.0 → 2.38.0
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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/components/atoms/Asd20RichBodyContent/index.vue +1 -1
- package/src/components/atoms/Asd20RichBodyContent/messageDocumentRenderer.js +2 -3
- package/src/components/templates/Asd20HandbookTemplate/index.vue +1 -1
- package/src/helpers/liveMessageData.js +22 -13
- package/src/helpers/liveMessageDataRuntime.js +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
# [2.38.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.37.0...ui-next-v2.38.0) (2026-11-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add organization email and omit missing public data ([8b1b8a7](https://github.com/academydistrict20/asd20-ui-next/commit/8b1b8a79a6b6ea04b8925cce517d022c839a1955))
|
|
9
|
+
|
|
3
10
|
# [2.37.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.36.0...ui-next-v2.37.0) (2026-11-10)
|
|
4
11
|
|
|
5
12
|
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
contextualAccordionItemKey,
|
|
5
5
|
normalizeHeadingContext,
|
|
6
6
|
} from './headingContext.js'
|
|
7
|
-
import { validateLiveDataReference
|
|
7
|
+
import { validateLiveDataReference } from '../../../helpers/liveMessageData.js'
|
|
8
8
|
|
|
9
9
|
const MESSAGE_DOCUMENT_SCHEMA_VERSION = 1
|
|
10
10
|
|
|
@@ -807,8 +807,7 @@ function renderImage(attrs, className = '') {
|
|
|
807
807
|
|
|
808
808
|
function renderStandardNode(node, context, path, accordionItemId) {
|
|
809
809
|
if (node.type === 'liveDataField' || node.type === 'liveDataBlock') {
|
|
810
|
-
|
|
811
|
-
return `<${tag} class="asd20-live-data-unavailable">${escapeHtml(liveDataLabel(node.attrs))} is currently unavailable.</${tag}>`
|
|
810
|
+
return ''
|
|
812
811
|
}
|
|
813
812
|
if (node.type === 'text') return renderMarkedText(node, context, path)
|
|
814
813
|
if (node.type === 'hardBreak') return '<br>'
|
|
@@ -3,21 +3,22 @@
|
|
|
3
3
|
|
|
4
4
|
// Canonical public live-data contract. The shared UI's copy is generated by
|
|
5
5
|
// scripts/sync-live-message-data.mjs; never add arbitrary paths or queries here.
|
|
6
|
-
const LIVE_DATA_VERSION =
|
|
6
|
+
const LIVE_DATA_VERSION = 4;
|
|
7
7
|
const ADMINISTRATION_TEAM_TAG = "asd20:handbook:administration";
|
|
8
8
|
const LIVE_DATA_MEMBER_FIELDS = Object.freeze({ name: "Name", title: "Title", phone: "Phone" });
|
|
9
9
|
const LIVE_DATA_FIELDS = Object.freeze({
|
|
10
10
|
organization: Object.freeze({
|
|
11
|
-
title: "Organization
|
|
12
|
-
address: "Organization
|
|
13
|
-
phone: "Organization
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
title: "Organization Name",
|
|
12
|
+
address: "Organization Address",
|
|
13
|
+
phone: "Organization Main Phone",
|
|
14
|
+
email: "Organization Email",
|
|
15
|
+
hours: "Organization Office Hours",
|
|
16
|
+
administrators: "Organization Administrators",
|
|
16
17
|
}),
|
|
17
18
|
team: Object.freeze({
|
|
18
|
-
contactPhone: "Team
|
|
19
|
-
officeHours: "Team
|
|
20
|
-
staff: "Team
|
|
19
|
+
contactPhone: "Team Contact Phone",
|
|
20
|
+
officeHours: "Team Office Hours",
|
|
21
|
+
staff: "Team Staff List",
|
|
21
22
|
}),
|
|
22
23
|
});
|
|
23
24
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -55,7 +56,7 @@ function validateLiveDataReference(node) {
|
|
|
55
56
|
|
|
56
57
|
function liveDataLabel(attrs) {
|
|
57
58
|
const label = LIVE_DATA_FIELDS[attrs?.source]?.[attrs?.field] || "Live information";
|
|
58
|
-
return attrs?.binding === "currentSchool" ? `Current
|
|
59
|
+
return attrs?.binding === "currentSchool" ? `Current Organization · ${label}` : label;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
function liveDataKey(attrs) {
|
|
@@ -170,7 +171,7 @@ function liveDataContent(attrs, snapshot, organizationId) {
|
|
|
170
171
|
|
|
171
172
|
// Materialization returns a copy containing only ordinary document nodes.
|
|
172
173
|
// Persist the authored references, never this presentation projection.
|
|
173
|
-
function resolveLiveDataDocument(bodyJson, snapshot, { strict = true, organizationId } = {}) {
|
|
174
|
+
function resolveLiveDataDocument(bodyJson, snapshot, { strict = true, organizationId, unavailable = "message" } = {}) {
|
|
174
175
|
collectLiveDataReferences(bodyJson);
|
|
175
176
|
const value = typeof bodyJson === "string" ? JSON.parse(bodyJson || "null") : bodyJson;
|
|
176
177
|
const diagnostics = [];
|
|
@@ -179,14 +180,22 @@ function resolveLiveDataDocument(bodyJson, snapshot, { strict = true, organizati
|
|
|
179
180
|
const content = liveDataContent(node.attrs, snapshot, organizationId);
|
|
180
181
|
if (content) return content;
|
|
181
182
|
diagnostics.push({ code: "live-data-unavailable", reference: liveDataKey(node.attrs) });
|
|
183
|
+
if (unavailable === "omit") return [];
|
|
182
184
|
const message = `${liveDataLabel(node.attrs)} is currently unavailable.`;
|
|
183
185
|
return node.type === "liveDataBlock" ? [paragraph(message)] : [textNode(message)];
|
|
184
186
|
}
|
|
185
187
|
if (!node || typeof node !== "object") return [node];
|
|
188
|
+
const content = Array.isArray(node.content) ? node.content.flatMap(resolve) : undefined;
|
|
189
|
+
if (unavailable === "omit" && node.type === "heading" && content?.length === 0 &&
|
|
190
|
+
node.content.some(child => LIVE_DATA_NODE_TYPES.has(child.type))) return [];
|
|
186
191
|
return [{ ...node, ...(node.attrs ? { attrs: { ...node.attrs } } : {}),
|
|
187
|
-
...(
|
|
192
|
+
...(content ? { content } : {}) }];
|
|
188
193
|
}
|
|
189
194
|
const document = resolve(value)[0];
|
|
195
|
+
// Keep an all-missing projection renderable without falling back to authored
|
|
196
|
+
// HTML. This placeholder is presentation-only and never saved to the message.
|
|
197
|
+
if (unavailable === "omit" && diagnostics.length && document?.type === "doc" && document.content?.length === 0)
|
|
198
|
+
document.content = [{ type: "paragraph" }];
|
|
190
199
|
if (strict && diagnostics.length) throw new Error("Required live information is unavailable. Review the message data sources.");
|
|
191
200
|
return { document, diagnostics };
|
|
192
201
|
}
|
|
@@ -212,7 +221,7 @@ async function fetchLiveDataSnapshot(documents, { endpoint, fetchImpl = globalTh
|
|
|
212
221
|
body: JSON.stringify({
|
|
213
222
|
query: `query liveMessageData($organizationIds: [uuid!]!, $teamIds: [uuid!]!${currentTeam ? ", $contextOrganizationIds: [uuid!]!" : ""}) {
|
|
214
223
|
organizations(where: {id: {_in: $organizationIds}, status: {_eq: "Published"}}) {
|
|
215
|
-
id status title address phone hours administrators
|
|
224
|
+
id status title address phone email hours administrators
|
|
216
225
|
}
|
|
217
226
|
teams: groups(where: {${currentTeam ? `_or: [{id: {_in: $teamIds}}, {ownerOrganizationId: {_in: $contextOrganizationIds}, tags: {_contains: ["${ADMINISTRATION_TEAM_TAG}"]}}]` : "id: {_in: $teamIds}"}, isPublic: {_eq: true}, ownerOrganization: {status: {_eq: "Published"}}}, limit: 101) {
|
|
218
227
|
id title isPublic contactPhone officeHours ownerOrganizationId tags ownerOrganization { status }
|
|
@@ -9,6 +9,7 @@ export const liveMessageDataOrganizationKey = 'liveMessageDataOrganization'
|
|
|
9
9
|
// descendants. Only references trigger a request; ordinary messages are inert.
|
|
10
10
|
export const liveMessageDataMixin = {
|
|
11
11
|
inject: {
|
|
12
|
+
showLiveDataDiagnostics: { from: 'liveMessageDataDiagnostics', default: false },
|
|
12
13
|
liveDataEndpoint: { from: liveMessageDataEndpointKey, default: '' },
|
|
13
14
|
liveDataProvider: { from: liveMessageDataProviderKey, default: null },
|
|
14
15
|
liveDataOrganizationProvider: { from: liveMessageDataOrganizationKey, default: null },
|
|
@@ -46,9 +47,9 @@ export const liveMessageDataMixin = {
|
|
|
46
47
|
signal: controller.signal,
|
|
47
48
|
organizationId: this.liveDataContextOrganizationId,
|
|
48
49
|
})
|
|
49
|
-
// Validate
|
|
50
|
+
// Validate references while retaining available fields in a partial capture.
|
|
50
51
|
this.liveDataDocuments.forEach(doc => {
|
|
51
|
-
if (collectLiveDataReferences(doc).length) resolveLiveDataDocument(doc, snapshot, { organizationId: this.liveDataContextOrganizationId })
|
|
52
|
+
if (collectLiveDataReferences(doc).length) resolveLiveDataDocument(doc, snapshot, { strict: false, organizationId: this.liveDataContextOrganizationId })
|
|
52
53
|
})
|
|
53
54
|
if (epoch === this.liveDataEpoch) this.liveDataSnapshot = snapshot
|
|
54
55
|
} catch { if (epoch === this.liveDataEpoch) this.liveDataError = 'Live information is currently unavailable.' }
|
|
@@ -57,7 +58,7 @@ export const liveMessageDataMixin = {
|
|
|
57
58
|
projectLiveMessageData(bodyJson) {
|
|
58
59
|
try {
|
|
59
60
|
if (!collectLiveDataReferences(bodyJson).length) return bodyJson
|
|
60
|
-
return resolveLiveDataDocument(bodyJson, this.effectiveLiveDataSnapshot, { strict: false, organizationId: this.liveDataContextOrganizationId }).document
|
|
61
|
+
return resolveLiveDataDocument(bodyJson, this.effectiveLiveDataSnapshot, { strict: false, organizationId: this.liveDataContextOrganizationId, unavailable: this.showLiveDataDiagnostics ? 'message' : 'omit' }).document
|
|
61
62
|
} catch { return bodyJson }
|
|
62
63
|
},
|
|
63
64
|
},
|