Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
57 changes: 57 additions & 0 deletions .github/workflows/update-api-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: update-api-docs

# Regenerates content/api when a new @nativescript/core version is released.
#
# Triggered by a repository_dispatch sent from NativeScript/NativeScript when
# a core release tag (e.g. "9.0.21-core") is pushed — see the
# notify_docs_core_release.yml workflow in that repo. Can also be run
# manually for any published version.

on:
repository_dispatch:
types: [core-release]
workflow_dispatch:
inputs:
version:
description: '@nativescript/core version to generate docs for'
required: false
default: 'latest'

# Needed by peter-evans/create-pull-request (push branch + open PR)
permissions:
contents: write
pull-requests: write

jobs:
regenerate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.14.0
- name: Resolve target version
run: |
VERSION="${{ github.event.client_payload.version || inputs.version || 'latest' }}"
echo "CORE_VERSION=$VERSION" >> "$GITHUB_ENV"
- name: Install dependencies
run: yarn
- name: Update @nativescript/core
run: yarn add -D @nativescript/core@$CORE_VERSION
- name: Regenerate API reference
run: yarn generate:api-docs
env:
# Used for the GitHub tree lookup that validates "Defined in" source links
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Open PR if the API changed
uses: peter-evans/create-pull-request@v6
with:
commit-message: 'chore(api): regenerate API reference for @nativescript/core@${{ env.CORE_VERSION }}'
title: 'chore(api): regenerate API reference (@nativescript/core@${{ env.CORE_VERSION }})'
body: |
Automated regeneration of `content/api` for `@nativescript/core@${{ env.CORE_VERSION }}`.

Generated with `yarn generate:api-docs`.
branch: chore/update-api-docs
delete-branch: true
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ node_modules
# content/api/**/*
.vitepress/dist
.vitepress/cache
tmp/
.wrangler/

explore/

Expand Down
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# generated API reference (yarn generate:api-docs)
content/api/
content/public/api-index.json
2 changes: 2 additions & 0 deletions .textlintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# generated API reference (yarn generate:api-docs)
content/api/**
37 changes: 35 additions & 2 deletions .vitepress/config.mts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { defineConfig } from 'vitepress'
import apiSidebar from '../content/api/sidebar.json'
import llmstxt from 'vitepress-plugin-llms'
import typedocSidebar from '../content/api/typedoc-sidebar.json'
import mainSidebar from '../content/sidebar'
import uiSidebar from '../content/ui/sidebar'
import pluginsSidebar from '../content/plugins/sidebar'
Expand All @@ -12,6 +13,14 @@ import { SiteMap } from './genSitemap.mjs'
const isDev = process.env.NODE_ENV !== 'production'
const branch = process.env.CF_PAGES_BRANCH ?? 'main'

const apiSidebar = [
{
text: 'API Reference',
link: '/api/',
},
...typedocSidebar,
]

const sitemap = new SiteMap()
const baseUrl = 'https://docs.nativescript.org'
function toUrl(path: string) {
Expand Down Expand Up @@ -45,6 +54,16 @@ export default defineConfig({
ssr: {
noExternal: ['@nativescript/vitepress-theme'],
},
plugins: [
llmstxt({
domain: 'https://docs.nativescript.org',
title: 'NativeScript',
description:
'NativeScript empowers you to access native platform APIs from JavaScript directly. Develop iOS, Android and visionOS apps with TypeScript, Angular, Vue, React, Svelte or Solid.',
// also emit /index.md for the home page (default skips it)
excludeIndexPage: false,
}),
],
},
themeConfig: {
editLink: {
Expand Down Expand Up @@ -72,7 +91,21 @@ export default defineConfig({
},
markdown: {
headers: true,
theme: "github-dark"
theme: "github-dark",
config(md) {
// Inject the "Copy page" button (markdown/LLM/MCP actions) at the top
// of every page body, right below the theme-rendered title.
md.core.ruler.push('copy-page-button', (state) => {
// only inject into full page renders (not inline snippets like
// custom container titles), and only for actual doc pages
if (state.inlineMode) return
if (!state.env?.relativePath) return
if (state.env?.frontmatter?.copyPage === false) return
const token = new state.Token('html_block', '', 0)
token.content = '<CopyPageButton />\n'
state.tokens.unshift(token)
})
},
},
async transformPageData(pageData, { siteConfig }) {
// const contributors = await githubAuthors.getAuthorsForFilePath(
Expand Down
247 changes: 247 additions & 0 deletions .vitepress/theme/CopyPageButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
<script setup lang="ts">
import { computed, ref, onMounted, onBeforeUnmount } from 'vue'
import { useData } from 'vitepress'

const { page } = useData()

const SITE_ORIGIN = 'https://docs.nativescript.org'
const MCP_URL = `${SITE_ORIGIN}/mcp`

const open = ref(false)
const copied = ref(false)
const root = ref<HTMLElement | null>(null)

// mirrors the vitepress-plugin-llms output layout: "dir/index.md" twins are
// written to "/dir.md"; the root "index.md" keeps its name
const mdPath = computed(() => {
const rel = '/' + page.value.relativePath
if (rel !== '/index.md' && rel.endsWith('/index.md')) {
return rel.slice(0, -'/index.md'.length) + '.md'
}
return rel
})
const mdUrl = computed(() => SITE_ORIGIN + mdPath.value)

const chatGptUrl = computed(
() =>
'https://chatgpt.com/?hints=search&q=' +
encodeURIComponent(`Read ${mdUrl.value} so I can ask questions about it.`)
)
const claudeUrl = computed(
() =>
'https://claude.ai/new?q=' +
encodeURIComponent(`Read ${mdUrl.value} so I can ask questions about it.`)
)

const cursorDeeplink = computed(() => {
const config = btoa(JSON.stringify({ url: MCP_URL }))
return `cursor://anysphere.cursor-deeplink/mcp/install?name=nativescript&config=${config}`
})
const vscodeDeeplink = computed(() => {
const config = JSON.stringify({
name: 'nativescript',
type: 'http',
url: MCP_URL,
})
return `vscode:mcp/install?${encodeURIComponent(config)}`
})

async function copyMarkdown() {
try {
const res = await fetch(mdPath.value + location.search)
const text = res.ok ? await res.text() : mdUrl.value
await navigator.clipboard.writeText(text)
} catch {
await navigator.clipboard.writeText(mdUrl.value)
}
copied.value = true
setTimeout(() => (copied.value = false), 2000)
open.value = false
}

async function copyMcpConfig() {
const config = {
mcpServers: {
nativescript: { url: MCP_URL },
},
}
await navigator.clipboard.writeText(JSON.stringify(config, null, 2))
copied.value = true
setTimeout(() => (copied.value = false), 2000)
open.value = false
}

function onClickOutside(event: MouseEvent) {
if (root.value && !root.value.contains(event.target as Node)) {
open.value = false
}
}

onMounted(() => document.addEventListener('click', onClickOutside))
onBeforeUnmount(() => document.removeEventListener('click', onClickOutside))
</script>

<template>
<div ref="root" class="copy-page">
<div class="copy-page-group">
<button class="copy-page-main" type="button" @click="copyMarkdown">
<svg
class="copy-page-icon"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
stroke-width="1.5"
aria-hidden="true"
>
<rect x="5" y="5" width="8" height="9" rx="1.5" />
<path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-5A1.5 1.5 0 0 0 3 3.5v7A1.5 1.5 0 0 0 4.5 12H5" />
</svg>
{{ copied ? 'Copied!' : 'Copy page' }}
</button>
<button
class="copy-page-toggle"
type="button"
aria-label="More copy options"
:aria-expanded="open"
@click="open = !open"
>
<svg
class="copy-page-icon"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
stroke-width="1.5"
aria-hidden="true"
>
<path d="M4 6l4 4 4-4" />
</svg>
</button>
</div>

<div v-if="open" class="copy-page-menu">
<button type="button" class="copy-page-item" @click="copyMarkdown">
<span class="copy-page-item-title">Copy page as Markdown</span>
<span class="copy-page-item-desc">Copy this page as Markdown for LLMs</span>
</button>
<a class="copy-page-item" :href="mdPath" target="_blank" rel="noopener" @click="open = false">
<span class="copy-page-item-title">View as Markdown</span>
<span class="copy-page-item-desc">Open this page as plain Markdown</span>
</a>
<a class="copy-page-item" :href="chatGptUrl" target="_blank" rel="noopener" @click="open = false">
<span class="copy-page-item-title">Open in ChatGPT</span>
<span class="copy-page-item-desc">Ask questions about this page</span>
</a>
<a class="copy-page-item" :href="claudeUrl" target="_blank" rel="noopener" @click="open = false">
<span class="copy-page-item-title">Open in Claude</span>
<span class="copy-page-item-desc">Ask questions about this page</span>
</a>
<div class="copy-page-divider" />
<a class="copy-page-item" :href="cursorDeeplink" @click="open = false">
<span class="copy-page-item-title">Install MCP in Cursor</span>
<span class="copy-page-item-desc">Add the NativeScript docs MCP server</span>
</a>
<a class="copy-page-item" :href="vscodeDeeplink" @click="open = false">
<span class="copy-page-item-title">Install MCP in VS Code</span>
<span class="copy-page-item-desc">Add the NativeScript docs MCP server</span>
</a>
<button type="button" class="copy-page-item" @click="copyMcpConfig">
<span class="copy-page-item-title">Copy MCP config</span>
<span class="copy-page-item-desc">For Claude Code, Windsurf and others</span>
</button>
</div>
</div>
</template>

<style scoped>
.copy-page {
position: relative;
display: inline-block;
margin: 0.25rem 0 1rem;
font-size: 0.8125rem;
line-height: 1;
}

.copy-page-group {
display: inline-flex;
align-items: stretch;
border: 1px solid rgba(128, 128, 128, 0.35);
border-radius: 8px;
overflow: hidden;
}

.copy-page-main,
.copy-page-toggle {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.45rem 0.65rem;
background: transparent;
color: inherit;
cursor: pointer;
font: inherit;
font-weight: 500;
}

.copy-page-toggle {
border-left: 1px solid rgba(128, 128, 128, 0.35);
padding: 0.45rem 0.45rem;
}

.copy-page-main:hover,
.copy-page-toggle:hover,
.copy-page-item:hover {
background: rgba(128, 128, 128, 0.12);
}

.copy-page-icon {
width: 14px;
height: 14px;
flex: none;
}

.copy-page-menu {
position: absolute;
z-index: 30;
top: calc(100% + 4px);
left: 0;
min-width: 280px;
padding: 0.25rem;
border: 1px solid rgba(128, 128, 128, 0.35);
border-radius: 10px;
background: var(--copy-page-menu-bg, #fff);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}

:global(html.dark) .copy-page-menu {
--copy-page-menu-bg: #1b1b1f;
}

.copy-page-item {
display: flex;
flex-direction: column;
gap: 0.15rem;
width: 100%;
padding: 0.45rem 0.55rem;
border-radius: 6px;
background: transparent;
color: inherit;
cursor: pointer;
font: inherit;
text-align: left;
text-decoration: none;
}

.copy-page-item-title {
font-weight: 500;
}

.copy-page-item-desc {
font-size: 0.72rem;
opacity: 0.65;
}

.copy-page-divider {
margin: 0.25rem 0;
border-top: 1px solid rgba(128, 128, 128, 0.25);
}
</style>
5 changes: 4 additions & 1 deletion .vitepress/theme/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import Theme from "@nativescript/vitepress-theme";
import "@nativescript/vitepress-theme/theme/style.css";
import CopyPageButton from "./CopyPageButton.vue";

export default {
...Theme(),
...Theme((ctx) => {
ctx.app.component("CopyPageButton", CopyPageButton);
}),
};
Loading
Loading