Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
65 changes: 22 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -15,39 +13,43 @@ 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 `<script>` tag (non-Node host apps)

For host apps where adding a bundler is overkill (classic HTML, PHP, Python, Java, ASP.NET, Ruby), load the connector from jsDelivr:
For host apps where adding a bundler is overkill (classic HTML, PHP, Python, Java, ASP.NET, Ruby), load the plugin from jsDelivr:

```html
<script src="https://cdn.jsdelivr.net/npm/@webspellchecker/wproofreader-learnosity@1.2.1/dist/wproofreader-learnosity.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@webspellchecker/wproofreader-learnosity@2.0.0/dist/wproofreader-learnosity.min.js"></script>
<script>
LearnosityWProofreader.init({
wproofreader: { serviceId: 'YOUR_WPROOFREADER_SERVICE_ID' }
});
</script>
```

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); }
Expand Down Expand Up @@ -76,17 +78,17 @@ 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'
}
});
```

Either `wproofreader.serviceId` or `wproofreader.serviceHost` must be set; otherwise `init()` no-ops with a console warning.

## 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`)

Expand All @@ -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 `<html lang>`, or `en_US`| Read from `<html lang>` 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 (`<input type="text">`) 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']
});
```
Expand All @@ -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**: <https://webspellchecker.com/wsc-proofreader/>
- **Configuration reference** (every WProofreader option beyond what this connector documents): <https://webspellchecker.com/docs/api/wscbundle/Options.html>
- **WProofreader SDK source** (the underlying library the connector wraps): <https://github.com/WebSpellChecker/wproofreader-sdk-js>
- **Configuration reference** (every WProofreader option beyond what this plugin documents): <https://webspellchecker.com/docs/api/wscbundle/Options.html>
- **Self-hosted server image**: <https://hub.docker.com/r/webspellchecker/wproofreader>
- **Contact and commercial enquiries**: <https://webspellchecker.com/contact-us/>

## Tests

```bash
npm install
npm test
```

`npm test` runs the unit suite under `node --test`.

## License

Apache-2.0. See [LICENSE](./LICENSE).
13 changes: 11 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 9 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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"
},
Expand Down
65 changes: 65 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -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 `<html lang>`, 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; });
}
Loading
Loading