Feature Request: Microsoft Loop integration
Category
New Integration (website/platform support)
Problem or Use Case
ExtractMD currently has no dedicated integration for Microsoft Loop pages. Loop content is rendered inside a rich canvas editor (Scriptor/Fluid) with many component types that generic HTML-to-Markdown conversion handles poorly or not at all: nested Fluid tables with typed columns, voting widgets, person/label pills, @mentions, Mermaid diagrams, collapsible sections, and checklist state.
Users working in Loop (standalone, Teams, SharePoint embeds, or M365 Copilot page views) need one-click extraction that preserves document structure and component semantics as clean, LLM-friendly Markdown.
A reference HTML dump covering all Loop component types was analyzed to define selectors, output shapes, and settings below. (Internal fixture only — not attached to avoid PII.)
Proposed Solution
Add a Loop site handler (alongside YouTube, HN, X, Articles) that:
- Detects Loop surfaces via URL patterns and/or DOM signals (
scriptor-pageFrame, data-testid Fluid/Loop markers, fluid/prod/loop-app assets).
- Scopes extraction to the Loop canvas root, excluding Copilot/Teams chrome, table-of-contents UI, attribution floaties, and editor chrome.
- Walks components in document order and emits Markdown using the rules in Component extraction spec below.
- Registers a small set of Loop settings in
defaults.js, options UI, and KPI type loop (keep the integration simple — most behavior is fixed).
Supported hosts (detection)
| Host pattern |
Notes |
https://*.loop.microsoft.com/* |
Standalone Loop |
https://teams.microsoft.com/* |
Loop components in Teams |
https://*.sharepoint.com/* + Fluid Loop embed |
Loop page / component in SharePoint |
| M365 Copilot page views embedding Loop canvas |
e.g. pages with scriptor-pageFrame + loop-app scripts |
Detection should prefer the inner Loop canvas (scriptor-pageFrame / main content region), not the surrounding Copilot shell.
DOM anchors (from reference page)
| Purpose |
Selector / signal |
| Canvas root |
.scriptor-pageFrame, .scriptor-webView |
| Block quote |
[data-testid="block-quote-component"] |
| Callout |
[data-testid="block-callout-component"] |
| Code block |
.scriptor-component-code-block, [aria-label="Code editor"] |
| Headings |
[role="heading"][aria-level="1|2|3"] |
| Lists |
.scriptor-listItem, .scriptor-listItem-marker-bullet, .scriptor-listItem-marker-checkbox |
| Hyperlinks |
.scriptor-hyperlink |
| Images |
.scriptor-image, .scriptor-image-element |
| Highlights / notes |
.scriptor-highlightWrapper, .scriptor-svgHighlight |
| Collapsible section |
.scriptor-collapsibleHeading, .scriptor-collapseButton |
| @mentions |
[data-testid="resolvedAtMention"] |
| Tables |
[data-testid="tableWrapperTestId"], [data-testid="tableRowTestId"], [data-testid="tableCellWrapperTestId"] |
| Fluid nested components in cells |
[data-testid="fluidComponentCellTestId"], [data-testid="fluidComponentNestedCellTestId"] |
| Summary cells (aggregates) |
[data-testid="summary-cell-test-id"], [data-testid="NumberSummaryValue"], [data-testid="PersonSummaryPersona"] |
| Labels |
[data-testid="label-container-test-id"], [data-testid="label-pill-test-id"] |
| Date cells |
[data-testid="dateCellWrapperTestId"] |
| Voting |
[data-testid="voting-container-test-id"], [data-testid="voting-button"] |
| Exclude |
[data-testid="attribution-floatie"], .scriptor-table-of-contents-root, nav/header outside canvas |
Component extraction spec
Process blocks top-to-bottom in the canvas. Each section below defines input signals, Markdown output, and edge cases.
1. Page metadata (optional preamble)
Always prepend the document title as H1 (from <title> or first heading). Strip product suffixes (e.g. | M365 Copilot).
When loopIncludeUrl is enabled, add:
# {title}
**Source:** {canonical page URL}
2. Headings (H1–H3)
- DOM:
role="heading" + aria-level="1|2|3" inside .scriptor-paragraph.
- Output:
# / ## / ### + plain text from .scriptor-textRun children.
- Edge cases: Collapsible headings (see §15) may also use
role="heading" — always extract body text even if collapsed in the UI.
3. Paragraphs
- DOM:
.scriptor-paragraph without list/heading/code specialized classes.
- Output: Plain paragraph text; blank line between paragraphs.
- Inline styles within paragraph (see §4).
4. Text styles (inline)
| Style |
DOM hint |
Markdown |
| Bold |
class contains bold or <strong> |
**text** |
| Italic |
italic / <em> |
*text* |
| Strikethrough |
strikethrough / <s> |
~~text~~ |
| Inline code |
.scriptor-code / monospace runs |
`text` |
| Highlight / notes inline |
.scriptor-highlight |
*text* (italic) for inline highlights |
Preserve nesting order: outer styles wrap inner (e.g. bold inside italic).
5. Lists
Unordered
- DOM:
.scriptor-listItem + .scriptor-listItem-marker-bullet.
- Output:
- {text} with indentation from margin-left / nesting level.
Ordered
- DOM:
.scriptor-listItem with numeric marker (.scriptor-listItem-marker-text without checkbox).
- Output:
1. {text} (recompute indices per list group).
Nested lists: Increase indent by 2 spaces per level.
6. Checklists (task list)
- DOM:
.scriptor-listItem-marker-checkbox, .scriptor-listItem-marker-checkbox-icon, aria-label containing Completed / item is Completed, role="checkbox".
- Output: GFM task list syntax:
- Unchecked:
- [ ] {text}
- Checked:
- [x] {text}
- Read completion from checkbox
aria-checked, icon class, or parent aria-label (e.g. 1 item is Completed).
7. Blockquote
- DOM:
[data-testid="block-quote-component"], .scriptor-component-block-quote.
- Output:
> {quoted text, one line per source line}
8. Callout
- DOM:
[data-testid="block-callout-component"], [data-testid="emoji-picker"] for leading emoji.
- Output: blockquote with callout emoji prefix:
- Read emoji from
emoji-picker aria-label (e.g. 📌️ icon, change emoji → 📌).
9. Code blocks
- DOM:
.scriptor-component-code-block, [aria-label="Code editor"][contenteditable].
- Output:
```{lang}
{code text}
```
- Detect language from class hints or first-line shebang if present; default
text.
- Strip line-number gutter elements (
.scriptor-code-line-numbers-*).
- Respect wrap mode only for extraction fidelity, not output formatting.
10. Divider / horizontal rule
- DOM: Paragraph containing
(three hyphens for a divider) or horizontal rule element.
- Output:
--- on its own line.
11. Links
- DOM:
.scriptor-hyperlink, role="link", title attribute often holds URL.
- Output:
[{visible text}]({url})
- Internal Loop anchor links (UUID
hrefs in TOC): output visible text only — do not emit broken UUID links.
12. Images
- DOM:
.scriptor-image, <img> inside image blocks.
- Output (when
loopIncludeImages is enabled):
- If alt is empty or generic (
Image has no description), use .
- When images are disabled, omit image blocks entirely.
13. Notes / highlights (block-level)
- DOM:
.scriptor-highlightWrapper, highlighted paragraph spans.
- Output:
> **Note:** {highlighted content}
14. Math / LaTeX equations
- DOM: KaTeX elements (
.katex), aria-label mentioning LaTeX equation.
- Output:
$$ {latex source} $$ for block, $ {source} $ for inline.
- Prefer retrieving source from
aria-label or adjacent annotation; if only rendered SVG, fall back to aria-label text or [equation].
15. Mermaid diagrams
- DOM: Mermaid section with rendered
<svg>, source often in text runs (e.g. flowchart TD, A[...] -->|use| B[...]).
- Output: fenced
mermaid block with source text (never dump SVG):
- Decode HTML entities in source (
--> → -->).
- If source unavailable, optionally note
[Mermaid diagram — source not extracted] and skip huge SVG dumps.
16. Collapsible headings
- DOM:
.scriptor-collapsibleHeading, .scriptor-collapseButton, aria-expanded.
- Output: include children regardless of collapsed UI state (DOM usually still contains text). Emit as normal heading + content.
17. Date (standalone component & table column)
Standalone date block
- DOM:
[data-testid="dateCellWrapperTestId"], aria-label="Date".
- Output: plain displayed date text.
Table date column
- Extract displayed value as shown in the UI.
18. @mentions (people)
- DOM:
[data-testid="resolvedAtMention"], aria-label, persona text inside.
- Output (controlled by
loopMentionFormat setting):
at (default): @Alex Example
plain: Alex Example
- Never emit emails, tenant IDs, object IDs, or raw Graph
@odata fragments from hydration JSON.
19. Person & label components
Person pill / persona summary
- DOM:
[data-testid="PersonSummaryPersona"], aria-label="{Name} has N item(s)", avatar initials.
- Output:
@Name or Name per mention format; include item count from aria-label when present (e.g. has 1 item).
Label pills
- DOM:
[data-testid="label-pill-test-id"], [data-testid="label-container-test-id"], data-automation-type="Label".
- Output:
`{label text}` inline or Label: {text} for standalone labels.
- Progress labels (e.g.
Progress : Completed): **Progress:** Completed or :completed: badge style.
Label category headers in tables: include as column header text.
20. Number component
- DOM:
[data-testid="NumberSummaryValue"], data-automation-type="Numero", aria-label containing Number.
- Output: raw number as text; in tables, use column header context.
21. Tables (Fluid / Loop grid)
Structure
- DOM:
[data-testid="tableWrapperTestId"] → rows [data-testid="tableRowTestId"] → cells [data-testid="tableCellWrapperTestId"].
- Header row: cells with
aria-label="Text Header Cell", Number Header Cell, Label Header Cell, Date Header Cell, Person Header Cell, Vote Header Cell, etc.
Output: GitHub-flavored Markdown table:
| Text | Number | Label | Date | Person | Votes |
| --- | ---: | --- | --- | --- | ---: |
| Sum 234 | 234 | Completed | Tomorrow | @Alex Example | +1 (1 voter) |
-
Column alignment: numbers/votes right-aligned (---:).
-
Fluid nested components in cells (fluidComponentCellTestId): recursively apply the relevant component rules inside the cell.
-
Summary row cells (summary-cell-test-id): extract aggregated display (e.g. Sum 234, C 1).
-
Empty cells: leave blank.
Voting column inside table
-
See §22; combine vote button label (+1) with voter count from aria-label (1 voters. Click to unvote.).
22. Voting table / poll component
- DOM:
[data-testid="voting-container-test-id"], [data-testid="voting-button"], aria-pressed, aria-label on vote buttons.
- Output:
| Option | Votes |
| --- | ---: |
| Option A | +1 (1 voter) |
| Option B | +0 (0 voters) |
- Parse
0 voters. Click to vote. / 1 voters. Click to unvote. for counts.
23. Table of contents (editor UI)
- DOM:
.scriptor-table-of-contents-root, .scriptor-table-of-contents-entry.
- Always exclude — duplicates body headings and uses internal UUID anchors.
24. Elements to always exclude
- Copilot nav drawer, chat input, peek pivots,
attribution-floatie, hidden elements, aria-hidden="true" decorative nodes, hydration <script> JSON, Suggested Actions UI, and non-canvas iframes.
Settings (minimal — 4 options)
Keep Loop aligned with ExtractMD’s “simple app” philosophy. Only expose settings where user preference genuinely changes the output; everything else is fixed behavior (see below).
| Setting |
Type |
Default |
Description |
enableLoopIntegration |
boolean |
true |
Master toggle (same pattern as YouTube, HN, X) |
loopMentionFormat |
enum |
at |
People mentions: at → @Name, plain → Name |
loopIncludeUrl |
boolean |
true |
Include **Source:** {url} in metadata |
loopIncludeImages |
boolean |
true |
Emit  for image blocks |
Fixed behavior (not configurable)
These are sensible defaults baked into the extractor — no options UI:
| Behavior |
Fixed choice |
| Page title |
Always prepended as H1 |
| Checklists |
GFM - [ ] / - [x] |
| Callouts |
Blockquote + emoji prefix |
| Tables |
Markdown tables; nested Fluid cells resolved recursively |
| Voting / polls |
Include vote counts |
| Mermaid |
Source in ```mermaid fence, never SVG |
| Notes / highlights |
> **Note:** … blockquote |
| Collapsible sections |
Always extract full body |
| Table of contents (editor UI) |
Excluded |
| Internal UUID links |
Text only, no broken links |
| Code blocks |
No line numbers; detect language when possible |
| Dates |
As displayed in UI |
| Chrome / nav / hydration JSON |
Always stripped |
| PII |
Never emit emails, tenant IDs, or Graph metadata |
Alternatives considered
- Universal extractor only — Insufficient: Fluid tables, voting widgets, and mention chips lose structure.
- Copy Loop's native Markdown export — Not always available in embed views; ExtractMD should still work from DOM.
- Screenshot / PDF — Poor for LLM pipelines; Markdown remains the goal.
Impact
Medium — Valuable for Microsoft 365 / Loop users in knowledge-work and LLM workflows; narrower than YouTube/HN but high depth for those users.
Additional context
Acceptance criteria (suggested)
Test fixture
Maintain an anonymized HTML fixture (derived from internal “all components” page) in tests/fixtures/loop/ with:
- Headings H1–H3, text styles, lists, checklists
- Blockquote, callout, code, divider, links, image, notes
- Mermaid, math, collapsible heading, date, mentions, labels, numbers
- Standard table + voting table
Architecture notes
Follow existing integration layout:
extension/content/loop/detection.js
extension/content/loop/extractors.js
- Wire into content script router +
copyHandler KPI loop
- Add 4 Loop settings to
shared/defaults.js and options UI (no per-component toggles)
Reference component inventory (from analyzed page)
| Section |
Present in fixture |
| Headings (1–3) |
✓ |
| Text styles (bold, italic, strike, inline code) |
✓ |
| Ordered / unordered lists |
✓ |
| Checklists (GFM + indented) |
✓ |
| Blockquote |
✓ |
| Callout (emoji) |
✓ |
| Code block |
✓ |
| Divider |
✓ |
| Links |
✓ |
| Image |
✓ |
| Notes / highlight |
✓ |
| Mermaid |
✓ |
| Math (LaTeX) |
✓ |
| Collapsible heading |
✓ |
| Date |
✓ |
| @mentions |
✓ |
| Person & label |
✓ |
| Number |
✓ |
| Multi-type table |
✓ |
| Voting / poll |
✓ |
Feature Request: Microsoft Loop integration
Category
New Integration (website/platform support)
Problem or Use Case
ExtractMD currently has no dedicated integration for Microsoft Loop pages. Loop content is rendered inside a rich canvas editor (Scriptor/Fluid) with many component types that generic HTML-to-Markdown conversion handles poorly or not at all: nested Fluid tables with typed columns, voting widgets, person/label pills, @mentions, Mermaid diagrams, collapsible sections, and checklist state.
Users working in Loop (standalone, Teams, SharePoint embeds, or M365 Copilot page views) need one-click extraction that preserves document structure and component semantics as clean, LLM-friendly Markdown.
A reference HTML dump covering all Loop component types was analyzed to define selectors, output shapes, and settings below. (Internal fixture only — not attached to avoid PII.)
Proposed Solution
Add a Loop site handler (alongside YouTube, HN, X, Articles) that:
scriptor-pageFrame,data-testidFluid/Loop markers,fluid/prod/loop-appassets).defaults.js, options UI, and KPI typeloop(keep the integration simple — most behavior is fixed).Supported hosts (detection)
https://*.loop.microsoft.com/*https://teams.microsoft.com/*https://*.sharepoint.com/*+ Fluid Loop embedscriptor-pageFrame+ loop-app scriptsDetection should prefer the inner Loop canvas (
scriptor-pageFrame/ main content region), not the surrounding Copilot shell.DOM anchors (from reference page)
.scriptor-pageFrame,.scriptor-webView[data-testid="block-quote-component"][data-testid="block-callout-component"].scriptor-component-code-block,[aria-label="Code editor"][role="heading"][aria-level="1|2|3"].scriptor-listItem,.scriptor-listItem-marker-bullet,.scriptor-listItem-marker-checkbox.scriptor-hyperlink.scriptor-image,.scriptor-image-element.scriptor-highlightWrapper,.scriptor-svgHighlight.scriptor-collapsibleHeading,.scriptor-collapseButton[data-testid="resolvedAtMention"][data-testid="tableWrapperTestId"],[data-testid="tableRowTestId"],[data-testid="tableCellWrapperTestId"][data-testid="fluidComponentCellTestId"],[data-testid="fluidComponentNestedCellTestId"][data-testid="summary-cell-test-id"],[data-testid="NumberSummaryValue"],[data-testid="PersonSummaryPersona"][data-testid="label-container-test-id"],[data-testid="label-pill-test-id"][data-testid="dateCellWrapperTestId"][data-testid="voting-container-test-id"],[data-testid="voting-button"][data-testid="attribution-floatie"],.scriptor-table-of-contents-root, nav/header outside canvasComponent extraction spec
Process blocks top-to-bottom in the canvas. Each section below defines input signals, Markdown output, and edge cases.
1. Page metadata (optional preamble)
Always prepend the document title as H1 (from
<title>or first heading). Strip product suffixes (e.g.| M365 Copilot).When
loopIncludeUrlis enabled, add:2. Headings (H1–H3)
role="heading"+aria-level="1|2|3"inside.scriptor-paragraph.#/##/###+ plain text from.scriptor-textRunchildren.role="heading"— always extract body text even if collapsed in the UI.3. Paragraphs
.scriptor-paragraphwithout list/heading/code specialized classes.4. Text styles (inline)
boldor<strong>**text**italic/<em>*text*strikethrough/<s>~~text~~.scriptor-code/ monospace runs`text`.scriptor-highlight*text*(italic) for inline highlightsPreserve nesting order: outer styles wrap inner (e.g. bold inside italic).
5. Lists
Unordered
.scriptor-listItem+.scriptor-listItem-marker-bullet.- {text}with indentation frommargin-left/ nesting level.Ordered
.scriptor-listItemwith numeric marker (.scriptor-listItem-marker-textwithout checkbox).1. {text}(recompute indices per list group).Nested lists: Increase indent by 2 spaces per level.
6. Checklists (task list)
.scriptor-listItem-marker-checkbox,.scriptor-listItem-marker-checkbox-icon,aria-labelcontainingCompleted/item is Completed,role="checkbox".- [ ] {text}- [x] {text}aria-checked, icon class, or parentaria-label(e.g.1 item is Completed).7. Blockquote
[data-testid="block-quote-component"],.scriptor-component-block-quote.> {quoted text, one line per source line}8. Callout
[data-testid="block-callout-component"],[data-testid="emoji-picker"]for leading emoji.> 📌 {callout body}emoji-pickeraria-label(e.g.📌️ icon, change emoji→📌).9. Code blocks
.scriptor-component-code-block,[aria-label="Code editor"][contenteditable].text..scriptor-code-line-numbers-*).10. Divider / horizontal rule
(three hyphens for a divider)or horizontal rule element.---on its own line.11. Links
.scriptor-hyperlink,role="link",titleattribute often holds URL.[{visible text}]({url})hrefs in TOC): output visible text only — do not emit broken UUID links.12. Images
.scriptor-image,<img>inside image blocks.loopIncludeImagesis enabled):Image has no description), use.13. Notes / highlights (block-level)
.scriptor-highlightWrapper, highlighted paragraph spans.> **Note:** {highlighted content}14. Math / LaTeX equations
.katex),aria-labelmentioningLaTeX equation.$$ {latex source} $$for block,$ {source} $for inline.aria-labelor adjacent annotation; if only rendered SVG, fall back toaria-labeltext or[equation].15. Mermaid diagrams
<svg>, source often in text runs (e.g.flowchart TD,A[...] -->|use| B[...]).mermaidblock with source text (never dump SVG):-->→-->).[Mermaid diagram — source not extracted]and skip huge SVG dumps.16. Collapsible headings
.scriptor-collapsibleHeading,.scriptor-collapseButton,aria-expanded.17. Date (standalone component & table column)
Standalone date block
[data-testid="dateCellWrapperTestId"],aria-label="Date".Table date column
18. @mentions (people)
[data-testid="resolvedAtMention"],aria-label, persona text inside.loopMentionFormatsetting):at(default):@Alex Exampleplain:Alex Example@odatafragments from hydration JSON.19. Person & label components
Person pill / persona summary
[data-testid="PersonSummaryPersona"],aria-label="{Name} has N item(s)", avatar initials.@NameorNameper mention format; include item count fromaria-labelwhen present (e.g.has 1 item).Label pills
[data-testid="label-pill-test-id"],[data-testid="label-container-test-id"],data-automation-type="Label".`{label text}`inline orLabel: {text}for standalone labels.Progress : Completed):**Progress:** Completedor:completed:badge style.Label category headers in tables: include as column header text.
20. Number component
[data-testid="NumberSummaryValue"],data-automation-type="Numero",aria-labelcontainingNumber.21. Tables (Fluid / Loop grid)
Structure
[data-testid="tableWrapperTestId"]→ rows[data-testid="tableRowTestId"]→ cells[data-testid="tableCellWrapperTestId"].aria-label="Text Header Cell",Number Header Cell,Label Header Cell,Date Header Cell,Person Header Cell,Vote Header Cell, etc.Output: GitHub-flavored Markdown table:
Column alignment: numbers/votes right-aligned (
---:).Fluid nested components in cells (
fluidComponentCellTestId): recursively apply the relevant component rules inside the cell.Summary row cells (
summary-cell-test-id): extract aggregated display (e.g.Sum 234,C 1).Empty cells: leave blank.
Voting column inside table
See §22; combine vote button label (
+1) with voter count fromaria-label(1 voters. Click to unvote.).22. Voting table / poll component
[data-testid="voting-container-test-id"],[data-testid="voting-button"],aria-pressed,aria-labelon vote buttons.0 voters. Click to vote./1 voters. Click to unvote.for counts.23. Table of contents (editor UI)
.scriptor-table-of-contents-root,.scriptor-table-of-contents-entry.24. Elements to always exclude
attribution-floatie, hidden elements,aria-hidden="true"decorative nodes, hydration<script>JSON, Suggested Actions UI, and non-canvas iframes.Settings (minimal — 4 options)
Keep Loop aligned with ExtractMD’s “simple app” philosophy. Only expose settings where user preference genuinely changes the output; everything else is fixed behavior (see below).
enableLoopIntegrationtrueloopMentionFormatatat→@Name,plain→NameloopIncludeUrltrue**Source:** {url}in metadataloopIncludeImagestruefor image blocksFixed behavior (not configurable)
These are sensible defaults baked into the extractor — no options UI:
- [ ]/- [x]```mermaidfence, never SVG> **Note:** …blockquoteAlternatives considered
Impact
Medium — Valuable for Microsoft 365 / Loop users in knowledge-work and LLM workflows; narrower than YouTube/HN but high depth for those users.
Additional context
Acceptance criteria (suggested)
enableLoopIntegration,loopMentionFormat,loopIncludeUrl,loopIncludeImages)loopincrements on successful extractionTest fixture
Maintain an anonymized HTML fixture (derived from internal “all components” page) in
tests/fixtures/loop/with:Architecture notes
Follow existing integration layout:
extension/content/loop/detection.jsextension/content/loop/extractors.jscopyHandlerKPIloopshared/defaults.jsand options UI (no per-component toggles)Reference component inventory (from analyzed page)