Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
6eec647
added utility to load quill editor
michaelchadwick Jul 31, 2025
0f155d2
merge qunit-dom dependency update
michaelchadwick Sep 2, 2025
ecbfbc4
added translations for toolbar buttons and link popup
michaelchadwick Aug 4, 2025
9d8c0ce
add helper function for loading quill editor
michaelchadwick Aug 4, 2025
0976dbe
HtmlEditor component now uses QuillEditor
michaelchadwick Aug 4, 2025
9159c71
update course and programYear routes to use QuillEditor
michaelchadwick Aug 4, 2025
2666628
updated HtmlEditor integration test to use QuillEditor
michaelchadwick Aug 4, 2025
6922e38
make unit test for quill editor utilty
michaelchadwick Aug 4, 2025
4e09650
globally add css for Quill...for now
michaelchadwick Aug 4, 2025
016e30f
pull in local node Quill CSS instead of using cdn
michaelchadwick Aug 4, 2025
3aa775b
remove froala loaders
michaelchadwick Aug 4, 2025
b77dbaa
remove froala helper unit test
michaelchadwick Aug 4, 2025
6fc6912
remove froala npm package
michaelchadwick Aug 4, 2025
1a63482
normalized the colors used for HtmlEditor in variable and format
michaelchadwick Aug 4, 2025
5710fae
fixed some pathing bugs
michaelchadwick Aug 4, 2025
4758140
update lock file post-froala
michaelchadwick Aug 5, 2025
8abd8dd
fixed toolbar alignment; made disabled undo/redo button correct color
michaelchadwick Aug 5, 2025
ec86ace
editor undo/redo now disabled unless there is actionable history
michaelchadwick Aug 5, 2025
f4038e1
fixed existing tests to work with Quill
michaelchadwick Aug 5, 2025
8a5fd7c
made quill content editor a little less tall
michaelchadwick Aug 5, 2025
2837804
make link popup insert button look more like froala's
michaelchadwick Aug 5, 2025
8e24030
link insert popup now disappears when you click off of it
michaelchadwick Aug 5, 2025
a5ef39b
beefed up integration test to make sure full toolbar is there; link p…
michaelchadwick Aug 6, 2025
a972abd
remove old commented-put Froala method no longer needed
michaelchadwick Aug 11, 2025
1d54565
updated comments about editor.root.innerHTML vs editor.getContents()
michaelchadwick Aug 11, 2025
94b8094
removed unneeded tracking variable for editor loading
michaelchadwick Aug 11, 2025
58a02d1
refactor undo/redo buttons import markup so it renders like other too…
michaelchadwick Aug 11, 2025
5fe9989
added better comment explaining where undo/redo svg markup comes from…
michaelchadwick Aug 11, 2025
a08ac87
make Quill instances fill 100% of parent container
michaelchadwick Aug 11, 2025
a9f31d4
added some padding to popup input fields
michaelchadwick Aug 11, 2025
2820c5e
link popup will close on escape key now
michaelchadwick Aug 11, 2025
d27a085
merged webpack dependency update
michaelchadwick Sep 2, 2025
c1013f0
import redo/undo svgs for quill in build stage
michaelchadwick Aug 13, 2025
88c547d
add quill to test-app
michaelchadwick Aug 13, 2025
ae6be45
move redo/undo svg files from quill node_modules dir into ilios publi…
michaelchadwick Aug 13, 2025
fd2ba87
fixed missing dependency reference
michaelchadwick Aug 18, 2025
543e92f
add quill to LTIs so they build
michaelchadwick Aug 18, 2025
5364810
Import SVGs from Quill
jrjohnson Aug 18, 2025
c461dfb
add moderately-good user link parsing for more valid links
michaelchadwick Aug 25, 2025
2a0bbc4
change inserted link default to be same window/tab
michaelchadwick Aug 26, 2025
ff63b04
override the base Link Blot so that target can be customized
michaelchadwick Aug 26, 2025
6d34495
make it more obvious why I'm converting existing data so it can displ…
michaelchadwick Aug 27, 2025
4c7e836
only allow the escape key to close popup, not open it
michaelchadwick Aug 27, 2025
8607886
allow enter key to submit insert link modal
michaelchadwick Aug 27, 2025
bc614e9
re-focus on the editor if the link popup is closed via escape key
michaelchadwick Aug 27, 2025
f642a8d
added integration tests for link popup and its accuracy in inserting …
michaelchadwick Aug 27, 2025
99454ae
addLink is now a task and YupValidation has been added to popup input…
michaelchadwick Aug 27, 2025
8559b27
fix quill existing link tooltip editing bug and hide ql-preview input…
michaelchadwick Aug 27, 2025
79f9149
remove convenience variable as not useful enough and potentially conf…
michaelchadwick Aug 27, 2025
e73904d
added test for checking edited links, but skipped for now because can…
michaelchadwick Aug 27, 2025
f2ffa34
added proper url YupValidation to popup URL value and modeled it afte…
michaelchadwick Aug 28, 2025
59a2fbf
switched test skip to todo so it shows up in browser testing
michaelchadwick Aug 29, 2025
69c1cbf
pnpm-lock.yaml re-creation
michaelchadwick Sep 2, 2025
d34207a
fixed test by changed yup's ensure() to required(); refactored to be …
michaelchadwick Sep 2, 2025
3330665
added some labels to the session acceptance test
michaelchadwick Sep 3, 2025
f008fc2
switched to using getSemanticHTML() and massaging output going into d…
michaelchadwick Sep 3, 2025
cf6eb94
removed debug option as it was set to the default already
michaelchadwick Sep 3, 2025
eb84763
changed popupValidations to validations to make searching for yup stu…
michaelchadwick Sep 3, 2025
15671be
added another integration test to check if editor data sent to db loo…
michaelchadwick Sep 3, 2025
1e7bf5f
updated eslint-disable to just use next-line
michaelchadwick Sep 3, 2025
cb9324a
removed old froala destroyed/destroying checks as those are not relev…
michaelchadwick Sep 5, 2025
027d94a
clear quill instance history on load so existing text is not on the s…
michaelchadwick Sep 5, 2025
d32f0ab
match quill editor font-size with old froala editor font-size
michaelchadwick Sep 5, 2025
ec5f5f9
set quill's user-stopped-typing-to-record-an-undo delay from default…
michaelchadwick Sep 5, 2025
d904ea3
make quill history undo act like Froala using a 500ms cutoff to save …
michaelchadwick Sep 8, 2025
59cb45a
undo/redo buttons transition color when enabling/disabling like froala
michaelchadwick Sep 8, 2025
1b47120
make sure editor exists before cutting off history so that acceptance…
michaelchadwick Sep 8, 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
6 changes: 3 additions & 3 deletions packages/frontend/app/routes/program-year.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';
import { loadFroalaEditor } from 'ilios-common/utils/load-froala-editor';
import { loadQuillEditor } from 'ilios-common/utils/load-quill-editor';

export default class ProgramYearRoute extends Route {
@service currentUser;
Expand Down Expand Up @@ -34,8 +34,8 @@ export default class ProgramYearRoute extends Route {
async afterModel(programYear) {
const permissionChecker = this.permissionChecker;
this.canUpdate = await permissionChecker.canUpdateProgramYear(programYear);
//pre load froala so it's available quickly when working in the course
loadFroalaEditor();
// pre-load quill so it's available quickly when working in the course
loadQuillEditor();
}

setupController(controller, model) {
Expand Down
8 changes: 8 additions & 0 deletions packages/frontend/ember-cli-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ module.exports = async function (defaults) {
}),
],
},
module: {
rules: [
{
test: /\.svg$/,
type: 'asset/source', // This will import SVG files as text
},
],
},
},
},
});
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
"prettier": "^3.5.3",
"prettier-plugin-ember-template-tag": "^2.0.6",
"query-string": "^9.1.0",
"quill": "^2.0.3",
"qunit": "^2.24.1",
"qunit-dom": "^3.4.0",
"sass": "^1.91.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -642,15 +642,24 @@ module('Acceptance | Session - Overview', function (hooks) {
const newInstructionalNotes = 'some new thing';
await page.visit({ courseId: 1, sessionId: 1 });

assert.strictEqual(currentRouteName(), 'session.index');
assert.strictEqual(page.details.overview.instructionalNotes.value, 'instructional note');
assert.strictEqual(currentRouteName(), 'session.index', 'route name is correct');
assert.strictEqual(
page.details.overview.instructionalNotes.value,
'instructional note',
'instructional notes value is correct',
);
await page.details.overview.instructionalNotes.edit();
await page.details.overview.instructionalNotes.set(newInstructionalNotes);
await page.details.overview.instructionalNotes.save();
assert.strictEqual(page.details.overview.instructionalNotes.value, newInstructionalNotes);
assert.strictEqual(
page.details.overview.instructionalNotes.value,
newInstructionalNotes,
'new instructional notes value is correct',
);
assert.strictEqual(
this.server.db.sessions[0].instructionalNotes,
`<p>${newInstructionalNotes}</p>`,
'instructional notes value in database is correct',
);
});

Expand All @@ -668,15 +677,24 @@ module('Acceptance | Session - Overview', function (hooks) {
const newInstructionalNotes = 'some new thing';
await page.visit({ courseId: 1, sessionId: 1 });

assert.strictEqual(currentRouteName(), 'session.index');
assert.strictEqual(page.details.overview.instructionalNotes.value, 'Click to edit');
assert.strictEqual(currentRouteName(), 'session.index', 'route name is correct');
assert.strictEqual(
page.details.overview.instructionalNotes.value,
'Click to edit',
'initial instructional notes value is correct',
);
await page.details.overview.instructionalNotes.edit();
await page.details.overview.instructionalNotes.set(newInstructionalNotes);
await page.details.overview.instructionalNotes.save();
assert.strictEqual(page.details.overview.instructionalNotes.value, newInstructionalNotes);
assert.strictEqual(
page.details.overview.instructionalNotes.value,
newInstructionalNotes,
'new instructional notes value is correct',
);
assert.strictEqual(
this.server.db.sessions[0].instructionalNotes,
`<p>${newInstructionalNotes}</p>`,
'instructional notes value in database is correct',
);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { clickable, create, hasClass, isPresent, isVisible, text } from 'ember-cli-page-object';
import { pageObjectFillInFroalaEditor, pageObjectFroalaEditorValue } from 'ilios-common';
import { pageObjectFillInQuillEditor, pageObjectQuillEditorValue } from 'ilios-common';
import meshManager from './manage-objective-descriptors';
import competencyManager from './manage-objective-competency';
import meshDescriptors from './objective-list-item-descriptors';
Expand All @@ -14,8 +14,8 @@ const definition = {
description: {
scope: '[data-test-description]',
openEditor: clickable('[data-test-edit]'),
editorContents: pageObjectFroalaEditorValue('[data-test-html-editor]'),
edit: pageObjectFillInFroalaEditor('[data-test-html-editor]'),
editorContents: pageObjectQuillEditorValue('[data-test-html-editor]'),
edit: pageObjectFillInQuillEditor('[data-test-html-editor]'),
save: clickable('.done'),
hasError: isPresent('[data-test-description-validation-error-message]'),
error: text('[data-test-description-validation-error-message]'),
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { findOne } from 'ember-cli-page-object/extend';
import { loadQuillEditor } from 'ilios-common/utils/load-quill-editor';
import { later } from '@ember/runloop';

export async function fillInQuillEditor(element, html) {
const editor = await getEditorInstance(element);
editor.setContents(editor.clipboard.convert({ html }));
}
export async function quillEditorValue(element) {
const editor = await getEditorInstance(element);
// easiest way to get the HTML in an editor, maintain multiple spaces, and make sure it's empty empty, as Quill leaves `<p><br></p>` even if the editor is "empty"
// not using editor.getContents() as it returns custom Delta object that doesn't actually have the HTML markup: https://quilljs.com/docs/api#getcontents
return editor.root.innerHTML.split(' ').join(' &nbsp;').replaceAll('<p><br></p>', '');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a comment or link. I'm guessing this is the only way to get at the value here, but it seems weird to reach into the HTML and that there isn't an API in quill to get the current value of the editor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a call to get the contents: https://quilljs.com/docs/api#getcontents, but it doesn't work as well as what I used.

There are three ways to get at what's in the editor (I had just added the exclamation point at the end):
Screenshot 2025-08-11 at 1 13 41 PM

}

export function pageObjectFillInQuillEditor(selector, options = {}) {
return {
isDescriptor: true,

get() {
return async function (html) {
const element = findOne(this, selector, options);
return fillInQuillEditor(element, html);
};
},
};
}

export function pageObjectQuillEditorValue(selector, options = {}) {
return {
isDescriptor: true,

get() {
return async function () {
const element = findOne(this, selector, options);
return quillEditorValue(element);
};
},
};
}

function getEditorInstance(element) {
return new Promise((resolve) => {
loadQuillEditor().then(({ QuillEditor }) => {
// eslint-disable-next-line ember/no-runloop
later(() => {
const ourInstance = QuillEditor.find(document.querySelector(`#${element.id}`));
resolve(ourInstance);
});
});
});
}
10 changes: 5 additions & 5 deletions packages/ilios-common/addon-test-support/ilios-common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ export { default as waitForResource } from './helpers/wait-for-resource';
export { freezeDateAt, unfreezeDate } from './helpers/mockdate';
export { flatpickrDatePicker, flatpickrDateValue } from './helpers/flatpickr-date-picker';
export {
fillInFroalaEditor,
froalaEditorValue,
pageObjectFillInFroalaEditor,
pageObjectFroalaEditorValue,
} from './helpers/froala-editor';
fillInQuillEditor,
quillEditorValue,
pageObjectFillInQuillEditor,
pageObjectQuillEditorValue,
} from './helpers/quill-editor';
export { getText, getElementText } from './helpers/custom-helpers';
export { hasFocus } from './helpers/has-focus';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { clickable, create, hasClass, isPresent, isVisible, text } from 'ember-cli-page-object';
import { pageObjectFillInFroalaEditor, pageObjectFroalaEditorValue } from 'ilios-common';
import { pageObjectFillInQuillEditor, pageObjectQuillEditorValue } from 'ilios-common';
import fadeText from '../fade-text';
import meshManager from './manage-objective-descriptors';
import parentManager from './manage-objective-parents';
Expand All @@ -15,8 +15,8 @@ const definition = {
scope: '[data-test-description]',
openEditor: clickable('[data-test-edit]'),
fadeText,
editorContents: pageObjectFroalaEditorValue('[data-test-html-editor]'),
edit: pageObjectFillInFroalaEditor('[data-test-html-editor]'),
editorContents: pageObjectQuillEditorValue('[data-test-html-editor]'),
edit: pageObjectFillInQuillEditor('[data-test-html-editor]'),
save: clickable('.done'),
hasError: isPresent('[data-test-description-validation-error-message]'),
error: text('[data-test-description-validation-error-message]'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import newLearningMaterial from './new-learningmaterial';
import datePicker from './date-picker';
import timePicker from './time-picker';
import items from './detail-learning-materials-item';
import { pageObjectFillInFroalaEditor, pageObjectFroalaEditorValue } from 'ilios-common';
import { pageObjectFillInQuillEditor, pageObjectQuillEditorValue } from 'ilios-common';

const definition = {
scope: '[data-test-detail-learning-materials]',
Expand Down Expand Up @@ -44,8 +44,8 @@ const definition = {
description: {
scope: '.description',
value: text(),
update: pageObjectFillInFroalaEditor('[data-test-html-editor]'),
editorValue: pageObjectFroalaEditorValue('[data-test-html-editor]'),
update: pageObjectFillInQuillEditor('[data-test-html-editor]'),
editorValue: pageObjectQuillEditorValue('[data-test-html-editor]'),
},
copyrightPermission: text('.copyrightpermission'),
copyrightRationale: text('.copyrightrationale'),
Expand All @@ -65,8 +65,8 @@ const definition = {
statusValue: value('select', { at: 0 }),
notes: {
scope: '.notes',
update: pageObjectFillInFroalaEditor('[data-test-html-editor]'),
value: pageObjectFroalaEditorValue('[data-test-html-editor]'),
update: pageObjectFillInQuillEditor('[data-test-html-editor]'),
value: pageObjectQuillEditorValue('[data-test-html-editor]'),
},
addStartDate: clickable('[data-test-add-start-date]'),
addEndDate: clickable('[data-test-add-end-date]'),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {
attribute,
clickable,
collection,
create,
fillable,
focusable,
isVisible,
isPresent,
property,
text,
} from 'ember-cli-page-object';

const definition = {
scope: '[data-test-quill-html-editor]',
toolbar: {
scope: '[data-test-toolbar]',
bold: isPresent('[data-test-toolbar-bold]'),
italic: isPresent('[data-test-toolbar-italic]'),
subscript: isPresent('[data-test-toolbar-subscript]'),
superscript: isPresent('[data-test-toolbar-superscript]'),
listOrdered: isPresent('[data-test-toolbar-list-ordered]'),
listUnordered: isPresent('[data-test-toolbar-list-unordered]'),
link: {
scope: '[data-test-toolbar-link]',
insertLink: clickable(),
},
undo: {
scope: '[data-test-toolbar-undo]',
disabled: attribute('disabled'),
goBack: clickable(),
},
redo: {
scope: '[data-test-toolbar-redo]',
disabled: attribute('disabled'),
goForward: clickable(),
},
},
editor: {
scope: '[data-test-html-editor]',
content: {
scope: '.ql-editor',
edit: fillable(),
focus: focusable(),
textContent: text(),
htmlContent: property('innerHTML'),
linkTooltip: {
scope: '.ql-tooltip',
openEditor: clickable('.ql-action'),
edit: fillable('input'),
},
},
},
popup: {
scope: '[data-test-insert-link-popup]',
errors: collection('.validation-error-message'),
activated: isVisible(),
form: {
url: {
scope: '[data-test-url]',
edit: fillable(),
},
text: {
scope: '[data-test-text]',
edit: fillable(),
},
linkNewTarget: isPresent('[data-test-link-new-target]'),
insert: {
scope: '[data-test-submit]',
submit: clickable(),
},
},
},
};

export default definition;
export const component = create(definition);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { attribute, clickable, create, fillable, isPresent } from 'ember-cli-page-object';
import { pageObjectFillInFroalaEditor } from 'ilios-common';
import { pageObjectFillInQuillEditor } from 'ilios-common';
import userNameInfo from './user-name-info';

const definition = {
Expand Down Expand Up @@ -57,7 +57,7 @@ const definition = {
scope: '[data-test-role]',
select: fillable('select'),
},
description: pageObjectFillInFroalaEditor('[data-test-html-editor]'),
description: pageObjectFillInQuillEditor('[data-test-html-editor]'),
copyrightPermission: {
scope: '[data-test-copyright-permission]',
toggle: clickable('input'),
Expand Down
Loading