diff --git a/.github/workflows/add-label-run-extended-tests-approve.yml b/.github/workflows/add-label-run-extended-tests-approve.yml deleted file mode 100644 index c571ced211e..00000000000 --- a/.github/workflows/add-label-run-extended-tests-approve.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Add Label to run extended tests on approve - -on: - pull_request_review: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - label_pull_requests: - if: github.event.review.state == 'approved' && !contains( github.event.pull_request.labels.*.name, 'extended-tests') - runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - - run: gh pr edit "$NUMBER" --add-label "extended-tests" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_REPO: ${{ github.repository }} - NUMBER: ${{ github.event.pull_request.number }} - e2e-tests: - needs: label_pull_requests - uses: ./.github/workflows/call-e2e-all-tests.yml diff --git a/.github/workflows/after-approval.yml b/.github/workflows/after-approval.yml new file mode 100644 index 00000000000..75f494a6388 --- /dev/null +++ b/.github/workflows/after-approval.yml @@ -0,0 +1,32 @@ +name: After Approval + +on: + pull_request_review: + types: [submitted] + pull_request_target: + types: [review_requested, opened, reopened, synchronize] + branches: + - 'main' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + label-pr: + if: github.event.review.state == 'approved' && !contains(github.event.pull_request.labels.*.name, 'extended-tests') + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Add label for extended tests + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.pull_request.number }} + run: | + echo "Adding label 'extended-tests' to PR $NUMBER" + gh pr edit "$NUMBER" --add-label "extended-tests" || (echo "Failed to add label" && exit 1) + e2e-tests: + needs: label-pr + uses: ./.github/workflows/call-e2e-all-tests.yml diff --git a/.github/workflows/call-e2e-all-tests.yml b/.github/workflows/call-e2e-all-tests.yml index 224fec288fc..8c91b5e5230 100644 --- a/.github/workflows/call-e2e-all-tests.yml +++ b/.github/workflows/call-e2e-all-tests.yml @@ -10,7 +10,7 @@ jobs: node-version: [18.18.0] browser: ['chromium', 'firefox', 'webkit'] editor-mode: ['rich-text', 'plain-text'] - events-mode: ['legacy-events', 'modern-events'] + events-mode: ['modern-events'] uses: ./.github/workflows/call-e2e-test.yml with: os: 'macos-latest' @@ -25,7 +25,7 @@ jobs: node-version: [18.18.0] browser: ['chromium', 'firefox'] editor-mode: ['rich-text', 'plain-text'] - events-mode: ['legacy-events', 'modern-events'] + events-mode: ['modern-events'] uses: ./.github/workflows/call-e2e-test.yml with: os: 'ubuntu-latest' @@ -41,6 +41,9 @@ jobs: browser: ['chromium', 'firefox'] editor-mode: ['rich-text', 'plain-text'] events-mode: ['legacy-events', 'modern-events'] + exclude: + - events-mode: 'legacy-events' + browser: 'firefox' uses: ./.github/workflows/call-e2e-test.yml with: os: 'windows-latest' diff --git a/.github/workflows/size-limit.yml b/.github/workflows/size-limit.yml index a2d05005781..9b5250a43a3 100644 --- a/.github/workflows/size-limit.yml +++ b/.github/workflows/size-limit.yml @@ -1,6 +1,7 @@ name: 'Bundles' on: pull_request: + pull_request_target: branches: - main jobs: diff --git a/packages/lexical-list/src/LexicalListItemNode.ts b/packages/lexical-list/src/LexicalListItemNode.ts index c63f24653ea..f593a5cf321 100644 --- a/packages/lexical-list/src/LexicalListItemNode.ts +++ b/packages/lexical-list/src/LexicalListItemNode.ts @@ -369,10 +369,12 @@ export class ListItemNode extends ElementNode { return this; } + /** @deprecated @internal */ canInsertAfter(node: LexicalNode): boolean { return $isListItemNode(node); } + /** @deprecated @internal */ canReplaceWith(replacement: LexicalNode): boolean { return $isListItemNode(replacement); } diff --git a/packages/lexical-playground/src/images/image-broken.svg b/packages/lexical-playground/src/images/image-broken.svg new file mode 100644 index 00000000000..58e4aa9a859 --- /dev/null +++ b/packages/lexical-playground/src/images/image-broken.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/lexical-playground/src/nodes/ImageComponent.tsx b/packages/lexical-playground/src/nodes/ImageComponent.tsx index 42e8249ec66..df684aed286 100644 --- a/packages/lexical-playground/src/nodes/ImageComponent.tsx +++ b/packages/lexical-playground/src/nodes/ImageComponent.tsx @@ -48,6 +48,7 @@ import {Suspense, useCallback, useEffect, useRef, useState} from 'react'; import {createWebsocketProvider} from '../collaboration'; import {useSettings} from '../context/SettingsContext'; import {useSharedHistoryContext} from '../context/SharedHistoryContext'; +import brokenImage from '../images/image-broken.svg'; import EmojisPlugin from '../plugins/EmojisPlugin'; import KeywordsPlugin from '../plugins/KeywordsPlugin'; import LinkPlugin from '../plugins/LinkPlugin'; @@ -72,6 +73,9 @@ function useSuspenseImage(src: string) { imageCache.add(src); resolve(null); }; + img.onerror = () => { + imageCache.add(src); + }; }); } } @@ -84,6 +88,7 @@ function LazyImage({ width, height, maxWidth, + onError, }: { altText: string; className: string | null; @@ -92,6 +97,7 @@ function LazyImage({ maxWidth: number; src: string; width: 'inherit' | number; + onError: () => void; }): JSX.Element { useSuspenseImage(src); return ( @@ -105,6 +111,21 @@ function LazyImage({ maxWidth, width, }} + onError={onError} + draggable="false" + /> + ); +} + +function BrokenImage(): JSX.Element { + return ( + ); @@ -142,6 +163,7 @@ export default function ImageComponent({ const [editor] = useLexicalComposerContext(); const [selection, setSelection] = useState(null); const activeEditorRef = useRef(null); + const [isLoadError, setIsLoadError] = useState(false); const $onDelete = useCallback( (payload: KeyboardEvent) => { @@ -371,20 +393,26 @@ export default function ImageComponent({ <>
- + {isLoadError ? ( + + ) : ( + setIsLoadError(true)} + /> + )}
+ {showCaption && (
@@ -428,7 +456,7 @@ export default function ImageComponent({ maxWidth={maxWidth} onResizeStart={onResizeStart} onResizeEnd={onResizeEnd} - captionsEnabled={captionsEnabled} + captionsEnabled={!isLoadError && captionsEnabled} /> )} diff --git a/packages/lexical-react/src/LexicalErrorBoundary.tsx b/packages/lexical-react/src/LexicalErrorBoundary.tsx index 8c3fab9b6a5..5eb59aa741a 100644 --- a/packages/lexical-react/src/LexicalErrorBoundary.tsx +++ b/packages/lexical-react/src/LexicalErrorBoundary.tsx @@ -6,6 +6,7 @@ * */ +import * as React from 'react'; import {ErrorBoundary as ReactErrorBoundary} from 'react-error-boundary'; export type LexicalErrorBoundaryProps = { diff --git a/packages/lexical-table/src/LexicalTableSelectionHelpers.ts b/packages/lexical-table/src/LexicalTableSelectionHelpers.ts index 400d88fdde6..7d89e3fb961 100644 --- a/packages/lexical-table/src/LexicalTableSelectionHelpers.ts +++ b/packages/lexical-table/src/LexicalTableSelectionHelpers.ts @@ -32,6 +32,7 @@ import { $getNearestNodeFromDOMNode, $getPreviousSelection, $getSelection, + $isDecoratorNode, $isElementNode, $isRangeSelection, $isTextNode, @@ -1359,6 +1360,11 @@ function $handleArrowKey( return false; } + const selectedNodes = selection.getNodes(); + if (selectedNodes.length === 1 && $isDecoratorNode(selectedNodes[0])) { + return false; + } + if ( isExitingTableAnchor(anchorType, anchorOffset, anchorNode, direction) ) { diff --git a/packages/lexical/src/LexicalEvents.ts b/packages/lexical/src/LexicalEvents.ts index e81ce0a5124..c56806c596e 100644 --- a/packages/lexical/src/LexicalEvents.ts +++ b/packages/lexical/src/LexicalEvents.ts @@ -1005,6 +1005,10 @@ function onKeyDown(event: KeyboardEvent, editor: LexicalEditor): void { return; } + if (key == null) { + return; + } + if (isMoveForward(key, ctrlKey, altKey, metaKey)) { dispatchCommand(editor, KEY_ARROW_RIGHT_COMMAND, event); } else if (isMoveToEnd(key, ctrlKey, shiftKey, altKey, metaKey)) { diff --git a/packages/lexical/src/nodes/LexicalElementNode.ts b/packages/lexical/src/nodes/LexicalElementNode.ts index 2a521ab5f4e..2707fb318de 100644 --- a/packages/lexical/src/nodes/LexicalElementNode.ts +++ b/packages/lexical/src/nodes/LexicalElementNode.ts @@ -525,9 +525,11 @@ export class ElementNode extends LexicalNode { excludeFromCopy(destination?: 'clone' | 'html'): boolean { return false; } + /** @deprecated @internal */ canReplaceWith(replacement: LexicalNode): boolean { return true; } + /** @deprecated @internal */ canInsertAfter(node: LexicalNode): boolean { return true; } @@ -550,6 +552,7 @@ export class ElementNode extends LexicalNode { isShadowRoot(): boolean { return false; } + /** @deprecated @internal */ canMergeWith(node: ElementNode): boolean { return false; } diff --git a/scripts/build.js b/scripts/build.js index 41b8d0ffed6..d80d5a6d1bf 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -192,7 +192,7 @@ async function build(name, inputFile, outputPath, outputFile, isProd, format) { tsconfig: path.resolve('./tsconfig.build.json'), }, ], - ['@babel/preset-react', {runtime: 'automatic'}], + ['@babel/preset-react', {runtime: isWWW ? 'classic' : 'automatic'}], ], }), {