Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5b391a8
fix(locales): invalid interpolation patterns
pchunky Apr 18, 2025
f48d536
fix(AchievementReordering): clarify button label
pchunky Apr 18, 2025
9770410
perf(AchievementReordering): lazy load jQuery, jQuery UI
pchunky Apr 18, 2025
f5d1f8f
fix(moment): import moment from lib/moment only
pchunky Apr 18, 2025
a9f62f1
perf(EditorField): lazy load Ace's language modes
pchunky Apr 18, 2025
35c1837
perf(VideoPlayer): import only YouTube adapter for react-player
pchunky Apr 18, 2025
de58b2f
perf(AuthenticatedApp): lazy load all routes
pchunky Apr 18, 2025
4730f44
perf(router): lazy load all routes
pchunky Apr 18, 2025
733681a
perf(UnauthenticatedApp): lazy load all routes
pchunky Apr 18, 2025
d325e82
perf(SubmissionEditIndex): lazy load all question type answer components
pchunky Apr 18, 2025
c2eb7d5
perf(index): remove initializers, webfontloader -> CSS `@import`
pchunky Apr 18, 2025
76778ad
fix(store): suppress legacy `onSubmit`, `onConfirm` console errors
pchunky Apr 18, 2025
09ff8c8
perf: replace lodash with lodash-es absolute imports
pchunky Apr 18, 2025
c57ed14
feat(webpack): allow configuring client port
pchunky Apr 18, 2025
3acdce4
chore(webpack): add webpack config types
pchunky Apr 18, 2025
19d0ade
perf(webpack): remove unused entry points
pchunky Apr 18, 2025
9b3a80c
perf(webpack): remove unused expose-loader
pchunky Apr 18, 2025
619ae49
perf(webpack): remove unused webpack-manifest-plugin
pchunky Apr 18, 2025
35f2230
perf(webpack): improve CSS, Sass/SCSS compilations
pchunky Apr 18, 2025
8ed1e4b
fix(webpack): favicon-webpack-plugin warnings in development mode
pchunky Apr 18, 2025
2361606
fix(webpack): ENAMETOOLONG error due to too many chunks
pchunky Apr 18, 2025
d5e5484
perf: partially remove PropTypes in production
pchunky Apr 18, 2025
678dee1
feat(webpack): always clean build folder before building
pchunky Apr 18, 2025
4db8dbe
perf(webpack): enable caching for production builds
pchunky Apr 18, 2025
05d54a4
perf(webpack): use SWC for faster & more aggressive minification
pchunky Apr 18, 2025
3d74ef2
perf(webpack): trim moment-timezone data to only from 2014
pchunky Apr 18, 2025
8a5bb3a
perf(webpack): optimise images, SVGs
pchunky Apr 18, 2025
17840d1
perf(webpack): generate gzip, brotli compressed bundles
pchunky Apr 18, 2025
66afa77
perf(i18n): lazy load translations
pchunky Apr 18, 2025
8f40e05
perf(CKEditorRichText): optimise build, upgrade to v45
pchunky Apr 18, 2025
da70363
test(client): await lazy loaded pages
pchunky Apr 19, 2025
937bb04
test(jest): add lodash overrides since lodash-es is ESM only
pchunky Apr 19, 2025
2fca6fa
test(capybara): remove jQuery check from `wait_for_ajax`
pchunky Apr 19, 2025
badc528
test(forum_disbursement_spec): wrong tab label
pchunky Apr 19, 2025
75435be
test(assessment_management_spec): fix flaky test
pchunky Apr 19, 2025
7941198
feat(application_html_formatters_helper): whitelist only YouTube URLs
pchunky Apr 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,5 @@ playwright/.cache/
coverage/

dump.rdb

compiled-locales
8 changes: 1 addition & 7 deletions app/helpers/application_html_formatters_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,7 @@ def self.build_html_pipeline(custom_options)
# List of video hosting site URLs to allow
VIDEO_URL_WHITELIST = Regexp.union(
/\A(?:https?:)?\/\/(?:www\.)?(?:m.)?youtube\.com\//,
/\A(?:https?:)?\/\/(?:www\.)?youtu.be\//,
/\A(?:https?:)?\/\/(?:www\.)?(?:player.)?vimeo\.com\//,
/\A(?:https?:)?\/\/(?:www\.)?vine\.co\//,
/\A(?:https?:)?\/\/(?:www\.)?instagram\.com\//,
/\A(?:https?:)?\/\/(?:www\.)?(?:geo.)?dailymotion\.com\//,
/\A(?:https?:)?\/\/(?:www\.)?dai\.ly\//,
/\A(?:https?:)?\/\/(?:www\.)?youku\.com\//
/\A(?:https?:)?\/\/(?:www\.)?youtu.be\//
).freeze

OEMBED_WHITELIST_TRANSFORMER = lambda do |env|
Expand Down
17 changes: 8 additions & 9 deletions client/.babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,6 @@
"ast": true
}
],
[
"babel-plugin-import",
{
"libraryName": "lodash",
"libraryDirectory": "",
"camel2DashComponentName": false
}
],
[
"babel-plugin-import",
{
Expand All @@ -44,7 +36,14 @@
"env": {
"production": {
"plugins": [
["react-remove-properties", { "properties": ["data-testid"] }]
["react-remove-properties", { "properties": ["data-testid"] }],
[
"transform-react-remove-prop-types",
{
"mode": "remove",
"removeImport": true
}
]
]
},
"test": {
Expand Down
6 changes: 0 additions & 6 deletions client/app/__test__/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ import './mocks/matchMedia';

Enzyme.configure({ adapter: new Adapter() });

require('@babel/polyfill');
// Our jquery is from CDN and loaded at runtime, so this is required in test.
const jQuery = require('jquery');

const timeZone = 'Asia/Singapore';
const intlCache = createIntlCache();
const intl = createIntl({ locale: 'en', timeZone }, intlCache);
Expand Down Expand Up @@ -47,8 +43,6 @@ const buildContextOptions = (store) => {
global.courseId = courseId;
global.window = window;
global.muiTheme = muiTheme;
global.$ = jQuery;
global.jQuery = jQuery;
global.buildContextOptions = buildContextOptions;

window.history.pushState({}, '', `/courses/${courseId}`);
Expand Down
2 changes: 1 addition & 1 deletion client/app/bundles/common/DashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { defineMessages } from 'react-intl';
import { Navigate } from 'react-router-dom';
import { ArrowForward } from '@mui/icons-material';
import { Avatar, Stack, Typography } from '@mui/material';
import moment from 'moment';
import { HomeLayoutCourseData } from 'types/home';

import { getCourseLogoUrl } from 'course/helper';
Expand All @@ -13,6 +12,7 @@ import { useAppContext } from 'lib/containers/AppContainer';
import { getUrlParameter } from 'lib/helpers/url-helpers';
import useItems from 'lib/hooks/items/useItems';
import useTranslation from 'lib/hooks/useTranslation';
import moment from 'lib/moment';

import NewCourseButton from './components/NewCourseButton';

Expand Down

This file was deleted.

16 changes: 6 additions & 10 deletions client/app/bundles/common/PrivacyPolicyPage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { lazy, Suspense } from 'react';
import { defineMessages } from 'react-intl';

const PrivacyPolicyPage = lazy(
() =>
import(/* webpackChunkName: "PrivacyPolicyPage" */ './PrivacyPolicyPage'),
);
import MarkdownPage from 'lib/components/core/layouts/MarkdownPage';

import privacyPolicy from './privacy-policy.md';

const translations = defineMessages({
privacyPolicy: {
Expand All @@ -13,12 +11,10 @@ const translations = defineMessages({
},
});

const SuspensedPrivacyPolicyPage = (): JSX.Element => (
<Suspense>
<PrivacyPolicyPage />
</Suspense>
const PrivacyPolicyPage = (): JSX.Element => (
<MarkdownPage className="m-auto max-w-7xl" markdown={privacyPolicy} />
);

const handle = translations.privacyPolicy;

export default Object.assign(SuspensedPrivacyPolicyPage, { handle });
export default Object.assign(PrivacyPolicyPage, { handle });

This file was deleted.

16 changes: 6 additions & 10 deletions client/app/bundles/common/TermsOfServicePage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { lazy, Suspense } from 'react';
import { defineMessages } from 'react-intl';

const TermsOfServicePage = lazy(
() =>
import(/* webpackChunkName: "TermsOfServicePage" */ './TermsOfServicePage'),
);
import MarkdownPage from 'lib/components/core/layouts/MarkdownPage';

import termsOfService from './terms-of-service.md';

const translations = defineMessages({
termsOfService: {
Expand All @@ -13,12 +11,10 @@ const translations = defineMessages({
},
});

const SuspensedTermsOfServicePage = (): JSX.Element => (
<Suspense>
<TermsOfServicePage />
</Suspense>
const TermsOfServicePage = (): JSX.Element => (
<MarkdownPage className="m-auto max-w-7xl" markdown={termsOfService} />
);

const handle = translations.termsOfService;

export default Object.assign(SuspensedTermsOfServicePage, { handle });
export default Object.assign(TermsOfServicePage, { handle });
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
import { useRef, useState } from 'react';
import { defineMessages } from 'react-intl';
import { Button } from '@mui/material';
import { LoadingButton } from '@mui/lab';

import CourseAPI from 'api/course';
import toast from 'lib/hooks/toast';
import useTranslation from 'lib/hooks/useTranslation';

require('jquery-ui/ui/widgets/sortable');

interface AchievementReorderingProps {
handleReordering: (state: boolean) => void;
isReordering: boolean;
}

const styles = {
AchievementReorderingButton: {
fontSize: 14,
marginRight: 12,
},
};

const translations = defineMessages({
startReorderAchievement: {
id: 'course.achievement.AchievementReordering.startReorderAchievement',
defaultMessage: 'Reorder',
},
endReorderAchievement: {
id: 'course.achievement.AchievementReordering.endReorderAchievement',
defaultMessage: 'Save New Ordering',
defaultMessage: 'Done reordering',
},
updateFailed: {
id: 'course.achievement.AchievementReordering.updateFailed',
Expand All @@ -38,12 +30,6 @@ const translations = defineMessages({
},
});

// Serialise the ordered achievements as data for the API call.
function serializedOrdering(): string {
const options = { attribute: 'achievementid', key: 'achievement_order[]' };
return $('tbody').first().sortable('serialize', options);
}

const AchievementReordering = (
props: AchievementReorderingProps,
): JSX.Element => {
Expand All @@ -60,35 +46,80 @@ const AchievementReordering = (
}
}

const [loadingSortable, setLoadingSortable] = useState(false);

const sortableCallbacksRef = useRef<{
enable: () => void;
disable: () => void;
}>();

return (
<Button
key="achievement-reordering-button"
className="achievement-reordering-button"
<LoadingButton
color="primary"
loading={loadingSortable}
loadingPosition="start"
onClick={(): void => {
if (loadingSortable) return;

if (!sortableCallbacksRef.current) {
setLoadingSortable(true);

(async (): Promise<void> => {
const [jquery] = await Promise.all([
import(
/* webpackChunkName: "jquery-sortable" */
'jquery'
),
import(
/* webpackChunkName: "jquery-sortable" */
'jquery-ui/ui/widgets/sortable'
),
]);

sortableCallbacksRef.current = {
enable: (): void => {
const table = jquery.default('tbody').first();

table.sortable({
disabled: false,
update() {
const ordering = table.sortable('serialize', {
attribute: 'achievementid',
key: 'achievement_order[]',
});

submitReordering(ordering);
},
});

handleReordering(true);
},
disable: (): void => {
jquery.default('tbody').first().sortable({ disabled: true });
handleReordering(false);
},
};

sortableCallbacksRef.current.enable();

setLoadingSortable(false);
})();

return;
}

if (isReordering) {
$('tbody').first().sortable({ disabled: true });
handleReordering(false);
sortableCallbacksRef.current.disable();
} else {
$('tbody')
.first()
.sortable({
update() {
const ordering = serializedOrdering();
submitReordering(ordering);
},
disabled: false,
});
handleReordering(true);
sortableCallbacksRef.current.enable();
}
}}
style={styles.AchievementReorderingButton}
variant={isReordering ? 'contained' : 'outlined'}
>
{isReordering
? t(translations.endReorderAchievement)
: t(translations.startReorderAchievement)}
</Button>
</LoadingButton>
);
};

Expand Down
Loading