Position floating sidenotes/comments next to a document with inline references.

<article> ID<InlineAnchor>; InlineAnchor is a span<AnchorBase>; AnchorBase is a divrelative, fixed or absolute positioning.The demo is pretty basic, and not nearly as pretty as the gif above, just blue, green and red divs floating around. See index.tsx for full the code/setup.
yarn install
yarn start

yarn add sidenotes
<article id="{docId}" onClick="{deselect}">
<AnchorBase anchor="{baseId}">
Content with <InlineAnchor sidenote="{sidenoteId}">inline reference</InlineAnchor>
</AnchorBase>
<Sidenote sidenote="{sidenoteId}" base="{baseId}"> Your custom UI, e.g. a comment </Sidenote>
</article>
The sidenotes class is the only CSS that is recommended. You can import it directly, or look at it and change it (~30 lines of scss). To import from javascript (assuming your bundler works with CSS):
import 'sidenotes/dist/sidenotes.css';
You can also use sidenotes from vanilla javascript, this is done by first connecting the ID.
// First dispatch the action to connect to any ID in the dom
store.dispatch(actions.connectAnchor(docId, sidenoteId, anchorId));
// Then setup your handlers to select that anchor on click
<span
id={anchorId}
onClickCapture={(event) => {
event.stopPropagation();
store.dispatch(actions.selectAnchor(docId, anchorId));
}}
>
Select a Sidenote with JavaScript! 🚀
</span>;
// To clean up later, disconnect the anchor
store.dispatch(actions.disconnectAnchor(docId, anchorId));
Once you create your own store, add a sidenotes.reducer, it must be called sidenotes. Then pass the store to setup with options of padding between sidenotes.
import { combineReducers, applyMiddleware, createStore } from 'redux';
import thunkMiddleware from 'redux-thunk';
import * as sidenotes from 'sidenotes';
const reducer = combineReducers({
yourStuff: yourReducers,
sidenotes: sidenotes.reducer, // Add this to your reducers
});
// Create your store as normal, must have thunkMiddleware
const store = createStore(reducer, applyMiddleware(thunkMiddleware));
// Then ensure that you pass the `store` to setup the sidenotes
sidenotes.setup(store as sidenotes.Store, { padding: 10 })
The sidenotes.ui state has the following structure:
sidenotes:
ui:
docs:
[docId]:
anchors:
[anchorId]: { id: string, sidenote: string, element: [span] }
sidenotes:
[sidenoteId]: { inlineAnchors: string[], top: number, id: string, baseAnchors: string[] }
id: string
selectedAnchor: string
selectedNote: string
It is common to put a click handler on the body (or similar) to deselect any sidenotes. This can be difficult to stop in some cases, but can be anticipated with a onClickCapture that fires the
disableNextDeselectSidenote action. This intercepts the redux action and stops it from happening for one time.
$ claude mcp add sidenotes \
-- python -m otcore.mcp_server <graph>