Skip to content

[Feature]: Microsoft Loop integration #84

Description

@miguelcorderocollar

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:

  1. Detects Loop surfaces via URL patterns and/or DOM signals (scriptor-pageFrame, data-testid Fluid/Loop markers, fluid/prod/loop-app assets).
  2. Scopes extraction to the Loop canvas root, excluding Copilot/Teams chrome, table-of-contents UI, attribution floaties, and editor chrome.
  3. Walks components in document order and emits Markdown using the rules in Component extraction spec below.
  4. 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:
> 📌 {callout body}
  • 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):
![{alt text}]({src url})
  • If alt is empty or generic (Image has no description), use ![Image](url).
  • 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):
```mermaid
{source}
```
  • Decode HTML entities in source (--&gt;-->).
  • 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, plainName
loopIncludeUrl boolean true Include **Source:** {url} in metadata
loopIncludeImages boolean true Emit ![alt](url) 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

  1. Universal extractor only — Insufficient: Fluid tables, voting widgets, and mention chips lose structure.
  2. Copy Loop's native Markdown export — Not always available in embed views; ExtractMD should still work from DOM.
  3. 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)

  • Detect Loop on standalone, Teams, SharePoint embed, and Copilot-embedded canvas
  • Extract all component types listed in §1–§22 from a reference “all components” page
  • No PII from hydration blobs (emails, tenant IDs) in output by default
  • Four Loop settings in Options (enableLoopIntegration, loopMentionFormat, loopIncludeUrl, loopIncludeImages)
  • KPI counter loop increments on successful extraction
  • Unit tests with sanitized HTML fixtures per component type

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: extractionContent detection and Markdown extractionarea: settingsSettings, preferences, and configurationenhancementNew feature or requestintegration: Microsoft LoopMicrosoft Loop content extraction

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions