An original service-notice editor has a specific publishing rule: the placeholder REPLACE_DESK must be replaced before review. That string is valid HTML text, so a syntax check has no reason to object. The editor should point the author to the unresolved placeholder and remove its warning after the wording is completed.
Give the application rule its own marker owner
This fragment belongs in a configured Monaco application with an existing noticeModel. It finds the literal placeholder anywhere in that source, marks every occurrence, and refreshes after model changes.
import * as monaco from 'monaco-editor';
const markerOwner = 'notice-publishing-rules';
const placeholder = 'REPLACE_DESK';
function validateNotice() {
const text = noticeModel.getValue();
const markers = [];
let from = 0;
while (true) {
const index = text.indexOf(placeholder, from);
if (index === -1) break;
const start = noticeModel.getPositionAt(index);
const end = noticeModel.getPositionAt(
index + placeholder.length
);
markers.push({
severity: monaco.MarkerSeverity.Error,
message: 'Replace the collection-desk placeholder before review.',
source: 'Notice publishing rules',
startLineNumber: start.lineNumber,
startColumn: start.column,
endLineNumber: end.lineNumber,
endColumn: end.column
});
from = index + placeholder.length;
}
monaco.editor.setModelMarkers(
noticeModel, markerOwner, markers
);
}
validateNotice();
const validationSubscription =
noticeModel.onDidChangeContent(validateNotice);
For <p>Collect at REPLACE_DESK.</p>, the marker covers the placeholder, not the whole paragraph. Replacing it with reception causes the next pass to supply an empty array, clearing this owner's markers. If the placeholder appears twice, both ranges are reported.
The rule intentionally scans source text, including comments and attribute values. If a project permits the string in documentation or quoted examples, scope the rule through an appropriate parser instead of silently blocking those uses. This sample is a literal publishing check, not an HTML validator.
Derive positions from the same source you checked
Markers use one-based line and column coordinates. getPositionAt() converts an offset in the model's source to that coordinate system, avoiding manual newline counting. The marker data contract requires a severity, message, and range.
This synchronous check reads and marks one current model without waiting for another operation. For a remote or asynchronous validator, capture the model identity, getVersionId(), and source with the request. Before applying the response, confirm that the model still exists and its current version matches that request. Otherwise a late error can underline the wrong words after the author edits them.
A validator using byte offsets must convert them to the model's string offsets before creating positions. Non-ASCII text can make byte counts and JavaScript string positions differ. The text model reference documents model versions and position conversion.
Keep language diagnostics and publishing rules separate
owner identifies this set of markers for this model. Keep it stable for this feature. Updating it with the current result replaces that owner's set; inventing a new owner for each edit makes cleanup harder. Use the empty array with the same owner to remove this feature's findings while leaving other marker owners alone.
If no diagnostic decoration is visible, check the actual attached model and the editor's validation-decoration settings. Inspect the returned marker state separately from the displayed squiggle. If the problem is missing HTML language support, use the Monaco HTML setup guide to inspect worker loading instead.
An error marker communicates a problem. It does not itself disable Save, reject a server request, or enforce a publishing policy. The application must check the actual submitted source under the authoritative review rules, with a useful explanation if it cannot proceed.
Clear resources when this feature closes
For this model-specific feature, stop its listener and remove its markers before releasing its model:
validationSubscription.dispose();
monaco.editor.setModelMarkers(noticeModel, markerOwner, []);
Dispose the model only when its actual owner closes it and no other view still needs it. In the running notice editor, check one placeholder, two placeholders, a correction, and reopening a different document. The final reader preview should contain the real collection desk, while any unrelated language diagnostics remain available to the author.