@atlaskit/editor-plugin-media
Version:
Media plugin for @atlaskit/editor-core
170 lines (156 loc) • 7.45 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
import FeatureGates from '@atlaskit/feature-gate-js-client';
/**
* ProseMirror plugin that manages AI-generating decorations on media nodes.
*
* Instead of storing transient `__isAIGenerating` attributes in the ADF schema
* (which pollutes the document model and undo history), this plugin uses
* ProseMirror decorations — a view-layer-only mechanism that never affects the
* document content, serialization, or undo/redo stack.
*/
// ── Plugin Key ──────────────────────────────────────────────────────────────
export var aiGeneratingDecorationPluginKey = new PluginKey('aiGeneratingDecoration');
// ── Types ───────────────────────────────────────────────────────────────────
var AI_GENERATING_DECORATION_TYPE = 'ai-generating';
// ── Helpers ─────────────────────────────────────────────────────────────────
/**
* Build a DecorationSet containing a Decoration.node for every media node
* whose `id` is in the given set.
*/
function buildDecorationSet(doc, mediaIds) {
if (mediaIds.size === 0) {
return DecorationSet.empty;
}
var decorations = [];
doc.descendants(function (node, pos) {
if (node.type.name === 'media' && mediaIds.has(node.attrs.id)) {
decorations.push(Decoration.node(pos, pos + node.nodeSize, {},
// no DOM attrs needed — the NodeView reads the decoration spec
{
type: AI_GENERATING_DECORATION_TYPE,
mediaId: node.attrs.id
}));
}
});
return DecorationSet.create(doc, decorations);
}
// ── Public utilities ────────────────────────────────────────────────────────
/**
* Returns `true` if the given decorations array contains an AI-generating
* decoration. Call this from a NodeView's `update()` / `viewShouldUpdate()`
* to determine whether to render the AI border.
*/
export function hasAIGeneratingDecoration(decorations) {
return decorations.some(function (d) {
return d.spec.type === AI_GENERATING_DECORATION_TYPE;
});
}
/**
* Dispatch a transaction that sets the AI-generating decoration on a media
* node identified by `mediaId`.
*
* Usage from the editor bridge:
* ```
* editorAPI.core.actions.execute(({ tr }) =>
* setAIGeneratingMeta(tr, mediaId),
* );
* ```
*/
export function setAIGeneratingMeta(tr, mediaId) {
return tr.setMeta(aiGeneratingDecorationPluginKey, {
type: 'SET_GENERATING',
mediaId: mediaId
}).setMeta('addToHistory', false);
}
/**
* Dispatch a transaction that clears the AI-generating decoration for a
* specific media node.
*/
export function clearAIGeneratingMeta(tr, mediaId) {
return tr.setMeta(aiGeneratingDecorationPluginKey, {
type: 'CLEAR_GENERATING',
mediaId: mediaId
}).setMeta('addToHistory', false);
}
// ── Plugin ──────────────────────────────────────────────────────────────────
/** Creates the ProseMirror plugin that manages AI-generating decorations on media nodes. */
export function createAIGeneratingDecorationPlugin() {
return new SafePlugin({
key: aiGeneratingDecorationPluginKey,
state: {
init: function init() {
return {
generatingMediaIds: new Set(),
decorationSet: DecorationSet.empty
};
},
apply: function apply(tr, pluginState, _oldState, newState) {
// Killswitch — if active, clear any existing decorations and stop
// eslint-disable-next-line @atlaskit/platform/use-recommended-utils -- dynamic config killswitch, not a standard feature gate
if (FeatureGates.getExperimentValue('maui_ai_border_killswitch', 'value', false)) {
if (pluginState.generatingMediaIds.size > 0) {
return {
generatingMediaIds: new Set(),
decorationSet: DecorationSet.empty
};
}
return pluginState;
}
var meta = tr.getMeta(aiGeneratingDecorationPluginKey);
if (meta) {
switch (meta.type) {
case 'SET_GENERATING':
{
var ids = new Set(pluginState.generatingMediaIds);
ids.add(meta.mediaId);
return {
generatingMediaIds: ids,
decorationSet: buildDecorationSet(newState.doc, ids)
};
}
case 'CLEAR_GENERATING':
{
var _ids = new Set(pluginState.generatingMediaIds);
_ids.delete(meta.mediaId);
return {
generatingMediaIds: _ids,
decorationSet: buildDecorationSet(newState.doc, _ids)
};
}
case 'CLEAR_ALL':
return {
generatingMediaIds: new Set(),
decorationSet: DecorationSet.empty
};
}
}
// Map decorations through document changes so positions stay in sync
if (tr.docChanged && pluginState.decorationSet !== DecorationSet.empty) {
try {
return _objectSpread(_objectSpread({}, pluginState), {}, {
decorationSet: pluginState.decorationSet.map(tr.mapping, newState.doc)
});
} catch (_unused) {
// Collaborative editing edge case — reset
return {
generatingMediaIds: new Set(),
decorationSet: DecorationSet.empty
};
}
}
return pluginState;
}
},
props: {
decorations: function decorations(state) {
var _aiGeneratingDecorati, _aiGeneratingDecorati2;
return (_aiGeneratingDecorati = (_aiGeneratingDecorati2 = aiGeneratingDecorationPluginKey.getState(state)) === null || _aiGeneratingDecorati2 === void 0 ? void 0 : _aiGeneratingDecorati2.decorationSet) !== null && _aiGeneratingDecorati !== void 0 ? _aiGeneratingDecorati : DecorationSet.empty;
}
}
});
}