Skip to content

Commit e78123a

Browse files
authored
Merge pull request #2269 from pie-framework/develop
fix: PIE-674, PIE-662, PIE-705, PIE-674, PIE-963
2 parents a17395e + 0f9838f commit e78123a

7 files changed

Lines changed: 152 additions & 6 deletions

File tree

.github/workflows/ci.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ on:
55
branches: [develop, master]
66
pull_request:
77
branches: [develop, master]
8+
workflow_dispatch:
9+
inputs:
10+
ref:
11+
description: Branch or commit to publish missing packages from
12+
required: false
13+
default: master
814

915
jobs:
1016
test:
@@ -76,3 +82,37 @@ jobs:
7682
- run: yarn install
7783
- run: scripts/release --build
7884
- run: scripts/release --release --loglevel verbose
85+
86+
# Manually-triggered retry: publishes only package versions that are missing
87+
# from the registry (lerna publish from-package), skipping already-published
88+
# ones. Use this to finish a release where some packages failed to publish
89+
# (e.g. a transient npm provenance/transparency-log error) without re-running
90+
# `lerna version`.
91+
publish-missing:
92+
needs: test
93+
if: github.event_name == 'workflow_dispatch'
94+
runs-on: ubuntu-latest
95+
permissions:
96+
contents: read
97+
id-token: write # REQUIRED for npm trusted publishing
98+
steps:
99+
- uses: actions/checkout@v5
100+
with:
101+
ref: ${{ github.event.inputs.ref }}
102+
fetch-depth: 0
103+
- name: Configure Git
104+
run: |
105+
git config user.name "github-actions[bot]"
106+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
107+
- uses: actions/setup-node@v5
108+
with:
109+
node-version: 24
110+
registry-url: https://registry.npmjs.org/
111+
cache: yarn
112+
- run: yarn install
113+
- run: scripts/release --build
114+
# Invoke lerna directly (not via scripts/release) so this step does not
115+
# depend on the scripts/release version present on the checked-out ref.
116+
# `from-package` publishes only versions missing from the registry and
117+
# never versions or pushes tags, so it needs no write access to the repo.
118+
- run: npx lerna publish from-package --yes --loglevel verbose

packages/editable-html-tip-tap/src/components/MenuBar.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,11 @@ const StyledMenuBarRoot = styled('div')(({ theme }) => ({
480480
background: 'none',
481481
border: 'none',
482482
cursor: 'pointer',
483+
// previously we had implicit 24×24 icon rendering for mui svg icons, but now we need to explicitly set the size to 24×24 to match the previous behavior
484+
'& svg': {
485+
width: '24px',
486+
height: '24px',
487+
},
483488
'&:hover': {
484489
color: 'black',
485490
},

packages/editable-html-tip-tap/src/components/TiptapContainer.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ function TiptapContainer(props) {
168168

169169
el.style.visibility = 'hidden';
170170
el.style.position = 'absolute';
171+
el.style.whiteSpace = 'nowrap';
171172
el.textContent = 'W'.repeat(props.charactersLimit);
172173

173174
rootRef.current.appendChild(el);

packages/editable-html-tip-tap/src/components/__tests__/InlineDropdown.test.jsx

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,85 @@ describe('InlineDropdown', () => {
185185
});
186186
});
187187

188+
it('uses the current node when closing on outside click after the node prop changes', async () => {
189+
const onToolbarCloseRequest = jest.fn((_tuple, _editor, onConfirm) => onConfirm());
190+
const options = {
191+
...mockOptions,
192+
onToolbarCloseRequest,
193+
};
194+
const updatedNode = {
195+
attrs: { index: '1', value: 'Updated Option', error: false },
196+
nodeSize: 1,
197+
};
198+
199+
const { queryByTestId, rerender } = render(<InlineDropdown {...defaultProps} options={options} selected={true} />);
200+
201+
await waitFor(() => {
202+
expect(queryByTestId('inline-dropdown-toolbar')).toBeInTheDocument();
203+
});
204+
205+
rerender(<InlineDropdown {...defaultProps} options={options} node={updatedNode} selected={true} />);
206+
207+
fireEvent.mouseDown(document.body);
208+
209+
await waitFor(() => {
210+
expect(onToolbarCloseRequest).toHaveBeenCalledWith(
211+
[updatedNode, 5],
212+
expect.anything(),
213+
expect.any(Function),
214+
expect.any(Function),
215+
);
216+
});
217+
});
218+
219+
it('respects hold state for the current node after the node prop changes', async () => {
220+
const updatedNode = {
221+
attrs: { index: '1', value: 'Updated Option', error: false },
222+
nodeSize: 1,
223+
};
224+
225+
mockEditor._holdInlineDropdownToolbarIndex = '1';
226+
227+
const { queryByTestId, rerender } = render(<InlineDropdown {...defaultProps} selected={true} />);
228+
229+
await waitFor(() => {
230+
expect(queryByTestId('inline-dropdown-toolbar')).toBeInTheDocument();
231+
});
232+
233+
rerender(<InlineDropdown {...defaultProps} node={updatedNode} selected={true} />);
234+
235+
fireEvent.mouseDown(document.body);
236+
237+
await waitFor(() => {
238+
expect(queryByTestId('inline-dropdown-toolbar')).toBeInTheDocument();
239+
});
240+
});
241+
242+
it('rebinds the outside-click listener when the node prop changes while the toolbar is open', async () => {
243+
const addSpy = jest.spyOn(document, 'addEventListener');
244+
const removeSpy = jest.spyOn(document, 'removeEventListener');
245+
const updatedNode = { ...mockNode, attrs: { ...mockNode.attrs, value: 'Updated Option' } };
246+
247+
const { rerender } = render(<InlineDropdown {...defaultProps} selected={true} />);
248+
249+
await waitFor(() => {
250+
expect(addSpy).toHaveBeenCalledWith('mousedown', expect.any(Function));
251+
});
252+
253+
const removeCallsBeforeRerender = removeSpy.mock.calls.filter(([type]) => type === 'mousedown').length;
254+
255+
rerender(<InlineDropdown {...defaultProps} node={updatedNode} selected={true} />);
256+
257+
await waitFor(() => {
258+
const removeCallsAfterRerender = removeSpy.mock.calls.filter(([type]) => type === 'mousedown').length;
259+
expect(removeCallsAfterRerender).toBeGreaterThan(removeCallsBeforeRerender);
260+
expect(addSpy).toHaveBeenCalledWith('mousedown', expect.any(Function));
261+
});
262+
263+
addSpy.mockRestore();
264+
removeSpy.mockRestore();
265+
});
266+
188267
it('has correct border styling', () => {
189268
const { container } = render(<InlineDropdown {...defaultProps} />);
190269
const dropdownDiv = container.querySelector('div[style*="border"]');

packages/editable-html-tip-tap/src/components/respArea/InlineDropdown.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ const InlineDropdown = (props) => {
126126
}
127127

128128
return () => document.removeEventListener('mousedown', handleClickOutside);
129-
}, [showToolbar]);
129+
}, [showToolbar, node]);
130130

131131
return (
132132
<NodeViewWrapper

packages/mask-markup/src/choices/choice.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ export default function Choice({ choice, disabled, instanceId }) {
7171
style={
7272
isDragging
7373
? {
74-
width: rootRef.current?.offsetWidth,
75-
height: rootRef.current?.offsetHeight,
74+
width: rootRef.current?.offsetWidth || 90, // min-width of chip is 90px, so if we don't have the width, we can use 90px as a fallback
75+
height: rootRef.current?.offsetHeight || 32, // min-height of chip is 32px, so if we don't have the height, we can use 32px as a fallback
7676
}
7777
: {}
7878
}

scripts/release

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ const CWD = process.cwd();
2626
* - `next`: Defaults to `false`.
2727
*/
2828
const argv = minimist(process.argv.slice(2), {
29-
boolean: ['dry-run', 'next', 'build', 'test', 'release'],
29+
boolean: ['dry-run', 'next', 'build', 'test', 'release', 'publish'],
3030
string: ['loglevel'],
31-
default: { 'dry-run': false, next: false, build: false, test: false, release: false },
31+
default: { 'dry-run': false, next: false, build: false, test: false, release: false, publish: false },
3232
});
3333

3434
/**
@@ -179,6 +179,26 @@ const release = () => {
179179
runCmd(`git push origin ${getCurrentBranch()} --follow-tags`);
180180
};
181181

182+
/**
183+
* Publishes any package versions that are missing from the registry, without
184+
* versioning or creating tags.
185+
*
186+
* Uses `lerna publish from-package`, which inspects each package's current
187+
* version and publishes only those not already present on npm. This makes it
188+
* a safe, idempotent retry for a release where some packages published and
189+
* others failed (e.g. a transient provenance/transparency-log error): already
190+
* published packages are skipped and only the missing ones are published.
191+
*
192+
* Intended to be run from a branch/commit that already carries the intended
193+
* versions (typically `master` after a `lerna version` has run).
194+
*/
195+
const publishMissing = () => {
196+
log(chalk.magenta('--- PUBLISH MISSING PACKAGES (from-package) ---'));
197+
let releaseCmd = 'npx lerna publish from-package --yes';
198+
if (argv.loglevel) releaseCmd += ` --loglevel ${argv.loglevel}`;
199+
runCmd(releaseCmd);
200+
};
201+
182202
/**
183203
* Executes the build and release process.
184204
*
@@ -193,7 +213,7 @@ const release = () => {
193213
const run = () => {
194214
try {
195215
// If no specific flags, run full workflow
196-
const anyFlag = argv.build || argv.test || argv.release;
216+
const anyFlag = argv.build || argv.test || argv.release || argv.publish;
197217
if (!anyFlag) {
198218
build();
199219
test();
@@ -202,6 +222,7 @@ const run = () => {
202222
if (argv.build) build();
203223
if (argv.test) test();
204224
if (argv.release) release();
225+
if (argv.publish) publishMissing();
205226
}
206227
log(chalk.green('✅ Tasks completed successfully.'));
207228
} catch (err) {

0 commit comments

Comments
 (0)