diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2ff30dc..2664619 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,38 @@ All notable changes to `@webspellchecker/wproofreader-learnosity`.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
+## [2.0.0]
+
+### Breaking
+
+- **`bundleUrl` option removed.** To point at a self-hosted or non-default
+ bundle, pass the WProofreader SDK's own `srcUrl` inside the `wproofreader`
+ block instead of the top-level `bundleUrl`:
+
+ ```js
+ // before
+ LearnosityWProofreader.init({ wproofreader: { serviceHost: '...' }, bundleUrl: '...' });
+ // after
+ LearnosityWProofreader.init({ wproofreader: { serviceHost: '...', srcUrl: '...' } });
+ ```
+
+### Added
+
+- **Named export.** The plugin can now be imported as
+ `import { LearnosityWProofreader } from '@webspellchecker/wproofreader-learnosity'`
+ in addition to the existing default import, mirroring the WProofreader SDK
+ (default plus named exports). Both resolve to the same object.
+
+### Changed
+
+- **Bundle loading delegated to the WProofreader SDK.** The plugin now uses
+ `@webspellchecker/wproofreader-sdk-js` to load the WProofreader bundle and
+ create instances, replacing the hand-rolled script injection and global
+ polling. The internal `window.WEBSPELLCHECKER_CONFIG` object is no longer set;
+ per-editor options are passed straight to the SDK's `init()` (so the SDK's
+ `srcUrl`, `lang`, etc. ride through the `wproofreader` block). The default
+ bundle URL is unchanged.
+
## [1.2.1]
### Features
@@ -43,6 +75,7 @@ Initial public release.
- **Configurable WProofreader bundle URL** via `bundleUrl`, for customers running WProofreader from a non-default CDN host.
- **Apache-2.0 licensed.**
+[2.0.0]: https://github.com/WebSpellChecker/wproofreader-learnosity/releases/tag/v2.0.0
[1.2.1]: https://github.com/WebSpellChecker/wproofreader-learnosity/releases/tag/v1.2.1
[1.2.0]: https://github.com/WebSpellChecker/wproofreader-learnosity/releases/tag/v1.2.0
[1.1.0]: https://github.com/WebSpellChecker/wproofreader-learnosity/releases/tag/v1.1.0
diff --git a/README.md b/README.md
index 3b63f6b..53af17d 100644
--- a/README.md
+++ b/README.md
@@ -2,11 +2,9 @@
WProofreader for Learnosity. Spelling, grammar and style assistance inside Learnosity rich-text widgets, with one function call.
-> v1.2.1. Apache-2.0. TypeScript definitions included. Two install paths: npm (ESM) and CDN (IIFE). Cloud and self-hosted WProofreader. Per-editor surface filter, lifecycle cleanup, locale-aware defaults, optional escape hatches for unusual host apps.
-
## Install
-The connector offers two install paths. The runtime API is identical, `LearnosityWProofreader.init({...})` in both.
+The plugin offers two install paths. The runtime API is identical, `LearnosityWProofreader.init({...})` in both.
### Via npm (Node host apps)
@@ -15,19 +13,25 @@ npm install @webspellchecker/wproofreader-learnosity
```
```js
-import LearnosityWProofreader from '@webspellchecker/wproofreader-learnosity';
+import { LearnosityWProofreader } from '@webspellchecker/wproofreader-learnosity';
LearnosityWProofreader.init({
wproofreader: { serviceId: 'YOUR_WPROOFREADER_SERVICE_ID' }
});
```
+The plugin is available as both a named and a default export, so a default import works too:
+
+```js
+import LearnosityWProofreader from '@webspellchecker/wproofreader-learnosity';
+```
+
### Via `
+
```
-The bundle exposes a single global, `window.LearnosityWProofreader`, with the same `init` method. About 5 KB minified, no runtime dependencies. jsDelivr serves from npm, so the pinned URL above resolves once the package is published.
-
## Quickstart
```js
-import LearnosityWProofreader from '@webspellchecker/wproofreader-learnosity';
+import { LearnosityWProofreader } from '@webspellchecker/wproofreader-learnosity';
// Before Learnosity widgets render.
LearnosityWProofreader.init({
wproofreader: { serviceId: 'YOUR_WPROOFREADER_SERVICE_ID' }
});
-// Initialise Learnosity as normal. The connector attaches to longtextV2 editors automatically.
+// Initialise Learnosity as normal. The plugin attaches to longtextV2 editors automatically.
LearnosityApp.init(signedRequest, {
readyListener() { console.log('Learnosity ready'); },
errorListener(err) { console.error(err); }
@@ -76,9 +78,9 @@ LearnosityWProofreader.init({
serviceProtocol: 'https',
serviceHost: 'localhost',
servicePort: 443,
- servicePath: '/wscservice/api'
- },
- bundleUrl: 'https://localhost/wscservice/wscbundle/wscbundle.js'
+ servicePath: '/wscservice/api',
+ srcUrl: 'https://localhost/wscservice/wscbundle/wscbundle.js'
+ }
});
```
@@ -86,7 +88,7 @@ Either `wproofreader.serviceId` or `wproofreader.serviceHost` must be set; other
## Configuration
-`init()` takes a single options object with two groups: a nested `wproofreader` sub-object (forwarded to WProofreader unchanged) and connector-only fields at the top level.
+`init()` takes a single options object with two groups: a nested `wproofreader` sub-object (forwarded to WProofreader unchanged) and plugin-only fields at the top level.
### WProofreader options (under `wproofreader`)
@@ -99,39 +101,26 @@ These pass straight through to WProofreader's runtime config. The tables below c
| `serviceHost` | _none_ | Self-hosted: WProofreader server hostname. Presence of `serviceHost` (or `serviceId`) is what activates `init()`. |
| `servicePort` | _none_ | Self-hosted: port. |
| `servicePath` | _none_ | Self-hosted: URL path to the WProofreader API. |
+| `srcUrl` | _SDK default (cloud CDN)_ | SDK option: where to load `wscbundle.js` from. Override for a self-hosted deployment or non-default CDN; defaults to the WebSpellChecker cloud bundle. |
| `enableGrammar` | `true` | Grammar checking on top of spell checking. |
| `autocorrect` | `true` | Suggested replacement on typo accept. |
| `lang` | host page's ``, or `en_US`| Read from `` with `-` normalised to `_` (e.g. `en-US` → `en_US`). Falls back to `en_US` if the page does not set a `lang` or sets a bare un-regioned one (`en`, `fr`), because WProofreader expects region-qualified codes. Pass explicitly to override. |
If neither `wproofreader.serviceId` nor `wproofreader.serviceHost` is set, `init()` no-ops with a console warning so the host app can call it unconditionally.
-### Connector options (top level)
+
+### Plugin options (top level)
| Option | Default | Notes |
|-------------------|---------------|--------------------------------------------------------|
| `surfaces` | `['author']` | Subset of `['questions','items','author']`. **Per-editor** filter: each editor's ancestry is walked for surface-specific markers (`lrn-author*` → author, `lrn_player*` → items, otherwise → questions); editors whose surface is not in the allowed list are skipped. Default is Author only, for assessment integrity. Opt in to assessment proofreading explicitly with `['questions', 'items', 'author']`. |
| `enableShorttext` | `false` | Also attach to `shorttext` inputs (``) inside Learnosity scope. Off by default because plain text inputs are usually short and proofreading them is noisy. |
| `customSelectors` | `[]` | Extra ancestor CSS selectors that count as "inside Learnosity scope" for unusual host apps. Each entry is matched via `element.matches`. |
-| `bundleUrl` | (default CDN) | Override the WProofreader bundle URL for non-default CDNs or self-hosted servers. |
-
-### Examples
```js
-// Authoring-only (the default). No-ops on assessment pages.
-LearnosityWProofreader.init({
- wproofreader: { serviceId: '...' }
-});
-
-// All three surfaces, including assessment. The host app should derive this
-// from server-side activity metadata so it cannot be flipped from the console.
-LearnosityWProofreader.init({
- wproofreader: { serviceId: '...' },
- surfaces: ['questions', 'items', 'author']
-});
-
-// Unusual host app that wraps Learnosity inside its own container class.
LearnosityWProofreader.init({
wproofreader: { serviceId: '...' },
+ surfaces: ['questions', 'items', 'author'],
customSelectors: ['.my-host-app-learnosity-region']
});
```
@@ -148,25 +137,15 @@ LearnosityWProofreader.init({
## Supported Learnosity versions
-Tested manually against the `latest-lts` channel. The connector tracks editor placement via the `lrn`-prefixed class convention plus a small per-surface marker set; class-rename changes between LTS releases are an isolated update in `src/index.js`.
+Tested manually against the `latest-lts` channel. The plugin tracks editor placement via the `lrn`-prefixed class convention plus a small per-surface marker set; class-rename changes between LTS releases are an isolated update in `src/index.js`.
## Documentation and support
- **WProofreader product page**:
-- **Configuration reference** (every WProofreader option beyond what this connector documents):
-- **WProofreader SDK source** (the underlying library the connector wraps):
+- **Configuration reference** (every WProofreader option beyond what this plugin documents):
- **Self-hosted server image**:
- **Contact and commercial enquiries**:
-## Tests
-
-```bash
-npm install
-npm test
-```
-
-`npm test` runs the unit suite under `node --test`.
-
## License
Apache-2.0. See [LICENSE](./LICENSE).
diff --git a/package-lock.json b/package-lock.json
index c43eb28..bf42dc7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,13 +1,16 @@
{
"name": "@webspellchecker/wproofreader-learnosity",
- "version": "1.2.0",
+ "version": "2.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@webspellchecker/wproofreader-learnosity",
- "version": "1.2.0",
+ "version": "2.0.0",
"license": "Apache-2.0",
+ "dependencies": {
+ "@webspellchecker/wproofreader-sdk-js": "^1.1.2"
+ },
"devDependencies": {
"esbuild": "^0.23.0"
},
@@ -423,6 +426,12 @@
"node": ">=18"
}
},
+ "node_modules/@webspellchecker/wproofreader-sdk-js": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@webspellchecker/wproofreader-sdk-js/-/wproofreader-sdk-js-1.1.2.tgz",
+ "integrity": "sha512-s6pEmGr5Y0k7p2JbG0mqNka4thzx0rbowaGlIXWp5UAj/W54NU73AxyIktPQF8Hzouxnsds1T323933Q6AZPVw==",
+ "license": "Apache-2.0"
+ },
"node_modules/esbuild": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
diff --git a/package.json b/package.json
index 56dd0ef..acaeb9c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@webspellchecker/wproofreader-learnosity",
- "version": "1.2.1",
+ "version": "2.0.0",
"description": "WProofreader for Learnosity. Spelling, grammar and style assistance inside Learnosity rich-text widgets.",
"type": "module",
"main": "src/index.js",
@@ -13,16 +13,18 @@
},
"files": [
"src/",
- "dist/",
- "README.md",
- "LICENSE",
- "CHANGELOG.md"
+ "dist/"
],
"scripts": {
- "test": "node --test tests/*.test.js",
- "build": "esbuild src/index.js --bundle --format=iife --global-name=LearnosityWProofreader '--footer:js=LearnosityWProofreader=LearnosityWProofreader.default;' --minify --outfile=dist/wproofreader-learnosity.min.js",
+ "test": "node --test --import ./tests/register-stubs.mjs tests/*.test.js",
+ "build": "npm run build:iife && npm run build:esm",
+ "build:iife": "esbuild src/index.js --bundle --format=iife --global-name=LearnosityWProofreader '--footer:js=LearnosityWProofreader=LearnosityWProofreader.LearnosityWProofreader;' --minify --outfile=dist/wproofreader-learnosity.min.js",
+ "build:esm": "esbuild src/index.js --bundle --format=esm --minify --outfile=dist/wproofreader-learnosity.esm.js",
"prepack": "npm run build"
},
+ "dependencies": {
+ "@webspellchecker/wproofreader-sdk-js": "^1.1.2"
+ },
"devDependencies": {
"esbuild": "^0.23.0"
},
diff --git a/src/config.js b/src/config.js
new file mode 100644
index 0000000..d7e1e4d
--- /dev/null
+++ b/src/config.js
@@ -0,0 +1,65 @@
+/**
+ * Pure configuration logic for the connector: framework- and DOM-agnostic
+ * enough to unit-test in isolation. Nothing here loads scripts or touches the
+ * WProofreader SDK.
+ */
+
+// Surfaces the connector recognises, and the `window` global Learnosity
+// exposes for each (used for the page-level gate and per-editor detection).
+export const ALLOWED_SURFACES = ['questions', 'items', 'author'];
+export const SURFACE_GLOBALS = {
+ questions: 'LearnosityApp',
+ items: 'LearnosityItems',
+ author: 'LearnosityAuthor'
+};
+// Author only by default: Questions and Items render student-facing
+// assessments where spell-check can compromise the activity. Host apps opt in
+// explicitly with `surfaces: ['questions', 'items', 'author']`.
+export const DEFAULT_SURFACES = ['author'];
+
+// Fixed partner tag so WebSpellChecker can attribute traffic to this plugin
+// (cf. the CKEditor 5 plugin's `proofreader_ck5`). Not caller-configurable.
+export const APP_TYPE = 'wpr_learnosity';
+
+/*
+ * Split options into the WProofreader block (forwarded to the SDK's init()
+ * unchanged; spread then default so unknown/self-hosted options — including the
+ * SDK's own `srcUrl` for the bundle location — flow through while our defaults
+ * apply only when unset) and connector-only fields. `appType` is pinned to the
+ * fixed partner tag (set after the spread) so a caller cannot override it.
+ */
+export function buildConfig(options) {
+ const wp = options.wproofreader || {};
+ return {
+ wproofreader: {
+ ...wp,
+ enableGrammar: wp.enableGrammar !== false,
+ autocorrect: wp.autocorrect !== false,
+ lang: wp.lang || defaultLang(),
+ appType: APP_TYPE
+ },
+ surfaces: normalizeSurfaces(options.surfaces),
+ enableShorttext: options.enableShorttext === true,
+ customSelectors: Array.isArray(options.customSelectors) ? options.customSelectors.slice() : []
+ };
+}
+
+/*
+ * Default language from the host page's ``, with `-` normalised to
+ * `_` (e.g. `en-US` → `en_US`). WProofreader rejects bare codes like `en`, so
+ * we fall back to `en_US` unless the page lang is region-qualified.
+ */
+export function defaultLang() {
+ if (typeof document === 'undefined') return 'en_US';
+ const html = document.documentElement;
+ if (!html || !html.lang) return 'en_US';
+ const lang = html.lang;
+ if (lang.indexOf('-') === -1 && lang.indexOf('_') === -1) return 'en_US';
+ return lang.replace('-', '_');
+}
+
+// Valid subset of ALLOWED_SURFACES; non-arrays fall back to the default.
+export function normalizeSurfaces(input) {
+ if (!Array.isArray(input)) return DEFAULT_SURFACES.slice();
+ return input.filter(function (s) { return ALLOWED_SURFACES.indexOf(s) !== -1; });
+}
diff --git a/src/index.d.ts b/src/index.d.ts
index 63d2d01..5e9a1fe 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -3,104 +3,32 @@
* Hand-written to match src/index.js. Keep in sync.
*/
-/**
- * Learnosity API surfaces the connector knows how to scope to. Each editor's
- * ancestry is walked for surface-specific markers; editors whose surface is
- * not in the allowed list are skipped.
- */
export type Surface = 'questions' | 'items' | 'author';
-/**
- * Runtime options forwarded to WProofreader unchanged. The connector does
- * not enforce any schema beyond `serviceId` / `serviceHost` activation, so
- * any other WProofreader option may also be set.
- */
export interface WProofreaderOptions {
- /**
- * WProofreader Cloud service ID. Domain-locked server-side. Required for
- * the Cloud path. Omit when using a self-hosted deployment.
- */
serviceId?: string;
-
- /** Self-hosted: protocol. */
serviceProtocol?: 'http' | 'https';
-
- /**
- * Self-hosted: WProofreader server hostname. Presence of `serviceHost` (or
- * `serviceId`) activates `init()`.
- */
serviceHost?: string;
-
- /** Self-hosted: port. */
servicePort?: number | string;
-
- /** Self-hosted: URL path to the WProofreader API (e.g. `/wscservice/api`). */
servicePath?: string;
-
- /** Default `true`. */
enableGrammar?: boolean;
-
- /** Default `true`. */
autocorrect?: boolean;
-
- /**
- * Default: the host page's `` attribute, with `-` normalised to
- * `_` (e.g. `en-US` becomes `en_US`). Falls back to `en_US` if the page
- * does not set a `lang` or sets a bare un-regioned one (WProofreader
- * requires region-qualified codes).
- */
lang?: string;
-
- /**
- * Forward-compatible escape hatch: any other property is passed through to
- * WProofreader's runtime config unchanged. Useful for WProofreader options
- * the connector does not enumerate explicitly.
- */
+ /** SDK option: WProofreader bundle URL, for self-hosted or non-default CDN deployments. */
+ srcUrl?: string;
+ // Note: `appType` is set by the plugin to a fixed partner tag and is not caller-configurable.
[other: string]: unknown;
}
export interface InitOptions {
- /** WProofreader runtime options. Forwarded to WProofreader unchanged. */
wproofreader?: WProofreaderOptions;
-
- /**
- * Subset of `['questions','items','author']`. Default `['author']` for
- * assessment integrity. The connector walks each editor's ancestry to
- * detect which Learnosity API placed it (`lrn-author*` → author,
- * `lrn_player*` → items, otherwise → questions) and skips editors whose
- * surface is not in the allowed list. Opt in to assessment proofreading
- * explicitly with `['questions', 'items', 'author']`.
- */
surfaces?: ReadonlyArray;
-
- /**
- * Default `false`. Also attach to `shorttext` inputs (``)
- * inside Learnosity scope.
- */
enableShorttext?: boolean;
-
- /**
- * Extra ancestor CSS selectors that count as "inside Learnosity scope" for
- * unusual host apps. Each entry is matched via `element.matches`.
- */
customSelectors?: ReadonlyArray;
-
- /** Override the WProofreader bundle URL for non-default CDNs or self-hosted servers. */
- bundleUrl?: string;
}
-/**
- * Public API surface. The default export is the namespace object; the call
- * site mirrors that shape.
- */
-export interface LearnosityWProofreader {
- /**
- * Initialise the connector. Should be called before Learnosity widgets
- * render. Safe to call multiple times: subsequent calls update the live
- * configuration without reloading the WProofreader bundle.
- */
- init(options?: InitOptions): void;
+export declare class LearnosityWProofreader {
+ static init(options?: InitOptions): void;
}
-declare const LearnosityWProofreader: LearnosityWProofreader;
export default LearnosityWProofreader;
diff --git a/src/index.js b/src/index.js
index 852f08b..ce4b826 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,502 +1,231 @@
/**
* WProofreader connector for Learnosity.
*
- * Targeted attachment to Learnosity-rendered editables, scoped by an ancestry
- * walk for any `lrn`-prefixed class plus optional caller-supplied selectors.
- * Per-editor `surfaces` filter (default `['author']`) covers assessment
- * integrity by excluding Questions and Items by default; the host app opts in
- * explicitly. Cleans up WProofreader instances when their host elements are
- * removed from the DOM (single-page apps that rebuild Learnosity widgets do
- * not leak).
+ * Attaches WProofreader to Learnosity-rendered editables only, scoped by an
+ * ancestry walk for `lrn`-prefixed classes (plus optional caller selectors),
+ * and filtered per editor by Learnosity surface. Bundle loading and instance
+ * creation are delegated to the WProofreader SDK; this class only decides which
+ * editors to attach to and cleans them up when they leave the DOM.
*
- * Usage in a host app:
- *
- * import LearnosityWProofreader from '@webspellchecker/wproofreader-learnosity';
- *
- * LearnosityWProofreader.init({
- * wproofreader: { serviceId: 'YOUR_WPROOFREADER_SERVICE_ID' }
- * });
- *
- * LearnosityApp.init(signedRequest, callbacks);
- */
-
-const DEFAULT_BUNDLE_URL =
- 'https://svc.webspellchecker.net/spellcheck31/wscbundle/wscbundle.js';
-
-/*
- * Surfaces the connector recognises. `SURFACE_GLOBALS` maps each to the
- * `window` global Learnosity exposes for that API. Used in two places:
- * - `isAnyAllowedSurfaceLoaded`: cheap page-level gate; skip everything if
- * no allowed-surface global has loaded yet.
- * - `detectSurface`: per-editor classification via DOM-ancestor markers,
- * so multi-API pages are filtered correctly.
- */
-const ALLOWED_SURFACES = ['questions', 'items', 'author'];
-const SURFACE_GLOBALS = {
- questions: 'LearnosityApp',
- items: 'LearnosityItems',
- author: 'LearnosityAuthor'
-};
-/*
- * Default surfaces. Author only, because Questions and Items render
- * student-facing assessments where spell-check can compromise the activity
- * itself. Host apps that want proofreading in those contexts opt in
- * explicitly with `surfaces: ['questions', 'items', 'author']`.
+ * import { LearnosityWProofreader } from '@webspellchecker/wproofreader-learnosity';
+ * LearnosityWProofreader.init({ wproofreader: { serviceId: '...' } });
*/
-const DEFAULT_SURFACES = ['author'];
-/*
- * Maximum wait for `window.WEBSPELLCHECKER` to appear after the bundle
- * `