├── .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 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/public/icons/indieauth.svg: -------------------------------------------------------------------------------- 1 | 2 | IndieAuth 3 | 5 | 6 | -------------------------------------------------------------------------------- /docs/public/icons/micropub.svg: -------------------------------------------------------------------------------- 1 | 2 | Micropub 3 | 5 | 6 | -------------------------------------------------------------------------------- /docs/public/icons/microsub.svg: -------------------------------------------------------------------------------- 1 | 2 | Microsub 3 | 5 | 6 | -------------------------------------------------------------------------------- /docs/public/icons/webmention.svg: -------------------------------------------------------------------------------- 1 | 2 | Webmention 3 | 5 | 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 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/endpoint-json-feed/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 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 | 2 | 3 | 4 | 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} Media file 8 | */ 9 | export const mediaTransform = async (imageProcessing, file) => { 10 | // Function currently only supports transforming images 11 | if (!file.mimetype.includes("image/")) { 12 | return file; 13 | } 14 | 15 | const { resize } = imageProcessing; 16 | 17 | file.data = await sharp(file.data).rotate().resize(resize).toBuffer(); 18 | 19 | return file; 20 | }; 21 | -------------------------------------------------------------------------------- /packages/endpoint-micropub/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/endpoint-micropub/lib/reserved-properties.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Reserved body property names 3 | * @see {@link https://micropub.spec.indieweb.org/#reserved-properties} 4 | */ 5 | export const reservedProperties = Object.freeze([ 6 | "access_token", 7 | "h", 8 | "action", 9 | "url", 10 | ]); 11 | -------------------------------------------------------------------------------- /packages/endpoint-posts/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/endpoint-posts/includes/@indiekit-endpoint-posts-syndicate.njk: -------------------------------------------------------------------------------- 1 |
2 | {{ input({ 3 | name: "access_token", 4 | type: "hidden", 5 | value: token 6 | }) | indent(2) }} 7 | 8 | {{ input({ 9 | name: "syndication[redirect_uri]", 10 | type: "hidden", 11 | value: redirectUri 12 | }) | indent(2) }} 13 | 14 | {{ button({ 15 | classes: "button--secondary button--small", 16 | name: "syndication[source_url]", 17 | value: data.url, 18 | icon: "syndicate", 19 | text: __("posts.post.syndicate") 20 | }) | indent(2) }} 21 |
-------------------------------------------------------------------------------- /packages/endpoint-posts/includes/@indiekit-endpoint-posts-widget.njk: -------------------------------------------------------------------------------- 1 | {% if application.micropubEndpoint %} 2 | {% call widget({ 3 | title: __("posts.create.title", ""), 4 | image: "/assets/" + plugin.id + "/icon.svg" 5 | }) %} 6 |
{% for type, config in publication.postTypes | dictsort %} 7 | {{ button({ 8 | classes: "button--secondary-on-offset button--inline", 9 | href: application.postsEndpoint + "/create/?type=" + config.type, 10 | text: (icon(config.type) or icon("note")) + config.name 11 | }) }} 12 | {% endfor %}
13 | {% endcall %} 14 | {% endif %} -------------------------------------------------------------------------------- /packages/endpoint-posts/includes/post-types/category-field.njk: -------------------------------------------------------------------------------- 1 | {{ tagInput({ 2 | name: "category", 3 | value: fieldData("category").value, 4 | label: __("posts.form.category.label"), 5 | hint: __("posts.form.category.hint"), 6 | optional: not field.required, 7 | localisation: { 8 | tag: __("posts.form.category.tag"), 9 | tags: __("posts.form.category.label") | lower 10 | } 11 | }) }} -------------------------------------------------------------------------------- /packages/endpoint-posts/includes/post-types/category.njk: -------------------------------------------------------------------------------- 1 | {{ tag({ 2 | label: __("posts.form.category.label"), 3 | items: property 4 | }) if property }} -------------------------------------------------------------------------------- /packages/endpoint-posts/includes/post-types/content.njk: -------------------------------------------------------------------------------- 1 | {{ prose({ 2 | html: property.text | markdown 3 | }) if property }} -------------------------------------------------------------------------------- /packages/endpoint-posts/includes/post-types/featured.njk: -------------------------------------------------------------------------------- 1 | {% set html %} 2 |
3 | {{ property[0].alt }} 4 |
5 | {% endset -%} 6 | {{- prose({ 7 | html: html 8 | }) if property }} -------------------------------------------------------------------------------- /packages/endpoint-posts/includes/post-types/geo-field.njk: -------------------------------------------------------------------------------- 1 | {{ geoInput({ 2 | name: "geo", 3 | value: geo, 4 | label: __("posts.form.geo.label"), 5 | hint: __("posts.form.geo.hint", "50.8211, -0.1452"), 6 | optional: not field.required, 7 | errorMessage: fieldData("geo").errorMessage 8 | }) }} -------------------------------------------------------------------------------- /packages/endpoint-posts/includes/post-types/location.njk: -------------------------------------------------------------------------------- 1 | {% set html -%} 2 | {{- icon("location") -}} 3 | {{- property.name + ", " if property.name -}} 4 | {{- property["street-address"] + ", " if property["street-address"] -}} 5 | {{- property.locality + ", " if property.locality -}} 6 | {{- property["country-name"] + ". " if property["country-name"] -}} 7 | {{- property["postal-code"] if property["postal-code"] -}} 8 | {%- endset -%} 9 | {{- prose({ 10 | classes: "prose--caption", 11 | html: html 12 | }) if property }} -------------------------------------------------------------------------------- /packages/endpoint-posts/includes/post-types/mp-channel.njk: -------------------------------------------------------------------------------- 1 | {{ tag({ 2 | label: __("posts.form.mp-channel.label"), 3 | items: publication.channels[property].name 4 | }) if property }} -------------------------------------------------------------------------------- /packages/endpoint-posts/includes/post-types/name-field.njk: -------------------------------------------------------------------------------- 1 | {{ input({ 2 | name: "name", 3 | value: fieldData("name").value, 4 | label: __("posts.form.name.label"), 5 | optional: not field.required, 6 | errorMessage: fieldData("name").errorMessage 7 | }) }} -------------------------------------------------------------------------------- /packages/endpoint-posts/includes/post-types/published.njk: -------------------------------------------------------------------------------- 1 |
2 |
{{ __("posts.form.published.label") }}
3 |
4 |
-------------------------------------------------------------------------------- /packages/endpoint-posts/includes/post-types/summary-field.njk: -------------------------------------------------------------------------------- 1 | {{ textarea({ 2 | name: "summary", 3 | value: properties.summary, 4 | label: __("posts.form.summary.label"), 5 | optional: not field.required, 6 | rows: 1, 7 | field: { 8 | attributes: { 9 | editor: true, 10 | "editor-endpoint": application.mediaEndpoint, 11 | "editor-id": (properties.uid or ("new-" + postType)) + "-summary", 12 | "editor-locale": application.locale, 13 | "editor-status": "false", 14 | "editor-toolbar": "false" 15 | } 16 | } 17 | }) }} -------------------------------------------------------------------------------- /packages/endpoint-posts/includes/post-types/summary.njk: -------------------------------------------------------------------------------- 1 | {{ prose({ 2 | classes: "prose--subhead", 3 | html: property 4 | }) if property }} -------------------------------------------------------------------------------- /packages/endpoint-posts/views/new.njk: -------------------------------------------------------------------------------- 1 | {% extends "form.njk" %} 2 | 3 | {% block fieldset %} 4 | {{ radios({ 5 | name: "type", 6 | values: postType, 7 | items: postTypeItems 8 | }) | indent(2) }} 9 | {% endblock %} 10 | 11 | {% block buttons %} 12 |
13 | {{ button({ 14 | text: __("posts.form.continue") 15 | }) | indent(4) }} 16 | 17 | {{ prose({ 18 | text: "[" + __("posts.form.cancel") + "](" + postsPath + ")" 19 | }) | indent(4) }} 20 |
21 | {% endblock %} -------------------------------------------------------------------------------- /packages/endpoint-posts/views/post-delete.njk: -------------------------------------------------------------------------------- 1 | {% extends "form.njk" %} 2 | 3 | {% block form %} 4 | {{ heading({ 5 | text: title, 6 | parent: parent 7 | }) }} 8 | 9 | {{ button({ 10 | classes: "button--warning" if action === "delete", 11 | text: __("posts." + action + ".submit") 12 | }) }} 13 | 14 | {{ prose({ 15 | text: "[" + __("posts.delete.cancel") + "](" + postsPath + ")" 16 | }) }} 17 | {% endblock %} -------------------------------------------------------------------------------- /packages/endpoint-posts/views/posts.njk: -------------------------------------------------------------------------------- 1 | {% extends "document.njk" %} 2 | 3 | {% block content %} 4 | {%- if posts.length > 0 %} 5 | {{ cardGrid({ 6 | cardSize: "16rem", 7 | items: posts 8 | }) }} 9 | {{ pagination(cursor) }} 10 | {%- else -%} 11 | {{ prose({ text: __("posts.posts.none") }) }} 12 | {%- endif %} 13 | {% endblock %} -------------------------------------------------------------------------------- /packages/endpoint-share/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/endpoint-share/lib/middleware/validation.js: -------------------------------------------------------------------------------- 1 | import { check } from "express-validator"; 2 | 3 | export const validate = [ 4 | check("name") 5 | .notEmpty() 6 | .withMessage((value, { req, path }) => req.__(`share.error.${path}.empty`)), 7 | check("bookmark-of") 8 | .exists() 9 | .isURL() 10 | .withMessage((value, { req, path }) => 11 | req.__(`share.error.${path}.empty`, "https://example.org"), 12 | ), 13 | ]; 14 | -------------------------------------------------------------------------------- /packages/endpoint-share/locales/zh-Hans-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "share": { 3 | "bookmark-of": { 4 | "label": "网址" 5 | }, 6 | "content": { 7 | "label": "内容" 8 | }, 9 | "error": { 10 | "bookmark-of": { 11 | "empty": "输入像 %s 这样的网址" 12 | }, 13 | "name": { 14 | "empty": "输入标题" 15 | } 16 | }, 17 | "name": { 18 | "label": "标题" 19 | }, 20 | "submit": "发布", 21 | "title": "分享" 22 | }, 23 | "status": { 24 | "bookmarklet": { 25 | "guidance": "将此链接拖到您的书签栏:%s", 26 | "label": "分享页面", 27 | "title": "分享书签" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/endpoint-syndicate/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/endpoint-webmention-io/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/endpoint-webmention-io/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "webmention-io": { 3 | "mention": { 4 | "bookmark-of": "mit einem Lesezeichen versehen %s", 5 | "in-reply-to": "antwortete auf %s", 6 | "like-of": "gemocht %s", 7 | "mention-of": "erwähnt %s", 8 | "repost-of": "neu gepostet %s", 9 | "rsvp": "antwortete auf %s" 10 | }, 11 | "title": "Webmentions", 12 | "webmentions": { 13 | "none": "Keine Webmentions" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/endpoint-webmention-io/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "webmention-io": { 3 | "title": "Webmentions", 4 | "webmentions": { 5 | "none": "No webmentions" 6 | }, 7 | "mention": { 8 | "bookmark-of": "bookmarked %s", 9 | "in-reply-to": "replied to %s", 10 | "like-of": "liked %s", 11 | "mention-of": "mentioned %s", 12 | "repost-of": "reposted %s", 13 | "rsvp": "responded to %s" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/endpoint-webmention-io/locales/es-419.json: -------------------------------------------------------------------------------- 1 | { 2 | "webmention-io": { 3 | "mention": { 4 | "bookmark-of": "marcado %s", 5 | "in-reply-to": "respondió a %s", 6 | "like-of": "gustó %s", 7 | "mention-of": "mencionado %s", 8 | "repost-of": "publicado de nuevo %s", 9 | "rsvp": "respondió a %s" 10 | }, 11 | "title": "Webmentions", 12 | "webmentions": { 13 | "none": "Sin webmentions" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/endpoint-webmention-io/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "webmention-io": { 3 | "mention": { 4 | "bookmark-of": "marcado %s", 5 | "in-reply-to": "respondió a %s", 6 | "like-of": "gustó %s", 7 | "mention-of": "mencionado %s", 8 | "repost-of": "publicado de nuevo %s", 9 | "rsvp": "respondió a %s" 10 | }, 11 | "title": "Webmentions", 12 | "webmentions": { 13 | "none": "Sin webmentions" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/endpoint-webmention-io/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "webmention-io": { 3 | "mention": { 4 | "bookmark-of": "mis en signet %s", 5 | "in-reply-to": "a répondu à %s", 6 | "like-of": "aimé %s", 7 | "mention-of": "mentionné %s", 8 | "repost-of": "republié %s", 9 | "rsvp": "a répondu à %s" 10 | }, 11 | "title": "Webmentions", 12 | "webmentions": { 13 | "none": "Pas de webmentions" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/endpoint-webmention-io/locales/hi.json: -------------------------------------------------------------------------------- 1 | { 2 | "webmention-io": { 3 | "mention": { 4 | "bookmark-of": "बुकमार्क किया हुआ %s", 5 | "in-reply-to": "को जवाब दिया %s", 6 | "like-of": "पसंद आया %s", 7 | "mention-of": "उल्लेख किया %s", 8 | "repost-of": "फिर से पोस्ट किया गया %s", 9 | "rsvp": "को जवाब दिया %s" 10 | }, 11 | "title": "Webmentions", 12 | "webmentions": { 13 | "none": "%s webmentions" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/endpoint-webmention-io/locales/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "webmention-io": { 3 | "mention": { 4 | "bookmark-of": "ditandai %s", 5 | "in-reply-to": "menjawab %s", 6 | "like-of": "suka %s", 7 | "mention-of": "disebutkan %s", 8 | "repost-of": "diposting ulang %s", 9 | "rsvp": "menanggapi %s" 10 | }, 11 | "title": "Webmentions", 12 | "webmentions": { 13 | "none": "Tidak ada webmentions" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/endpoint-webmention-io/locales/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "webmention-io": { 3 | "mention": { 4 | "bookmark-of": "aggiunto ai segnalibri %s", 5 | "in-reply-to": "risposto a %s", 6 | "like-of": "preferito %s", 7 | "mention-of": "menzionato %s", 8 | "repost-of": "ripubblicato %s", 9 | "rsvp": "risposto a %s" 10 | }, 11 | "title": "Webmentions", 12 | "webmentions": { 13 | "none": "Nessuna webmentions" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/endpoint-webmention-io/locales/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "webmention-io": { 3 | "mention": { 4 | "bookmark-of": "gemarkeerd als bladwijzer %s", 5 | "in-reply-to": "antwoordde op %s", 6 | "like-of": "vond leuk %s", 7 | "mention-of": "genoemd %s", 8 | "repost-of": "opnieuw gepost %s", 9 | "rsvp": "reageerde op %s" 10 | }, 11 | "title": "Webmentions", 12 | "webmentions": { 13 | "none": "Geen webmentions" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/endpoint-webmention-io/locales/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "webmention-io": { 3 | "mention": { 4 | "bookmark-of": "dodany do zakładek %s", 5 | "in-reply-to": "odpowiedział %s", 6 | "like-of": "podobał się %s", 7 | "mention-of": "wspomniany %s", 8 | "repost-of": "ponownie opublikowane %s", 9 | "rsvp": "odpowiedział na %s" 10 | }, 11 | "title": "Webmentions", 12 | "webmentions": { 13 | "none": "Nie webmentions" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/endpoint-webmention-io/locales/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "webmention-io": { 3 | "mention": { 4 | "bookmark-of": "marcado %s", 5 | "in-reply-to": "respondeu a %s", 6 | "like-of": "gostei %s", 7 | "mention-of": "mencionado %s", 8 | "repost-of": "republicado %s", 9 | "rsvp": "respondeu a %s" 10 | }, 11 | "title": "Webmentions", 12 | "webmentions": { 13 | "none": "Sem webmentions" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/endpoint-webmention-io/locales/sr.json: -------------------------------------------------------------------------------- 1 | { 2 | "webmention-io": { 3 | "mention": { 4 | "bookmark-of": "obeležen %s", 5 | "in-reply-to": "odgovorio na %s", 6 | "like-of": "svidelo se %s", 7 | "mention-of": "pomenuto %s", 8 | "repost-of": "ponovo objavljeno %s", 9 | "rsvp": "odgovorio na %s" 10 | }, 11 | "title": "Webmentions", 12 | "webmentions": { 13 | "none": "Nema webmentions" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/endpoint-webmention-io/locales/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "webmention-io": { 3 | "mention": { 4 | "bookmark-of": "bokmärkt %s", 5 | "in-reply-to": "svarade på %s", 6 | "like-of": "gillade %s", 7 | "mention-of": "nämns %s", 8 | "repost-of": "ompostat %s", 9 | "rsvp": "svarade på %s" 10 | }, 11 | "title": "Webmentions", 12 | "webmentions": { 13 | "none": "Inga webmentions" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/endpoint-webmention-io/locales/zh-Hans-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "webmention-io": { 3 | "mention": { 4 | "bookmark-of": "已加入书签 %s", 5 | "in-reply-to": "回复了 %s", 6 | "like-of": "喜欢 %s", 7 | "mention-of": "提到 %s", 8 | "repost-of": "重新发布了 %s", 9 | "rsvp": "回复了 %s" 10 | }, 11 | "title": "Webmentions", 12 | "webmentions": { 13 | "none": "没有 webmentions" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/error/README.md: -------------------------------------------------------------------------------- 1 | # @indiekit/error 2 | 3 | Error handling for Indiekit. 4 | 5 | ## Installation 6 | 7 | `npm install @indiekit/error` 8 | 9 | > [!NOTE] 10 | > This package is installed alongside `@indiekit/indiekit` 11 | -------------------------------------------------------------------------------- /packages/frontend/README.md: -------------------------------------------------------------------------------- 1 | # @indiekit/frontend 2 | 3 | Frontend components for Indiekit. 4 | 5 | ## Installation 6 | 7 | `npm install @indiekit/frontend` 8 | 9 | > [!NOTE] 10 | > This package is installed alongside `@indiekit/indiekit` 11 | -------------------------------------------------------------------------------- /packages/frontend/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/frontend/assets/not-found.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/frontend/assets/offline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/frontend/assets/plug-in.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/frontend/components/actions/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro actions(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/actions/styles.css: -------------------------------------------------------------------------------- 1 | .actions { 2 | --anchor-decoration-color: transparent; 3 | --icon-margin: var(--space-2xs); 4 | display: flex; 5 | flex-wrap: wrap; 6 | font: var(--font-body); 7 | gap: var(--space-2xs) var(--space-l); 8 | } 9 | 10 | .actions__link { 11 | margin: calc(var(--space-s) * -1); 12 | padding: var(--space-s); 13 | white-space: nowrap; 14 | } 15 | 16 | .actions__link--warning { 17 | --anchor-color: var(--color-error); 18 | --anchor-color-hover: var(--color-error-variant); 19 | } 20 | -------------------------------------------------------------------------------- /packages/frontend/components/add-another/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro addAnother(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/authorize/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro authorize(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/avatar/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro avatar(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/avatar/template.njk: -------------------------------------------------------------------------------- 1 | {{ opts.alt }} -------------------------------------------------------------------------------- /packages/frontend/components/back-link/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro backLink(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/back-link/template.njk: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/frontend/components/badge/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro badge(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/badge/template.njk: -------------------------------------------------------------------------------- 1 | 5 | {{- icon(opts.icon) if opts.icon -}} 6 | {{- opts.text | safe -}} 7 | -------------------------------------------------------------------------------- /packages/frontend/components/bookmarklet/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro bookmarklet(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/bookmarklet/styles.css: -------------------------------------------------------------------------------- 1 | .bookmarklet { 2 | --anchor-color: var(--color-on-offset); 3 | --anchor-decoration-line: none; 4 | background-color: var(--color-offset); 5 | border: var(--border-width-thin) solid var(--color-shadow); 6 | border-radius: var(--border-radius-small); 7 | display: inline-block; 8 | font: var(--font-caption); 9 | margin-block: var(--space-xs); 10 | padding-block: calc(var(--space-xs) / 2); 11 | padding-inline: var(--space-xs); 12 | white-space: nowrap; 13 | 14 | &:hover { 15 | background-color: var(--color-offset-variant); 16 | border-color: currentcolor; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/frontend/components/bookmarklet/template.njk: -------------------------------------------------------------------------------- 1 | {{ opts.text }} -------------------------------------------------------------------------------- /packages/frontend/components/button/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro button(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/card-grid/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro cardGrid(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/card-grid/styles.css: -------------------------------------------------------------------------------- 1 | .card-grid { 2 | display: grid; 3 | grid-gap: var(--space-l); 4 | grid-template-columns: repeat(auto-fill, minmax(var(--min-card-size), 1fr)); 5 | } 6 | 7 | .card-grid__item { 8 | display: flex; 9 | } 10 | -------------------------------------------------------------------------------- /packages/frontend/components/card-grid/template.njk: -------------------------------------------------------------------------------- 1 | {% from "../card/macro.njk" import card with context %} 2 |
    3 | {% for item in opts.items %} 4 |
  1. 5 | {{ card(item) | indent(4) }} 6 |
  2. 7 | {% endfor %} 8 |
-------------------------------------------------------------------------------- /packages/frontend/components/card/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro card(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/character-count/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro characterCount(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/character-count/styles.css: -------------------------------------------------------------------------------- 1 | character-count { 2 | display: block; 3 | } 4 | -------------------------------------------------------------------------------- /packages/frontend/components/character-count/template.njk: -------------------------------------------------------------------------------- 1 | {% from "../hint/macro.njk" import hint with context %} 2 | {% from "../textarea/macro.njk" import textarea with context %} 3 | {%- set id = opts.id or opts.name | slugify({ decamelize: true }) -%} 4 | 9 | {{- textarea(opts) }} 10 | {{ hint({ 11 | text: " ", 12 | id: id + "-info" 13 | }) | trim | indent(2) }} 14 | -------------------------------------------------------------------------------- /packages/frontend/components/checkboxes/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro checkboxes(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/details/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro details(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/details/template.njk: -------------------------------------------------------------------------------- 1 | {% from "../prose/macro.njk" import prose with context %} 2 |
6 | 7 | {{ opts.summary | safe }} 8 | 9 |
10 | {{ caller() if caller else prose({ text: opts.text }) }} 11 |
12 |
-------------------------------------------------------------------------------- /packages/frontend/components/error-message/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro errorMessage(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/error-message/styles.css: -------------------------------------------------------------------------------- 1 | .error-message { 2 | color: var(--color-error); 3 | font: var(--font-caption); 4 | font-weight: 600; 5 | } 6 | -------------------------------------------------------------------------------- /packages/frontend/components/error-message/template.njk: -------------------------------------------------------------------------------- 1 |

3 | 4 | {{- opts.visuallyHiddenText | default(__("error")) + ":" -}} 5 | 6 | 7 | {{- opts.text | safe -}} 8 | 9 |

-------------------------------------------------------------------------------- /packages/frontend/components/error-summary/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro errorSummary(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/event-duration/styles.css: -------------------------------------------------------------------------------- 1 | event-duration { 2 | --fieldset-flow-space: var(--space-s); 3 | align-items: end; 4 | column-gap: var(--space-xl); 5 | display: flex; 6 | flex-wrap: wrap; 7 | } 8 | -------------------------------------------------------------------------------- /packages/frontend/components/field/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro field(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/field/styles.css: -------------------------------------------------------------------------------- 1 | .field { 2 | & > * { 3 | --fieldset-flow-space: var(--space-xs); 4 | } 5 | 6 | &:has(.input[type="hidden"]) { 7 | display: none; 8 | } 9 | } 10 | 11 | .field--error { 12 | border-inline-start: var(--color-error) solid var(--border-width-thickest); 13 | padding-inline-start: var(--space-m); 14 | } 15 | -------------------------------------------------------------------------------- /packages/frontend/components/field/template.njk: -------------------------------------------------------------------------------- 1 | {%- set element = opts.element or "div" %} 2 | <{{element}} class="{{ classes("field", opts) }}"{{ attributes(opts.attributes) }}> 3 | {{ caller() if caller }} 4 | -------------------------------------------------------------------------------- /packages/frontend/components/fieldset/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro fieldset(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/fieldset/template.njk: -------------------------------------------------------------------------------- 1 | {% from "../heading/macro.njk" import heading with context %} 2 | {% set legend %} 3 | 4 | {{ opts.legend }}{{ " " + __("optionalValue") if opts.optional }} 5 | 6 | {%- endset %} 7 |
11 | {{ legend | indent(2) | safe if opts.legend }} 12 | {{ caller() if caller }} 13 |
-------------------------------------------------------------------------------- /packages/frontend/components/file-input/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro fileInput(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/file-input/styles.css: -------------------------------------------------------------------------------- 1 | file-input-field { 2 | display: block; 3 | } 4 | -------------------------------------------------------------------------------- /packages/frontend/components/footer/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro footer(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/footer/styles.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | --anchor-color: var(--color-on-offset); 3 | --anchor-color-hover: var(--color-primary-on-background); 4 | --anchor-decoration-color: transparent; 5 | background-color: var(--color-offset); 6 | border-block-start: var(--border-hairline); 7 | color: var(--color-on-offset); 8 | display: flex; 9 | font: var(--font-caption); 10 | justify-content: flex-end; 11 | } 12 | 13 | .footer__container { 14 | align-items: center; 15 | display: flex; 16 | justify-content: space-between; 17 | padding-block: var(--space-m); 18 | } 19 | -------------------------------------------------------------------------------- /packages/frontend/components/footer/template.njk: -------------------------------------------------------------------------------- 1 | {% from "../logo/macro.njk" import logo with context %} 2 | {% from "../navigation/macro.njk" import navigation with context %} 3 |
4 | 8 |
-------------------------------------------------------------------------------- /packages/frontend/components/geo-input/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro geoInput(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/geo-input/styles.css: -------------------------------------------------------------------------------- 1 | geo-input-field { 2 | display: block; 3 | } 4 | -------------------------------------------------------------------------------- /packages/frontend/components/header/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro header(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/header/template.njk: -------------------------------------------------------------------------------- 1 | {% from "../navigation/macro.njk" import navigation with context %} 2 |
3 |
4 | 9 | {{ navigation(opts.navigation) | indent(4) if opts.navigation.items.length > 0 }} 10 |
11 |
-------------------------------------------------------------------------------- /packages/frontend/components/heading/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro heading(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/heading/styles.css: -------------------------------------------------------------------------------- 1 | .heading { 2 | display: flex; 3 | flex-direction: column; 4 | gap: var(--space-s); 5 | } 6 | 7 | .heading__photo { 8 | --icon-size: 5em; 9 | --icon-margin: 0; 10 | } 11 | 12 | .heading__title { 13 | font: var(--font-title); 14 | max-inline-size: var(--line-measure); 15 | text-wrap: balance; 16 | } 17 | 18 | .heading__parent { 19 | --anchor-decoration-color: var(--color-shadow); 20 | color: var(--color-on-offset); 21 | display: block; 22 | font: var(--font-body); 23 | max-inline-size: max-content; 24 | } 25 | -------------------------------------------------------------------------------- /packages/frontend/components/hint/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro hint(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/hint/styles.css: -------------------------------------------------------------------------------- 1 | .hint { 2 | color: var(--color-on-offset); 3 | font: var(--font-caption); 4 | margin-block-start: var(--space-2xs); 5 | overflow-wrap: anywhere; 6 | } 7 | 8 | .button + .hint { 9 | margin-block-start: var(--space-s); 10 | } 11 | -------------------------------------------------------------------------------- /packages/frontend/components/hint/template.njk: -------------------------------------------------------------------------------- 1 |

2 | {{- opts.text | safe -}} 3 |

-------------------------------------------------------------------------------- /packages/frontend/components/icon/styles.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | block-size: var(--icon-size, 1em); 3 | display: inline-block; 4 | flex-shrink: 0; 5 | inline-size: var(--icon-size, 1em); 6 | inset-block-start: -0.0625em; 7 | margin-inline-end: var(--icon-margin, var(--space-xs)); 8 | position: relative; 9 | vertical-align: middle; 10 | } 11 | 12 | .icon--rounded { 13 | border-radius: var(--border-radius-small); 14 | } 15 | -------------------------------------------------------------------------------- /packages/frontend/components/input/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro input(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/label/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro label(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/label/styles.css: -------------------------------------------------------------------------------- 1 | .label { 2 | display: block; 3 | font: var(--label-font, var(--font-label)); 4 | } 5 | -------------------------------------------------------------------------------- /packages/frontend/components/label/template.njk: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/frontend/components/logo/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro logo(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/logo/styles.css: -------------------------------------------------------------------------------- 1 | .logo { 2 | display: inline flow-root; 3 | padding: var(--space-s); 4 | 5 | & img { 6 | block-size: var(--icon-size, 1em); 7 | inline-size: var(--icon-size, 1em); 8 | opacity: 0.25; 9 | } 10 | 11 | &:hover img { 12 | opacity: 0.5; 13 | } 14 | } 15 | 16 | @media (prefers-color-scheme: dark) { 17 | :not([data-color-scheme]) .logo { 18 | filter: invert(100%); 19 | } 20 | } 21 | 22 | [data-color-scheme="dark"] .logo { 23 | filter: invert(100%); 24 | } 25 | -------------------------------------------------------------------------------- /packages/frontend/components/logo/template.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/frontend/components/main/styles.css: -------------------------------------------------------------------------------- 1 | .main { 2 | --anchor-color: var(--color-primary-on-background); 3 | background-color: var(--color-background); 4 | color: var(--color-on-background); 5 | flex: 1; 6 | } 7 | 8 | .main__container { 9 | padding-block: var(--space-xl); 10 | 11 | & > * + * { 12 | margin-block-start: var(--space-xl); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/frontend/components/mention/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro mention(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/mention/styles.css: -------------------------------------------------------------------------------- 1 | .mention { 2 | display: grid; 3 | gap: var(--space-s); 4 | max-inline-size: var(--line-measure); 5 | 6 | + .mention { 7 | margin-block-start: var(--space-2xl); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/frontend/components/mention/template.njk: -------------------------------------------------------------------------------- 1 | {% from "../card/macro.njk" import card with context %} 2 | {% from "../user/macro.njk" import user with context %} 3 |
4 | {{ user(opts.user) }} 5 | {{ card(opts.mention) }} 6 |
-------------------------------------------------------------------------------- /packages/frontend/components/navigation/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro navigation(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/navigation/styles.css: -------------------------------------------------------------------------------- 1 | .navigation__list { 2 | margin-inline: calc(var(--space-s) * -1); 3 | } 4 | 5 | .navigation__list-item { 6 | display: inline-flex; 7 | padding-block: var(--navigation-item-padding-block, 0); 8 | padding-inline: var(--navigation-item-padding-inline, 0); 9 | 10 | & a { 11 | align-items: center; 12 | padding: var(--space-s); 13 | } 14 | 15 | &:has(a[aria-current="true"]) { 16 | box-shadow: inset 0 calc(var(--border-width-thickest) * -1) 0 0 17 | var(--navigation-item-current-color); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/frontend/components/navigation/template.njk: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/frontend/components/notification-banner/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro notificationBanner(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/pagination/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro pagination(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/progress/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro progress(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/progress/template.njk: -------------------------------------------------------------------------------- 1 | {% from "../label/macro.njk" import label with context %} 2 |
3 | {{- label({ 4 | for: opts.id, 5 | text: opts.label 6 | }) if opts.label }} 7 | 8 | {{- caller() if caller -}} 9 | 10 |
11 | -------------------------------------------------------------------------------- /packages/frontend/components/prose/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro prose(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/prose/styles.css: -------------------------------------------------------------------------------- 1 | .prose { 2 | font: var(--prose-font, var(--font-body)); 3 | } 4 | 5 | .prose--caption { 6 | --prose-font: var(--font-caption); 7 | color: var(--color-on-offset); 8 | } 9 | 10 | .prose--subhead { 11 | --prose-font: var(--font-subhead); 12 | } 13 | -------------------------------------------------------------------------------- /packages/frontend/components/prose/template.njk: -------------------------------------------------------------------------------- 1 |
2 | {{ opts.text | markdown | trim | safe if opts.text else opts.html | safe }} 3 |
-------------------------------------------------------------------------------- /packages/frontend/components/radios/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro radios(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/section/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro section(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/section/styles.css: -------------------------------------------------------------------------------- 1 | .section__header { 2 | align-items: baseline; 3 | display: flex; 4 | flex-wrap: wrap; 5 | gap: var(--space-s); 6 | justify-content: space-between; 7 | padding-block-end: var(--space-s); 8 | } 9 | 10 | .section__title { 11 | font: var(--font-heading); 12 | } 13 | -------------------------------------------------------------------------------- /packages/frontend/components/select/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro select(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/share-preview/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro sharePreview(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/skip-link/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro skipLink(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/skip-link/styles.css: -------------------------------------------------------------------------------- 1 | .skip-link { 2 | background-color: var(--color-focus); 3 | display: block; 4 | inline-size: 100%; 5 | padding: var(--space-s); 6 | position: static; 7 | 8 | &:not(:focus-visible) { 9 | transform: translateY(-100%); 10 | transition: transform 0.5s ease-out; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/frontend/components/skip-link/template.njk: -------------------------------------------------------------------------------- 1 | 2 | {{- opts.text | default(__("skipLink.text")) | safe -}} 3 | -------------------------------------------------------------------------------- /packages/frontend/components/summary/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro summary(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/tag-input/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro tagInput(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/tag/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro tag(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/tag/styles.css: -------------------------------------------------------------------------------- 1 | .tag:not(.token) { 2 | align-items: center; 3 | background-color: var(--color-offset); 4 | border-radius: var(--border-radius-small); 5 | color: var(--color-on-offset); 6 | display: inline-flex; 7 | font: var(--font-caption); 8 | padding-block: var(--space-2xs); 9 | padding-inline: var(--space-s); 10 | 11 | & + .tag { 12 | margin-inline-start: var(--space-xs); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/frontend/components/tag/template.njk: -------------------------------------------------------------------------------- 1 | {%- set tags = opts.items if (opts.items | isArray) else [opts.items] -%} 2 |
3 |
{{ opts.label }}
4 | {% for tag in tags %}
{{ tag }}
{% endfor %} 5 |
-------------------------------------------------------------------------------- /packages/frontend/components/textarea/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro textarea(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/user/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro user(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/user/styles.css: -------------------------------------------------------------------------------- 1 | .user { 2 | align-items: center; 3 | display: flex; 4 | gap: var(--space-xs); 5 | } 6 | 7 | .user__body { 8 | display: flex; 9 | flex-direction: column; 10 | gap: var(--space-2xs); 11 | } 12 | 13 | .user__name { 14 | font: var(--user-name-font, var(--font-label)); 15 | } 16 | 17 | .user__meta { 18 | color: var(--color-on-offset); 19 | font: var(--font-caption); 20 | } 21 | -------------------------------------------------------------------------------- /packages/frontend/components/user/template.njk: -------------------------------------------------------------------------------- 1 | {% from "../avatar/macro.njk" import avatar with context %} 2 |
3 | {{ avatar(opts.avatar) if opts.avatar }} 4 | 5 |
6 | {% if opts.url %} 7 | {{- opts.name | safe -}} 8 | {% else %} 9 | {{- opts.name | safe -}} 10 | {% endif %} 11 | {% if opts.meta %} 12 | {{ opts.meta | safe }} 13 | {% endif %} 14 |
15 |
-------------------------------------------------------------------------------- /packages/frontend/components/warning-text/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro warningText(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/components/warning-text/styles.css: -------------------------------------------------------------------------------- 1 | .warning-text { 2 | --icon-margin: var(--space-s); 3 | --icon-size: 1.5em; 4 | align-items: start; 5 | background-color: var(--color-offset); 6 | border-radius: var(--border-radius-small); 7 | display: flex; 8 | flex-direction: row; 9 | font: var(--font-label); 10 | padding: var(--space-m); 11 | 12 | & .warning-text__text { 13 | margin-block-start: var(--space-2xs); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/frontend/components/warning-text/template.njk: -------------------------------------------------------------------------------- 1 |
3 | {{ icon(opts.icon | default("warning")) }} 4 | 5 | {% if not opts.iconFallbackText %}{{ opts.iconFallbackText | default(__("warning")) }}{% endif %} 6 | {{ opts.text | safe }} 7 | 8 |
-------------------------------------------------------------------------------- /packages/frontend/components/widget/macro.njk: -------------------------------------------------------------------------------- 1 | {% macro widget(opts) %} 2 | {%- include "./template.njk" -%} 3 | {% endmacro %} -------------------------------------------------------------------------------- /packages/frontend/index.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from "node:url"; 2 | 3 | export const assetsPath = fileURLToPath(new URL("assets", import.meta.url)); 4 | export { appIcon, shortcutIcon } from "./lib/sharp.js"; 5 | export { templates } from "./lib/nunjucks.js"; 6 | export { scripts } from "./lib/esbuild.js"; 7 | export { styles } from "./lib/lightningcss.js"; 8 | export { getBackgroundColor, getThemeColor } from "./lib/utils/theme.js"; 9 | 10 | export { tagInputSanitizer } from "./components/tag-input/sanitizer.js"; 11 | -------------------------------------------------------------------------------- /packages/frontend/layouts/document.njk: -------------------------------------------------------------------------------- 1 | {% extends "default.njk" %} 2 | 3 | {% block main %} 4 |
5 | {{ heading({ 6 | photo: photo, 7 | parent: parent, 8 | text: title, 9 | actions: actions 10 | }) if title }} 11 | 12 | {% block content %} 13 | {% endblock %} 14 |
15 | {% endblock %} -------------------------------------------------------------------------------- /packages/frontend/layouts/error.njk: -------------------------------------------------------------------------------- 1 | {% extends "document.njk" %} 2 | 3 | {% set appClasses = "app--minimalui" %} 4 | 5 | {% block header %} 6 | {{ header({ 7 | url: application.url, 8 | name: application.name 9 | }) }} 10 | {% endblock %} 11 | 12 | {% block content %} 13 | {{ prose({ text: content | linkTo(uri) }) }} 14 | 15 | {{ details({ 16 | summary: "Status code: " + status + "", 17 | text: "```\n" + stack + "\n```" 18 | }) }} 19 | {% endblock %} -------------------------------------------------------------------------------- /packages/frontend/layouts/form.njk: -------------------------------------------------------------------------------- 1 | {% extends "default.njk" %} 2 | 3 | {% block main %} 4 |
5 | {{ errorSummary({ 6 | errorList: errorList(errors) 7 | }) if errors }} 8 | 9 | {% block form %} 10 | {% call fieldset({ 11 | attributes: { "aria-label": title } if minimalui, 12 | legend: heading({ text: title, parent: parent }) if not minimalui 13 | }) -%} 14 | {% block fieldset %} 15 | {% endblock %} 16 | {% endcall %} 17 | {% endblock %} 18 | 19 | {% block buttons %} 20 | {% endblock %} 21 |
22 | {% endblock %} -------------------------------------------------------------------------------- /packages/frontend/lib/esbuild.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from "node:url"; 2 | 3 | import * as esbuild from "esbuild"; 4 | 5 | export const scripts = async () => { 6 | const inputFile = fileURLToPath( 7 | new URL("../scripts/app.js", import.meta.url), 8 | ); 9 | 10 | const result = await esbuild.build({ 11 | entryPoints: [inputFile], 12 | bundle: true, 13 | legalComments: "none", 14 | minify: true, 15 | write: false, 16 | }); 17 | 18 | const { contents } = result.outputFiles[0]; 19 | const code = new TextDecoder().decode(contents); 20 | 21 | return code; 22 | }; 23 | -------------------------------------------------------------------------------- /packages/frontend/lib/filters/array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if value is an array 3 | * @param {*} value - Value to check 4 | * @returns {boolean} Value is an array 5 | */ 6 | export const isArray = (value) => Array.isArray(value); 7 | -------------------------------------------------------------------------------- /packages/frontend/lib/filters/index.js: -------------------------------------------------------------------------------- 1 | export { 2 | excerpt, 3 | formatDate as date, 4 | formatZonedToLocalDate as localDate, 5 | getCanonicalUrl as url, 6 | slugify, 7 | } from "@indiekit/util"; 8 | export { isArray } from "./array.js"; 9 | export { languageName, languageNativeName } from "./locale.js"; 10 | export { includes, linkTo, markdown } from "./string.js"; 11 | export { friendlyUrl, imageUrl } from "./url.js"; 12 | -------------------------------------------------------------------------------- /packages/frontend/lib/filters/locale.js: -------------------------------------------------------------------------------- 1 | import languages from "iso-639-1"; 2 | 3 | /** 4 | * Get a language name 5 | * @param {string} string - ISO 639-1 language code 6 | * @returns {string} Native language name 7 | * @example language('de') => Deutsch 8 | */ 9 | export const languageName = (string) => languages.getName(string); 10 | 11 | /** 12 | * Get a language’s native name 13 | * @param {string} string - ISO 639-1 language code 14 | * @returns {string} Native language name 15 | * @example language('de') => Deutsch 16 | */ 17 | export const languageNativeName = (string) => languages.getNativeName(string); 18 | -------------------------------------------------------------------------------- /packages/frontend/lib/globals/classes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate space-separated list of class names 3 | * @param {string} baseClass - Base class name 4 | * @param {object} [options] - Component options 5 | * @returns {string} Space-separated list of class names 6 | */ 7 | export const classes = (baseClass, options = {}) => { 8 | const classes = [baseClass]; 9 | 10 | if (options.errorMessage) { 11 | classes.push(`${baseClass}--error`); 12 | } 13 | 14 | if (options.classes) { 15 | classes.push(options.classes); 16 | } 17 | 18 | return classes.join(" "); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/frontend/lib/globals/field-data.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | 3 | /** 4 | * Get field data 5 | * @param {string} key - Key name 6 | * @returns {object} Field data 7 | */ 8 | export function fieldData(key) { 9 | const { errors, properties } = this.ctx; 10 | const errorData = _.get(errors, key); 11 | 12 | return { 13 | value: errorData ? errorData?.value : _.get(properties, key), 14 | ...(errorData && { 15 | errorMessage: { 16 | text: errorData?.msg, 17 | }, 18 | }), 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /packages/frontend/lib/globals/index.js: -------------------------------------------------------------------------------- 1 | export { attributes } from "./attributes.js"; 2 | export { classes } from "./classes.js"; 3 | export { errorList } from "./error-list.js"; 4 | export { fieldData } from "./field-data.js"; 5 | export { icon } from "./icon.js"; 6 | export { itemId } from "./item-id.js"; 7 | export { summaryRows } from "./summary-rows.js"; 8 | export { 9 | getBackgroundColor as backgroundColor, 10 | getThemeColor as themeColor, 11 | getThemeCustomProperties as themeCustomProperties, 12 | } from "../utils/theme.js"; 13 | -------------------------------------------------------------------------------- /packages/frontend/lib/globals/item-id.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get item `id` for radio/checkbox 3 | * @param {string} id - Item `id` 4 | * @param {string} idPrefix - Prefix for each item `id` if no `id` specified 5 | * @param {object} loop - Nunjucks for loop object 6 | * @returns {string} Item `id` 7 | */ 8 | export const itemId = (id, idPrefix, loop) => { 9 | // If user explicitly sets `id`, use this instead of `idPrefix` 10 | if (id) { 11 | return id; 12 | } 13 | 14 | // The first `id` should not have a number suffix so 15 | // easier to link to from the error summary component 16 | return loop.first ? idPrefix : `${idPrefix}-${loop.index}`; 17 | }; 18 | -------------------------------------------------------------------------------- /packages/frontend/lib/utils/wrap-element.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrap element with another 3 | * @param {HTMLElement} $element - Element to wrap 4 | * @param {HTMLElement} $wrapper - Element to wrap with 5 | */ 6 | export const wrapElement = ($element, $wrapper) => { 7 | if ($element?.parentNode) { 8 | $element.parentNode.insertBefore($wrapper, $element); 9 | $wrapper.append($element); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /packages/frontend/styles/base/embedded.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Embedded content 3 | * ================ 4 | * https://html.spec.whatwg.org/#embedded-content 5 | */ 6 | 7 | svg { 8 | display: block; 9 | } 10 | 11 | iframe, 12 | img, 13 | embed, 14 | object { 15 | block-size: auto; 16 | display: block; 17 | max-inline-size: 100%; 18 | page-break-inside: avoid; 19 | } 20 | 21 | @media (prefers-color-scheme: dark) { 22 | :not([data-color-scheme]) img { 23 | filter: brightness(0.8) contrast(1.2); 24 | } 25 | } 26 | 27 | [data-color-scheme="dark"] img { 28 | filter: brightness(0.8) contrast(1.2); 29 | } 30 | -------------------------------------------------------------------------------- /packages/frontend/styles/base/interactive.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Interactive content 3 | * =================== 4 | * https://html.spec.whatwg.org/#interactive-elements 5 | */ 6 | 7 | details { 8 | summary { 9 | display: block; 10 | } 11 | 12 | summary::before { 13 | content: "⏵\00A0"; 14 | } 15 | 16 | &[open] summary::before { 17 | content: "⏷\00A0"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/frontend/styles/base/tables.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Tabular data 3 | * ============= 4 | * https://html.spec.whatwg.org/#tables 5 | */ 6 | 7 | table { 8 | border-spacing: 0; 9 | break-inside: avoid; 10 | font-size: inherit; 11 | font-variant-numeric: tabular-nums; 12 | } 13 | 14 | tr { 15 | break-inside: avoid; 16 | } 17 | 18 | td, 19 | th { 20 | text-align: start; 21 | vertical-align: top; 22 | } 23 | 24 | thead th { 25 | font-weight: 600; 26 | } 27 | 28 | tbody th { 29 | font-weight: normal; 30 | } 31 | -------------------------------------------------------------------------------- /packages/frontend/styles/utilities/container.css: -------------------------------------------------------------------------------- 1 | .-\!-container { 2 | inline-size: var(--container-inline-size); 3 | margin-inline: auto; 4 | max-inline-size: var(--container-max-inline-size); 5 | padding-inline: var(--container-padding-inline); 6 | } 7 | -------------------------------------------------------------------------------- /packages/frontend/styles/utilities/visually-hidden.css: -------------------------------------------------------------------------------- 1 | .-\!-visually-hidden:not(:focus) { 2 | block-size: 1px; 3 | border: 0; 4 | clip: rect(0 0 0 0); 5 | inline-size: 1px; 6 | margin: -1px; 7 | overflow: hidden; 8 | padding: 0; 9 | position: absolute; 10 | } 11 | -------------------------------------------------------------------------------- /packages/frontend/test/unit/components/tag-input.js: -------------------------------------------------------------------------------- 1 | import { strict as assert } from "node:assert"; 2 | import { describe, it } from "node:test"; 3 | 4 | import { tagInputSanitizer } from "../../../components/tag-input/sanitizer.js"; 5 | 6 | describe("frontend/components/tag-input/sanitizer", () => { 7 | it("Excerpts a string", () => { 8 | assert.deepEqual(tagInputSanitizer.customSanitizer("foo, bar, bar,"), [ 9 | "foo", 10 | "bar", 11 | ]); 12 | assert.deepEqual(tagInputSanitizer.customSanitizer(`["foo","bar"]`), [ 13 | "foo", 14 | "bar", 15 | ]); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/frontend/test/unit/filters/array.js: -------------------------------------------------------------------------------- 1 | import { strict as assert } from "node:assert"; 2 | import { describe, it } from "node:test"; 3 | 4 | import { isArray } from "../../../lib/filters/index.js"; 5 | 6 | describe("frontend/lib/filters/array", () => { 7 | it("Excerpts a string", () => { 8 | assert.equal(isArray(["foo", "bar"]), true); 9 | assert.equal(isArray("foo bar"), false); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/frontend/test/unit/filters/locale.js: -------------------------------------------------------------------------------- 1 | import { strict as assert } from "node:assert"; 2 | import { describe, it } from "node:test"; 3 | 4 | import { 5 | languageName, 6 | languageNativeName, 7 | } from "../../../lib/filters/index.js"; 8 | 9 | describe("frontend/lib/filters/locale", () => { 10 | it("Gets a language name", () => { 11 | assert.equal(languageName("fr"), "French"); 12 | }); 13 | 14 | it("Gets a language’s native name", () => { 15 | assert.equal(languageNativeName("fr"), "Français"); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/frontend/test/unit/globals/attributes.js: -------------------------------------------------------------------------------- 1 | import { strict as assert } from "node:assert"; 2 | import { describe, it } from "node:test"; 3 | 4 | import { attributes } from "../../../lib/globals/index.js"; 5 | 6 | describe("frontend/lib/globals/attributes", () => { 7 | it("Generates space-separated list of HTML attribute key values", () => { 8 | const result = attributes({ 9 | id: "foo", 10 | "data-value": "bar", 11 | readonly: true, 12 | class: "", 13 | }); 14 | 15 | assert.equal(result, ' id="foo" data-value="bar" readonly'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/indiekit/README.md: -------------------------------------------------------------------------------- 1 | # @indiekit/indiekit 2 | 3 | The little server that connects your website to the independent web. 4 | 5 | ## Installation 6 | 7 | `npm install @indiekit/indiekit` 8 | -------------------------------------------------------------------------------- /packages/indiekit/config/locales.js: -------------------------------------------------------------------------------- 1 | export const locales = new Set([ 2 | "de", 3 | "en", 4 | "es", 5 | "es-419", 6 | "fr", 7 | "hi", 8 | "id", 9 | "it", 10 | "nl", 11 | "pl", 12 | "pt", 13 | "sr", 14 | "sv", 15 | "zh-Hans-CN", 16 | ]); 17 | -------------------------------------------------------------------------------- /packages/indiekit/lib/controllers/client.js: -------------------------------------------------------------------------------- 1 | export const get = async (request, response) => { 2 | const { name: client_name, url: client_uri } = request.app.locals.application; 3 | const { href: client_id } = new URL("id", client_uri); 4 | const { href: logo_uri } = new URL("assets/app-icon-512-any.png", client_uri); 5 | 6 | response.set("Cache-Control", "public, max-age=604800"); // 7 days 7 | return response.type("application/json").json({ 8 | client_id, 9 | client_name, 10 | client_uri, 11 | logo_uri, 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/indiekit/lib/controllers/homepage.js: -------------------------------------------------------------------------------- 1 | export const viewHomepage = (request, response) => { 2 | response.render("homepage", { 3 | title: response.locals.__("homepage.title"), 4 | }); 5 | }; 6 | -------------------------------------------------------------------------------- /packages/indiekit/lib/controllers/offline.js: -------------------------------------------------------------------------------- 1 | import { getServiceWorker } from "../utils.js"; 2 | 3 | export const offline = (request, response) => { 4 | response.render("offline", { 5 | title: response.locals.__("offline.title"), 6 | }); 7 | }; 8 | 9 | export const serviceworker = async (request, response) => { 10 | const { application } = request.app.locals; 11 | const serviceworker = await getServiceWorker(application); 12 | 13 | return response.type("text/javascript").send(serviceworker).end(); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/indiekit/lib/controllers/session.js: -------------------------------------------------------------------------------- 1 | export const login = (request, response) => { 2 | if (request.session.access_token) { 3 | return response.redirect("/"); 4 | } 5 | 6 | return response.render("session/login", { 7 | title: response.locals.__("session.login.title"), 8 | referrer: request.query.referrer, 9 | }); 10 | }; 11 | 12 | export const logout = (request, response) => { 13 | request.session = undefined; 14 | return response.redirect("/"); 15 | }; 16 | -------------------------------------------------------------------------------- /packages/indiekit/lib/controllers/status.js: -------------------------------------------------------------------------------- 1 | export const viewStatus = (request, response) => { 2 | const { scope } = request.app.locals; 3 | 4 | response.render("status", { 5 | title: response.locals.__("status.title"), 6 | scope: scope?.split(" "), 7 | actions: [ 8 | { 9 | text: response.locals.__("status.application.installedPlugins"), 10 | href: "/plugins/", 11 | }, 12 | ], 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/indiekit/lib/middleware/force-https.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Redirect HTTP requests to HTTPS (typically on Heroku) 3 | * @type {import("express").RequestHandler} 4 | */ 5 | export function forceHttps(request, response, next) { 6 | const protocol = request.headers["x-forwarded-proto"]; 7 | 8 | if (protocol && protocol !== "https") { 9 | console.info("Redirecting request to https"); 10 | // 302 temporary. This is a feature that can be disabled 11 | return response.redirect( 12 | 302, 13 | "https://" + request.get("Host") + request.originalUrl, 14 | ); 15 | } 16 | 17 | next(); 18 | } 19 | -------------------------------------------------------------------------------- /packages/indiekit/lib/middleware/logging.js: -------------------------------------------------------------------------------- 1 | import makeDebug from "debug"; 2 | 3 | const debug = makeDebug("indiekit:request"); 4 | 5 | export const logging = (request, response, next) => { 6 | // Send debug logging output to console.info 7 | debug.log = console.info.bind(console); 8 | 9 | debug("URL", request.method, request.originalUrl); 10 | debug("Headers", request.headers); 11 | debug("Body", request.body); 12 | 13 | next(); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/indiekit/test/integration/200-offline-view.js: -------------------------------------------------------------------------------- 1 | import { strict as assert } from "node:assert"; 2 | import { after, describe, it } from "node:test"; 3 | 4 | import { testServer } from "@indiekit-test/server"; 5 | import supertest from "supertest"; 6 | 7 | const server = await testServer(); 8 | const request = supertest.agent(server); 9 | 10 | describe("indiekit GET /offline", () => { 11 | it("Displays offline page", async () => { 12 | const result = await request.get("/offline"); 13 | 14 | assert.equal(result.status, 200); 15 | assert.equal(result.type, "text/html"); 16 | }); 17 | 18 | after(() => server.close()); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/indiekit/test/integration/200-robots.js: -------------------------------------------------------------------------------- 1 | import { strict as assert } from "node:assert"; 2 | import { after, describe, it } from "node:test"; 3 | 4 | import { testServer } from "@indiekit-test/server"; 5 | import supertest from "supertest"; 6 | 7 | const server = await testServer(); 8 | const request = supertest.agent(server); 9 | 10 | describe("indiekit GET /robots.txt", () => { 11 | it("Returns JavaScript", async () => { 12 | const result = await request.get("/robots.txt"); 13 | 14 | assert.equal(result.status, 200); 15 | assert.equal(result.type, "text/plain"); 16 | }); 17 | 18 | after(() => server.close()); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/indiekit/test/integration/200-styles.js: -------------------------------------------------------------------------------- 1 | import { strict as assert } from "node:assert"; 2 | import { after, describe, it } from "node:test"; 3 | 4 | import { testServer } from "@indiekit-test/server"; 5 | import supertest from "supertest"; 6 | 7 | const server = await testServer(); 8 | const request = supertest.agent(server); 9 | 10 | describe("indiekit GET /assets/app-[hash].css", () => { 11 | it("Returns CSS", async () => { 12 | const result = await request.get("/assets/app-[hash].css"); 13 | 14 | assert.equal(result.status, 200); 15 | assert.equal(result.type, "text/css"); 16 | }); 17 | 18 | after(() => server.close()); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/indiekit/views/offline.njk: -------------------------------------------------------------------------------- 1 | {% extends "document.njk" %} 2 | 3 | {% block content %} 4 | {{ warningText({ 5 | icon: "offline", 6 | iconFallbackText: false, 7 | text: __("offline.description") 8 | }) | indent(2) }} 9 | {% endblock %} -------------------------------------------------------------------------------- /packages/indiekit/views/plugins/list.njk: -------------------------------------------------------------------------------- 1 | {% extends "document.njk" %} 2 | 3 | {% block content %} 4 | {{ cardGrid({ 5 | cardSize: "100%", 6 | items: plugins 7 | }) }} 8 | {% endblock %} -------------------------------------------------------------------------------- /packages/indiekit/views/session/login.njk: -------------------------------------------------------------------------------- 1 | {% extends "form.njk" %} 2 | 3 | {% set appClasses = "app--minimalui" %} 4 | {% set me = publication.me | friendlyUrl %} 5 | 6 | {% block header %} 7 | {{ header({ 8 | url: application.url, 9 | name: application.name 10 | }) }} 11 | {% endblock %} 12 | {% block form %} 13 | {{ heading({ 14 | text: title 15 | }) | indent(2) }} 16 | 17 | {{ prose({ 18 | text: __("session.login.description", "**" + me + "**") 19 | }) | indent(2) }} 20 | 21 | {{ button({ 22 | text: __("session.login.submit"), 23 | classes: "button--block" 24 | }) | indent(2) }} 25 | {% endblock %} -------------------------------------------------------------------------------- /packages/post-type-article/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/post-type-audio/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/post-type-audio/includes/post-types/audio.njk: -------------------------------------------------------------------------------- 1 | {% set html %}{% for item in property -%} 2 |
3 |
5 | {%- endfor %}{% endset -%} 6 | {{- prose({ 7 | html: html 8 | }) if property }} -------------------------------------------------------------------------------- /packages/post-type-audio/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "audio": { 5 | "label": "Audio", 6 | "name": "audiodatei" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-audio/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "audio": { 5 | "label": "Audio", 6 | "name": "audio file" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-audio/locales/es-419.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "audio": { 5 | "label": "Audio", 6 | "name": "archivo de audio" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-audio/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "audio": { 5 | "label": "Sonido", 6 | "name": "archivo de audio" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-audio/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "audio": { 5 | "label": "Audio", 6 | "name": "fichier audio" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-audio/locales/hi.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "audio": { 5 | "label": "ऑडियो", 6 | "name": "ऑडियो फ़ाइल" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /packages/post-type-audio/locales/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "audio": { 5 | "label": "Audio", 6 | "name": "berkas audio" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-audio/locales/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "audio": { 5 | "label": "Audio", 6 | "name": "file audio" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-audio/locales/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "audio": { 5 | "label": "Audio", 6 | "name": "audiobestand" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-audio/locales/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "audio": { 5 | "label": "Audio", 6 | "name": "plik audio" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-audio/locales/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "audio": { 5 | "label": "Áudio", 6 | "name": "arquivo de áudio" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-audio/locales/sr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "audio": { 5 | "label": "Audio", 6 | "name": "audio fajl" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-audio/locales/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "audio": { 5 | "label": "Ljud", 6 | "name": "ljudfil" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /packages/post-type-audio/locales/zh-Hans-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "audio": { 5 | "label": "音频", 6 | "name": "音频文件" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-bookmark/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/post-type-bookmark/includes/post-types/bookmark-of-field.njk: -------------------------------------------------------------------------------- 1 | {{ input({ 2 | name: "bookmark-of", 3 | type: "url", 4 | value: fieldData("bookmark-of").value, 5 | label: __("posts.form.bookmark-of.label"), 6 | optional: not field.required, 7 | attributes: { 8 | placeholder: "https://" 9 | }, 10 | errorMessage: fieldData("bookmark-of").errorMessage 11 | }) }} -------------------------------------------------------------------------------- /packages/post-type-bookmark/includes/post-types/bookmark-of.njk: -------------------------------------------------------------------------------- 1 | {% set html -%} 2 | {{- icon("bookmark") -}} 3 | {{- __("posts.form.bookmark-of.label") }} 4 | {{ property | urlize | safe -}} 5 | {% endset -%} 6 | {{- prose({ 7 | classes: "prose--caption", 8 | html: html 9 | }) if property }} -------------------------------------------------------------------------------- /packages/post-type-bookmark/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "bookmark-of": { 5 | "label": "Lesezeichen für" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-bookmark/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "bookmark-of": { 5 | "label": "Bookmark of" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-bookmark/locales/es-419.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "bookmark-of": { 5 | "label": "Marcador de" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-bookmark/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "bookmark-of": { 5 | "label": "Marcador de" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-bookmark/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "bookmark-of": { 5 | "label": "Signet de" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-bookmark/locales/hi.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "bookmark-of": { 5 | "label": "का बुकमार्क" 6 | } 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /packages/post-type-bookmark/locales/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "bookmark-of": { 5 | "label": "Penanda dari" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-bookmark/locales/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "bookmark-of": { 5 | "label": "Segnalibro di" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-bookmark/locales/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "bookmark-of": { 5 | "label": "Bladwijzer van" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-bookmark/locales/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "bookmark-of": { 5 | "label": "Zakładka z" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-bookmark/locales/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "bookmark-of": { 5 | "label": "Marcador de" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-bookmark/locales/sr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "bookmark-of": { 5 | "label": "Bookmark of" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-bookmark/locales/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "bookmark-of": { 5 | "label": "Bokmärke av" 6 | } 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /packages/post-type-bookmark/locales/zh-Hans-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "bookmark-of": { 5 | "label": "书签来自" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-event/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/post-type-event/includes/post-types/start.njk: -------------------------------------------------------------------------------- 1 | {%- set startFormat = "PPp" if property | includes("T") else "PP" -%} 2 | {%- if properties.end -%} 3 | {%- set endFormat = "PPp" if properties.end | includes("T") else "PP" -%} 4 | {%- endif -%} 5 | {%- set html -%} 6 | {{ icon("event") }} 7 | {{- property | date(startFormat, { locale: opts.locale, timeZone: application.timeZone }) -}} 8 | {{- " – " + properties.end | date(endFormat, { locale: opts.locale, timeZone: application.timeZone }) if properties.end -}} 9 | {%- endset -%} 10 | {{- prose({ 11 | classes: "prose--caption", 12 | html: html 13 | }) if property }} -------------------------------------------------------------------------------- /packages/post-type-event/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "start": { 5 | "empty": "Geben Sie ein Startdatum ein" 6 | } 7 | }, 8 | "form": { 9 | "event": { 10 | "all-day": "Ganztägig", 11 | "end": "Endet", 12 | "label": "Datum und Uhrzeit", 13 | "start": "Beginnt" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/post-type-event/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "start": { 5 | "empty": "Enter a start date" 6 | } 7 | }, 8 | "form": { 9 | "event": { 10 | "label": "Date and time", 11 | "start": "Starts", 12 | "end": "Ends", 13 | "all-day": "All-day" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/post-type-event/locales/es-419.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "start": { 5 | "empty": "Introduce una fecha de inicio" 6 | } 7 | }, 8 | "form": { 9 | "event": { 10 | "all-day": "Todo el día", 11 | "end": "Termina", 12 | "label": "Fecha y hora", 13 | "start": "Comienza" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/post-type-event/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "start": { 5 | "empty": "Introduce una fecha de inicio" 6 | } 7 | }, 8 | "form": { 9 | "event": { 10 | "all-day": "Todo el día", 11 | "end": "Termina", 12 | "label": "Fecha y hora", 13 | "start": "Comienza" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/post-type-event/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "start": { 5 | "empty": "Entrez la date de début" 6 | } 7 | }, 8 | "form": { 9 | "event": { 10 | "all-day": "Toute la journée", 11 | "end": "Fin", 12 | "label": "Date et heure", 13 | "start": "Commence" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/post-type-event/locales/hi.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "start": { 5 | "empty": "प्रारंभ तिथि प्रविष्ट करें" 6 | } 7 | }, 8 | "form": { 9 | "event": { 10 | "all-day": "पूरे दिन", 11 | "end": "समाप्त होता है", 12 | "label": "तारीख और समय", 13 | "start": "शुरू होता है" 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /packages/post-type-event/locales/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "start": { 5 | "empty": "Masukkan tanggal mulai" 6 | } 7 | }, 8 | "form": { 9 | "event": { 10 | "all-day": "Sepanjang hari", 11 | "end": "Berakhir", 12 | "label": "Tanggal dan waktu", 13 | "start": "Mulai" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/post-type-event/locales/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "start": { 5 | "empty": "Inserisci una data di inizio" 6 | } 7 | }, 8 | "form": { 9 | "event": { 10 | "all-day": "Tutto il giorno", 11 | "end": "Termina", 12 | "label": "Data e ora", 13 | "start": "Inizia" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/post-type-event/locales/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "start": { 5 | "empty": "Voer een startdatum in" 6 | } 7 | }, 8 | "form": { 9 | "event": { 10 | "all-day": "Hele dag", 11 | "end": "Eindigt", 12 | "label": "Datum en tijd", 13 | "start": "Begint" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/post-type-event/locales/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "start": { 5 | "empty": "Wpisz datę rozpoczęcia" 6 | } 7 | }, 8 | "form": { 9 | "event": { 10 | "all-day": "Cały dzień", 11 | "end": "Kończy się", 12 | "label": "Data i godzina", 13 | "start": "Rozpoczyna się" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/post-type-event/locales/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "start": { 5 | "empty": "Insira uma data de início" 6 | } 7 | }, 8 | "form": { 9 | "event": { 10 | "all-day": "Todo o dia", 11 | "end": "Termina", 12 | "label": "Data e hora", 13 | "start": "Começa" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/post-type-event/locales/sr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "start": { 5 | "empty": "Unesite datum početka" 6 | } 7 | }, 8 | "form": { 9 | "event": { 10 | "all-day": "Ceo dan", 11 | "end": "Krajevi", 12 | "label": "Datum i vreme", 13 | "start": "Počinje" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/post-type-event/locales/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "start": { 5 | "empty": "Ange ett startdatum" 6 | } 7 | }, 8 | "form": { 9 | "event": { 10 | "all-day": "Hela dagen", 11 | "end": "Slutar", 12 | "label": "Datum och tid", 13 | "start": "Börjar" 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /packages/post-type-event/locales/zh-Hans-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "start": { 5 | "empty": "输入开始日期" 6 | } 7 | }, 8 | "form": { 9 | "event": { 10 | "all-day": "全天", 11 | "end": "结束", 12 | "label": "日期和时间", 13 | "start": "开始" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/post-type-jam/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/post-type-jam/includes/post-types/jam-of.njk: -------------------------------------------------------------------------------- 1 | {{ prose({ 2 | html: card({ 3 | url: property.url if property.url, 4 | title: "" + property.name + "" if property.name, 5 | description: "by " + property.author if property.author 6 | }) 7 | }) if property }} -------------------------------------------------------------------------------- /packages/post-type-jam/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "jam": { 5 | "author": { 6 | "empty": "Tragen Sie einen Künstler ein" 7 | }, 8 | "name": { 9 | "empty": "Gib einen Songtitel ein" 10 | } 11 | } 12 | }, 13 | "form": { 14 | "jam-of": { 15 | "author": "Künstler", 16 | "label": "Jam von", 17 | "name": "Song Titel", 18 | "url": "URL" 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /packages/post-type-jam/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "jam": { 5 | "author": { 6 | "empty": "Enter an artist" 7 | }, 8 | "name": { 9 | "empty": "Enter a song title" 10 | } 11 | } 12 | }, 13 | "form": { 14 | "jam-of": { 15 | "label": "Jam of", 16 | "name": "Song title", 17 | "author": "Artist", 18 | "url": "URL" 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/post-type-jam/locales/es-419.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "jam": { 5 | "author": { 6 | "empty": "Introduce un artista" 7 | }, 8 | "name": { 9 | "empty": "Introduce el título de una canción" 10 | } 11 | } 12 | }, 13 | "form": { 14 | "jam-of": { 15 | "author": "Artista", 16 | "label": "Jam de", 17 | "name": "Título de la canción", 18 | "url": "URL" 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /packages/post-type-jam/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "jam": { 5 | "author": { 6 | "empty": "Introduce un artista" 7 | }, 8 | "name": { 9 | "empty": "Introduce el título de una canción" 10 | } 11 | } 12 | }, 13 | "form": { 14 | "jam-of": { 15 | "author": "Artista", 16 | "label": "Jam de", 17 | "name": "Título de la canción", 18 | "url": "URL" 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /packages/post-type-jam/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "jam": { 5 | "author": { 6 | "empty": "Entrez un artiste" 7 | }, 8 | "name": { 9 | "empty": "Entrez le titre d'une chanson" 10 | } 11 | } 12 | }, 13 | "form": { 14 | "jam-of": { 15 | "author": "Artiste", 16 | "label": "Jam de", 17 | "name": "Titre de la chanson", 18 | "url": "URL" 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /packages/post-type-jam/locales/hi.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "jam": { 5 | "author": { 6 | "empty": "एक कलाकार दर्ज करें" 7 | }, 8 | "name": { 9 | "empty": "गीत का शीर्षक प्रविष्ट करें" 10 | } 11 | } 12 | }, 13 | "form": { 14 | "jam-of": { 15 | "author": "आर्टिस्ट", 16 | "label": "Jam ऑफ", 17 | "name": "गाने का शीर्षक", 18 | "url": "URL" 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /packages/post-type-jam/locales/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "jam": { 5 | "author": { 6 | "empty": "Masukkan artis" 7 | }, 8 | "name": { 9 | "empty": "Masukkan judul lagu" 10 | } 11 | } 12 | }, 13 | "form": { 14 | "jam-of": { 15 | "author": "Artis", 16 | "label": "Jam dari", 17 | "name": "Judul lagu", 18 | "url": "Tautan" 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /packages/post-type-jam/locales/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "jam": { 5 | "author": { 6 | "empty": "Inserisci un artista" 7 | }, 8 | "name": { 9 | "empty": "Inserisci il titolo di una canzone" 10 | } 11 | } 12 | }, 13 | "form": { 14 | "jam-of": { 15 | "author": "Artista", 16 | "label": "Jam di", 17 | "name": "Titolo canzone", 18 | "url": "URL" 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/post-type-jam/locales/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "jam": { 5 | "author": { 6 | "empty": "Voer een artiest in" 7 | }, 8 | "name": { 9 | "empty": "Voer een titel van een nummer in" 10 | } 11 | } 12 | }, 13 | "form": { 14 | "jam-of": { 15 | "author": "Artiest", 16 | "label": "Jam van", 17 | "name": "Titel", 18 | "url": "URL" 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /packages/post-type-jam/locales/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "jam": { 5 | "author": { 6 | "empty": "Wprowadź artystę" 7 | }, 8 | "name": { 9 | "empty": "Wprowadź tytuł utworu" 10 | } 11 | } 12 | }, 13 | "form": { 14 | "jam-of": { 15 | "author": "Artysta", 16 | "label": "Jam z", 17 | "name": "Tytuł piosenki", 18 | "url": "Adres URL" 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /packages/post-type-jam/locales/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "jam": { 5 | "author": { 6 | "empty": "Insira um artista" 7 | }, 8 | "name": { 9 | "empty": "Insira o título de uma música" 10 | } 11 | } 12 | }, 13 | "form": { 14 | "jam-of": { 15 | "author": "Artista", 16 | "label": "Jam de", 17 | "name": "Título da faixa", 18 | "url": "URL" 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /packages/post-type-jam/locales/sr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "jam": { 5 | "author": { 6 | "empty": "Unesite umetnika" 7 | }, 8 | "name": { 9 | "empty": "Unesite naslov pesme" 10 | } 11 | } 12 | }, 13 | "form": { 14 | "jam-of": { 15 | "author": "Umetnik", 16 | "label": "Jam od", 17 | "name": "Naslov pesme", 18 | "url": "URL" 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /packages/post-type-jam/locales/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "jam": { 5 | "author": { 6 | "empty": "Ange en artist" 7 | }, 8 | "name": { 9 | "empty": "Ange en låttitel" 10 | } 11 | } 12 | }, 13 | "form": { 14 | "jam-of": { 15 | "author": "Artist", 16 | "label": "Jam av", 17 | "name": "Låttitel", 18 | "url": "URL" 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /packages/post-type-jam/locales/zh-Hans-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "jam": { 5 | "author": { 6 | "empty": "输入艺术家" 7 | }, 8 | "name": { 9 | "empty": "输入歌曲标题" 10 | } 11 | } 12 | }, 13 | "form": { 14 | "jam-of": { 15 | "author": "艺术家", 16 | "label": "Jam", 17 | "name": "歌曲标题", 18 | "url": "网址" 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /packages/post-type-like/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/post-type-like/includes/post-types/like-of-field.njk: -------------------------------------------------------------------------------- 1 | {{ input({ 2 | name: "like-of", 3 | type: "url", 4 | value: fieldData("like-of").value, 5 | label: __("posts.form.like-of.label"), 6 | optional: not field.required, 7 | attributes: { 8 | placeholder: "https://" 9 | }, 10 | errorMessage: fieldData("like-of").errorMessage 11 | }) }} -------------------------------------------------------------------------------- /packages/post-type-like/includes/post-types/like-of.njk: -------------------------------------------------------------------------------- 1 | {% set html -%} 2 | {{- icon("like") -}} 3 | {{- __("posts.form.like-of.label") }} 4 | {{ property | urlize | safe -}} 5 | {% endset -%} 6 | {{- prose({ 7 | classes: "prose--caption", 8 | html: html 9 | }) if property }} -------------------------------------------------------------------------------- /packages/post-type-like/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "like-of": { 5 | "label": "Wie von" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-like/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "like-of": { 5 | "label": "Like of" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-like/locales/es-419.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "like-of": { 5 | "label": "Como de" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-like/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "like-of": { 5 | "label": "Como de" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-like/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "like-of": { 5 | "label": "J’aime" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-like/locales/hi.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "like-of": { 5 | "label": "की तरह" 6 | } 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /packages/post-type-like/locales/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "like-of": { 5 | "label": "Seperti" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-like/locales/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "like-of": { 5 | "label": "Preferito di" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-like/locales/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "like-of": { 5 | "label": "Zoals van" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-like/locales/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "like-of": { 5 | "label": "Jak z" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-like/locales/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "like-of": { 5 | "label": "Como de" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-like/locales/sr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "like-of": { 5 | "label": "Kao od" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-like/locales/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "like-of": { 5 | "label": "Gillade" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-like/locales/zh-Hans-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "like-of": { 5 | "label": "像这样" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-note/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/post-type-photo/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/post-type-photo/includes/post-types/photo.njk: -------------------------------------------------------------------------------- 1 | {% set html %}{% for item in property -%} 2 |
3 | {{ item.alt }} 4 |
5 | {%- endfor %}{% endset -%} 6 | {{- prose({ 7 | html: html 8 | }) if property }} -------------------------------------------------------------------------------- /packages/post-type-photo/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "mp-photo-alt": { 5 | "empty": "Geben Sie eine Beschreibung dieses Fotos ein" 6 | } 7 | }, 8 | "form": { 9 | "mp-photo-alt": { 10 | "label": "Barrierefreie Beschreibung" 11 | }, 12 | "photo": { 13 | "label": "Fotos", 14 | "name": "foto" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-photo/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "mp-photo-alt": { 5 | "empty": "Enter a description of this photo" 6 | } 7 | }, 8 | "form": { 9 | "mp-photo-alt": { 10 | "label": "Accessible description" 11 | }, 12 | "photo": { 13 | "label": "Photos", 14 | "name": "photo" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-photo/locales/es-419.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "mp-photo-alt": { 5 | "empty": "Introducir una descripción de esta foto" 6 | } 7 | }, 8 | "form": { 9 | "mp-photo-alt": { 10 | "label": "Descripción accesible" 11 | }, 12 | "photo": { 13 | "label": "Fotos", 14 | "name": "foto" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-photo/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "mp-photo-alt": { 5 | "empty": "Introduce una descripción de esta foto" 6 | } 7 | }, 8 | "form": { 9 | "mp-photo-alt": { 10 | "label": "Descripción accesible" 11 | }, 12 | "photo": { 13 | "label": "Fotos", 14 | "name": "foto" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-photo/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "mp-photo-alt": { 5 | "empty": "Entrez une description de cette photo" 6 | } 7 | }, 8 | "form": { 9 | "mp-photo-alt": { 10 | "label": "Description accessible" 11 | }, 12 | "photo": { 13 | "label": "Photos", 14 | "name": "photo" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-photo/locales/hi.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "mp-photo-alt": { 5 | "empty": "इस फ़ोटो का विवरण दर्ज करें" 6 | } 7 | }, 8 | "form": { 9 | "mp-photo-alt": { 10 | "label": "सुलभ वर्णन" 11 | }, 12 | "photo": { 13 | "label": "फ़ोटोज़", 14 | "name": "तस्वीर" 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /packages/post-type-photo/locales/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "mp-photo-alt": { 5 | "empty": "Masukkan deskripsi foto ini" 6 | } 7 | }, 8 | "form": { 9 | "mp-photo-alt": { 10 | "label": "Deskripsi yang dapat diakses" 11 | }, 12 | "photo": { 13 | "label": "Foto", 14 | "name": "foto" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-photo/locales/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "mp-photo-alt": { 5 | "empty": "Inserisci una descrizione di questa foto" 6 | } 7 | }, 8 | "form": { 9 | "mp-photo-alt": { 10 | "label": "Descrizione accessibile" 11 | }, 12 | "photo": { 13 | "label": "Fotografie", 14 | "name": "foto" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-photo/locales/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "mp-photo-alt": { 5 | "empty": "Voer een beschrijving van deze foto in" 6 | } 7 | }, 8 | "form": { 9 | "mp-photo-alt": { 10 | "label": "Toegankelijke beschrijving" 11 | }, 12 | "photo": { 13 | "label": "Foto’s", 14 | "name": "foto" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-photo/locales/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "mp-photo-alt": { 5 | "empty": "Wprowadź opis tego zdjęcia" 6 | } 7 | }, 8 | "form": { 9 | "mp-photo-alt": { 10 | "label": "Dostępny opis" 11 | }, 12 | "photo": { 13 | "label": "Zdjęcia", 14 | "name": "zdjęcie" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-photo/locales/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "mp-photo-alt": { 5 | "empty": "Insira uma descrição desta foto" 6 | } 7 | }, 8 | "form": { 9 | "mp-photo-alt": { 10 | "label": "Descrição acessível" 11 | }, 12 | "photo": { 13 | "label": "Fotos", 14 | "name": "foto" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-photo/locales/sr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "mp-photo-alt": { 5 | "empty": "Unesite opis ove fotografije" 6 | } 7 | }, 8 | "form": { 9 | "mp-photo-alt": { 10 | "label": "Pristupačan opis" 11 | }, 12 | "photo": { 13 | "label": "Fotografije", 14 | "name": "photo" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-photo/locales/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "mp-photo-alt": { 5 | "empty": "Ange en beskrivning av det här fotot" 6 | } 7 | }, 8 | "form": { 9 | "mp-photo-alt": { 10 | "label": "Tillgänglig beskrivning" 11 | }, 12 | "photo": { 13 | "label": "Foton", 14 | "name": "foto" 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /packages/post-type-photo/locales/zh-Hans-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "mp-photo-alt": { 5 | "empty": "输入这张照片的描述" 6 | } 7 | }, 8 | "form": { 9 | "mp-photo-alt": { 10 | "label": "无障碍描述" 11 | }, 12 | "photo": { 13 | "label": "照片", 14 | "name": "照片" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-reply/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/post-type-reply/includes/post-types/in-reply-to-field.njk: -------------------------------------------------------------------------------- 1 | {{ input({ 2 | name: "in-reply-to", 3 | type: "url", 4 | value: fieldData("in-reply-to").value, 5 | label: __("posts.form.in-reply-to.label"), 6 | optional: not field.required, 7 | attributes: { 8 | placeholder: "https://" 9 | }, 10 | errorMessage: fieldData("in-reply-to").errorMessage 11 | }) }} -------------------------------------------------------------------------------- /packages/post-type-reply/includes/post-types/in-reply-to.njk: -------------------------------------------------------------------------------- 1 | {% set html -%} 2 | {{- icon("reply") -}} 3 | {{- __("posts.form.in-reply-to.label") }} 4 | {{ property | urlize | safe -}} 5 | {% endset -%} 6 | {{- prose({ 7 | classes: "prose--caption", 8 | html: html 9 | }) if property }} -------------------------------------------------------------------------------- /packages/post-type-reply/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "in-reply-to": { 5 | "label": "Als Antwort auf" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-reply/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "in-reply-to": { 5 | "label": "In reply to" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-reply/locales/es-419.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "in-reply-to": { 5 | "label": "En respuesta a" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-reply/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "in-reply-to": { 5 | "label": "En respuesta a" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-reply/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "in-reply-to": { 5 | "label": "En réponse à" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-reply/locales/hi.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "in-reply-to": { 5 | "label": "के जवाब में" 6 | } 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /packages/post-type-reply/locales/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "in-reply-to": { 5 | "label": "Sebagai balasan" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-reply/locales/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "in-reply-to": { 5 | "label": "In risposta a" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-reply/locales/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "in-reply-to": { 5 | "label": "Als antwoord op" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-reply/locales/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "in-reply-to": { 5 | "label": "W odpowiedzi na" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-reply/locales/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "in-reply-to": { 5 | "label": "Em resposta a" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-reply/locales/sr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "in-reply-to": { 5 | "label": "U odgovoru na" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-reply/locales/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "in-reply-to": { 5 | "label": "Som svar på" 6 | } 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /packages/post-type-reply/locales/zh-Hans-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "in-reply-to": { 5 | "label": "在回复中" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-repost/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/post-type-repost/includes/post-types/repost-of-field.njk: -------------------------------------------------------------------------------- 1 | {{ input({ 2 | name: "repost-of", 3 | type: "url", 4 | value: fieldData("repost-of").value, 5 | label: __("posts.form.repost-of.label"), 6 | optional: not field.required, 7 | attributes: { 8 | placeholder: "https://" 9 | }, 10 | errorMessage: fieldData("repost-of").errorMessage 11 | }) }} -------------------------------------------------------------------------------- /packages/post-type-repost/includes/post-types/repost-of.njk: -------------------------------------------------------------------------------- 1 | {% set html -%} 2 | {{- icon("repost") -}} 3 | {{- __("posts.form.repost-of.label") }} 4 | {{ property | urlize | safe -}} 5 | {% endset -%} 6 | {{- prose({ 7 | classes: "prose--caption", 8 | html: html 9 | }) if property }} -------------------------------------------------------------------------------- /packages/post-type-repost/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "repost-of": { 5 | "label": "Repost von" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-repost/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "repost-of": { 5 | "label": "Repost of" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-repost/locales/es-419.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "repost-of": { 5 | "label": "Republicación de" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-repost/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "repost-of": { 5 | "label": "Republicación de" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-repost/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "repost-of": { 5 | "label": "Republication de" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-repost/locales/hi.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "repost-of": { 5 | "label": "का रेपोस्ट" 6 | } 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /packages/post-type-repost/locales/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "repost-of": { 5 | "label": "Repost dari" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-repost/locales/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "repost-of": { 5 | "label": "Ripubblicazione di" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-repost/locales/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "repost-of": { 5 | "label": "Repost van" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-repost/locales/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "repost-of": { 5 | "label": "Repost z" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-repost/locales/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "repost-of": { 5 | "label": "Republicação de" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-repost/locales/sr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "repost-of": { 5 | "label": "Repost od" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-repost/locales/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "repost-of": { 5 | "label": "Återpostning av" 6 | } 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /packages/post-type-repost/locales/zh-Hans-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "repost-of": { 5 | "label": "重新发布的" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/post-type-rsvp/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/post-type-rsvp/includes/post-types/rsvp-field.njk: -------------------------------------------------------------------------------- 1 | {{ radios({ 2 | inline: true, 3 | name: "rsvp", 4 | values: properties.rsvp or "yes", 5 | fieldset: { 6 | legend: __("posts.form.rsvp.label") 7 | }, 8 | optional: not field.required, 9 | items: [{ 10 | label: __("posts.form.rsvp.yes"), 11 | value: "yes" 12 | }, { 13 | label: __("posts.form.rsvp.no"), 14 | value: "no" 15 | }, { 16 | label: __("posts.form.rsvp.maybe"), 17 | value: "maybe" 18 | }, { 19 | label: __("posts.form.rsvp.interested"), 20 | value: "interested" 21 | }] 22 | }) }} -------------------------------------------------------------------------------- /packages/post-type-rsvp/includes/post-types/rsvp.njk: -------------------------------------------------------------------------------- 1 | {{ badge({ 2 | color: "green", 3 | icon: "tick", 4 | text: __("posts.form.rsvp.yes") 5 | }) if property == "yes" -}} 6 | {{- badge({ 7 | color: "red", 8 | icon: "cross", 9 | text: __("posts.form.rsvp.no") 10 | }) if property == "no" -}} 11 | {{- badge({ 12 | color: "offset-yellow", 13 | text: __("posts.form.rsvp.maybe") 14 | }) if property == "maybe" -}} 15 | {{- badge({ 16 | color: "offset-green", 17 | text: __("posts.form.rsvp.interested") 18 | }) if property == "interested" }} -------------------------------------------------------------------------------- /packages/post-type-rsvp/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "rsvp": { 5 | "empty": "Antwort auswählen" 6 | } 7 | }, 8 | "form": { 9 | "rsvp": { 10 | "interested": "Interessiert", 11 | "label": "RSVP", 12 | "maybe": "Vielleicht", 13 | "no": "Nein", 14 | "yes": "Ja" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-rsvp/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "rsvp": { 5 | "empty": "Select a response" 6 | } 7 | }, 8 | "form": { 9 | "rsvp": { 10 | "label": "RSVP", 11 | "yes": "Yes", 12 | "no": "No", 13 | "maybe": "Maybe", 14 | "interested": "Interested" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-rsvp/locales/es-419.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "rsvp": { 5 | "empty": "Selecciona una respuesta" 6 | } 7 | }, 8 | "form": { 9 | "rsvp": { 10 | "interested": "Interesado", 11 | "label": "RSVP", 12 | "maybe": "Quizás", 13 | "no": "No", 14 | "yes": "Sí" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-rsvp/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "rsvp": { 5 | "empty": "Seleccione una respuesta" 6 | } 7 | }, 8 | "form": { 9 | "rsvp": { 10 | "interested": "Interesado", 11 | "label": "Responder", 12 | "maybe": "Quizás", 13 | "no": "No", 14 | "yes": "Sí" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-rsvp/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "rsvp": { 5 | "empty": "Sélectionnez une réponse" 6 | } 7 | }, 8 | "form": { 9 | "rsvp": { 10 | "interested": "Intéressé", 11 | "label": "RSVP", 12 | "maybe": "Peut-être", 13 | "no": "Non", 14 | "yes": "Oui" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-rsvp/locales/hi.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "rsvp": { 5 | "empty": "प्रतिक्रिया चुनें" 6 | } 7 | }, 8 | "form": { 9 | "rsvp": { 10 | "interested": "इच्छुक", 11 | "label": "आरएसवीपी", 12 | "maybe": "हो सकता है", 13 | "no": "नहीं", 14 | "yes": "हाँ" 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /packages/post-type-rsvp/locales/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "rsvp": { 5 | "empty": "Pilih respon" 6 | } 7 | }, 8 | "form": { 9 | "rsvp": { 10 | "interested": "Tertarik", 11 | "label": "RSVP", 12 | "maybe": "Mungkin", 13 | "no": "Tidak", 14 | "yes": "Ya" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-rsvp/locales/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "rsvp": { 5 | "empty": "Seleziona una risposta" 6 | } 7 | }, 8 | "form": { 9 | "rsvp": { 10 | "interested": "Interessato", 11 | "label": "Risposta", 12 | "maybe": "Forse", 13 | "no": "No", 14 | "yes": "Sì" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-rsvp/locales/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "rsvp": { 5 | "empty": "Selecteer een antwoord" 6 | } 7 | }, 8 | "form": { 9 | "rsvp": { 10 | "interested": "Geïnteresseerd", 11 | "label": "RSVP", 12 | "maybe": "Misschien", 13 | "no": "Nee", 14 | "yes": "Ja" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-rsvp/locales/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "rsvp": { 5 | "empty": "Wybierz odpowiedź" 6 | } 7 | }, 8 | "form": { 9 | "rsvp": { 10 | "interested": "Zainteresowani", 11 | "label": "RSVP", 12 | "maybe": "Może", 13 | "no": "Nie", 14 | "yes": "Tak" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-rsvp/locales/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "rsvp": { 5 | "empty": "Selecione uma resposta" 6 | } 7 | }, 8 | "form": { 9 | "rsvp": { 10 | "interested": "Interessados", 11 | "label": "RSVP", 12 | "maybe": "Talvez", 13 | "no": "Não", 14 | "yes": "Sim" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-rsvp/locales/sr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "rsvp": { 5 | "empty": "Izaberite odgovor" 6 | } 7 | }, 8 | "form": { 9 | "rsvp": { 10 | "interested": "Zainteresovani", 11 | "label": "RSVP", 12 | "maybe": "Možda", 13 | "no": "Ne", 14 | "yes": "Da" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-rsvp/locales/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "rsvp": { 5 | "empty": "Välj ett svar" 6 | } 7 | }, 8 | "form": { 9 | "rsvp": { 10 | "interested": "Intresserad", 11 | "label": "OSA", 12 | "maybe": "Kanske", 13 | "no": "Nej", 14 | "yes": "Ja" 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /packages/post-type-rsvp/locales/zh-Hans-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "error": { 4 | "rsvp": { 5 | "empty": "选择回应" 6 | } 7 | }, 8 | "form": { 9 | "rsvp": { 10 | "interested": "感兴趣", 11 | "label": "回复", 12 | "maybe": "可能", 13 | "no": "否", 14 | "yes": "是" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/post-type-video/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/post-type-video/includes/post-types/video.njk: -------------------------------------------------------------------------------- 1 | {% set html %}{% for item in property -%} 2 |
3 |
5 | {%- endfor %}{% endset -%} 6 | {{- prose({ 7 | html: html 8 | }) if property }} -------------------------------------------------------------------------------- /packages/post-type-video/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "video": { 5 | "label": "Videos", 6 | "name": "video" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-video/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "video": { 5 | "label": "Videos", 6 | "name": "video" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-video/locales/es-419.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "video": { 5 | "label": "Vídeos", 6 | "name": "vídeo" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-video/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "video": { 5 | "label": "Vídeos", 6 | "name": "vídeo" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-video/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "video": { 5 | "label": "Vidéos", 6 | "name": "vidéo" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-video/locales/hi.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "video": { 5 | "label": "वीडियो", 6 | "name": "वीडियो" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /packages/post-type-video/locales/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "video": { 5 | "label": "Video", 6 | "name": "video" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-video/locales/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "video": { 5 | "label": "Video", 6 | "name": "video" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-video/locales/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "video": { 5 | "label": "Video’s", 6 | "name": "video" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-video/locales/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "video": { 5 | "label": "Wideo", 6 | "name": "wideo" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-video/locales/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "video": { 5 | "label": "Vídeos", 6 | "name": "vídeo" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-video/locales/sr.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "video": { 5 | "label": "Video snimci", 6 | "name": "video" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/post-type-video/locales/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "video": { 5 | "label": "Videor", 6 | "name": "video" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /packages/post-type-video/locales/zh-Hans-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": { 3 | "form": { 4 | "video": { 5 | "label": "视频", 6 | "name": "视频" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/preset-eleventy/README.md: -------------------------------------------------------------------------------- 1 | # @indiekit/preset-eleventy 2 | 3 | [Eleventy](https://www.11ty.dev) publication preset for Indiekit. 4 | 5 | ## Installation 6 | 7 | `npm install @indiekit/preset-eleventy` 8 | 9 | ## Usage 10 | 11 | Add `@indiekit/preset-eleventy` to your list of plug-ins: 12 | 13 | ```json 14 | { 15 | "plugins": ["@indiekit/preset-eleventy"] 16 | } 17 | ``` 18 | -------------------------------------------------------------------------------- /packages/preset-eleventy/index.js: -------------------------------------------------------------------------------- 1 | import { getPostTemplate } from "./lib/post-template.js"; 2 | import { getPostTypes } from "./lib/post-types.js"; 3 | 4 | export default class EleventyPreset { 5 | constructor() { 6 | this.name = "Eleventy preset"; 7 | } 8 | 9 | get info() { 10 | return { 11 | name: "Eleventy", 12 | }; 13 | } 14 | 15 | postTemplate(properties) { 16 | return getPostTemplate(properties); 17 | } 18 | 19 | init(Indiekit) { 20 | this.postTypes = getPostTypes(Indiekit.postTypes); 21 | 22 | Indiekit.addPreset(this); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/preset-hugo/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/preset-jekyll/README.md: -------------------------------------------------------------------------------- 1 | # @indiekit/preset-jekyll 2 | 3 | [Jekyll](https://jekyllrb.com) publication preset for Indiekit. Can also be used with static site generators that use a similar folder structure and document format, like [Eleventy](https://www.11ty.dev) and [Lume](https://lume.land). 4 | 5 | ## Installation 6 | 7 | `npm install @indiekit/preset-jekyll` 8 | 9 | > [!NOTE] 10 | > This package is installed alongside `@indiekit/indiekit` 11 | 12 | ## Usage 13 | 14 | Add `@indiekit/preset-jekyll` to your list of plug-ins: 15 | 16 | ```json 17 | { 18 | "plugins": ["@indiekit/preset-jekyll"] 19 | } 20 | ``` 21 | -------------------------------------------------------------------------------- /packages/preset-jekyll/index.js: -------------------------------------------------------------------------------- 1 | import { getPostTemplate } from "./lib/post-template.js"; 2 | import { getPostTypes } from "./lib/post-types.js"; 3 | 4 | export default class JekyllPreset { 5 | constructor() { 6 | this.name = "Jekyll preset"; 7 | } 8 | 9 | get info() { 10 | return { 11 | name: "Jekyll", 12 | }; 13 | } 14 | 15 | postTemplate(properties) { 16 | return getPostTemplate(properties); 17 | } 18 | 19 | init(Indiekit) { 20 | this.postTypes = getPostTypes(Indiekit.postTypes); 21 | 22 | Indiekit.addPreset(this); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/store-bitbucket/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/store-file-system/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/store-ftp/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/store-github/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/store-gitlab/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/store-s3/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/syndicator-bluesky/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/syndicator-internet-archive/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/util/README.md: -------------------------------------------------------------------------------- 1 | # @indiekit/util 2 | 3 | Common utilities for Indiekit. 4 | 5 | ## Installation 6 | 7 | `npm install @indiekit/util` 8 | 9 | > [!NOTE] 10 | > This package is installed alongside `@indiekit/indiekit` 11 | -------------------------------------------------------------------------------- /packages/util/index.js: -------------------------------------------------------------------------------- 1 | export { getCursor, getObjectId } from "./lib/collection.js"; 2 | export { 3 | dateTokens, 4 | formatDate, 5 | formatZonedToLocalDate, 6 | formatLocalToZonedDate, 7 | getDate, 8 | isDate, 9 | } from "./lib/date.js"; 10 | export { sanitise } from "./lib/object.js"; 11 | export { ISO_6709_RE } from "./lib/regex.js"; 12 | export { 13 | excerpt, 14 | md5, 15 | randomString, 16 | sha1, 17 | slugify, 18 | supplant, 19 | } from "./lib/string.js"; 20 | export { getCanonicalUrl, isSameOrigin } from "./lib/url.js"; 21 | export { isRequired } from "./lib/validation-schema.js"; 22 | -------------------------------------------------------------------------------- /packages/util/lib/regex.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Geographic point location by coordinates (ISO 6709) 3 | * @example 50.8211,-0.1452 => matches 4 | * @see {@link https://en.wikipedia.org/wiki/ISO_6709} 5 | */ 6 | export const ISO_6709_RE = 7 | /^(?(?:-?|\+?)?\d+(?:\.\d+)?),\s*(?(?:-?|\+?)?\d+(?:\.\d+)?)$/; 8 | -------------------------------------------------------------------------------- /packages/util/lib/validation-schema.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if field is required 3 | * @param {import("express").Request} request - Request 4 | * @param {string} field - Field name 5 | * @returns {boolean} Field is required 6 | */ 7 | export const isRequired = (request, field) => { 8 | const { postTypes } = request.app.locals.publication; 9 | const postTypeConfig = postTypes[request.body.postType]; 10 | 11 | return postTypeConfig["required-properties"].includes(field); 12 | }; 13 | -------------------------------------------------------------------------------- /types/express/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Express } from "express-serve-static-core"; 2 | 3 | declare global { 4 | namespace Express { 5 | interface Request { 6 | verifiedToken: { 7 | client_id: string; 8 | code_challenge: string; 9 | code_challenge_method: string; 10 | jti: string; 11 | me: string; 12 | redirect_uri: string; 13 | scope: string; 14 | iat: number; 15 | exp: number; 16 | }; 17 | } 18 | } 19 | } 20 | --------------------------------------------------------------------------------