|
@@ -17,10 +17,11 @@
|
|
|
// selection-quoting, and the goal agent's runs. Every long-form
|
|
// selection-quoting, and the goal agent's runs. Every long-form
|
|
|
// body (brief, plans, reports, blocks, comments, events) renders
|
|
// body (brief, plans, reports, blocks, comments, events) renders
|
|
|
// as markdown via stm-markdown. The plan review collects REMARKS
|
|
// as markdown via stm-markdown. The plan review collects REMARKS
|
|
|
- // (plan 14 B2): "Add remark" appends to a per-block queue that
|
|
|
|
|
- // renders at the bottom of the plan, persists across pulse
|
|
|
|
|
- // refreshes (the maps live outside statum re-renders), rides the
|
|
|
|
|
- // rejection numbered, and clears on accept. The status strip's
|
|
|
|
|
|
|
+ // through the shared initPlanReviews engine (main.html, plan 16
|
|
|
|
|
+ // R3/arch 15): a per-block queue that renders at the bottom of
|
|
|
|
|
+ // the plan, persists across pulse refreshes (the maps live
|
|
|
|
|
+ // outside statum re-renders), rides the rejection numbered, and
|
|
|
|
|
+ // clears on accept. The status strip's
|
|
|
// filter lives client-side in window.goalTaskFilter; clicking a
|
|
// filter lives client-side in window.goalTaskFilter; clicking a
|
|
|
// strip button re-runs the sealed refresh action, and the filter
|
|
// strip button re-runs the sealed refresh action, and the filter
|
|
|
// rides the re-render.
|
|
// rides the re-render.
|
|
@@ -78,167 +79,11 @@
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Plan 14 B2: the plan-review remarks queue — the
|
|
|
|
|
- // task-page collect-remarks pattern (the feedbackByBlock
|
|
|
|
|
- // maps) ported to the goal's plan review. The queue lives
|
|
|
|
|
- // in these maps (keyed by goal block id) so statum
|
|
|
|
|
- // re-renders never lose it; the DOM list is rebuilt after
|
|
|
|
|
- // every render cycle.
|
|
|
|
|
- var remarksByBlock = {};
|
|
|
|
|
- var rejectOpenByBlock = {};
|
|
|
|
|
- var remarkPopover = null;
|
|
|
|
|
-
|
|
|
|
|
- statum.afterEvaluate(syncPlanRemarks);
|
|
|
|
|
-
|
|
|
|
|
- document.addEventListener('click', function (event) {
|
|
|
|
|
- if (!event.target.closest) return;
|
|
|
|
|
- var accept = event.target.closest('[data-plan-accept]');
|
|
|
|
|
- if (accept) {
|
|
|
|
|
- // Accept ignores/clears the queued remarks.
|
|
|
|
|
- var accepted = accept.closest('[data-plan-review]');
|
|
|
|
|
- if (accepted) {
|
|
|
|
|
- var acceptId = accepted.getAttribute('data-block-id');
|
|
|
|
|
- remarksByBlock[acceptId] = [];
|
|
|
|
|
- rejectOpenByBlock[acceptId] = false;
|
|
|
|
|
- }
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- var toggle = event.target.closest('[data-plan-toggle]');
|
|
|
|
|
- if (toggle) {
|
|
|
|
|
- hideRemarkPopover();
|
|
|
|
|
- var review = toggle.closest('[data-plan-review]');
|
|
|
|
|
- var id = review && review.getAttribute('data-block-id');
|
|
|
|
|
- if (id) rejectOpenByBlock[id] = !rejectOpenByBlock[id];
|
|
|
|
|
- syncPlanRemarks();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- var submit = event.target.closest('[data-plan-submit]');
|
|
|
|
|
- if (submit) {
|
|
|
|
|
- submitPlanRemarks(submit.closest('[data-plan-review]'));
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- var remove = event.target.closest('[data-plan-remove]');
|
|
|
|
|
- if (remove) {
|
|
|
|
|
- var entry = remove.closest('[data-plan-remark-id]');
|
|
|
|
|
- var blockId = remove.closest('[data-plan-review]').getAttribute('data-block-id');
|
|
|
|
|
- var list = remarksByBlock[blockId] || [];
|
|
|
|
|
- var index = parseInt(entry.getAttribute('data-plan-remark-id'), 10);
|
|
|
|
|
- if (!isNaN(index) && list[index]) list.splice(index, 1);
|
|
|
|
|
- remarksByBlock[blockId] = list;
|
|
|
|
|
- syncPlanRemarks();
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- document.addEventListener('mouseup', function (event) {
|
|
|
|
|
- if (!event.target.closest) return;
|
|
|
|
|
- if (remarkPopover && remarkPopover.contains(event.target)) return;
|
|
|
|
|
- var body = event.target.closest('[data-plan-body]');
|
|
|
|
|
- if (!body) {
|
|
|
|
|
- hideRemarkPopover();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- var review = body.closest('[data-plan-review]');
|
|
|
|
|
- if (!review || review.getAttribute('data-plan-open') !== '1') return;
|
|
|
|
|
- var selection = window.getSelection();
|
|
|
|
|
- var text = selection ? String(selection.toString()).trim() : '';
|
|
|
|
|
- if (text.length === 0) {
|
|
|
|
|
- hideRemarkPopover();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- showRemarkPopover(event.pageX, event.pageY, text, review);
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- document.addEventListener('mousedown', function (event) {
|
|
|
|
|
- if (remarkPopover && event.target.closest
|
|
|
|
|
- && event.target.closest('.plan-popover')) return;
|
|
|
|
|
- hideRemarkPopover();
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- function showRemarkPopover(x, y, quote, review) {
|
|
|
|
|
- hideRemarkPopover();
|
|
|
|
|
- remarkPopover = document.createElement('div');
|
|
|
|
|
- remarkPopover.className = 'plan-popover';
|
|
|
|
|
- remarkPopover.style.left = Math.min(x, window.innerWidth - 140) + 'px';
|
|
|
|
|
- remarkPopover.style.top = y + 'px';
|
|
|
|
|
-
|
|
|
|
|
- var quoted = document.createElement('blockquote');
|
|
|
|
|
- quoted.textContent = quote;
|
|
|
|
|
- remarkPopover.appendChild(quoted);
|
|
|
|
|
-
|
|
|
|
|
- var body = document.createElement('textarea');
|
|
|
|
|
- body.placeholder = 'Your remark on the selected plan text';
|
|
|
|
|
- remarkPopover.appendChild(body);
|
|
|
|
|
-
|
|
|
|
|
- var actions = document.createElement('div');
|
|
|
|
|
- actions.className = 'actions';
|
|
|
|
|
- var add = document.createElement('button');
|
|
|
|
|
- add.type = 'button';
|
|
|
|
|
- add.className = 'primary';
|
|
|
|
|
- add.textContent = 'Add remark';
|
|
|
|
|
- add.addEventListener('click', function () {
|
|
|
|
|
- if (body.value.trim().length === 0) return;
|
|
|
|
|
- var id = review.getAttribute('data-block-id');
|
|
|
|
|
- var list = remarksByBlock[id] || [];
|
|
|
|
|
- list.push({ quote: quote, body: body.value.trim() });
|
|
|
|
|
- remarksByBlock[id] = list;
|
|
|
|
|
- hideRemarkPopover();
|
|
|
|
|
- syncPlanRemarks();
|
|
|
|
|
- });
|
|
|
|
|
- var cancel = document.createElement('button');
|
|
|
|
|
- cancel.type = 'button';
|
|
|
|
|
- cancel.textContent = 'Cancel';
|
|
|
|
|
- cancel.addEventListener('click', hideRemarkPopover);
|
|
|
|
|
- actions.appendChild(add);
|
|
|
|
|
- actions.appendChild(cancel);
|
|
|
|
|
- remarkPopover.appendChild(actions);
|
|
|
|
|
-
|
|
|
|
|
- document.body.appendChild(remarkPopover);
|
|
|
|
|
- body.focus();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function hideRemarkPopover() {
|
|
|
|
|
- if (remarkPopover) {
|
|
|
|
|
- remarkPopover.remove();
|
|
|
|
|
- remarkPopover = null;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function submitPlanRemarks(review) {
|
|
|
|
|
- if (!review) return;
|
|
|
|
|
- var id = review.getAttribute('data-block-id');
|
|
|
|
|
- var list = remarksByBlock[id] || [];
|
|
|
|
|
- var extra = review.querySelector('[data-plan-feedback] textarea[name="feedback"]');
|
|
|
|
|
- var extraText = extra && extra.value.trim().length > 0 ? extra.value.trim() : '';
|
|
|
|
|
- // Remark-less rejection is allowed when the feedback
|
|
|
|
|
- // textarea carries the review's substance — one of the
|
|
|
|
|
- // two must be non-empty (plan 16 S2).
|
|
|
|
|
- if (list.length === 0 && extraText.length === 0) {
|
|
|
|
|
- if (window.showToast) window.showToast('Highlight some text and add a remark, or write feedback first', 'error');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- var block = resolveReviewBlock(id);
|
|
|
|
|
- if (!block || !block.answer_action) return;
|
|
|
|
|
- // The rejection body rides `feedback` and the SERVER
|
|
|
|
|
- // wraps it with the per-kind stable prefix (plan 16
|
|
|
|
|
- // S2/R4 — the template carries no verdict constants,
|
|
|
|
|
- // any [data-plan-review] container resolves its own
|
|
|
|
|
- // block). The body keeps the historical shape: every
|
|
|
|
|
- // queued remark NUMBERED in queue order, each with its
|
|
|
|
|
- // selection quote prefixed like a comment, then the
|
|
|
|
|
- // free-text feedback.
|
|
|
|
|
- var feedback = list.map(function (item, index) {
|
|
|
|
|
- return (item.quote
|
|
|
|
|
- ? '> ' + item.quote.replace(/\n/g, '\n> ') + '\n\n'
|
|
|
|
|
- : '') + (index + 1) + '. ' + item.body;
|
|
|
|
|
- }).join('\n\n');
|
|
|
|
|
- if (extraText.length > 0) {
|
|
|
|
|
- feedback += (feedback.length > 0 ? '\n\n' : '') + extraText;
|
|
|
|
|
- }
|
|
|
|
|
- remarksByBlock[id] = [];
|
|
|
|
|
- rejectOpenByBlock[id] = false;
|
|
|
|
|
- statum.act(block.answer_action, { feedback: feedback }).catch(function () { });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
+ // The goal's plan-review remarks ride the SHARED
|
|
|
|
|
+ // engine (main.html's initPlanReviews, plan 16 R3/arch
|
|
|
|
|
+ // 15 — the duplicated ~180-line collect-remarks pattern
|
|
|
|
|
+ // now lives once); only the block resolution is page
|
|
|
|
|
+ // state.
|
|
|
// Plan 16 S2: the block for a review container resolves
|
|
// Plan 16 S2: the block for a review container resolves
|
|
|
// from the ACTUAL [data-plan-review] element — the pending
|
|
// from the ACTUAL [data-plan-review] element — the pending
|
|
|
// completion review renders as an attention block (NOT the
|
|
// completion review renders as an attention block (NOT the
|
|
@@ -259,40 +104,12 @@
|
|
|
return block;
|
|
return block;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- function syncPlanRemarks() {
|
|
|
|
|
- document.querySelectorAll('[data-plan-review]').forEach(function (review) {
|
|
|
|
|
- var id = review.getAttribute('data-block-id');
|
|
|
|
|
- var open = rejectOpenByBlock[id] ? '1' : '0';
|
|
|
|
|
- review.setAttribute('data-plan-open', open);
|
|
|
|
|
- var feedback = review.querySelector('[data-plan-feedback]');
|
|
|
|
|
- if (feedback) {
|
|
|
|
|
- if (rejectOpenByBlock[id]) feedback.removeAttribute('hidden');
|
|
|
|
|
- else feedback.setAttribute('hidden', '');
|
|
|
|
|
- }
|
|
|
|
|
- var list = review.querySelector('[data-plan-remarks]');
|
|
|
|
|
- if (!list) return;
|
|
|
|
|
- while (list.firstChild) list.removeChild(list.firstChild);
|
|
|
|
|
- (remarksByBlock[id] || []).forEach(function (item, index) {
|
|
|
|
|
- var entry = document.createElement('li');
|
|
|
|
|
- entry.setAttribute('data-plan-remark-id', String(index));
|
|
|
|
|
- if (item.quote) {
|
|
|
|
|
- var quoted = document.createElement('blockquote');
|
|
|
|
|
- quoted.textContent = item.quote;
|
|
|
|
|
- entry.appendChild(quoted);
|
|
|
|
|
- }
|
|
|
|
|
- var body = document.createElement('div');
|
|
|
|
|
- body.textContent = item.body;
|
|
|
|
|
- entry.appendChild(body);
|
|
|
|
|
- var remove = document.createElement('button');
|
|
|
|
|
- remove.type = 'button';
|
|
|
|
|
- remove.className = 'inline';
|
|
|
|
|
- remove.setAttribute('data-plan-remove', '');
|
|
|
|
|
- remove.textContent = 'Remove';
|
|
|
|
|
- entry.appendChild(remove);
|
|
|
|
|
- list.appendChild(entry);
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ if (window.initPlanReviews) {
|
|
|
|
|
+ window.initPlanReviews({
|
|
|
|
|
+ resolveBlock: function (id) { return resolveReviewBlock(id); }
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
})();
|
|
})();
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|