├── .eslintignore
├── .github
├── dependabot.yml
└── workflows
│ ├── ci.yaml
│ ├── deploy.yaml
│ └── testdeploy.yaml
├── .gitignore
├── .husky
├── .gitignore
└── pre-commit
├── .nvmrc
├── .prettierignore
├── .prettierrc.yaml
├── LICENSE
├── README.md
├── deployment
├── installtutorials.py
└── requirements.txt
├── gatsby-config.js
├── gatsby-node.js
├── package-lock.json
├── package.json
├── src
├── components
│ ├── callToAction.js
│ ├── footer.js
│ ├── header.js
│ ├── instantsearch
│ │ ├── hits.js
│ │ ├── poweredBy.js
│ │ ├── refinementList.js
│ │ ├── searchBox.js
│ │ └── virtualPrioritySort.js
│ ├── layout.js
│ ├── pageCover.js
│ ├── resultCard.js
│ ├── searchLayout.js
│ └── seo.js
├── contributing
│ └── index.md
├── pages
│ ├── 404.js
│ └── index.js
├── searchClient.js
├── styles
│ ├── breakpoints.js
│ └── globalStyles.js
└── templates
│ └── contribTemplate.js
└── static
├── CNAME
├── Numfocus_stamp.png
├── astropy_favicon.ico
├── astropy_logo_notext.svg
├── dunlap-logo.png
└── learn-astropy-logo.png
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache
3 | package-lock.json
4 | public
5 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "github-actions" # See documentation for possible values
9 | directory: ".github/workflows" # Location of package manifests
10 | schedule:
11 | interval: "monthly"
12 | groups:
13 | actions:
14 | patterns:
15 | - "*"
16 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | 'on':
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
15 |
16 | - name: Read .nvmrc
17 | id: node_version
18 | run: echo ::set-output name=NODE_VERSION::$(cat .nvmrc)
19 |
20 | - name: Set up node
21 | uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
22 | with:
23 | node-version: ${{ steps.node_version.outputs.NODE_VERSION }}
24 |
25 | - name: Cache dependencies
26 | uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
27 | with:
28 | path: ~/.npm
29 | key: ${{ runner.os }}-node-${{ steps.node_version.outputs.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
30 | restore-keys: |
31 | ${{ runner.os }}-node-${{ steps.node_version.outputs.NODE_VERSION }}
32 |
33 | - run: npm ci
34 | name: Install
35 |
36 | - run: npm run lint
37 | name: ESLint
38 |
39 | - run: npm run prettier
40 | name: Prettier
41 |
42 | - run: npm run build
43 | name: Build
44 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yaml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 |
3 | 'on':
4 | push:
5 | branches:
6 | - main
7 | repository_dispatch:
8 | types:
9 | - 'tutorials-build' # triggered from astropy/astropy-tutorials
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
17 |
18 | - name: Read .nvmrc
19 | id: node_version
20 | run: echo ::set-output name=NODE_VERSION::$(cat .nvmrc)
21 |
22 | - name: Set up node
23 | uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
24 | with:
25 | node-version: ${{ steps.node_version.outputs.NODE_VERSION }}
26 |
27 | - name: Cache dependencies
28 | uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
29 | with:
30 | path: ~/.npm
31 | key: ${{ runner.os }}-node-${{ steps.node_version.outputs.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
32 | restore-keys: |
33 | ${{ runner.os }}-node-${{ steps.node_version.outputs.NODE_VERSION }}
34 |
35 | - run: npm ci
36 | name: Install
37 |
38 | - run: npm run build
39 | name: Build
40 |
41 | - name: Upload gatsby artifact
42 | uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
43 | with:
44 | name: gatsby-build
45 | path: ./public
46 |
47 | deploy:
48 | runs-on: ubuntu-latest
49 | needs: build
50 |
51 | steps:
52 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
53 |
54 | - name: Set up Python
55 | uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
56 | with:
57 | python-version: 3.12
58 |
59 | - name: Install Python dependencies
60 | run: |
61 | python -m pip install -U pip
62 | python -m pip install -r deployment/requirements.txt
63 |
64 | - name: Download gatsby artifact
65 | uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
66 | with:
67 | name: gatsby-build
68 | path: ./public
69 |
70 | - name: Download tutorials for tutorial dispatch event
71 | if: ${{ github.event == 'repository_dispatch' }}
72 | env:
73 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
74 | run: |
75 | python deployment/installtutorials.py \
76 | --dest public/tutorials \
77 | --tutorials-run ${{ github.event.client_payload.runid }} \
78 | --tutorials-artifact ${{ github.event.client_payload.artifactName }} \
79 | --tutorials-repo ${{ github.event.client_payload.repo }}
80 |
81 | - name: Download latest tutorials
82 | if: ${{ github.event != 'repository_dispatch' }}
83 | env:
84 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
85 | run: |
86 | python deployment/installtutorials.py --dest public/tutorials
87 |
88 | - name: Deploy to gh-pages
89 | uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
90 | with:
91 | github_token: ${{ secrets.GITHUB_TOKEN }}
92 | publish_dir: ./public
93 |
94 | - name: Index tutorials
95 | env:
96 | ALGOLIA_ID: ${{ secrets.ALGOLIA_ID }}
97 | ALGOLIA_KEY: ${{ secrets.ALGOLIA_KEY }}
98 | ALGOLIA_INDEX: ${{ secrets.ALGOLIA_INDEX }}
99 | run: |
100 | astropylibrarian index tutorial-site \
101 | public/tutorials \
102 | https://learn.astropy.org/tutorials
103 |
--------------------------------------------------------------------------------
/.github/workflows/testdeploy.yaml:
--------------------------------------------------------------------------------
1 | name: Test deployment scripts
2 |
3 | 'on':
4 | pull_request:
5 |
6 | jobs:
7 | deploy:
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
12 |
13 | - name: Set up Python
14 | uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
15 | with:
16 | python-version: 3.12
17 |
18 | - name: Install Python dependencies
19 | run: |
20 | python -m pip install -U pip
21 | python -m pip install -r deployment/requirements.txt
22 |
23 | - name: Check Astropy Librarian installation
24 | run: |
25 | astropylibrarian --help
26 |
27 | - name: Download latest tutorials
28 | env:
29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30 | run: |
31 | python deployment/installtutorials.py --dest public/tutorials
32 |
33 | - name: List tutorials
34 | run: |
35 | tree public
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .cache/
3 | public
4 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx --no-install lint-staged && npx --no-install pretty-quick --staged
5 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 14.16.0
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache
3 | .github
4 | package.json
5 | package-lock.json
6 | public
7 |
--------------------------------------------------------------------------------
/.prettierrc.yaml:
--------------------------------------------------------------------------------
1 | trailingComma: es5
2 | singleQuote: true
3 | printWidth: 80
4 | endOfLine: auto
5 | proseWrap: never
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2020, Astropy Developers
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Astropy Learn
2 |
3 | This repository hosts the homepage of the Astropy Learn project, https://learn.astropy.org, and serves the tutorial content from the [astropy-learn](https://github.com/astropy-learn) organization. The site itself is built with [Gatsby](https://www.gatsbyjs.com/) and the [Algolia](https://www.algolia.com) search service. Records for the Algolia database are curated and formatted by the [learn-astropy-librarian](https://github.com/astropy-learn/learn-astropy-librarian) app.
4 |
5 | ## Developer guide
6 |
7 | ### Initial set up
8 |
9 | Create a fork on https://github.com/astropy-learn/learn.
10 |
11 | ```bash
12 | npm install
13 | ```
14 |
15 | ### Run a development server
16 |
17 | You can run a development server that will serve the site and reload as you develop the app:
18 |
19 | ```bash
20 | npm run develop
21 | ```
22 |
23 | By default the app is hosted at http://localhost:8000. You can also interact with the GraphQL data layer by browsing
24 |
25 | ### Build for production
26 |
27 | ```bash
28 | npm run build
29 | ```
30 |
31 | Preview the built site by running:
32 |
33 | ```bash
34 | npm run serve
35 | ```
36 |
37 | ### Linting and autoformatting
38 |
39 | This app uses ESLint to lint JavaScript, which in turn runs Prettier to format JavaScript. The configuration is based on [wesbos/eslint-config-wesbos](https://github.com/wesbos/eslint-config-wesbos).
40 |
41 | A Git pre-commit hooks runs both ESLint and Prettier and automatically lints and reformats code before every commit. These hooks are run by [husky](https://typicode.github.io/husky/#/) and should already be installed when you ran `npm install`.
42 |
43 | To manually lint the code base:
44 |
45 | ```bash
46 | npm run lint
47 | ```
48 |
49 | To also fix issues and format the code base:
50 |
51 | ```bash
52 | npm run lint:fix
53 | ```
54 |
55 | Ideally your editor will also apply eslint/prettier on save, though these commands are handy as a fallback.
56 |
57 | ### About the node version
58 |
59 | This project is intended to be built with a Node.js version that's encoded in the [`.nvmrc`](./.nvmrc) file. To adopt this Node version, we recommend installing and using the [node version manager](https://github.com/nvm-sh/nvm).
60 |
61 | Then you can use the preferred node version by running `nvm` from the project root:
62 |
63 | ```sh
64 | nvm use
65 | ```
66 |
67 | ### Additional resources for developers
68 |
69 | Learn more about Gatsby:
70 |
71 | - [Documentation](https://www.gatsbyjs.com/docs/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
72 | - [Tutorials](https://www.gatsbyjs.com/tutorial/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
73 | - [Guides](https://www.gatsbyjs.com/tutorial/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
74 | - [API Reference](https://www.gatsbyjs.com/docs/api-reference/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
75 | - [Plugin Library](https://www.gatsbyjs.com/plugins?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
76 | - [Cheat Sheet](https://www.gatsbyjs.com/docs/cheat-sheet/?utm_source=starter&utm_medium=readme&utm_campaign=minimal-starter)
77 |
78 | Learn more about Algolia:
79 |
80 | - [Documentation](https://www.algolia.com/doc/)
81 | - [React instantsearch](https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/react/)
82 |
--------------------------------------------------------------------------------
/deployment/installtutorials.py:
--------------------------------------------------------------------------------
1 | """Install the built tutorials HTML from astropy/astropy-tutorials into the
2 | built Gatsby site.
3 | """
4 |
5 | from __future__ import annotations
6 |
7 | import argparse
8 | import os
9 | from io import BytesIO
10 | from pathlib import Path
11 | from typing import Any, Dict, Optional
12 | from zipfile import ZipFile
13 |
14 | import requests
15 | from uritemplate import expand
16 |
17 |
18 | def parse_args() -> argparse.Namespace:
19 | parser = argparse.ArgumentParser(
20 | description=(
21 | 'Install the tutorials HTML artifact from '
22 | 'astropy/astropy-tutorials into the build Gatsby site.\n\n'
23 | 'There are two usage modes:\n\n'
24 | '1. If --tutorials-run is set, get the tutorials from the '
25 | 'corresponding workflow artifact.\n'
26 | '2. If --tutorials-run is not set, the workflow artifact from '
27 | 'the most recent merge to the main branch is used.\n\n'
28 | ),
29 | formatter_class=argparse.RawDescriptionHelpFormatter
30 | )
31 | parser.add_argument(
32 | '--dest',
33 | required=True,
34 | help="Directory where the tutorials are installed. This should be "
35 | "inside the Gatsby 'public' directory."
36 | )
37 | parser.add_argument(
38 | '--tutorials-repo',
39 | default="astropy/astropy-tutorials",
40 | help='Tutorials repo slug (should be astropy/astropy-tutorials).'
41 | )
42 | parser.add_argument(
43 | '--tutorials-artifact',
44 | default="rendered-tutorials",
45 | help='Name of the artifact from the tutorials repo.'
46 | )
47 | parser.add_argument(
48 | '--tutorials-run',
49 | help='ID of the workflow run from the tutorials repo.'
50 | )
51 | return parser.parse_args()
52 |
53 |
54 | def main() -> None:
55 | """The main script entrypoint."""
56 | args = parse_args()
57 |
58 | github_token = os.getenv("GITHUB_TOKEN")
59 | if github_token is None:
60 | raise RuntimeError("Set the GITHUB_TOKEN environment variable")
61 |
62 | if args.tutorials_run is not None:
63 | artifact = TutorialsArtifact(
64 | repo=args.tutorials_repo,
65 | name=args.tutorials_artifact,
66 | run_id=args.tutorials_run,
67 | github_token=github_token
68 | )
69 | else:
70 | artifact = TutorialsArtifact.from_latest(
71 | repo=args.tutorials_repo,
72 | name=args.tutorials_artifact,
73 | github_token=github_token
74 | )
75 |
76 | artifact.install(args.dest)
77 |
78 |
79 | class TutorialsArtifact:
80 | """A tutorials build artifact that can be downloaded and installed into
81 | the site's build directory.
82 |
83 | Parameters
84 | ----------
85 | repo : str
86 | The repository slug (astropy/astropy-tutorials).
87 | name : str
88 | The name of the workflow artifact with rendered tutorials.
89 | run_id : str
90 | The workflow run ID corresponding to the workflow artifact.
91 | github_token : str
92 | A GitHub token.
93 | """
94 |
95 | def __init__(
96 | self,
97 | *,
98 | repo: str,
99 | name: str,
100 | run_id: str,
101 | github_token: str
102 | ) -> None:
103 | self.repo = repo
104 | self.name = name
105 | self.run_id = run_id
106 | self.github_token = github_token
107 | self._zip_path: Optional[Path] = None
108 |
109 | @classmethod
110 | def from_latest(
111 | cls,
112 | *,
113 | repo: str,
114 | name: str,
115 | github_token: str
116 | ) -> TutorialsArtifact:
117 | """Get the artifact from the latest run on the main branch.
118 |
119 | Parameters
120 | ----------
121 | repo : str
122 | The repository slug (astropy/astropy-tutorials).
123 | name : str
124 | The name of the workflow artifact with rendered tutorials.
125 | run_id : str
126 | The workflow run ID corresponding to the workflow artifact.
127 | github_token : str
128 | A GitHub token.
129 | """
130 | owner, repo_name = repo.split('/')
131 | url = expand(
132 | "https://api.github.com/repos/{owner}/{repo}/actions/runs",
133 | owner=owner,
134 | repo=repo_name
135 | )
136 | headers = cls._make_github_headers(github_token)
137 | response = requests.get(
138 | url,
139 | headers=headers,
140 | params={"branch": "main", "event": "push", "status": "success"}
141 | )
142 | response.raise_for_status()
143 | runs_data = response.json()
144 | first_run = runs_data["workflow_runs"][0]
145 | run_id = first_run["id"]
146 | return cls(
147 | repo=repo,
148 | name=name,
149 | run_id=run_id,
150 | github_token=github_token
151 | )
152 |
153 | @staticmethod
154 | def _make_github_headers(github_token) -> Dict[str, str]:
155 | return {
156 | "Accept": "application/vnd.github.v3+json",
157 | "Authorization": f"Bearer {github_token}"
158 | }
159 |
160 | def _download_artifact(self) -> bytes:
161 | # Note that if there's a large number of artifacts (>30), we need
162 | # to iterate over all pages of the request. Generally this shouldn't
163 | # be necessary.
164 | artifacts_data = self._get_workflow_run_artifacts()
165 | for artifact in artifacts_data["artifacts"]:
166 | if artifact["name"] == self.name:
167 | download_url = artifact["archive_download_url"]
168 | response = requests.get(
169 | download_url,
170 | headers={"Authorization": f"Bearer {self.github_token}"}
171 | )
172 | return response.content
173 | raise RuntimeError("Did not find artifact for download")
174 |
175 | def _get_workflow_run_artifacts(self, page=1) -> Dict[str, Any]:
176 | owner, repo = self.repo.split('/')
177 | uri = expand(
178 | "https://api.github.com/repos/{owner}/{repo}/actions/runs/"
179 | "{run_id}/artifacts",
180 | owner=owner,
181 | repo=repo,
182 | run_id=self.run_id,
183 | )
184 | headers = TutorialsArtifact._make_github_headers(self.github_token)
185 | response = requests.get(
186 | uri,
187 | params={"page": str(page)},
188 | headers=headers
189 | )
190 | response.raise_for_status()
191 | return response.json()
192 |
193 | def install(self, destination_directory: str) -> None:
194 | """Download and install the artifact contents into the desination
195 | directory, which is created if it does not already exist.
196 | """
197 | artifact_archive = self._download_artifact()
198 | bytes_stream = BytesIO(artifact_archive)
199 | zip_file = ZipFile(bytes_stream)
200 | zip_file.extractall(path=Path(destination_directory))
201 |
202 |
203 | if __name__ == '__main__':
204 | main()
205 |
--------------------------------------------------------------------------------
/deployment/requirements.txt:
--------------------------------------------------------------------------------
1 | requests
2 | uritemplate
3 | git+https://github.com/astropy-learn/learn-astropy-librarian.git#egg=astropy-librarian
4 |
--------------------------------------------------------------------------------
/gatsby-config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | siteMetadata: {
3 | title: 'Learn Astropy',
4 | description:
5 | 'Astropy is a Python library for use in astronomy. Learn Astropy provides a portal to all of the Astropy educational material.',
6 | author: 'Astropy Project',
7 | twitter: '@astropy',
8 | siteUrl: 'https://learn.astropy.org',
9 | },
10 | plugins: [
11 | 'gatsby-plugin-styled-components',
12 | 'gatsby-plugin-react-helmet',
13 | {
14 | resolve: `gatsby-source-filesystem`,
15 | options: {
16 | name: `markdown-docs`,
17 | path: `${__dirname}/src/contributing`,
18 | },
19 | },
20 | `gatsby-transformer-remark`,
21 | ],
22 | };
23 |
--------------------------------------------------------------------------------
/gatsby-node.js:
--------------------------------------------------------------------------------
1 | exports.createPages = async ({ actions, graphql, reporter }) => {
2 | const { createPage } = actions;
3 | const contribTemplate = require.resolve(`./src/templates/contribTemplate.js`);
4 | const result = await graphql(`
5 | {
6 | allMarkdownRemark {
7 | edges {
8 | node {
9 | frontmatter {
10 | slug
11 | }
12 | }
13 | }
14 | }
15 | }
16 | `);
17 | // Handle errors
18 | if (result.errors) {
19 | reporter.panicOnBuild(`Error while running GraphQL query.`);
20 | return;
21 | }
22 | result.data.allMarkdownRemark.edges.forEach(({ node }) => {
23 | createPage({
24 | path: node.frontmatter.slug,
25 | component: contribTemplate,
26 | context: {
27 | // additional data can be passed via context
28 | slug: node.frontmatter.slug,
29 | },
30 | });
31 | });
32 | };
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "learn-astropy",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "Learn Astropy",
6 | "author": "Jonathan Sick",
7 | "keywords": [
8 | "gatsby"
9 | ],
10 | "scripts": {
11 | "develop": "gatsby develop",
12 | "start": "gatsby develop",
13 | "build": "gatsby build",
14 | "serve": "gatsby serve",
15 | "clean": "gatsby clean",
16 | "lint": "eslint .",
17 | "lint:fix": "eslint . --fix",
18 | "prettier": "npx prettier . --check",
19 | "prettier:fix": "npm run prettier -- --write",
20 | "prepare": "husky install"
21 | },
22 | "eslintConfig": {
23 | "extends": [
24 | "airbnb",
25 | "prettier"
26 | ],
27 | "parser": "babel-eslint",
28 | "plugins": [
29 | "html",
30 | "react-hooks"
31 | ],
32 | "rules": {
33 | "no-debugger": 0,
34 | "no-alert": 0,
35 | "no-await-in-loop": 0,
36 | "no-return-assign": [
37 | "error",
38 | "except-parens"
39 | ],
40 | "no-restricted-syntax": [
41 | 2,
42 | "ForInStatement",
43 | "LabeledStatement",
44 | "WithStatement"
45 | ],
46 | "no-unused-vars": [
47 | 1,
48 | {
49 | "ignoreRestSiblings": true,
50 | "argsIgnorePattern": "res|next|^err"
51 | }
52 | ],
53 | "prefer-const": [
54 | "error",
55 | {
56 | "destructuring": "all"
57 | }
58 | ],
59 | "arrow-body-style": [
60 | 2,
61 | "as-needed"
62 | ],
63 | "no-unused-expressions": [
64 | 2,
65 | {
66 | "allowTaggedTemplates": true
67 | }
68 | ],
69 | "no-param-reassign": [
70 | 2,
71 | {
72 | "props": false
73 | }
74 | ],
75 | "no-console": 0,
76 | "import/prefer-default-export": 0,
77 | "import": 0,
78 | "func-names": 0,
79 | "space-before-function-paren": 0,
80 | "comma-dangle": 0,
81 | "max-len": 0,
82 | "import/extensions": 0,
83 | "no-underscore-dangle": 0,
84 | "consistent-return": 0,
85 | "react/display-name": 1,
86 | "react/no-array-index-key": 0,
87 | "react/react-in-jsx-scope": 0,
88 | "react/prefer-stateless-function": 0,
89 | "react/forbid-prop-types": 0,
90 | "react/no-unescaped-entities": 0,
91 | "jsx-a11y/accessible-emoji": 0,
92 | "jsx-a11y/label-has-associated-control": [
93 | "error",
94 | {
95 | "assert": "either"
96 | }
97 | ],
98 | "react/require-default-props": 0,
99 | "react/jsx-filename-extension": [
100 | 1,
101 | {
102 | "extensions": [
103 | ".js",
104 | ".jsx"
105 | ]
106 | }
107 | ],
108 | "radix": 0,
109 | "no-shadow": [
110 | 2,
111 | {
112 | "hoist": "all",
113 | "allow": [
114 | "resolve",
115 | "reject",
116 | "done",
117 | "next",
118 | "err",
119 | "error"
120 | ]
121 | }
122 | ],
123 | "quotes": [
124 | 2,
125 | "single",
126 | {
127 | "avoidEscape": true,
128 | "allowTemplateLiterals": true
129 | }
130 | ],
131 | "jsx-a11y/href-no-hash": "off",
132 | "jsx-a11y/anchor-is-valid": [
133 | "warn",
134 | {
135 | "aspects": [
136 | "invalidHref"
137 | ]
138 | }
139 | ],
140 | "react-hooks/rules-of-hooks": "error",
141 | "react-hooks/exhaustive-deps": "warn"
142 | }
143 | },
144 | "lint-staged": {
145 | "*.js": "eslint"
146 | },
147 | "dependencies": {
148 | "@fontsource/source-sans-pro": "^4.5.0",
149 | "algoliasearch": "^4.10.3",
150 | "babel-plugin-styled-components": "^1.13.2",
151 | "gatsby": "^3.15.0",
152 | "gatsby-image": "^3.10.0",
153 | "gatsby-plugin-react-helmet": "^4.10.0",
154 | "gatsby-plugin-styled-components": "^4.12.0",
155 | "gatsby-source-filesystem": "^3.10.0",
156 | "gatsby-transformer-remark": "^4.7.0",
157 | "instantsearch.css": "^7.4.5",
158 | "polished": "^4.1.3",
159 | "prop-types": "^15.7.2",
160 | "react": "^17.0.2",
161 | "react-dom": "^17.0.2",
162 | "react-helmet": "^6.1.0",
163 | "react-instantsearch-dom": "^6.12.1",
164 | "styled-components": "^5.3.1"
165 | },
166 | "devDependencies": {
167 | "babel-eslint": "^10.1.0",
168 | "eslint": "^7.32.0",
169 | "eslint-config-airbnb": "^18.2.1",
170 | "eslint-config-prettier": "^8.3.0",
171 | "eslint-plugin-html": "^6.1.2",
172 | "eslint-plugin-import": "^2.23.4",
173 | "eslint-plugin-jsx-a11y": "^6.4.1",
174 | "eslint-plugin-react": "^7.24.0",
175 | "eslint-plugin-react-hooks": "^4.2.0",
176 | "husky": "^7.0.1",
177 | "lint-staged": "^11.1.1",
178 | "prettier": "^2.3.2",
179 | "pretty-quick": "^3.1.1"
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/src/components/callToAction.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { Link } from 'gatsby';
5 |
6 | const Button = styled.div`
7 | background-color: #fa743b;
8 | border-color: #fa743b;
9 | cursor: pointer;
10 | text-decoration: none;
11 | color: #ffffff;
12 | text-align: center;
13 | vertical-align: middle;
14 | padding: 0.375rem 0.75rem;
15 | font-size: 0.875rem;
16 | border-radius: 0.25rem;
17 | display: inline-block;
18 | `;
19 |
20 | /*
21 | * A call-to-action button that is a link to an internal page (using the
22 | * Gatsby Link API).
23 | */
24 | export default function CallToActionLink({ children, to }) {
25 | return (
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | CallToActionLink.propTypes = {
33 | children: PropTypes.node,
34 | to: PropTypes.string.isRequired,
35 | };
36 |
--------------------------------------------------------------------------------
/src/components/footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'gatsby';
3 | import styled from 'styled-components';
4 |
5 | import numfocusStamp from '../../static/Numfocus_stamp.png';
6 | import dunlapLogo from '../../static/dunlap-logo.png';
7 |
8 | const FullWidthContainer = styled.div`
9 | width: 100vw;
10 | position: relative;
11 | left: 50%;
12 | right: 50%;
13 | margin: var(--astropy-size-xl) -50vw 0;
14 | background-color: rgb(250, 250, 250);
15 | `;
16 |
17 | const StyledFooter = styled.footer`
18 | margin: 0 auto;
19 | max-width: var(--astropy-content-width);
20 | padding: var(--astropy-size-m) var(--astropy-size-s);
21 |
22 | h2 {
23 | font-size: var(--astropy-font-size-ml);
24 | font-weight: 400;
25 | }
26 |
27 | nav ul {
28 | list-style: none;
29 | padding-left: 0;
30 | }
31 |
32 | nav a {
33 | font-weight: 500;
34 | }
35 |
36 | nav ul li:first-child a {
37 | font-weight: 700;
38 | }
39 |
40 | .footer-content-layer {
41 | display: flex;
42 | flex-flow: row nowrap;
43 | justify-content: space-between;
44 | align-items: flex-start;
45 | margin: var(--astropy-size-m) 0;
46 | }
47 |
48 | .sponsors,
49 | .code-of-conduct {
50 | width: 24rem;
51 | }
52 |
53 | .code-of-conduct p {
54 | margin-bottom: 0;
55 | }
56 |
57 | .numfocusStamp {
58 | margin-top: var(--astropy-size-l);
59 | }
60 |
61 | .sponsors .numfocusStamp__image {
62 | width: 16rem;
63 | }
64 |
65 | .sponsors .dunlapLogo__image {
66 | margin-top: var(--astropy-size-m);
67 | width: 20rem;
68 | }
69 |
70 | .copyright {
71 | margin-top: var(--astropy-size-xl);
72 | }
73 | `;
74 |
75 | /*
76 | * Footer component (contained within a Layout component).
77 | */
78 | export default function Footer() {
79 | return (
80 |
81 |
82 |
92 |
93 |
94 |
Code of Conduct
95 |
96 | The Astropy project is committed to fostering an inclusive
97 | community. The community of participants in open source Astronomy
98 | projects is made up of members from around the globe with a
99 | diverse set of skills, personalities, and experiences. It is
100 | through these differences that our community experiences success
101 | and continued growth.{' '}
102 |
103 | Learn more.
104 |
105 |
157 |
158 | );
159 | };
160 |
161 | ResultCard.propTypes = {
162 | hit: PropTypes.object.isRequired,
163 | };
164 |
165 | export default ResultCard;
166 |
--------------------------------------------------------------------------------
/src/components/searchLayout.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import bp from '../styles/breakpoints';
4 |
5 | export const SearchLayout = styled.div`
6 | grid-template-columns: 16rem 1fr;
7 | grid-template-rows: auto 1fr;
8 | grid-column-gap: 2rem;
9 | grid-row-gap: 2rem;
10 | margin-top: 4rem;
11 |
12 | /*
13 | * Use grid layout on bigger screens.
14 | */
15 | @media only screen and (min-width: ${bp.phone}) {
16 | display: grid;
17 | }
18 |
19 | .search-box-area {
20 | grid-column: 2 / 3;
21 | grid-row: 1 / 2;
22 |
23 | @media screen and (max-width: 750px) {
24 | grid-column: 1 / 3;
25 | }
26 | }
27 |
28 | .search-refinements-area {
29 | grid-column: 1 / 2;
30 | grid-row: 1 / 3;
31 |
32 | margin-top: 1rem;
33 | @media only screen and (min-width: ${bp.phone}) {
34 | margin-top: 0;
35 | }
36 | @media screen and (max-width: 750px) {
37 | display: none;
38 | }
39 | }
40 |
41 | .search-results-area {
42 | grid-column: 2 / 3;
43 | grid-row: 2 / 3;
44 |
45 | @media screen and (max-width: 750px) {
46 | grid-column: 1 / 3;
47 | }
48 | }
49 | `;
50 |
51 | /* Styled component div around a refinement widget.
52 | *
53 | * This styling controls spacing and the heading styling
54 | */
55 | export const SearchRefinementsSection = styled.div`
56 | margin-bottom: var(--astropy-size-l);
57 |
58 | h2 {
59 | margin-top: 0;
60 | font-size: var(--astropy-font-size-ml);
61 | }
62 | `;
63 |
--------------------------------------------------------------------------------
/src/components/seo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Helmet from 'react-helmet';
4 | import { useStaticQuery, graphql } from 'gatsby';
5 |
6 | /*
7 | * SEO component that adds tags to the page's header using react-helmet.
8 | */
9 | export default function SEO({ children, location, title, description, image }) {
10 | const { site } = useStaticQuery(
11 | graphql`
12 | query {
13 | site {
14 | siteMetadata {
15 | title
16 | description
17 | author
18 | siteUrl
19 | twitter
20 | }
21 | }
22 | }
23 | `
24 | );
25 |
26 | // The description can be overidden for individual pages via description
27 | // prop.
28 | const desc = description || site.siteMetadata.description;
29 |
30 | // The page's canonical URL
31 | const canonicalUrl = site.siteMetadata.siteUrl + location.pathname;
32 |
33 | return (
34 |
35 |
36 | {title}
37 | {/* Favicon */}
38 |
39 |
40 | {/* General meta tags */}
41 |
42 |
43 |
44 |
45 | {/* Open Graph */}
46 |
47 |
52 |
53 |
58 |
59 | {/* Twitter card */}
60 |
65 | {children}
66 |
67 | );
68 | }
69 |
70 | SEO.propTypes = {
71 | children: PropTypes.node,
72 | title: PropTypes.string.isRequired,
73 | description: PropTypes.string,
74 | location: PropTypes.shape({
75 | pathname: PropTypes.string.isRequired,
76 | }),
77 | image: PropTypes.string,
78 | };
79 |
--------------------------------------------------------------------------------
/src/contributing/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Contributing to Learn Astropy'
3 | slug: '/contributing/'
4 | ---
5 |
6 | We are always interested in incorporating new tutorials into Learn Astropy and the Astropy Tutorials series. We welcome tutorials covering astro-relevant topics; they do not need to use the Astropy package in order to be hosted or indexed here. If you have astronomy tutorials that you would like to contribute, or if you have a separate tutorial series that you would like indexed by the Learn Astropy website, see below.
7 |
8 | ## Overview
9 |
10 | Each tutorial is a [Jupyter notebook](https://jupyter.org/) file in a unique repository `tutorial--*` in the [astropy-learn organization](https://github.com/astropy-learn). For example, let's look at the [FITS-header](https://github.com/astropy-learn/tutorial--FITS-header/tree/main/) tutorial. The repository has a few files that authors should always write/amend when contributing a new tutorial or set of tutorials:
11 |
12 | - A single Jupyter notebook file that contains the text and code for the tutorial (it should follow the content guidelines below),
13 | - Any small data files used in the tutorial (in this case, a single FITS file),
14 | - A `requirements.txt` file that specifies the required packages to run the notebook, and
15 | - An `AUTHORS.md` file that lists the notebook authors.
16 | - If you are contributing multiple, thematically-linked notebooks, the `index.md` file should summarize the contents of the individual notebooks (it will be the first page in the 'jupyter book' containing all individual notebooks). Additionally each `.ipynb` filename should be preceded by a number and an underscore, in the order the notebooks should appear in the book, e.g.:
17 | ```
18 | 1_intro-to-modeling
19 | 2_applying-model-to-data
20 | ```
21 |
22 | The notebook file(s) are automatically run and converted into a static HTML page ([for example](https://learn.astropy.org/tutorials/FITS-header.html)), which is then displayed in the listing on the main tutorials webpage, http://tutorials.astropy.org.
23 |
24 | ## Content Guidelines
25 |
26 | ### Overview
27 |
28 | - Each tutorial should have 3–5 explicit [Learning Goals](http://tll.mit.edu/help/intended-learning-outcomes), demonstrate ~2–3 pieces of functionality relevant to astronomy, and contain 2–3 demonstrations of generic but commonly used functionality (e.g., `numpy`, `matplotlib`).
29 | - Each tutorial should roughly follow this progression:
30 | - _Input/Output_: read in some data (use [astroquery](https://astroquery.readthedocs.io/en/latest/) where possible to query real astronomical datasets)
31 | - _Analysis_: do something insightful / useful with the data
32 | - _Visualization_: make a pretty figure (use [astropy.visualization](http://docs.astropy.org/en/stable/visualization/) where possible)
33 | - The tutorials must be compatible with the versions supported by the last major release of the Astropy core package (i.e. Python >= 3.5)
34 |
35 | ### Template intro
36 |
37 | The first cell in every tutorial notebook is a markdown cell used for the title, author list, keywords, and summary. All of this information should be contained in a single cell and should adhere to the following format:
38 |
39 | ```
40 | # Title name
41 |
42 | ## Authors
43 | Jane Smith (@GITHUB-ID, ORCID-ID), Jose Jones (@GITHUB-ID, ORCID-ID)
44 |
45 | ## Learning Goals
46 | * Query the ... dataset
47 | * Calculate ...
48 | * Display ...
49 |
50 | ## Keywords (please draw from [this list](https://github.com/astropy-learn/astropy-tutorials/blob/main/resources/keywords.md))
51 | Example, example, example
52 |
53 | ## Companion Content
54 | Carroll & Ostlie 10.3, Binney & Tremaine 1.5
55 |
56 | ## Summary
57 | In this tutorial, we will download a data file, do something to it, and then
58 | visualize it.
59 | ```
60 |
61 | ### Code
62 |
63 | - Demonstrate good commenting practice
64 | - Add comments to sections of code that use concepts not included in the Learning Goals
65 | - Demonstrate best practices of variable names
66 | - Variables should be all lower case with words separated by underscores
67 | - Variable names should be descriptive, e.g., `galaxy_mass`, `u_mag`
68 | - Use the print function explicitly to display information about variables
69 | - As much as possible, comply with [PEP8](https://www.python.org/dev/peps/pep-0008/).
70 | - As much as possible, comply with Jupyter notebook style guides - [STScI style guide](https://github.com/spacetelescope/style-guides/blob/master/guides/jupyter-notebooks.md) and [Official Coding Style](https://jupyter.readthedocs.io/en/latest/development_guide/coding_style.html).
71 | - Imports
72 | - Do not use `from package import *`; import packages, classes, and functions explicitly
73 | - Follow recommended package name abbreviations:
74 | - `import numpy as np`
75 | - `import matplotlib as mpl`
76 | - `import matplotlib.pyplot as plt`
77 | - `import astropy.units as u`
78 | - `import astropy.coordinates as coord`
79 | - `from astropy.io import fits`
80 | - Display figures inline using matplotlib's inline backend:
81 | - `%matplotlib inline # make plots display in notebooks`
82 |
83 | ### Narrative
84 |
85 | - Please read through the other tutorials to get a sense of the desired tone and length.
86 | - Use [Markdown formatting](http://jupyter-notebook.readthedocs.io/en/latest/examples/Notebook/Working%20With%20Markdown%20Cells.html) in text cells for formatting, links, latex, and code snippets.
87 | - Titles should be short yet descriptive and emphasize the learning goals of the tutorial. Try to make the title appeal to a broad audience and avoid referencing a specific instrument, catalog, or anything wavelength dependent.
88 | - List all authors' full names (comma separated) and link to GitHub profiles and/or [ORCID iD](https://orcid.org/) when relevant.
89 | - Include [Learning Goals](http://tll.mit.edu/help/intended-learning-outcomes) at the top as a bulleted list.
90 | - Include Keywords as a comma separated list of topics, packages, and functions demonstrated.
91 | - The first paragraph should give a brief overview of the entire tutorial including relevant astronomy concepts.
92 | - Use the first-person inclusive plural ("we"). For example, "We are going to make a plot which...", or "Above, we did it the hard way, but here is the easier way..."
93 | - Section headings should be in the imperative mood. For example, "Download the data."
94 | - Avoid extraneous words such as "obviously", "just", "simply", or "easily." For example, avoid phrases like "we just have to do this one thing."
95 | - Use `
Note
` for Notes and `
Warning
` for Warnings (Markdown supports raw HTML)
96 |
97 | ## Procedure for contributing a notebook or set of notebooks
98 |
99 | There are two methods for submitting a tutorial or set of thematically linked tutorials.
100 |
101 | ### Method 1: Provide a link
102 |
103 | - [Open an issue on the astropy-tutorials Github repo](https://github.com/astropy/astropy-tutorials/issues) with a link to your Jupyter notebook(s).
104 |
105 | Learn Astropy maintainers will download your notebook, test it, and edit the file if necessary to conform to the above style guide. When the tutorial is ready to be incorporated, maintainers will open a pull request on behalf of the tutorial authors.
106 |
107 | ### Method 2: Submit a pull request
108 |
109 | This process for contributing a tutorial involves the [GitHub fork](https://help.github.com/articles/working-with-forks/) and `git` workflow concepts [branch, push, pull request](https://help.github.com/articles/proposing-changes-to-your-work-with-pull-requests/).
110 |
111 | To contribute a new tutorial, first fork the [Astropy Learn tutorial template repository](https://github.com/astropy-learn/tutorial--template). Then clone your fork locally to your machine (replace `` with your GitHub username):
112 |
113 | ```
114 | git clone git@github.com:/astropy-tutorials.git
115 | ```
116 |
117 | Next, create a branch in your local repository with the name of the tutorial you'd like to contribute. Say we're adding a tutorial to demonstrate spectral line fitting -- we might call it "Spectral-Line-Fitting":
118 |
119 | ```
120 | git checkout -b Spectral-Line-Fitting
121 | ```
122 |
123 | Include the notebook `.ipynb` file(s) and any data files used by the notebook (see the 'Data files' section below). Update the `AUTHORS.md` file. Update the `requirements.txt` file with the Python packages the tutorial depends on and files. For example, if your tutorial requires `scipy` version 1.0 and `numpy` version 1.13 or greater, your `requirements.txt` file would look like:
124 |
125 | ```
126 | scipy==1.0
127 | numpy>=1.13
128 | ```
129 |
130 | Push the notebook and other files from your local branch up to your fork of the repository on GitHub (by default, named 'origin'):
131 |
132 | ```
133 | git push origin Spectral-Line-Fitting
134 | ```
135 |
136 | When the tutorial is ready for submission, [open a pull request](https://help.github.com/articles/creating-a-pull-request/) against the main [`tutorial--template` repository](https://github.com/astropy-learn/tutorial--template), and your submission will be reviewed.
137 |
138 | ## Data files
139 |
140 | If your tutorial includes large data files (where large means >~ 1 MB), including them in the tutorial's repository would drastically slow down cloning of the repository. Instead, for files < 10 MB, we encourage use of the `astropy.utils.download_files` function, and we will host data files on the http://data.astropy.org server (or you can do this directly by opening a PR at the https://github.com/astropy/astropy-data repository). Alternatively, if the file size is > 10 MB, the data should be hosted on Zenodo. To do the former, use the following procedure:
141 |
142 | - If contributing your notebook(s) via a pull request, include the data files (e.g., `tutorials/notebooks/My-tutorial-name/mydatafile.fits`). **IMPORTANT**: when you add or modify data files, make sure the only thing in that commit involves the data files. That is, do _not_ edit your notebook and add/change data files in the same commit. This will make it easier to remove the data files when your tutorial is merged.
143 |
144 | - To access your data files in the notebook, do something like this at the top of the notebook:
145 |
146 | ```
147 | from astropy.utils.data import download_file
148 |
149 | tutorialpath = ''
150 | mydatafilename1 = download_file(tutorialpath + 'mydatafile1.fits', cache=True)
151 | mydatafilename2 = download_file(tutorialpath + 'mydatafile2.dat', cache=True)
152 | ```
153 |
154 | And then use them like this:
155 |
156 | ```
157 | fits.open(mydatafilename1)
158 | ...
159 | with open(mydatafilename2) as f:
160 | ...
161 | ```
162 |
163 | If you do this, the only change necessary when merging your notebook will be to set `tutorialpath` to `'http://data.astropy.org/tutorials/My-tutorial-name/'`.
164 |
165 | For data files that are larger than 10 MB in size, we recommend hosting with Zenodo. To use this approach, follow these steps:
166 |
167 | - Sign up for an account at https://zenodo.org/ if you do not have one already.
168 |
169 | - Log in to Zenodo and perform a new upload. Follow the Zenodo instructions and complete all the required fields in order to have the data file(s) uploaded to their records. Once this is done you will have a link to share the data.
170 |
171 | - With the link to the data file record, which has the format `https://zenodo.org/api/records/:id`, an example HTTP GET request needed to retrieve the data using the Python package `requests` is shown below:
172 |
173 | ```
174 | import requests
175 | r = requests.get("https://zenodo.org/api/records/1234)
176 | ```
177 |
178 | To use the output as a locally stored file, you would first need to write the file contents to a file, for example:
179 |
180 | ```
181 | with open('./some-data-file.fits', 'wb') as f:
182 | f.write(r.content)
183 | ```
184 |
--------------------------------------------------------------------------------
/src/pages/404.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Link } from 'gatsby';
4 | import SEO from '../components/seo';
5 |
6 | // styles
7 | const pageStyles = {
8 | color: '#232129',
9 | padding: '96px',
10 | fontFamily: '-apple-system, Roboto, sans-serif, serif',
11 | };
12 | const headingStyles = {
13 | marginTop: 0,
14 | marginBottom: 64,
15 | maxWidth: 320,
16 | };
17 |
18 | const paragraphStyles = {
19 | marginBottom: 48,
20 | };
21 | const codeStyles = {
22 | color: '#8A6534',
23 | padding: 4,
24 | backgroundColor: '#FFF4DB',
25 | fontSize: '1.25rem',
26 | borderRadius: 4,
27 | };
28 |
29 | // markup
30 | const NotFoundPage = ({ location }) => (
31 |
32 |
33 |
Page not found
34 |
35 | Sorry{' '}
36 |
37 | 😔
38 | {' '}
39 | we couldn’t find what you were looking for.
40 |
41 | {process.env.NODE_ENV === 'development' ? (
42 | <>
43 |
44 | Try creating a page in src/pages/.
45 |
46 | >
47 | ) : null}
48 |
49 | Go home.
50 |
27 | Learn how to use Python for astronomy through tutorials and guides
28 | that cover Astropy and other packages in the astronomy Python
29 | ecosystem.
30 |