├── .commitlintrc.json
├── .editorconfig
├── .github
└── workflows
│ ├── build.yml
│ ├── codeql-analysis.yml
│ ├── deploy.yml
│ ├── localazy-upload.yml
│ └── sponsors.yml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .lintstagedrc.json
├── .nvmrc
├── .prettierignore
├── .stylelintrc.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
├── .vitepress
│ ├── config.js
│ └── theme
│ │ ├── custom.css
│ │ └── index.js
├── CNAME
├── accessibility.md
├── api
│ ├── add-collection.md
│ ├── add-endpoint.md
│ ├── add-post-type.md
│ ├── add-preset.md
│ ├── add-store.md
│ ├── add-syndicator.md
│ ├── error.md
│ └── index.md
├── clients.md
├── concepts.md
├── configuration
│ ├── application.md
│ ├── commit-messages.md
│ ├── index.md
│ ├── localisation.md
│ ├── post-template.md
│ ├── post-types.md
│ ├── publication.md
│ ├── time-zone.md
│ └── tokens.md
├── contributing.md
├── decisions
│ ├── 0001-record-architecture-decisions.md
│ ├── 0002-share-publication-state-using-jf2.md
│ ├── 0003-store-post-data-as-jf2.md
│ ├── 0004-seperate-endpoints-for-api-and-ui.md
│ └── 0005-post-type-config-keys.md
├── development.md
├── get-started.md
├── index.md
├── introduction.md
├── plugins
│ ├── endpoints
│ │ ├── auth.md
│ │ ├── files.md
│ │ ├── image.md
│ │ ├── index.md
│ │ ├── json-feed.md
│ │ ├── media.md
│ │ ├── micropub.md
│ │ ├── posts.md
│ │ ├── share.md
│ │ ├── syndicate.md
│ │ └── webmention-io.md
│ ├── index.md
│ ├── post-types
│ │ ├── article.md
│ │ ├── audio.md
│ │ ├── bookmark.md
│ │ ├── event.md
│ │ ├── index.md
│ │ ├── jam.md
│ │ ├── like.md
│ │ ├── note.md
│ │ ├── photo.md
│ │ ├── reply.md
│ │ ├── repost.md
│ │ ├── rsvp.md
│ │ └── video.md
│ ├── presets
│ │ ├── eleventy.md
│ │ ├── hugo.md
│ │ ├── index.md
│ │ └── jekyll.md
│ ├── stores
│ │ ├── bitbucket.md
│ │ ├── file-system.md
│ │ ├── ftp.md
│ │ ├── gitea.md
│ │ ├── github.md
│ │ ├── gitlab.md
│ │ ├── index.md
│ │ └── s3.md
│ └── syndicators
│ │ ├── bluesky.md
│ │ ├── index.md
│ │ ├── internet-archive.md
│ │ └── mastodon.md
├── public
│ ├── authorization-flow.png
│ ├── clients
│ │ ├── ia-writer.png
│ │ ├── indiepass.png
│ │ ├── micro-blog.png
│ │ ├── micropublish.png
│ │ ├── quill.png
│ │ ├── sparkles.png
│ │ └── sunlit.png
│ ├── homepage-illustration.svg
│ ├── icon.svg
│ ├── icons
│ │ ├── indieauth.svg
│ │ ├── micropub.svg
│ │ ├── microsub.svg
│ │ └── webmention.svg
│ ├── interface-dark.png
│ ├── interface-light.png
│ ├── opengraph-image.png
│ ├── publication-flow.png
│ └── syndication-flow.png
├── specifications.md
└── sponsors.md
├── eslint.config.js
├── helpers
├── access-token
│ ├── index.js
│ └── package.json
├── config
│ ├── index.js
│ └── package.json
├── database
│ ├── index.js
│ └── package.json
├── fixtures
│ ├── file-types
│ │ ├── audio.mp3
│ │ ├── font.ttf
│ │ ├── photo.jpg
│ │ └── video.mp4
│ ├── html
│ │ ├── client-simple.html
│ │ ├── client.html
│ │ ├── page.html
│ │ └── post.html
│ ├── index.js
│ ├── jf2
│ │ ├── all-properties.jf2
│ │ ├── article-content-missing.jf2
│ │ ├── article-content-provided-html-text.jf2
│ │ ├── article-content-provided-html.jf2
│ │ ├── article-content-provided-text.jf2
│ │ ├── article-content-provided.jf2
│ │ ├── article-slug-provided-empty.jf2
│ │ ├── article-syndicate-to-provided.jf2
│ │ ├── audio-provided-string-value.jf2
│ │ ├── audio-provided-value.jf2
│ │ ├── audio-provided.jf2
│ │ ├── feed-empty.jf2
│ │ ├── feed.jf2
│ │ ├── note-content-provided-html-with-link.jf2
│ │ ├── note-content-provided-html-with-mastodon-link.jf2
│ │ ├── note-content-provided-html.jf2
│ │ ├── note-location-provided.jf2
│ │ ├── note-published-missing.jf2
│ │ ├── note-published-provided-short.jf2
│ │ ├── note-published-provided.jf2
│ │ ├── note-slug-provided-unslugified.jf2
│ │ ├── note-slug-provided.jf2
│ │ ├── note-visibility-unlisted.jf2
│ │ ├── photo-provided-mp-photo-alt.jf2
│ │ ├── photo-provided-string-value.jf2
│ │ ├── photo-provided-value-alt.jf2
│ │ ├── photo-provided.jf2
│ │ ├── post-template-properties.jf2
│ │ ├── reply-mastodon.jf2
│ │ ├── reply-off-service.jf2
│ │ ├── repost-mastodon.jf2
│ │ ├── video-provided-string-value.jf2
│ │ ├── video-provided-value.jf2
│ │ └── video-provided.jf2
│ └── package.json
├── frontend
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── package.json
│ └── views
│ │ ├── frontend-components.njk
│ │ ├── frontend-flow.njk
│ │ └── frontend-form.njk
├── media-data
│ ├── index.js
│ └── package.json
├── mock-agent
│ ├── endpoint-auth.js
│ ├── endpoint-files.js
│ ├── endpoint-image.js
│ ├── endpoint-media.js
│ ├── endpoint-micropub.js
│ ├── endpoint-posts.js
│ ├── endpoint-syndicate.js
│ ├── endpoint-webmention-io.js
│ ├── index.js
│ ├── indiekit.js
│ ├── package.json
│ ├── store-gitea.js
│ ├── store-github.js
│ ├── store-gitlab.js
│ ├── syndicator-bluesky.js
│ ├── syndicator-internet-archive.js
│ └── syndicator-mastodon.js
├── post-data
│ ├── index.js
│ └── package.json
├── publication
│ ├── index.js
│ └── package.json
├── server
│ ├── index.js
│ └── package.json
├── session
│ ├── index.js
│ └── package.json
└── store
│ ├── index.js
│ └── package.json
├── indiekit.config.js
├── jsconfig.json
├── lerna.json
├── localazy.json
├── package-lock.json
├── package.json
├── packages
├── create-indiekit
│ ├── CHANGELOG.md
│ ├── bin
│ │ └── create.js
│ ├── files
│ │ ├── template.dockerfile
│ │ ├── template.dockerignore
│ │ └── template.gitignore
│ ├── index.js
│ ├── lib
│ │ ├── docker.js
│ │ ├── files.js
│ │ ├── package.js
│ │ ├── setup-prompts.js
│ │ └── utils.js
│ ├── package.json
│ └── test
│ │ └── unit
│ │ ├── docker.js
│ │ ├── files.js
│ │ ├── package.js
│ │ ├── setup-prompts.js
│ │ └── utils.js
├── endpoint-auth
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── lib
│ │ ├── client.js
│ │ ├── controllers
│ │ │ ├── authorization.js
│ │ │ ├── consent.js
│ │ │ ├── documentation.js
│ │ │ ├── introspection.js
│ │ │ ├── metadata.js
│ │ │ ├── password.js
│ │ │ └── token.js
│ │ ├── middleware
│ │ │ ├── code.js
│ │ │ ├── secret.js
│ │ │ └── validation.js
│ │ ├── password.js
│ │ ├── pkce.js
│ │ ├── pushed-authorization-request.js
│ │ ├── redirect.js
│ │ ├── scope.js
│ │ ├── token.js
│ │ └── utils.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ ├── package.json
│ ├── test
│ │ ├── integration
│ │ │ ├── 200-authorization-documentation.js
│ │ │ ├── 200-authorization-invalid-client-id.js
│ │ │ ├── 200-authorization-invalid-redirect-uri.js
│ │ │ ├── 200-authorization-invalid-response-type.js
│ │ │ ├── 200-authorization-no-response-type.js
│ │ │ ├── 200-authorization-profile-json.js
│ │ │ ├── 200-authorization-profile-url-encoded.js
│ │ │ ├── 200-consent-authenticate.js
│ │ │ ├── 200-consent-authorize.js
│ │ │ ├── 200-introspect-token-active.js
│ │ │ ├── 200-introspect-token-inactive.js
│ │ │ ├── 200-metadata.js
│ │ │ ├── 200-new-password-response.js
│ │ │ ├── 200-new-password.js
│ │ │ ├── 200-token-grant-json.js
│ │ │ ├── 200-token-grant-url-encoded.js
│ │ │ ├── 200-token-verify.js
│ │ │ ├── 302-authorization.js
│ │ │ ├── 302-consent-setup-password.js
│ │ │ ├── 302-consent-submit-authenticate-with-me.js
│ │ │ ├── 302-consent-submit-authenticate.js
│ │ │ ├── 302-consent-submit-authorize.js
│ │ │ ├── 302-well-known-change-password.js
│ │ │ ├── 400-consent-invalid-request-uri.js
│ │ │ ├── 400-consent-no-request-uri.js
│ │ │ ├── 400-token-grant-invalid-grant-type.js
│ │ │ ├── 400-token-grant-invalid-pkce-code.js
│ │ │ ├── 400-token-grant-invalid-redirect-uri.js
│ │ │ ├── 400-token-grant-no-client-id.js
│ │ │ ├── 400-token-grant-no-code.js
│ │ │ ├── 400-token-grant-no-redirect-uri.js
│ │ │ ├── 401-token-grant-invalid-client-id.js
│ │ │ ├── 401-token-grant-invalid-token.js
│ │ │ ├── 422-consent-password-invalid.js
│ │ │ ├── 422-consent-password-missing.js
│ │ │ ├── 422-new-password-response.js
│ │ │ ├── 500-authorization-documentation.js
│ │ │ └── 501-token-grant-missing-secret.js
│ │ └── unit
│ │ │ ├── client.js
│ │ │ ├── password.js
│ │ │ ├── pkce.js
│ │ │ ├── pushed-authorization-request.js
│ │ │ ├── redirect.js
│ │ │ ├── scope.js
│ │ │ ├── token.js
│ │ │ └── utils.js
│ └── views
│ │ ├── auth.njk
│ │ ├── consent.njk
│ │ └── new-password.njk
├── endpoint-files
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── includes
│ │ └── @indiekit-endpoint-files-widget.njk
│ ├── index.js
│ ├── lib
│ │ ├── controllers
│ │ │ ├── delete.js
│ │ │ ├── file.js
│ │ │ ├── files.js
│ │ │ └── form.js
│ │ ├── endpoint.js
│ │ ├── middleware
│ │ │ ├── file-data.js
│ │ │ └── validation.js
│ │ └── utils.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ ├── package.json
│ ├── test
│ │ ├── integration
│ │ │ ├── 200-file.js
│ │ │ ├── 200-files-no-database.js
│ │ │ ├── 200-files.js
│ │ │ ├── 200-get-delete.js
│ │ │ ├── 200-get-upload.js
│ │ │ ├── 302-get-delete.js
│ │ │ ├── 302-get-upload.js
│ │ │ ├── 302-post-delete.js
│ │ │ ├── 302-post-upload.js
│ │ │ ├── 401-post-delete-unauthorized.js
│ │ │ ├── 401-post-upload-unauthorized.js
│ │ │ ├── 404-file.js
│ │ │ └── 422-post-upload.js
│ │ └── unit
│ │ │ └── utils.js
│ └── views
│ │ ├── file-delete.njk
│ │ ├── file-form.njk
│ │ ├── file.njk
│ │ └── files.njk
├── endpoint-image
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ └── package.json
├── endpoint-json-feed
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── includes
│ │ └── @indiekit-endpoint-json-feed-widget.njk
│ ├── index.js
│ ├── lib
│ │ ├── controllers
│ │ │ └── json-feed.js
│ │ └── json-feed.js
│ ├── package.json
│ └── test
│ │ ├── integration
│ │ └── 200-feed-json.js
│ │ └── unit
│ │ └── json-feed.js
├── endpoint-media
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── lib
│ │ ├── controllers
│ │ │ ├── action.js
│ │ │ └── query.js
│ │ ├── file.js
│ │ ├── media-content.js
│ │ ├── media-data.js
│ │ ├── media-transform.js
│ │ ├── media-type-count.js
│ │ ├── scope.js
│ │ └── utils.js
│ ├── package.json
│ └── test
│ │ ├── integration
│ │ ├── 200-action-upload.js
│ │ ├── 200-query-source-no-database.js
│ │ ├── 200-query-source.js
│ │ ├── 202-action-delete.js
│ │ ├── 400-action-delete-no-url.js
│ │ ├── 400-action-upload-no-file.js
│ │ ├── 400-query-invalid.js
│ │ ├── 400-query-source-url-no-database.js
│ │ ├── 400-query-source-url-not-found.js
│ │ ├── 401-action-upload-invalid-token.js
│ │ ├── 403-action-upload-insufficient-scope.js
│ │ ├── 404-action-delete-not-found.js
│ │ ├── 415-action-upload-unsupported-media-type.js
│ │ ├── 501-query-unsupported.js
│ │ └── 501-upload-unsupported-post-type.js
│ │ └── unit
│ │ ├── file.js
│ │ ├── media-content.js
│ │ ├── media-data.js
│ │ ├── media-type-count.js
│ │ ├── scope.js
│ │ └── utils.js
├── endpoint-micropub
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── lib
│ │ ├── config.js
│ │ ├── controllers
│ │ │ ├── action.js
│ │ │ └── query.js
│ │ ├── jf2.js
│ │ ├── markdown.js
│ │ ├── media.js
│ │ ├── mf2.js
│ │ ├── post-content.js
│ │ ├── post-data.js
│ │ ├── post-type-count.js
│ │ ├── post-type-discovery.js
│ │ ├── reserved-properties.js
│ │ ├── scope.js
│ │ ├── update.js
│ │ └── utils.js
│ ├── package.json
│ └── test
│ │ ├── integration
│ │ ├── 200-action-update-ignored.js
│ │ ├── 200-query-category.js
│ │ ├── 200-query-config.js
│ │ ├── 200-query-media-endpoint.js
│ │ ├── 200-query-post-types.js
│ │ ├── 200-query-source-no-database.js
│ │ ├── 200-query-source-url-property.js
│ │ ├── 200-query-source-url.js
│ │ ├── 200-query-source.js
│ │ ├── 200-query-syndicate-to.js
│ │ ├── 201-action-update.js
│ │ ├── 202-action-create-draft.js
│ │ ├── 202-action-create-form-encoded.js
│ │ ├── 202-action-create-json.js
│ │ ├── 202-action-delete.js
│ │ ├── 202-action-undelete.js
│ │ ├── 400-action-delete-no-url.js
│ │ ├── 400-action-update-no-operation.js
│ │ ├── 400-query-invalid.js
│ │ ├── 400-query-source-url-no-database.js
│ │ ├── 400-query-source-url-not-found.js
│ │ ├── 401-action-invalid-token.js
│ │ ├── 403-action-insufficient-scope.js
│ │ ├── 403-action-update-draft-insufficient-scope.js
│ │ ├── 404-action-delete-not-found.js
│ │ ├── 501-action-unsupported-post-type.js
│ │ └── 501-query-unsupported.js
│ │ └── unit
│ │ ├── config.js
│ │ ├── jf2.js
│ │ ├── markdown.js
│ │ ├── media.js
│ │ ├── mf2.js
│ │ ├── post-content.js
│ │ ├── post-data.js
│ │ ├── post-type-count.js
│ │ ├── post-type-discovery.js
│ │ ├── scope.js
│ │ ├── update.js
│ │ └── utils.js
├── endpoint-posts
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── includes
│ │ ├── @indiekit-endpoint-posts-syndicate.njk
│ │ ├── @indiekit-endpoint-posts-widget.njk
│ │ └── post-types
│ │ │ ├── category-field.njk
│ │ │ ├── category.njk
│ │ │ ├── content-field.njk
│ │ │ ├── content.njk
│ │ │ ├── featured-field.njk
│ │ │ ├── featured.njk
│ │ │ ├── geo-field.njk
│ │ │ ├── location-field.njk
│ │ │ ├── location.njk
│ │ │ ├── mp-channel.njk
│ │ │ ├── name-field.njk
│ │ │ ├── published.njk
│ │ │ ├── summary-field.njk
│ │ │ └── summary.njk
│ ├── index.js
│ ├── lib
│ │ ├── controllers
│ │ │ ├── delete.js
│ │ │ ├── form.js
│ │ │ ├── new.js
│ │ │ ├── post.js
│ │ │ └── posts.js
│ │ ├── endpoint.js
│ │ ├── middleware
│ │ │ ├── post-data.js
│ │ │ └── validation.js
│ │ ├── status-types.js
│ │ └── utils.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ ├── package.json
│ ├── test
│ │ ├── integration
│ │ │ ├── 200-get-create.js
│ │ │ ├── 200-get-delete.js
│ │ │ ├── 200-get-new.js
│ │ │ ├── 200-post.js
│ │ │ ├── 200-posts-no-database.js
│ │ │ ├── 200-posts.js
│ │ │ ├── 302-get-create.js
│ │ │ ├── 302-get-delete.js
│ │ │ ├── 302-post-create.js
│ │ │ ├── 302-post-delete.js
│ │ │ ├── 401-post-create-unauthorized.js
│ │ │ ├── 401-post-delete-unauthorized.js
│ │ │ ├── 404-post.js
│ │ │ └── 422-post-create.js
│ │ └── unit
│ │ │ └── utils.js
│ └── views
│ │ ├── new.njk
│ │ ├── post-delete.njk
│ │ ├── post-form.njk
│ │ ├── post.njk
│ │ └── posts.njk
├── endpoint-share
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── includes
│ │ └── @indiekit-endpoint-share-widget.njk
│ ├── index.js
│ ├── lib
│ │ ├── controllers
│ │ │ └── share.js
│ │ └── middleware
│ │ │ └── validation.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ ├── package.json
│ ├── test
│ │ └── integration
│ │ │ ├── 200-get-share.js
│ │ │ ├── 302-post-share.js
│ │ │ ├── 400-post-share.js
│ │ │ └── 422-post-share.js
│ └── views
│ │ └── share.njk
├── endpoint-syndicate
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── lib
│ │ ├── controllers
│ │ │ └── syndicate.js
│ │ ├── token.js
│ │ └── utils.js
│ ├── package.json
│ └── test
│ │ ├── integration
│ │ ├── 200-no-post-record-for-url.js
│ │ ├── 200-no-post-records.js
│ │ ├── 200-no-posts-awaiting-syndication.js
│ │ ├── 200-no-syndication-targets.js
│ │ ├── 200-syndicates-recent-post.js
│ │ ├── 200-syndicates-url-for-multiple-targets.js
│ │ ├── 200-syndicates-url-for-target.js
│ │ ├── 200-target-error.js
│ │ ├── 302-syndicates-url-with-redirect.js
│ │ ├── 403-syndicates-url-missing-update-scope.js
│ │ └── 501-no-database.js
│ │ └── unit
│ │ ├── token.js
│ │ └── utils.js
├── endpoint-webmention-io
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── lib
│ │ ├── controllers
│ │ │ └── webmentions.js
│ │ └── utils.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ ├── package.json
│ ├── test
│ │ ├── integration
│ │ │ └── 200-webmentions.js
│ │ └── unit
│ │ │ └── utils.js
│ └── views
│ │ └── webmentions.njk
├── error
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── errors.js
│ ├── index.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ ├── package.json
│ └── test
│ │ └── index.js
├── frontend
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ ├── app-icon-any.svg
│ │ ├── app-icon-maskable.svg
│ │ ├── icon.svg
│ │ ├── not-found.svg
│ │ ├── offline.svg
│ │ └── plug-in.svg
│ ├── components
│ │ ├── actions
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── add-another
│ │ │ ├── index.js
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── app
│ │ │ └── styles.css
│ │ ├── authorize
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── avatar
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── back-link
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── badge
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── bookmarklet
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── button
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── card-grid
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── card
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── character-count
│ │ │ ├── index.js
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── checkboxes
│ │ │ ├── index.js
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── details
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── error-message
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── error-summary
│ │ │ ├── index.js
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── event-duration
│ │ │ ├── index.js
│ │ │ └── styles.css
│ │ ├── field
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── fieldset
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── file-input
│ │ │ ├── index.js
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── footer
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── geo-input
│ │ │ ├── index.js
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── header
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── heading
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── hint
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── icon
│ │ │ └── styles.css
│ │ ├── input
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── label
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── logo
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── main
│ │ │ └── styles.css
│ │ ├── mention
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── navigation
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── notification-banner
│ │ │ ├── index.js
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── pagination
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── progress
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── prose
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── radios
│ │ │ ├── index.js
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── section
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── select
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── share-preview
│ │ │ ├── index.js
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── skip-link
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── summary
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── tag-input
│ │ │ ├── index.js
│ │ │ ├── macro.njk
│ │ │ ├── sanitizer.js
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── tag
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── textarea
│ │ │ ├── index.js
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── user
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ ├── warning-text
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ │ └── widget
│ │ │ ├── macro.njk
│ │ │ ├── styles.css
│ │ │ └── template.njk
│ ├── index.js
│ ├── layouts
│ │ ├── default.njk
│ │ ├── document.njk
│ │ ├── error.njk
│ │ └── form.njk
│ ├── lib
│ │ ├── esbuild.js
│ │ ├── filters
│ │ │ ├── array.js
│ │ │ ├── index.js
│ │ │ ├── locale.js
│ │ │ ├── string.js
│ │ │ └── url.js
│ │ ├── globals
│ │ │ ├── attributes.js
│ │ │ ├── classes.js
│ │ │ ├── error-list.js
│ │ │ ├── field-data.js
│ │ │ ├── icon.js
│ │ │ ├── index.js
│ │ │ ├── item-id.js
│ │ │ └── summary-rows.js
│ │ ├── lightningcss.js
│ │ ├── markdown-it.js
│ │ ├── nunjucks.js
│ │ ├── serviceworker.js
│ │ ├── sharp.js
│ │ └── utils
│ │ │ ├── theme.js
│ │ │ └── wrap-element.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ ├── package.json
│ ├── scripts
│ │ └── app.js
│ ├── styles
│ │ ├── app.css
│ │ ├── base
│ │ │ ├── embedded.css
│ │ │ ├── forms.css
│ │ │ ├── grouping.css
│ │ │ ├── interactive.css
│ │ │ ├── sections.css
│ │ │ ├── tables.css
│ │ │ └── text.css
│ │ ├── config
│ │ │ └── custom-properties.css
│ │ ├── scopes
│ │ │ └── flow.css
│ │ ├── utilities
│ │ │ ├── container.css
│ │ │ └── visually-hidden.css
│ │ └── vendor
│ │ │ ├── codemirror.css
│ │ │ ├── easy-markdown-editor.css
│ │ │ └── markdown-it-prism.css
│ └── test
│ │ └── unit
│ │ ├── components
│ │ └── tag-input.js
│ │ ├── filters
│ │ ├── array.js
│ │ ├── locale.js
│ │ ├── string.js
│ │ └── url.js
│ │ ├── globals
│ │ ├── attributes.js
│ │ ├── classes.js
│ │ ├── error-list.js
│ │ ├── field-data.js
│ │ ├── icon.js
│ │ ├── item-id.js
│ │ └── summary-row.js
│ │ └── theme.js
├── indiekit
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── bin
│ │ └── cli.js
│ ├── config
│ │ ├── defaults.js
│ │ ├── express.js
│ │ └── locales.js
│ ├── index.js
│ ├── lib
│ │ ├── cache.js
│ │ ├── categories.js
│ │ ├── config.js
│ │ ├── controllers
│ │ │ ├── assets.js
│ │ │ ├── client.js
│ │ │ ├── feed.js
│ │ │ ├── homepage.js
│ │ │ ├── manifest.js
│ │ │ ├── offline.js
│ │ │ ├── plugin.js
│ │ │ ├── session.js
│ │ │ └── status.js
│ │ ├── endpoints.js
│ │ ├── indieauth.js
│ │ ├── locale-catalog.js
│ │ ├── middleware
│ │ │ ├── error.js
│ │ │ ├── force-https.js
│ │ │ ├── internationalisation.js
│ │ │ ├── locals.js
│ │ │ └── logging.js
│ │ ├── mongodb.js
│ │ ├── navigation.js
│ │ ├── plugins.js
│ │ ├── post-template.js
│ │ ├── post-types.js
│ │ ├── routes.js
│ │ ├── shortcuts.js
│ │ ├── state.js
│ │ ├── store.js
│ │ ├── token.js
│ │ ├── utils.js
│ │ └── views.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ ├── package.json
│ ├── test
│ │ ├── index.js
│ │ ├── integration
│ │ │ ├── 200-app-icon.js
│ │ │ ├── 200-client.js
│ │ │ ├── 200-feed-jf2.js
│ │ │ ├── 200-homepage-view.js
│ │ │ ├── 200-manifest.js
│ │ │ ├── 200-offline-view.js
│ │ │ ├── 200-plugin-list.js
│ │ │ ├── 200-plugin-view.js
│ │ │ ├── 200-robots.js
│ │ │ ├── 200-scripts.js
│ │ │ ├── 200-session-auth.js
│ │ │ ├── 200-session-localised.js
│ │ │ ├── 200-session-login.js
│ │ │ ├── 200-shortcut-icon.js
│ │ │ ├── 200-status-view.js
│ │ │ ├── 200-styles.js
│ │ │ ├── 302-session-login-auth.js
│ │ │ ├── 302-session-login-redirect.js
│ │ │ ├── 302-session-logout.js
│ │ │ ├── 302-status-redirect.js
│ │ │ ├── 400-session-missing-code.js
│ │ │ ├── 400-session-missing-state.js
│ │ │ ├── 401-session-auth-invalid-token.js
│ │ │ ├── 403-session-auth-invalid-redirect.js
│ │ │ ├── 403-session-auth-invalid-state.js
│ │ │ └── 404-not-found.js
│ │ ├── server.js
│ │ └── unit
│ │ │ ├── cache.js
│ │ │ ├── categories.js
│ │ │ ├── endpoints.js
│ │ │ ├── indieauth.js
│ │ │ ├── middleware
│ │ │ ├── error.js
│ │ │ ├── force-https.js
│ │ │ ├── internationalisation.js
│ │ │ └── locals.js
│ │ │ ├── mongodb.js
│ │ │ ├── navigation.js
│ │ │ ├── plugins.js
│ │ │ ├── post-template.js
│ │ │ ├── post-types.js
│ │ │ ├── shortcuts.js
│ │ │ ├── state.js
│ │ │ ├── store.js
│ │ │ ├── tokens.js
│ │ │ └── utils.js
│ └── views
│ │ ├── homepage.njk
│ │ ├── offline.njk
│ │ ├── plugins
│ │ ├── list.njk
│ │ └── view.njk
│ │ ├── session
│ │ └── login.njk
│ │ └── status.njk
├── post-type-article
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ └── package.json
├── post-type-audio
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── includes
│ │ └── post-types
│ │ │ ├── audio-field.njk
│ │ │ └── audio.njk
│ ├── index.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ └── package.json
├── post-type-bookmark
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── includes
│ │ └── post-types
│ │ │ ├── bookmark-of-field.njk
│ │ │ └── bookmark-of.njk
│ ├── index.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ └── package.json
├── post-type-event
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── includes
│ │ └── post-types
│ │ │ ├── start-field.njk
│ │ │ └── start.njk
│ ├── index.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ └── package.json
├── post-type-jam
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── includes
│ │ └── post-types
│ │ │ ├── jam-of-field.njk
│ │ │ └── jam-of.njk
│ ├── index.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ └── package.json
├── post-type-like
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── includes
│ │ └── post-types
│ │ │ ├── like-of-field.njk
│ │ │ └── like-of.njk
│ ├── index.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ └── package.json
├── post-type-note
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ └── package.json
├── post-type-photo
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── includes
│ │ └── post-types
│ │ │ ├── photo-field.njk
│ │ │ └── photo.njk
│ ├── index.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ └── package.json
├── post-type-reply
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── includes
│ │ └── post-types
│ │ │ ├── in-reply-to-field.njk
│ │ │ └── in-reply-to.njk
│ ├── index.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ └── package.json
├── post-type-repost
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── includes
│ │ └── post-types
│ │ │ ├── repost-of-field.njk
│ │ │ └── repost-of.njk
│ ├── index.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ └── package.json
├── post-type-rsvp
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── includes
│ │ └── post-types
│ │ │ ├── rsvp-field.njk
│ │ │ └── rsvp.njk
│ ├── index.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ └── package.json
├── post-type-video
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── includes
│ │ └── post-types
│ │ │ ├── video-field.njk
│ │ │ └── video.njk
│ ├── index.js
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-419.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── hi.json
│ │ ├── id.json
│ │ ├── it.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── sr.json
│ │ ├── sv.json
│ │ └── zh-Hans-CN.json
│ └── package.json
├── preset-eleventy
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── lib
│ │ ├── post-template.js
│ │ └── post-types.js
│ ├── package.json
│ └── test
│ │ ├── index.js
│ │ └── unit
│ │ ├── post-template.js
│ │ └── post-types.js
├── preset-hugo
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── lib
│ │ ├── post-template.js
│ │ └── post-types.js
│ ├── package.json
│ └── test
│ │ ├── index.js
│ │ └── unit
│ │ ├── post-template.js
│ │ └── post-types.js
├── preset-jekyll
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── lib
│ │ ├── post-template.js
│ │ └── post-types.js
│ ├── package.json
│ └── test
│ │ ├── index.js
│ │ └── unit
│ │ ├── post-template.js
│ │ └── post-types.js
├── store-bitbucket
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── package.json
│ └── test
│ │ └── index.js
├── store-file-system
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── package.json
│ └── test
│ │ └── index.js
├── store-ftp
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── package.json
│ └── test
│ │ └── index.js
├── store-gitea
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── package.json
│ └── test
│ │ └── index.js
├── store-github
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── package.json
│ └── test
│ │ └── index.js
├── store-gitlab
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── package.json
│ └── test
│ │ └── index.js
├── store-s3
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── package.json
│ └── test
│ │ └── index.js
├── syndicator-bluesky
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── lib
│ │ ├── bluesky.js
│ │ └── utils.js
│ ├── package.json
│ └── test
│ │ ├── index.js
│ │ └── unit
│ │ ├── bluesky.js
│ │ └── utils.js
├── syndicator-internet-archive
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── lib
│ │ └── internet-archive.js
│ ├── package.json
│ └── test
│ │ ├── index.js
│ │ └── unit
│ │ └── internet-archive.js
├── syndicator-mastodon
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── icon.svg
│ ├── index.js
│ ├── lib
│ │ ├── mastodon.js
│ │ └── utils.js
│ ├── package.json
│ └── test
│ │ ├── index.js
│ │ └── unit
│ │ ├── mastodon.js
│ │ └── utils.js
└── util
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── index.js
│ ├── lib
│ ├── collection.js
│ ├── date.js
│ ├── object.js
│ ├── regex.js
│ ├── string.js
│ ├── url.js
│ └── validation-schema.js
│ ├── package.json
│ └── test
│ └── unit
│ ├── collection.js
│ ├── date.js
│ ├── object.js
│ ├── string.js
│ ├── url.js
│ └── validation-schema.js
└── types
└── express
└── index.d.ts
/.commitlintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["@commitlint/config-conventional"]
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
16 | [*.njk]
17 | insert_final_newline = false
18 |
--------------------------------------------------------------------------------
/.github/workflows/localazy-upload.yml:
--------------------------------------------------------------------------------
1 | name: localazy-upload
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | paths:
8 | - packages/**/locales/en.json
9 |
10 | jobs:
11 | localazy-upload:
12 | name: Upload strings to Localazy
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 | - uses: localazy/upload@v1
17 | with:
18 | read_key: ${{ secrets.LOCALAZY_READ_KEY }}
19 | write_key: ${{ secrets.LOCALAZY_WRITE_KEY }}
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Testing and coverage
7 | coverage
8 | tmp
9 |
10 | # Dependencies
11 | node_modules/
12 |
13 | # Documentation
14 | _site
15 | docs/.cache
16 |
17 | # Environment variables
18 | *.env
19 | .envrc
20 |
21 | # Localisation keys
22 | localazy.keys.json
23 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | npx --no-install commitlint --edit
4 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | npx lint-staged
4 |
--------------------------------------------------------------------------------
/.lintstagedrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "*.js": ["prettier --write", "eslint"],
3 | "*.css": ["prettier --write", "stylelint"]
4 | }
5 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 22
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | coverage
2 | docs/**/*.md
3 | **/locales/*.json
4 | *.html
5 | CHANGELOG.md
6 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["stylelint-config-recommended"],
3 | "ignoreFiles": ["_site/**"],
4 | "plugins": ["stylelint-order"],
5 | "rules": {
6 | "order/order": ["custom-properties", "declarations"],
7 | "order/properties-alphabetical-order": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/no-unresolved
2 | export { default } from "vitepress/theme-without-fonts";
3 | import "./custom.css";
4 |
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | getindiekit.com
--------------------------------------------------------------------------------
/docs/api/add-collection.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # `Indiekit.addCollection`
6 |
7 | This method is enables plug-ins to add a new collection to the MongoDB database for storing data.
8 |
9 | ## Syntax
10 |
11 | ```js
12 | new Indiekit.addCollection(name);
13 | ```
14 |
15 | ## Constructor
16 |
17 | `name`
18 | : Collection name. This cannot share the name of a collection added by another plug-in. Indiekit currently adds 2 collections: `posts` and `media`.
19 |
--------------------------------------------------------------------------------
/docs/decisions/0001-record-architecture-decisions.md:
--------------------------------------------------------------------------------
1 | # Record architecture decisions
2 |
3 | Date: 2021-01-10
4 |
5 | ## Status
6 |
7 | Accepted
8 |
9 | ## Context
10 |
11 | We need to record the architectural decisions made on this project.
12 |
13 | ## Decision
14 |
15 | We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions).
16 |
17 | ## Consequences
18 |
19 | See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools).
20 |
--------------------------------------------------------------------------------
/docs/plugins/endpoints/auth.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/endpoints/files.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/endpoints/image.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/endpoints/json-feed.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/endpoints/media.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/endpoints/micropub.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/endpoints/posts.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/endpoints/share.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/endpoints/syndicate.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/endpoints/webmention-io.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/post-types/article.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/post-types/audio.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/post-types/bookmark.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/post-types/event.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/post-types/jam.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/post-types/like.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/post-types/note.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/post-types/photo.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/post-types/reply.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/post-types/repost.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/post-types/rsvp.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/post-types/video.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/presets/eleventy.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/presets/hugo.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/presets/index.md:
--------------------------------------------------------------------------------
1 | # Publication presets
2 |
3 | A [publication preset](../../concepts#publication-preset) provides default values for post types and post templates. Plug-ins are available for the following platforms:
4 |
5 | ## Pre-installed plug-ins
6 |
7 | - [Jekyll](jekyll.md) `@indiekit/preset-jekyll`
8 |
9 | ## Official plug-ins
10 |
11 | - [Eleventy](eleventy.md) `@indiekit/preset-eleventy`
12 | - [Hugo](hugo.md) `@indiekit/preset-hugo`
13 |
14 | ## Community plug-ins
15 |
16 | - [FFF](https://npmjs.org/package/indiekit-preset-fff) `indiekit-preset-fff`
17 |
--------------------------------------------------------------------------------
/docs/plugins/presets/jekyll.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/stores/bitbucket.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/stores/file-system.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/stores/ftp.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/stores/gitea.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/stores/github.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/stores/gitlab.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/stores/index.md:
--------------------------------------------------------------------------------
1 | # Content stores
2 |
3 | A [content store](../../concepts#content-store) is a location where Indiekit can save post content and media files. Plug-ins are available for the following platforms:
4 |
5 | ## Official plug-ins
6 |
7 | - [Bitbucket](bitbucket.md) `@indiekit/store-bitbucket`
8 | - [File system](file-system.md) `@indiekit/store-file-system`
9 | - [FTP](ftp.md) `@indiekit/store-ftp`
10 | - [Gitea](gitea.md) `@indiekit/store-gitea`
11 | - [GitHub](github.md) `@indiekit/store-github`
12 | - [GitLab](gitlab.md) `@indiekit/store-gitlab`
13 | - [S3-compatible](s3.md) `@indiekit/store-s3`
14 |
--------------------------------------------------------------------------------
/docs/plugins/stores/s3.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/syndicators/bluesky.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/syndicators/index.md:
--------------------------------------------------------------------------------
1 | # Syndicators
2 |
3 | A [syndicator](../../concepts#syndicator) enables content to be posted to third-party websites, in addition to your own. Plug-ins are available for the following platforms:
4 |
5 | ## Official plug-ins
6 |
7 | - [Bluesky](bluesky.md) `@indiekit/syndicator-bluesky`
8 | - [Internet Archive](internet-archive.md) `@indiekit/syndicator-internet-archive`
9 | - [Mastodon](mastodon.md) `@indiekit/syndicator-mastodon`
10 |
--------------------------------------------------------------------------------
/docs/plugins/syndicators/internet-archive.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/plugins/syndicators/mastodon.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/public/authorization-flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/docs/public/authorization-flow.png
--------------------------------------------------------------------------------
/docs/public/clients/ia-writer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/docs/public/clients/ia-writer.png
--------------------------------------------------------------------------------
/docs/public/clients/indiepass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/docs/public/clients/indiepass.png
--------------------------------------------------------------------------------
/docs/public/clients/micro-blog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/docs/public/clients/micro-blog.png
--------------------------------------------------------------------------------
/docs/public/clients/micropublish.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/docs/public/clients/micropublish.png
--------------------------------------------------------------------------------
/docs/public/clients/quill.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/docs/public/clients/quill.png
--------------------------------------------------------------------------------
/docs/public/clients/sparkles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/docs/public/clients/sparkles.png
--------------------------------------------------------------------------------
/docs/public/clients/sunlit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/docs/public/clients/sunlit.png
--------------------------------------------------------------------------------
/docs/public/icon.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/docs/public/icons/indieauth.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/docs/public/icons/micropub.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/docs/public/icons/microsub.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/docs/public/icons/webmention.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/docs/public/interface-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/docs/public/interface-dark.png
--------------------------------------------------------------------------------
/docs/public/interface-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/docs/public/interface-light.png
--------------------------------------------------------------------------------
/docs/public/opengraph-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/docs/public/opengraph-image.png
--------------------------------------------------------------------------------
/docs/public/publication-flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/docs/public/publication-flow.png
--------------------------------------------------------------------------------
/docs/public/syndication-flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/docs/public/syndication-flow.png
--------------------------------------------------------------------------------
/docs/sponsors.md:
--------------------------------------------------------------------------------
1 | # Sponsors
2 |
3 | Indiekit is [supported by its community](https://github.com/sponsors/getindiekit). Special thanks to:
4 |
5 |
6 |
--------------------------------------------------------------------------------
/helpers/access-token/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@indiekit-test/token",
3 | "main": "index.js",
4 | "type": "module",
5 | "private": true,
6 | "license": "MIT"
7 | }
8 |
--------------------------------------------------------------------------------
/helpers/config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@indiekit-test/config",
3 | "main": "index.js",
4 | "type": "module",
5 | "private": true,
6 | "license": "MIT"
7 | }
8 |
--------------------------------------------------------------------------------
/helpers/database/index.js:
--------------------------------------------------------------------------------
1 | import { getMongodbClient } from "@indiekit/indiekit/lib/mongodb.js";
2 | import { MongoMemoryServer } from "mongodb-memory-server";
3 |
4 | export const testDatabase = async () => {
5 | const mongoServer = await MongoMemoryServer.create();
6 | const mongoUri = mongoServer.getUri();
7 | const { client } = await getMongodbClient(mongoUri);
8 | const database = await client.db();
9 |
10 | return { client, database, mongoServer, mongoUri };
11 | };
12 |
--------------------------------------------------------------------------------
/helpers/database/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@indiekit-test/database",
3 | "main": "index.js",
4 | "type": "module",
5 | "private": true,
6 | "license": "MIT"
7 | }
8 |
--------------------------------------------------------------------------------
/helpers/fixtures/file-types/audio.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/helpers/fixtures/file-types/audio.mp3
--------------------------------------------------------------------------------
/helpers/fixtures/file-types/font.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/helpers/fixtures/file-types/font.ttf
--------------------------------------------------------------------------------
/helpers/fixtures/file-types/photo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/helpers/fixtures/file-types/photo.jpg
--------------------------------------------------------------------------------
/helpers/fixtures/file-types/video.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getindiekit/indiekit/3460703666c0ee318a3845815a83617ba84a22d6/helpers/fixtures/file-types/video.mp4
--------------------------------------------------------------------------------
/helpers/fixtures/html/client-simple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Client (with microformats2)
5 |
6 |
7 |
8 | Simple client example
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/helpers/fixtures/html/client.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Client (with microformats2)
5 |
6 |
7 |
8 |
9 |
10 | Another client
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/helpers/fixtures/html/page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Page (without microformats2)
5 |
6 |
7 |
8 | I ate a cheese sandwich, which was nice.
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/helpers/fixtures/html/post.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Post (with microformats2)
5 |
6 |
7 |
8 |
9 | :
10 | I ate a cheese sandwich, which was nice.
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/helpers/fixtures/index.js:
--------------------------------------------------------------------------------
1 | import fs from "node:fs";
2 | import { fileURLToPath } from "node:url";
3 |
4 | /**
5 | * @param {string} filename - Fixture’s file name
6 | * @param {boolean} utf8 - Encoding fixture as UTF8
7 | * @returns {object} File contents
8 | */
9 | export const getFixture = (filename, utf8 = true) => {
10 | const file = fileURLToPath(new URL(filename, import.meta.url));
11 | return fs.readFileSync(file, {
12 | ...(utf8 && { encoding: "utf8" }),
13 | });
14 | };
15 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/article-content-missing.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "What I had for lunch"
4 | }
5 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/article-content-provided-html-text.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "What I had for lunch",
4 | "content": {
5 | "html": "I atehad a cheese sandwich from https://cafe.example, which was > 10.
– Me, then.
",
6 | "text": "> I atehad a [cheese](https://en.wikipedia.org/wiki/Cheese) sandwich from https://cafe.example, which was > 10.\n\n-- Me, then."
7 | },
8 | "url": "https://foo.bar/lunchtime"
9 | }
10 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/article-content-provided-html.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "What I had for lunch",
4 | "content": {
5 | "html": "I atehad a cheese sandwich from https://cafe.example, which was > 10.
– Me, then.
"
6 | },
7 | "url": "https://foo.bar/lunchtime"
8 | }
9 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/article-content-provided-text.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "What I had for lunch",
4 | "content": {
5 | "text": "> I atehad a [cheese](https://en.wikipedia.org/wiki/Cheese) sandwich from https://cafe.example, which was > 10.\n\n-- Me, then."
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/article-content-provided.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "What I had for lunch",
4 | "content": "> I atehad a [cheese](https://en.wikipedia.org/wiki/Cheese) sandwich from https://cafe.example, which was > 10.\n\n-- Me, then."
5 | }
6 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/article-slug-provided-empty.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "What I had for lunch",
4 | "content": "I ate a *cheese* sandwich, which was nice.",
5 | "mp-slug": ""
6 | }
7 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/article-syndicate-to-provided.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "What I had for lunch",
4 | "content": "I ate a *cheese* sandwich, which was nice.",
5 | "mp-syndicate-to": ["https://example.website/"]
6 | }
7 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/audio-provided-string-value.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "Audio",
4 | "audio": "https://website.example/baz.mp3"
5 | }
6 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/audio-provided-value.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "Audio",
4 | "audio": [{
5 | "url": "https://website.example/baz.mp3"
6 | }, {
7 | "url": "https://foo.bar/qux.mp3"
8 | }]
9 | }
10 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/audio-provided.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "Audio",
4 | "audio": [
5 | "https://website.example/baz.mp3",
6 | "https://foo.bar/qux.mp3"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/feed-empty.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "feed",
3 | "name": "My Example Feed",
4 | "summary": "A description of my example feed",
5 | "url": "https://website.example/",
6 | "photo": "https://website.example/icon.png",
7 | "author": {
8 | "name": "Jane Doe",
9 | "url": "https://website.example/~janedoe",
10 | "avatar": "https://website.example/~janedoe/photo.jpg"
11 | },
12 | "children": []
13 | }
14 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/note-content-provided-html-with-link.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "content": {
4 | "html": "I ate a cheese sandwich, which was > 10.
"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/note-content-provided-html-with-mastodon-link.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "content": {
4 | "html": "I ate @cheese’s sandwich, which was nice.
"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/note-content-provided-html.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "content": {
4 | "html": "I ate a cheese sandwich, which was > 10.
"
5 | },
6 | "url": "https://foo.bar/lunchtime"
7 | }
8 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/note-location-provided.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "content": {
4 | "html": "I ate a cheese sandwich right here!
",
5 | "text": "I ate a cheese sandwich right here!"
6 | },
7 | "location": {
8 | "type": "geo",
9 | "latitude": "37.780080",
10 | "longitude": "-122.420160",
11 | "name": "37° 46′ 48.29″ N 122° 25′ 12.576″ W"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/note-published-missing.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "content": "I ate a cheese sandwich, which was nice."
4 | }
5 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/note-published-provided-short.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "content": "I ate a cheese sandwich, which was nice.",
4 | "published": "2019-01-02"
5 | }
6 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/note-published-provided.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "content": "I ate a cheese sandwich, which was nice.",
4 | "published": "2019-01-02T03:04:05.678Z"
5 | }
6 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/note-slug-provided-unslugified.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "content": "I ate a cheese sandwich, which was nice.",
4 | "mp-slug": "Cheese sandwich"
5 | }
6 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/note-slug-provided.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "content": "I ate a cheese sandwich, which was nice.",
4 | "mp-slug": "cheese-sandwich"
5 | }
6 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/note-visibility-unlisted.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "content": {
4 | "html": "I ate a cheese sandwich, which was nice.
"
5 | },
6 | "visibility": "unlisted"
7 | }
8 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/photo-provided-mp-photo-alt.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "Photo",
4 | "photo": [
5 | "https://website.example/baz.jpg",
6 | "https://foo.bar/qux.jpg"
7 | ],
8 | "mp-photo-alt": ["Baz", "Qux"]
9 | }
10 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/photo-provided-string-value.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "Photo",
4 | "photo": "https://website.example/baz.jpg"
5 | }
6 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/photo-provided-value-alt.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "Photo",
4 | "photo": [{
5 | "alt": "Baz",
6 | "url": "https://website.example/baz.jpg"
7 | }, {
8 | "alt": " Qux ",
9 | "url": "https://foo.bar/qux.jpg"
10 | }]
11 | }
12 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/photo-provided.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "Photo",
4 | "photo": [
5 | "https://website.example/baz.jpg",
6 | "https://foo.bar/qux.jpg"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/reply-mastodon.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "content": {
4 | "html": "I ate a cheese sandwich too!
",
5 | "text": "I ate a cheese sandwich too!"
6 | },
7 | "in-reply-to": "https://mastodon.example/@username/1234567890987654321"
8 | }
9 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/reply-off-service.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "content": {
4 | "html": "I ate a cheese sandwich too!
",
5 | "text": "I ate a cheese sandwich too!"
6 | },
7 | "in-reply-to": "https://deadbird.example/username/1234567890987654321"
8 | }
9 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/repost-mastodon.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "content": {
4 | "html": "Someone else who likes cheese sandwiches.
",
5 | "text": "Someone else who likes cheese sandwiches."
6 | },
7 | "repost-of": "https://mastodon.example/@username/1234567890987654321"
8 | }
9 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/video-provided-string-value.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "Video",
4 | "video": "https://website.example/baz.mp4"
5 | }
6 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/video-provided-value.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "Video",
4 | "video": [{
5 | "url": "https://website.example/baz.mp4"
6 | }, {
7 | "url": "https://foo.bar/qux.mp4"
8 | }]
9 | }
10 |
--------------------------------------------------------------------------------
/helpers/fixtures/jf2/video-provided.jf2:
--------------------------------------------------------------------------------
1 | {
2 | "type": "entry",
3 | "name": "Video",
4 | "video": [
5 | "https://website.example/baz.mp4",
6 | "https://foo.bar/qux.mp4"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/helpers/fixtures/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@indiekit-test/fixtures",
3 | "main": "index.js",
4 | "type": "module",
5 | "private": true,
6 | "license": "MIT"
7 | }
8 |
--------------------------------------------------------------------------------
/helpers/frontend/assets/icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/helpers/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@indiekit-test/frontend",
3 | "main": "index.js",
4 | "type": "module",
5 | "private": true,
6 | "license": "MIT"
7 | }
8 |
--------------------------------------------------------------------------------
/helpers/media-data/index.js:
--------------------------------------------------------------------------------
1 | export const mediaData = {
2 | path: "photo.jpg",
3 | properties: {
4 | "content-type": "image/jpeg",
5 | "media-type": "photo",
6 | published: "2020-01-01T00:00:00+00:00",
7 | url: "https://website.example/photo.jpg",
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/helpers/media-data/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@indiekit-test/media-data",
3 | "main": "index.js",
4 | "type": "module",
5 | "private": true,
6 | "license": "MIT"
7 | }
8 |
--------------------------------------------------------------------------------
/helpers/mock-agent/index.js:
--------------------------------------------------------------------------------
1 | import { setGlobalDispatcher } from "undici";
2 |
3 | /**
4 | * @param {string} name - Base name of mock client file
5 | * @param {object} [options] - Client options
6 | */
7 | export const mockAgent = async (name, options = {}) => {
8 | const { mockClient } = await import(`./${name}.js`);
9 | setGlobalDispatcher(mockClient(options));
10 | };
11 |
--------------------------------------------------------------------------------
/helpers/mock-agent/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@indiekit-test/mock-agent",
3 | "main": "index.js",
4 | "type": "module",
5 | "private": true,
6 | "license": "MIT"
7 | }
8 |
--------------------------------------------------------------------------------
/helpers/post-data/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@indiekit-test/post-data",
3 | "main": "index.js",
4 | "type": "module",
5 | "private": true,
6 | "license": "MIT"
7 | }
8 |
--------------------------------------------------------------------------------
/helpers/publication/index.js:
--------------------------------------------------------------------------------
1 | import JekyllPreset from "@indiekit/preset-jekyll";
2 | import TestStore from "@indiekit-test/store";
3 |
4 | export const publication = {
5 | categories: `https://website.example/categories.json`,
6 | me: "https://website.example",
7 | postTemplate(properties) {
8 | return JSON.stringify(properties);
9 | },
10 | mediaStore: new TestStore({
11 | user: "user",
12 | }),
13 | store: new TestStore({
14 | user: "user",
15 | }),
16 | storeMessageTemplate: (metaData) =>
17 | `${metaData.action} ${metaData.postType} ${metaData.fileType}`,
18 | preset: new JekyllPreset(),
19 | };
20 |
--------------------------------------------------------------------------------
/helpers/publication/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@indiekit-test/publication",
3 | "main": "index.js",
4 | "type": "module",
5 | "private": true,
6 | "license": "MIT"
7 | }
8 |
--------------------------------------------------------------------------------
/helpers/server/index.js:
--------------------------------------------------------------------------------
1 | import { mock } from "node:test";
2 |
3 | import "dotenv/config.js";
4 | import { Indiekit } from "@indiekit/indiekit";
5 | import { testConfig } from "@indiekit-test/config";
6 | import getPort from "get-port";
7 |
8 | export const testServer = async (options) => {
9 | mock.method(console, "info", () => {}); // Disable console.info
10 | mock.method(console, "warn", () => {}); // Disable console.warn
11 |
12 | const config = await testConfig(options);
13 | const indiekit = await Indiekit.initialize({ config });
14 | const port = await getPort();
15 | const server = await indiekit.server({ port });
16 |
17 | return server;
18 | };
19 |
--------------------------------------------------------------------------------
/helpers/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@indiekit-test/server",
3 | "main": "index.js",
4 | "type": "module",
5 | "private": true,
6 | "license": "MIT",
7 | "dependencies": {
8 | "get-port": "^7.0.0"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/helpers/session/index.js:
--------------------------------------------------------------------------------
1 | import process from "node:process";
2 |
3 | import { testToken } from "@indiekit-test/token";
4 | import mockSession from "mock-session";
5 |
6 | export const testCookie = (options) => {
7 | return mockSession("Test configuration", process.env.SECRET, {
8 | access_token: testToken(options),
9 | scope: options?.scope || "create update delete media",
10 | });
11 | };
12 |
--------------------------------------------------------------------------------
/helpers/session/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@indiekit-test/session",
3 | "main": "index.js",
4 | "type": "module",
5 | "private": true,
6 | "license": "MIT"
7 | }
8 |
--------------------------------------------------------------------------------
/helpers/store/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@indiekit-test/store",
3 | "main": "index.js",
4 | "type": "module",
5 | "private": true,
6 | "license": "MIT"
7 | }
8 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "checkJs": true,
4 | "module": "nodenext",
5 | "resolveJsonModule": true,
6 | "target": "es2022"
7 | },
8 | "exclude": ["node_modules", "packages/frontend/lib/serviceworker.js"],
9 | "typeRoots": ["types", "./node_modules/@types"]
10 | }
11 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json",
3 | "command": {
4 | "publish": {
5 | "conventionalCommits": true
6 | }
7 | },
8 | "ignoreChanges": ["**/tests/**", "**/*.md"],
9 | "version": "1.0.0-beta.22"
10 | }
11 |
--------------------------------------------------------------------------------
/packages/create-indiekit/bin/create.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import process from "node:process";
3 |
4 | import { init } from "../index.js";
5 | import { checkNodeVersion } from "../lib/utils.js";
6 |
7 | checkNodeVersion(process.versions.node, 20);
8 |
9 | await init();
10 |
--------------------------------------------------------------------------------
/packages/create-indiekit/files/template.dockerfile:
--------------------------------------------------------------------------------
1 | # Adjust NODE_VERSION as desired
2 | ARG NODE_VERSION=20.5.0
3 | FROM node:${NODE_VERSION}-alpine
4 |
5 | # Create app directory
6 | WORKDIR /usr/src/app
7 |
8 | # Set production environment
9 | ENV NODE_ENV=production
10 |
11 | # Install node modules
12 | COPY package*.json ./
13 |
14 | # Can’t use `npm ci` due to https://github.com/npm/cli/issues/4828
15 | RUN npm i --omit=dev --package-lock=false
16 |
17 | # Copy application code
18 | COPY . .
19 |
20 | # Expose port
21 | EXPOSE 3000
22 |
23 | # Start the server by default, this can be overwritten at runtime
24 | CMD [ "npx", "indiekit", "serve" ]
25 |
--------------------------------------------------------------------------------
/packages/create-indiekit/files/template.dockerignore:
--------------------------------------------------------------------------------
1 | # Docker
2 | .docker
3 | .dockerignore
4 |
5 | # Environment
6 | .env
7 |
8 | # Git
9 | .git
10 | .gitignore
11 |
12 | # Node
13 | node_modules
14 | npm-debug.log
15 |
--------------------------------------------------------------------------------
/packages/create-indiekit/files/template.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/packages/endpoint-auth/assets/icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/packages/endpoint-auth/lib/controllers/documentation.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Service documentation
3 | * @type {import("express").ErrorRequestHandler}
4 | */
5 | export const documentationController = (error, request, response, next) => {
6 | if (request.accepts("html")) {
7 | response.render("auth", {
8 | title: response.locals.__("auth.guidance.title"),
9 | ...(error.message && { error }),
10 | });
11 | } else if (request.accepts("json")) {
12 | return next(error);
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/packages/endpoint-auth/lib/middleware/secret.js:
--------------------------------------------------------------------------------
1 | import process from "node:process";
2 |
3 | import { IndiekitError } from "@indiekit/error";
4 |
5 | /**
6 | * Check that server secret has been set
7 | * @type {import("express").RequestHandler}
8 | */
9 | export const hasSecret = (request, response, next) => {
10 | if (!process.env.SECRET) {
11 | const error = IndiekitError.notImplemented(
12 | response.locals.__("NotImplementedError.secret"),
13 | );
14 |
15 | next(error);
16 | }
17 |
18 | next();
19 | };
20 |
--------------------------------------------------------------------------------
/packages/endpoint-auth/lib/password.js:
--------------------------------------------------------------------------------
1 | import process from "node:process";
2 |
3 | import bcrypt from "bcrypt";
4 |
5 | /**
6 | * Create password hash
7 | * @param {string} password - Password
8 | * @returns {Promise} Password hash
9 | */
10 | export async function createPasswordHash(password) {
11 | return bcrypt.hash(password, 10);
12 | }
13 |
14 | /**
15 | * Verify password
16 | * @param {string} password - Password
17 | * @returns {Promise} Password valid
18 | */
19 | export async function verifyPassword(password) {
20 | return bcrypt.compare(password, process.env.PASSWORD_SECRET);
21 | }
22 |
--------------------------------------------------------------------------------
/packages/endpoint-auth/lib/pkce.js:
--------------------------------------------------------------------------------
1 | import { createHash } from "node:crypto";
2 |
3 | /**
4 | * Verify PKCE (Proof Key for Code Exchange) code
5 | * @param {string} verifier - Code verifier
6 | * @param {string} challenge - Code challenge
7 | * @param {string} [challengeMethod] - Code challenge method
8 | * @returns {boolean} - Code challenge result
9 | */
10 | export const verifyCode = (verifier, challenge, challengeMethod = "sha256") => {
11 | const base64Digest = createHash(challengeMethod)
12 | .update(verifier)
13 | .digest("base64url");
14 |
15 | return challenge === base64Digest.toString();
16 | };
17 |
--------------------------------------------------------------------------------
/packages/endpoint-auth/lib/token.js:
--------------------------------------------------------------------------------
1 | import process from "node:process";
2 |
3 | import jwt from "jsonwebtoken";
4 |
5 | /**
6 | * Sign token
7 | * @param {object} payload - JSON Web Token payload
8 | * @param {string} [expiresIn] - Token expiry
9 | * @returns {string} Signed JSON Web Token
10 | */
11 | export const signToken = (payload, expiresIn = "10m") =>
12 | jwt.sign(payload, process.env.SECRET, { expiresIn });
13 |
14 | /**
15 | * Verify signed token
16 | * @param {string} code - Signed JSON Web Token
17 | * @returns {object} - JSON Web Token
18 | */
19 | export const verifyToken = (code) => jwt.verify(code, process.env.SECRET);
20 |
--------------------------------------------------------------------------------
/packages/endpoint-auth/lib/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Get request parameters from either query string or JSON body
3 | * @param {import("express").Request} request - Request
4 | * @returns {object} - Request parameters
5 | */
6 | export const getRequestParameters = (request) => {
7 | if (Object.entries(request.query).length === 0) {
8 | return request.body;
9 | }
10 |
11 | return request.query;
12 | };
13 |
--------------------------------------------------------------------------------
/packages/endpoint-files/assets/icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/packages/endpoint-files/includes/@indiekit-endpoint-files-widget.njk:
--------------------------------------------------------------------------------
1 | {% if application.mediaEndpoint %}
2 | {% call widget({
3 | title: __("files.upload.title"),
4 | image: "/assets/" + plugin.id + "/icon.svg"
5 | }) %}
6 | {{ button({
7 | classes: "button--secondary-on-offset button--block",
8 | href: application.filesEndpoint + "/upload/",
9 | icon: "uploadFile",
10 | text: __("files.upload.title")
11 | }) }}
12 | {% endcall %}
13 | {% endif %}
--------------------------------------------------------------------------------
/packages/endpoint-files/lib/middleware/validation.js:
--------------------------------------------------------------------------------
1 | import { check } from "express-validator";
2 |
3 | export const validate = [
4 | check("file").custom((value, { req, path }) => {
5 | if (!req.files) throw new Error(req.__(`files.error.${path}.empty`));
6 | return true;
7 | }),
8 | ];
9 |
--------------------------------------------------------------------------------
/packages/endpoint-files/test/unit/utils.js:
--------------------------------------------------------------------------------
1 | import { strict as assert } from "node:assert";
2 | import { describe, it } from "node:test";
3 |
4 | import { getFileName, getFileUrl } from "../../lib/utils.js";
5 |
6 | describe("endpoint-files/lib/utils", () => {
7 | it("Gets file name from a URL", () => {
8 | assert.equal(getFileName("http://foo.bar/baz.jpg"), "baz.jpg");
9 | assert.equal(getFileName("http://foo.bar/bar/qux.mp3"), "qux.mp3");
10 | });
11 |
12 | it("Gets file URL", () => {
13 | assert.equal(
14 | getFileUrl("aHR0cHM6Ly93ZWJzaXRlLmV4YW1wbGUvZm9vYmFy"),
15 | "https://website.example/foobar",
16 | );
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/packages/endpoint-files/views/file-delete.njk:
--------------------------------------------------------------------------------
1 | {% extends "form.njk" %}
2 |
3 | {% block form %}
4 | {{ heading({
5 | text: title,
6 | parent: parent
7 | }) }}
8 |
9 | {{ prose({ text: __("files.delete.note", publication.store.info.name) }) }}
10 |
11 | {{ button({
12 | classes: "button--warning",
13 | text: __("files.delete.submit")
14 | }) }}
15 |
16 | {{ prose({
17 | text: "[" + __("files.delete.cancel") + "](" + filesPath + ")"
18 | }) }}
19 | {% endblock %}
--------------------------------------------------------------------------------
/packages/endpoint-files/views/files.njk:
--------------------------------------------------------------------------------
1 | {% extends "document.njk" %}
2 |
3 | {% block content %}
4 | {%- if files.length > 0 %}
5 | {{ cardGrid({
6 | items: files
7 | }) }}
8 | {{ pagination(cursor) }}
9 | {%- else -%}
10 | {{ prose({ text: __("files.files.none") }) }}
11 | {%- endif %}
12 | {% endblock %}
--------------------------------------------------------------------------------
/packages/endpoint-image/assets/icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/packages/endpoint-json-feed/assets/icon.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/packages/endpoint-json-feed/includes/@indiekit-endpoint-json-feed-widget.njk:
--------------------------------------------------------------------------------
1 | {%- set feedUrl = application.url + application.jsonFeed -%}
2 | {{ widget({
3 | image: "/assets/" + plugin.id + "/icon.svg",
4 | title: "JSON Feed",
5 | text: '' + feedUrl + ""
6 | }) }}
--------------------------------------------------------------------------------
/packages/endpoint-json-feed/lib/controllers/json-feed.js:
--------------------------------------------------------------------------------
1 | import { jsonFeed } from "../json-feed.js";
2 |
3 | export const jsonFeedController = async (request, response) => {
4 | const { application } = request.app.locals;
5 | const feedUrl = new URL(request.originalUrl, application.url).href;
6 |
7 | const postsCollection = application?.collections?.get("posts");
8 | const posts = await postsCollection
9 | .find({
10 | "properties.post-status": {
11 | $ne: "draft",
12 | },
13 | })
14 | .toArray();
15 |
16 | return response
17 | .type("application/feed+json")
18 | .json(jsonFeed(application, feedUrl, posts));
19 | };
20 |
--------------------------------------------------------------------------------
/packages/endpoint-media/assets/icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/packages/endpoint-media/lib/media-transform.js:
--------------------------------------------------------------------------------
1 | import sharp from "sharp";
2 |
3 | /**
4 | * Apply media transformation
5 | * @param {object} imageProcessing - Sharp image processing options
6 | * @param {object} file - File
7 | * @returns {Promise