Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9f9f60b
feat: add blockly-workspace-notes plugin
preetvadaliya Sep 8, 2026
e21dae4
refactor: split src into layered modules
preetvadaliya Sep 8, 2026
7f997b9
style: pin prettier options and hold every line to 80 columns
preetvadaliya Sep 8, 2026
0eb5e94
docs: rewrite the plugin README
preetvadaliya Sep 8, 2026
23fb541
fix: restore Blockly's default comment size on dispose
preetvadaliya Sep 8, 2026
4148255
chore: start the scoped package at 0.1.0
preetvadaliya Sep 8, 2026
271e1c9
fix: align note serialization with Blockly's own contract
preetvadaliya Sep 8, 2026
e9eaa8d
docs: split the documentation into docs/ and redraw the diagrams
preetvadaliya Sep 8, 2026
5c04cfc
feat: mark a pinned note with a pin in its title row
preetvadaliya Sep 8, 2026
85cfd03
feat: rebuild the note to Blockly's comment pattern
preetvadaliya Sep 9, 2026
cc7f7dc
style: tighten the title bar and square up its buttons
preetvadaliya Sep 9, 2026
f79cb28
fix: even the body padding and the selection ring
preetvadaliya Sep 9, 2026
143243a
docs: bring prose and comments in line with the redesign
preetvadaliya Sep 9, 2026
dc69964
feat: show a note's author and last-changed date
preetvadaliya Sep 9, 2026
3a6217b
fix: give note text the workspace font and centre it properly
preetvadaliya Sep 9, 2026
6a9d001
feat: add locked notes
preetvadaliya Sep 9, 2026
823e8b4
docs: show locking in the note anatomy diagram
preetvadaliya Sep 9, 2026
bf249db
refactor: remove the circular import in model/
preetvadaliya Sep 9, 2026
0f44598
refactor: name every number the note draws
preetvadaliya Sep 9, 2026
9f7ffa4
fix: make the diagrams draw what the code draws
preetvadaliya Sep 9, 2026
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
70 changes: 70 additions & 0 deletions .github/workflows/test-workspace-notes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Test blockly-workspace-notes

on:
pull_request:
workflow_dispatch:

jobs:
quality:
name: Lint and format
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install dependencies
working-directory: blockly-workspace-notes
run: npm ci

- name: Lint
working-directory: blockly-workspace-notes
run: npm run lint

- name: Check formatting
working-directory: blockly-workspace-notes
run: npm run format:check

# `build:types`, not `typecheck`: `tsc --noEmit` returns before
# declaration diagnostics run, so it cannot see the TS4xxx errors that
# declaration emit surfaces.
- name: Typecheck
working-directory: blockly-workspace-notes
run: npm run build:types

test-workspace-notes:
name: Test on Node ${{ matrix.node }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# The minimum supported version from package.json engines, plus the
# current LTS releases. Node 18 is excluded deliberately: it is past
# end of life, and jsdom (which blockly/core-node.js pulls in for XML
# handling) now reaches an ES module through require(), which only
# works from Node 20.19 onwards.
node: [20, 22, 24]

steps:
- uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}

- name: Install dependencies
working-directory: blockly-workspace-notes
run: npm ci

- name: Run blockly-workspace-notes tests
working-directory: blockly-workspace-notes
run: npm run test

- name: Build
working-directory: blockly-workspace-notes
run: npm run build
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ Plugins for [Blockly](https://github.com/google/blockly) based on code by
[MIT App Inventor](https://github.com/mit-cml/appinventor-sources)

* [Lexical Variables](./block-lexical-variables)
* [Workspace Notes](./blockly-workspace-notes)
5 changes: 5 additions & 0 deletions blockly-workspace-notes/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build/
dist/
node_modules/
package-lock.json
docs/images/
14 changes: 14 additions & 0 deletions blockly-workspace-notes/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "preserve",
"trailingComma": "all",
"bracketSpacing": false,
"bracketSameLine": false,
"arrowParens": "always",
"proseWrap": "preserve",
"endOfLine": "lf"
}
70 changes: 70 additions & 0 deletions blockly-workspace-notes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Workspace Notes

[![Built on Blockly](https://tinyurl.com/built-on-blockly)](https://github.com/google/blockly)

Sticky notes for a Blockly workspace: draggable, resizable, colour-coded cards
with a title, an author and a stacking order.

<p align="center">
<img src="./docs/images/note-anatomy.svg" width="100%" alt="The parts of a note: title bar, collapse and delete buttons, title, body, author and date, and resize handle; and below it a locked note with a padlock, no delete button and no resize handle" />
</p>

Notes extend Blockly's own workspace comments, so dragging, resizing,
selection, keyboard navigation and undo all work exactly as they already do.
They are saved alongside your blocks, and round-trip through both JSON and XML.

## Install

```bash
npm install @mit-app-inventor/blockly-workspace-notes
```

Needs Blockly 13.2.1 or newer as a peer dependency, and Node 20+ to build.

## Use

```js
import * as Blockly from 'blockly';
import {WorkspaceNotes} from '@mit-app-inventor/blockly-workspace-notes';

const workspace = Blockly.inject('blocklyDiv', {toolbox});
new WorkspaceNotes(workspace).init();
```

That is the whole setup. Right-click the workspace to add a note; click a
note's title to rename it; right-click a note to recolour, pin, collapse or
restack it.

> **Import the plugin before `Blockly.inject`.** Blockly's stylesheets only
> reach workspaces injected after they are registered.

## Documentation

- [Getting started](./docs/getting-started.md) — install, setup and options
- [Using notes](./docs/using-notes.md) — everything you can do with a note
- [API](./docs/api.md) — the exported classes and functions
- [Saving and loading](./docs/saving.md) — the save format, JSON and XML
- [Design](./docs/design.md) — why it works this way, and how the source is
laid out

## Develop

```bash
git clone https://github.com/mit-cml/blockly-plugins.git
cd blockly-plugins/blockly-workspace-notes
npm install
npm start
```

`npm start` opens the playground. Other scripts:

```bash
npm test # typecheck, then the mocha suites
npm run build # bundle and .d.ts into dist/
npm run lint # ESLint
npm run format # Prettier
```

## License

Apache-2.0
11 changes: 11 additions & 0 deletions blockly-workspace-notes/docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Documentation

- **[Getting started](./getting-started.md)** — install it, switch it on, and
the options you can pass.
- **[Using notes](./using-notes.md)** — what a note is and everything you can
do with one.
- **[API](./api.md)** — the classes and functions the package exports.
- **[Saving and loading](./saving.md)** — what a saved note looks like, in JSON
and in XML.
- **[Design](./design.md)** — why a note works the way it does, and how the
source is laid out.
86 changes: 86 additions & 0 deletions blockly-workspace-notes/docs/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# API

Most apps only need `WorkspaceNotes`. The rest is here for apps that drive
saving and loading themselves.

## WorkspaceNotes

```js
const notes = new WorkspaceNotes(workspace, options);
```

| Member | What it does |
| -------------------- | --------------------------------------------------- |
| `init()` | Starts the plugin. Safe to call twice. |
| `dispose()` | Stops it and restores everything Blockly had before |
| `createNote(state?)` | Adds a note, in front, as one undo step |
| `getNotes()` | Every note on the workspace |

```js
notes.createNote({title: 'TODO', text: 'Check the loop', colour: '#f9bbc5'});
notes.getNotes().filter((note) => note.isPinned());
```

## A note

`Note` is a note on a rendered workspace; `NoteComment` is the same thing
headless, which is what you get on a workspace with no renderer. Both carry the
same note-specific methods on top of Blockly's own comment API.

| Method | What it does |
| ------------------------------ | -------------------------------------- |
| `getTitle()` / `setTitle(s)` | The heading |
| `getColour()` / `setColour(c)` | Any CSS colour; stored as hex |
| `isPinned()` / `setPinned(b)` | Held in place and raised to the front |
| `isLocked()` / `setLocked(b)` | Read-only, undeletable, uncopyable |
| `getZIndex()` / `setZIndex(n)` | Stacking order; higher is nearer front |
| `getMeta()` | `{author, createdAt, updatedAt}` |
| `saveNoteState()` | A plain copy of all of the above |

Everything else — `getText`, `setText`, `moveTo`, `setSize`, `setCollapsed`,
`dispose` — is Blockly's, and documented there.

Two things about `setLocked` are worth knowing before you build on it.

**Locking owns `editable` and `deletable` outright.** Unlocking sets both back
to `true`, so if your app had independently made a note read-only and you then
lock and unlock it, that read-only state is gone. Drive one or the other, not
both. Pinning owns `movable` the same way, which is why the two compose
cleanly.

**`canToggleLock` gates the menu, not the model.** `setLocked` always works
from code — undo, paste and loading a file all go through it. Locking states
intent and stops accidents; it is not a security boundary.

## Helpers

| Function | What it does |
| ------------------------- | ------------------------------------------------- |
| `isNote(x)` | Whether a workspace comment is one of ours |
| `restackNotes(workspace)` | Reorders notes on screen to match their z-indices |
| `saveNote(note, opts?)` | One note to JSON |
| `appendNote(state, ws)` | JSON back to a note on the workspace |
| `migrate(payload)` | Upgrades an older payload to the current version |
| `noteToDom(note)` | One note to a `<comment>` element |
| `domToNoteState(elem)` | A `<comment>` element to JSON |
| `domToNote(elem, ws)` | A `<comment>` element to a note on the workspace |

```js
import {isNote, saveNote} from '@mit-app-inventor/blockly-workspace-notes';

const notes = workspace.getTopComments(false).filter(isNote);
const json = notes.map((note) => saveNote(note, {addCoordinates: true}));
```

## Types

`SavedNote`, `NotesPayload`, `NoteState`, `NoteMeta`, `PaletteEntry`,
`WorkspaceNotesOptions`, `NoteCopyData`, `NoteChangeJson`, `NoteProperty`,
`NotePropertyValue` and `NoteSurface` are all exported for TypeScript users.

## For extending the plugin

`NoteSerializer`, `LegacyCommentAdapter`, `NotePaster` and `NoteChange` are
exported so you can subclass or inspect them. `NOTE_SERIALIZER_NAME`,
`SCHEMA_VERSION`, `DEFAULT_COLOUR` and `DEFAULT_PALETTE` are the constants
worth reading.
Loading