Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query content model blocks. #2851

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

juliaroldi
Copy link
Contributor

@juliaroldi juliaroldi commented Oct 29, 2024

This changed introduced a new API, queryContentModel, to the roosterjs-content-model-api. Its role is to retrieve either blocks of a similar type, or a particular block specified by a selector. This enhancement builds upon the findEditingImage function, enabling its logic to be applied in additional features like the OWA Accessibility Checker. Moreover, it addresses the issue of how images inserted into tables from Word Online are handled, as queryContentModel also checks formatContainer blocks during image searches.

Test case:

  1. Copy a table from Word Online
  2. Paste in the editor
  3. Insert an image in a table cell
  4. Select the image and click away
  5. the image must be deselected

imageOnWordTables

Copy link
Collaborator

@JiuqingSong JiuqingSong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, it is a perfect idea to have a common query function.

Once done, this function will be used widely, so we need to be careful on how it is designed and worked. We need to make sure if it is misused, it can fail the build.

/**
* Options for queryContentModel
*/
export interface QueryContentModelOptions<T> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add type constraint here for T.

/**
* Optional selector to filter the blocks/segments
*/
selector?: (element: T) => boolean;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about name it "filter"?

elements.push(...(blockGroupsResults as T[]));
break;
case 'Table':
if (type == block.blockType && (!selector || selector(block as T))) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if T is a segment type, are we converting block to segment?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually we should make sure only the correct type of object can trigger selector()

const blocks: T[] = [];
for (const row of table.rows) {
for (const cell of row.cells) {
const items = queryContentModel<T>(cell, options);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better create a internalQueryContentModel which does not need to check value of options then let both here and queryContentModel call it

@juliaroldi juliaroldi changed the title Query content model segments or blocks. Query content model blocks. Nov 5, 2024
return elements;
}

function queryContentModelBlocksInternal<T extends ReadonlyContentModelBlock>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since you don't need the parameter options now, you can merge these two functions together, so no need to duplicate the for loop for block group

blockType?: ContentModelBlockType,
filter?: (element: T) => element is T,
findFirstOnly?: boolean
): T[] {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you also want to return the path?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, all the elements of type or filtered by the filter function

block: ReadonlyContentModelBlock,
type: string
): block is T {
return block.blockType == type;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to have this function. Just directly check blockType == type should work

elements.push(block);
}

if (block.blockType == 'BlockGroup') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use switch...case instead of multiple if

}

if (block.blockType == 'BlockGroup') {
for (const childBlock of block.blocks) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if you are querying block group? Then you should return directly here

const table = block;
for (const row of table.rows) {
for (const cell of row.cells) {
for (const cellBlock of cell.blocks) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cell is BlockGroup, so you can reuse the code for blockGroup here

*/
export function queryContentModelBlocks<T extends ReadonlyContentModelBlock>(
group: ReadonlyContentModelBlockGroup,
blockType?: ContentModelBlockType,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This blockType needs to be coupled with T. Otherwise, you can pass two different types, for example:

queryContentModelBlock<ContentModelParagraph>(group, 'Table');

In theory you should make this fail the build.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can declare like this.

export function queryContentModelBlocks<T extends ReadonlyContentModelBlock>(
  group: ReadonlyContentModelBlockGroup,
  blockType: T extends ReadonlyContentModelBlockBase<infer U> ? U : never,
 ...) {
   ...
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then blockType and T must match, otherwise build will fail.

case 'Paragraph':
case 'Divider':
case 'Entity':
if (isExpectedBlockType(block, type, filter)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JiuqingSong, I tried not verifying this in every, but not do it is causing build errors

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants