Skip to content

Commit 23bb85a

Browse files
author
Claire Peng
committed
merge with develop
2 parents a713de7 + 7392453 commit 23bb85a

30 files changed

+5903
-4415
lines changed

client/i18n.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export function languageKeyToLabel(lang) {
7070
it: 'Italiano',
7171
ja: '日本語',
7272
ko: '한국어',
73-
'pt-BR': 'Português',
73+
'pt-BR': 'Português do Brasil',
7474
sv: 'Svenska',
7575
'uk-UA': 'Українська',
7676
'zh-CN': '简体中文',

client/index.jsx

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import React, { Suspense } from 'react';
1+
import React, { Suspense, useEffect } from 'react';
22
import { render } from 'react-dom';
3-
import { Provider } from 'react-redux';
3+
import { Provider, useSelector } from 'react-redux';
44
import { Router } from 'react-router-dom';
55

66
import { useTranslation } from 'react-i18next';
@@ -21,6 +21,8 @@ const initialState = window.__INITIAL_STATE__;
2121

2222
const store = configureStore(initialState);
2323

24+
const DONATE_LOGO_IMAGE_URL = 'https://donorbox.org/images/white_logo.svg';
25+
2426
if (
2527
window.location.href.indexOf('full') === -1 &&
2628
window.location.href.indexOf('embed') === -1
@@ -56,30 +58,30 @@ if (
5658
'background: #f1678e; color: #fff; text-decoration: none; font-family: Verdana, sans-serif; display: flex; gap: 8px; width: fit-content; font-size: 16px; border-radius: 0 0 5px 5px; line-height: 24px; position: fixed; top: 50%; transform-origin: center; z-index: 9999; overflow: hidden; padding: 8px 22px 8px 18px; right: 20px; left: auto; transform: translate(50%, -50%) rotate(90deg)'
5759
);
5860
buttonScript.setAttribute('data-button-cta', 'Donate');
59-
buttonScript.setAttribute(
60-
'data-img-src',
61-
'https://donorbox.org/images/white_logo.svg'
62-
);
61+
buttonScript.setAttribute('data-img-src', DONATE_LOGO_IMAGE_URL);
6362

6463
document.body.appendChild(buttonScript);
6564
}
6665

6766
const App = () => {
6867
const { t } = useTranslation();
68+
const language = useSelector((state) => state.preferences.language);
6969

70-
setTimeout(() => {
71-
const donateButton = document.getElementsByClassName(
72-
'dbox-donation-button'
73-
)[0];
70+
useEffect(() => {
71+
setTimeout(() => {
72+
const donateButton = document.getElementsByClassName(
73+
'dbox-donation-button'
74+
)[0];
7475

75-
if (donateButton) {
76-
const donateLogoImage = document.createElement('img');
77-
donateLogoImage.src = 'https://donorbox.org/images/white_logo.svg';
76+
if (donateButton) {
77+
const donateLogoImage = document.createElement('img');
78+
donateLogoImage.src = DONATE_LOGO_IMAGE_URL;
7879

79-
donateButton.text = t('About.Donate');
80-
donateButton.prepend(donateLogoImage);
81-
}
82-
}, 0);
80+
donateButton.text = t('About.Donate');
81+
donateButton.prepend(donateLogoImage);
82+
}
83+
}, 500);
84+
}, [language]);
8385

8486
return (
8587
<div>
@@ -91,13 +93,19 @@ const App = () => {
9193
);
9294
};
9395

94-
render(
95-
<Provider store={store}>
96-
<ThemeProvider>
97-
<Suspense fallback={<Loader />}>
98-
<App />
99-
</Suspense>
100-
</ThemeProvider>
101-
</Provider>,
102-
document.getElementById('root')
103-
);
96+
// This prevents crashes in test environments (like Jest) where document.getElementById('root') may return null.
97+
const rootEl = document.getElementById('root');
98+
if (rootEl) {
99+
render(
100+
<Provider store={store}>
101+
<ThemeProvider>
102+
<Suspense fallback={<Loader />}>
103+
<App />
104+
</Suspense>
105+
</ThemeProvider>
106+
</Provider>,
107+
rootEl
108+
);
109+
}
110+
111+
export default store;

client/modules/IDE/actions/project.js

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ export function deleteProject(id) {
413413
});
414414
};
415415
}
416-
export function changeVisibility(projectId, projectName, visibility) {
416+
export function changeVisibility(projectId, projectName, visibility, t) {
417417
return (dispatch, getState) => {
418418
const state = getState();
419419

@@ -443,11 +443,25 @@ export function changeVisibility(projectId, projectName, visibility) {
443443
name: response.data.name
444444
});
445445

446-
dispatch(
447-
setToastText(
448-
`${projectName} is now ${newVisibility.toLowerCase()}`
449-
)
450-
);
446+
let visibilityLabel;
447+
448+
switch (newVisibility) {
449+
case 'Public':
450+
visibilityLabel = t('Visibility.Public.Label');
451+
break;
452+
case 'Private':
453+
visibilityLabel = t('Visibility.Private.Label');
454+
break;
455+
default:
456+
visibilityLabel = newVisibility;
457+
}
458+
459+
const visibilityToastText = t('Visibility.Changed', {
460+
projectName,
461+
newVisibility: visibilityLabel.toLowerCase()
462+
});
463+
464+
dispatch(setToastText(visibilityToastText));
451465
dispatch(showToast(2000));
452466
}
453467
}

client/modules/IDE/components/Editor/index.jsx

Lines changed: 84 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ import { EditorContainer, EditorHolder } from './MobileEditor';
7373
import { FolderIcon } from '../../../../common/icons';
7474
import { IconButton } from '../../../../common/IconButton';
7575

76+
import contextAwareHinter from '../../../../utils/contextAwareHinter';
77+
import showRenameDialog from '../../../../utils/showRenameDialog';
78+
import handleRename from '../../../../utils/rename-variable';
79+
import { jumpToDefinition } from '../../../../utils/jump-to-definition';
80+
import { ensureAriaLiveRegion } from '../../../../utils/ScreenReaderHelper';
81+
import { isMac } from '../../../../utils/device';
82+
7683
emmet(CodeMirror);
7784

7885
window.JSHINT = JSHINT;
@@ -109,6 +116,7 @@ class Editor extends React.Component {
109116

110117
componentDidMount() {
111118
this.beep = new Audio(beepUrl);
119+
ensureAriaLiveRegion();
112120
// this.widgets = [];
113121
this._cm = CodeMirror(this.codemirrorContainer, {
114122
theme: `p5-${this.props.theme}`,
@@ -154,6 +162,17 @@ class Editor extends React.Component {
154162

155163
delete this._cm.options.lint.options.errors;
156164

165+
this._cm.getWrapperElement().addEventListener('click', (e) => {
166+
const isCtrlClick = isMac() ? e.metaKey : e.ctrlKey;
167+
168+
if (isCtrlClick) {
169+
const pos = this._cm.coordsChar({ left: e.clientX, top: e.clientY });
170+
jumpToDefinition.call(this, pos);
171+
}
172+
});
173+
174+
const renameKey = isMac() ? 'Ctrl-F2' : 'F2';
175+
157176
const replaceCommand =
158177
metaKey === 'Ctrl' ? `${metaKey}-H` : `${metaKey}-Option-F`;
159178
this._cm.setOption('extraKeys', {
@@ -172,6 +191,7 @@ class Editor extends React.Component {
172191
[`Shift-${metaKey}-E`]: (cm) => {
173192
cm.getInputField().blur();
174193
},
194+
[renameKey]: (cm) => this.renameVariable(cm),
175195
[`Shift-Tab`]: false,
176196
[`${metaKey}-Enter`]: () => null,
177197
[`Shift-${metaKey}-Enter`]: () => null,
@@ -209,7 +229,14 @@ class Editor extends React.Component {
209229
}
210230

211231
this._cm.on('keydown', (_cm, e) => {
212-
// Show hint
232+
// Skip hinting if the user is pasting (Ctrl/Cmd+V) or using modifier keys (Ctrl/Alt)
233+
if (
234+
((e.ctrlKey || e.metaKey) && e.key === 'v') ||
235+
e.ctrlKey ||
236+
e.altKey
237+
) {
238+
return;
239+
}
213240
const mode = this._cm.getOption('mode');
214241
if (/^[a-z]$/i.test(e.key) && (mode === 'css' || mode === 'javascript')) {
215242
this.showHint(_cm);
@@ -395,12 +422,15 @@ class Editor extends React.Component {
395422
}
396423

397424
showHint(_cm) {
425+
if (!_cm) return;
426+
398427
if (!this.props.autocompleteHinter) {
399428
CodeMirror.showHint(_cm, () => {}, {});
400429
return;
401430
}
402431

403432
let focusedLinkElement = null;
433+
404434
const setFocusedLinkElement = (set) => {
405435
if (set && !focusedLinkElement) {
406436
const activeItemLink = document.querySelector(
@@ -415,6 +445,7 @@ class Editor extends React.Component {
415445
}
416446
}
417447
};
448+
418449
const removeFocusedLinkElement = () => {
419450
if (focusedLinkElement) {
420451
focusedLinkElement.classList.remove('focused-hint-link');
@@ -437,12 +468,8 @@ class Editor extends React.Component {
437468
);
438469
if (activeItemLink) activeItemLink.click();
439470
},
440-
Right: (cm, e) => {
441-
setFocusedLinkElement(true);
442-
},
443-
Left: (cm, e) => {
444-
removeFocusedLinkElement();
445-
},
471+
Right: (cm, e) => setFocusedLinkElement(true),
472+
Left: (cm, e) => removeFocusedLinkElement(),
446473
Up: (cm, e) => {
447474
const onLink = removeFocusedLinkElement();
448475
e.moveFocus(-1);
@@ -461,30 +488,28 @@ class Editor extends React.Component {
461488
closeOnUnfocus: false
462489
};
463490

464-
if (_cm.options.mode === 'javascript') {
465-
// JavaScript
466-
CodeMirror.showHint(
467-
_cm,
468-
() => {
469-
const c = _cm.getCursor();
470-
const token = _cm.getTokenAt(c);
471-
472-
const hints = this.hinter
473-
.search(token.string)
474-
.filter((h) => h.item.text[0] === token.string[0]);
475-
476-
return {
477-
list: hints,
478-
from: CodeMirror.Pos(c.line, token.start),
479-
to: CodeMirror.Pos(c.line, c.ch)
480-
};
481-
},
482-
hintOptions
483-
);
484-
} else if (_cm.options.mode === 'css') {
485-
// CSS
486-
CodeMirror.showHint(_cm, CodeMirror.hint.css, hintOptions);
487-
}
491+
const triggerHints = () => {
492+
if (_cm.options.mode === 'javascript') {
493+
CodeMirror.showHint(
494+
_cm,
495+
() => {
496+
const c = _cm.getCursor();
497+
const token = _cm.getTokenAt(c);
498+
const hints = contextAwareHinter(_cm, { hinter: this.hinter });
499+
return {
500+
list: hints,
501+
from: CodeMirror.Pos(c.line, token.start),
502+
to: CodeMirror.Pos(c.line, c.ch)
503+
};
504+
},
505+
hintOptions
506+
);
507+
} else if (_cm.options.mode === 'css') {
508+
CodeMirror.showHint(_cm, CodeMirror.hint.css, hintOptions);
509+
}
510+
};
511+
512+
setTimeout(triggerHints, 0);
488513
}
489514

490515
showReplace() {
@@ -522,6 +547,34 @@ class Editor extends React.Component {
522547
}
523548
}
524549

550+
renameVariable(cm) {
551+
const cursorCoords = cm.cursorCoords(true, 'page');
552+
const selection = cm.getSelection();
553+
const pos = cm.getCursor(); // or selection start
554+
const token = cm.getTokenAt(pos);
555+
const tokenType = token.type;
556+
if (!selection) {
557+
return;
558+
}
559+
560+
const sel = cm.listSelections()[0];
561+
const fromPos =
562+
CodeMirror.cmpPos(sel.anchor, sel.head) <= 0 ? sel.anchor : sel.head;
563+
564+
showRenameDialog(
565+
cm,
566+
fromPos,
567+
tokenType,
568+
cursorCoords,
569+
selection,
570+
(newName) => {
571+
if (newName && newName.trim() !== '' && newName !== selection) {
572+
handleRename(fromPos, selection, newName, cm);
573+
}
574+
}
575+
);
576+
}
577+
525578
initializeDocuments(files) {
526579
this._docs = {};
527580
files.forEach((file) => {

client/modules/IDE/components/Header/Toolbar.jsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ const Toolbar = (props) => {
3333
const dispatch = useDispatch();
3434
const { t } = useTranslation();
3535
const userIsOwner = user?.username === project.owner?.username;
36-
3736
const showVisibilityDropdown = project?.owner && userIsOwner;
3837

3938
const playButtonClass = classNames({
@@ -51,7 +50,7 @@ const Toolbar = (props) => {
5150

5251
const handleVisibilityChange = useCallback(
5352
(sketchId, sketchName, newVisibility) => {
54-
dispatch(changeVisibility(sketchId, sketchName, newVisibility));
53+
dispatch(changeVisibility(sketchId, sketchName, newVisibility, t));
5554
},
5655
[changeVisibility]
5756
);

client/modules/IDE/components/KeyboardShortcutModal.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ function KeyboardShortcutModal() {
88
metaKey === 'Ctrl' ? `${metaKeyName} + H` : `${metaKeyName} + ⌥ + F`;
99
const newFileCommand =
1010
metaKey === 'Ctrl' ? `${metaKeyName} + Alt + N` : `${metaKeyName} + ⌥ + N`;
11+
const renameCommand = metaKey === 'Ctrl' ? 'F2' : 'Ctrl + F2';
1112
return (
1213
<div className="keyboard-shortcuts">
1314
<h3 className="keyboard-shortcuts__title">
@@ -75,6 +76,10 @@ function KeyboardShortcutModal() {
7576
<span className="keyboard-shortcut__command">{newFileCommand}</span>
7677
<span>{t('KeyboardShortcuts.CodeEditing.CreateNewFile')}</span>
7778
</li>
79+
<li className="keyboard-shortcut-item">
80+
<span className="keyboard-shortcut__command">{renameCommand}</span>
81+
<span>{t('KeyboardShortcuts.CodeEditing.RenameVariable')}</span>
82+
</li>
7883
</ul>
7984
<h3 className="keyboard-shortcuts__title">
8085
{t('KeyboardShortcuts.General')}

client/modules/IDE/components/SketchListRowBase.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ const SketchListRowBase = ({
9191

9292
const handleVisibilityChange = useCallback(
9393
(sketchId, sketchName, newVisibility) => {
94-
changeVisibility(sketchId, sketchName, newVisibility);
94+
changeVisibility(sketchId, sketchName, newVisibility, t);
9595
},
9696
[changeVisibility]
9797
);

0 commit comments

Comments
 (0)