Skip to content

Commit

Permalink
Port AnnouncePlugin step 3: Add announce features for list and table (#…
Browse files Browse the repository at this point in the history
…2592)

* Port AnnouncePlugin step 1: refactor list number code

* Port AnnouncePlugin step 2

* Port AnnouncePlugin ste 3

* add test
  • Loading branch information
JiuqingSong authored Apr 24, 2024
1 parent 7e5f1f5 commit af63a38
Show file tree
Hide file tree
Showing 24 changed files with 944 additions and 215 deletions.
23 changes: 21 additions & 2 deletions demo/scripts/controlsV2/mainPane/MainPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import * as React from 'react';
import * as ReactDOM from 'react-dom';
import SampleEntityPlugin from '../plugins/SampleEntityPlugin';
import { ApiPlaygroundPlugin } from '../sidePane/apiPlayground/ApiPlaygroundPlugin';
import { Border, ContentModelDocument, EditorOptions } from 'roosterjs-content-model-types';
import { Colors, EditorPlugin, IEditor, Snapshots } from 'roosterjs-content-model-types';
import { ContentModelPanePlugin } from '../sidePane/contentModel/ContentModelPanePlugin';
import { createEmojiPlugin } from '../roosterjsReact/emoji';
import { createImageEditMenuProvider } from '../roosterjsReact/contextMenu/menus/createImageEditMenuProvider';
Expand Down Expand Up @@ -41,6 +39,16 @@ import { undoButton } from '../roosterjsReact/ribbon/buttons/undoButton';
import { UpdateContentPlugin } from '../plugins/UpdateContentPlugin';
import { WindowProvider } from '@fluentui/react/lib/WindowProvider';
import { zoomButton } from '../demoButtons/zoomButton';
import {
Border,
Colors,
ContentModelDocument,
EditorOptions,
EditorPlugin,
IEditor,
KnownAnnounceStrings,
Snapshots,
} from 'roosterjs-content-model-types';
import {
AutoFormatPlugin,
CustomReplacePlugin,
Expand Down Expand Up @@ -361,6 +369,7 @@ export class MainPane extends React.Component<{}, MainPaneState> {
dir={this.state.isRtl ? 'rtl' : 'ltr'}
knownColors={this.knownColors}
disableCache={this.state.initState.disableCache}
announcerStringGetter={getAnnouncingString}
/>
)}
</div>
Expand Down Expand Up @@ -511,6 +520,16 @@ export class MainPane extends React.Component<{}, MainPaneState> {
}
}

const AnnounceStringMap: Record<KnownAnnounceStrings, string> = {
announceListItemBullet: 'Auto corrected Bullet',
announceListItemNumbering: 'Auto corrected {0}',
announceOnFocusLastCell: 'Warning, pressing tab here adds an extra row.',
};

function getAnnouncingString(key: KnownAnnounceStrings) {
return AnnounceStringMap[key];
}

export function mount(parent: HTMLElement) {
ReactDOM.render(<MainPane />, parent);
}
16 changes: 2 additions & 14 deletions demo/scripts/controlsV2/plugins/createLegacyPlugins.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Announce, ImageEdit } from 'roosterjs-editor-plugins';
import { EditorPlugin as LegacyEditorPlugin, KnownAnnounceStrings } from 'roosterjs-editor-types';
import { EditorPlugin as LegacyEditorPlugin } from 'roosterjs-editor-types';
import { ImageEdit } from 'roosterjs-editor-plugins';
import { LegacyPluginList, OptionState } from '../sidePane/editorOptions/OptionState';

export function createLegacyPlugins(initState: OptionState): LegacyEditorPlugin[] {
Expand All @@ -12,19 +12,7 @@ export function createLegacyPlugins(initState: OptionState): LegacyEditorPlugin[
applyChangesOnMouseUp: initState.applyChangesOnMouseUp,
})
: null,
announce: pluginList.announce ? new Announce(getDefaultStringsMap()) : null,
};

return Object.values(plugins).filter(x => !!x);
}

function getDefaultStringsMap(): Map<KnownAnnounceStrings, string> {
return new Map<KnownAnnounceStrings, string>([
[KnownAnnounceStrings.AnnounceListItemBullet, 'Autocorrected Bullet'],
[KnownAnnounceStrings.AnnounceListItemNumbering, 'Autocorrected {0}'],
[
KnownAnnounceStrings.AnnounceOnFocusLastCell,
'Warning, pressing tab here adds an extra row.',
],
]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,14 @@ export function Rooster(props: RoosterProps) {
}, [inDarkMode]);

const divProps = getNativeProps<React.HTMLAttributes<HTMLDivElement>>(props, divProperties);
return <div ref={editorDiv} tabIndex={0} {...(divProps || {})}></div>;
return (
<div
ref={editorDiv}
tabIndex={0}
role="textbox"
aria-multiline="true"
{...(divProps || {})}></div>
);
}

function defaultEditorCreator(div: HTMLDivElement, options: EditorOptions) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const initialState: OptionState = {

// Legacy plugins
imageEdit: false,
announce: false,
},
defaultFormat: {
fontFamily: 'Calibri',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { ContentModelSegmentFormat } from 'roosterjs-content-model-types';

export interface LegacyPluginList {
imageEdit: boolean;
announce: boolean;
}

export interface NewPluginList {
Expand Down
1 change: 0 additions & 1 deletion demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ export class LegacyPlugins extends PluginsBase<keyof LegacyPluginList> {
(state, value) => (state.forcePreserveRatio = value)
)
)}
{this.renderPluginItem('announce', 'Announce')}
</tbody>
</table>
);
Expand Down
1 change: 1 addition & 0 deletions packages/roosterjs-content-model-api/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,4 @@ export { setModelListStartNumber } from './modelApi/list/setModelListStartNumber
export { findListItemsInSameThread } from './modelApi/list/findListItemsInSameThread';
export { setModelIndentation } from './modelApi/block/setModelIndentation';
export { matchLink } from './modelApi/link/matchLink';
export { getListAnnounceData } from './modelApi/list/getListAnnounceData';
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { findListItemsInSameThread } from '../list/findListItemsInSameThread';
import { getListAnnounceData } from '../list/getListAnnounceData';
import {
createListLevel,
getOperationalBlocks,
Expand All @@ -13,6 +14,7 @@ import type {
ContentModelDocument,
ContentModelListItem,
ContentModelListLevel,
FormatContentModelContext,
} from 'roosterjs-content-model-types';

const IndentStepInPixel = 40;
Expand All @@ -26,7 +28,8 @@ const IndentStepInPixel = 40;
export function setModelIndentation(
model: ContentModelDocument,
indentation: 'indent' | 'outdent',
length: number = IndentStepInPixel
length: number = IndentStepInPixel,
context?: FormatContentModelContext
) {
const paragraphOrListItem = getOperationalBlocks<ContentModelListItem>(
model,
Expand Down Expand Up @@ -80,6 +83,10 @@ export function setModelIndentation(
} else {
block.levels.pop();
}

if (block.levels.length > 0 && context) {
context.announceData = getListAnnounceData([block, ...path]);
}
}
} else if (block) {
let currentBlock: ContentModelBlock = block;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import type {
ContentModelBlockGroup,
ContentModelDocument,
ContentModelListItem,
} from 'roosterjs-content-model-types';
import type { ContentModelBlockGroup, ContentModelListItem } from 'roosterjs-content-model-types';

/**
* @param model The content model
* @param currentItem The current list item
* Search for all list items in the same thread as the current list item
*/
export function findListItemsInSameThread(
model: ContentModelDocument,
group: ContentModelBlockGroup,
currentItem: ContentModelListItem
): ContentModelListItem[] {
const items: (ContentModelListItem | null)[] = [];

findListItems(model, items);
findListItems(group, items);

return filterListItems(items, currentItem);
}
Expand Down Expand Up @@ -97,7 +93,11 @@ function filterListItems(
if (isOrderedList && startNumberOverride) {
break;
}
} else if (!isOrderedList || startNumberOverride) {
} else if (
!isOrderedList ||
startNumberOverride ||
item.levels.length < currentItem.levels.length
) {
break;
}
}
Expand All @@ -117,7 +117,11 @@ function filterListItems(

if (areListTypesCompatible(items, currentIndex, i) && !startNumberOverride) {
result.push(item);
} else if (!isOrderedList || startNumberOverride) {
} else if (
!isOrderedList ||
startNumberOverride ||
item.levels.length < currentItem.levels.length
) {
break;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { findListItemsInSameThread } from './findListItemsInSameThread';
import {
getAutoListStyleType,
getClosestAncestorBlockGroupIndex,
getOrderedListNumberStr,
updateListMetadata,
} from 'roosterjs-content-model-dom';
import type {
AnnounceData,
ContentModelBlockGroup,
ContentModelListItem,
} from 'roosterjs-content-model-types';

/**
* Get announce data for list item
* @param path Content model path that include the list item
* @returns Announce data of current list item if any, or null
*/
export function getListAnnounceData(path: ContentModelBlockGroup[]): AnnounceData | null {
const index = getClosestAncestorBlockGroupIndex(path, ['ListItem'], ['TableCell']);

if (index >= 0) {
const listItem = path[index] as ContentModelListItem;
const level = listItem.levels[listItem.levels.length - 1];

if (level.format.displayForDummyItem) {
return null;
} else if (level.listType == 'OL') {
const listNumber = getListNumber(path, listItem);
const metadata = updateListMetadata(level);
const listStyle = getAutoListStyleType(
'OL',
metadata ?? {},
listItem.levels.length - 1,
level.format.listStyleType
);

return listStyle === undefined
? null
: {
defaultStrings: 'announceListItemNumbering',
formatStrings: [getOrderedListNumberStr(listStyle, listNumber)],
};
} else {
return {
defaultStrings: 'announceListItemBullet',
};
}
} else {
return null;
}
}

function getListNumber(path: ContentModelBlockGroup[], listItem: ContentModelListItem) {
const items = findListItemsInSameThread(path[path.length - 1], listItem);
let listNumber = 0;

for (let i = 0; i < items.length; i++) {
const item = items[i];

if (listNumber == 0 && item.levels.length == listItem.levels.length) {
listNumber = item.levels[item.levels.length - 1]?.format.startNumberOverride ?? 1;
}

if (item == listItem) {
// Found current item, so break and return
break;
} else if (item.levels.length < listItem.levels.length) {
// Found upper level item, reset list number
listNumber = 0;
} else if (item.levels.length > listItem.levels.length) {
// Found deeper level item, skip
continue;
} else if (!item.levels[item.levels.length - 1].format.displayForDummyItem) {
// Save level, and is not dummy, number plus one
listNumber++;
}
}
return listNumber;
}
Loading

0 comments on commit af63a38

Please sign in to comment.