diff --git a/SPEC.md b/SPEC.md index ba8c0c1..5d71ef3 100644 --- a/SPEC.md +++ b/SPEC.md @@ -63,6 +63,20 @@ type VideoSource = AVSource & { `VideoSource` extends AVSource to add in the properties relevant just to videos +### `QuestionAndAnswerByline` + +```ts +interface QuestionAndAnswerByline extends Node { + type: "question-and-answer-byline" + conceptId: string + title?: string + external displayName: string + external headshotUrl: string +} +``` + +`QuestionAndAnswerByline` is a byline used for Q&A answers - it carries a `conceptId` to resolve external fields in cp-content-pipeline. + ## Core Nodes ### `Node` @@ -342,6 +356,9 @@ type StoryBlock = | Definition | InfoBox | InfoPair + | QuestionAndAnswer + | Question + | Answer ``` `StoryBlock` nodes are things that can be inserted into an article body. @@ -375,6 +392,41 @@ interface ImageSet extends Node { } ``` +### `QuestionAndAnswer` + +```ts +interface QuestionAndAnswer extends Parent { + type: "question-and-answer" + children: [Question, Answer, ...Answer[]] +} +``` + +`QuestionAndAnswer` defines a container grouping a `Question` followed by one or more `Answer` nodes + +### `Question` + +```ts +interface Question extends Parent { + type: "question" + displayName?: string + children: (Paragraph | (Text | Break | Strong | Emphasis | Strikethrough))[] +} +``` + +`Question` defines a Q&A question. Disallows `Link` and `FindOutMoreLink` nodes by explicitly picking the allowed phrasing node types. + +### `Answer` + +```ts +interface Answer extends Parent { + type: "answer" + byline: QuestionAndAnswerByline + children: (Paragraph | Phrasing)[] +} +``` + +`Answer` defines an answer to a Q&A Question. Uses `QuestionAndAnswerByline` to display the author. + #### Image types ##### `ImageSetPicture` diff --git a/content-tree.d.ts b/content-tree.d.ts index 23a1ef6..79ad2a9 100644 --- a/content-tree.d.ts +++ b/content-tree.d.ts @@ -12,6 +12,13 @@ export declare namespace ContentTree { pixelWidth?: number; videoCodec?: string; }; + interface QuestionAndAnswerByline extends Node { + type: "question-and-answer-byline"; + conceptId: string; + title?: string; + displayName: string; + headshotUrl: string; + } interface Node { type: string; data?: any; @@ -88,7 +95,7 @@ export declare namespace ContentTree { type: "blockquote"; children: (Paragraph | Phrasing)[]; } - type StoryBlock = ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | Pullquote | ScrollyBlock | ClipSet | Table | Recommended | RecommendedList | Tweet | Video | YoutubeVideo | Timeline | ImagePair | InNumbers | Definition | InfoBox | InfoPair; + type StoryBlock = ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | Pullquote | ScrollyBlock | ClipSet | Table | Recommended | RecommendedList | Tweet | Video | YoutubeVideo | Timeline | ImagePair | InNumbers | Definition | InfoBox | InfoPair | QuestionAndAnswer | Question | Answer; interface Pullquote extends Node { type: "pullquote"; text: string; @@ -100,6 +107,20 @@ export declare namespace ContentTree { picture: ImageSetPicture; fragmentIdentifier?: string; } + interface QuestionAndAnswer extends Parent { + type: "question-and-answer"; + children: [Question, Answer, ...Answer[]]; + } + interface Question extends Parent { + type: "question"; + displayName?: string; + children: (Paragraph | (Text | Break | Strong | Emphasis | Strikethrough))[]; + } + interface Answer extends Parent { + type: "answer"; + byline: QuestionAndAnswerByline; + children: (Paragraph | Phrasing)[]; + } type ImageSetPicture = { layoutWidth: string; imageType: "image" | "graphic"; @@ -431,6 +452,13 @@ export declare namespace ContentTree { pixelWidth?: number; videoCodec?: string; }; + interface QuestionAndAnswerByline extends Node { + type: "question-and-answer-byline"; + conceptId: string; + title?: string; + displayName: string; + headshotUrl: string; + } interface Node { type: string; data?: any; @@ -507,7 +535,7 @@ export declare namespace ContentTree { type: "blockquote"; children: (Paragraph | Phrasing)[]; } - type StoryBlock = ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | Pullquote | ScrollyBlock | ClipSet | Table | Recommended | RecommendedList | Tweet | Video | YoutubeVideo | Timeline | ImagePair | InNumbers | Definition | InfoBox | InfoPair; + type StoryBlock = ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | Pullquote | ScrollyBlock | ClipSet | Table | Recommended | RecommendedList | Tweet | Video | YoutubeVideo | Timeline | ImagePair | InNumbers | Definition | InfoBox | InfoPair | QuestionAndAnswer | Question | Answer; interface Pullquote extends Node { type: "pullquote"; text: string; @@ -519,6 +547,20 @@ export declare namespace ContentTree { picture: ImageSetPicture; fragmentIdentifier?: string; } + interface QuestionAndAnswer extends Parent { + type: "question-and-answer"; + children: [Question, Answer, ...Answer[]]; + } + interface Question extends Parent { + type: "question"; + displayName?: string; + children: (Paragraph | (Text | Break | Strong | Emphasis | Strikethrough))[]; + } + interface Answer extends Parent { + type: "answer"; + byline: QuestionAndAnswerByline; + children: (Paragraph | Phrasing)[]; + } type ImageSetPicture = { layoutWidth: string; imageType: "image" | "graphic"; @@ -851,6 +893,11 @@ export declare namespace ContentTree { pixelWidth?: number; videoCodec?: string; }; + interface QuestionAndAnswerByline extends Node { + type: "question-and-answer-byline"; + conceptId: string; + title?: string; + } interface Node { type: string; data?: any; @@ -927,7 +974,7 @@ export declare namespace ContentTree { type: "blockquote"; children: (Paragraph | Phrasing)[]; } - type StoryBlock = ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | Pullquote | ScrollyBlock | ClipSet | Table | Recommended | RecommendedList | Tweet | Video | YoutubeVideo | Timeline | ImagePair | InNumbers | Definition | InfoBox | InfoPair; + type StoryBlock = ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | Pullquote | ScrollyBlock | ClipSet | Table | Recommended | RecommendedList | Tweet | Video | YoutubeVideo | Timeline | ImagePair | InNumbers | Definition | InfoBox | InfoPair | QuestionAndAnswer | Question | Answer; interface Pullquote extends Node { type: "pullquote"; text: string; @@ -938,6 +985,20 @@ export declare namespace ContentTree { id: string; fragmentIdentifier?: string; } + interface QuestionAndAnswer extends Parent { + type: "question-and-answer"; + children: [Question, Answer, ...Answer[]]; + } + interface Question extends Parent { + type: "question"; + displayName?: string; + children: (Paragraph | (Text | Break | Strong | Emphasis | Strikethrough))[]; + } + interface Answer extends Parent { + type: "answer"; + byline: QuestionAndAnswerByline; + children: (Paragraph | Phrasing)[]; + } type ImageSetPicture = { layoutWidth: string; imageType: "image" | "graphic"; @@ -1244,6 +1305,13 @@ export declare namespace ContentTree { pixelWidth?: number; videoCodec?: string; }; + interface QuestionAndAnswerByline extends Node { + type: "question-and-answer-byline"; + conceptId: string; + title?: string; + displayName?: string; + headshotUrl?: string; + } interface Node { type: string; data?: any; @@ -1320,7 +1388,7 @@ export declare namespace ContentTree { type: "blockquote"; children: (Paragraph | Phrasing)[]; } - type StoryBlock = ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | Pullquote | ScrollyBlock | ClipSet | Table | Recommended | RecommendedList | Tweet | Video | YoutubeVideo | Timeline | ImagePair | InNumbers | Definition | InfoBox | InfoPair; + type StoryBlock = ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | Pullquote | ScrollyBlock | ClipSet | Table | Recommended | RecommendedList | Tweet | Video | YoutubeVideo | Timeline | ImagePair | InNumbers | Definition | InfoBox | InfoPair | QuestionAndAnswer | Question | Answer; interface Pullquote extends Node { type: "pullquote"; text: string; @@ -1332,6 +1400,20 @@ export declare namespace ContentTree { picture?: ImageSetPicture; fragmentIdentifier?: string; } + interface QuestionAndAnswer extends Parent { + type: "question-and-answer"; + children: [Question, Answer, ...Answer[]]; + } + interface Question extends Parent { + type: "question"; + displayName?: string; + children: (Paragraph | (Text | Break | Strong | Emphasis | Strikethrough))[]; + } + interface Answer extends Parent { + type: "answer"; + byline: QuestionAndAnswerByline; + children: (Paragraph | Phrasing)[]; + } type ImageSetPicture = { layoutWidth: string; imageType: "image" | "graphic"; diff --git a/content_tree.go b/content_tree.go index fc883b1..92c3129 100644 --- a/content_tree.go +++ b/content_tree.go @@ -80,6 +80,7 @@ const ( ScrollyCopyChildType = "scrolly-copy-child" ScrollySectionChildType = "scrolly-section-child" TableChildType = "table-child" + QuestionChildType = "question-child" TimelineType = "timeline" TimelineEventType = "timeline-event" @@ -93,7 +94,10 @@ const ( DefinitionType = "definition" InNumbersType = "in-numbers" - ImagePairType = "image-pair" + ImagePairType = "image-pair" + QuestionAndAnswerType = "question-and-answer" + QuestionType = "question" + AnswerType = "answer" ) var ( @@ -142,6 +146,14 @@ type ColumnSettingsItems struct { Sortable bool `json:"sortable,omitempty"` } +type QuestionAndAnswerByline struct { + Type string `json:"type"` + ConceptId string `json:"conceptId"` + Title string `json:"title,omitempty"` + DisplayName string `json:"displayName"` + HeadshotUrl string `json:"headshotUrl"` +} + type BigNumber struct { Type string `json:"type"` Description string `json:"description"` @@ -433,6 +445,9 @@ type BodyBlock struct { *ImagePair *InfoBox *InfoPair + *QuestionAndAnswer + *Question + *Answer } func (n *BodyBlock) GetType() string { @@ -515,6 +530,15 @@ func (n *BodyBlock) GetEmbedded() Node { if n.InfoPair != nil { return n.InfoPair } + if n.QuestionAndAnswer != nil { + return n.QuestionAndAnswer + } + if n.Question != nil { + return n.Question + } + if n.Answer != nil { + return n.Answer + } return nil } @@ -756,6 +780,24 @@ func (n *BodyBlock) UnmarshalJSON(data []byte) error { return err } n.InfoPair = &v + case QuestionAndAnswerType: + var v QuestionAndAnswer + if err := json.Unmarshal(data, &v); err != nil { + return err + } + n.QuestionAndAnswer = &v + case QuestionType: + var v Question + if err := json.Unmarshal(data, &v); err != nil { + return err + } + n.Question = &v + case AnswerType: + var v Answer + if err := json.Unmarshal(data, &v); err != nil { + return err + } + n.Answer = &v default: return fmt.Errorf("failed to unmarshal BodyBlock from %s: %w", data, ErrUnmarshalInvalidNode) } @@ -814,6 +856,12 @@ func (n *BodyBlock) MarshalJSON() ([]byte, error) { return json.Marshal(n.InfoBox) case n.InfoPair != nil: return json.Marshal(n.InfoPair) + case n.QuestionAndAnswer != nil: + return json.Marshal(n.QuestionAndAnswer) + case n.Question != nil: + return json.Marshal(n.Question) + case n.Answer != nil: + return json.Marshal(n.Answer) default: return []byte(`{}`), nil } @@ -872,6 +920,12 @@ func makeBodyBlock(n Node) (*BodyBlock, error) { return &BodyBlock{InfoBox: n.(*InfoBox)}, nil case InfoPairType: return &BodyBlock{InfoPair: n.(*InfoPair)}, nil + case QuestionAndAnswerType: + return &BodyBlock{QuestionAndAnswer: n.(*QuestionAndAnswer)}, nil + case QuestionType: + return &BodyBlock{Question: n.(*Question)}, nil + case AnswerType: + return &BodyBlock{Answer: n.(*Answer)}, nil default: return nil, ErrInvalidChildType } @@ -3333,3 +3387,293 @@ func (n *InfoPair) AppendChild(child Node) error { n.Children = append(n.Children, child.(*Card)) return nil } + +type QuestionAndAnswer struct { + Type string `json:"type"` + Children []*BodyBlock `json:"children"` +} + +func (n *QuestionAndAnswer) GetType() string { return n.Type } +func (n *QuestionAndAnswer) GetEmbedded() Node { return nil } +func (n *QuestionAndAnswer) GetChildren() []Node { + result := make([]Node, len(n.Children)) + for i, v := range n.Children { + result[i] = v + } + return result +} +func (n *QuestionAndAnswer) AppendChild(child Node) error { + switch child.GetType() { + case QuestionType: + if len(n.Children) != 0 { + return ErrInvalidChildType + } + bb, err := makeBodyBlock(child) + if err != nil { + return err + } + n.Children = append(n.Children, bb) + return nil + case AnswerType: + if len(n.Children) == 0 { + return ErrInvalidChildType + } + bb, err := makeBodyBlock(child) + if err != nil { + return err + } + n.Children = append(n.Children, bb) + return nil + default: + return ErrInvalidChildType + } +} + +func (n *QuestionAndAnswer) UnmarshalJSON(data []byte) error { + // Temporary struct to parse children as raw messages first + var raw struct { + Type string `json:"type"` + Children []json.RawMessage `json:"children"` + } + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + if raw.Type != QuestionAndAnswerType { + return fmt.Errorf("failed to unmarshal QuestionAndAnswer from %s: %w", data, ErrUnmarshalInvalidNode) + } + + // Must have at least two children: one Question and one Answer + if len(raw.Children) < 2 { + return fmt.Errorf("invalid QuestionAndAnswer, expected at least 2 children, got %d", len(raw.Children)) + } + + children := make([]*BodyBlock, 0, len(raw.Children)) + for i, rc := range raw.Children { + // Determine the type of the child + var tn typedNode + if err := json.Unmarshal(rc, &tn); err != nil { + return err + } + var bb BodyBlock + switch tn.Type { + case QuestionType: + // first child must be Question + if i != 0 { + return fmt.Errorf("invalid QuestionAndAnswer: Question found at position %d", i) + } + var v Question + if err := json.Unmarshal(rc, &v); err != nil { + return err + } + bb = BodyBlock{Question: &v} + case AnswerType: + // Answers allowed only after first child + if i == 0 { + return fmt.Errorf("invalid QuestionAndAnswer: Answer found at position 0") + } + var v Answer + if err := json.Unmarshal(rc, &v); err != nil { + return err + } + bb = BodyBlock{Answer: &v} + default: + return fmt.Errorf("invalid child type %s in QuestionAndAnswer", tn.Type) + } + children = append(children, &bb) + } + + n.Type = raw.Type + n.Children = children + return nil +} + +type Question struct { + Type string `json:"type"` + DisplayName string `json:"displayName,omitempty"` + Children []*QuestionChild `json:"children"` +} + +func (n *Question) GetType() string { return n.Type } +func (n *Question) GetEmbedded() Node { return nil } +func (n *Question) GetChildren() []Node { + result := make([]Node, len(n.Children)) + for i, v := range n.Children { + result[i] = v + } + return result +} +func (n *Question) AppendChild(child Node) error { + c, err := makeQuestionChild(child) + if err != nil { + return err + } + n.Children = append(n.Children, c) + return nil +} + +type Answer struct { + Type string `json:"type"` + Byline QuestionAndAnswerByline `json:"byline"` + Children []*Phrasing `json:"children"` +} + +func (n *Answer) GetType() string { return n.Type } +func (n *Answer) GetEmbedded() Node { return nil } +func (n *Answer) GetChildren() []Node { + result := make([]Node, len(n.Children)) + for i, v := range n.Children { + result[i] = v + } + return result +} +func (n *Answer) AppendChild(child Node) error { + p, err := makePhrasing(child) + if err != nil { + return err + } + n.Children = append(n.Children, p) + return nil +} + +type QuestionChild struct { + *Paragraph + *Text + *Break + *Strong + *Emphasis + *Strikethrough +} + +func (n *QuestionChild) GetType() string { return QuestionChildType } + +func (n *QuestionChild) GetEmbedded() Node { + if n.Paragraph != nil { + return n.Paragraph + } + if n.Text != nil { + return n.Text + } + if n.Break != nil { + return n.Break + } + if n.Strong != nil { + return n.Strong + } + if n.Emphasis != nil { + return n.Emphasis + } + if n.Strikethrough != nil { + return n.Strikethrough + } + return nil +} + +func (n *QuestionChild) GetChildren() []Node { + if n.Paragraph != nil { + return n.Paragraph.GetChildren() + } + if n.Text != nil { + return n.Text.GetChildren() + } + if n.Break != nil { + return n.Break.GetChildren() + } + if n.Strong != nil { + return n.Strong.GetChildren() + } + if n.Emphasis != nil { + return n.Emphasis.GetChildren() + } + if n.Strikethrough != nil { + return n.Strikethrough.GetChildren() + } + return nil +} + +func (n *QuestionChild) AppendChild(_ Node) error { return ErrCannotHaveChildren } + +func (n *QuestionChild) UnmarshalJSON(data []byte) error { + var tn typedNode + if err := json.Unmarshal(data, &tn); err != nil { + return err + } + switch tn.Type { + case ParagraphType: + var v Paragraph + if err := json.Unmarshal(data, &v); err != nil { + return err + } + n.Paragraph = &v + case TextType: + var v Text + if err := json.Unmarshal(data, &v); err != nil { + return err + } + n.Text = &v + case BreakType: + var v Break + if err := json.Unmarshal(data, &v); err != nil { + return err + } + n.Break = &v + case StrongType: + var v Strong + if err := json.Unmarshal(data, &v); err != nil { + return err + } + n.Strong = &v + case EmphasisType: + var v Emphasis + if err := json.Unmarshal(data, &v); err != nil { + return err + } + n.Emphasis = &v + case StrikethroughType: + var v Strikethrough + if err := json.Unmarshal(data, &v); err != nil { + return err + } + n.Strikethrough = &v + default: + return fmt.Errorf("failed to unmarshal QuestionChild from %s: %w", data, ErrUnmarshalInvalidNode) + } + return nil +} + +func (n *QuestionChild) MarshalJSON() ([]byte, error) { + switch { + case n.Paragraph != nil: + return json.Marshal(n.Paragraph) + case n.Text != nil: + return json.Marshal(n.Text) + case n.Break != nil: + return json.Marshal(n.Break) + case n.Strong != nil: + return json.Marshal(n.Strong) + case n.Emphasis != nil: + return json.Marshal(n.Emphasis) + case n.Strikethrough != nil: + return json.Marshal(n.Strikethrough) + default: + return []byte(`{}`), nil + } +} + +func makeQuestionChild(n Node) (*QuestionChild, error) { + switch n.GetType() { + case ParagraphType: + return &QuestionChild{Paragraph: n.(*Paragraph)}, nil + case TextType: + return &QuestionChild{Text: n.(*Text)}, nil + case BreakType: + return &QuestionChild{Break: n.(*Break)}, nil + case StrongType: + return &QuestionChild{Strong: n.(*Strong)}, nil + case EmphasisType: + return &QuestionChild{Emphasis: n.(*Emphasis)}, nil + case StrikethroughType: + return &QuestionChild{Strikethrough: n.(*Strikethrough)}, nil + default: + return nil, ErrInvalidChildType + } +} diff --git a/schemas/body-tree.schema.json b/schemas/body-tree.schema.json index 41d0904..bcb4d2c 100644 --- a/schemas/body-tree.schema.json +++ b/schemas/body-tree.schema.json @@ -2,6 +2,56 @@ "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "definitions": { + "ContentTree.transit.Answer": { + "additionalProperties": false, + "properties": { + "byline": { + "$ref": "#/definitions/ContentTree.transit.QuestionAndAnswerByline" + }, + "children": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/ContentTree.transit.Paragraph" + }, + { + "$ref": "#/definitions/ContentTree.transit.Text" + }, + { + "$ref": "#/definitions/ContentTree.transit.Break" + }, + { + "$ref": "#/definitions/ContentTree.transit.Strong" + }, + { + "$ref": "#/definitions/ContentTree.transit.Emphasis" + }, + { + "$ref": "#/definitions/ContentTree.transit.Strikethrough" + }, + { + "$ref": "#/definitions/ContentTree.transit.Link" + }, + { + "$ref": "#/definitions/ContentTree.transit.FindOutMoreLink" + } + ] + }, + "type": "array" + }, + "data": {}, + "type": { + "const": "answer", + "type": "string" + } + }, + "required": [ + "byline", + "children", + "type" + ], + "type": "object" + }, "ContentTree.transit.BigNumber": { "additionalProperties": false, "properties": { @@ -149,6 +199,15 @@ }, { "$ref": "#/definitions/ContentTree.transit.InfoPair" + }, + { + "$ref": "#/definitions/ContentTree.transit.QuestionAndAnswer" + }, + { + "$ref": "#/definitions/ContentTree.transit.Question" + }, + { + "$ref": "#/definitions/ContentTree.transit.Answer" } ] }, @@ -907,6 +966,100 @@ ], "type": "object" }, + "ContentTree.transit.Question": { + "additionalProperties": false, + "properties": { + "children": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/ContentTree.transit.Paragraph" + }, + { + "$ref": "#/definitions/ContentTree.transit.Text" + }, + { + "$ref": "#/definitions/ContentTree.transit.Break" + }, + { + "$ref": "#/definitions/ContentTree.transit.Strong" + }, + { + "$ref": "#/definitions/ContentTree.transit.Emphasis" + }, + { + "$ref": "#/definitions/ContentTree.transit.Strikethrough" + } + ] + }, + "type": "array" + }, + "data": {}, + "displayName": { + "type": "string" + }, + "type": { + "const": "question", + "type": "string" + } + }, + "required": [ + "children", + "type" + ], + "type": "object" + }, + "ContentTree.transit.QuestionAndAnswer": { + "additionalProperties": false, + "properties": { + "children": { + "additionalItems": { + "$ref": "#/definitions/ContentTree.transit.Answer" + }, + "items": [ + { + "$ref": "#/definitions/ContentTree.transit.Question" + }, + { + "$ref": "#/definitions/ContentTree.transit.Answer" + } + ], + "minItems": 2, + "type": "array" + }, + "data": {}, + "type": { + "const": "question-and-answer", + "type": "string" + } + }, + "required": [ + "children", + "type" + ], + "type": "object" + }, + "ContentTree.transit.QuestionAndAnswerByline": { + "additionalProperties": false, + "properties": { + "conceptId": { + "type": "string" + }, + "data": {}, + "title": { + "type": "string" + }, + "type": { + "const": "question-and-answer-byline", + "type": "string" + } + }, + "required": [ + "conceptId", + "type" + ], + "type": "object" + }, "ContentTree.transit.Recommended": { "additionalProperties": false, "properties": { diff --git a/schemas/content-tree.schema.json b/schemas/content-tree.schema.json index c8a2adc..8811ef8 100644 --- a/schemas/content-tree.schema.json +++ b/schemas/content-tree.schema.json @@ -2,6 +2,56 @@ "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "definitions": { + "ContentTree.full.Answer": { + "additionalProperties": false, + "properties": { + "byline": { + "$ref": "#/definitions/ContentTree.full.QuestionAndAnswerByline" + }, + "children": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/ContentTree.full.Paragraph" + }, + { + "$ref": "#/definitions/ContentTree.full.Text" + }, + { + "$ref": "#/definitions/ContentTree.full.Break" + }, + { + "$ref": "#/definitions/ContentTree.full.Strong" + }, + { + "$ref": "#/definitions/ContentTree.full.Emphasis" + }, + { + "$ref": "#/definitions/ContentTree.full.Strikethrough" + }, + { + "$ref": "#/definitions/ContentTree.full.Link" + }, + { + "$ref": "#/definitions/ContentTree.full.FindOutMoreLink" + } + ] + }, + "type": "array" + }, + "data": {}, + "type": { + "const": "answer", + "type": "string" + } + }, + "required": [ + "byline", + "children", + "type" + ], + "type": "object" + }, "ContentTree.full.AssetFormat": { "enum": [ "desktop", @@ -186,6 +236,15 @@ }, { "$ref": "#/definitions/ContentTree.full.InfoPair" + }, + { + "$ref": "#/definitions/ContentTree.full.QuestionAndAnswer" + }, + { + "$ref": "#/definitions/ContentTree.full.Question" + }, + { + "$ref": "#/definitions/ContentTree.full.Answer" } ] }, @@ -1391,6 +1450,108 @@ ], "type": "object" }, + "ContentTree.full.Question": { + "additionalProperties": false, + "properties": { + "children": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/ContentTree.full.Paragraph" + }, + { + "$ref": "#/definitions/ContentTree.full.Text" + }, + { + "$ref": "#/definitions/ContentTree.full.Break" + }, + { + "$ref": "#/definitions/ContentTree.full.Strong" + }, + { + "$ref": "#/definitions/ContentTree.full.Emphasis" + }, + { + "$ref": "#/definitions/ContentTree.full.Strikethrough" + } + ] + }, + "type": "array" + }, + "data": {}, + "displayName": { + "type": "string" + }, + "type": { + "const": "question", + "type": "string" + } + }, + "required": [ + "children", + "type" + ], + "type": "object" + }, + "ContentTree.full.QuestionAndAnswer": { + "additionalProperties": false, + "properties": { + "children": { + "additionalItems": { + "$ref": "#/definitions/ContentTree.full.Answer" + }, + "items": [ + { + "$ref": "#/definitions/ContentTree.full.Question" + }, + { + "$ref": "#/definitions/ContentTree.full.Answer" + } + ], + "minItems": 2, + "type": "array" + }, + "data": {}, + "type": { + "const": "question-and-answer", + "type": "string" + } + }, + "required": [ + "children", + "type" + ], + "type": "object" + }, + "ContentTree.full.QuestionAndAnswerByline": { + "additionalProperties": false, + "properties": { + "conceptId": { + "type": "string" + }, + "data": {}, + "displayName": { + "type": "string" + }, + "headshotUrl": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "const": "question-and-answer-byline", + "type": "string" + } + }, + "required": [ + "conceptId", + "displayName", + "headshotUrl", + "type" + ], + "type": "object" + }, "ContentTree.full.Recommended": { "additionalProperties": false, "properties": { diff --git a/schemas/transit-tree.schema.json b/schemas/transit-tree.schema.json index 52035ec..9d24f63 100644 --- a/schemas/transit-tree.schema.json +++ b/schemas/transit-tree.schema.json @@ -2,6 +2,56 @@ "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "definitions": { + "ContentTree.transit.Answer": { + "additionalProperties": false, + "properties": { + "byline": { + "$ref": "#/definitions/ContentTree.transit.QuestionAndAnswerByline" + }, + "children": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/ContentTree.transit.Paragraph" + }, + { + "$ref": "#/definitions/ContentTree.transit.Text" + }, + { + "$ref": "#/definitions/ContentTree.transit.Break" + }, + { + "$ref": "#/definitions/ContentTree.transit.Strong" + }, + { + "$ref": "#/definitions/ContentTree.transit.Emphasis" + }, + { + "$ref": "#/definitions/ContentTree.transit.Strikethrough" + }, + { + "$ref": "#/definitions/ContentTree.transit.Link" + }, + { + "$ref": "#/definitions/ContentTree.transit.FindOutMoreLink" + } + ] + }, + "type": "array" + }, + "data": {}, + "type": { + "const": "answer", + "type": "string" + } + }, + "required": [ + "byline", + "children", + "type" + ], + "type": "object" + }, "ContentTree.transit.BigNumber": { "additionalProperties": false, "properties": { @@ -174,6 +224,15 @@ }, { "$ref": "#/definitions/ContentTree.transit.InfoPair" + }, + { + "$ref": "#/definitions/ContentTree.transit.QuestionAndAnswer" + }, + { + "$ref": "#/definitions/ContentTree.transit.Question" + }, + { + "$ref": "#/definitions/ContentTree.transit.Answer" } ] }, @@ -932,6 +991,100 @@ ], "type": "object" }, + "ContentTree.transit.Question": { + "additionalProperties": false, + "properties": { + "children": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/ContentTree.transit.Paragraph" + }, + { + "$ref": "#/definitions/ContentTree.transit.Text" + }, + { + "$ref": "#/definitions/ContentTree.transit.Break" + }, + { + "$ref": "#/definitions/ContentTree.transit.Strong" + }, + { + "$ref": "#/definitions/ContentTree.transit.Emphasis" + }, + { + "$ref": "#/definitions/ContentTree.transit.Strikethrough" + } + ] + }, + "type": "array" + }, + "data": {}, + "displayName": { + "type": "string" + }, + "type": { + "const": "question", + "type": "string" + } + }, + "required": [ + "children", + "type" + ], + "type": "object" + }, + "ContentTree.transit.QuestionAndAnswer": { + "additionalProperties": false, + "properties": { + "children": { + "additionalItems": { + "$ref": "#/definitions/ContentTree.transit.Answer" + }, + "items": [ + { + "$ref": "#/definitions/ContentTree.transit.Question" + }, + { + "$ref": "#/definitions/ContentTree.transit.Answer" + } + ], + "minItems": 2, + "type": "array" + }, + "data": {}, + "type": { + "const": "question-and-answer", + "type": "string" + } + }, + "required": [ + "children", + "type" + ], + "type": "object" + }, + "ContentTree.transit.QuestionAndAnswerByline": { + "additionalProperties": false, + "properties": { + "conceptId": { + "type": "string" + }, + "data": {}, + "title": { + "type": "string" + }, + "type": { + "const": "question-and-answer-byline", + "type": "string" + } + }, + "required": [ + "conceptId", + "type" + ], + "type": "object" + }, "ContentTree.transit.Recommended": { "additionalProperties": false, "properties": {