Spaces:
Running
Running
thibaud frere
commited on
Commit
·
a17a3bf
1
Parent(s):
1ee6ce7
add notion to mdx converter
Browse files- .gitignore +2 -0
- app/scripts/notion-to-mdx/.cursorignore +1 -0
- app/scripts/notion-to-mdx/.notion-to-md/media/27877f1c-9c9d-804d-9c82-f7b3905578ff_media.json +3 -0
- app/scripts/notion-to-mdx/README.md +261 -0
- app/scripts/notion-to-mdx/custom-code-renderer.mjs +33 -0
- app/scripts/notion-to-mdx/debug-properties.mjs +87 -0
- app/scripts/notion-to-mdx/env.example +72 -0
- app/scripts/notion-to-mdx/index.mjs +252 -0
- app/scripts/notion-to-mdx/input/pages.json +3 -0
- app/scripts/notion-to-mdx/mdx-converter.mjs +551 -0
- app/scripts/notion-to-mdx/notion-converter.mjs +259 -0
- app/scripts/notion-to-mdx/notion-metadata-extractor.mjs +303 -0
- app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-8013-b668-f14bd1ac0ec0.png +3 -0
- app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-8014-834f-d700b623256b.png +3 -0
- app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-801d-841a-e35011491566.png +3 -0
- app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-8031-ac8d-c5678af1bdd5.png +3 -0
- app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-8048-9b7e-db4fa7485915.png +3 -0
- app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-804d-bd0a-e0b1c15e504f.png +3 -0
- app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-8075-ae2e-dc24fe9296ca.png +3 -0
- app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-8078-b6da-c7a4c67c8f35.png +3 -0
- app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-808d-9c6d-fae817ac8868.png +3 -0
- app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-808f-b712-c7c608da3fc6.png +3 -0
- app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-80a9-b4d0-f2129716632d.png +3 -0
- app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-80aa-b968-c54c9fe7e5d7.png +3 -0
- app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-80b6-be07-e8646502f82a.png +3 -0
- app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-80b9-8cfb-f0a6aaaa8760.png +3 -0
- app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-80e7-a500-fb79cebde7e3.png +3 -0
- app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-80e9-b729-dbd328930bed.png +3 -0
- app/scripts/notion-to-mdx/output/smol-training-guide.md +0 -0
- app/scripts/notion-to-mdx/output/smol-training-guide.mdx +0 -0
- app/scripts/notion-to-mdx/package-lock.json +0 -0
- app/scripts/notion-to-mdx/package.json +0 -0
- app/scripts/notion-to-mdx/post-processor.mjs +369 -0
- app/scripts/notion-to-mdx/test-access.mjs +39 -0
- app/scripts/notion-to-mdx/yarn.lock +1118 -0
- app/src/components/Hero.astro +147 -73
- app/src/components/HtmlEmbed.astro +8 -3
- app/src/components/Sidenote.astro +36 -15
- app/src/styles/_layout.css +15 -6
- app/src/styles/_variables.css +1 -2
- app/src/styles/components/_form.css +9 -4
- app/src/styles/components/_table.css +123 -95
- tools/duplicated-spaces/README.md +0 -32
- tools/duplicated-spaces/duplicated_spaces/__init__.py +0 -5
- tools/duplicated-spaces/duplicated_spaces/cli.py +0 -69
- tools/duplicated-spaces/duplicated_spaces/finder.py +0 -99
- tools/duplicated-spaces/pyproject.toml +0 -23
.gitignore
CHANGED
|
@@ -11,6 +11,8 @@ build/
|
|
| 11 |
*.egg
|
| 12 |
.idea/
|
| 13 |
.vscode/
|
|
|
|
|
|
|
| 14 |
*.swp
|
| 15 |
.DS_Store
|
| 16 |
# Node
|
|
|
|
| 11 |
*.egg
|
| 12 |
.idea/
|
| 13 |
.vscode/
|
| 14 |
+
.astro/
|
| 15 |
+
.claude/
|
| 16 |
*.swp
|
| 17 |
.DS_Store
|
| 18 |
# Node
|
app/scripts/notion-to-mdx/.cursorignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
.env
|
app/scripts/notion-to-mdx/.notion-to-md/media/27877f1c-9c9d-804d-9c82-f7b3905578ff_media.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c282e7bcb40ee2caafda422b3614d996d6023dbb4bbe10f96521348ee151aeb0
|
| 3 |
+
size 36969
|
app/scripts/notion-to-mdx/README.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Notion to MDX Toolkit
|
| 2 |
+
|
| 3 |
+
Complete Notion to MDX (Markdown + JSX) conversion optimized for Astro with advanced media handling, interactive components, and seamless integration.
|
| 4 |
+
|
| 5 |
+
## 🚀 Quick Start
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
# Install dependencies
|
| 9 |
+
npm install
|
| 10 |
+
|
| 11 |
+
# Setup environment variables
|
| 12 |
+
cp env.example .env
|
| 13 |
+
# Edit .env with your Notion token
|
| 14 |
+
|
| 15 |
+
# Complete Notion → MDX conversion with all features
|
| 16 |
+
node index.mjs
|
| 17 |
+
|
| 18 |
+
# For step-by-step debugging
|
| 19 |
+
node notion-converter.mjs # Notion → Markdown
|
| 20 |
+
node mdx-converter.mjs # Markdown → MDX
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
## 📁 Structure
|
| 24 |
+
|
| 25 |
+
```
|
| 26 |
+
notion-to-mdx/
|
| 27 |
+
├── index.mjs # Complete Notion → MDX pipeline
|
| 28 |
+
├── notion-converter.mjs # Notion → Markdown with notion-to-md v4
|
| 29 |
+
├── mdx-converter.mjs # Markdown → MDX with Astro components
|
| 30 |
+
├── post-processor.mjs # Markdown post-processing
|
| 31 |
+
├── package.json # Dependencies and scripts
|
| 32 |
+
├── env.example # Environment variables template
|
| 33 |
+
├── input/ # Configuration
|
| 34 |
+
│ └── pages.json # Notion pages to convert
|
| 35 |
+
└── output/ # Results
|
| 36 |
+
├── *.md # Intermediate Markdown
|
| 37 |
+
├── *.mdx # Final MDX for Astro
|
| 38 |
+
└── media/ # Downloaded media files
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
## ✨ Key Features
|
| 42 |
+
|
| 43 |
+
### 🎯 **Advanced Media Handling**
|
| 44 |
+
- **Local download**: Automatic download of all Notion media (images, files, PDFs)
|
| 45 |
+
- **Path transformation**: Smart path conversion for web accessibility
|
| 46 |
+
- **Figure components**: Automatic conversion to Astro `Figure` components with zoom/download
|
| 47 |
+
- **Media organization**: Structured media storage by page ID
|
| 48 |
+
|
| 49 |
+
### 🧮 **Interactive Components**
|
| 50 |
+
- **Callouts → Notes**: Notion callouts converted to Astro `Note` components
|
| 51 |
+
- **Enhanced tables**: Tables wrapped in styled containers
|
| 52 |
+
- **Code blocks**: Enhanced with copy functionality
|
| 53 |
+
- **Automatic imports**: Smart component and image import generation
|
| 54 |
+
|
| 55 |
+
### 🎨 **Smart Formatting**
|
| 56 |
+
- **Link fixing**: Notion internal links converted to relative links
|
| 57 |
+
- **Artifact cleanup**: Removal of Notion-specific formatting artifacts
|
| 58 |
+
- **Frontmatter generation**: Automatic YAML frontmatter from Notion properties
|
| 59 |
+
- **Astro compatibility**: Full compatibility with Astro MDX processing
|
| 60 |
+
|
| 61 |
+
### 🔧 **Robust Pipeline**
|
| 62 |
+
- **Notion preprocessing**: Advanced page configuration and media strategy
|
| 63 |
+
- **Post-processing**: Markdown cleanup and optimization
|
| 64 |
+
- **MDX conversion**: Final transformation with Astro components
|
| 65 |
+
- **Auto-copy**: Automatic copying to Astro content directory
|
| 66 |
+
|
| 67 |
+
## 📊 Example Workflow
|
| 68 |
+
|
| 69 |
+
```bash
|
| 70 |
+
# 1. Configure your Notion pages
|
| 71 |
+
# Edit input/pages.json with your page IDs
|
| 72 |
+
|
| 73 |
+
# 2. Complete automatic conversion
|
| 74 |
+
NOTION_TOKEN=your_token node index.mjs --clean
|
| 75 |
+
|
| 76 |
+
# 3. Generated results
|
| 77 |
+
ls output/
|
| 78 |
+
# → getting-started.md (Intermediate Markdown)
|
| 79 |
+
# → getting-started.mdx (Final MDX for Astro)
|
| 80 |
+
# → media/ (downloaded images and files)
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
### 📋 Conversion Result
|
| 84 |
+
|
| 85 |
+
The pipeline generates MDX files optimized for Astro with:
|
| 86 |
+
|
| 87 |
+
```mdx
|
| 88 |
+
---
|
| 89 |
+
title: "Getting Started with Notion"
|
| 90 |
+
published: "2024-01-15"
|
| 91 |
+
tableOfContentsAutoCollapse: true
|
| 92 |
+
---
|
| 93 |
+
|
| 94 |
+
import Figure from '../components/Figure.astro';
|
| 95 |
+
import Note from '../components/Note.astro';
|
| 96 |
+
import gettingStartedImage from './media/getting-started/image1.png';
|
| 97 |
+
|
| 98 |
+
## Introduction
|
| 99 |
+
|
| 100 |
+
Here is some content with a callout:
|
| 101 |
+
|
| 102 |
+
<Note type="info" title="Important">
|
| 103 |
+
This is a converted Notion callout.
|
| 104 |
+
</Note>
|
| 105 |
+
|
| 106 |
+
And an image:
|
| 107 |
+
|
| 108 |
+
<Figure
|
| 109 |
+
src={gettingStartedImage}
|
| 110 |
+
alt="Getting started screenshot"
|
| 111 |
+
zoomable
|
| 112 |
+
downloadable
|
| 113 |
+
layout="fixed"
|
| 114 |
+
/>
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
## ⚙️ Required Astro Configuration
|
| 118 |
+
|
| 119 |
+
To use the generated MDX files, ensure your Astro project has the required components:
|
| 120 |
+
|
| 121 |
+
```astro
|
| 122 |
+
// src/components/Figure.astro
|
| 123 |
+
---
|
| 124 |
+
export interface Props {
|
| 125 |
+
src: any;
|
| 126 |
+
alt?: string;
|
| 127 |
+
caption?: string;
|
| 128 |
+
zoomable?: boolean;
|
| 129 |
+
downloadable?: boolean;
|
| 130 |
+
layout?: string;
|
| 131 |
+
id?: string;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
const { src, alt, caption, zoomable, downloadable, layout, id } = Astro.props;
|
| 135 |
+
---
|
| 136 |
+
|
| 137 |
+
<figure {id} class="figure">
|
| 138 |
+
<img src={src} alt={alt} />
|
| 139 |
+
{caption && <figcaption>{caption}</figcaption>}
|
| 140 |
+
</figure>
|
| 141 |
+
```
|
| 142 |
+
|
| 143 |
+
## 🛠️ Prerequisites
|
| 144 |
+
|
| 145 |
+
- **Node.js** with ESM support
|
| 146 |
+
- **Notion Integration**: Set up an integration in your Notion workspace
|
| 147 |
+
- **Notion Token**: Copy the "Internal Integration Token"
|
| 148 |
+
- **Shared Pages**: Share the specific Notion page(s) with your integration
|
| 149 |
+
- **Astro** to use the generated MDX
|
| 150 |
+
|
| 151 |
+
## 🎯 Technical Architecture
|
| 152 |
+
|
| 153 |
+
### 4-Stage Pipeline
|
| 154 |
+
|
| 155 |
+
1. **Notion Preprocessing** (`notion-converter.mjs`)
|
| 156 |
+
- Configuration loading from `pages.json`
|
| 157 |
+
- Notion API client initialization
|
| 158 |
+
- Media download strategy configuration
|
| 159 |
+
|
| 160 |
+
2. **Notion-to-Markdown** (notion-to-md v4)
|
| 161 |
+
- Page conversion with `NotionConverter`
|
| 162 |
+
- Media downloading with `downloadMediaTo()`
|
| 163 |
+
- File export with `DefaultExporter`
|
| 164 |
+
|
| 165 |
+
3. **Markdown Post-processing** (`post-processor.mjs`)
|
| 166 |
+
- Notion artifact cleanup
|
| 167 |
+
- Link fixing and optimization
|
| 168 |
+
- Table and code block enhancement
|
| 169 |
+
|
| 170 |
+
4. **MDX Conversion** (`mdx-converter.mjs`)
|
| 171 |
+
- Component transformation (Figure, Note)
|
| 172 |
+
- Automatic import generation
|
| 173 |
+
- Frontmatter enhancement
|
| 174 |
+
- Astro compatibility optimization
|
| 175 |
+
|
| 176 |
+
## 📊 Configuration Options
|
| 177 |
+
|
| 178 |
+
### Pages Configuration (`input/pages.json`)
|
| 179 |
+
|
| 180 |
+
```json
|
| 181 |
+
{
|
| 182 |
+
"pages": [
|
| 183 |
+
{
|
| 184 |
+
"id": "your-notion-page-id",
|
| 185 |
+
"title": "Page Title",
|
| 186 |
+
"slug": "page-slug"
|
| 187 |
+
}
|
| 188 |
+
]
|
| 189 |
+
}
|
| 190 |
+
```
|
| 191 |
+
|
| 192 |
+
### Environment Variables
|
| 193 |
+
|
| 194 |
+
Copy `env.example` to `.env` and configure:
|
| 195 |
+
|
| 196 |
+
```bash
|
| 197 |
+
cp env.example .env
|
| 198 |
+
# Edit .env with your actual Notion token
|
| 199 |
+
```
|
| 200 |
+
|
| 201 |
+
Required variables:
|
| 202 |
+
```bash
|
| 203 |
+
NOTION_TOKEN=secret_your_notion_integration_token_here
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
### Command Line Options
|
| 207 |
+
|
| 208 |
+
```bash
|
| 209 |
+
# Full workflow
|
| 210 |
+
node index.mjs --clean --token=your_token
|
| 211 |
+
|
| 212 |
+
# Notion to Markdown only
|
| 213 |
+
node index.mjs --notion-only
|
| 214 |
+
|
| 215 |
+
# Markdown to MDX only
|
| 216 |
+
node index.mjs --mdx-only
|
| 217 |
+
|
| 218 |
+
# Custom paths
|
| 219 |
+
node index.mjs --input=my-pages.json --output=converted/
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
## 📊 Conversion Statistics
|
| 223 |
+
|
| 224 |
+
For a typical Notion page:
|
| 225 |
+
- **Media files** automatically downloaded and organized
|
| 226 |
+
- **Callouts** converted to interactive Note components
|
| 227 |
+
- **Images** transformed to Figure components with zoom/download
|
| 228 |
+
- **Tables** enhanced with proper styling containers
|
| 229 |
+
- **Code blocks** enhanced with copy functionality
|
| 230 |
+
- **Links** fixed for proper internal navigation
|
| 231 |
+
|
| 232 |
+
## ✅ Project Status
|
| 233 |
+
|
| 234 |
+
### 🎉 **Complete Features**
|
| 235 |
+
- ✅ **Notion → MDX Pipeline**: Full end-to-end functional conversion
|
| 236 |
+
- ✅ **Media Management**: Automatic download and path transformation
|
| 237 |
+
- ✅ **Component Integration**: Seamless Astro component integration
|
| 238 |
+
- ✅ **Smart Formatting**: Intelligent cleanup and optimization
|
| 239 |
+
- ✅ **Robustness**: Error handling and graceful degradation
|
| 240 |
+
- ✅ **Flexibility**: Modular pipeline with step-by-step options
|
| 241 |
+
|
| 242 |
+
### 🚀 **Production Ready**
|
| 243 |
+
The toolkit is now **100% operational** for converting Notion pages to MDX/Astro with all advanced features (media handling, component integration, smart formatting).
|
| 244 |
+
|
| 245 |
+
## 🔗 Integration with notion-to-md v4
|
| 246 |
+
|
| 247 |
+
This toolkit leverages the powerful [notion-to-md v4](https://notionconvert.com/docs/v4/guides/) library with:
|
| 248 |
+
|
| 249 |
+
- **Advanced Media Strategies**: Download, upload, and direct media handling
|
| 250 |
+
- **Custom Renderers**: Block transformers and annotation transformers
|
| 251 |
+
- **Exporter Plugins**: File, buffer, and stdout output options
|
| 252 |
+
- **Database Support**: Full database property and frontmatter transformation
|
| 253 |
+
- **Page References**: Smart internal link handling
|
| 254 |
+
|
| 255 |
+
## 📚 Additional Resources
|
| 256 |
+
|
| 257 |
+
- [notion-to-md v4 Documentation](https://notionconvert.com/docs/v4/guides/)
|
| 258 |
+
- [Notion API Documentation](https://developers.notion.com/)
|
| 259 |
+
- [Astro MDX Documentation](https://docs.astro.build/en/guides/integrations-guide/mdx/)
|
| 260 |
+
- [Media Handling Strategies](https://notionconvert.com/blog/mastering-media-handling-in-notion-to-md-v4-download-upload-and-direct-strategies/)
|
| 261 |
+
- [Frontmatter Transformation](https://notionconvert.com/blog/how-to-convert-notion-properties-to-frontmatter-with-notion-to-md-v4/)
|
app/scripts/notion-to-mdx/custom-code-renderer.mjs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Custom Code Block Renderer for notion-to-md
|
| 5 |
+
* Fixes the issue where code blocks end with "text" instead of proper closing
|
| 6 |
+
*/
|
| 7 |
+
|
| 8 |
+
export function createCustomCodeRenderer() {
|
| 9 |
+
return {
|
| 10 |
+
name: 'custom-code-renderer',
|
| 11 |
+
type: 'renderer',
|
| 12 |
+
|
| 13 |
+
/**
|
| 14 |
+
* Custom renderer for code blocks
|
| 15 |
+
* @param {Object} block - Notion code block
|
| 16 |
+
* @returns {string} - Properly formatted markdown code block
|
| 17 |
+
*/
|
| 18 |
+
code: (block) => {
|
| 19 |
+
const { language, rich_text } = block.code;
|
| 20 |
+
|
| 21 |
+
// Extract the actual code content from rich_text
|
| 22 |
+
const codeContent = rich_text
|
| 23 |
+
.map(text => text.plain_text)
|
| 24 |
+
.join('');
|
| 25 |
+
|
| 26 |
+
// Determine the language (default to empty string if not specified)
|
| 27 |
+
const lang = language || '';
|
| 28 |
+
|
| 29 |
+
// Return properly formatted markdown code block
|
| 30 |
+
return `\`\`\`${lang}\n${codeContent}\n\`\`\``;
|
| 31 |
+
}
|
| 32 |
+
};
|
| 33 |
+
}
|
app/scripts/notion-to-mdx/debug-properties.mjs
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
import { config } from 'dotenv';
|
| 4 |
+
import { Client } from '@notionhq/client';
|
| 5 |
+
|
| 6 |
+
// Load environment variables from .env file
|
| 7 |
+
config();
|
| 8 |
+
|
| 9 |
+
const notion = new Client({
|
| 10 |
+
auth: process.env.NOTION_TOKEN,
|
| 11 |
+
});
|
| 12 |
+
|
| 13 |
+
async function debugPageProperties() {
|
| 14 |
+
const pageId = '27877f1c9c9d804d9c82f7b3905578ff';
|
| 15 |
+
|
| 16 |
+
try {
|
| 17 |
+
console.log('🔍 Debugging page properties...');
|
| 18 |
+
console.log(`📄 Page ID: ${pageId}`);
|
| 19 |
+
|
| 20 |
+
const page = await notion.pages.retrieve({ page_id: pageId });
|
| 21 |
+
|
| 22 |
+
console.log('\n📋 Available properties:');
|
| 23 |
+
console.log('========================');
|
| 24 |
+
|
| 25 |
+
for (const [key, value] of Object.entries(page.properties)) {
|
| 26 |
+
console.log(`\n🔹 ${key}:`);
|
| 27 |
+
console.log(` Type: ${value.type}`);
|
| 28 |
+
|
| 29 |
+
switch (value.type) {
|
| 30 |
+
case 'title':
|
| 31 |
+
console.log(` Value: "${value.title.map(t => t.plain_text).join('')}"`);
|
| 32 |
+
break;
|
| 33 |
+
case 'rich_text':
|
| 34 |
+
console.log(` Value: "${value.rich_text.map(t => t.plain_text).join('')}"`);
|
| 35 |
+
break;
|
| 36 |
+
case 'people':
|
| 37 |
+
console.log(` People: ${value.people.map(p => p.name || p.id).join(', ')}`);
|
| 38 |
+
break;
|
| 39 |
+
case 'select':
|
| 40 |
+
console.log(` Value: ${value.select?.name || 'null'}`);
|
| 41 |
+
break;
|
| 42 |
+
case 'multi_select':
|
| 43 |
+
console.log(` Values: [${value.multi_select.map(s => s.name).join(', ')}]`);
|
| 44 |
+
break;
|
| 45 |
+
case 'date':
|
| 46 |
+
console.log(` Value: ${value.date?.start || 'null'}`);
|
| 47 |
+
break;
|
| 48 |
+
case 'checkbox':
|
| 49 |
+
console.log(` Value: ${value.checkbox}`);
|
| 50 |
+
break;
|
| 51 |
+
case 'url':
|
| 52 |
+
console.log(` Value: ${value.url || 'null'}`);
|
| 53 |
+
break;
|
| 54 |
+
case 'email':
|
| 55 |
+
console.log(` Value: ${value.email || 'null'}`);
|
| 56 |
+
break;
|
| 57 |
+
case 'phone_number':
|
| 58 |
+
console.log(` Value: ${value.phone_number || 'null'}`);
|
| 59 |
+
break;
|
| 60 |
+
case 'number':
|
| 61 |
+
console.log(` Value: ${value.number || 'null'}`);
|
| 62 |
+
break;
|
| 63 |
+
case 'created_time':
|
| 64 |
+
console.log(` Value: ${value.created_time}`);
|
| 65 |
+
break;
|
| 66 |
+
case 'created_by':
|
| 67 |
+
console.log(` Value: ${value.created_by?.id || 'null'}`);
|
| 68 |
+
break;
|
| 69 |
+
case 'last_edited_time':
|
| 70 |
+
console.log(` Value: ${value.last_edited_time}`);
|
| 71 |
+
break;
|
| 72 |
+
case 'last_edited_by':
|
| 73 |
+
console.log(` Value: ${value.last_edited_by?.id || 'null'}`);
|
| 74 |
+
break;
|
| 75 |
+
default:
|
| 76 |
+
console.log(` Value: ${JSON.stringify(value, null, 2)}`);
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
console.log('\n✅ Properties debug completed!');
|
| 81 |
+
|
| 82 |
+
} catch (error) {
|
| 83 |
+
console.error('❌ Error:', error.message);
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
debugPageProperties();
|
app/scripts/notion-to-mdx/env.example
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Notion to MDX Toolkit - Environment Variables
|
| 2 |
+
# Copy this file to .env and fill in your actual values
|
| 3 |
+
|
| 4 |
+
# ===========================================
|
| 5 |
+
# NOTION API CONFIGURATION
|
| 6 |
+
# ===========================================
|
| 7 |
+
|
| 8 |
+
# Your Notion Integration Token
|
| 9 |
+
# Get this from: https://www.notion.so/my-integrations
|
| 10 |
+
# Format: secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
| 11 |
+
NOTION_TOKEN=secret_your_notion_integration_token_here
|
| 12 |
+
|
| 13 |
+
# ===========================================
|
| 14 |
+
# OPTIONAL CONFIGURATION
|
| 15 |
+
# ===========================================
|
| 16 |
+
|
| 17 |
+
# Custom output directory (optional)
|
| 18 |
+
# Default: ./output
|
| 19 |
+
# OUTPUT_DIR=./my-custom-output
|
| 20 |
+
|
| 21 |
+
# Custom input configuration file (optional)
|
| 22 |
+
# Default: ./input/pages.json
|
| 23 |
+
# INPUT_CONFIG=./my-pages.json
|
| 24 |
+
|
| 25 |
+
# ===========================================
|
| 26 |
+
# USAGE EXAMPLES
|
| 27 |
+
# ===========================================
|
| 28 |
+
|
| 29 |
+
# 1. Basic usage:
|
| 30 |
+
# NOTION_TOKEN=secret_xxx node index.mjs
|
| 31 |
+
|
| 32 |
+
# 2. With custom paths:
|
| 33 |
+
# NOTION_TOKEN=secret_xxx OUTPUT_DIR=./converted node index.mjs
|
| 34 |
+
|
| 35 |
+
# 3. Test access to a page:
|
| 36 |
+
# NOTION_TOKEN=secret_xxx node test-access.mjs
|
| 37 |
+
|
| 38 |
+
# ===========================================
|
| 39 |
+
# SETUP INSTRUCTIONS
|
| 40 |
+
# ===========================================
|
| 41 |
+
|
| 42 |
+
# 1. Create a Notion integration:
|
| 43 |
+
# - Go to https://www.notion.so/my-integrations
|
| 44 |
+
# - Click "New integration"
|
| 45 |
+
# - Give it a name (e.g., "MDX Converter")
|
| 46 |
+
# - Select your workspace
|
| 47 |
+
# - Click "Submit"
|
| 48 |
+
# - Copy the "Internal Integration Token"
|
| 49 |
+
|
| 50 |
+
# 2. Share your Notion pages with the integration:
|
| 51 |
+
# - Open your Notion page
|
| 52 |
+
# - Click "Share" (top right)
|
| 53 |
+
# - Click "Invite"
|
| 54 |
+
# - Search for your integration name
|
| 55 |
+
# - Select it and give "Can read content" permission
|
| 56 |
+
# - Click "Invite"
|
| 57 |
+
|
| 58 |
+
# 3. Configure your pages in input/pages.json:
|
| 59 |
+
# {
|
| 60 |
+
# "pages": [
|
| 61 |
+
# {
|
| 62 |
+
# "id": "your-notion-page-id",
|
| 63 |
+
# "title": "Page Title",
|
| 64 |
+
# "slug": "page-slug"
|
| 65 |
+
# }
|
| 66 |
+
# ]
|
| 67 |
+
# }
|
| 68 |
+
|
| 69 |
+
# 4. Run the conversion:
|
| 70 |
+
# cp env.example .env
|
| 71 |
+
# # Edit .env with your actual token
|
| 72 |
+
# node index.mjs --clean
|
app/scripts/notion-to-mdx/index.mjs
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
import { config } from 'dotenv';
|
| 4 |
+
import { join, dirname, basename } from 'path';
|
| 5 |
+
import { fileURLToPath } from 'url';
|
| 6 |
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync } from 'fs';
|
| 7 |
+
import { convertNotionToMarkdown } from './notion-converter.mjs';
|
| 8 |
+
import { convertToMdx } from './mdx-converter.mjs';
|
| 9 |
+
|
| 10 |
+
// Load environment variables from .env file
|
| 11 |
+
config();
|
| 12 |
+
|
| 13 |
+
const __filename = fileURLToPath(import.meta.url);
|
| 14 |
+
const __dirname = dirname(__filename);
|
| 15 |
+
|
| 16 |
+
// Default configuration
|
| 17 |
+
const DEFAULT_INPUT = join(__dirname, 'input', 'pages.json');
|
| 18 |
+
const DEFAULT_OUTPUT = join(__dirname, 'output');
|
| 19 |
+
const ASTRO_CONTENT_PATH = join(__dirname, '..', '..', 'src', 'content', 'article.mdx');
|
| 20 |
+
const ASTRO_ASSETS_PATH = join(__dirname, '..', '..', 'src', 'content', 'assets', 'image');
|
| 21 |
+
const ASTRO_BIB_PATH = join(__dirname, '..', '..', 'src', 'content', 'bibliography.bib');
|
| 22 |
+
|
| 23 |
+
function parseArgs() {
|
| 24 |
+
const args = process.argv.slice(2);
|
| 25 |
+
const config = {
|
| 26 |
+
input: DEFAULT_INPUT,
|
| 27 |
+
output: DEFAULT_OUTPUT,
|
| 28 |
+
clean: false,
|
| 29 |
+
notionOnly: false,
|
| 30 |
+
mdxOnly: false,
|
| 31 |
+
token: process.env.NOTION_TOKEN
|
| 32 |
+
};
|
| 33 |
+
|
| 34 |
+
for (const arg of args) {
|
| 35 |
+
if (arg.startsWith('--input=')) {
|
| 36 |
+
config.input = arg.split('=')[1];
|
| 37 |
+
} else if (arg.startsWith('--output=')) {
|
| 38 |
+
config.output = arg.split('=')[1];
|
| 39 |
+
} else if (arg.startsWith('--token=')) {
|
| 40 |
+
config.token = arg.split('=')[1];
|
| 41 |
+
} else if (arg === '--clean') {
|
| 42 |
+
config.clean = true;
|
| 43 |
+
} else if (arg === '--notion-only') {
|
| 44 |
+
config.notionOnly = true;
|
| 45 |
+
} else if (arg === '--mdx-only') {
|
| 46 |
+
config.mdxOnly = true;
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
return config;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
function showHelp() {
|
| 54 |
+
console.log(`
|
| 55 |
+
🚀 Notion to MDX Toolkit
|
| 56 |
+
|
| 57 |
+
Usage:
|
| 58 |
+
node index.mjs [options]
|
| 59 |
+
|
| 60 |
+
Options:
|
| 61 |
+
--input=PATH Input pages configuration file (default: input/pages.json)
|
| 62 |
+
--output=PATH Output directory (default: output/)
|
| 63 |
+
--token=TOKEN Notion API token (or set NOTION_TOKEN env var)
|
| 64 |
+
--clean Clean output directory before processing
|
| 65 |
+
--notion-only Only convert Notion to Markdown (skip MDX conversion)
|
| 66 |
+
--mdx-only Only convert existing Markdown to MDX
|
| 67 |
+
--help, -h Show this help
|
| 68 |
+
|
| 69 |
+
Environment Variables:
|
| 70 |
+
NOTION_TOKEN Your Notion integration token
|
| 71 |
+
|
| 72 |
+
Examples:
|
| 73 |
+
# Full conversion workflow
|
| 74 |
+
NOTION_TOKEN=your_token node index.mjs --clean
|
| 75 |
+
|
| 76 |
+
# Only convert Notion pages to Markdown
|
| 77 |
+
node index.mjs --notion-only --token=your_token
|
| 78 |
+
|
| 79 |
+
# Only convert existing Markdown to MDX
|
| 80 |
+
node index.mjs --mdx-only
|
| 81 |
+
|
| 82 |
+
# Custom paths
|
| 83 |
+
node index.mjs --input=my-pages.json --output=converted/ --token=your_token
|
| 84 |
+
|
| 85 |
+
Configuration File Format (pages.json):
|
| 86 |
+
{
|
| 87 |
+
"pages": [
|
| 88 |
+
{
|
| 89 |
+
"id": "your-notion-page-id",
|
| 90 |
+
"title": "Page Title",
|
| 91 |
+
"slug": "page-slug"
|
| 92 |
+
}
|
| 93 |
+
]
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
Workflow:
|
| 97 |
+
1. Notion → Markdown (with media download)
|
| 98 |
+
2. Markdown → MDX (with Astro components)
|
| 99 |
+
3. Copy to Astro content directory
|
| 100 |
+
`);
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
function ensureDirectory(dir) {
|
| 104 |
+
if (!existsSync(dir)) {
|
| 105 |
+
mkdirSync(dir, { recursive: true });
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
async function cleanDirectory(dir) {
|
| 110 |
+
if (existsSync(dir)) {
|
| 111 |
+
const { execSync } = await import('child_process');
|
| 112 |
+
execSync(`rm -rf "${dir}"/*`, { stdio: 'inherit' });
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
function readPagesConfig(inputFile) {
|
| 117 |
+
try {
|
| 118 |
+
const content = readFileSync(inputFile, 'utf8');
|
| 119 |
+
return JSON.parse(content);
|
| 120 |
+
} catch (error) {
|
| 121 |
+
console.error(`❌ Error reading pages config: ${error.message}`);
|
| 122 |
+
return { pages: [] };
|
| 123 |
+
}
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
function copyToAstroContent(outputDir) {
|
| 127 |
+
console.log('📋 Copying MDX files to Astro content directory...');
|
| 128 |
+
|
| 129 |
+
try {
|
| 130 |
+
// Ensure Astro directories exist
|
| 131 |
+
mkdirSync(dirname(ASTRO_CONTENT_PATH), { recursive: true });
|
| 132 |
+
mkdirSync(ASTRO_ASSETS_PATH, { recursive: true });
|
| 133 |
+
|
| 134 |
+
// Copy MDX file
|
| 135 |
+
const files = readdirSync(outputDir);
|
| 136 |
+
const mdxFiles = files.filter(file => file.endsWith('.mdx'));
|
| 137 |
+
if (mdxFiles.length > 0) {
|
| 138 |
+
const mdxFile = join(outputDir, mdxFiles[0]); // Take the first MDX file
|
| 139 |
+
copyFileSync(mdxFile, ASTRO_CONTENT_PATH);
|
| 140 |
+
console.log(` ✅ Copied MDX to ${ASTRO_CONTENT_PATH}`);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
// Copy images
|
| 144 |
+
const mediaDir = join(outputDir, 'media');
|
| 145 |
+
if (existsSync(mediaDir)) {
|
| 146 |
+
const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.svg'];
|
| 147 |
+
let imageCount = 0;
|
| 148 |
+
|
| 149 |
+
function copyImagesRecursively(dir) {
|
| 150 |
+
const files = readdirSync(dir);
|
| 151 |
+
for (const file of files) {
|
| 152 |
+
const filePath = join(dir, file);
|
| 153 |
+
const stat = statSync(filePath);
|
| 154 |
+
|
| 155 |
+
if (stat.isDirectory()) {
|
| 156 |
+
copyImagesRecursively(filePath);
|
| 157 |
+
} else if (imageExtensions.some(ext => file.toLowerCase().endsWith(ext))) {
|
| 158 |
+
const filename = basename(filePath);
|
| 159 |
+
const destPath = join(ASTRO_ASSETS_PATH, filename);
|
| 160 |
+
copyFileSync(filePath, destPath);
|
| 161 |
+
imageCount++;
|
| 162 |
+
}
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
copyImagesRecursively(mediaDir);
|
| 167 |
+
console.log(` ✅ Copied ${imageCount} image(s) to ${ASTRO_ASSETS_PATH}`);
|
| 168 |
+
|
| 169 |
+
// Update image paths in MDX file
|
| 170 |
+
const mdxContent = readFileSync(ASTRO_CONTENT_PATH, 'utf8');
|
| 171 |
+
let updatedContent = mdxContent.replace(/\.\/media\//g, './assets/image/');
|
| 172 |
+
// Remove the subdirectory from image paths since we copy images directly to assets/image/
|
| 173 |
+
updatedContent = updatedContent.replace(/\.\/assets\/image\/[^\/]+\//g, './assets/image/');
|
| 174 |
+
writeFileSync(ASTRO_CONTENT_PATH, updatedContent);
|
| 175 |
+
console.log(` ✅ Updated image paths in MDX file`);
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
// Create empty bibliography.bib
|
| 179 |
+
writeFileSync(ASTRO_BIB_PATH, '');
|
| 180 |
+
console.log(` ✅ Created empty bibliography at ${ASTRO_BIB_PATH}`);
|
| 181 |
+
|
| 182 |
+
} catch (error) {
|
| 183 |
+
console.warn(` ⚠️ Failed to copy to Astro: ${error.message}`);
|
| 184 |
+
}
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
async function main() {
|
| 189 |
+
const args = process.argv.slice(2);
|
| 190 |
+
|
| 191 |
+
if (args.includes('--help') || args.includes('-h')) {
|
| 192 |
+
showHelp();
|
| 193 |
+
process.exit(0);
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
const config = parseArgs();
|
| 197 |
+
|
| 198 |
+
console.log('🚀 Notion to MDX Toolkit');
|
| 199 |
+
console.log('========================');
|
| 200 |
+
|
| 201 |
+
try {
|
| 202 |
+
if (config.clean) {
|
| 203 |
+
console.log('🧹 Cleaning output directory...');
|
| 204 |
+
await cleanDirectory(config.output);
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
if (config.mdxOnly) {
|
| 208 |
+
// Only convert existing Markdown to MDX
|
| 209 |
+
console.log('📝 MDX conversion only mode');
|
| 210 |
+
await convertToMdx(config.output, config.output);
|
| 211 |
+
copyToAstroContent(config.output);
|
| 212 |
+
|
| 213 |
+
} else if (config.notionOnly) {
|
| 214 |
+
// Only convert Notion to Markdown
|
| 215 |
+
console.log('📄 Notion conversion only mode');
|
| 216 |
+
await convertNotionToMarkdown(config.input, config.output, config.token);
|
| 217 |
+
|
| 218 |
+
} else {
|
| 219 |
+
// Full workflow
|
| 220 |
+
console.log('🔄 Full conversion workflow');
|
| 221 |
+
|
| 222 |
+
// Step 1: Convert Notion to Markdown
|
| 223 |
+
console.log('\n📄 Step 1: Converting Notion pages to Markdown...');
|
| 224 |
+
await convertNotionToMarkdown(config.input, config.output, config.token);
|
| 225 |
+
|
| 226 |
+
// Step 2: Convert Markdown to MDX with Notion metadata
|
| 227 |
+
console.log('\n📝 Step 2: Converting Markdown to MDX...');
|
| 228 |
+
const pagesConfig = readPagesConfig(config.input);
|
| 229 |
+
const firstPage = pagesConfig.pages && pagesConfig.pages.length > 0 ? pagesConfig.pages[0] : null;
|
| 230 |
+
const pageId = firstPage ? firstPage.id : null;
|
| 231 |
+
await convertToMdx(config.output, config.output, pageId, config.token);
|
| 232 |
+
|
| 233 |
+
// Step 3: Copy to Astro content directory
|
| 234 |
+
console.log('\n📋 Step 3: Copying to Astro content directory...');
|
| 235 |
+
copyToAstroContent(config.output);
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
console.log('\n🎉 Conversion completed successfully!');
|
| 239 |
+
|
| 240 |
+
} catch (error) {
|
| 241 |
+
console.error('❌ Error:', error.message);
|
| 242 |
+
process.exit(1);
|
| 243 |
+
}
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
// Export functions for use as module
|
| 247 |
+
export { convertNotionToMarkdown, convertToMdx };
|
| 248 |
+
|
| 249 |
+
// Run CLI if called directly
|
| 250 |
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
| 251 |
+
main();
|
| 252 |
+
}
|
app/scripts/notion-to-mdx/input/pages.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2d51fba4ce9b05562f5df611a150e3cd702b487d2e608441318336556e0f248a
|
| 3 |
+
size 188
|
app/scripts/notion-to-mdx/mdx-converter.mjs
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'fs';
|
| 4 |
+
import { join, dirname, basename, extname } from 'path';
|
| 5 |
+
import { fileURLToPath } from 'url';
|
| 6 |
+
import matter from 'gray-matter';
|
| 7 |
+
import { extractAndGenerateNotionFrontmatter } from './notion-metadata-extractor.mjs';
|
| 8 |
+
|
| 9 |
+
const __filename = fileURLToPath(import.meta.url);
|
| 10 |
+
const __dirname = dirname(__filename);
|
| 11 |
+
|
| 12 |
+
// Configuration
|
| 13 |
+
const DEFAULT_INPUT = join(__dirname, 'output');
|
| 14 |
+
const DEFAULT_OUTPUT = join(__dirname, 'output');
|
| 15 |
+
|
| 16 |
+
function parseArgs() {
|
| 17 |
+
const args = process.argv.slice(2);
|
| 18 |
+
const config = {
|
| 19 |
+
input: DEFAULT_INPUT,
|
| 20 |
+
output: DEFAULT_OUTPUT,
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
for (const arg of args) {
|
| 24 |
+
if (arg.startsWith('--input=')) {
|
| 25 |
+
config.input = arg.substring('--input='.length);
|
| 26 |
+
} else if (arg.startsWith('--output=')) {
|
| 27 |
+
config.output = arg.substring('--output='.length);
|
| 28 |
+
} else if (arg === '--help' || arg === '-h') {
|
| 29 |
+
console.log(`
|
| 30 |
+
📝 Notion Markdown to MDX Converter
|
| 31 |
+
|
| 32 |
+
Usage:
|
| 33 |
+
node mdx-converter.mjs [options]
|
| 34 |
+
|
| 35 |
+
Options:
|
| 36 |
+
--input=PATH Input directory or file (default: ${DEFAULT_INPUT})
|
| 37 |
+
--output=PATH Output directory (default: ${DEFAULT_OUTPUT})
|
| 38 |
+
--help, -h Show this help
|
| 39 |
+
|
| 40 |
+
Examples:
|
| 41 |
+
# Convert all markdown files in output directory
|
| 42 |
+
node mdx-converter.mjs
|
| 43 |
+
|
| 44 |
+
# Convert specific file
|
| 45 |
+
node mdx-converter.mjs --input=article.md --output=converted/
|
| 46 |
+
|
| 47 |
+
# Convert directory
|
| 48 |
+
node mdx-converter.mjs --input=markdown-files/ --output=mdx-files/
|
| 49 |
+
`);
|
| 50 |
+
process.exit(0);
|
| 51 |
+
} else if (!config.input) {
|
| 52 |
+
config.input = arg;
|
| 53 |
+
} else if (!config.output) {
|
| 54 |
+
config.output = arg;
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
return config;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
/**
|
| 61 |
+
* Track which Astro components are used during transformations
|
| 62 |
+
*/
|
| 63 |
+
const usedComponents = new Set();
|
| 64 |
+
|
| 65 |
+
/**
|
| 66 |
+
* Track individual image imports needed
|
| 67 |
+
*/
|
| 68 |
+
const imageImports = new Map(); // src -> varName
|
| 69 |
+
|
| 70 |
+
/**
|
| 71 |
+
* Generate a variable name from image path
|
| 72 |
+
* @param {string} src - Image source path
|
| 73 |
+
* @returns {string} - Valid variable name
|
| 74 |
+
*/
|
| 75 |
+
function generateImageVarName(src) {
|
| 76 |
+
// Extract filename without extension and make it a valid JS variable
|
| 77 |
+
const filename = src.split('/').pop().replace(/\.[^.]+$/, '');
|
| 78 |
+
return filename.replace(/[^a-zA-Z0-9]/g, '_').replace(/^[0-9]/, 'img_$&');
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
/**
|
| 82 |
+
* Add required component imports to the frontmatter
|
| 83 |
+
* @param {string} content - MDX content
|
| 84 |
+
* @returns {string} - Content with component imports
|
| 85 |
+
*/
|
| 86 |
+
function addComponentImports(content) {
|
| 87 |
+
console.log(' 📦 Adding component and image imports...');
|
| 88 |
+
|
| 89 |
+
let imports = [];
|
| 90 |
+
|
| 91 |
+
// Add component imports
|
| 92 |
+
if (usedComponents.size > 0) {
|
| 93 |
+
const componentImports = Array.from(usedComponents)
|
| 94 |
+
.map(component => `import ${component} from '../components/${component}.astro';`);
|
| 95 |
+
imports.push(...componentImports);
|
| 96 |
+
console.log(` ✅ Importing components: ${Array.from(usedComponents).join(', ')}`);
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
// Add image imports
|
| 100 |
+
if (imageImports.size > 0) {
|
| 101 |
+
const imageImportStatements = Array.from(imageImports.entries())
|
| 102 |
+
.map(([src, varName]) => `import ${varName} from '${src}';`);
|
| 103 |
+
imports.push(...imageImportStatements);
|
| 104 |
+
console.log(` ✅ Importing ${imageImports.size} image(s)`);
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
if (imports.length === 0) {
|
| 108 |
+
console.log(' ℹ️ No imports needed');
|
| 109 |
+
return content;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
const importBlock = imports.join('\n');
|
| 113 |
+
|
| 114 |
+
// Insert imports after frontmatter
|
| 115 |
+
const frontmatterEnd = content.indexOf('---', 3) + 3;
|
| 116 |
+
if (frontmatterEnd > 2) {
|
| 117 |
+
return content.slice(0, frontmatterEnd) + '\n\n' + importBlock + '\n' + content.slice(frontmatterEnd);
|
| 118 |
+
} else {
|
| 119 |
+
// No frontmatter, add at beginning
|
| 120 |
+
return importBlock + '\n\n' + content;
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
/**
|
| 125 |
+
* Transform Notion images to Figure components
|
| 126 |
+
* @param {string} content - MDX content
|
| 127 |
+
* @returns {string} - Content with Figure components
|
| 128 |
+
*/
|
| 129 |
+
function transformImages(content) {
|
| 130 |
+
console.log(' 🖼️ Transforming images to Figure components...');
|
| 131 |
+
|
| 132 |
+
let hasImages = false;
|
| 133 |
+
|
| 134 |
+
// Helper function to clean source paths
|
| 135 |
+
const cleanSrcPath = (src) => {
|
| 136 |
+
// Convert Notion media paths to relative paths
|
| 137 |
+
return src.replace(/^\/media\//, './media/')
|
| 138 |
+
.replace(/^\.\/media\//, './media/');
|
| 139 |
+
};
|
| 140 |
+
|
| 141 |
+
// Helper to clean caption text
|
| 142 |
+
const cleanCaption = (caption) => {
|
| 143 |
+
return caption
|
| 144 |
+
.replace(/<[^>]*>/g, '') // Remove HTML tags
|
| 145 |
+
.replace(/\n/g, ' ') // Replace newlines with spaces
|
| 146 |
+
.replace(/\r/g, ' ') // Replace carriage returns with spaces
|
| 147 |
+
.replace(/\s+/g, ' ') // Replace multiple spaces with single space
|
| 148 |
+
.replace(/'/g, "\\'") // Escape quotes
|
| 149 |
+
.trim(); // Trim whitespace
|
| 150 |
+
};
|
| 151 |
+
|
| 152 |
+
// Helper to clean alt text
|
| 153 |
+
const cleanAltText = (alt, maxLength = 100) => {
|
| 154 |
+
const cleaned = alt
|
| 155 |
+
.replace(/<[^>]*>/g, '') // Remove HTML tags
|
| 156 |
+
.replace(/\n/g, ' ') // Replace newlines with spaces
|
| 157 |
+
.replace(/\r/g, ' ') // Replace carriage returns with spaces
|
| 158 |
+
.replace(/\s+/g, ' ') // Replace multiple spaces with single space
|
| 159 |
+
.trim(); // Trim whitespace
|
| 160 |
+
|
| 161 |
+
return cleaned.length > maxLength
|
| 162 |
+
? cleaned.substring(0, maxLength) + '...'
|
| 163 |
+
: cleaned;
|
| 164 |
+
};
|
| 165 |
+
|
| 166 |
+
// Create Figure component with import
|
| 167 |
+
const createFigureComponent = (src, alt = '', caption = '') => {
|
| 168 |
+
const cleanSrc = cleanSrcPath(src);
|
| 169 |
+
|
| 170 |
+
// Skip PDF URLs and external URLs - they should remain as links only
|
| 171 |
+
if (cleanSrc.includes('.pdf') || cleanSrc.includes('arxiv.org/pdf') ||
|
| 172 |
+
(cleanSrc.startsWith('http') && !cleanSrc.includes('/media/'))) {
|
| 173 |
+
console.log(` ⚠️ Skipping external/PDF URL: ${cleanSrc}`);
|
| 174 |
+
// Return the original markdown image syntax for external URLs
|
| 175 |
+
return ``;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
const varName = generateImageVarName(cleanSrc);
|
| 179 |
+
imageImports.set(cleanSrc, varName);
|
| 180 |
+
usedComponents.add('Figure');
|
| 181 |
+
|
| 182 |
+
const props = [];
|
| 183 |
+
props.push(`src={${varName}}`);
|
| 184 |
+
props.push('zoomable');
|
| 185 |
+
props.push('downloadable');
|
| 186 |
+
props.push('layout="fixed"');
|
| 187 |
+
if (alt) props.push(`alt="${alt}"`);
|
| 188 |
+
if (caption) props.push(`caption={'${caption}'}`);
|
| 189 |
+
|
| 190 |
+
return `<Figure\n ${props.join('\n ')}\n/>`;
|
| 191 |
+
};
|
| 192 |
+
|
| 193 |
+
// Transform markdown images: 
|
| 194 |
+
content = content.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => {
|
| 195 |
+
const cleanSrc = cleanSrcPath(src);
|
| 196 |
+
const cleanAlt = cleanAltText(alt || 'Figure');
|
| 197 |
+
hasImages = true;
|
| 198 |
+
|
| 199 |
+
return createFigureComponent(cleanSrc, cleanAlt);
|
| 200 |
+
});
|
| 201 |
+
|
| 202 |
+
// Transform images with captions (Notion sometimes adds captions as separate text)
|
| 203 |
+
content = content.replace(/!\[([^\]]*)\]\(([^)]+)\)\s*\n\s*([^\n]+)/g, (match, alt, src, caption) => {
|
| 204 |
+
const cleanSrc = cleanSrcPath(src);
|
| 205 |
+
const cleanAlt = cleanAltText(alt || 'Figure');
|
| 206 |
+
const cleanCap = cleanCaption(caption);
|
| 207 |
+
hasImages = true;
|
| 208 |
+
|
| 209 |
+
return createFigureComponent(cleanSrc, cleanAlt, cleanCap);
|
| 210 |
+
});
|
| 211 |
+
|
| 212 |
+
if (hasImages) {
|
| 213 |
+
console.log(' ✅ Figure components with imports will be created');
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
return content;
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
/**
|
| 220 |
+
* Transform Notion callouts to Note components
|
| 221 |
+
* @param {string} content - MDX content
|
| 222 |
+
* @returns {string} - Content with Note components
|
| 223 |
+
*/
|
| 224 |
+
function transformCallouts(content) {
|
| 225 |
+
console.log(' 📝 Transforming callouts to Note components...');
|
| 226 |
+
|
| 227 |
+
let transformedCount = 0;
|
| 228 |
+
|
| 229 |
+
// Transform blockquotes that look like Notion callouts
|
| 230 |
+
content = content.replace(/^> \*\*([^*]+)\*\*\s*\n> (.+?)(?=\n> \*\*|\n\n|\n$)/gms, (match, title, content) => {
|
| 231 |
+
transformedCount++;
|
| 232 |
+
usedComponents.add('Note');
|
| 233 |
+
|
| 234 |
+
const cleanContent = content
|
| 235 |
+
.replace(/^> /gm, '') // Remove blockquote markers
|
| 236 |
+
.replace(/\n+/g, '\n') // Normalize newlines
|
| 237 |
+
.trim();
|
| 238 |
+
|
| 239 |
+
return `<Note type="${title.toLowerCase()}" title="${title}">\n${cleanContent}\n</Note>\n\n`;
|
| 240 |
+
});
|
| 241 |
+
|
| 242 |
+
if (transformedCount > 0) {
|
| 243 |
+
console.log(` ✅ Transformed ${transformedCount} callout(s) to Note components`);
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
return content;
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
/**
|
| 250 |
+
* Transform Notion databases/tables to enhanced table components
|
| 251 |
+
* @param {string} content - MDX content
|
| 252 |
+
* @returns {string} - Content with enhanced tables
|
| 253 |
+
*/
|
| 254 |
+
function transformTables(content) {
|
| 255 |
+
console.log(' 📊 Enhancing tables...');
|
| 256 |
+
|
| 257 |
+
let enhancedCount = 0;
|
| 258 |
+
|
| 259 |
+
// Wrap tables in a container for better styling
|
| 260 |
+
content = content.replace(/^(\|[^|\n]+\|[\s\S]*?)(?=\n\n|\n$)/gm, (match) => {
|
| 261 |
+
if (match.includes('|') && match.split('\n').length > 2) {
|
| 262 |
+
enhancedCount++;
|
| 263 |
+
return `<div class="table-container">\n\n${match}\n\n</div>`;
|
| 264 |
+
}
|
| 265 |
+
return match;
|
| 266 |
+
});
|
| 267 |
+
|
| 268 |
+
if (enhancedCount > 0) {
|
| 269 |
+
console.log(` ✅ Enhanced ${enhancedCount} table(s)`);
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
return content;
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
/**
|
| 276 |
+
* Transform Notion code blocks to enhanced code components
|
| 277 |
+
* @param {string} content - MDX content
|
| 278 |
+
* @returns {string} - Content with enhanced code blocks
|
| 279 |
+
*/
|
| 280 |
+
function transformCodeBlocks(content) {
|
| 281 |
+
console.log(' 💻 Enhancing code blocks...');
|
| 282 |
+
|
| 283 |
+
let enhancedCount = 0;
|
| 284 |
+
|
| 285 |
+
// Add copy functionality to code blocks
|
| 286 |
+
content = content.replace(/^```(\w+)\n([\s\S]*?)\n```$/gm, (match, lang, code) => {
|
| 287 |
+
enhancedCount++;
|
| 288 |
+
return `\`\`\`${lang} copy\n${code}\n\`\`\``;
|
| 289 |
+
});
|
| 290 |
+
|
| 291 |
+
if (enhancedCount > 0) {
|
| 292 |
+
console.log(` ✅ Enhanced ${enhancedCount} code block(s)`);
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
return content;
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
/**
|
| 299 |
+
* Fix Notion-specific formatting issues
|
| 300 |
+
* @param {string} content - MDX content
|
| 301 |
+
* @returns {string} - Content with fixed formatting
|
| 302 |
+
*/
|
| 303 |
+
function fixNotionFormatting(content) {
|
| 304 |
+
console.log(' 🔧 Fixing Notion formatting issues...');
|
| 305 |
+
|
| 306 |
+
let fixedCount = 0;
|
| 307 |
+
|
| 308 |
+
// Fix Notion's toggle lists that don't convert well
|
| 309 |
+
content = content.replace(/^(\s*)•\s*(.+)$/gm, (match, indent, text) => {
|
| 310 |
+
fixedCount++;
|
| 311 |
+
return `${indent}- ${text}`;
|
| 312 |
+
});
|
| 313 |
+
|
| 314 |
+
// Fix Notion's numbered lists that might have issues
|
| 315 |
+
content = content.replace(/^(\s*)\d+\.\s*(.+)$/gm, (match, indent, text) => {
|
| 316 |
+
// Only fix if it's not already properly formatted
|
| 317 |
+
if (!text.includes('\n') || text.split('\n').length === 1) {
|
| 318 |
+
return match; // Keep as is
|
| 319 |
+
}
|
| 320 |
+
fixedCount++;
|
| 321 |
+
return `${indent}1. ${text}`;
|
| 322 |
+
});
|
| 323 |
+
|
| 324 |
+
// Fix Notion's bold/italic combinations
|
| 325 |
+
content = content.replace(/\*\*([^*]+)\*\*([^*]+)\*\*([^*]+)\*\*/g, (match, part1, part2, part3) => {
|
| 326 |
+
fixedCount++;
|
| 327 |
+
return `**${part1}${part2}${part3}**`;
|
| 328 |
+
});
|
| 329 |
+
|
| 330 |
+
if (fixedCount > 0) {
|
| 331 |
+
console.log(` ✅ Fixed ${fixedCount} formatting issue(s)`);
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
return content;
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
/**
|
| 338 |
+
* Ensure proper frontmatter for MDX with Notion metadata
|
| 339 |
+
* @param {string} content - MDX content
|
| 340 |
+
* @param {string} pageId - Notion page ID (optional)
|
| 341 |
+
* @param {string} notionToken - Notion API token (optional)
|
| 342 |
+
* @returns {string} - Content with proper frontmatter
|
| 343 |
+
*/
|
| 344 |
+
async function ensureFrontmatter(content, pageId = null, notionToken = null) {
|
| 345 |
+
console.log(' 📄 Ensuring proper frontmatter...');
|
| 346 |
+
|
| 347 |
+
if (!content.startsWith('---')) {
|
| 348 |
+
let frontmatter;
|
| 349 |
+
|
| 350 |
+
if (pageId && notionToken) {
|
| 351 |
+
try {
|
| 352 |
+
console.log(' 🔍 Extracting Notion metadata...');
|
| 353 |
+
frontmatter = await extractAndGenerateNotionFrontmatter(pageId, notionToken);
|
| 354 |
+
console.log(' ✅ Generated rich frontmatter from Notion');
|
| 355 |
+
} catch (error) {
|
| 356 |
+
console.log(' ⚠️ Failed to extract Notion metadata, using basic frontmatter');
|
| 357 |
+
frontmatter = generateBasicFrontmatter();
|
| 358 |
+
}
|
| 359 |
+
} else {
|
| 360 |
+
frontmatter = generateBasicFrontmatter();
|
| 361 |
+
console.log(' ✅ Generated basic frontmatter');
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
return frontmatter + content;
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
// Parse existing frontmatter and enhance it
|
| 368 |
+
try {
|
| 369 |
+
const { data, content: body } = matter(content);
|
| 370 |
+
|
| 371 |
+
// If we have Notion metadata available, try to enhance the frontmatter
|
| 372 |
+
if (pageId && notionToken && (!data.notion_id || data.notion_id !== pageId)) {
|
| 373 |
+
try {
|
| 374 |
+
console.log(' 🔍 Enhancing frontmatter with Notion metadata...');
|
| 375 |
+
const notionFrontmatter = await extractAndGenerateNotionFrontmatter(pageId, notionToken);
|
| 376 |
+
const { data: notionData } = matter(notionFrontmatter);
|
| 377 |
+
|
| 378 |
+
// Merge Notion metadata with existing frontmatter
|
| 379 |
+
const enhancedData = { ...data, ...notionData };
|
| 380 |
+
const enhancedContent = matter.stringify(body, enhancedData);
|
| 381 |
+
console.log(' ✅ Enhanced frontmatter with Notion metadata');
|
| 382 |
+
return enhancedContent;
|
| 383 |
+
} catch (error) {
|
| 384 |
+
console.log(' ⚠️ Could not enhance with Notion metadata, keeping existing');
|
| 385 |
+
}
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
// Ensure required fields
|
| 389 |
+
if (!data.title) data.title = 'Notion Article';
|
| 390 |
+
if (!data.published) data.published = new Date().toISOString().split('T')[0];
|
| 391 |
+
if (!data.tableOfContentsAutoCollapse) data.tableOfContentsAutoCollapse = true;
|
| 392 |
+
|
| 393 |
+
const enhancedContent = matter.stringify(body, data);
|
| 394 |
+
console.log(' ✅ Enhanced existing frontmatter');
|
| 395 |
+
return enhancedContent;
|
| 396 |
+
} catch (error) {
|
| 397 |
+
console.log(' ⚠️ Could not parse frontmatter, keeping as is');
|
| 398 |
+
return content;
|
| 399 |
+
}
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
/**
|
| 403 |
+
* Generate basic frontmatter
|
| 404 |
+
* @returns {string} - Basic frontmatter
|
| 405 |
+
*/
|
| 406 |
+
function generateBasicFrontmatter() {
|
| 407 |
+
const currentDate = new Date().toLocaleDateString('en-US', {
|
| 408 |
+
year: 'numeric',
|
| 409 |
+
month: 'short',
|
| 410 |
+
day: '2-digit'
|
| 411 |
+
});
|
| 412 |
+
return `---
|
| 413 |
+
title: "Notion Article"
|
| 414 |
+
published: "${currentDate}"
|
| 415 |
+
tableOfContentsAutoCollapse: true
|
| 416 |
+
---
|
| 417 |
+
|
| 418 |
+
`;
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
/**
|
| 422 |
+
* Main MDX processing function that applies all transformations
|
| 423 |
+
* @param {string} content - Raw Markdown content
|
| 424 |
+
* @param {string} pageId - Notion page ID (optional)
|
| 425 |
+
* @param {string} notionToken - Notion API token (optional)
|
| 426 |
+
* @returns {string} - Processed MDX content compatible with Astro
|
| 427 |
+
*/
|
| 428 |
+
async function processMdxContent(content, pageId = null, notionToken = null) {
|
| 429 |
+
console.log('🔧 Processing for Astro MDX compatibility...');
|
| 430 |
+
|
| 431 |
+
// Clear previous tracking
|
| 432 |
+
usedComponents.clear();
|
| 433 |
+
imageImports.clear();
|
| 434 |
+
|
| 435 |
+
let processedContent = content;
|
| 436 |
+
|
| 437 |
+
// Apply each transformation step sequentially
|
| 438 |
+
processedContent = await ensureFrontmatter(processedContent, pageId, notionToken);
|
| 439 |
+
processedContent = fixNotionFormatting(processedContent);
|
| 440 |
+
processedContent = transformCallouts(processedContent);
|
| 441 |
+
processedContent = transformImages(processedContent);
|
| 442 |
+
processedContent = transformTables(processedContent);
|
| 443 |
+
processedContent = transformCodeBlocks(processedContent);
|
| 444 |
+
|
| 445 |
+
// Add component imports at the end
|
| 446 |
+
processedContent = addComponentImports(processedContent);
|
| 447 |
+
|
| 448 |
+
return processedContent;
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
/**
|
| 452 |
+
* Convert a single markdown file to MDX
|
| 453 |
+
* @param {string} inputFile - Input markdown file
|
| 454 |
+
* @param {string} outputDir - Output directory
|
| 455 |
+
* @param {string} pageId - Notion page ID (optional)
|
| 456 |
+
* @param {string} notionToken - Notion API token (optional)
|
| 457 |
+
*/
|
| 458 |
+
async function convertFileToMdx(inputFile, outputDir, pageId = null, notionToken = null) {
|
| 459 |
+
const filename = basename(inputFile, '.md');
|
| 460 |
+
const outputFile = join(outputDir, `${filename}.mdx`);
|
| 461 |
+
|
| 462 |
+
console.log(`📝 Converting: ${basename(inputFile)} → ${basename(outputFile)}`);
|
| 463 |
+
|
| 464 |
+
try {
|
| 465 |
+
const markdownContent = readFileSync(inputFile, 'utf8');
|
| 466 |
+
const mdxContent = await processMdxContent(markdownContent, pageId, notionToken);
|
| 467 |
+
writeFileSync(outputFile, mdxContent);
|
| 468 |
+
|
| 469 |
+
console.log(` ✅ Converted: ${outputFile}`);
|
| 470 |
+
|
| 471 |
+
// Show file size
|
| 472 |
+
const inputSize = Math.round(markdownContent.length / 1024);
|
| 473 |
+
const outputSize = Math.round(mdxContent.length / 1024);
|
| 474 |
+
console.log(` 📊 Input: ${inputSize}KB → Output: ${outputSize}KB`);
|
| 475 |
+
|
| 476 |
+
} catch (error) {
|
| 477 |
+
console.error(` ❌ Failed to convert ${inputFile}: ${error.message}`);
|
| 478 |
+
}
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
/**
|
| 482 |
+
* Convert all markdown files in a directory to MDX
|
| 483 |
+
* @param {string} inputPath - Input path (file or directory)
|
| 484 |
+
* @param {string} outputDir - Output directory
|
| 485 |
+
* @param {string} pageId - Notion page ID (optional)
|
| 486 |
+
* @param {string} notionToken - Notion API token (optional)
|
| 487 |
+
*/
|
| 488 |
+
async function convertToMdx(inputPath, outputDir, pageId = null, notionToken = null) {
|
| 489 |
+
console.log('📝 Notion Markdown to Astro MDX Converter');
|
| 490 |
+
console.log(`📁 Input: ${inputPath}`);
|
| 491 |
+
console.log(`📁 Output: ${outputDir}`);
|
| 492 |
+
|
| 493 |
+
// Check if input exists
|
| 494 |
+
if (!existsSync(inputPath)) {
|
| 495 |
+
console.error(`❌ Input not found: ${inputPath}`);
|
| 496 |
+
process.exit(1);
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
try {
|
| 500 |
+
// Ensure output directory exists
|
| 501 |
+
if (!existsSync(outputDir)) {
|
| 502 |
+
mkdirSync(outputDir, { recursive: true });
|
| 503 |
+
}
|
| 504 |
+
|
| 505 |
+
let filesToConvert = [];
|
| 506 |
+
|
| 507 |
+
if (statSync(inputPath).isDirectory()) {
|
| 508 |
+
// Convert all .md files in directory
|
| 509 |
+
const files = readdirSync(inputPath);
|
| 510 |
+
filesToConvert = files
|
| 511 |
+
.filter(file => file.endsWith('.md'))
|
| 512 |
+
.map(file => join(inputPath, file));
|
| 513 |
+
} else if (inputPath.endsWith('.md')) {
|
| 514 |
+
// Convert single file
|
| 515 |
+
filesToConvert = [inputPath];
|
| 516 |
+
} else {
|
| 517 |
+
console.error('❌ Input must be a .md file or directory containing .md files');
|
| 518 |
+
process.exit(1);
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
if (filesToConvert.length === 0) {
|
| 522 |
+
console.log('ℹ️ No .md files found to convert');
|
| 523 |
+
return;
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
console.log(`🔄 Found ${filesToConvert.length} file(s) to convert`);
|
| 527 |
+
|
| 528 |
+
// Convert each file
|
| 529 |
+
for (const file of filesToConvert) {
|
| 530 |
+
await convertFileToMdx(file, outputDir, pageId, notionToken);
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
console.log(`✅ Conversion completed! ${filesToConvert.length} file(s) processed`);
|
| 534 |
+
|
| 535 |
+
} catch (error) {
|
| 536 |
+
console.error('❌ Conversion failed:', error.message);
|
| 537 |
+
process.exit(1);
|
| 538 |
+
}
|
| 539 |
+
}
|
| 540 |
+
|
| 541 |
+
export { convertToMdx };
|
| 542 |
+
|
| 543 |
+
function main() {
|
| 544 |
+
const config = parseArgs();
|
| 545 |
+
convertToMdx(config.input, config.output);
|
| 546 |
+
console.log('🎉 MDX conversion completed!');
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
| 550 |
+
main();
|
| 551 |
+
}
|
app/scripts/notion-to-mdx/notion-converter.mjs
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
import { config } from 'dotenv';
|
| 4 |
+
import { Client } from '@notionhq/client';
|
| 5 |
+
import { NotionConverter } from 'notion-to-md';
|
| 6 |
+
import { DefaultExporter } from 'notion-to-md/plugins/exporter';
|
| 7 |
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
| 8 |
+
import { join, dirname, basename } from 'path';
|
| 9 |
+
import { fileURLToPath } from 'url';
|
| 10 |
+
import { postProcessMarkdown } from './post-processor.mjs';
|
| 11 |
+
import { createCustomCodeRenderer } from './custom-code-renderer.mjs';
|
| 12 |
+
|
| 13 |
+
// Load environment variables from .env file
|
| 14 |
+
config();
|
| 15 |
+
|
| 16 |
+
const __filename = fileURLToPath(import.meta.url);
|
| 17 |
+
const __dirname = dirname(__filename);
|
| 18 |
+
|
| 19 |
+
// Configuration
|
| 20 |
+
const DEFAULT_INPUT = join(__dirname, 'input', 'pages.json');
|
| 21 |
+
const DEFAULT_OUTPUT = join(__dirname, 'output');
|
| 22 |
+
|
| 23 |
+
function parseArgs() {
|
| 24 |
+
const args = process.argv.slice(2);
|
| 25 |
+
const config = {
|
| 26 |
+
input: DEFAULT_INPUT,
|
| 27 |
+
output: DEFAULT_OUTPUT,
|
| 28 |
+
clean: false,
|
| 29 |
+
token: process.env.NOTION_TOKEN
|
| 30 |
+
};
|
| 31 |
+
|
| 32 |
+
for (const arg of args) {
|
| 33 |
+
if (arg.startsWith('--input=')) {
|
| 34 |
+
config.input = arg.split('=')[1];
|
| 35 |
+
} else if (arg.startsWith('--output=')) {
|
| 36 |
+
config.output = arg.split('=')[1];
|
| 37 |
+
} else if (arg.startsWith('--token=')) {
|
| 38 |
+
config.token = arg.split('=')[1];
|
| 39 |
+
} else if (arg === '--clean') {
|
| 40 |
+
config.clean = true;
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
return config;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
function ensureDirectory(dir) {
|
| 48 |
+
if (!existsSync(dir)) {
|
| 49 |
+
mkdirSync(dir, { recursive: true });
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
function loadPagesConfig(configFile) {
|
| 54 |
+
if (!existsSync(configFile)) {
|
| 55 |
+
console.error(`❌ Configuration file not found: ${configFile}`);
|
| 56 |
+
console.log('📝 Create a pages.json file with your Notion page IDs:');
|
| 57 |
+
console.log(`
|
| 58 |
+
{
|
| 59 |
+
"pages": [
|
| 60 |
+
{
|
| 61 |
+
"id": "your-notion-page-id-1",
|
| 62 |
+
"title": "Page Title 1",
|
| 63 |
+
"slug": "page-1"
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"id": "your-notion-page-id-2",
|
| 67 |
+
"title": "Page Title 2",
|
| 68 |
+
"slug": "page-2"
|
| 69 |
+
}
|
| 70 |
+
]
|
| 71 |
+
}
|
| 72 |
+
`);
|
| 73 |
+
process.exit(1);
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
try {
|
| 77 |
+
const config = JSON.parse(readFileSync(configFile, 'utf8'));
|
| 78 |
+
return config.pages || [];
|
| 79 |
+
} catch (error) {
|
| 80 |
+
console.error(`❌ Error reading configuration: ${error.message}`);
|
| 81 |
+
process.exit(1);
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
/**
|
| 86 |
+
* Convert a single Notion page to Markdown with advanced media handling
|
| 87 |
+
* @param {Object} notion - Notion client
|
| 88 |
+
* @param {string} pageId - Notion page ID
|
| 89 |
+
* @param {string} outputDir - Output directory
|
| 90 |
+
* @param {string} pageTitle - Page title for file naming
|
| 91 |
+
* @returns {Promise<string>} - Path to generated markdown file
|
| 92 |
+
*/
|
| 93 |
+
async function convertNotionPage(notion, pageId, outputDir, pageTitle) {
|
| 94 |
+
console.log(`📄 Converting Notion page: ${pageTitle} (${pageId})`);
|
| 95 |
+
|
| 96 |
+
try {
|
| 97 |
+
// Create media directory for this page
|
| 98 |
+
const mediaDir = join(outputDir, 'media', pageId);
|
| 99 |
+
ensureDirectory(mediaDir);
|
| 100 |
+
|
| 101 |
+
// Configure the DefaultExporter to save to a file
|
| 102 |
+
const outputFile = join(outputDir, `${pageTitle}.md`);
|
| 103 |
+
const exporter = new DefaultExporter({
|
| 104 |
+
outputType: 'file',
|
| 105 |
+
outputPath: outputFile,
|
| 106 |
+
});
|
| 107 |
+
|
| 108 |
+
// Create the converter with media downloading strategy
|
| 109 |
+
const n2m = new NotionConverter(notion)
|
| 110 |
+
.withExporter(exporter)
|
| 111 |
+
// Download media to local directory with path transformation
|
| 112 |
+
.downloadMediaTo({
|
| 113 |
+
outputDir: mediaDir,
|
| 114 |
+
// Transform paths to be web-accessible
|
| 115 |
+
transformPath: (localPath) => `/media/${pageId}/${basename(localPath)}`,
|
| 116 |
+
});
|
| 117 |
+
|
| 118 |
+
// Convert the page
|
| 119 |
+
const result = await n2m.convert(pageId);
|
| 120 |
+
|
| 121 |
+
console.log(` ✅ Converted to: ${outputFile}`);
|
| 122 |
+
console.log(` 📊 Content length: ${result.content.length} characters`);
|
| 123 |
+
console.log(` 🖼️ Media saved to: ${mediaDir}`);
|
| 124 |
+
|
| 125 |
+
return outputFile;
|
| 126 |
+
|
| 127 |
+
} catch (error) {
|
| 128 |
+
console.error(` ❌ Failed to convert page ${pageId}: ${error.message}`);
|
| 129 |
+
throw error;
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
/**
|
| 134 |
+
* Process Notion pages with advanced configuration
|
| 135 |
+
* @param {string} inputFile - Path to pages configuration
|
| 136 |
+
* @param {string} outputDir - Output directory
|
| 137 |
+
* @param {string} notionToken - Notion API token
|
| 138 |
+
*/
|
| 139 |
+
export async function convertNotionToMarkdown(inputFile, outputDir, notionToken) {
|
| 140 |
+
console.log('🚀 Notion to Markdown Converter');
|
| 141 |
+
console.log(`📁 Input: ${inputFile}`);
|
| 142 |
+
console.log(`📁 Output: ${outputDir}`);
|
| 143 |
+
|
| 144 |
+
// Validate Notion token
|
| 145 |
+
if (!notionToken) {
|
| 146 |
+
console.error('❌ NOTION_TOKEN not found. Please set it as environment variable or use --token=YOUR_TOKEN');
|
| 147 |
+
process.exit(1);
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
// Ensure output directory exists
|
| 151 |
+
ensureDirectory(outputDir);
|
| 152 |
+
|
| 153 |
+
try {
|
| 154 |
+
// Initialize Notion client
|
| 155 |
+
const notion = new Client({
|
| 156 |
+
auth: notionToken,
|
| 157 |
+
});
|
| 158 |
+
|
| 159 |
+
// Load pages configuration
|
| 160 |
+
const pages = loadPagesConfig(inputFile);
|
| 161 |
+
console.log(`📋 Found ${pages.length} page(s) to convert`);
|
| 162 |
+
|
| 163 |
+
const convertedFiles = [];
|
| 164 |
+
|
| 165 |
+
// Convert each page
|
| 166 |
+
for (const page of pages) {
|
| 167 |
+
try {
|
| 168 |
+
const outputFile = await convertNotionPage(
|
| 169 |
+
notion,
|
| 170 |
+
page.id,
|
| 171 |
+
outputDir,
|
| 172 |
+
page.slug || page.title?.toLowerCase().replace(/\s+/g, '-') || page.id
|
| 173 |
+
);
|
| 174 |
+
convertedFiles.push(outputFile);
|
| 175 |
+
} catch (error) {
|
| 176 |
+
console.error(`❌ Failed to convert page ${page.id}: ${error.message}`);
|
| 177 |
+
// Continue with other pages
|
| 178 |
+
}
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
// Post-process all converted files
|
| 182 |
+
console.log('🔧 Post-processing converted files...');
|
| 183 |
+
for (const file of convertedFiles) {
|
| 184 |
+
try {
|
| 185 |
+
let content = readFileSync(file, 'utf8');
|
| 186 |
+
content = postProcessMarkdown(content);
|
| 187 |
+
writeFileSync(file, content);
|
| 188 |
+
console.log(` ✅ Post-processed: ${basename(file)}`);
|
| 189 |
+
} catch (error) {
|
| 190 |
+
console.error(` ❌ Failed to post-process ${file}: ${error.message}`);
|
| 191 |
+
}
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
console.log(`✅ Conversion completed! ${convertedFiles.length} file(s) generated`);
|
| 195 |
+
|
| 196 |
+
} catch (error) {
|
| 197 |
+
console.error('❌ Conversion failed:', error.message);
|
| 198 |
+
process.exit(1);
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
function main() {
|
| 203 |
+
const config = parseArgs();
|
| 204 |
+
|
| 205 |
+
if (config.clean) {
|
| 206 |
+
console.log('🧹 Cleaning output directory...');
|
| 207 |
+
// Clean output directory logic would go here
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
convertNotionToMarkdown(config.input, config.output, config.token);
|
| 211 |
+
console.log('🎉 Notion conversion completed!');
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
// Show help if requested
|
| 215 |
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
| 216 |
+
console.log(`
|
| 217 |
+
🚀 Notion to Markdown Converter
|
| 218 |
+
|
| 219 |
+
Usage:
|
| 220 |
+
node notion-converter.mjs [options]
|
| 221 |
+
|
| 222 |
+
Options:
|
| 223 |
+
--input=PATH Input pages configuration file (default: input/pages.json)
|
| 224 |
+
--output=PATH Output directory (default: output/)
|
| 225 |
+
--token=TOKEN Notion API token (or set NOTION_TOKEN env var)
|
| 226 |
+
--clean Clean output directory before conversion
|
| 227 |
+
--help, -h Show this help
|
| 228 |
+
|
| 229 |
+
Environment Variables:
|
| 230 |
+
NOTION_TOKEN Your Notion integration token
|
| 231 |
+
|
| 232 |
+
Examples:
|
| 233 |
+
# Basic conversion with environment token
|
| 234 |
+
NOTION_TOKEN=your_token node notion-converter.mjs
|
| 235 |
+
|
| 236 |
+
# Custom paths and token
|
| 237 |
+
node notion-converter.mjs --input=my-pages.json --output=converted/ --token=your_token
|
| 238 |
+
|
| 239 |
+
# Clean output first
|
| 240 |
+
node notion-converter.mjs --clean
|
| 241 |
+
|
| 242 |
+
Configuration File Format (pages.json):
|
| 243 |
+
{
|
| 244 |
+
"pages": [
|
| 245 |
+
{
|
| 246 |
+
"id": "your-notion-page-id",
|
| 247 |
+
"title": "Page Title",
|
| 248 |
+
"slug": "page-slug"
|
| 249 |
+
}
|
| 250 |
+
]
|
| 251 |
+
}
|
| 252 |
+
`);
|
| 253 |
+
process.exit(0);
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
// Run CLI if called directly
|
| 257 |
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
| 258 |
+
main();
|
| 259 |
+
}
|
app/scripts/notion-to-mdx/notion-metadata-extractor.mjs
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
import { Client } from '@notionhq/client';
|
| 4 |
+
|
| 5 |
+
/**
|
| 6 |
+
* Notion Metadata Extractor
|
| 7 |
+
* Extracts document metadata from Notion pages for frontmatter generation
|
| 8 |
+
*/
|
| 9 |
+
|
| 10 |
+
/**
|
| 11 |
+
* Extract metadata from Notion page
|
| 12 |
+
* @param {string} pageId - Notion page ID
|
| 13 |
+
* @param {string} notionToken - Notion API token
|
| 14 |
+
* @returns {object} - Extracted metadata object
|
| 15 |
+
*/
|
| 16 |
+
export async function extractNotionMetadata(pageId, notionToken) {
|
| 17 |
+
const notion = new Client({
|
| 18 |
+
auth: notionToken,
|
| 19 |
+
});
|
| 20 |
+
|
| 21 |
+
const metadata = {};
|
| 22 |
+
|
| 23 |
+
try {
|
| 24 |
+
// Get page information
|
| 25 |
+
const page = await notion.pages.retrieve({ page_id: pageId });
|
| 26 |
+
|
| 27 |
+
// Extract title from page properties
|
| 28 |
+
if (page.properties.title && page.properties.title.title && page.properties.title.title.length > 0) {
|
| 29 |
+
metadata.title = page.properties.title.title[0].plain_text;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
// Extract creation date
|
| 33 |
+
if (page.created_time) {
|
| 34 |
+
metadata.published = new Date(page.created_time).toLocaleDateString('en-US', {
|
| 35 |
+
year: 'numeric',
|
| 36 |
+
month: 'short',
|
| 37 |
+
day: '2-digit'
|
| 38 |
+
});
|
| 39 |
+
metadata.created_time = page.created_time;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
// Extract last edited date
|
| 43 |
+
if (page.last_edited_time) {
|
| 44 |
+
metadata.last_edited_time = page.last_edited_time;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
// Extract created by
|
| 48 |
+
if (page.created_by && page.created_by.id) {
|
| 49 |
+
metadata.created_by = page.created_by.id;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
// Extract last edited by
|
| 53 |
+
if (page.last_edited_by && page.last_edited_by.id) {
|
| 54 |
+
metadata.last_edited_by = page.last_edited_by.id;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
// Extract page URL
|
| 58 |
+
metadata.notion_url = page.url;
|
| 59 |
+
|
| 60 |
+
// Extract page ID
|
| 61 |
+
metadata.notion_id = page.id;
|
| 62 |
+
|
| 63 |
+
// Extract parent information
|
| 64 |
+
if (page.parent) {
|
| 65 |
+
metadata.parent = {
|
| 66 |
+
type: page.parent.type,
|
| 67 |
+
id: page.parent[page.parent.type]?.id || page.parent[page.parent.type]
|
| 68 |
+
};
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
// Extract cover image if available
|
| 72 |
+
if (page.cover) {
|
| 73 |
+
metadata.cover = {
|
| 74 |
+
type: page.cover.type,
|
| 75 |
+
url: page.cover[page.cover.type]?.url || page.cover[page.cover.type]
|
| 76 |
+
};
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
// Extract icon if available
|
| 80 |
+
if (page.icon) {
|
| 81 |
+
metadata.icon = {
|
| 82 |
+
type: page.icon.type,
|
| 83 |
+
emoji: page.icon.emoji,
|
| 84 |
+
url: page.icon.external?.url || page.icon.file?.url
|
| 85 |
+
};
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
// Extract authors and custom properties
|
| 89 |
+
const customProperties = {};
|
| 90 |
+
for (const [key, value] of Object.entries(page.properties)) {
|
| 91 |
+
if (key !== 'title') { // Skip title as it's handled separately
|
| 92 |
+
const extractedValue = extractPropertyValue(value);
|
| 93 |
+
|
| 94 |
+
// Check for author-related properties
|
| 95 |
+
if (key.toLowerCase().includes('author') ||
|
| 96 |
+
key.toLowerCase().includes('writer') ||
|
| 97 |
+
key.toLowerCase().includes('creator') ||
|
| 98 |
+
value.type === 'people') {
|
| 99 |
+
metadata.authors = extractedValue;
|
| 100 |
+
} else {
|
| 101 |
+
customProperties[key] = extractedValue;
|
| 102 |
+
}
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
// If no authors found in properties, try to get from created_by
|
| 107 |
+
if (!metadata.authors && page.created_by) {
|
| 108 |
+
try {
|
| 109 |
+
const user = await notion.users.retrieve({ user_id: page.created_by.id });
|
| 110 |
+
metadata.authors = [{
|
| 111 |
+
name: user.name || user.id,
|
| 112 |
+
id: user.id
|
| 113 |
+
}];
|
| 114 |
+
} catch (error) {
|
| 115 |
+
console.log(' ⚠️ Could not fetch author from created_by:', error.message);
|
| 116 |
+
// Fallback to basic info
|
| 117 |
+
metadata.authors = [{
|
| 118 |
+
name: page.created_by.name || page.created_by.id,
|
| 119 |
+
id: page.created_by.id
|
| 120 |
+
}];
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
if (Object.keys(customProperties).length > 0) {
|
| 125 |
+
metadata.properties = customProperties;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
// Try to extract description from page content (first paragraph)
|
| 129 |
+
try {
|
| 130 |
+
const blocks = await notion.blocks.children.list({ block_id: pageId });
|
| 131 |
+
const firstParagraph = blocks.results.find(block =>
|
| 132 |
+
block.type === 'paragraph' &&
|
| 133 |
+
block.paragraph.rich_text &&
|
| 134 |
+
block.paragraph.rich_text.length > 0
|
| 135 |
+
);
|
| 136 |
+
|
| 137 |
+
if (firstParagraph) {
|
| 138 |
+
const description = firstParagraph.paragraph.rich_text
|
| 139 |
+
.map(text => text.plain_text)
|
| 140 |
+
.join('')
|
| 141 |
+
.trim();
|
| 142 |
+
|
| 143 |
+
if (description && description.length > 0) {
|
| 144 |
+
metadata.description = description.substring(0, 200) + (description.length > 200 ? '...' : '');
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
} catch (error) {
|
| 148 |
+
console.log(' ⚠️ Could not extract description from page content');
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
// Generate tags from page properties
|
| 152 |
+
const tags = [];
|
| 153 |
+
for (const [key, value] of Object.entries(page.properties)) {
|
| 154 |
+
if (value.type === 'multi_select' && value.multi_select) {
|
| 155 |
+
value.multi_select.forEach(option => {
|
| 156 |
+
tags.push(option.name);
|
| 157 |
+
});
|
| 158 |
+
} else if (value.type === 'select' && value.select) {
|
| 159 |
+
tags.push(value.select.name);
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
if (tags.length > 0) {
|
| 164 |
+
metadata.tags = tags;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
} catch (error) {
|
| 168 |
+
console.error('Error extracting Notion metadata:', error.message);
|
| 169 |
+
// Return basic metadata if extraction fails
|
| 170 |
+
metadata.title = "Notion Article";
|
| 171 |
+
metadata.published = new Date().toLocaleDateString('en-US', {
|
| 172 |
+
year: 'numeric',
|
| 173 |
+
month: 'short',
|
| 174 |
+
day: '2-digit'
|
| 175 |
+
});
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
return metadata;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
/**
|
| 182 |
+
* Extract value from Notion property
|
| 183 |
+
* @param {object} property - Notion property object
|
| 184 |
+
* @returns {any} - Extracted value
|
| 185 |
+
*/
|
| 186 |
+
function extractPropertyValue(property) {
|
| 187 |
+
switch (property.type) {
|
| 188 |
+
case 'rich_text':
|
| 189 |
+
return property.rich_text.map(text => text.plain_text).join('');
|
| 190 |
+
case 'title':
|
| 191 |
+
return property.title.map(text => text.plain_text).join('');
|
| 192 |
+
case 'number':
|
| 193 |
+
return property.number;
|
| 194 |
+
case 'select':
|
| 195 |
+
return property.select?.name || null;
|
| 196 |
+
case 'multi_select':
|
| 197 |
+
return property.multi_select.map(option => option.name);
|
| 198 |
+
case 'date':
|
| 199 |
+
return property.date?.start || null;
|
| 200 |
+
case 'checkbox':
|
| 201 |
+
return property.checkbox;
|
| 202 |
+
case 'url':
|
| 203 |
+
return property.url;
|
| 204 |
+
case 'email':
|
| 205 |
+
return property.email;
|
| 206 |
+
case 'phone_number':
|
| 207 |
+
return property.phone_number;
|
| 208 |
+
case 'created_time':
|
| 209 |
+
return property.created_time;
|
| 210 |
+
case 'created_by':
|
| 211 |
+
return property.created_by?.id || null;
|
| 212 |
+
case 'last_edited_time':
|
| 213 |
+
return property.last_edited_time;
|
| 214 |
+
case 'last_edited_by':
|
| 215 |
+
return property.last_edited_by?.id || null;
|
| 216 |
+
case 'people':
|
| 217 |
+
return property.people.map(person => ({
|
| 218 |
+
name: person.name || person.id,
|
| 219 |
+
id: person.id
|
| 220 |
+
}));
|
| 221 |
+
default:
|
| 222 |
+
return null;
|
| 223 |
+
}
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
/**
|
| 227 |
+
* Generate YAML frontmatter from metadata object
|
| 228 |
+
* @param {object} metadata - Metadata object
|
| 229 |
+
* @returns {string} - YAML frontmatter string
|
| 230 |
+
*/
|
| 231 |
+
export function generateNotionFrontmatter(metadata) {
|
| 232 |
+
let frontmatter = '---\n';
|
| 233 |
+
|
| 234 |
+
// Title
|
| 235 |
+
if (metadata.title) {
|
| 236 |
+
frontmatter += `title: "${metadata.title}"\n`;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
// Description
|
| 240 |
+
if (metadata.description) {
|
| 241 |
+
frontmatter += `description: "${metadata.description}"\n`;
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
// Publication date
|
| 245 |
+
if (metadata.published) {
|
| 246 |
+
frontmatter += `published: "${metadata.published}"\n`;
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
// Authors
|
| 250 |
+
if (metadata.authors && metadata.authors.length > 0) {
|
| 251 |
+
frontmatter += 'authors:\n';
|
| 252 |
+
metadata.authors.forEach(author => {
|
| 253 |
+
if (typeof author === 'string') {
|
| 254 |
+
frontmatter += ` - name: "${author}"\n`;
|
| 255 |
+
} else if (author.name) {
|
| 256 |
+
frontmatter += ` - name: "${author.name}"\n`;
|
| 257 |
+
}
|
| 258 |
+
});
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
// Tags
|
| 262 |
+
if (metadata.tags && metadata.tags.length > 0) {
|
| 263 |
+
frontmatter += 'tags:\n';
|
| 264 |
+
metadata.tags.forEach(tag => {
|
| 265 |
+
frontmatter += ` - "${tag}"\n`;
|
| 266 |
+
});
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
// Notion metadata removed - keeping only standard frontmatter fields
|
| 270 |
+
|
| 271 |
+
// Cover image
|
| 272 |
+
if (metadata.cover && metadata.cover.url) {
|
| 273 |
+
frontmatter += `cover: "${metadata.cover.url}"\n`;
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
// Icon
|
| 277 |
+
if (metadata.icon) {
|
| 278 |
+
if (metadata.icon.emoji) {
|
| 279 |
+
frontmatter += `icon: "${metadata.icon.emoji}"\n`;
|
| 280 |
+
} else if (metadata.icon.url) {
|
| 281 |
+
frontmatter += `icon: "${metadata.icon.url}"\n`;
|
| 282 |
+
}
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
// Custom properties removed - keeping frontmatter clean and standard
|
| 286 |
+
|
| 287 |
+
// Default Astro configuration
|
| 288 |
+
frontmatter += 'tableOfContentsAutoCollapse: true\n';
|
| 289 |
+
frontmatter += '---\n\n';
|
| 290 |
+
|
| 291 |
+
return frontmatter;
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
/**
|
| 295 |
+
* Extract and generate frontmatter from Notion page
|
| 296 |
+
* @param {string} pageId - Notion page ID
|
| 297 |
+
* @param {string} notionToken - Notion API token
|
| 298 |
+
* @returns {string} - Complete YAML frontmatter
|
| 299 |
+
*/
|
| 300 |
+
export async function extractAndGenerateNotionFrontmatter(pageId, notionToken) {
|
| 301 |
+
const metadata = await extractNotionMetadata(pageId, notionToken);
|
| 302 |
+
return generateNotionFrontmatter(metadata);
|
| 303 |
+
}
|
app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-8013-b668-f14bd1ac0ec0.png
ADDED
|
Git LFS Details
|
app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-8014-834f-d700b623256b.png
ADDED
|
Git LFS Details
|
app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-801d-841a-e35011491566.png
ADDED
|
Git LFS Details
|
app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-8031-ac8d-c5678af1bdd5.png
ADDED
|
Git LFS Details
|
app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-8048-9b7e-db4fa7485915.png
ADDED
|
Git LFS Details
|
app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-804d-bd0a-e0b1c15e504f.png
ADDED
|
Git LFS Details
|
app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-8075-ae2e-dc24fe9296ca.png
ADDED
|
Git LFS Details
|
app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-8078-b6da-c7a4c67c8f35.png
ADDED
|
Git LFS Details
|
app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-808d-9c6d-fae817ac8868.png
ADDED
|
Git LFS Details
|
app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-808f-b712-c7c608da3fc6.png
ADDED
|
Git LFS Details
|
app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-80a9-b4d0-f2129716632d.png
ADDED
|
Git LFS Details
|
app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-80aa-b968-c54c9fe7e5d7.png
ADDED
|
Git LFS Details
|
app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-80b6-be07-e8646502f82a.png
ADDED
|
Git LFS Details
|
app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-80b9-8cfb-f0a6aaaa8760.png
ADDED
|
Git LFS Details
|
app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-80e7-a500-fb79cebde7e3.png
ADDED
|
Git LFS Details
|
app/scripts/notion-to-mdx/output/media/27877f1c9c9d804d9c82f7b3905578ff/image_27877f1c-9c9d-80e9-b729-dbd328930bed.png
ADDED
|
Git LFS Details
|
app/scripts/notion-to-mdx/output/smol-training-guide.md
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
app/scripts/notion-to-mdx/output/smol-training-guide.mdx
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
app/scripts/notion-to-mdx/package-lock.json
ADDED
|
Binary file (89.3 kB). View file
|
|
|
app/scripts/notion-to-mdx/package.json
ADDED
|
Binary file (1.19 kB). View file
|
|
|
app/scripts/notion-to-mdx/post-processor.mjs
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
| 4 |
+
import { join, dirname, basename } from 'path';
|
| 5 |
+
import { fileURLToPath } from 'url';
|
| 6 |
+
|
| 7 |
+
const __filename = fileURLToPath(import.meta.url);
|
| 8 |
+
const __dirname = dirname(__filename);
|
| 9 |
+
|
| 10 |
+
/**
|
| 11 |
+
* Post-process Notion-generated Markdown for better MDX compatibility
|
| 12 |
+
* @param {string} content - Raw markdown content from Notion
|
| 13 |
+
* @returns {string} - Processed markdown content
|
| 14 |
+
*/
|
| 15 |
+
export function postProcessMarkdown(content) {
|
| 16 |
+
console.log('🔧 Post-processing Notion Markdown for MDX compatibility...');
|
| 17 |
+
|
| 18 |
+
let processedContent = content;
|
| 19 |
+
|
| 20 |
+
// Apply each transformation step
|
| 21 |
+
processedContent = cleanNotionArtifacts(processedContent);
|
| 22 |
+
processedContent = fixNotionLinks(processedContent);
|
| 23 |
+
processedContent = optimizeImages(processedContent);
|
| 24 |
+
processedContent = shiftHeadingLevels(processedContent);
|
| 25 |
+
processedContent = cleanEmptyLines(processedContent);
|
| 26 |
+
processedContent = fixCodeBlocks(processedContent);
|
| 27 |
+
processedContent = fixCodeBlockEndings(processedContent);
|
| 28 |
+
processedContent = optimizeTables(processedContent);
|
| 29 |
+
|
| 30 |
+
return processedContent;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
/**
|
| 34 |
+
* Clean Notion-specific artifacts and formatting
|
| 35 |
+
* @param {string} content - Markdown content
|
| 36 |
+
* @returns {string} - Cleaned content
|
| 37 |
+
*/
|
| 38 |
+
function cleanNotionArtifacts(content) {
|
| 39 |
+
console.log(' 🧹 Cleaning Notion artifacts...');
|
| 40 |
+
|
| 41 |
+
let cleanedCount = 0;
|
| 42 |
+
|
| 43 |
+
// Remove Notion's internal page references that don't convert well
|
| 44 |
+
content = content.replace(/\[([^\]]+)\]\(https:\/\/www\.notion\.so\/[^)]+\)/g, (match, text) => {
|
| 45 |
+
cleanedCount++;
|
| 46 |
+
return text; // Keep just the text, remove the broken link
|
| 47 |
+
});
|
| 48 |
+
|
| 49 |
+
// Clean up Notion's callout blocks that might not render properly
|
| 50 |
+
content = content.replace(/^> \*\*([^*]+)\*\*\s*\n/gm, '> **$1**\n\n');
|
| 51 |
+
|
| 52 |
+
// Remove Notion's page dividers that don't have markdown equivalents
|
| 53 |
+
content = content.replace(/^---+\s*$/gm, '');
|
| 54 |
+
|
| 55 |
+
// Clean up empty blockquotes
|
| 56 |
+
content = content.replace(/^>\s*$/gm, '');
|
| 57 |
+
|
| 58 |
+
if (cleanedCount > 0) {
|
| 59 |
+
console.log(` ✅ Cleaned ${cleanedCount} Notion artifact(s)`);
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
return content;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
/**
|
| 66 |
+
* Fix Notion internal links to be more MDX-friendly
|
| 67 |
+
* @param {string} content - Markdown content
|
| 68 |
+
* @returns {string} - Content with fixed links
|
| 69 |
+
*/
|
| 70 |
+
function fixNotionLinks(content) {
|
| 71 |
+
console.log(' 🔗 Fixing Notion internal links...');
|
| 72 |
+
|
| 73 |
+
let fixedCount = 0;
|
| 74 |
+
|
| 75 |
+
// Convert Notion page links to relative links (assuming they'll be converted to MDX)
|
| 76 |
+
content = content.replace(/\[([^\]]+)\]\(https:\/\/www\.notion\.so\/[^/]+\/([^?#)]+)\)/g, (match, text, pageId) => {
|
| 77 |
+
fixedCount++;
|
| 78 |
+
// Convert to relative link - this will need to be updated based on your routing
|
| 79 |
+
return `[${text}](#${pageId})`;
|
| 80 |
+
});
|
| 81 |
+
|
| 82 |
+
// Fix broken notion.so links that might be malformed
|
| 83 |
+
content = content.replace(/\[([^\]]+)\]\(https:\/\/www\.notion\.so\/[^)]*\)/g, (match, text) => {
|
| 84 |
+
fixedCount++;
|
| 85 |
+
return text; // Remove broken links, keep text
|
| 86 |
+
});
|
| 87 |
+
|
| 88 |
+
if (fixedCount > 0) {
|
| 89 |
+
console.log(` ✅ Fixed ${fixedCount} Notion link(s)`);
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
return content;
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
/**
|
| 96 |
+
* Optimize images for better MDX compatibility
|
| 97 |
+
* @param {string} content - Markdown content
|
| 98 |
+
* @returns {string} - Content with optimized images
|
| 99 |
+
*/
|
| 100 |
+
function optimizeImages(content) {
|
| 101 |
+
console.log(' 🖼️ Optimizing images...');
|
| 102 |
+
|
| 103 |
+
let optimizedCount = 0;
|
| 104 |
+
|
| 105 |
+
// Ensure images have proper alt text
|
| 106 |
+
content = content.replace(/!\[\]\(([^)]+)\)/g, (match, src) => {
|
| 107 |
+
optimizedCount++;
|
| 108 |
+
const filename = basename(src);
|
| 109 |
+
return ``;
|
| 110 |
+
});
|
| 111 |
+
|
| 112 |
+
// Clean up image paths that might have query parameters
|
| 113 |
+
content = content.replace(/!\[([^\]]*)\]\(([^)]+)\?[^)]*\)/g, (match, alt, src) => {
|
| 114 |
+
optimizedCount++;
|
| 115 |
+
return ``;
|
| 116 |
+
});
|
| 117 |
+
|
| 118 |
+
if (optimizedCount > 0) {
|
| 119 |
+
console.log(` ✅ Optimized ${optimizedCount} image(s)`);
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
return content;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
/**
|
| 126 |
+
* Shift all heading levels down by one (H1 → H2, H2 → H3, etc.)
|
| 127 |
+
* @param {string} content - Markdown content
|
| 128 |
+
* @returns {string} - Content with shifted heading levels
|
| 129 |
+
*/
|
| 130 |
+
function shiftHeadingLevels(content) {
|
| 131 |
+
console.log(' 📝 Shifting heading levels down by one...');
|
| 132 |
+
|
| 133 |
+
let shiftedCount = 0;
|
| 134 |
+
|
| 135 |
+
// Shift heading levels: H1 → H2, H2 → H3, H3 → H4, H4 → H5, H5 → H6
|
| 136 |
+
// Process from highest to lowest to avoid conflicts
|
| 137 |
+
content = content.replace(/^##### (.*$)/gim, '###### $1');
|
| 138 |
+
content = content.replace(/^#### (.*$)/gim, '##### $1');
|
| 139 |
+
content = content.replace(/^### (.*$)/gim, '#### $1');
|
| 140 |
+
content = content.replace(/^## (.*$)/gim, '### $1');
|
| 141 |
+
content = content.replace(/^# (.*$)/gim, '## $1');
|
| 142 |
+
|
| 143 |
+
// Count the number of headings shifted
|
| 144 |
+
const headingMatches = content.match(/^#{1,6} /gm);
|
| 145 |
+
if (headingMatches) {
|
| 146 |
+
shiftedCount = headingMatches.length;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
console.log(` ✅ Shifted ${shiftedCount} heading level(s)`);
|
| 150 |
+
return content;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
/**
|
| 154 |
+
* Fix code block endings that end with "text" instead of proper closing
|
| 155 |
+
* @param {string} content - Markdown content
|
| 156 |
+
* @returns {string} - Content with fixed code block endings
|
| 157 |
+
*/
|
| 158 |
+
function fixCodeBlockEndings(content) {
|
| 159 |
+
console.log(' 💻 Fixing code block endings...');
|
| 160 |
+
|
| 161 |
+
let fixedCount = 0;
|
| 162 |
+
|
| 163 |
+
// Fix code blocks that end with ```text instead of ```
|
| 164 |
+
content = content.replace(/```text\n/g, '```\n');
|
| 165 |
+
|
| 166 |
+
// Count the number of fixes
|
| 167 |
+
const textEndingMatches = content.match(/```text\n/g);
|
| 168 |
+
if (textEndingMatches) {
|
| 169 |
+
fixedCount = textEndingMatches.length;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
if (fixedCount > 0) {
|
| 173 |
+
console.log(` ✅ Fixed ${fixedCount} code block ending(s)`);
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
return content;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
/**
|
| 180 |
+
* Clean up excessive empty lines
|
| 181 |
+
* @param {string} content - Markdown content
|
| 182 |
+
* @returns {string} - Content with cleaned spacing
|
| 183 |
+
*/
|
| 184 |
+
function cleanEmptyLines(content) {
|
| 185 |
+
console.log(' 📝 Cleaning excessive empty lines...');
|
| 186 |
+
|
| 187 |
+
// Replace 3+ consecutive newlines with 2 newlines
|
| 188 |
+
const cleanedContent = content.replace(/\n{3,}/g, '\n\n');
|
| 189 |
+
|
| 190 |
+
const originalLines = content.split('\n').length;
|
| 191 |
+
const cleanedLines = cleanedContent.split('\n').length;
|
| 192 |
+
const removedLines = originalLines - cleanedLines;
|
| 193 |
+
|
| 194 |
+
if (removedLines > 0) {
|
| 195 |
+
console.log(` ✅ Removed ${removedLines} excessive empty line(s)`);
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
return cleanedContent;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
/**
|
| 202 |
+
* Fix code blocks for better MDX compatibility
|
| 203 |
+
* @param {string} content - Markdown content
|
| 204 |
+
* @returns {string} - Content with fixed code blocks
|
| 205 |
+
*/
|
| 206 |
+
function fixCodeBlocks(content) {
|
| 207 |
+
console.log(' 💻 Fixing code blocks...');
|
| 208 |
+
|
| 209 |
+
let fixedCount = 0;
|
| 210 |
+
|
| 211 |
+
// Ensure code blocks have proper language identifiers
|
| 212 |
+
content = content.replace(/^```\s*$/gm, '```text');
|
| 213 |
+
|
| 214 |
+
// Fix code blocks that might have Notion-specific formatting
|
| 215 |
+
content = content.replace(/^```(\w+)\s*\n([\s\S]*?)\n```$/gm, (match, lang, code) => {
|
| 216 |
+
// Clean up any Notion artifacts in code
|
| 217 |
+
const cleanCode = code.replace(/\u00A0/g, ' '); // Replace non-breaking spaces
|
| 218 |
+
return `\`\`\`${lang}\n${cleanCode}\n\`\`\``;
|
| 219 |
+
});
|
| 220 |
+
|
| 221 |
+
if (fixedCount > 0) {
|
| 222 |
+
console.log(` ✅ Fixed ${fixedCount} code block(s)`);
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
return content;
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
/**
|
| 229 |
+
* Optimize tables for better MDX rendering
|
| 230 |
+
* @param {string} content - Markdown content
|
| 231 |
+
* @returns {string} - Content with optimized tables
|
| 232 |
+
*/
|
| 233 |
+
function optimizeTables(content) {
|
| 234 |
+
console.log(' 📊 Optimizing tables...');
|
| 235 |
+
|
| 236 |
+
let optimizedCount = 0;
|
| 237 |
+
|
| 238 |
+
// Fix tables that might have inconsistent column counts
|
| 239 |
+
content = content.replace(/^\|(.+)\|\s*$/gm, (match, row) => {
|
| 240 |
+
const cells = row.split('|').map(cell => cell.trim());
|
| 241 |
+
const cleanCells = cells.filter(cell => cell.length > 0);
|
| 242 |
+
|
| 243 |
+
if (cleanCells.length > 0) {
|
| 244 |
+
optimizedCount++;
|
| 245 |
+
return `| ${cleanCells.join(' | ')} |`;
|
| 246 |
+
}
|
| 247 |
+
return match;
|
| 248 |
+
});
|
| 249 |
+
|
| 250 |
+
// Ensure table headers are properly formatted
|
| 251 |
+
content = content.replace(/^\|(.+)\|\s*\n\|([-:\s|]+)\|\s*$/gm, (match, header, separator) => {
|
| 252 |
+
const headerCells = header.split('|').map(cell => cell.trim()).filter(cell => cell.length > 0);
|
| 253 |
+
const separatorCells = separator.split('|').map(cell => cell.trim()).filter(cell => cell.length > 0);
|
| 254 |
+
|
| 255 |
+
if (headerCells.length !== separatorCells.length) {
|
| 256 |
+
optimizedCount++;
|
| 257 |
+
const newSeparator = headerCells.map(() => '---').join(' | ');
|
| 258 |
+
return `| ${headerCells.join(' | ')} |\n| ${newSeparator} |`;
|
| 259 |
+
}
|
| 260 |
+
return match;
|
| 261 |
+
});
|
| 262 |
+
|
| 263 |
+
if (optimizedCount > 0) {
|
| 264 |
+
console.log(` ✅ Optimized ${optimizedCount} table(s)`);
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
return content;
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
/**
|
| 271 |
+
* Extract frontmatter from Notion page properties
|
| 272 |
+
* @param {Object} pageProperties - Notion page properties
|
| 273 |
+
* @returns {string} - YAML frontmatter
|
| 274 |
+
*/
|
| 275 |
+
export function generateFrontmatter(pageProperties) {
|
| 276 |
+
console.log(' 📄 Generating frontmatter from Notion properties...');
|
| 277 |
+
|
| 278 |
+
const frontmatter = {
|
| 279 |
+
title: pageProperties.title || 'Untitled',
|
| 280 |
+
published: new Date().toISOString().split('T')[0],
|
| 281 |
+
tableOfContentsAutoCollapse: true
|
| 282 |
+
};
|
| 283 |
+
|
| 284 |
+
// Add other properties if they exist
|
| 285 |
+
if (pageProperties.description) {
|
| 286 |
+
frontmatter.description = pageProperties.description;
|
| 287 |
+
}
|
| 288 |
+
if (pageProperties.tags) {
|
| 289 |
+
frontmatter.tags = pageProperties.tags;
|
| 290 |
+
}
|
| 291 |
+
if (pageProperties.author) {
|
| 292 |
+
frontmatter.author = pageProperties.author;
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
// Convert to YAML string
|
| 296 |
+
const yamlLines = Object.entries(frontmatter)
|
| 297 |
+
.map(([key, value]) => {
|
| 298 |
+
if (Array.isArray(value)) {
|
| 299 |
+
return `${key}:\n${value.map(v => ` - ${v}`).join('\n')}`;
|
| 300 |
+
}
|
| 301 |
+
return `${key}: "${value}"`;
|
| 302 |
+
});
|
| 303 |
+
|
| 304 |
+
return `---\n${yamlLines.join('\n')}\n---\n\n`;
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
function main() {
|
| 308 |
+
const args = process.argv.slice(2);
|
| 309 |
+
|
| 310 |
+
if (args.includes('--help') || args.includes('-h')) {
|
| 311 |
+
console.log(`
|
| 312 |
+
🔧 Notion Markdown Post-Processor
|
| 313 |
+
|
| 314 |
+
Usage:
|
| 315 |
+
node post-processor.mjs [options] [input-file] [output-file]
|
| 316 |
+
|
| 317 |
+
Options:
|
| 318 |
+
--verbose Show detailed processing information
|
| 319 |
+
--help, -h Show this help
|
| 320 |
+
|
| 321 |
+
Examples:
|
| 322 |
+
# Process a single file
|
| 323 |
+
node post-processor.mjs input.md output.md
|
| 324 |
+
|
| 325 |
+
# Process with verbose output
|
| 326 |
+
node post-processor.mjs --verbose input.md output.md
|
| 327 |
+
`);
|
| 328 |
+
process.exit(0);
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
const verbose = args.includes('--verbose');
|
| 332 |
+
const inputFile = args.find(arg => !arg.startsWith('--') && arg.endsWith('.md'));
|
| 333 |
+
const outputFile = args.find(arg => !arg.startsWith('--') && arg !== inputFile && arg.endsWith('.md'));
|
| 334 |
+
|
| 335 |
+
if (!inputFile) {
|
| 336 |
+
console.error('❌ Please provide an input markdown file');
|
| 337 |
+
process.exit(1);
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
if (!existsSync(inputFile)) {
|
| 341 |
+
console.error(`❌ Input file not found: ${inputFile}`);
|
| 342 |
+
process.exit(1);
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
try {
|
| 346 |
+
console.log(`📖 Reading: ${inputFile}`);
|
| 347 |
+
const content = readFileSync(inputFile, 'utf8');
|
| 348 |
+
|
| 349 |
+
const processedContent = postProcessMarkdown(content);
|
| 350 |
+
|
| 351 |
+
const finalOutputFile = outputFile || inputFile.replace('.md', '.processed.md');
|
| 352 |
+
writeFileSync(finalOutputFile, processedContent);
|
| 353 |
+
|
| 354 |
+
console.log(`✅ Processed: ${finalOutputFile}`);
|
| 355 |
+
|
| 356 |
+
if (verbose) {
|
| 357 |
+
console.log(`📊 Input: ${content.length} chars → Output: ${processedContent.length} chars`);
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
} catch (error) {
|
| 361 |
+
console.error('❌ Processing failed:', error.message);
|
| 362 |
+
process.exit(1);
|
| 363 |
+
}
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
// Run CLI if called directly
|
| 367 |
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
| 368 |
+
main();
|
| 369 |
+
}
|
app/scripts/notion-to-mdx/test-access.mjs
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
import { config } from 'dotenv';
|
| 4 |
+
import { Client } from '@notionhq/client';
|
| 5 |
+
|
| 6 |
+
// Load environment variables from .env file
|
| 7 |
+
config();
|
| 8 |
+
|
| 9 |
+
const notion = new Client({
|
| 10 |
+
auth: process.env.NOTION_TOKEN,
|
| 11 |
+
});
|
| 12 |
+
|
| 13 |
+
async function testAccess() {
|
| 14 |
+
const pageId = '27877f1c9c9d804d9c82f7b3905578ff';
|
| 15 |
+
|
| 16 |
+
try {
|
| 17 |
+
console.log('🔍 Testing access to Notion page...');
|
| 18 |
+
console.log(`📄 Page ID: ${pageId}`);
|
| 19 |
+
|
| 20 |
+
const response = await notion.pages.retrieve({ page_id: pageId });
|
| 21 |
+
|
| 22 |
+
console.log('✅ Access successful!');
|
| 23 |
+
console.log(`📝 Page title: ${response.properties.title?.title?.[0]?.text?.content || 'No title'}`);
|
| 24 |
+
console.log(`📅 Created: ${response.created_time}`);
|
| 25 |
+
console.log(`👤 Created by: ${response.created_by.id}`);
|
| 26 |
+
|
| 27 |
+
} catch (error) {
|
| 28 |
+
console.error('❌ Access failed:', error.message);
|
| 29 |
+
|
| 30 |
+
if (error.code === 'unauthorized') {
|
| 31 |
+
console.log('\n💡 Solutions:');
|
| 32 |
+
console.log('1. Check that your NOTION_TOKEN is correct');
|
| 33 |
+
console.log('2. Make sure the page is shared with your integration');
|
| 34 |
+
console.log('3. Verify that the integration has the right permissions');
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
testAccess();
|
app/scripts/notion-to-mdx/yarn.lock
ADDED
|
@@ -0,0 +1,1118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
| 2 |
+
# yarn lockfile v1
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
"@cspotcode/source-map-support@^0.8.0":
|
| 6 |
+
version "0.8.1"
|
| 7 |
+
resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz"
|
| 8 |
+
integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
|
| 9 |
+
dependencies:
|
| 10 |
+
"@jridgewell/trace-mapping" "0.3.9"
|
| 11 |
+
|
| 12 |
+
"@jridgewell/resolve-uri@^3.0.3":
|
| 13 |
+
version "3.1.2"
|
| 14 |
+
resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz"
|
| 15 |
+
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
|
| 16 |
+
|
| 17 |
+
"@jridgewell/sourcemap-codec@^1.4.10":
|
| 18 |
+
version "1.5.5"
|
| 19 |
+
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz"
|
| 20 |
+
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
|
| 21 |
+
|
| 22 |
+
"@jridgewell/trace-mapping@0.3.9":
|
| 23 |
+
version "0.3.9"
|
| 24 |
+
resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz"
|
| 25 |
+
integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
|
| 26 |
+
dependencies:
|
| 27 |
+
"@jridgewell/resolve-uri" "^3.0.3"
|
| 28 |
+
"@jridgewell/sourcemap-codec" "^1.4.10"
|
| 29 |
+
|
| 30 |
+
"@notionhq/client@^2.0.0", "@notionhq/client@^2.2.15":
|
| 31 |
+
version "2.3.0"
|
| 32 |
+
resolved "https://registry.npmjs.org/@notionhq/client/-/client-2.3.0.tgz"
|
| 33 |
+
integrity sha512-l7WqTCpQqC+HibkB9chghONQTYcxNQT0/rOJemBfmuKQRTu2vuV8B3yA395iKaUdDo7HI+0KvQaz9687Xskzkw==
|
| 34 |
+
dependencies:
|
| 35 |
+
"@types/node-fetch" "^2.5.10"
|
| 36 |
+
node-fetch "^2.6.1"
|
| 37 |
+
|
| 38 |
+
"@tsconfig/node10@^1.0.7":
|
| 39 |
+
version "1.0.11"
|
| 40 |
+
resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz"
|
| 41 |
+
integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==
|
| 42 |
+
|
| 43 |
+
"@tsconfig/node12@^1.0.7":
|
| 44 |
+
version "1.0.11"
|
| 45 |
+
resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz"
|
| 46 |
+
integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==
|
| 47 |
+
|
| 48 |
+
"@tsconfig/node14@^1.0.0":
|
| 49 |
+
version "1.0.3"
|
| 50 |
+
resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz"
|
| 51 |
+
integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==
|
| 52 |
+
|
| 53 |
+
"@tsconfig/node16@^1.0.2":
|
| 54 |
+
version "1.0.4"
|
| 55 |
+
resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz"
|
| 56 |
+
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
|
| 57 |
+
|
| 58 |
+
"@types/debug@^4.0.0":
|
| 59 |
+
version "4.1.12"
|
| 60 |
+
resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz"
|
| 61 |
+
integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==
|
| 62 |
+
dependencies:
|
| 63 |
+
"@types/ms" "*"
|
| 64 |
+
|
| 65 |
+
"@types/estree-jsx@^1.0.0":
|
| 66 |
+
version "1.0.5"
|
| 67 |
+
resolved "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz"
|
| 68 |
+
integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==
|
| 69 |
+
dependencies:
|
| 70 |
+
"@types/estree" "*"
|
| 71 |
+
|
| 72 |
+
"@types/estree@*", "@types/estree@^1.0.0":
|
| 73 |
+
version "1.0.8"
|
| 74 |
+
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz"
|
| 75 |
+
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
| 76 |
+
|
| 77 |
+
"@types/hast@^3.0.0":
|
| 78 |
+
version "3.0.4"
|
| 79 |
+
resolved "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz"
|
| 80 |
+
integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==
|
| 81 |
+
dependencies:
|
| 82 |
+
"@types/unist" "*"
|
| 83 |
+
|
| 84 |
+
"@types/mdast@^4.0.0":
|
| 85 |
+
version "4.0.4"
|
| 86 |
+
resolved "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz"
|
| 87 |
+
integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==
|
| 88 |
+
dependencies:
|
| 89 |
+
"@types/unist" "*"
|
| 90 |
+
|
| 91 |
+
"@types/ms@*":
|
| 92 |
+
version "2.1.0"
|
| 93 |
+
resolved "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz"
|
| 94 |
+
integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
|
| 95 |
+
|
| 96 |
+
"@types/node-fetch@^2.5.10":
|
| 97 |
+
version "2.6.13"
|
| 98 |
+
resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz"
|
| 99 |
+
integrity sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==
|
| 100 |
+
dependencies:
|
| 101 |
+
"@types/node" "*"
|
| 102 |
+
form-data "^4.0.4"
|
| 103 |
+
|
| 104 |
+
"@types/node@*":
|
| 105 |
+
version "24.5.2"
|
| 106 |
+
resolved "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz"
|
| 107 |
+
integrity sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==
|
| 108 |
+
dependencies:
|
| 109 |
+
undici-types "~7.12.0"
|
| 110 |
+
|
| 111 |
+
"@types/unist@*", "@types/unist@^3.0.0":
|
| 112 |
+
version "3.0.3"
|
| 113 |
+
resolved "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz"
|
| 114 |
+
integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==
|
| 115 |
+
|
| 116 |
+
"@types/unist@^2.0.0":
|
| 117 |
+
version "2.0.11"
|
| 118 |
+
resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz"
|
| 119 |
+
integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==
|
| 120 |
+
|
| 121 |
+
acorn-jsx@^5.0.0:
|
| 122 |
+
version "5.3.2"
|
| 123 |
+
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
|
| 124 |
+
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
| 125 |
+
|
| 126 |
+
acorn-walk@^8.1.1:
|
| 127 |
+
version "8.3.4"
|
| 128 |
+
resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz"
|
| 129 |
+
integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==
|
| 130 |
+
dependencies:
|
| 131 |
+
acorn "^8.11.0"
|
| 132 |
+
|
| 133 |
+
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.0.0, acorn@^8.11.0, acorn@^8.4.1:
|
| 134 |
+
version "8.15.0"
|
| 135 |
+
resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz"
|
| 136 |
+
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
|
| 137 |
+
|
| 138 |
+
arg@^4.1.0:
|
| 139 |
+
version "4.1.3"
|
| 140 |
+
resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz"
|
| 141 |
+
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
|
| 142 |
+
|
| 143 |
+
argparse@^1.0.7:
|
| 144 |
+
version "1.0.10"
|
| 145 |
+
resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz"
|
| 146 |
+
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
|
| 147 |
+
dependencies:
|
| 148 |
+
sprintf-js "~1.0.2"
|
| 149 |
+
|
| 150 |
+
asynckit@^0.4.0:
|
| 151 |
+
version "0.4.0"
|
| 152 |
+
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
|
| 153 |
+
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
|
| 154 |
+
|
| 155 |
+
bail@^2.0.0:
|
| 156 |
+
version "2.0.2"
|
| 157 |
+
resolved "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz"
|
| 158 |
+
integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==
|
| 159 |
+
|
| 160 |
+
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
|
| 161 |
+
version "1.0.2"
|
| 162 |
+
resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz"
|
| 163 |
+
integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
|
| 164 |
+
dependencies:
|
| 165 |
+
es-errors "^1.3.0"
|
| 166 |
+
function-bind "^1.1.2"
|
| 167 |
+
|
| 168 |
+
ccount@^2.0.0:
|
| 169 |
+
version "2.0.1"
|
| 170 |
+
resolved "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz"
|
| 171 |
+
integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==
|
| 172 |
+
|
| 173 |
+
character-entities-html4@^2.0.0:
|
| 174 |
+
version "2.1.0"
|
| 175 |
+
resolved "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz"
|
| 176 |
+
integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==
|
| 177 |
+
|
| 178 |
+
character-entities-legacy@^3.0.0:
|
| 179 |
+
version "3.0.0"
|
| 180 |
+
resolved "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz"
|
| 181 |
+
integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==
|
| 182 |
+
|
| 183 |
+
character-entities@^2.0.0:
|
| 184 |
+
version "2.0.2"
|
| 185 |
+
resolved "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz"
|
| 186 |
+
integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==
|
| 187 |
+
|
| 188 |
+
character-reference-invalid@^2.0.0:
|
| 189 |
+
version "2.0.1"
|
| 190 |
+
resolved "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz"
|
| 191 |
+
integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
|
| 192 |
+
|
| 193 |
+
combined-stream@^1.0.8:
|
| 194 |
+
version "1.0.8"
|
| 195 |
+
resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
|
| 196 |
+
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
| 197 |
+
dependencies:
|
| 198 |
+
delayed-stream "~1.0.0"
|
| 199 |
+
|
| 200 |
+
create-require@^1.1.0:
|
| 201 |
+
version "1.1.1"
|
| 202 |
+
resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz"
|
| 203 |
+
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
|
| 204 |
+
|
| 205 |
+
data-uri-to-buffer@^4.0.0:
|
| 206 |
+
version "4.0.1"
|
| 207 |
+
resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz"
|
| 208 |
+
integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==
|
| 209 |
+
|
| 210 |
+
debug@^4.0.0:
|
| 211 |
+
version "4.4.3"
|
| 212 |
+
resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz"
|
| 213 |
+
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
|
| 214 |
+
dependencies:
|
| 215 |
+
ms "^2.1.3"
|
| 216 |
+
|
| 217 |
+
decode-named-character-reference@^1.0.0:
|
| 218 |
+
version "1.2.0"
|
| 219 |
+
resolved "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz"
|
| 220 |
+
integrity sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==
|
| 221 |
+
dependencies:
|
| 222 |
+
character-entities "^2.0.0"
|
| 223 |
+
|
| 224 |
+
delayed-stream@~1.0.0:
|
| 225 |
+
version "1.0.0"
|
| 226 |
+
resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
|
| 227 |
+
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
|
| 228 |
+
|
| 229 |
+
dequal@^2.0.0:
|
| 230 |
+
version "2.0.3"
|
| 231 |
+
resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz"
|
| 232 |
+
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
|
| 233 |
+
|
| 234 |
+
devlop@^1.0.0, devlop@^1.1.0:
|
| 235 |
+
version "1.1.0"
|
| 236 |
+
resolved "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz"
|
| 237 |
+
integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==
|
| 238 |
+
dependencies:
|
| 239 |
+
dequal "^2.0.0"
|
| 240 |
+
|
| 241 |
+
diff@^4.0.1:
|
| 242 |
+
version "4.0.2"
|
| 243 |
+
resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz"
|
| 244 |
+
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
| 245 |
+
|
| 246 |
+
dotenv@^17.2.2:
|
| 247 |
+
version "17.2.2"
|
| 248 |
+
resolved "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz"
|
| 249 |
+
integrity sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==
|
| 250 |
+
|
| 251 |
+
dunder-proto@^1.0.1:
|
| 252 |
+
version "1.0.1"
|
| 253 |
+
resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz"
|
| 254 |
+
integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
|
| 255 |
+
dependencies:
|
| 256 |
+
call-bind-apply-helpers "^1.0.1"
|
| 257 |
+
es-errors "^1.3.0"
|
| 258 |
+
gopd "^1.2.0"
|
| 259 |
+
|
| 260 |
+
es-define-property@^1.0.1:
|
| 261 |
+
version "1.0.1"
|
| 262 |
+
resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz"
|
| 263 |
+
integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
|
| 264 |
+
|
| 265 |
+
es-errors@^1.3.0:
|
| 266 |
+
version "1.3.0"
|
| 267 |
+
resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz"
|
| 268 |
+
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
|
| 269 |
+
|
| 270 |
+
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
|
| 271 |
+
version "1.1.1"
|
| 272 |
+
resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz"
|
| 273 |
+
integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
|
| 274 |
+
dependencies:
|
| 275 |
+
es-errors "^1.3.0"
|
| 276 |
+
|
| 277 |
+
es-set-tostringtag@^2.1.0:
|
| 278 |
+
version "2.1.0"
|
| 279 |
+
resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz"
|
| 280 |
+
integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
|
| 281 |
+
dependencies:
|
| 282 |
+
es-errors "^1.3.0"
|
| 283 |
+
get-intrinsic "^1.2.6"
|
| 284 |
+
has-tostringtag "^1.0.2"
|
| 285 |
+
hasown "^2.0.2"
|
| 286 |
+
|
| 287 |
+
esprima@^4.0.0:
|
| 288 |
+
version "4.0.1"
|
| 289 |
+
resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz"
|
| 290 |
+
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
| 291 |
+
|
| 292 |
+
estree-util-is-identifier-name@^3.0.0:
|
| 293 |
+
version "3.0.0"
|
| 294 |
+
resolved "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz"
|
| 295 |
+
integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==
|
| 296 |
+
|
| 297 |
+
estree-util-visit@^2.0.0:
|
| 298 |
+
version "2.0.0"
|
| 299 |
+
resolved "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz"
|
| 300 |
+
integrity sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==
|
| 301 |
+
dependencies:
|
| 302 |
+
"@types/estree-jsx" "^1.0.0"
|
| 303 |
+
"@types/unist" "^3.0.0"
|
| 304 |
+
|
| 305 |
+
extend-shallow@^2.0.1:
|
| 306 |
+
version "2.0.1"
|
| 307 |
+
resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz"
|
| 308 |
+
integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==
|
| 309 |
+
dependencies:
|
| 310 |
+
is-extendable "^0.1.0"
|
| 311 |
+
|
| 312 |
+
extend@^3.0.0:
|
| 313 |
+
version "3.0.2"
|
| 314 |
+
resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz"
|
| 315 |
+
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
| 316 |
+
|
| 317 |
+
fetch-blob@^3.1.2, fetch-blob@^3.1.4:
|
| 318 |
+
version "3.2.0"
|
| 319 |
+
resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz"
|
| 320 |
+
integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==
|
| 321 |
+
dependencies:
|
| 322 |
+
node-domexception "^1.0.0"
|
| 323 |
+
web-streams-polyfill "^3.0.3"
|
| 324 |
+
|
| 325 |
+
form-data@^4.0.4:
|
| 326 |
+
version "4.0.4"
|
| 327 |
+
resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz"
|
| 328 |
+
integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==
|
| 329 |
+
dependencies:
|
| 330 |
+
asynckit "^0.4.0"
|
| 331 |
+
combined-stream "^1.0.8"
|
| 332 |
+
es-set-tostringtag "^2.1.0"
|
| 333 |
+
hasown "^2.0.2"
|
| 334 |
+
mime-types "^2.1.12"
|
| 335 |
+
|
| 336 |
+
formdata-polyfill@^4.0.10:
|
| 337 |
+
version "4.0.10"
|
| 338 |
+
resolved "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz"
|
| 339 |
+
integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==
|
| 340 |
+
dependencies:
|
| 341 |
+
fetch-blob "^3.1.2"
|
| 342 |
+
|
| 343 |
+
function-bind@^1.1.2:
|
| 344 |
+
version "1.1.2"
|
| 345 |
+
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
|
| 346 |
+
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
|
| 347 |
+
|
| 348 |
+
get-intrinsic@^1.2.6:
|
| 349 |
+
version "1.3.0"
|
| 350 |
+
resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz"
|
| 351 |
+
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
|
| 352 |
+
dependencies:
|
| 353 |
+
call-bind-apply-helpers "^1.0.2"
|
| 354 |
+
es-define-property "^1.0.1"
|
| 355 |
+
es-errors "^1.3.0"
|
| 356 |
+
es-object-atoms "^1.1.1"
|
| 357 |
+
function-bind "^1.1.2"
|
| 358 |
+
get-proto "^1.0.1"
|
| 359 |
+
gopd "^1.2.0"
|
| 360 |
+
has-symbols "^1.1.0"
|
| 361 |
+
hasown "^2.0.2"
|
| 362 |
+
math-intrinsics "^1.1.0"
|
| 363 |
+
|
| 364 |
+
get-proto@^1.0.1:
|
| 365 |
+
version "1.0.1"
|
| 366 |
+
resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz"
|
| 367 |
+
integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
|
| 368 |
+
dependencies:
|
| 369 |
+
dunder-proto "^1.0.1"
|
| 370 |
+
es-object-atoms "^1.0.0"
|
| 371 |
+
|
| 372 |
+
gopd@^1.2.0:
|
| 373 |
+
version "1.2.0"
|
| 374 |
+
resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz"
|
| 375 |
+
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
|
| 376 |
+
|
| 377 |
+
gray-matter@^4.0.3:
|
| 378 |
+
version "4.0.3"
|
| 379 |
+
resolved "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz"
|
| 380 |
+
integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==
|
| 381 |
+
dependencies:
|
| 382 |
+
js-yaml "^3.13.1"
|
| 383 |
+
kind-of "^6.0.2"
|
| 384 |
+
section-matter "^1.0.0"
|
| 385 |
+
strip-bom-string "^1.0.0"
|
| 386 |
+
|
| 387 |
+
has-symbols@^1.0.3, has-symbols@^1.1.0:
|
| 388 |
+
version "1.1.0"
|
| 389 |
+
resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz"
|
| 390 |
+
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
|
| 391 |
+
|
| 392 |
+
has-tostringtag@^1.0.2:
|
| 393 |
+
version "1.0.2"
|
| 394 |
+
resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz"
|
| 395 |
+
integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
|
| 396 |
+
dependencies:
|
| 397 |
+
has-symbols "^1.0.3"
|
| 398 |
+
|
| 399 |
+
hasown@^2.0.2:
|
| 400 |
+
version "2.0.2"
|
| 401 |
+
resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz"
|
| 402 |
+
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
|
| 403 |
+
dependencies:
|
| 404 |
+
function-bind "^1.1.2"
|
| 405 |
+
|
| 406 |
+
is-alphabetical@^2.0.0:
|
| 407 |
+
version "2.0.1"
|
| 408 |
+
resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz"
|
| 409 |
+
integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==
|
| 410 |
+
|
| 411 |
+
is-alphanumerical@^2.0.0:
|
| 412 |
+
version "2.0.1"
|
| 413 |
+
resolved "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz"
|
| 414 |
+
integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==
|
| 415 |
+
dependencies:
|
| 416 |
+
is-alphabetical "^2.0.0"
|
| 417 |
+
is-decimal "^2.0.0"
|
| 418 |
+
|
| 419 |
+
is-decimal@^2.0.0:
|
| 420 |
+
version "2.0.1"
|
| 421 |
+
resolved "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz"
|
| 422 |
+
integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==
|
| 423 |
+
|
| 424 |
+
is-extendable@^0.1.0:
|
| 425 |
+
version "0.1.1"
|
| 426 |
+
resolved "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz"
|
| 427 |
+
integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==
|
| 428 |
+
|
| 429 |
+
is-hexadecimal@^2.0.0:
|
| 430 |
+
version "2.0.1"
|
| 431 |
+
resolved "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz"
|
| 432 |
+
integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==
|
| 433 |
+
|
| 434 |
+
is-plain-obj@^4.0.0:
|
| 435 |
+
version "4.1.0"
|
| 436 |
+
resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz"
|
| 437 |
+
integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==
|
| 438 |
+
|
| 439 |
+
js-yaml@^3.13.1:
|
| 440 |
+
version "3.14.1"
|
| 441 |
+
resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz"
|
| 442 |
+
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
|
| 443 |
+
dependencies:
|
| 444 |
+
argparse "^1.0.7"
|
| 445 |
+
esprima "^4.0.0"
|
| 446 |
+
|
| 447 |
+
kind-of@^6.0.0, kind-of@^6.0.2:
|
| 448 |
+
version "6.0.3"
|
| 449 |
+
resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz"
|
| 450 |
+
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
|
| 451 |
+
|
| 452 |
+
longest-streak@^3.0.0:
|
| 453 |
+
version "3.1.0"
|
| 454 |
+
resolved "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz"
|
| 455 |
+
integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==
|
| 456 |
+
|
| 457 |
+
make-error@^1.1.1:
|
| 458 |
+
version "1.3.6"
|
| 459 |
+
resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz"
|
| 460 |
+
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
|
| 461 |
+
|
| 462 |
+
math-intrinsics@^1.1.0:
|
| 463 |
+
version "1.1.0"
|
| 464 |
+
resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz"
|
| 465 |
+
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
|
| 466 |
+
|
| 467 |
+
mdast-util-from-markdown@^2.0.0:
|
| 468 |
+
version "2.0.2"
|
| 469 |
+
resolved "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz"
|
| 470 |
+
integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==
|
| 471 |
+
dependencies:
|
| 472 |
+
"@types/mdast" "^4.0.0"
|
| 473 |
+
"@types/unist" "^3.0.0"
|
| 474 |
+
decode-named-character-reference "^1.0.0"
|
| 475 |
+
devlop "^1.0.0"
|
| 476 |
+
mdast-util-to-string "^4.0.0"
|
| 477 |
+
micromark "^4.0.0"
|
| 478 |
+
micromark-util-decode-numeric-character-reference "^2.0.0"
|
| 479 |
+
micromark-util-decode-string "^2.0.0"
|
| 480 |
+
micromark-util-normalize-identifier "^2.0.0"
|
| 481 |
+
micromark-util-symbol "^2.0.0"
|
| 482 |
+
micromark-util-types "^2.0.0"
|
| 483 |
+
unist-util-stringify-position "^4.0.0"
|
| 484 |
+
|
| 485 |
+
mdast-util-mdx-expression@^2.0.0:
|
| 486 |
+
version "2.0.1"
|
| 487 |
+
resolved "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz"
|
| 488 |
+
integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==
|
| 489 |
+
dependencies:
|
| 490 |
+
"@types/estree-jsx" "^1.0.0"
|
| 491 |
+
"@types/hast" "^3.0.0"
|
| 492 |
+
"@types/mdast" "^4.0.0"
|
| 493 |
+
devlop "^1.0.0"
|
| 494 |
+
mdast-util-from-markdown "^2.0.0"
|
| 495 |
+
mdast-util-to-markdown "^2.0.0"
|
| 496 |
+
|
| 497 |
+
mdast-util-mdx-jsx@^3.0.0:
|
| 498 |
+
version "3.2.0"
|
| 499 |
+
resolved "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz"
|
| 500 |
+
integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==
|
| 501 |
+
dependencies:
|
| 502 |
+
"@types/estree-jsx" "^1.0.0"
|
| 503 |
+
"@types/hast" "^3.0.0"
|
| 504 |
+
"@types/mdast" "^4.0.0"
|
| 505 |
+
"@types/unist" "^3.0.0"
|
| 506 |
+
ccount "^2.0.0"
|
| 507 |
+
devlop "^1.1.0"
|
| 508 |
+
mdast-util-from-markdown "^2.0.0"
|
| 509 |
+
mdast-util-to-markdown "^2.0.0"
|
| 510 |
+
parse-entities "^4.0.0"
|
| 511 |
+
stringify-entities "^4.0.0"
|
| 512 |
+
unist-util-stringify-position "^4.0.0"
|
| 513 |
+
vfile-message "^4.0.0"
|
| 514 |
+
|
| 515 |
+
mdast-util-mdx@^3.0.0:
|
| 516 |
+
version "3.0.0"
|
| 517 |
+
resolved "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz"
|
| 518 |
+
integrity sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==
|
| 519 |
+
dependencies:
|
| 520 |
+
mdast-util-from-markdown "^2.0.0"
|
| 521 |
+
mdast-util-mdx-expression "^2.0.0"
|
| 522 |
+
mdast-util-mdx-jsx "^3.0.0"
|
| 523 |
+
mdast-util-mdxjs-esm "^2.0.0"
|
| 524 |
+
mdast-util-to-markdown "^2.0.0"
|
| 525 |
+
|
| 526 |
+
mdast-util-mdxjs-esm@^2.0.0:
|
| 527 |
+
version "2.0.1"
|
| 528 |
+
resolved "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz"
|
| 529 |
+
integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==
|
| 530 |
+
dependencies:
|
| 531 |
+
"@types/estree-jsx" "^1.0.0"
|
| 532 |
+
"@types/hast" "^3.0.0"
|
| 533 |
+
"@types/mdast" "^4.0.0"
|
| 534 |
+
devlop "^1.0.0"
|
| 535 |
+
mdast-util-from-markdown "^2.0.0"
|
| 536 |
+
mdast-util-to-markdown "^2.0.0"
|
| 537 |
+
|
| 538 |
+
mdast-util-phrasing@^4.0.0:
|
| 539 |
+
version "4.1.0"
|
| 540 |
+
resolved "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz"
|
| 541 |
+
integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==
|
| 542 |
+
dependencies:
|
| 543 |
+
"@types/mdast" "^4.0.0"
|
| 544 |
+
unist-util-is "^6.0.0"
|
| 545 |
+
|
| 546 |
+
mdast-util-to-markdown@^2.0.0:
|
| 547 |
+
version "2.1.2"
|
| 548 |
+
resolved "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz"
|
| 549 |
+
integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==
|
| 550 |
+
dependencies:
|
| 551 |
+
"@types/mdast" "^4.0.0"
|
| 552 |
+
"@types/unist" "^3.0.0"
|
| 553 |
+
longest-streak "^3.0.0"
|
| 554 |
+
mdast-util-phrasing "^4.0.0"
|
| 555 |
+
mdast-util-to-string "^4.0.0"
|
| 556 |
+
micromark-util-classify-character "^2.0.0"
|
| 557 |
+
micromark-util-decode-string "^2.0.0"
|
| 558 |
+
unist-util-visit "^5.0.0"
|
| 559 |
+
zwitch "^2.0.0"
|
| 560 |
+
|
| 561 |
+
mdast-util-to-string@^4.0.0:
|
| 562 |
+
version "4.0.0"
|
| 563 |
+
resolved "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz"
|
| 564 |
+
integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==
|
| 565 |
+
dependencies:
|
| 566 |
+
"@types/mdast" "^4.0.0"
|
| 567 |
+
|
| 568 |
+
micromark-core-commonmark@^2.0.0:
|
| 569 |
+
version "2.0.3"
|
| 570 |
+
resolved "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz"
|
| 571 |
+
integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==
|
| 572 |
+
dependencies:
|
| 573 |
+
decode-named-character-reference "^1.0.0"
|
| 574 |
+
devlop "^1.0.0"
|
| 575 |
+
micromark-factory-destination "^2.0.0"
|
| 576 |
+
micromark-factory-label "^2.0.0"
|
| 577 |
+
micromark-factory-space "^2.0.0"
|
| 578 |
+
micromark-factory-title "^2.0.0"
|
| 579 |
+
micromark-factory-whitespace "^2.0.0"
|
| 580 |
+
micromark-util-character "^2.0.0"
|
| 581 |
+
micromark-util-chunked "^2.0.0"
|
| 582 |
+
micromark-util-classify-character "^2.0.0"
|
| 583 |
+
micromark-util-html-tag-name "^2.0.0"
|
| 584 |
+
micromark-util-normalize-identifier "^2.0.0"
|
| 585 |
+
micromark-util-resolve-all "^2.0.0"
|
| 586 |
+
micromark-util-subtokenize "^2.0.0"
|
| 587 |
+
micromark-util-symbol "^2.0.0"
|
| 588 |
+
micromark-util-types "^2.0.0"
|
| 589 |
+
|
| 590 |
+
micromark-extension-mdx-expression@^3.0.0:
|
| 591 |
+
version "3.0.1"
|
| 592 |
+
resolved "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz"
|
| 593 |
+
integrity sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==
|
| 594 |
+
dependencies:
|
| 595 |
+
"@types/estree" "^1.0.0"
|
| 596 |
+
devlop "^1.0.0"
|
| 597 |
+
micromark-factory-mdx-expression "^2.0.0"
|
| 598 |
+
micromark-factory-space "^2.0.0"
|
| 599 |
+
micromark-util-character "^2.0.0"
|
| 600 |
+
micromark-util-events-to-acorn "^2.0.0"
|
| 601 |
+
micromark-util-symbol "^2.0.0"
|
| 602 |
+
micromark-util-types "^2.0.0"
|
| 603 |
+
|
| 604 |
+
micromark-extension-mdx-jsx@^3.0.0:
|
| 605 |
+
version "3.0.2"
|
| 606 |
+
resolved "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz"
|
| 607 |
+
integrity sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==
|
| 608 |
+
dependencies:
|
| 609 |
+
"@types/estree" "^1.0.0"
|
| 610 |
+
devlop "^1.0.0"
|
| 611 |
+
estree-util-is-identifier-name "^3.0.0"
|
| 612 |
+
micromark-factory-mdx-expression "^2.0.0"
|
| 613 |
+
micromark-factory-space "^2.0.0"
|
| 614 |
+
micromark-util-character "^2.0.0"
|
| 615 |
+
micromark-util-events-to-acorn "^2.0.0"
|
| 616 |
+
micromark-util-symbol "^2.0.0"
|
| 617 |
+
micromark-util-types "^2.0.0"
|
| 618 |
+
vfile-message "^4.0.0"
|
| 619 |
+
|
| 620 |
+
micromark-extension-mdx-md@^2.0.0:
|
| 621 |
+
version "2.0.0"
|
| 622 |
+
resolved "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz"
|
| 623 |
+
integrity sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==
|
| 624 |
+
dependencies:
|
| 625 |
+
micromark-util-types "^2.0.0"
|
| 626 |
+
|
| 627 |
+
micromark-extension-mdxjs-esm@^3.0.0:
|
| 628 |
+
version "3.0.0"
|
| 629 |
+
resolved "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz"
|
| 630 |
+
integrity sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==
|
| 631 |
+
dependencies:
|
| 632 |
+
"@types/estree" "^1.0.0"
|
| 633 |
+
devlop "^1.0.0"
|
| 634 |
+
micromark-core-commonmark "^2.0.0"
|
| 635 |
+
micromark-util-character "^2.0.0"
|
| 636 |
+
micromark-util-events-to-acorn "^2.0.0"
|
| 637 |
+
micromark-util-symbol "^2.0.0"
|
| 638 |
+
micromark-util-types "^2.0.0"
|
| 639 |
+
unist-util-position-from-estree "^2.0.0"
|
| 640 |
+
vfile-message "^4.0.0"
|
| 641 |
+
|
| 642 |
+
micromark-extension-mdxjs@^3.0.0:
|
| 643 |
+
version "3.0.0"
|
| 644 |
+
resolved "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz"
|
| 645 |
+
integrity sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==
|
| 646 |
+
dependencies:
|
| 647 |
+
acorn "^8.0.0"
|
| 648 |
+
acorn-jsx "^5.0.0"
|
| 649 |
+
micromark-extension-mdx-expression "^3.0.0"
|
| 650 |
+
micromark-extension-mdx-jsx "^3.0.0"
|
| 651 |
+
micromark-extension-mdx-md "^2.0.0"
|
| 652 |
+
micromark-extension-mdxjs-esm "^3.0.0"
|
| 653 |
+
micromark-util-combine-extensions "^2.0.0"
|
| 654 |
+
micromark-util-types "^2.0.0"
|
| 655 |
+
|
| 656 |
+
micromark-factory-destination@^2.0.0:
|
| 657 |
+
version "2.0.1"
|
| 658 |
+
resolved "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz"
|
| 659 |
+
integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==
|
| 660 |
+
dependencies:
|
| 661 |
+
micromark-util-character "^2.0.0"
|
| 662 |
+
micromark-util-symbol "^2.0.0"
|
| 663 |
+
micromark-util-types "^2.0.0"
|
| 664 |
+
|
| 665 |
+
micromark-factory-label@^2.0.0:
|
| 666 |
+
version "2.0.1"
|
| 667 |
+
resolved "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz"
|
| 668 |
+
integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==
|
| 669 |
+
dependencies:
|
| 670 |
+
devlop "^1.0.0"
|
| 671 |
+
micromark-util-character "^2.0.0"
|
| 672 |
+
micromark-util-symbol "^2.0.0"
|
| 673 |
+
micromark-util-types "^2.0.0"
|
| 674 |
+
|
| 675 |
+
micromark-factory-mdx-expression@^2.0.0:
|
| 676 |
+
version "2.0.3"
|
| 677 |
+
resolved "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz"
|
| 678 |
+
integrity sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==
|
| 679 |
+
dependencies:
|
| 680 |
+
"@types/estree" "^1.0.0"
|
| 681 |
+
devlop "^1.0.0"
|
| 682 |
+
micromark-factory-space "^2.0.0"
|
| 683 |
+
micromark-util-character "^2.0.0"
|
| 684 |
+
micromark-util-events-to-acorn "^2.0.0"
|
| 685 |
+
micromark-util-symbol "^2.0.0"
|
| 686 |
+
micromark-util-types "^2.0.0"
|
| 687 |
+
unist-util-position-from-estree "^2.0.0"
|
| 688 |
+
vfile-message "^4.0.0"
|
| 689 |
+
|
| 690 |
+
micromark-factory-space@^2.0.0:
|
| 691 |
+
version "2.0.1"
|
| 692 |
+
resolved "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz"
|
| 693 |
+
integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==
|
| 694 |
+
dependencies:
|
| 695 |
+
micromark-util-character "^2.0.0"
|
| 696 |
+
micromark-util-types "^2.0.0"
|
| 697 |
+
|
| 698 |
+
micromark-factory-title@^2.0.0:
|
| 699 |
+
version "2.0.1"
|
| 700 |
+
resolved "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz"
|
| 701 |
+
integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==
|
| 702 |
+
dependencies:
|
| 703 |
+
micromark-factory-space "^2.0.0"
|
| 704 |
+
micromark-util-character "^2.0.0"
|
| 705 |
+
micromark-util-symbol "^2.0.0"
|
| 706 |
+
micromark-util-types "^2.0.0"
|
| 707 |
+
|
| 708 |
+
micromark-factory-whitespace@^2.0.0:
|
| 709 |
+
version "2.0.1"
|
| 710 |
+
resolved "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz"
|
| 711 |
+
integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==
|
| 712 |
+
dependencies:
|
| 713 |
+
micromark-factory-space "^2.0.0"
|
| 714 |
+
micromark-util-character "^2.0.0"
|
| 715 |
+
micromark-util-symbol "^2.0.0"
|
| 716 |
+
micromark-util-types "^2.0.0"
|
| 717 |
+
|
| 718 |
+
micromark-util-character@^2.0.0:
|
| 719 |
+
version "2.1.1"
|
| 720 |
+
resolved "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz"
|
| 721 |
+
integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==
|
| 722 |
+
dependencies:
|
| 723 |
+
micromark-util-symbol "^2.0.0"
|
| 724 |
+
micromark-util-types "^2.0.0"
|
| 725 |
+
|
| 726 |
+
micromark-util-chunked@^2.0.0:
|
| 727 |
+
version "2.0.1"
|
| 728 |
+
resolved "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz"
|
| 729 |
+
integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==
|
| 730 |
+
dependencies:
|
| 731 |
+
micromark-util-symbol "^2.0.0"
|
| 732 |
+
|
| 733 |
+
micromark-util-classify-character@^2.0.0:
|
| 734 |
+
version "2.0.1"
|
| 735 |
+
resolved "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz"
|
| 736 |
+
integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==
|
| 737 |
+
dependencies:
|
| 738 |
+
micromark-util-character "^2.0.0"
|
| 739 |
+
micromark-util-symbol "^2.0.0"
|
| 740 |
+
micromark-util-types "^2.0.0"
|
| 741 |
+
|
| 742 |
+
micromark-util-combine-extensions@^2.0.0:
|
| 743 |
+
version "2.0.1"
|
| 744 |
+
resolved "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz"
|
| 745 |
+
integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==
|
| 746 |
+
dependencies:
|
| 747 |
+
micromark-util-chunked "^2.0.0"
|
| 748 |
+
micromark-util-types "^2.0.0"
|
| 749 |
+
|
| 750 |
+
micromark-util-decode-numeric-character-reference@^2.0.0:
|
| 751 |
+
version "2.0.2"
|
| 752 |
+
resolved "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz"
|
| 753 |
+
integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==
|
| 754 |
+
dependencies:
|
| 755 |
+
micromark-util-symbol "^2.0.0"
|
| 756 |
+
|
| 757 |
+
micromark-util-decode-string@^2.0.0:
|
| 758 |
+
version "2.0.1"
|
| 759 |
+
resolved "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz"
|
| 760 |
+
integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==
|
| 761 |
+
dependencies:
|
| 762 |
+
decode-named-character-reference "^1.0.0"
|
| 763 |
+
micromark-util-character "^2.0.0"
|
| 764 |
+
micromark-util-decode-numeric-character-reference "^2.0.0"
|
| 765 |
+
micromark-util-symbol "^2.0.0"
|
| 766 |
+
|
| 767 |
+
micromark-util-encode@^2.0.0:
|
| 768 |
+
version "2.0.1"
|
| 769 |
+
resolved "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz"
|
| 770 |
+
integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==
|
| 771 |
+
|
| 772 |
+
micromark-util-events-to-acorn@^2.0.0:
|
| 773 |
+
version "2.0.3"
|
| 774 |
+
resolved "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz"
|
| 775 |
+
integrity sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==
|
| 776 |
+
dependencies:
|
| 777 |
+
"@types/estree" "^1.0.0"
|
| 778 |
+
"@types/unist" "^3.0.0"
|
| 779 |
+
devlop "^1.0.0"
|
| 780 |
+
estree-util-visit "^2.0.0"
|
| 781 |
+
micromark-util-symbol "^2.0.0"
|
| 782 |
+
micromark-util-types "^2.0.0"
|
| 783 |
+
vfile-message "^4.0.0"
|
| 784 |
+
|
| 785 |
+
micromark-util-html-tag-name@^2.0.0:
|
| 786 |
+
version "2.0.1"
|
| 787 |
+
resolved "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz"
|
| 788 |
+
integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==
|
| 789 |
+
|
| 790 |
+
micromark-util-normalize-identifier@^2.0.0:
|
| 791 |
+
version "2.0.1"
|
| 792 |
+
resolved "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz"
|
| 793 |
+
integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==
|
| 794 |
+
dependencies:
|
| 795 |
+
micromark-util-symbol "^2.0.0"
|
| 796 |
+
|
| 797 |
+
micromark-util-resolve-all@^2.0.0:
|
| 798 |
+
version "2.0.1"
|
| 799 |
+
resolved "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz"
|
| 800 |
+
integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==
|
| 801 |
+
dependencies:
|
| 802 |
+
micromark-util-types "^2.0.0"
|
| 803 |
+
|
| 804 |
+
micromark-util-sanitize-uri@^2.0.0:
|
| 805 |
+
version "2.0.1"
|
| 806 |
+
resolved "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz"
|
| 807 |
+
integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==
|
| 808 |
+
dependencies:
|
| 809 |
+
micromark-util-character "^2.0.0"
|
| 810 |
+
micromark-util-encode "^2.0.0"
|
| 811 |
+
micromark-util-symbol "^2.0.0"
|
| 812 |
+
|
| 813 |
+
micromark-util-subtokenize@^2.0.0:
|
| 814 |
+
version "2.1.0"
|
| 815 |
+
resolved "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz"
|
| 816 |
+
integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==
|
| 817 |
+
dependencies:
|
| 818 |
+
devlop "^1.0.0"
|
| 819 |
+
micromark-util-chunked "^2.0.0"
|
| 820 |
+
micromark-util-symbol "^2.0.0"
|
| 821 |
+
micromark-util-types "^2.0.0"
|
| 822 |
+
|
| 823 |
+
micromark-util-symbol@^2.0.0:
|
| 824 |
+
version "2.0.1"
|
| 825 |
+
resolved "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz"
|
| 826 |
+
integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==
|
| 827 |
+
|
| 828 |
+
micromark-util-types@^2.0.0:
|
| 829 |
+
version "2.0.2"
|
| 830 |
+
resolved "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz"
|
| 831 |
+
integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==
|
| 832 |
+
|
| 833 |
+
micromark@^4.0.0:
|
| 834 |
+
version "4.0.2"
|
| 835 |
+
resolved "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz"
|
| 836 |
+
integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==
|
| 837 |
+
dependencies:
|
| 838 |
+
"@types/debug" "^4.0.0"
|
| 839 |
+
debug "^4.0.0"
|
| 840 |
+
decode-named-character-reference "^1.0.0"
|
| 841 |
+
devlop "^1.0.0"
|
| 842 |
+
micromark-core-commonmark "^2.0.0"
|
| 843 |
+
micromark-factory-space "^2.0.0"
|
| 844 |
+
micromark-util-character "^2.0.0"
|
| 845 |
+
micromark-util-chunked "^2.0.0"
|
| 846 |
+
micromark-util-combine-extensions "^2.0.0"
|
| 847 |
+
micromark-util-decode-numeric-character-reference "^2.0.0"
|
| 848 |
+
micromark-util-encode "^2.0.0"
|
| 849 |
+
micromark-util-normalize-identifier "^2.0.0"
|
| 850 |
+
micromark-util-resolve-all "^2.0.0"
|
| 851 |
+
micromark-util-sanitize-uri "^2.0.0"
|
| 852 |
+
micromark-util-subtokenize "^2.0.0"
|
| 853 |
+
micromark-util-symbol "^2.0.0"
|
| 854 |
+
micromark-util-types "^2.0.0"
|
| 855 |
+
|
| 856 |
+
mime-db@1.52.0:
|
| 857 |
+
version "1.52.0"
|
| 858 |
+
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
|
| 859 |
+
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
| 860 |
+
|
| 861 |
+
mime-types@^2.1.12, mime-types@^2.1.35:
|
| 862 |
+
version "2.1.35"
|
| 863 |
+
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
|
| 864 |
+
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
| 865 |
+
dependencies:
|
| 866 |
+
mime-db "1.52.0"
|
| 867 |
+
|
| 868 |
+
mime@^3.0.0:
|
| 869 |
+
version "3.0.0"
|
| 870 |
+
resolved "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz"
|
| 871 |
+
integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==
|
| 872 |
+
|
| 873 |
+
ms@^2.1.3:
|
| 874 |
+
version "2.1.3"
|
| 875 |
+
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
|
| 876 |
+
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
| 877 |
+
|
| 878 |
+
node-domexception@^1.0.0:
|
| 879 |
+
version "1.0.0"
|
| 880 |
+
resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz"
|
| 881 |
+
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
|
| 882 |
+
|
| 883 |
+
node-fetch@^2.6.1:
|
| 884 |
+
version "2.7.0"
|
| 885 |
+
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz"
|
| 886 |
+
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
|
| 887 |
+
dependencies:
|
| 888 |
+
whatwg-url "^5.0.0"
|
| 889 |
+
|
| 890 |
+
node-fetch@^2.7.0:
|
| 891 |
+
version "2.7.0"
|
| 892 |
+
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz"
|
| 893 |
+
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
|
| 894 |
+
dependencies:
|
| 895 |
+
whatwg-url "^5.0.0"
|
| 896 |
+
|
| 897 |
+
node-fetch@^3.3.2:
|
| 898 |
+
version "3.3.2"
|
| 899 |
+
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz"
|
| 900 |
+
integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==
|
| 901 |
+
dependencies:
|
| 902 |
+
data-uri-to-buffer "^4.0.0"
|
| 903 |
+
fetch-blob "^3.1.4"
|
| 904 |
+
formdata-polyfill "^4.0.10"
|
| 905 |
+
|
| 906 |
+
notion-to-md@^4.0.0-alpha:
|
| 907 |
+
version "4.0.0-alpha.7"
|
| 908 |
+
resolved "https://registry.npmjs.org/notion-to-md/-/notion-to-md-4.0.0-alpha.7.tgz"
|
| 909 |
+
integrity sha512-3kocKMEVcivy2ccuv2uZDJQFKXdvRmsujbN2GeOwP6yoNqhj/c/fmXroqPkk4XXRqNdJB2jzf5NPhPSWpuZkdA==
|
| 910 |
+
dependencies:
|
| 911 |
+
mime "^3.0.0"
|
| 912 |
+
node-fetch "^2.7.0"
|
| 913 |
+
ts-node "^10.9.2"
|
| 914 |
+
|
| 915 |
+
parse-entities@^4.0.0:
|
| 916 |
+
version "4.0.2"
|
| 917 |
+
resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz"
|
| 918 |
+
integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==
|
| 919 |
+
dependencies:
|
| 920 |
+
"@types/unist" "^2.0.0"
|
| 921 |
+
character-entities-legacy "^3.0.0"
|
| 922 |
+
character-reference-invalid "^2.0.0"
|
| 923 |
+
decode-named-character-reference "^1.0.0"
|
| 924 |
+
is-alphanumerical "^2.0.0"
|
| 925 |
+
is-decimal "^2.0.0"
|
| 926 |
+
is-hexadecimal "^2.0.0"
|
| 927 |
+
|
| 928 |
+
remark-mdx@^3.0.0:
|
| 929 |
+
version "3.1.1"
|
| 930 |
+
resolved "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz"
|
| 931 |
+
integrity sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==
|
| 932 |
+
dependencies:
|
| 933 |
+
mdast-util-mdx "^3.0.0"
|
| 934 |
+
micromark-extension-mdxjs "^3.0.0"
|
| 935 |
+
|
| 936 |
+
remark-parse@^11.0.0:
|
| 937 |
+
version "11.0.0"
|
| 938 |
+
resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz"
|
| 939 |
+
integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==
|
| 940 |
+
dependencies:
|
| 941 |
+
"@types/mdast" "^4.0.0"
|
| 942 |
+
mdast-util-from-markdown "^2.0.0"
|
| 943 |
+
micromark-util-types "^2.0.0"
|
| 944 |
+
unified "^11.0.0"
|
| 945 |
+
|
| 946 |
+
remark-stringify@^11.0.0:
|
| 947 |
+
version "11.0.0"
|
| 948 |
+
resolved "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz"
|
| 949 |
+
integrity sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==
|
| 950 |
+
dependencies:
|
| 951 |
+
"@types/mdast" "^4.0.0"
|
| 952 |
+
mdast-util-to-markdown "^2.0.0"
|
| 953 |
+
unified "^11.0.0"
|
| 954 |
+
|
| 955 |
+
section-matter@^1.0.0:
|
| 956 |
+
version "1.0.0"
|
| 957 |
+
resolved "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz"
|
| 958 |
+
integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==
|
| 959 |
+
dependencies:
|
| 960 |
+
extend-shallow "^2.0.1"
|
| 961 |
+
kind-of "^6.0.0"
|
| 962 |
+
|
| 963 |
+
sprintf-js@~1.0.2:
|
| 964 |
+
version "1.0.3"
|
| 965 |
+
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
|
| 966 |
+
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
|
| 967 |
+
|
| 968 |
+
stringify-entities@^4.0.0:
|
| 969 |
+
version "4.0.4"
|
| 970 |
+
resolved "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz"
|
| 971 |
+
integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==
|
| 972 |
+
dependencies:
|
| 973 |
+
character-entities-html4 "^2.0.0"
|
| 974 |
+
character-entities-legacy "^3.0.0"
|
| 975 |
+
|
| 976 |
+
strip-bom-string@^1.0.0:
|
| 977 |
+
version "1.0.0"
|
| 978 |
+
resolved "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz"
|
| 979 |
+
integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==
|
| 980 |
+
|
| 981 |
+
tr46@~0.0.3:
|
| 982 |
+
version "0.0.3"
|
| 983 |
+
resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
|
| 984 |
+
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
|
| 985 |
+
|
| 986 |
+
trough@^2.0.0:
|
| 987 |
+
version "2.2.0"
|
| 988 |
+
resolved "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz"
|
| 989 |
+
integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==
|
| 990 |
+
|
| 991 |
+
ts-node@^10.9.2:
|
| 992 |
+
version "10.9.2"
|
| 993 |
+
resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz"
|
| 994 |
+
integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==
|
| 995 |
+
dependencies:
|
| 996 |
+
"@cspotcode/source-map-support" "^0.8.0"
|
| 997 |
+
"@tsconfig/node10" "^1.0.7"
|
| 998 |
+
"@tsconfig/node12" "^1.0.7"
|
| 999 |
+
"@tsconfig/node14" "^1.0.0"
|
| 1000 |
+
"@tsconfig/node16" "^1.0.2"
|
| 1001 |
+
acorn "^8.4.1"
|
| 1002 |
+
acorn-walk "^8.1.1"
|
| 1003 |
+
arg "^4.1.0"
|
| 1004 |
+
create-require "^1.1.0"
|
| 1005 |
+
diff "^4.0.1"
|
| 1006 |
+
make-error "^1.1.1"
|
| 1007 |
+
v8-compile-cache-lib "^3.0.1"
|
| 1008 |
+
yn "3.1.1"
|
| 1009 |
+
|
| 1010 |
+
typescript@>=2.7:
|
| 1011 |
+
version "5.9.2"
|
| 1012 |
+
resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz"
|
| 1013 |
+
integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==
|
| 1014 |
+
|
| 1015 |
+
undici-types@~7.12.0:
|
| 1016 |
+
version "7.12.0"
|
| 1017 |
+
resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz"
|
| 1018 |
+
integrity sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==
|
| 1019 |
+
|
| 1020 |
+
unified@^11.0.0, unified@^11.0.4:
|
| 1021 |
+
version "11.0.5"
|
| 1022 |
+
resolved "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz"
|
| 1023 |
+
integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==
|
| 1024 |
+
dependencies:
|
| 1025 |
+
"@types/unist" "^3.0.0"
|
| 1026 |
+
bail "^2.0.0"
|
| 1027 |
+
devlop "^1.0.0"
|
| 1028 |
+
extend "^3.0.0"
|
| 1029 |
+
is-plain-obj "^4.0.0"
|
| 1030 |
+
trough "^2.0.0"
|
| 1031 |
+
vfile "^6.0.0"
|
| 1032 |
+
|
| 1033 |
+
unist-util-is@^6.0.0:
|
| 1034 |
+
version "6.0.0"
|
| 1035 |
+
resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz"
|
| 1036 |
+
integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==
|
| 1037 |
+
dependencies:
|
| 1038 |
+
"@types/unist" "^3.0.0"
|
| 1039 |
+
|
| 1040 |
+
unist-util-position-from-estree@^2.0.0:
|
| 1041 |
+
version "2.0.0"
|
| 1042 |
+
resolved "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz"
|
| 1043 |
+
integrity sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==
|
| 1044 |
+
dependencies:
|
| 1045 |
+
"@types/unist" "^3.0.0"
|
| 1046 |
+
|
| 1047 |
+
unist-util-stringify-position@^4.0.0:
|
| 1048 |
+
version "4.0.0"
|
| 1049 |
+
resolved "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz"
|
| 1050 |
+
integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==
|
| 1051 |
+
dependencies:
|
| 1052 |
+
"@types/unist" "^3.0.0"
|
| 1053 |
+
|
| 1054 |
+
unist-util-visit-parents@^6.0.0:
|
| 1055 |
+
version "6.0.1"
|
| 1056 |
+
resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz"
|
| 1057 |
+
integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==
|
| 1058 |
+
dependencies:
|
| 1059 |
+
"@types/unist" "^3.0.0"
|
| 1060 |
+
unist-util-is "^6.0.0"
|
| 1061 |
+
|
| 1062 |
+
unist-util-visit@^5.0.0:
|
| 1063 |
+
version "5.0.0"
|
| 1064 |
+
resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz"
|
| 1065 |
+
integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==
|
| 1066 |
+
dependencies:
|
| 1067 |
+
"@types/unist" "^3.0.0"
|
| 1068 |
+
unist-util-is "^6.0.0"
|
| 1069 |
+
unist-util-visit-parents "^6.0.0"
|
| 1070 |
+
|
| 1071 |
+
v8-compile-cache-lib@^3.0.1:
|
| 1072 |
+
version "3.0.1"
|
| 1073 |
+
resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz"
|
| 1074 |
+
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
|
| 1075 |
+
|
| 1076 |
+
vfile-message@^4.0.0:
|
| 1077 |
+
version "4.0.3"
|
| 1078 |
+
resolved "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz"
|
| 1079 |
+
integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==
|
| 1080 |
+
dependencies:
|
| 1081 |
+
"@types/unist" "^3.0.0"
|
| 1082 |
+
unist-util-stringify-position "^4.0.0"
|
| 1083 |
+
|
| 1084 |
+
vfile@^6.0.0:
|
| 1085 |
+
version "6.0.3"
|
| 1086 |
+
resolved "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz"
|
| 1087 |
+
integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==
|
| 1088 |
+
dependencies:
|
| 1089 |
+
"@types/unist" "^3.0.0"
|
| 1090 |
+
vfile-message "^4.0.0"
|
| 1091 |
+
|
| 1092 |
+
web-streams-polyfill@^3.0.3:
|
| 1093 |
+
version "3.3.3"
|
| 1094 |
+
resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz"
|
| 1095 |
+
integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==
|
| 1096 |
+
|
| 1097 |
+
webidl-conversions@^3.0.0:
|
| 1098 |
+
version "3.0.1"
|
| 1099 |
+
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
|
| 1100 |
+
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
|
| 1101 |
+
|
| 1102 |
+
whatwg-url@^5.0.0:
|
| 1103 |
+
version "5.0.0"
|
| 1104 |
+
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz"
|
| 1105 |
+
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
|
| 1106 |
+
dependencies:
|
| 1107 |
+
tr46 "~0.0.3"
|
| 1108 |
+
webidl-conversions "^3.0.0"
|
| 1109 |
+
|
| 1110 |
+
yn@3.1.1:
|
| 1111 |
+
version "3.1.1"
|
| 1112 |
+
resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz"
|
| 1113 |
+
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
| 1114 |
+
|
| 1115 |
+
zwitch@^2.0.0:
|
| 1116 |
+
version "2.0.4"
|
| 1117 |
+
resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz"
|
| 1118 |
+
integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==
|
app/src/components/Hero.astro
CHANGED
|
@@ -5,26 +5,49 @@ interface Props {
|
|
| 5 |
title: string; // may contain HTML (e.g., <br/>)
|
| 6 |
titleRaw?: string; // plain title for slug/PDF (optional)
|
| 7 |
description?: string;
|
| 8 |
-
authors?: Array<
|
|
|
|
|
|
|
| 9 |
affiliations?: Array<{ id: number; name: string; url?: string }>;
|
| 10 |
affiliation?: string; // legacy single affiliation
|
| 11 |
published?: string;
|
| 12 |
doi?: string;
|
| 13 |
}
|
| 14 |
|
| 15 |
-
const {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
type Author = { name: string; url?: string; affiliationIndices?: number[] };
|
| 18 |
|
| 19 |
-
function normalizeAuthors(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
return (Array.isArray(input) ? input : [])
|
| 21 |
.map((a) => {
|
| 22 |
-
if (typeof a ===
|
| 23 |
return { name: a } as Author;
|
| 24 |
}
|
| 25 |
-
const name = (a?.name ??
|
| 26 |
const url = (a?.url ?? a?.link) as string | undefined;
|
| 27 |
-
const affiliationIndices = Array.isArray((a as any)?.affiliationIndices)
|
|
|
|
|
|
|
| 28 |
return { name, url, affiliationIndices } as Author;
|
| 29 |
})
|
| 30 |
.filter((a) => a.name && a.name.trim().length > 0);
|
|
@@ -35,35 +58,41 @@ const normalizedAuthors: Author[] = normalizeAuthors(authors as any);
|
|
| 35 |
// Determine if affiliation superscripts should be shown (only when there are multiple distinct affiliations referenced by authors)
|
| 36 |
const authorAffiliationIndexSet = new Set<number>();
|
| 37 |
for (const author of normalizedAuthors) {
|
| 38 |
-
const indices = Array.isArray(author.affiliationIndices)
|
|
|
|
|
|
|
| 39 |
for (const idx of indices) {
|
| 40 |
-
if (typeof idx ===
|
| 41 |
authorAffiliationIndexSet.add(idx);
|
| 42 |
}
|
| 43 |
}
|
| 44 |
}
|
| 45 |
const shouldShowAffiliationSupers = authorAffiliationIndexSet.size > 1;
|
| 46 |
-
const hasMultipleAffiliations =
|
|
|
|
| 47 |
|
| 48 |
function stripHtml(text: string): string {
|
| 49 |
-
return String(text ||
|
| 50 |
}
|
| 51 |
|
| 52 |
function slugify(text: string): string {
|
| 53 |
-
return
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
|
|
|
|
|
|
| 60 |
}
|
| 61 |
|
| 62 |
const pdfBase = titleRaw ? stripHtml(titleRaw) : stripHtml(title);
|
| 63 |
const pdfFilename = `${slugify(pdfBase)}.pdf`;
|
| 64 |
---
|
|
|
|
| 65 |
<section class="hero">
|
| 66 |
-
<h1 class="hero-title" set:html={title}
|
| 67 |
<div class="hero-banner">
|
| 68 |
<HtmlEmbed src="banner.html" frameless />
|
| 69 |
{description && <p class="hero-desc">{description}</p>}
|
|
@@ -72,53 +101,82 @@ const pdfFilename = `${slugify(pdfBase)}.pdf`;
|
|
| 72 |
|
| 73 |
<header class="meta" aria-label="Article meta information">
|
| 74 |
<div class="meta-container">
|
| 75 |
-
{
|
| 76 |
-
|
| 77 |
-
<
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
<
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
<!-- {doi && (
|
| 123 |
<div class="meta-container-cell">
|
| 124 |
<h3>DOI</h3>
|
|
@@ -128,7 +186,12 @@ const pdfFilename = `${slugify(pdfBase)}.pdf`;
|
|
| 128 |
<div class="meta-container-cell meta-container-cell--pdf">
|
| 129 |
<h3>PDF</h3>
|
| 130 |
<p>
|
| 131 |
-
<a
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
Download PDF
|
| 133 |
</a>
|
| 134 |
</p>
|
|
@@ -136,7 +199,6 @@ const pdfFilename = `${slugify(pdfBase)}.pdf`;
|
|
| 136 |
</div>
|
| 137 |
</header>
|
| 138 |
|
| 139 |
-
|
| 140 |
<style>
|
| 141 |
/* Hero (full-width) */
|
| 142 |
.hero {
|
|
@@ -185,7 +247,7 @@ const pdfFilename = `${slugify(pdfBase)}.pdf`;
|
|
| 185 |
text-underline-offset: 2px;
|
| 186 |
text-decoration-thickness: 0.06em;
|
| 187 |
text-decoration-color: var(--link-underline);
|
| 188 |
-
transition: text-decoration-color .15s ease-in-out;
|
| 189 |
}
|
| 190 |
.meta-container a:hover {
|
| 191 |
text-decoration-color: var(--link-underline-hover);
|
|
@@ -198,6 +260,7 @@ const pdfFilename = `${slugify(pdfBase)}.pdf`;
|
|
| 198 |
display: flex;
|
| 199 |
flex-direction: column;
|
| 200 |
gap: 8px;
|
|
|
|
| 201 |
}
|
| 202 |
.meta-container-cell h3 {
|
| 203 |
margin: 0;
|
|
@@ -205,15 +268,21 @@ const pdfFilename = `${slugify(pdfBase)}.pdf`;
|
|
| 205 |
font-weight: 400;
|
| 206 |
color: var(--muted-color);
|
| 207 |
text-transform: uppercase;
|
| 208 |
-
letter-spacing: .02em;
|
| 209 |
}
|
| 210 |
.meta-container-cell p {
|
| 211 |
margin: 0;
|
| 212 |
-
}
|
| 213 |
.authors {
|
| 214 |
margin: 0;
|
| 215 |
list-style-type: none;
|
| 216 |
padding-left: 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
}
|
| 218 |
.affiliations {
|
| 219 |
margin: 0;
|
|
@@ -227,12 +296,17 @@ const pdfFilename = `${slugify(pdfBase)}.pdf`;
|
|
| 227 |
flex-wrap: wrap;
|
| 228 |
row-gap: 12px;
|
| 229 |
}
|
| 230 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
@media print {
|
| 232 |
.meta-container-cell--pdf {
|
| 233 |
display: none !important;
|
| 234 |
}
|
| 235 |
}
|
| 236 |
</style>
|
| 237 |
-
|
| 238 |
-
|
|
|
|
| 5 |
title: string; // may contain HTML (e.g., <br/>)
|
| 6 |
titleRaw?: string; // plain title for slug/PDF (optional)
|
| 7 |
description?: string;
|
| 8 |
+
authors?: Array<
|
| 9 |
+
string | { name: string; url?: string; affiliationIndices?: number[] }
|
| 10 |
+
>;
|
| 11 |
affiliations?: Array<{ id: number; name: string; url?: string }>;
|
| 12 |
affiliation?: string; // legacy single affiliation
|
| 13 |
published?: string;
|
| 14 |
doi?: string;
|
| 15 |
}
|
| 16 |
|
| 17 |
+
const {
|
| 18 |
+
title,
|
| 19 |
+
titleRaw,
|
| 20 |
+
description,
|
| 21 |
+
authors = [],
|
| 22 |
+
affiliations = [],
|
| 23 |
+
affiliation,
|
| 24 |
+
published,
|
| 25 |
+
doi,
|
| 26 |
+
} = Astro.props as Props;
|
| 27 |
|
| 28 |
type Author = { name: string; url?: string; affiliationIndices?: number[] };
|
| 29 |
|
| 30 |
+
function normalizeAuthors(
|
| 31 |
+
input: Array<
|
| 32 |
+
| string
|
| 33 |
+
| {
|
| 34 |
+
name?: string;
|
| 35 |
+
url?: string;
|
| 36 |
+
link?: string;
|
| 37 |
+
affiliationIndices?: number[];
|
| 38 |
+
}
|
| 39 |
+
>,
|
| 40 |
+
): Author[] {
|
| 41 |
return (Array.isArray(input) ? input : [])
|
| 42 |
.map((a) => {
|
| 43 |
+
if (typeof a === "string") {
|
| 44 |
return { name: a } as Author;
|
| 45 |
}
|
| 46 |
+
const name = (a?.name ?? "").toString();
|
| 47 |
const url = (a?.url ?? a?.link) as string | undefined;
|
| 48 |
+
const affiliationIndices = Array.isArray((a as any)?.affiliationIndices)
|
| 49 |
+
? (a as any).affiliationIndices
|
| 50 |
+
: undefined;
|
| 51 |
return { name, url, affiliationIndices } as Author;
|
| 52 |
})
|
| 53 |
.filter((a) => a.name && a.name.trim().length > 0);
|
|
|
|
| 58 |
// Determine if affiliation superscripts should be shown (only when there are multiple distinct affiliations referenced by authors)
|
| 59 |
const authorAffiliationIndexSet = new Set<number>();
|
| 60 |
for (const author of normalizedAuthors) {
|
| 61 |
+
const indices = Array.isArray(author.affiliationIndices)
|
| 62 |
+
? author.affiliationIndices
|
| 63 |
+
: [];
|
| 64 |
for (const idx of indices) {
|
| 65 |
+
if (typeof idx === "number") {
|
| 66 |
authorAffiliationIndexSet.add(idx);
|
| 67 |
}
|
| 68 |
}
|
| 69 |
}
|
| 70 |
const shouldShowAffiliationSupers = authorAffiliationIndexSet.size > 1;
|
| 71 |
+
const hasMultipleAffiliations =
|
| 72 |
+
Array.isArray(affiliations) && affiliations.length > 1;
|
| 73 |
|
| 74 |
function stripHtml(text: string): string {
|
| 75 |
+
return String(text || "").replace(/<[^>]*>/g, "");
|
| 76 |
}
|
| 77 |
|
| 78 |
function slugify(text: string): string {
|
| 79 |
+
return (
|
| 80 |
+
String(text || "")
|
| 81 |
+
.normalize("NFKD")
|
| 82 |
+
.replace(/\p{Diacritic}+/gu, "")
|
| 83 |
+
.toLowerCase()
|
| 84 |
+
.replace(/[^a-z0-9]+/g, "-")
|
| 85 |
+
.replace(/^-+|-+$/g, "")
|
| 86 |
+
.slice(0, 120) || "article"
|
| 87 |
+
);
|
| 88 |
}
|
| 89 |
|
| 90 |
const pdfBase = titleRaw ? stripHtml(titleRaw) : stripHtml(title);
|
| 91 |
const pdfFilename = `${slugify(pdfBase)}.pdf`;
|
| 92 |
---
|
| 93 |
+
|
| 94 |
<section class="hero">
|
| 95 |
+
<h1 class="hero-title" set:html={title} />
|
| 96 |
<div class="hero-banner">
|
| 97 |
<HtmlEmbed src="banner.html" frameless />
|
| 98 |
{description && <p class="hero-desc">{description}</p>}
|
|
|
|
| 101 |
|
| 102 |
<header class="meta" aria-label="Article meta information">
|
| 103 |
<div class="meta-container">
|
| 104 |
+
{
|
| 105 |
+
normalizedAuthors.length > 0 && (
|
| 106 |
+
<div class="meta-container-cell">
|
| 107 |
+
<h3>Author{normalizedAuthors.length > 1 ? "s" : ""}</h3>
|
| 108 |
+
<ul class="authors">
|
| 109 |
+
{normalizedAuthors.map((a, i) => {
|
| 110 |
+
const supers =
|
| 111 |
+
shouldShowAffiliationSupers &&
|
| 112 |
+
Array.isArray(a.affiliationIndices) &&
|
| 113 |
+
a.affiliationIndices.length ? (
|
| 114 |
+
<sup>{a.affiliationIndices.join(",")}</sup>
|
| 115 |
+
) : null;
|
| 116 |
+
return (
|
| 117 |
+
<li>
|
| 118 |
+
{a.url ? <a href={a.url}>{a.name}</a> : a.name}
|
| 119 |
+
{supers}
|
| 120 |
+
{i < normalizedAuthors.length - 1 && ", "}
|
| 121 |
+
</li>
|
| 122 |
+
);
|
| 123 |
+
})}
|
| 124 |
+
</ul>
|
| 125 |
+
</div>
|
| 126 |
+
)
|
| 127 |
+
}
|
| 128 |
+
{
|
| 129 |
+
Array.isArray(affiliations) && affiliations.length > 0 && (
|
| 130 |
+
<div class="meta-container-cell meta-container-cell--affiliations">
|
| 131 |
+
<h3>Affiliation{affiliations.length > 1 ? "s" : ""}</h3>
|
| 132 |
+
{hasMultipleAffiliations ? (
|
| 133 |
+
<ol class="affiliations">
|
| 134 |
+
{affiliations.map((af) => (
|
| 135 |
+
<li value={af.id}>
|
| 136 |
+
{af.url ? (
|
| 137 |
+
<a href={af.url} target="_blank" rel="noopener noreferrer">
|
| 138 |
+
{af.name}
|
| 139 |
+
</a>
|
| 140 |
+
) : (
|
| 141 |
+
af.name
|
| 142 |
+
)}
|
| 143 |
+
</li>
|
| 144 |
+
))}
|
| 145 |
+
</ol>
|
| 146 |
+
) : (
|
| 147 |
+
<p>
|
| 148 |
+
{affiliations[0]?.url ? (
|
| 149 |
+
<a
|
| 150 |
+
href={affiliations[0].url}
|
| 151 |
+
target="_blank"
|
| 152 |
+
rel="noopener noreferrer"
|
| 153 |
+
>
|
| 154 |
+
{affiliations[0].name}
|
| 155 |
+
</a>
|
| 156 |
+
) : (
|
| 157 |
+
affiliations[0]?.name
|
| 158 |
+
)}
|
| 159 |
+
</p>
|
| 160 |
+
)}
|
| 161 |
+
</div>
|
| 162 |
+
)
|
| 163 |
+
}
|
| 164 |
+
{
|
| 165 |
+
(!affiliations || affiliations.length === 0) && affiliation && (
|
| 166 |
+
<div class="meta-container-cell meta-container-cell--affiliations">
|
| 167 |
+
<h3>Affiliation</h3>
|
| 168 |
+
<p>{affiliation}</p>
|
| 169 |
+
</div>
|
| 170 |
+
)
|
| 171 |
+
}
|
| 172 |
+
{
|
| 173 |
+
published && (
|
| 174 |
+
<div class="meta-container-cell meta-container-cell--published">
|
| 175 |
+
<h3>Published</h3>
|
| 176 |
+
<p>{published}</p>
|
| 177 |
+
</div>
|
| 178 |
+
)
|
| 179 |
+
}
|
| 180 |
<!-- {doi && (
|
| 181 |
<div class="meta-container-cell">
|
| 182 |
<h3>DOI</h3>
|
|
|
|
| 186 |
<div class="meta-container-cell meta-container-cell--pdf">
|
| 187 |
<h3>PDF</h3>
|
| 188 |
<p>
|
| 189 |
+
<a
|
| 190 |
+
class="button"
|
| 191 |
+
href={`/${pdfFilename}`}
|
| 192 |
+
download={pdfFilename}
|
| 193 |
+
aria-label={`Download PDF ${pdfFilename}`}
|
| 194 |
+
>
|
| 195 |
Download PDF
|
| 196 |
</a>
|
| 197 |
</p>
|
|
|
|
| 199 |
</div>
|
| 200 |
</header>
|
| 201 |
|
|
|
|
| 202 |
<style>
|
| 203 |
/* Hero (full-width) */
|
| 204 |
.hero {
|
|
|
|
| 247 |
text-underline-offset: 2px;
|
| 248 |
text-decoration-thickness: 0.06em;
|
| 249 |
text-decoration-color: var(--link-underline);
|
| 250 |
+
transition: text-decoration-color 0.15s ease-in-out;
|
| 251 |
}
|
| 252 |
.meta-container a:hover {
|
| 253 |
text-decoration-color: var(--link-underline-hover);
|
|
|
|
| 260 |
display: flex;
|
| 261 |
flex-direction: column;
|
| 262 |
gap: 8px;
|
| 263 |
+
max-width: 250px;
|
| 264 |
}
|
| 265 |
.meta-container-cell h3 {
|
| 266 |
margin: 0;
|
|
|
|
| 268 |
font-weight: 400;
|
| 269 |
color: var(--muted-color);
|
| 270 |
text-transform: uppercase;
|
| 271 |
+
letter-spacing: 0.02em;
|
| 272 |
}
|
| 273 |
.meta-container-cell p {
|
| 274 |
margin: 0;
|
| 275 |
+
}
|
| 276 |
.authors {
|
| 277 |
margin: 0;
|
| 278 |
list-style-type: none;
|
| 279 |
padding-left: 0;
|
| 280 |
+
display: flex;
|
| 281 |
+
flex-wrap: wrap;
|
| 282 |
+
}
|
| 283 |
+
.authors li {
|
| 284 |
+
white-space: nowrap;
|
| 285 |
+
margin-right: 4px;
|
| 286 |
}
|
| 287 |
.affiliations {
|
| 288 |
margin: 0;
|
|
|
|
| 296 |
flex-wrap: wrap;
|
| 297 |
row-gap: 12px;
|
| 298 |
}
|
| 299 |
+
|
| 300 |
+
@media (max-width: 768px) {
|
| 301 |
+
.meta-container-cell--affiliations,
|
| 302 |
+
.meta-container-cell--pdf {
|
| 303 |
+
text-align: right;
|
| 304 |
+
}
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
@media print {
|
| 308 |
.meta-container-cell--pdf {
|
| 309 |
display: none !important;
|
| 310 |
}
|
| 311 |
}
|
| 312 |
</style>
|
|
|
|
|
|
app/src/components/HtmlEmbed.astro
CHANGED
|
@@ -20,12 +20,15 @@ const html = resolveFragment(src);
|
|
| 20 |
const mountId = `frag-${Math.random().toString(36).slice(2)}`;
|
| 21 |
const dataAttr = Array.isArray(data) ? JSON.stringify(data) : (typeof data === 'string' ? data : undefined);
|
| 22 |
const configAttr = typeof config === 'string' ? config : (config != null ? JSON.stringify(config) : undefined);
|
|
|
|
|
|
|
|
|
|
| 23 |
---
|
| 24 |
{ html ? (
|
| 25 |
<figure class="html-embed" id={id}>
|
| 26 |
{title && <figcaption class="html-embed__title" style={`text-align:${align}`}>{title}</figcaption>}
|
| 27 |
<div class={`html-embed__card${frameless ? ' is-frameless' : ''}`}>
|
| 28 |
-
<div id={mountId} data-datafiles={dataAttr} data-config={configAttr} set:html={
|
| 29 |
</div>
|
| 30 |
{desc && <figcaption class="html-embed__desc" style={`text-align:${align}`} set:html={desc}></figcaption>}
|
| 31 |
</figure>
|
|
@@ -70,7 +73,6 @@ const configAttr = typeof config === 'string' ? config : (config != null ? JSON.
|
|
| 70 |
.html-embed { margin: 0 0 var(--block-spacing-y);
|
| 71 |
z-index: var(--z-elevated);
|
| 72 |
position: relative;
|
| 73 |
-
|
| 74 |
}
|
| 75 |
.html-embed__title {
|
| 76 |
text-align: left;
|
|
@@ -83,12 +85,14 @@ const configAttr = typeof config === 'string' ? config : (config != null ? JSON.
|
|
| 83 |
position: relative;
|
| 84 |
display: block;
|
| 85 |
width: 100%;
|
|
|
|
|
|
|
| 86 |
}
|
| 87 |
.html-embed__card {
|
| 88 |
background: var(--code-bg);
|
| 89 |
border: 1px solid var(--border-color);
|
| 90 |
border-radius: 10px;
|
| 91 |
-
padding:
|
| 92 |
z-index: calc(var(--z-elevated) + 1);
|
| 93 |
position: relative;
|
| 94 |
}
|
|
@@ -108,6 +112,7 @@ const configAttr = typeof config === 'string' ? config : (config != null ? JSON.
|
|
| 108 |
z-index: var(--z-elevated);
|
| 109 |
display: block;
|
| 110 |
width: 100%;
|
|
|
|
| 111 |
}
|
| 112 |
/* Plotly – fragments & controls */
|
| 113 |
.html-embed__card svg text { fill: var(--text-color); }
|
|
|
|
| 20 |
const mountId = `frag-${Math.random().toString(36).slice(2)}`;
|
| 21 |
const dataAttr = Array.isArray(data) ? JSON.stringify(data) : (typeof data === 'string' ? data : undefined);
|
| 22 |
const configAttr = typeof config === 'string' ? config : (config != null ? JSON.stringify(config) : undefined);
|
| 23 |
+
|
| 24 |
+
// Apply the ID to the HTML content if provided
|
| 25 |
+
const htmlWithId = id && html ? html.replace(/<div class="([^"]*)"[^>]*>/, `<div class="$1" id="${id}">`) : html;
|
| 26 |
---
|
| 27 |
{ html ? (
|
| 28 |
<figure class="html-embed" id={id}>
|
| 29 |
{title && <figcaption class="html-embed__title" style={`text-align:${align}`}>{title}</figcaption>}
|
| 30 |
<div class={`html-embed__card${frameless ? ' is-frameless' : ''}`}>
|
| 31 |
+
<div id={mountId} data-datafiles={dataAttr} data-config={configAttr} set:html={htmlWithId} />
|
| 32 |
</div>
|
| 33 |
{desc && <figcaption class="html-embed__desc" style={`text-align:${align}`} set:html={desc}></figcaption>}
|
| 34 |
</figure>
|
|
|
|
| 73 |
.html-embed { margin: 0 0 var(--block-spacing-y);
|
| 74 |
z-index: var(--z-elevated);
|
| 75 |
position: relative;
|
|
|
|
| 76 |
}
|
| 77 |
.html-embed__title {
|
| 78 |
text-align: left;
|
|
|
|
| 85 |
position: relative;
|
| 86 |
display: block;
|
| 87 |
width: 100%;
|
| 88 |
+
background: var(--page-bg);
|
| 89 |
+
z-index: var(--z-elevated);
|
| 90 |
}
|
| 91 |
.html-embed__card {
|
| 92 |
background: var(--code-bg);
|
| 93 |
border: 1px solid var(--border-color);
|
| 94 |
border-radius: 10px;
|
| 95 |
+
padding: 24px;
|
| 96 |
z-index: calc(var(--z-elevated) + 1);
|
| 97 |
position: relative;
|
| 98 |
}
|
|
|
|
| 112 |
z-index: var(--z-elevated);
|
| 113 |
display: block;
|
| 114 |
width: 100%;
|
| 115 |
+
background: var(--page-bg);
|
| 116 |
}
|
| 117 |
/* Plotly – fragments & controls */
|
| 118 |
.html-embed__card svg text { fill: var(--text-color); }
|
app/src/components/Sidenote.astro
CHANGED
|
@@ -14,28 +14,38 @@
|
|
| 14 |
|
| 15 |
containers.forEach((container) => {
|
| 16 |
// Find the previous element (sibling just before)
|
| 17 |
-
const previousElement = container.previousElementSibling;
|
| 18 |
|
| 19 |
-
if (previousElement) {
|
| 20 |
-
//
|
| 21 |
-
|
|
|
|
| 22 |
|
| 23 |
-
//
|
| 24 |
-
previousElement.
|
| 25 |
|
| 26 |
-
//
|
| 27 |
-
|
| 28 |
-
container
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
// Display the container with a fade-in
|
| 33 |
-
|
| 34 |
-
|
| 35 |
|
| 36 |
// Fade-in with transition
|
| 37 |
setTimeout(() => {
|
| 38 |
-
|
| 39 |
}, 10);
|
| 40 |
}
|
| 41 |
});
|
|
@@ -43,10 +53,16 @@
|
|
| 43 |
</script>
|
| 44 |
|
| 45 |
<style is:global>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
.sidenote-container {
|
| 47 |
/* Caché par défaut, sera affiché par JS */
|
| 48 |
display: none;
|
| 49 |
-
margin: 0
|
| 50 |
/* Transition for fade-in */
|
| 51 |
transition: opacity 0.3s ease-in-out;
|
| 52 |
}
|
|
@@ -60,6 +76,11 @@
|
|
| 60 |
}
|
| 61 |
|
| 62 |
@media (--bp-content-collapse) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
.sidenote-container {
|
| 64 |
position: static !important;
|
| 65 |
width: auto !important;
|
|
|
|
| 14 |
|
| 15 |
containers.forEach((container) => {
|
| 16 |
// Find the previous element (sibling just before)
|
| 17 |
+
const previousElement = container.previousElementSibling as HTMLElement;
|
| 18 |
|
| 19 |
+
if (previousElement && previousElement.parentNode) {
|
| 20 |
+
// Create a wrapper div that will contain both the previous element and the sidenote
|
| 21 |
+
const wrapper = document.createElement("div");
|
| 22 |
+
wrapper.className = "sidenote-wrapper";
|
| 23 |
|
| 24 |
+
// Insert the wrapper before the previous element
|
| 25 |
+
previousElement.parentNode.insertBefore(wrapper, previousElement);
|
| 26 |
|
| 27 |
+
// Move both the previous element and the sidenote container into the wrapper
|
| 28 |
+
wrapper.appendChild(previousElement);
|
| 29 |
+
wrapper.appendChild(container);
|
| 30 |
+
|
| 31 |
+
// Style the wrapper to create the layout
|
| 32 |
+
wrapper.style.position = "relative";
|
| 33 |
+
wrapper.style.display = "block";
|
| 34 |
+
|
| 35 |
+
// Style the sidenote container so it positions correctly
|
| 36 |
+
const sidenoteContainer = container as HTMLElement;
|
| 37 |
+
sidenoteContainer.style.position = "absolute";
|
| 38 |
+
sidenoteContainer.style.top = "0";
|
| 39 |
+
sidenoteContainer.style.right = "-292px"; // 260px width + 32px gap
|
| 40 |
+
sidenoteContainer.style.width = "260px";
|
| 41 |
|
| 42 |
// Display the container with a fade-in
|
| 43 |
+
sidenoteContainer.style.display = "block";
|
| 44 |
+
sidenoteContainer.style.opacity = "0";
|
| 45 |
|
| 46 |
// Fade-in with transition
|
| 47 |
setTimeout(() => {
|
| 48 |
+
sidenoteContainer.style.opacity = "1";
|
| 49 |
}, 10);
|
| 50 |
}
|
| 51 |
});
|
|
|
|
| 53 |
</script>
|
| 54 |
|
| 55 |
<style is:global>
|
| 56 |
+
.sidenote-wrapper {
|
| 57 |
+
/* Le wrapper contient l'élément original et le sidenote */
|
| 58 |
+
position: relative;
|
| 59 |
+
display: block;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
.sidenote-container {
|
| 63 |
/* Caché par défaut, sera affiché par JS */
|
| 64 |
display: none;
|
| 65 |
+
margin: 0;
|
| 66 |
/* Transition for fade-in */
|
| 67 |
transition: opacity 0.3s ease-in-out;
|
| 68 |
}
|
|
|
|
| 76 |
}
|
| 77 |
|
| 78 |
@media (--bp-content-collapse) {
|
| 79 |
+
.sidenote-wrapper {
|
| 80 |
+
/* Sur mobile, le wrapper n'a pas besoin de position relative */
|
| 81 |
+
position: static !important;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
.sidenote-container {
|
| 85 |
position: static !important;
|
| 86 |
width: auto !important;
|
app/src/styles/_layout.css
CHANGED
|
@@ -13,12 +13,16 @@
|
|
| 13 |
align-items: start;
|
| 14 |
}
|
| 15 |
|
| 16 |
-
.content-grid
|
| 17 |
max-width: 100%;
|
| 18 |
margin: 0;
|
| 19 |
padding: 0;
|
| 20 |
}
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
@media (--bp-content-collapse) {
|
| 23 |
.content-grid {
|
| 24 |
overflow: hidden;
|
|
@@ -44,7 +48,7 @@
|
|
| 44 |
gap: 16px;
|
| 45 |
}
|
| 46 |
|
| 47 |
-
.footer-inner
|
| 48 |
grid-column: auto;
|
| 49 |
margin-top: 16px;
|
| 50 |
}
|
|
@@ -74,6 +78,9 @@
|
|
| 74 |
width: min(1100px, 100vw - var(--content-padding-x) * 2);
|
| 75 |
margin-left: 50%;
|
| 76 |
transform: translateX(-50%);
|
|
|
|
|
|
|
|
|
|
| 77 |
}
|
| 78 |
|
| 79 |
.full-width {
|
|
@@ -84,11 +91,13 @@
|
|
| 84 |
}
|
| 85 |
|
| 86 |
@media (--bp-content-collapse) {
|
|
|
|
| 87 |
.wide,
|
| 88 |
.full-width {
|
| 89 |
width: 100%;
|
| 90 |
margin-left: 0;
|
| 91 |
margin-right: 0;
|
|
|
|
| 92 |
transform: none;
|
| 93 |
}
|
| 94 |
}
|
|
@@ -118,6 +127,7 @@
|
|
| 118 |
max-width: 100%;
|
| 119 |
padding: 0 var(--spacing-4);
|
| 120 |
}
|
|
|
|
| 121 |
header.meta .meta-container .meta-container-cell {
|
| 122 |
flex: 1 1 calc(50% - 8px);
|
| 123 |
min-width: 0;
|
|
@@ -129,12 +139,14 @@
|
|
| 129 |
flex-basis: 100%;
|
| 130 |
text-align: center;
|
| 131 |
}
|
|
|
|
| 132 |
/* Center ordered list numbers within meta (e.g., affiliations) */
|
| 133 |
header.meta .affiliations {
|
| 134 |
list-style-position: inside;
|
| 135 |
padding-left: 0;
|
| 136 |
margin-left: 0;
|
| 137 |
}
|
|
|
|
| 138 |
header.meta .affiliations li {
|
| 139 |
text-align: center;
|
| 140 |
}
|
|
@@ -160,7 +172,4 @@
|
|
| 160 |
width: 100%;
|
| 161 |
min-width: 0;
|
| 162 |
}
|
| 163 |
-
}
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
|
|
|
| 13 |
align-items: start;
|
| 14 |
}
|
| 15 |
|
| 16 |
+
.content-grid>main {
|
| 17 |
max-width: 100%;
|
| 18 |
margin: 0;
|
| 19 |
padding: 0;
|
| 20 |
}
|
| 21 |
|
| 22 |
+
.content-grid>main>*:first-child {
|
| 23 |
+
margin-top: 0;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
@media (--bp-content-collapse) {
|
| 27 |
.content-grid {
|
| 28 |
overflow: hidden;
|
|
|
|
| 48 |
gap: 16px;
|
| 49 |
}
|
| 50 |
|
| 51 |
+
.footer-inner>h3 {
|
| 52 |
grid-column: auto;
|
| 53 |
margin-top: 16px;
|
| 54 |
}
|
|
|
|
| 78 |
width: min(1100px, 100vw - var(--content-padding-x) * 2);
|
| 79 |
margin-left: 50%;
|
| 80 |
transform: translateX(-50%);
|
| 81 |
+
padding: var(--content-padding-x);
|
| 82 |
+
border-radius: var(--button-radius);
|
| 83 |
+
background-color: var(--page-bg);
|
| 84 |
}
|
| 85 |
|
| 86 |
.full-width {
|
|
|
|
| 91 |
}
|
| 92 |
|
| 93 |
@media (--bp-content-collapse) {
|
| 94 |
+
|
| 95 |
.wide,
|
| 96 |
.full-width {
|
| 97 |
width: 100%;
|
| 98 |
margin-left: 0;
|
| 99 |
margin-right: 0;
|
| 100 |
+
padding: 0;
|
| 101 |
transform: none;
|
| 102 |
}
|
| 103 |
}
|
|
|
|
| 127 |
max-width: 100%;
|
| 128 |
padding: 0 var(--spacing-4);
|
| 129 |
}
|
| 130 |
+
|
| 131 |
header.meta .meta-container .meta-container-cell {
|
| 132 |
flex: 1 1 calc(50% - 8px);
|
| 133 |
min-width: 0;
|
|
|
|
| 139 |
flex-basis: 100%;
|
| 140 |
text-align: center;
|
| 141 |
}
|
| 142 |
+
|
| 143 |
/* Center ordered list numbers within meta (e.g., affiliations) */
|
| 144 |
header.meta .affiliations {
|
| 145 |
list-style-position: inside;
|
| 146 |
padding-left: 0;
|
| 147 |
margin-left: 0;
|
| 148 |
}
|
| 149 |
+
|
| 150 |
header.meta .affiliations li {
|
| 151 |
text-align: center;
|
| 152 |
}
|
|
|
|
| 172 |
width: 100%;
|
| 173 |
min-width: 0;
|
| 174 |
}
|
| 175 |
+
}
|
|
|
|
|
|
|
|
|
app/src/styles/_variables.css
CHANGED
|
@@ -109,11 +109,10 @@
|
|
| 109 |
--grid-color: rgba(255, 255, 255, .10);
|
| 110 |
|
| 111 |
/* Primary (lower L in dark) */
|
| 112 |
-
--primary-color: oklch(from var(--primary-base) calc(l - 0.08) c 100);
|
| 113 |
--primary-color-hover: oklch(from var(--primary-color) calc(l - 0.05) c h);
|
| 114 |
--primary-color-active: oklch(from var(--primary-color) calc(l - 0.10) c h);
|
| 115 |
--on-primary: #0f1115;
|
| 116 |
|
| 117 |
color-scheme: dark;
|
| 118 |
-
background:
|
| 119 |
}
|
|
|
|
| 109 |
--grid-color: rgba(255, 255, 255, .10);
|
| 110 |
|
| 111 |
/* Primary (lower L in dark) */
|
|
|
|
| 112 |
--primary-color-hover: oklch(from var(--primary-color) calc(l - 0.05) c h);
|
| 113 |
--primary-color-active: oklch(from var(--primary-color) calc(l - 0.10) c h);
|
| 114 |
--on-primary: #0f1115;
|
| 115 |
|
| 116 |
color-scheme: dark;
|
| 117 |
+
background: var(--page-bg);
|
| 118 |
}
|
app/src/styles/components/_form.css
CHANGED
|
@@ -5,7 +5,7 @@
|
|
| 5 |
/* Select styling with modern chevron */
|
| 6 |
select {
|
| 7 |
background-color: var(--page-bg);
|
| 8 |
-
border: 1px solid var(--border-color);
|
| 9 |
border-radius: var(--button-radius);
|
| 10 |
padding: var(--button-padding-y) var(--button-padding-x) var(--button-padding-y) var(--button-padding-x);
|
| 11 |
font-family: var(--default-font-family);
|
|
@@ -13,13 +13,18 @@ select {
|
|
| 13 |
color: var(--text-color);
|
| 14 |
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8.825L1.175 4 2.35 2.825 6 6.475 9.65 2.825 10.825 4z'/%3E%3C/svg%3E");
|
| 15 |
background-repeat: no-repeat;
|
| 16 |
-
background-position: right calc(var(--button-padding-x) +
|
| 17 |
background-size: 12px;
|
| 18 |
cursor: pointer;
|
| 19 |
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
|
|
|
|
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
-
select:hover,
|
|
|
|
|
|
|
| 23 |
border-color: var(--primary-color);
|
| 24 |
}
|
| 25 |
|
|
@@ -238,4 +243,4 @@ div[class*="flex"] label,
|
|
| 238 |
.theme-selector label {
|
| 239 |
margin-bottom: 0 !important;
|
| 240 |
align-self: center;
|
| 241 |
-
}
|
|
|
|
| 5 |
/* Select styling with modern chevron */
|
| 6 |
select {
|
| 7 |
background-color: var(--page-bg);
|
| 8 |
+
border: 1px solid color-mix(in srgb, var(--primary-color) 50%, var(--border-color));
|
| 9 |
border-radius: var(--button-radius);
|
| 10 |
padding: var(--button-padding-y) var(--button-padding-x) var(--button-padding-y) var(--button-padding-x);
|
| 11 |
font-family: var(--default-font-family);
|
|
|
|
| 13 |
color: var(--text-color);
|
| 14 |
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8.825L1.175 4 2.35 2.825 6 6.475 9.65 2.825 10.825 4z'/%3E%3C/svg%3E");
|
| 15 |
background-repeat: no-repeat;
|
| 16 |
+
background-position: right calc(var(--button-padding-x) + 2px) center;
|
| 17 |
background-size: 12px;
|
| 18 |
cursor: pointer;
|
| 19 |
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
| 20 |
+
-webkit-appearance: none;
|
| 21 |
+
-moz-appearance: none;
|
| 22 |
+
appearance: none;
|
| 23 |
}
|
| 24 |
|
| 25 |
+
select:hover,
|
| 26 |
+
select:focus,
|
| 27 |
+
select:active {
|
| 28 |
border-color: var(--primary-color);
|
| 29 |
}
|
| 30 |
|
|
|
|
| 243 |
.theme-selector label {
|
| 244 |
margin-bottom: 0 !important;
|
| 245 |
align-self: center;
|
| 246 |
+
}
|
app/src/styles/components/_table.css
CHANGED
|
@@ -1,98 +1,126 @@
|
|
| 1 |
.content-grid main table {
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
min-width: 100%;
|
| 43 |
-
max-width: none;
|
| 44 |
-
}
|
| 45 |
-
/* Vertical dividers between columns (no outer right border) */
|
| 46 |
-
.content-grid main .table-scroll > table th,
|
| 47 |
-
.content-grid main .table-scroll > table td {
|
| 48 |
-
border-right: 1px solid var(--border-color);
|
| 49 |
-
}
|
| 50 |
-
.content-grid main .table-scroll > table th:last-child,
|
| 51 |
-
.content-grid main .table-scroll > table td:last-child {
|
| 52 |
-
border-right: none;
|
| 53 |
-
}
|
| 54 |
-
.content-grid main .table-scroll > table thead th:first-child {
|
| 55 |
-
border-top-left-radius: var(--table-border-radius);
|
| 56 |
-
}
|
| 57 |
-
.content-grid main .table-scroll > table thead th:last-child {
|
| 58 |
-
border-top-right-radius: var(--table-border-radius);
|
| 59 |
-
}
|
| 60 |
-
.content-grid main .table-scroll > table tbody tr:last-child td:first-child {
|
| 61 |
-
border-bottom-left-radius: var(--table-border-radius);
|
| 62 |
-
}
|
| 63 |
-
.content-grid main .table-scroll > table tbody tr:last-child td:last-child {
|
| 64 |
-
border-bottom-right-radius: var(--table-border-radius);
|
| 65 |
-
}
|
| 66 |
-
/* Zebra striping for odd rows */
|
| 67 |
-
.content-grid main .table-scroll > table tbody tr:nth-child(odd) td {
|
| 68 |
-
background: var(--table-row-odd-bg);
|
| 69 |
-
}
|
| 70 |
-
/* Remove bottom border on last row */
|
| 71 |
-
.content-grid main .table-scroll > table tbody tr:last-child td {
|
| 72 |
-
border-bottom: none;
|
| 73 |
-
}
|
| 74 |
-
|
| 75 |
-
/* Accordion context: remove outer borders/radius and fit content flush */
|
| 76 |
-
.accordion .accordion__content .table-scroll {
|
| 77 |
-
border: none;
|
| 78 |
-
border-radius: 0;
|
| 79 |
-
margin: 0;
|
| 80 |
-
margin-bottom: 0 !important;
|
| 81 |
-
}
|
| 82 |
-
/* Ensure no bottom margin even if table isn't wrapped (fallback) */
|
| 83 |
-
.accordion .accordion__content table { margin: 0 !important; }
|
| 84 |
-
.accordion .accordion__content .table-scroll > table thead th:first-child,
|
| 85 |
-
.accordion .accordion__content .table-scroll > table thead th:last-child,
|
| 86 |
-
.accordion .accordion__content .table-scroll > table tbody tr:last-child td:first-child,
|
| 87 |
-
.accordion .accordion__content .table-scroll > table tbody tr:last-child td:last-child {
|
| 88 |
-
border-radius: 0;
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
/* Fallback for browsers without fit-content support */
|
| 92 |
-
@supports not (width: fit-content) {
|
| 93 |
-
.content-grid main .table-scroll > table {
|
| 94 |
-
width: max-content;
|
| 95 |
-
min-width: 100%;
|
| 96 |
-
}
|
| 97 |
}
|
| 98 |
-
|
|
|
|
| 1 |
.content-grid main table {
|
| 2 |
+
border-collapse: collapse;
|
| 3 |
+
table-layout: auto;
|
| 4 |
+
margin: 0;
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
.content-grid main th,
|
| 8 |
+
.content-grid main td {
|
| 9 |
+
border-bottom: 1px solid var(--border-color);
|
| 10 |
+
padding: 6px 8px;
|
| 11 |
+
text-align: left;
|
| 12 |
+
font-size: 15px;
|
| 13 |
+
white-space: nowrap;
|
| 14 |
+
/* prevent squashing; allow horizontal scroll instead */
|
| 15 |
+
word-break: auto-phrase;
|
| 16 |
+
white-space: break-spaces;
|
| 17 |
+
vertical-align: top;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
.content-grid main th:last-child,
|
| 21 |
+
.content-grid main td:last-child {
|
| 22 |
+
text-align: right;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
.content-grid main thead th {
|
| 26 |
+
border-bottom: 1px solid var(--border-color);
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
.content-grid main thead th {
|
| 30 |
+
border-bottom: 1px solid var(--border-color);
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
.content-grid main thead th {
|
| 34 |
+
background: var(--table-header-bg);
|
| 35 |
+
padding-top: 10px;
|
| 36 |
+
padding-bottom: 10px;
|
| 37 |
+
font-weight: 600;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.content-grid main hr {
|
| 41 |
+
border: none;
|
| 42 |
+
border-bottom: 1px solid var(--border-color);
|
| 43 |
+
margin: var(--spacing-5) 0;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
/* Scroll wrapper: keeps table 100% width but enables horizontal scroll when needed */
|
| 47 |
+
.content-grid main .table-scroll {
|
| 48 |
+
width: 100%;
|
| 49 |
+
overflow-x: auto;
|
| 50 |
+
-webkit-overflow-scrolling: touch;
|
| 51 |
+
border: 1px solid var(--border-color);
|
| 52 |
+
border-radius: var(--table-border-radius);
|
| 53 |
+
background: var(--surface-bg);
|
| 54 |
+
margin: 0 0 var(--block-spacing-y);
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.content-grid main .table-scroll>table {
|
| 58 |
+
width: fit-content;
|
| 59 |
+
min-width: 100%;
|
| 60 |
+
max-width: none;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
/* Vertical dividers between columns (no outer right border) */
|
| 64 |
+
.content-grid main .table-scroll>table th,
|
| 65 |
+
.content-grid main .table-scroll>table td {
|
| 66 |
+
border-right: 1px solid var(--border-color);
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
.content-grid main .table-scroll>table th:last-child,
|
| 70 |
+
.content-grid main .table-scroll>table td:last-child {
|
| 71 |
+
border-right: none;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
.content-grid main .table-scroll>table thead th:first-child {
|
| 75 |
+
border-top-left-radius: var(--table-border-radius);
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.content-grid main .table-scroll>table thead th:last-child {
|
| 79 |
+
border-top-right-radius: var(--table-border-radius);
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
.content-grid main .table-scroll>table tbody tr:last-child td:first-child {
|
| 83 |
+
border-bottom-left-radius: var(--table-border-radius);
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.content-grid main .table-scroll>table tbody tr:last-child td:last-child {
|
| 87 |
+
border-bottom-right-radius: var(--table-border-radius);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
/* Zebra striping for odd rows */
|
| 91 |
+
.content-grid main .table-scroll>table tbody tr:nth-child(odd) td {
|
| 92 |
+
background: var(--table-row-odd-bg);
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
/* Remove bottom border on last row */
|
| 96 |
+
.content-grid main .table-scroll>table tbody tr:last-child td {
|
| 97 |
+
border-bottom: none;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
/* Accordion context: remove outer borders/radius and fit content flush */
|
| 101 |
+
.accordion .accordion__content .table-scroll {
|
| 102 |
+
border: none;
|
| 103 |
+
border-radius: 0;
|
| 104 |
+
margin: 0;
|
| 105 |
+
margin-bottom: 0 !important;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
/* Ensure no bottom margin even if table isn't wrapped (fallback) */
|
| 109 |
+
.accordion .accordion__content table {
|
| 110 |
+
margin: 0 !important;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
.accordion .accordion__content .table-scroll>table thead th:first-child,
|
| 114 |
+
.accordion .accordion__content .table-scroll>table thead th:last-child,
|
| 115 |
+
.accordion .accordion__content .table-scroll>table tbody tr:last-child td:first-child,
|
| 116 |
+
.accordion .accordion__content .table-scroll>table tbody tr:last-child td:last-child {
|
| 117 |
+
border-radius: 0;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
/* Fallback for browsers without fit-content support */
|
| 121 |
+
@supports not (width: fit-content) {
|
| 122 |
+
.content-grid main .table-scroll>table {
|
| 123 |
+
width: max-content;
|
| 124 |
min-width: 100%;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
}
|
| 126 |
+
}
|
tools/duplicated-spaces/README.md
DELETED
|
@@ -1,32 +0,0 @@
|
|
| 1 |
-
# duplicated-spaces
|
| 2 |
-
|
| 3 |
-
Small Poetry project to list public Spaces created in the last N days that were duplicated from a given source Space.
|
| 4 |
-
|
| 5 |
-
## Setup
|
| 6 |
-
|
| 7 |
-
```bash
|
| 8 |
-
cd tools/duplicated-spaces
|
| 9 |
-
poetry install --no-root
|
| 10 |
-
```
|
| 11 |
-
|
| 12 |
-
Optionally export your token:
|
| 13 |
-
|
| 14 |
-
```bash
|
| 15 |
-
export HF_TOKEN=hf_xxx
|
| 16 |
-
```
|
| 17 |
-
|
| 18 |
-
## Usage
|
| 19 |
-
|
| 20 |
-
```bash
|
| 21 |
-
poetry run find-duplicated-spaces --source owner/space-name --days 14
|
| 22 |
-
```
|
| 23 |
-
|
| 24 |
-
Options:
|
| 25 |
-
- `--source`: required. The source Space in the form `owner/space-name`.
|
| 26 |
-
- `--days`: optional. Time window in days (default: 14).
|
| 27 |
-
- `--token`: optional. Your HF token. Defaults to `HF_TOKEN` env var if set.
|
| 28 |
-
- `--no-deep`: optional. Disable README/frontmatter fallback detection.
|
| 29 |
-
|
| 30 |
-
The tool checks card metadata and may fallback to README frontmatter parsing for robustness.
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tools/duplicated-spaces/duplicated_spaces/__init__.py
DELETED
|
@@ -1,5 +0,0 @@
|
|
| 1 |
-
from .finder import find_duplicated_spaces
|
| 2 |
-
|
| 3 |
-
__all__ = ["find_duplicated_spaces"]
|
| 4 |
-
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tools/duplicated-spaces/duplicated_spaces/cli.py
DELETED
|
@@ -1,69 +0,0 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import argparse
|
| 4 |
-
import os
|
| 5 |
-
from typing import Optional
|
| 6 |
-
|
| 7 |
-
from huggingface_hub import HfApi
|
| 8 |
-
|
| 9 |
-
from .finder import find_duplicated_spaces
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
def build_parser() -> argparse.ArgumentParser:
|
| 13 |
-
parser = argparse.ArgumentParser(
|
| 14 |
-
description="List recent Spaces duplicated from a given Space"
|
| 15 |
-
)
|
| 16 |
-
parser.add_argument(
|
| 17 |
-
"--source",
|
| 18 |
-
required=True,
|
| 19 |
-
help="Source Space in the form 'owner/space-name'",
|
| 20 |
-
)
|
| 21 |
-
parser.add_argument(
|
| 22 |
-
"--days",
|
| 23 |
-
type=int,
|
| 24 |
-
default=14,
|
| 25 |
-
help="Time window in days (default: 14)",
|
| 26 |
-
)
|
| 27 |
-
parser.add_argument(
|
| 28 |
-
"--token",
|
| 29 |
-
default=os.environ.get("HF_TOKEN"),
|
| 30 |
-
help="Hugging Face token (optional). Defaults to HF_TOKEN env var if set.",
|
| 31 |
-
)
|
| 32 |
-
parser.add_argument(
|
| 33 |
-
"--no-deep",
|
| 34 |
-
action="store_true",
|
| 35 |
-
help=(
|
| 36 |
-
"Disable deep detection (README/frontmatter fetch) when card metadata is missing."
|
| 37 |
-
),
|
| 38 |
-
)
|
| 39 |
-
return parser
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
def main(argv: Optional[list[str]] = None) -> None:
|
| 43 |
-
parser = build_parser()
|
| 44 |
-
args = parser.parse_args(argv)
|
| 45 |
-
|
| 46 |
-
api = HfApi(token=args.token)
|
| 47 |
-
duplicated = find_duplicated_spaces(
|
| 48 |
-
api=api,
|
| 49 |
-
source=args.source,
|
| 50 |
-
days=args.days,
|
| 51 |
-
deep_detection=not args.no_deep,
|
| 52 |
-
)
|
| 53 |
-
|
| 54 |
-
if duplicated:
|
| 55 |
-
print(
|
| 56 |
-
f"Found {len(duplicated)} Space(s) duplicated from {args.source} in the last {args.days} days:\n"
|
| 57 |
-
)
|
| 58 |
-
for sid in duplicated:
|
| 59 |
-
print(f"https://huggingface.co/spaces/{sid}")
|
| 60 |
-
else:
|
| 61 |
-
print(
|
| 62 |
-
f"No public Spaces duplicated from {args.source} in the last {args.days} days."
|
| 63 |
-
)
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
if __name__ == "__main__":
|
| 67 |
-
main()
|
| 68 |
-
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tools/duplicated-spaces/duplicated_spaces/finder.py
DELETED
|
@@ -1,99 +0,0 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
"""
|
| 4 |
-
Core logic to find Spaces duplicated from a given source within a time window.
|
| 5 |
-
Comments are in English (per user preference for code comments).
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
from datetime import datetime, timedelta, timezone
|
| 9 |
-
from typing import Iterable, List, Optional
|
| 10 |
-
|
| 11 |
-
import requests
|
| 12 |
-
from huggingface_hub import HfApi
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
def iso_to_datetime(value: str) -> datetime:
|
| 16 |
-
"""Parse ISO 8601 timestamps returned by the Hub to aware datetime in UTC."""
|
| 17 |
-
try:
|
| 18 |
-
dt = datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%fZ")
|
| 19 |
-
except ValueError:
|
| 20 |
-
dt = datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ")
|
| 21 |
-
return dt.replace(tzinfo=timezone.utc)
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
def readme_frontmatter_duplicated_from(space_id: str) -> Optional[str]:
|
| 25 |
-
"""Fetch README raw and try to extract duplicated_from from YAML frontmatter."""
|
| 26 |
-
url = f"https://huggingface.co/spaces/{space_id}/raw/README.md"
|
| 27 |
-
try:
|
| 28 |
-
resp = requests.get(url, timeout=10)
|
| 29 |
-
if resp.status_code != 200:
|
| 30 |
-
return None
|
| 31 |
-
text = resp.text
|
| 32 |
-
except requests.RequestException:
|
| 33 |
-
return None
|
| 34 |
-
|
| 35 |
-
lines = text.splitlines()
|
| 36 |
-
in_frontmatter = False
|
| 37 |
-
for line in lines:
|
| 38 |
-
if line.strip() == "---":
|
| 39 |
-
in_frontmatter = not in_frontmatter
|
| 40 |
-
if not in_frontmatter:
|
| 41 |
-
break
|
| 42 |
-
continue
|
| 43 |
-
if in_frontmatter and line.strip().startswith("duplicated_from:"):
|
| 44 |
-
value = line.split(":", 1)[1].strip().strip("'\"")
|
| 45 |
-
return value or None
|
| 46 |
-
return None
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
def get_recent_spaces(api: HfApi, days: int) -> Iterable:
|
| 50 |
-
"""Yield Spaces created within the last `days` days, iterating newest first if possible."""
|
| 51 |
-
cutoff = datetime.now(timezone.utc) - timedelta(days=days)
|
| 52 |
-
try:
|
| 53 |
-
spaces_iter = api.list_spaces(full=True, sort="created", direction=-1)
|
| 54 |
-
except TypeError:
|
| 55 |
-
spaces_iter = api.list_spaces(full=True)
|
| 56 |
-
|
| 57 |
-
for space in spaces_iter:
|
| 58 |
-
created_at_raw = getattr(space, "created_at", None) or getattr(space, "createdAt", None)
|
| 59 |
-
if not created_at_raw:
|
| 60 |
-
yield space
|
| 61 |
-
continue
|
| 62 |
-
created_at = (
|
| 63 |
-
created_at_raw if isinstance(created_at_raw, datetime) else iso_to_datetime(str(created_at_raw))
|
| 64 |
-
)
|
| 65 |
-
if created_at >= cutoff:
|
| 66 |
-
yield space
|
| 67 |
-
else:
|
| 68 |
-
# We cannot guarantee sort order when falling back; continue to be safe.
|
| 69 |
-
continue
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
def find_duplicated_spaces(api: HfApi, source: str, days: int, deep_detection: bool) -> List[str]:
|
| 73 |
-
"""Return list of Space IDs that were duplicated from `source` within `days`."""
|
| 74 |
-
source = source.strip().strip("/ ")
|
| 75 |
-
results: List[str] = []
|
| 76 |
-
for space in get_recent_spaces(api, days=days):
|
| 77 |
-
space_id = getattr(space, "id", None) or getattr(space, "repo_id", None)
|
| 78 |
-
if not space_id:
|
| 79 |
-
continue
|
| 80 |
-
|
| 81 |
-
card = getattr(space, "cardData", None) or getattr(space, "card_data", None)
|
| 82 |
-
duplicated_from_value: Optional[str] = None
|
| 83 |
-
if isinstance(card, dict):
|
| 84 |
-
for key in ("duplicated_from", "duplicatedFrom", "duplicated-from"):
|
| 85 |
-
if key in card and isinstance(card[key], str):
|
| 86 |
-
duplicated_from_value = card[key].strip().strip("/ ")
|
| 87 |
-
break
|
| 88 |
-
|
| 89 |
-
if not duplicated_from_value and deep_detection:
|
| 90 |
-
duplicated_from_value = readme_frontmatter_duplicated_from(space_id)
|
| 91 |
-
if duplicated_from_value:
|
| 92 |
-
duplicated_from_value = duplicated_from_value.strip().strip("/ ")
|
| 93 |
-
|
| 94 |
-
if duplicated_from_value and duplicated_from_value.lower() == source.lower():
|
| 95 |
-
results.append(space_id)
|
| 96 |
-
|
| 97 |
-
return results
|
| 98 |
-
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tools/duplicated-spaces/pyproject.toml
DELETED
|
@@ -1,23 +0,0 @@
|
|
| 1 |
-
[tool.poetry]
|
| 2 |
-
name = "duplicated-spaces"
|
| 3 |
-
version = "0.1.0"
|
| 4 |
-
description = "Find recent Hugging Face Spaces duplicated from a given Space"
|
| 5 |
-
authors = ["thibaud frere <>"]
|
| 6 |
-
readme = "README.md"
|
| 7 |
-
packages = [{ include = "duplicated_spaces" }]
|
| 8 |
-
|
| 9 |
-
[tool.poetry.dependencies]
|
| 10 |
-
python = ">=3.9,<4.0"
|
| 11 |
-
huggingface_hub = "^0.24.0"
|
| 12 |
-
requests = "^2.31.0"
|
| 13 |
-
|
| 14 |
-
[tool.poetry.group.dev.dependencies]
|
| 15 |
-
|
| 16 |
-
[tool.poetry.scripts]
|
| 17 |
-
find-duplicated-spaces = "duplicated_spaces.cli:main"
|
| 18 |
-
|
| 19 |
-
[build-system]
|
| 20 |
-
requires = ["poetry-core>=1.7.0"]
|
| 21 |
-
build-backend = "poetry.core.masonry.api"
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|