2
1
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2023-12-13 21:00:40 +01:00

Merge branch 'main' into urdu-lang

This commit is contained in:
Ryan Feigenbaum 2023-12-04 09:06:47 -05:00 committed by GitHub
commit 6eb16577f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1935 changed files with 48809 additions and 82952 deletions

128
.github/CODE_OF_CONDUCT.md vendored Normal file
View file

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
report@ghost.org.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View file

@ -7,8 +7,3 @@ Please include a description of your change & check your PR against this list, t
- [ ] The build will pass (run `yarn test:all` and `yarn lint`)
We appreciate your contribution!
---
<!-- Leave the line below if you'd like GitHub Copilot to generate a summary from your commit -->
copilot:summary

7
.github/labeler.yml vendored Normal file
View file

@ -0,0 +1,7 @@
'affects:i18n':
- 'ghost/i18n/**'
'affects:admin':
- 'ghost/admin/**'
- 'apps/admin-x-*/**'
'affects:portal':
- 'apps/portal/**'

View file

@ -2,6 +2,7 @@ const path = require('path');
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const chalk = require('chalk');
const concurrently = require('concurrently');
// check we're running on Node 18 and above
@ -44,32 +45,40 @@ const COMMAND_ADMIN = {
const COMMAND_TYPESCRIPT = {
name: 'ts',
command: 'nx watch --projects=ghost/collections,ghost/in-memory-repository,ghost/bookshelf-repository,ghost/mail-events,ghost/model-to-domain-event-interceptor,ghost/post-revisions,ghost/nql-filter-expansions,ghost/post-events,ghost/donations,ghost/recommendations -- nx run \\$NX_PROJECT_NAME:build:ts',
command: 'nx watch --projects=ghost/collections,ghost/in-memory-repository,ghost/bookshelf-repository,ghost/mail-events,ghost/model-to-domain-event-interceptor,ghost/post-revisions,ghost/nql-filter-expansions,ghost/post-events,ghost/donations,ghost/recommendations,ghost/email-addresses -- nx run \\$NX_PROJECT_NAME:build:ts',
cwd: path.resolve(__dirname, '../../'),
prefixColor: 'cyan',
env: {}
};
const COMMAND_ADMINX = {
const adminXApps = '@tryghost/admin-x-demo,@tryghost/admin-x-settings';
const COMMANDS_ADMINX = [{
name: 'adminXDeps',
command: 'nx watch --projects=apps/admin-x-design-system,apps/admin-x-framework -- nx run \\$NX_PROJECT_NAME:build --skip-nx-cache',
cwd: path.resolve(__dirname, '../..'),
prefixColor: '#C35831',
env: {}
}, {
name: 'adminX',
command: 'yarn dev',
command: `nx run-many --projects=${adminXApps} --targets=build && nx run-many --projects=${adminXApps} --parallel=${adminXApps.length} --targets=dev`,
cwd: path.resolve(__dirname, '../../apps/admin-x-settings'),
prefixColor: '#C35831',
env: {}
};
}];
if (DASH_DASH_ARGS.includes('ghost')) {
commands = [COMMAND_GHOST, COMMAND_TYPESCRIPT];
} else if (DASH_DASH_ARGS.includes('admin')) {
commands = [COMMAND_ADMIN, COMMAND_ADMINX];
commands = [COMMAND_ADMIN, ...COMMANDS_ADMINX];
} else {
commands = [COMMAND_GHOST, COMMAND_TYPESCRIPT, COMMAND_ADMIN, COMMAND_ADMINX];
commands = [COMMAND_GHOST, COMMAND_TYPESCRIPT, COMMAND_ADMIN, ...COMMANDS_ADMINX];
}
if (DASH_DASH_ARGS.includes('portal') || DASH_DASH_ARGS.includes('all')) {
commands.push({
name: 'portal',
command: 'yarn dev',
command: 'nx run @tryghost/portal:dev',
cwd: path.resolve(__dirname, '../../apps/portal'),
prefixColor: 'magenta',
env: {}
@ -92,7 +101,7 @@ if (DASH_DASH_ARGS.includes('portal') || DASH_DASH_ARGS.includes('all')) {
if (DASH_DASH_ARGS.includes('signup') || DASH_DASH_ARGS.includes('all')) {
commands.push({
name: 'signup-form',
command: DASH_DASH_ARGS.includes('signup') ? 'yarn dev' : 'yarn preview',
command: DASH_DASH_ARGS.includes('signup') ? 'nx run @tryghost/signup-form:dev' : 'nx run @tryghost/signup-form:preview',
cwd: path.resolve(__dirname, '../../apps/signup-form'),
prefixColor: 'magenta',
env: {}
@ -103,7 +112,7 @@ if (DASH_DASH_ARGS.includes('signup') || DASH_DASH_ARGS.includes('all')) {
if (DASH_DASH_ARGS.includes('announcement-bar') || DASH_DASH_ARGS.includes('announcementBar') || DASH_DASH_ARGS.includes('announcementbar') || DASH_DASH_ARGS.includes('all')) {
commands.push({
name: 'announcement-bar',
command: 'yarn dev',
command: 'nx run @tryghost/announcement-bar:dev',
cwd: path.resolve(__dirname, '../../apps/announcement-bar'),
prefixColor: '#DC9D00',
env: {}
@ -114,7 +123,7 @@ if (DASH_DASH_ARGS.includes('announcement-bar') || DASH_DASH_ARGS.includes('anno
if (DASH_DASH_ARGS.includes('search') || DASH_DASH_ARGS.includes('all')) {
commands.push({
name: 'search',
command: 'yarn dev',
command: 'nx run @tryghost/sodo-search:dev',
cwd: path.resolve(__dirname, '../../apps/sodo-search'),
prefixColor: '#23de43',
env: {}
@ -153,7 +162,7 @@ if (DASH_DASH_ARGS.includes('comments') || DASH_DASH_ARGS.includes('all')) {
commands.push({
name: 'comments',
command: 'yarn dev',
command: 'nx run @tryghost/comments-ui:dev',
cwd: path.resolve(__dirname, '../../apps/comments-ui'),
prefixColor: '#E55137',
env: {}
@ -165,7 +174,6 @@ async function handleStripe() {
if (DASH_DASH_ARGS.includes('offline')) {
return;
}
console.log('Fetching Stripe secret token..');
let stripeSecret;
try {
@ -198,6 +206,8 @@ async function handleStripe() {
process.exit(0);
}
console.log(`Running projects: ${commands.map(c => chalk.green(c.name)).join(', ')}`);
const {result} = concurrently(commands, {
prefix: 'name',
killOthers: ['failure', 'success']
@ -206,6 +216,10 @@ async function handleStripe() {
try {
await result;
} catch (err) {
console.error('\nExecuting dev command failed, ensure dependencies are up-to-date by running `yarn fix`\n');
console.error();
console.error(chalk.red(`Executing dev command failed:`) + `\n`);
console.error(chalk.red(`If you've recently done a \`yarn main\`, dependencies might be out of sync. Try running \`${chalk.green('yarn fix')}\` to fix this.`));
console.error(chalk.red(`If not, something else went wrong. Please report this to the Ghost team.`));
console.error();
}
})();

View file

@ -1,113 +0,0 @@
name: Browser Tests
on:
push:
branches:
- 'main'
workflow_dispatch:
inputs:
environment:
description: 'Environment to run tests against'
type: environment
required: true
site_url:
description: 'Site URL (override)'
required: false
type: string
owner_email:
description: 'Owner email (override)'
required: false
type: string
owner_password:
description: 'Owner password (override)'
required: false
type: string
concurrency:
group: ${{ github.workflow }}
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))
environment: ${{ github.event.inputs.environment || 'browser-tests-local' }}
env:
ENVIRONMENT: ${{ github.event.inputs.environment || 'browser-tests-local' }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
submodules: true
- uses: actions/setup-node@v3
with:
node-version: '18.x'
cache: yarn
- name: Install Stripe-CLI
run: |
export VERSION=1.13.5
wget "https://github.com/stripe/stripe-cli/releases/download/v$VERSION/stripe_${VERSION}_linux_x86_64.tar.gz"
tar -zxvf "stripe_${VERSION}_linux_x86_64.tar.gz"
mv stripe /usr/local/bin
stripe -v
- name: Install dependencies
run: yarn
- name: Build packages
run: yarn nx run-many -t build:ts
- name: Run migrations
working-directory: ghost/core
run: yarn knex-migrator init
- name: Get Playwright version
id: playwright-version
run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Check if Playwright browser is cached
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-Playwright-${{steps.playwright-version.outputs.version}}
- name: Install Playwright browser if not cached
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: npx playwright install --with-deps
- name: Install OS dependencies of Playwright if cache hit
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps
- name: Build Admin
if: env.ENVIRONMENT == 'browser-tests-local'
working-directory: ghost/admin
run: yarn build:dev
- name: Run Playwright tests on a remote site
if: env.ENVIRONMENT == 'browser-tests-staging'
working-directory: ghost/core
run: yarn test:browser
env:
TEST_URL: ${{ github.event.inputs.site_url || secrets.TEST_URL }}
TEST_OWNER_EMAIL: ${{ github.event.inputs.owner_email || secrets.TEST_OWNER_EMAIL }}
TEST_OWNER_PASSWORD: ${{ github.event.inputs.owner_password || secrets.TEST_OWNER_PASSWORD }}
- name: Run Playwright tests locally
if: env.ENVIRONMENT == 'browser-tests-local'
working-directory: ghost/core
run: yarn test:browser
env:
STRIPE_PUBLISHABLE_KEY: ${{ secrets.STRIPE_PUBLISHABLE_KEY }}
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
STRIPE_ACCOUNT_ID: ${{ secrets.STRIPE_ACCOUNT_ID }}
- uses: tryghost/actions/actions/slack-build@main
if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
status: ${{ job.status }}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
- uses: actions/upload-artifact@v3
if: always()
with:
name: browser-tests-playwright-report
path: ghost/core/playwright-report
retention-days: 30

View file

@ -12,7 +12,6 @@ on:
env:
FORCE_COLOR: 1
HEAD_COMMIT: ${{ github.sha }}
GITHUB_CONTEXT: ${{ toJson(github) }}
CACHED_DEPENDENCY_PATHS: |
${{ github.workspace }}/node_modules
${{ github.workspace }}/apps/*/node_modules
@ -43,6 +42,11 @@ jobs:
ref: ${{ env.HEAD_COMMIT }}
fetch-depth: 2
- name: Output GitHub context
run: echo "$GITHUB_CONTEXT"
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
- name: Get metadata (push)
if: github.event_name == 'push'
run: |
@ -115,6 +119,7 @@ jobs:
base_commit: ${{ env.BASE_COMMIT }}
is_canary_branch: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/arch') }}
is_main: ${{ github.ref == 'refs/heads/main' }}
has_browser_tests_label: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'browser-tests') }}
job_install_deps:
name: Install Dependencies
@ -166,7 +171,7 @@ jobs:
key: ${{ env.HEAD_COMMIT }}
- name: Set up Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
env:
FORCE_COLOR: 0
with:
@ -191,7 +196,7 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 100
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
env:
FORCE_COLOR: 0
with:
@ -216,6 +221,29 @@ jobs:
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
job_i18n:
runs-on: ubuntu-latest
needs: [job_get_metadata, job_install_deps]
name: i18n
if: |
needs.job_get_metadata.outputs.changed_comments_ui == 'true'
|| needs.job_get_metadata.outputs.changed_signup_form == 'true'
|| needs.job_get_metadata.outputs.changed_portal == 'true'
|| needs.job_get_metadata.outputs.changed_core == 'true'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "18.12.1"
- name: Restore caches
uses: ./.github/actions/restore-cache
env:
DEPENDENCY_CACHE_KEY: ${{ needs.job_install_deps.outputs.dependency_cache_key }}
- name: Run i18n tests
run: yarn nx run @tryghost/i18n:test
job_admin-tests:
runs-on: ubuntu-latest
needs: [job_get_metadata, job_install_deps]
@ -228,7 +256,7 @@ jobs:
COVERAGE: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: "18.12.1"
@ -257,6 +285,132 @@ jobs:
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
job_browser-tests:
name: Browser tests
timeout-minutes: 60
runs-on:
labels: ubuntu-latest-4-cores
needs: [job_get_metadata, job_install_deps]
if: needs.job_get_metadata.outputs.changed_any_code == 'true' && (needs.job_get_metadata.outputs.is_main == 'true' || needs.job_get_metadata.outputs.has_browser_tests_label == 'true')
concurrency:
group: ${{ github.workflow }}
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: actions/setup-node@v4
env:
FORCE_COLOR: 0
with:
node-version: '18.x'
cache: yarn
- name: Install Stripe-CLI
run: |
export VERSION=1.13.5
wget "https://github.com/stripe/stripe-cli/releases/download/v$VERSION/stripe_${VERSION}_linux_x86_64.tar.gz"
tar -zxvf "stripe_${VERSION}_linux_x86_64.tar.gz"
mv stripe /usr/local/bin
stripe -v
- name: Restore caches
uses: ./.github/actions/restore-cache
env:
DEPENDENCY_CACHE_KEY: ${{ needs.job_install_deps.outputs.dependency_cache_key }}
- name: Run migrations
working-directory: ghost/core
run: yarn knex-migrator init
- name: Get Playwright version
id: playwright-version
run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Check if Playwright browser is cached
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-Playwright-${{steps.playwright-version.outputs.version}}
- name: Install Playwright browser if not cached
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: npx playwright install --with-deps
- name: Install OS dependencies of Playwright if cache hit
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps
- name: Build Admin
run: yarn nx run ghost-admin:build:dev
- name: Run Playwright tests locally
working-directory: ghost/core
run: yarn test:browser
env:
CI: true
STRIPE_PUBLISHABLE_KEY: ${{ secrets.STRIPE_PUBLISHABLE_KEY }}
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
- uses: tryghost/actions/actions/slack-build@main
if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
status: ${{ job.status }}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
- uses: actions/upload-artifact@v3
if: always()
with:
name: browser-tests-playwright-report
path: ghost/core/playwright-report
retention-days: 30
job_perf-tests:
runs-on:
labels: ubuntu-latest-4-cores
needs: [job_get_metadata, job_install_deps]
if: needs.job_get_metadata.outputs.changed_core == 'true' && needs.job_get_metadata.outputs.is_main == 'true'
name: Performance tests
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: actions/setup-node@v4
env:
FORCE_COLOR: 0
with:
node-version: '18.12.1'
- name: Restore caches
uses: ./.github/actions/restore-cache
env:
DEPENDENCY_CACHE_KEY: ${{ needs.job_install_deps.outputs.dependency_cache_key }}
- name: Install hyperfine
run: |
export HYPERFINE_VERSION=1.18.0
wget https://github.com/sharkdp/hyperfine/releases/download/v$HYPERFINE_VERSION/hyperfine-v$HYPERFINE_VERSION-x86_64-unknown-linux-gnu.tar.gz
tar -zxvf hyperfine-v$HYPERFINE_VERSION-x86_64-unknown-linux-gnu.tar.gz
mv hyperfine-v$HYPERFINE_VERSION-x86_64-unknown-linux-gnu/hyperfine /usr/local/bin
chmod +x /usr/local/bin/hyperfine
- name: Run hyperfine on boot
working-directory: ghost/core
run: hyperfine --show-output --warmup 3 'GHOST_CI_SHUTDOWN_AFTER_BOOT=1 node index.js' --export-json boot-perf.json
- name: Convert data
working-directory: ghost/core
run: |
jq '[{ name: "Boot time", unit: "s", value: .results[0].median, range: ((.results[0].max - .results[0].min) | tostring) }]' < boot-perf.json > boot-perf-formatted.json
- name: Run analysis
uses: benchmark-action/github-action-benchmark@v1.18.0
with:
tool: 'customSmallerIsBetter'
output-file-path: ghost/core/boot-perf-formatted.json
benchmark-data-dir-path: ""
gh-repository: github.com/TryGhost/Ghost-Benchmarks
github-token: ${{ secrets.CANARY_DOCKER_BUILD }}
auto-push: true
job_unit-tests:
runs-on: ubuntu-latest
needs: [job_get_metadata, job_install_deps]
@ -269,7 +423,7 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 100
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
env:
FORCE_COLOR: 0
with:
@ -316,7 +470,7 @@ jobs:
name: Database tests (Node ${{ matrix.node }}, ${{ matrix.env.DB }})
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
env:
FORCE_COLOR: 0
with:
@ -437,7 +591,9 @@ jobs:
name: Regression tests (Node ${{ matrix.node }}, ${{ matrix.env.DB }})
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
submodules: true
- uses: actions/setup-node@v4
env:
FORCE_COLOR: 0
with:
@ -488,7 +644,7 @@ jobs:
CI: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
env:
FORCE_COLOR: 0
with:
@ -541,7 +697,7 @@ jobs:
CI: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
env:
FORCE_COLOR: 0
with:
@ -594,7 +750,7 @@ jobs:
CI: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
env:
FORCE_COLOR: 0
with:
@ -648,7 +804,7 @@ jobs:
with:
fetch-depth: 0
submodules: true
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
env:
FORCE_COLOR: 0
with:
@ -671,6 +827,22 @@ jobs:
- run: mv ghost-*.tgz ghost.tgz
working-directory: ghost/core
- name: Install latest v4
run: |
DIR=$(mktemp -d)
echo "V4_DIR=$DIR" >> $GITHUB_ENV
ghost install v4 --local -d $DIR
- uses: actions/setup-node@v4
env:
FORCE_COLOR: 0
with:
node-version: '18.12.1'
- name: Update from v4
run: |
ghost update -f -d $V4_DIR --archive $(pwd)/ghost/core/ghost.tgz
- name: Clean Install
run: |
DIR=$(mktemp -d)
@ -682,12 +854,6 @@ jobs:
ghost install local -d $DIR
ghost update -d $DIR --archive $(pwd)/ghost/core/ghost.tgz
- name: Update from latest v4
run: |
DIR=$(mktemp -d)
ghost install v4 --local -d $DIR
ghost update -f -d $DIR --archive $(pwd)/ghost/core/ghost.tgz
- name: Print debug logs
if: failure()
run: |
@ -753,11 +919,13 @@ jobs:
job_get_metadata,
job_install_deps,
job_lint,
job_i18n,
job_ghost-cli,
job_admin-tests,
job_unit-tests,
job_database-tests,
job_regression-tests,
job_browser-tests,
job_admin_x_settings,
job_comments_ui,
job_signup_form,

View file

@ -1,21 +0,0 @@
name: i18n check
on:
pull_request_target:
types: [opened]
paths:
- 'ghost/i18n/locales/**'
jobs:
create-label:
runs-on: ubuntu-latest
if: github.repository_owner == 'TryGhost'
name: Add i18n label
steps:
- uses: actions/github-script@v6
with:
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ["i18n"]
})

11
.github/workflows/labeler.yml vendored Normal file
View file

@ -0,0 +1,11 @@
name: PR labeler
on:
- pull_request_target
jobs:
triage:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4

View file

@ -21,7 +21,7 @@ jobs:
labels: ["migration"]
})
- uses: peter-evans/create-or-update-comment@ddff993e3c91296d410ace8836568b0e4aeada34
- uses: peter-evans/create-or-update-comment@ac8e6509d7545ebc2e5e7c35eaa12195c2f77adc
with:
issue-number: ${{ github.event.pull_request.number }}
body: |

View file

@ -30,7 +30,7 @@
</a>
</p>
<p align="center">
Love open source? <a href="https://careers.ghost.org">We're hiring</a> JavaScript engineers to work on Ghost full-time.
Love open source? <a href="https://careers.ghost.org/devops-engineer">We're hiring</a> DevOps engineers to work on Ghost full-time.
</p>
&nbsp;

View file

@ -0,0 +1 @@
tailwind.config.cjs

View file

@ -0,0 +1,56 @@
/* eslint-env node */
module.exports = {
root: true,
extends: [
'plugin:ghost/ts',
'plugin:react/recommended',
'plugin:react-hooks/recommended'
],
plugins: [
'ghost',
'react-refresh',
'tailwindcss'
],
settings: {
react: {
version: 'detect'
}
},
rules: {
// sort multiple import lines into alphabetical groups
'ghost/sort-imports-es6-autofix/sort-imports-es6': ['error', {
memberSyntaxSortOrder: ['none', 'all', 'single', 'multiple']
}],
// TODO: re-enable this (maybe fixed fast refresh?)
'react-refresh/only-export-components': 'off',
// suppress errors for missing 'import React' in JSX files, as we don't need it
'react/react-in-jsx-scope': 'off',
// ignore prop-types for now
'react/prop-types': 'off',
// TODO: re-enable these if deemed useful
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-empty-function': 'off',
// custom react rules
'react/jsx-sort-props': ['error', {
reservedFirst: true,
callbacksLast: true,
shorthandLast: true,
locale: 'en'
}],
'react/button-has-type': 'error',
'react/no-array-index-key': 'error',
'react/jsx-key': 'off',
'tailwindcss/classnames-order': ['error', {config: 'tailwind.config.cjs'}],
'tailwindcss/enforces-negative-arbitrary-values': ['warn', {config: 'tailwind.config.cjs'}],
'tailwindcss/enforces-shorthand': ['warn', {config: 'tailwind.config.cjs'}],
'tailwindcss/migration-from-tailwind-2': ['warn', {config: 'tailwind.config.cjs'}],
'tailwindcss/no-arbitrary-value': 'off',
'tailwindcss/no-custom-classname': 'off',
'tailwindcss/no-contradicting-classname': ['error', {config: 'tailwind.config.cjs'}]
}
};

3
apps/admin-x-demo/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
dist
playwright-report
test-results

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AdminX Standalone</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/standalone.tsx"></script>
</body>
</html>

View file

@ -0,0 +1,71 @@
{
"name": "@tryghost/admin-x-demo",
"version": "0.0.20",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/TryGhost/Ghost/tree/main/apps/admin-x-demo"
},
"author": "Ghost Foundation",
"files": [
"LICENSE",
"README.md",
"dist/"
],
"main": "./dist/admin-x-demo.umd.cjs",
"module": "./dist/admin-x-demo.js",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"scripts": {
"dev": "vite build --watch",
"dev:start": "vite",
"build": "tsc && vite build",
"lint": "yarn run lint:code && yarn run lint:test",
"lint:code": "eslint --ext .js,.ts,.cjs,.tsx --cache src",
"lint:test": "eslint -c test/.eslintrc.cjs --ext .js,.ts,.cjs,.tsx --cache test",
"test:unit": "yarn nx build && vitest run",
"test:acceptance": "NODE_OPTIONS='--experimental-specifier-resolution=node --no-warnings' VITE_TEST=true playwright test",
"test:acceptance:slowmo": "TIMEOUT=100000 PLAYWRIGHT_SLOWMO=100 yarn test:acceptance --headed",
"test:acceptance:full": "ALL_BROWSERS=1 yarn test:acceptance",
"preview": "vite preview"
},
"devDependencies": {
"@testing-library/react": "14.1.0",
"@tryghost/admin-x-design-system": "0.0.0",
"@tryghost/admin-x-framework": "0.0.0",
"@types/react": "18.2.41",
"@types/react-dom": "18.2.17",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"nx": {
"targets": {
"build": {
"dependsOn": [
"build",
{
"projects": [
"@tryghost/admin-x-design-system",
"@tryghost/admin-x-framework"
],
"target": "build"
}
]
},
"test:acceptance": {
"dependsOn": [
"test:acceptance",
{
"projects": [
"@tryghost/admin-x-design-system",
"@tryghost/admin-x-framework"
],
"target": "build"
}
]
}
}
}
}

View file

@ -0,0 +1,3 @@
import adminXPlaywrightConfig from '@tryghost/admin-x-framework/playwright';
export default adminXPlaywrightConfig();

View file

@ -0,0 +1 @@
module.exports = require('@tryghost/admin-x-design-system/postcss.config.cjs');

View file

@ -0,0 +1,30 @@
import MainContent from './MainContent';
import {DesignSystemApp, DesignSystemAppProps} from '@tryghost/admin-x-design-system';
import {FrameworkProvider, TopLevelFrameworkProps} from '@tryghost/admin-x-framework';
import {RoutingProvider} from '@tryghost/admin-x-framework/routing';
interface AppProps {
framework: TopLevelFrameworkProps;
designSystem: DesignSystemAppProps;
}
const modals = {
paths: {
'demo-modal': 'DemoModal'
},
load: async () => import('./components/modals')
};
const App: React.FC<AppProps> = ({framework, designSystem}) => {
return (
<FrameworkProvider {...framework}>
<RoutingProvider basePath='demo-x' modals={modals}>
<DesignSystemApp className='admin-x-demo' {...designSystem}>
<MainContent />
</DesignSystemApp>
</RoutingProvider>
</FrameworkProvider>
);
};
export default App;

View file

@ -0,0 +1,147 @@
import {Avatar, Breadcrumbs, Button, Heading, Page, Toggle, ViewContainer} from '@tryghost/admin-x-design-system';
import {useRouting} from '@tryghost/admin-x-framework/routing';
const DetailPage: React.FC = () => {
const {updateRoute} = useRouting();
return (
<Page
breadCrumbs={
<Breadcrumbs
items={[
{
label: 'Members',
onClick: () => {
updateRoute('');
}
},
{
label: 'Emerson Vaccaro'
}
]}
onBack={() => {
updateRoute('');
}}
/>
}
fullBleedPage={false}
>
<ViewContainer
firstOnPage={false}
headerContent={
<div>
<Avatar bgColor='#A5D5F7' image='https://i.pravatar.cc/150' label='EV' labelColor='white' size='2xl' />
<Heading className='mt-2' level={1}>Emerson Vaccaro</Heading>
<div className=''>Colombus, OH</div>
</div>
}
primaryAction={
{
icon: 'ellipsis',
color: 'outline'
}
}
type='page'
>
<div className='grid grid-cols-3 border-b border-grey-200 pb-5 tablet:grid-cols-4'>
<div className='col-span-3 -ml-5 mb-5 hidden h-full gap-4 px-5 tablet:!visible tablet:col-span-1 tablet:mb-0 tablet:!flex tablet:flex-col tablet:gap-0'>
<span>Last seen on <strong>22 June 2023</strong></span>
<span className='tablet:mt-2'>Created on <strong>27 Jan 2021</strong></span>
</div>
<div className='flex h-full flex-col tablet:px-5'>
<Heading level={6}>Emails received</Heading>
<span className='mt-1 text-4xl font-bold leading-none'>181</span>
</div>
<div className='flex h-full flex-col tablet:px-5'>
<Heading level={6}>Emails opened</Heading>
<span className='mt-1 text-4xl font-bold leading-none'>104</span>
</div>
<div className='-mr-5 flex h-full flex-col tablet:px-5'>
<Heading level={6}>Average open rate</Heading>
<span className='mt-1 text-4xl font-bold leading-none'>57%</span>
</div>
</div>
<div className='grid grid-cols-2 items-baseline border-b border-grey-200 py-5 tablet:grid-cols-4'>
<div className='-ml-5 flex h-full flex-col gap-6 border-r border-grey-200 px-5'>
<div className='flex justify-between'>
<Heading level={5}>Member data</Heading>
<Button color='green' label='Edit' link />
</div>
<div>
<Heading level={6}>Name</Heading>
<div>Emerson Vaccaro</div>
</div>
<div>
<Heading level={6}>Email</Heading>
<div>emerson@vaccaro.com</div>
</div>
<div>
<Heading level={6}>Labels</Heading>
<div className='mt-2 flex gap-1'>
<div className='inline-block rounded-sm bg-grey-200 px-1.5 text-xs font-medium'>VIP</div>
<div className='inline-block rounded-sm bg-grey-200 px-1.5 text-xs font-medium'>Inner Circle</div>
</div>
</div>
<div>
<Heading level={6}>Notes</Heading>
<div className='text-grey-500'>No notes.</div>
</div>
</div>
<div className='flex h-full flex-col gap-6 border-grey-200 px-5 tablet:border-r'>
<Heading level={5}>Newsletters</Heading>
<div className='flex flex-col gap-3'>
<div className='flex items-center gap-2'>
<Toggle />
<span>Daily news</span>
</div>
<div className='flex items-center gap-2'>
<Toggle />
<span>Weekly roundup</span>
</div>
<div className='flex items-center gap-2'>
<Toggle checked />
<span>The Inner Circle</span>
</div>
<div className='mt-5 rounded border border-red p-4 text-sm text-red'>
This member cannot receive emails due to permanent failure (bounce).
</div>
</div>
</div>
<div className='-ml-5 flex h-full flex-col gap-6 border-r border-grey-200 px-5 pt-10 tablet:ml-0 tablet:pt-0'>
<Heading level={5}>Subscriptions</Heading>
<div className='flex items-center gap-3'>
<div className='flex h-16 w-16 flex-col items-center justify-center rounded-md bg-grey-200'>
<Heading level={5}>$5</Heading>
<span className='text-xs text-grey-700'>Yearly</span>
</div>
<div className='flex flex-col'>
<span className='font-semibold'>Gold</span>
<span className='text-sm text-grey-500'>Renews 21 Jan 2024</span>
</div>
</div>
</div>
<div className='-mr-5 flex h-full flex-col gap-6 px-5 pt-10 tablet:pt-0'>
<div className='flex justify-between'>
<Heading level={5}>Activity</Heading>
<Button color='green' label='View all' link />
</div>
<div className='flex flex-col text-sm'>
<span className='font-semibold'>Logged in</span>
<span className='text-sm text-grey-500'>13 days ago</span>
</div>
<div className='flex flex-col text-sm'>
<span className='font-semibold'>Subscribed to Daily News</span>
<span className='text-sm text-grey-500'>17 days ago</span>
</div>
<div className='flex flex-col text-sm'>
<span className='font-semibold'>Logged in</span>
<span className='text-sm text-grey-500'>21 days ago</span>
</div>
</div>
</div>
</ViewContainer>
</Page>
);
};
export default DetailPage;

View file

@ -0,0 +1,246 @@
import {Avatar, Button, ButtonGroup, DynamicTable, DynamicTableColumn, DynamicTableRow, Heading, Hint, Page, SortMenu, Tooltip, ViewContainer, showToast} from '@tryghost/admin-x-design-system';
import {useRouting} from '@tryghost/admin-x-framework/routing';
import {useState} from 'react';
const ListPage = () => {
const {updateRoute} = useRouting();
const [view, setView] = useState<string>('list');
const dummyActions = [
<Button label='Filter' onClick={() => {
showToast({message: 'Were you really expecting a filter? 😛'});
}} />,
<SortMenu
direction='desc'
items={[
{
id: 'date-added',
label: 'Date added',
selected: true
},
{
id: 'name',
label: 'Name'
},
{
id: 'redemptions',
label: 'Open Rate'
}
]}
position="left"
onDirectionChange={() => {}}
onSortChange={() => {}}
/>,
<Tooltip content="Search members">
<Button icon='magnifying-glass' size='sm' onClick={() => {
alert('Clicked search');
}} />
</Tooltip>,
<ButtonGroup buttons={[
{
icon: 'listview',
size: 'sm',
iconColorClass: (view === 'list' ? 'text-black' : 'text-grey-500'),
onClick: () => {
setView('list');
}
},
{
icon: 'cardview',
size: 'sm',
iconColorClass: (view === 'card' ? 'text-black' : 'text-grey-500'),
onClick: () => {
setView('card');
}
}
]} clearBg={false} link />
];
const testColumns: DynamicTableColumn[] = [
{
title: 'Member',
noWrap: true,
minWidth: '1%',
maxWidth: '1%'
},
{
title: 'Status'
},
{
title: 'Open rate'
},
{
title: 'Location',
noWrap: true
},
{
title: 'Created',
noWrap: true
},
{
title: 'Signed up on post',
noWrap: true,
maxWidth: '150px'
},
{
title: 'Newsletter'
},
{
title: 'Billing period'
},
{
title: 'Email sent'
},
{
title: '',
hidden: true,
disableRowClick: true
}
];
const testRows = (noOfRows: number) => {
const data: DynamicTableRow[] = [];
for (let i = 0; i < noOfRows; i++) {
data.push(
{
onClick: () => {
updateRoute('detail');
},
cells: [
(<div className='flex items-center gap-3 whitespace-nowrap pr-10'>
<Avatar image={`https://i.pravatar.cc/150?img=${i}`} />
<div>
{i % 3 === 0 && <div className='whitespace-nowrap text-md'>Jamie Larson</div>}
{i % 3 === 1 && <div className='whitespace-nowrap text-md'>Giana Septimus</div>}
{i % 3 === 2 && <div className='whitespace-nowrap text-md'>Zaire Bator</div>}
<div className='text-grey-700'>jamie@larson.com</div>
</div>
</div>),
'Free',
'40%',
'London, UK',
<div>
<div>22 June 2023</div>
<div className='text-grey-500'>5 months ago</div>
</div>,
'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
'Subscribed',
'Monthly',
'1,303',
<Button color='green' label='Edit' link onClick={() => {
alert('Clicked Edit in row:' + i);
}} />
]
}
);
}
return data;
};
const dummyCards = (noOfCards: number) => {
const cards = [];
for (let i = 0; i < noOfCards; i++) {
cards.push(
<div className='flex min-h-[20vh] cursor-pointer flex-col items-center gap-5 rounded-sm bg-grey-100 p-7 pt-9 transition-all hover:bg-grey-200' onClick={() => {
updateRoute('detail');
}}>
<Avatar image={`https://i.pravatar.cc/150?img=${i}`} size='xl' />
<div className='flex flex-col items-center'>
<Heading level={5}>
{i % 3 === 0 && 'Jamie Larson'}
{i % 3 === 1 && 'Giana Septimus'}
{i % 3 === 2 && 'Zaire Bator'}
</Heading>
<div className='mt-1 text-sm text-grey-700'>
{i % 3 === 0 && 'jamie@larson.com'}
{i % 3 === 1 && 'giana@septimus.com'}
{i % 3 === 2 && 'zaire@bator.com'}
</div>
</div>
<div className='flex w-full flex-col gap-4 border-t border-grey-300 pt-5'>
{i % 3 === 0 && (<>
<div className='flex gap-4'>
<div className='basis-1/2 text-center'>
<Heading level={6}>Open rate</Heading>
<div className='text-lg'>83%</div>
</div>
<div className='basis-1/2 text-center'>
<Heading level={6}>Click rate</Heading>
<div className='text-lg'>19%</div>
</div>
</div>
</>)}
{i % 3 === 1 && (<>
<div className='flex gap-4'>
<div className='basis-1/2 text-center'>
<Heading level={6}>Open rate</Heading>
<div className='text-lg'>68%</div>
</div>
<div className='basis-1/2 text-center'>
<Heading level={6}>Click rate</Heading>
<div className='text-lg'>21%</div>
</div>
</div>
</>)}
{i % 3 === 2 && (<>
<div className='flex gap-4'>
<div className='basis-1/2 text-center'>
<Heading level={6}>Open rate</Heading>
<div className='text-lg'>89%</div>
</div>
<div className='basis-1/2 text-center'>
<Heading level={6}>Click rate</Heading>
<div className='text-lg'>34%</div>
</div>
</div>
</>)}
</div>
</div>
);
}
return cards;
};
let contents = <></>;
switch (view) {
case 'list':
contents = <DynamicTable
cellClassName='text-sm'
columns={testColumns}
footer={
<Hint>30 members</Hint>
}
rows={testRows(30)}
stickyFooter
stickyHeader
/>;
break;
case 'card':
contents = <div className='grid grid-cols-4 gap-8 py-8'>{dummyCards(30)}</div>;
break;
}
const demoPage = (
<Page>
<ViewContainer
actions={dummyActions}
primaryAction={{
title: 'About',
onClick: () => {
updateRoute('demo-modal');
}
}}
title='AdminX Demo App'
toolbarBorder={view === 'card'}
type='page'
>
{contents}
</ViewContainer>
</Page>
);
return demoPage;
};
export default ListPage;

View file

@ -0,0 +1,15 @@
import DetailPage from './DetailPage';
import ListPage from './ListPage';
import {useRouting} from '@tryghost/admin-x-framework/routing';
const MainContent = () => {
const {route} = useRouting();
if (route === 'detail') {
return <DetailPage />;
} else {
return <ListPage />;
}
};
export default MainContent;

View file

@ -0,0 +1,33 @@
import NiceModal from '@ebay/nice-modal-react';
import {Heading, Modal} from '@tryghost/admin-x-design-system';
import {useRouting} from '@tryghost/admin-x-framework/routing';
const DemoModal = NiceModal.create(() => {
const {updateRoute} = useRouting();
const modal = NiceModal.useModal();
return (
<Modal
afterClose={() => {
updateRoute('');
}}
cancelLabel=''
okLabel='Close'
size='sm'
title='About'
onOk={() => {
updateRoute('');
modal.remove();
}}
>
<div className='mt-3 flex flex-col gap-4'>
<p>{`You're looking at a React app inside Ghost Admin. It uses common AdminX framework and Design System packages, and works seamlessly with the current Admin's routing.`}</p>
<p>{`At the moment the look and feel follows the current Admin's style to blend in with existing pages. However the system is built in a very flexible way to allow easy updates in the future.`}</p>
<Heading className='-mb-2 mt-4' level={5}>Contents</Heading>
<p>{`The demo uses a mocked list of members — it's `}<strong>not</strong> {`the actual or future design of members in Ghost Admin. Instead, the pages showcase common design patterns like a list and detail, navigation, modals and toasts.`}</p>
</div>
</Modal>
);
});
export default DemoModal;

View file

@ -0,0 +1,9 @@
import DemoModal from './DemoModal';
import {ModalComponent} from '@tryghost/admin-x-framework/routing';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const modals = {DemoModal} satisfies {[key: string]: ModalComponent<any>};
export default modals;
export type ModalName = keyof typeof modals;

View file

@ -0,0 +1,6 @@
import './styles/index.css';
import App from './App.tsx';
export {
App as AdminXApp
};

View file

@ -0,0 +1,5 @@
import './styles/index.css';
import App from './App.tsx';
import renderStandaloneApp from '@tryghost/admin-x-framework/test/render';
renderStandaloneApp(App, {});

View file

@ -0,0 +1 @@
@import '@tryghost/admin-x-design-system/styles.css';

View file

@ -0,0 +1,6 @@
const adminXPreset = require('@tryghost/admin-x-design-system/tailwind.cjs');
module.exports = {
presets: [adminXPreset('.admin-x-demo')],
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}', '../../node_modules/@tryghost/admin-x-design-system/es/**/*.{js,ts,jsx,tsx}']
};

View file

@ -0,0 +1,6 @@
module.exports = {
plugins: ['ghost'],
extends: [
'plugin:ghost/ts-test'
]
};

View file

@ -0,0 +1,18 @@
import {expect, test} from '@playwright/test';
import {mockApi, responseFixtures} from '@tryghost/admin-x-framework/test/acceptance';
test.describe('Demo', async () => {
test('Renders the list page', async ({page}) => {
await mockApi({page, requests: {
browseSettings: {
method: 'GET',
path: /^\/settings\/\?group=/,
response: responseFixtures.settings
}
}});
await page.goto('/');
await expect(page.locator('body')).toContainText('AdminX Demo App');
});
});

View file

@ -0,0 +1,10 @@
import ListPage from '../../src/ListPage';
import {render, screen} from '@testing-library/react';
describe('Demo', function () {
it('renders a component', async function () {
render(<ListPage />);
expect(screen.getAllByRole('heading')[0].textContent).toEqual('AdminX Demo App');
});
});

View file

@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src", "test"]
}

View file

@ -0,0 +1,10 @@
import adminXViteConfig from '@tryghost/admin-x-framework/vite';
import pkg from './package.json';
import {resolve} from 'path';
export default (function viteConfig() {
return adminXViteConfig({
packageName: pkg.name,
entry: resolve(__dirname, 'src/index.tsx')
});
});

View file

@ -0,0 +1,41 @@
module.exports = {
extends: [
'plugin:ghost/ts',
'plugin:react/recommended',
'plugin:react-hooks/recommended'
],
plugins: [
'ghost',
'react-refresh',
'tailwindcss'
],
settings: {
react: {
version: 'detect'
}
},
rules: {
// suppress errors for missing 'import React' in JSX files, as we don't need it
'react/react-in-jsx-scope': 'off',
// ignore prop-types for now
'react/prop-types': 'off',
'react/jsx-sort-props': ['error', {
reservedFirst: true,
callbacksLast: true,
shorthandLast: true,
locale: 'en'
}],
'react/button-has-type': 'error',
'react/no-array-index-key': 'error',
'react/jsx-key': 'off',
'tailwindcss/classnames-order': ['error', {config: 'tailwind.config.cjs'}],
'tailwindcss/enforces-negative-arbitrary-values': ['warn', {config: 'tailwind.config.cjs'}],
'tailwindcss/enforces-shorthand': ['warn', {config: 'tailwind.config.cjs'}],
'tailwindcss/migration-from-tailwind-2': ['warn', {config: 'tailwind.config.cjs'}],
'tailwindcss/no-arbitrary-value': 'off',
'tailwindcss/no-custom-classname': 'off',
'tailwindcss/no-contradicting-classname': ['error', {config: 'tailwind.config.cjs'}]
}
};

2
apps/admin-x-design-system/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
es
types

View file

@ -0,0 +1,38 @@
import {create} from '@storybook/theming/create';
export default create({
base: 'light',
// Typography
fontBase: '"Inter", sans-serif',
fontCode: 'monospace',
brandTitle: 'AdminX Design System',
brandUrl: 'https://ghost.org',
brandImage: 'https://github.com/peterzimon/playground/assets/353959/c4358b4e-232f-4dba-8abb-adb3142ccd89',
brandTarget: '_self',
//
colorPrimary: '#30CF43',
colorSecondary: '#15171A',
// UI
appBg: '#ffffff',
appContentBg: '#ffffff',
appBorderColor: '#EBEEF0',
appBorderRadius: 0,
// Text colors
textColor: '#15171A',
textInverseColor: '#ffffff',
// Toolbar default and active colors
barTextColor: '#9E9E9E',
barSelectedColor: '#15171A',
barBg: '#ffffff',
// Form colors
inputBg: '#ffffff',
inputBorder: '#15171A',
inputTextColor: '#15171A',
inputBorderRadius: 2,
});

View file

@ -0,0 +1,27 @@
import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
{
name: '@storybook/addon-styling',
},
],
framework: {
name: "@storybook/react-vite",
options: {},
},
docs: {
autodocs: "tag",
},
async viteFinal(config, options) {
config.resolve!.alias = {
crypto: require.resolve('rollup-plugin-node-builtins')
}
return config;
}
};
export default config;

View file

@ -0,0 +1,6 @@
import {addons} from '@storybook/manager-api';
import adminxTheme from './adminx-theme';
addons.setConfig({
theme: adminxTheme
});

View file

@ -0,0 +1,107 @@
import React from 'react';
import '../styles.css';
import './storybook.css';
import type { Preview } from "@storybook/react";
import DesignSystemProvider from '../src/providers/DesignSystemProvider';
import adminxTheme from './adminx-theme';
// import { MINIMAL_VIEWPORTS } from '@storybook/addon-viewport';
const customViewports = {
sm: {
name: 'sm',
styles: {
width: '480px',
height: '801px',
},
},
md: {
name: 'md',
styles: {
width: '640px',
height: '801px',
},
},
lg: {
name: 'lg',
styles: {
width: '1024px',
height: '801px',
},
},
xl: {
name: 'xl',
styles: {
width: '1320px',
height: '801px',
},
},
tablet: {
name: 'tablet',
styles: {
width: '860px',
height: '801px',
},
},
};
const preview: Preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
options: {
storySort: {
method: 'alphabetical',
order: ['Welcome', 'Foundations', ['Style Guide', 'Colors', 'Icons', 'ErrorHandling'], 'Global', ['Form', 'Chrome', 'Modal', 'Layout', ['View Container', 'Page Header', 'Page'], 'List', 'Table', '*'], 'Settings', ['Setting Section', 'Setting Group', '*'], 'Experimental'],
},
},
docs: {
theme: adminxTheme,
},
viewport: {
viewports: {
...customViewports,
},
},
},
decorators: [
(Story, context) => {
let {scheme} = context.globals;
return (
<div className={`admin-x-design-system admin-x-base ${scheme === 'dark' ? 'dark' : ''}`} style={{
// padding: '24px',
// width: 'unset',
height: 'unset',
// overflow: 'unset',
background: (scheme === 'dark' ? '#131416' : '')
}}>
{/* 👇 Decorators in Storybook also accept a function. Replace <Story/> with Story() to enable it */}
<DesignSystemProvider fetchKoenigLexical={async () => {}}>
<Story />
</DesignSystemProvider>
</div>);
},
],
globalTypes: {
scheme: {
name: "Scheme",
description: "Select light or dark mode",
defaultValue: "light",
toolbar: {
icon: "mirror",
items: ["light", "dark"],
dynamicTitle: true
}
}
}
};
export default preview;

View file

@ -0,0 +1,247 @@
/*
* We load Inter in Ember admin, so loading it explicitly here makes the final rendering
* in Storybook match the final rendering when embedded in Ember
*/
@font-face {
font-family: "Inter";
src: url("./Inter.ttf") format("truetype-variations");
font-weight: 100 900;
}
:root {
font-size: 62.5%;
line-height: 1.5;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
html, body, #root {
width: 100%;
height: 100%;
margin: 0;
letter-spacing: unset;
}
.sbdocs-wrapper {
padding: 3vmin !important;
}
.sbdocs-wrapper .sbdocs-content {
max-width: 1320px;
}
.sb-doc {
max-width: 740px;
width: 100%;
margin: 0 auto !important;
}
.sb-doc,
.sb-doc a,
.sb-doc h1,
.sb-doc h2,
.sb-doc h3,
.sb-doc h4,
.sb-doc h5,
.sb-doc h6,
.sb-doc p,
.sb-doc ul li,
.sbdocs-title,
.sb-doc ol li {
font-family: Inter, sans-serif !important;
padding: 0 !important;
}
.sb-doc a {
color: #30CF43;
}
.sb-doc h1 {
font-size: 48px !important;
letter-spacing: -0.04em !important;
margin-bottom: 20px;
}
.sb-doc h2 {
margin-top: 40px !important;
font-size: 27px;
border: none;
margin-bottom: 2px;
}
.sb-doc h3 {
margin-top: 40px !important;
margin-bottom: 4px !important;
font-size: 20px;
}
.sb-doc h4 {
margin: 0 0 4px !important;
}
.sb-doc p,
.sb-doc div,
.sb-doc ul li,
.sb-doc ol li {
font-size: 15px;
line-height: 1.5em;
}
.sb-doc ul li,
.sb-doc ol li {
margin-bottom: 8px;
}
.sb-doc h2 + p,
.sb-doc h3 + p {
margin-top: 8px;
}
.sb-doc img,
.sb-wide img {
margin-top: 40px !important;
margin-bottom: 40px !important;
}
.sb-doc img.small {
max-width: 520px;
margin: 0 auto;
display: block;
}
.sb-doc p.excerpt {
font-size: 19px;
letter-spacing: -0.02em;
}
.sb-doc .highlight {
padding: 12px 20px;
border-radius: 4px;
background: #EBEEF0;
}
.sb-doc .highlight.purple {
background: #F0E9FA;
}
.sb-doc .highlight.purple a {
color: #8E42FF;
}
/* Welcome */
.sb-doc img.main-image {
margin-top: -2vmin !important;
margin-left: -44px;
margin-right: -32px;
margin-bottom: 0 !important;
max-width: unset;
width: calc(100% + 64px);
}
.sb-doc .main-structure-container {
display: flex;
gap: 32px;
margin: 32px 0 80px;
}
.sb-doc .main-structure-container div {
flex-basis: 33%;
}
.sb-doc .main-structure-container div p {
display: flex;
flex-direction: column;
gap: 4px;
}
.sb-doc .main-structure-container img {
margin: 12px 0 !important;
width: 32px;
height: 32px;
}
.sb-doc .main-structure-container div h4 {
border-bottom: 1px solid #EBEEF0;
padding-bottom: 8px !important;
margin-bottom: 8px !important;
}
.sb-doc .main-structure-container div p {
margin: 0;
font-size: 13.5px;
}
/* Colors */
.color-grid {
display: flex;
gap: 20px;
flex-wrap: wrap;
margin-top: 20px;
}
.color-grid div {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 12px;
border-radius: 4px;
border: 1px solid #EBEEF0;
}
.color-grid .swatch {
display: block;
background: #EFEFEF;
border-radius: 100%;
width: 28px;
height: 28px;
}
.swatch.green {
background: #30CF43;
}
.swatch.black {
background: #15171A;
}
.swatch.white {
background: #FFFFFF;
border: 1px solid #EBEEF0;
}
.swatch.lime {
background: #B5FF18;
}
.swatch.blue {
background: #14B8FF;
}
.swatch.purple {
background: #8E42FF;
}
.swatch.pink {
background: #FB2D8D;
}
.swatch.yellow {
background: #FFB41F;
}
.swatch.red {
background: #F50B23;
}
/* Icons */
.sb-doc .streamline {
display: grid;
grid-template-columns: auto 240px;
gap: 32px;
}
.sbdocs-a {
color: #30CF43 !important;
}

View file

@ -0,0 +1,23 @@
# Admin X Design
Components, design guidelines and documentation for building apps in Ghost Admin
## Usage
## Develop
This is a monorepo package.
Follow the instructions for the top-level repo.
1. `git clone` this repo & `cd` into it as usual
2. Run `yarn` to install top-level dependencies.
## Test
- `yarn lint` run just eslint
- `yarn test` run lint and tests

View file

@ -0,0 +1,76 @@
{
"name": "@tryghost/admin-x-design-system",
"type": "module",
"version": "0.0.0",
"repository": "https://github.com/TryGhost/Ghost/tree/main/packages/admin-x-design-system",
"author": "Ghost Foundation",
"private": true,
"main": "es/index.js",
"types": "types/index.d.ts",
"sideEffects": false,
"scripts": {
"build": "vite build && tsc -p tsconfig.declaration.json",
"prepare": "yarn build",
"test": "yarn test:types",
"test:types": "tsc --noEmit",
"lint:code": "eslint --ext .js,.ts,.cjs,.tsx src/ --cache",
"lint": "yarn lint:code && yarn lint:test",
"lint:test": "eslint -c test/.eslintrc.cjs --ext .js,.ts,.cjs,.tsx test/ --cache",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"files": [
"es",
"types",
"tailwind.cjs",
"tailwind.config.cjs"
],
"devDependencies": {
"@codemirror/lang-html": "^6.4.5",
"@storybook/addon-essentials": "7.5.3",
"@storybook/addon-interactions": "7.5.3",
"@storybook/addon-links": "7.5.3",
"@storybook/addon-styling": "1.3.7",
"@storybook/blocks": "7.5.3",
"@storybook/react": "7.5.3",
"@storybook/react-vite": "7.5.3",
"@storybook/testing-library": "0.2.2",
"@testing-library/react": "14.1.0",
"@vitejs/plugin-react": "4.2.0",
"c8": "8.0.1",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-react-refresh": "0.4.3",
"eslint-plugin-tailwindcss": "3.13.0",
"mocha": "10.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rollup-plugin-node-builtins": "2.1.2",
"sinon": "17.0.0",
"storybook": "7.5.3",
"ts-node": "10.9.1",
"typescript": "5.3.2",
"vite": "4.5.1",
"vite-plugin-svgr": "3.3.0"
},
"dependencies": {
"@dnd-kit/core": "6.1.0",
"@dnd-kit/sortable": "7.0.2",
"@ebay/nice-modal-react": "1.2.13",
"@sentry/react": "7.84.0",
"@tailwindcss/forms": "0.5.7",
"@tailwindcss/line-clamp": "0.4.4",
"@uiw/react-codemirror": "^4.21.9",
"autoprefixer": "10.4.16",
"clsx": "2.0.0",
"postcss": "8.4.32",
"postcss-import": "15.1.0",
"react-colorful": "^5.1.2",
"react-hot-toast": "2.4.1",
"react-select": "5.8.0",
"tailwindcss": "3.3.5"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}

View file

@ -0,0 +1,8 @@
module.exports = {
plugins: {
'postcss-import': {},
'tailwindcss/nesting': {},
tailwindcss: {},
autoprefixer: {}
}
};

View file

@ -0,0 +1,381 @@
.admin-x-base {
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box; /* 1 */
max-width: revert;
max-height: revert;
min-width: revert;
min-height: revert;
border-width: 0; /* 2 */
border-style: solid; /* 2 */
border-color: theme('borderColor.DEFAULT', currentColor); /* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
*/
html {
line-height: 1.5; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
-moz-tab-size: 4; /* 3 */
tab-size: 4; /* 3 */
font-family: theme('fontFamily.sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); /* 4 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0; /* 1 */
line-height: inherit; /* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0; /* 1 */
color: inherit; /* 2 */
border-top-width: 1px; /* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0;
padding: 0;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font family by default.
2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: theme('fontFamily.mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); /* 1 */
font-size: 1em; /* 2 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0; /* 1 */
border-color: inherit; /* 2 */
border-collapse: collapse; /* 3 */
margin: 0;
width: auto;
max-width: auto;
}
table td, table th {
padding: unset;
vertical-align: middle;
text-align: left;
line-height: auto;
-webkit-user-select: text;
-moz-user-select: text;
user-select: text;
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
font-weight: inherit; /* 1 */
line-height: inherit; /* 1 */
color: inherit; /* 1 */
margin: 0; /* 2 */
padding: 0; /* 3 */
outline: none;
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
letter-spacing: inherit;
border-radius: inherit;
appearance: auto;
-webkit-appearance: auto;
background: unset;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
/* [type='button'], */
[type='reset'],
[type='submit'] {
-webkit-appearance: button; /* 1 */
background-color: transparent; /* 2 */
background-image: none; /* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: none;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
li {
margin: unset;
line-height: unset;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::placeholder,
textarea::placeholder {
opacity: 1; /* 1 */
@apply text-grey-500; /* 2 */
}
button:focus-visible,
input:focus-visible {
outline: none;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block; /* 1 */
vertical-align: middle; /* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
}

View file

@ -0,0 +1,18 @@
import type {Meta, StoryObj} from '@storybook/react';
import BoilerPlate from './Boilerplate';
const meta = {
title: 'Meta / Boilerplate',
component: BoilerPlate,
tags: ['autodocs']
} satisfies Meta<typeof BoilerPlate>;
export default meta;
type Story = StoryObj<typeof BoilerPlate>;
export const Default: Story = {
args: {
children: 'This is a boilerplate component. Use as a basis to create new components.'
}
};

View file

@ -0,0 +1,27 @@
import clsx from 'clsx';
import React from 'react';
import {FetchKoenigLexical} from './global/form/HtmlEditor';
import DesignSystemProvider from './providers/DesignSystemProvider';
export interface DesignSystemAppProps extends React.HTMLProps<HTMLDivElement> {
darkMode: boolean;
fetchKoenigLexical: FetchKoenigLexical;
}
const DesignSystemApp: React.FC<DesignSystemAppProps> = ({darkMode, fetchKoenigLexical, className, children, ...props}) => {
const appClassName = clsx(
'admin-x-base',
darkMode && 'dark',
className
);
return (
<div className={appClassName} {...props}>
<DesignSystemProvider fetchKoenigLexical={fetchKoenigLexical}>
{children}
</DesignSystemProvider>
</div>
);
};
export default DesignSystemApp;

View file

Before

Width:  |  Height:  |  Size: 401 B

After

Width:  |  Height:  |  Size: 401 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="-0.75 -0.75 24 24" height="24" width="24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M13.341093749999999 17.55496875c2.03146875 -0.408375 3.667125 -2.0639062499999996 4.07615625 -4.14796875 0.40903125 2.0840625 2.0442187499999998 3.73959375 4.07578125 4.14796875m0 0.00234375c-2.0315624999999997 0.408375 -3.667125 2.0639062499999996 -4.07615625 4.14796875 -0.40903125 -2.0840625 -2.0443125 -3.73959375 -4.07578125 -4.14796875" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m19.54621875 12.32025 0.56521875 -0.56521875c0.53071875 -0.53071875 0.8272499999999999 -1.25146875 0.8236875 -2.00203125l-0.0271875 -5.777896875000001c-0.00721875 -1.5429374999999999 -1.25625 -2.791940625 -2.7991875 -2.799225l-5.778 -0.027290625c-0.7505625 -0.003553125 -1.4713124999999998 0.293034375 -2.00203125 0.82374375L1.32765 10.97353125c-0.732223125 0.7321875 -0.7322203125000001 1.91934375 0.000009375 2.6516249999999997l7.13105625 7.131c0.732234375 0.73228125 1.9194093749999999 0.73228125 2.6516906249999996 0l0.94190625 -0.94190625" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M17.75428125 4.329000000000001c-0.1393125 -0.13935 -0.41803125 -0.139359375 -0.5574375 0 -0.1393125 0.13935 -0.1393125 0.418059375 0 0.557409375" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M17.7553125 4.328221875c0.13940625 0.13935 0.13940625 0.418059375 0 0.55741875 -0.1393125 0.13935 -0.41803125 0.13934062500000002 -0.55734375 -0.000009375" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

Before

Width:  |  Height:  |  Size: 364 B

After

Width:  |  Height:  |  Size: 364 B

View file

Before

Width:  |  Height:  |  Size: 363 B

After

Width:  |  Height:  |  Size: 363 B

View file

@ -0,0 +1 @@
<svg viewBox="-0.75 -0.75 24 24" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="m7.152187499999999 4.21875 -6.0375000000000005 6.0365625000000005a1.40625 1.40625 0 0 0 0 1.9884375l6.0375000000000005 6.0375000000000005" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m15.347812499999998 4.21875 6.0375000000000005 6.0365625000000005a1.40625 1.40625 0 0 1 0 1.9884375l-6.0375000000000005 6.0375000000000005" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 608 B

View file

Before

Width:  |  Height:  |  Size: 451 B

After

Width:  |  Height:  |  Size: 451 B

View file

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 450 B

View file

Before

Width:  |  Height:  |  Size: 452 B

After

Width:  |  Height:  |  Size: 452 B

View file

Before

Width:  |  Height:  |  Size: 354 B

After

Width:  |  Height:  |  Size: 354 B

View file

Before

Width:  |  Height:  |  Size: 354 B

After

Width:  |  Height:  |  Size: 354 B

View file

Before

Width:  |  Height:  |  Size: 448 B

After

Width:  |  Height:  |  Size: 448 B

View file

@ -0,0 +1 @@
<svg viewBox="-0.75 -0.75 24 24" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="M16.171875 11.25A4.921875 4.921875 0 1 1 11.25 6.328125 4.921875 4.921875 0 0 1 16.171875 11.25Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M16.171875 11.25v2.109375a2.8125 2.8125 0 0 0 5.625 0V11.25a10.5459375 10.5459375 0 1 0 -4.21875 8.4375" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 532 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="-0.75 -0.75 24 24" height="24" width="24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M0.9375 20.0625h1.8403125" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M19.723125 20.0625H21.5625" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M15.02625 20.0625h1.8403125" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M10.3303125 20.0625h1.839375" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M5.6343749999999995 20.0625h1.839375" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m0.9375 16.53 4.790625 -6.511875a3.1565625 3.1565625 0 0 1 3.1753125 -1.2225000000000001l4.685625 0.9590624999999999a3.1565625 3.1565625 0 0 0 3.17625 -1.2215624999999999l4.790625 -6.511875" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="-0.75 -0.75 24 24" height="24" width="24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M17.578125 4.21875H2.109375A1.40625 1.40625 0 0 0 0.703125 5.625v8.4375a1.40625 1.40625 0 0 0 1.40625 1.40625h15.46875a1.40625 1.40625 0 0 0 1.40625 -1.40625V5.625a1.40625 1.40625 0 0 0 -1.40625 -1.40625Z" stroke-width="1.5"></path><path stroke="currentColor" d="M3.8671875 7.734375a0.3515625 0.3515625 0 1 1 0 -0.703125" stroke-width="1.5"></path><path stroke="currentColor" d="M3.8671875 7.734375a0.3515625 0.3515625 0 1 0 0 -0.703125" stroke-width="1.5"></path><path stroke="currentColor" d="M15.8203125 12.65625a0.3515625 0.3515625 0 0 1 0 -0.703125" stroke-width="1.5"></path><path stroke="currentColor" d="M15.8203125 12.65625a0.3515625 0.3515625 0 0 0 0 -0.703125" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M9.84375 12.65625a2.8125 2.8125 0 1 0 0 -5.625 2.8125 2.8125 0 0 0 0 5.625Z" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M21.796875 8.4375v8.4375a1.40625 1.40625 0 0 1 -1.40625 1.40625H4.921875" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1 @@
<svg viewBox="-0.75 -0.75 24 24" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="M12.1875 21.474375a15.9271875 15.9271875 0 0 1 8.3025 -3.646875 1.5 1.5 0 0 0 1.3040625000000001 -1.4878125V2.2171875a1.5121875 1.5121875 0 0 0 -1.7203125 -1.5A16.009687500000002 16.009687500000002 0 0 0 12.1875 4.3125a1.53375 1.53375 0 0 1 -1.875 0A16.009687500000002 16.009687500000002 0 0 0 2.4234375 0.7190625 1.5121875 1.5121875 0 0 0 0.703125 2.2171875v14.1225a1.5 1.5 0 0 0 1.3040625000000001 1.4878125A15.9271875 15.9271875 0 0 1 10.3125 21.474375a1.5309375 1.5309375 0 0 0 1.875 0Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m11.25 4.629375 0 17.1665625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 851 B

View file

@ -0,0 +1 @@
<svg viewBox="-0.75 -0.75 24 24" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="m7.152187499999999 4.21875 -6.0375000000000005 6.0365625000000005a1.40625 1.40625 0 0 0 0 1.9884375l6.0375000000000005 6.0375000000000005" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m15.347812499999998 4.21875 6.0375000000000005 6.0365625000000005a1.40625 1.40625 0 0 1 0 1.9884375l-6.0375000000000005 6.0375000000000005" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 608 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.75 -0.75 24 24" height="24" width="24"><defs></defs><title>layout-module-1</title><path d="M2.109375 0.7003125h5.625s1.40625 0 1.40625 1.40625v5.625s0 1.40625 -1.40625 1.40625h-5.625s-1.40625 0 -1.40625 -1.40625v-5.625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M2.109375 13.356562499999999h5.625s1.40625 0 1.40625 1.40625v5.625s0 1.40625 -1.40625 1.40625h-5.625s-1.40625 0 -1.40625 -1.40625v-5.625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M14.765625 0.7003125h5.625s1.40625 0 1.40625 1.40625v5.625s0 1.40625 -1.40625 1.40625h-5.625s-1.40625 0 -1.40625 -1.40625v-5.625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M14.765625 13.356562499999999h5.625s1.40625 0 1.40625 1.40625v5.625s0 1.40625 -1.40625 1.40625h-5.625s-1.40625 0 -1.40625 -1.40625v-5.625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 423 B

After

Width:  |  Height:  |  Size: 423 B

View file

Before

Width:  |  Height:  |  Size: 275 B

After

Width:  |  Height:  |  Size: 275 B

View file

Before

Width:  |  Height:  |  Size: 315 B

After

Width:  |  Height:  |  Size: 315 B

View file

Before

Width:  |  Height:  |  Size: 313 B

After

Width:  |  Height:  |  Size: 313 B

View file

Before

Width:  |  Height:  |  Size: 311 B

After

Width:  |  Height:  |  Size: 311 B

View file

Before

Width:  |  Height:  |  Size: 314 B

After

Width:  |  Height:  |  Size: 314 B

View file

Before

Width:  |  Height:  |  Size: 417 B

After

Width:  |  Height:  |  Size: 417 B

View file

Before

Width:  |  Height:  |  Size: 352 B

After

Width:  |  Height:  |  Size: 352 B

View file

Before

Width:  |  Height:  |  Size: 502 B

After

Width:  |  Height:  |  Size: 502 B

View file

@ -0,0 +1 @@
<svg viewBox="-0.75 -0.75 24 24" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="m2.109375 20.390625 18.28125 -18.28125Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M14.765625 17.578125a2.8125 2.8125 0 1 0 5.625 0 2.8125 2.8125 0 1 0 -5.625 0Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M2.109375 4.921875a2.8125 2.8125 0 1 0 5.625 0 2.8125 2.8125 0 1 0 -5.625 0Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 645 B

View file

Before

Width:  |  Height:  |  Size: 642 B

After

Width:  |  Height:  |  Size: 642 B

View file

Before

Width:  |  Height:  |  Size: 388 B

After

Width:  |  Height:  |  Size: 388 B

View file

@ -0,0 +1 @@
<svg viewBox="-0.75 -0.75 24 24" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="M10.546875 16.171875a5.625 5.625 0 1 0 11.25 0 5.625 5.625 0 1 0 -11.25 0Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m18.67875 14.536875 -2.7234374999999997 3.6309375000000004a0.705 0.705 0 0 1 -1.0603125 0.0759375l-1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M7.734375 14.765625h-5.625a1.40625 1.40625 0 0 1 -1.40625 -1.40625v-11.25a1.40625 1.40625 0 0 1 1.40625 -1.40625h16.875a1.40625 1.40625 0 0 1 1.40625 1.40625V8.4375" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m20.0728125 1.21875 -7.635 5.8725000000000005a3.10125 3.10125 0 0 1 -3.781875 0L1.0209375 1.21875" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 1,019 B

View file

@ -0,0 +1 @@
<svg viewBox="-0.75 -0.75 24 24" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="m1.40625 4.453125 19.6875 0 0 14.0625 -19.6875 0Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m20.7759375 4.96875 -7.635 5.8725000000000005a3.10125 3.10125 0 0 1 -3.781875 0L1.7240625 4.96875" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 479 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.75 -0.75 24 24" height="24" width="24"><defs></defs><path d="M21.796875 12.421875v5.859375a0.9375 0.9375 0 0 1 -0.9375 0.9375H1.640625a0.9375 0.9375 0 0 1 -0.9375 -0.9375V8.671875a0.9375 0.9375 0 0 1 0.9375 -0.9375H8.4375" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M18.125625 13.300312499999999A5.15625 5.15625 0 1 1 21.5625 8.4375" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M14.6878125 8.4375a1.7184375 1.7184375 0 1 0 3.436875 0 1.7184375 1.7184375 0 1 0 -3.436875 0" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M18.1246875 8.4375A1.719375 1.719375 0 0 0 21.5625 8.4375" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m4.3706249999999995 10.9378125 0 5.077500000000001" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 347 B

After

Width:  |  Height:  |  Size: 347 B

View file

@ -0,0 +1 @@
<svg viewBox="-0.75 -0.75 24 24" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="M16.996875 7.265625h-3.99375V5.475a0.9375 0.9375 0 0 1 0.9375 -1.03125h2.8125v-3.75h-4.059375c-3.684375 0 -4.378125 2.8125 -4.378125 4.55625v2.015625h-2.8125v3.75h2.8125v10.78125h4.6875v-10.78125h3.609375Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 420 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="-0.75 -0.75 24 24" height="24" width="24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M6.140625 10.828125c-1.78125 0 -3.28125 1.5 -3.28125 3.28125 0 1.5 0.375 3 1.21875 4.3125l0.65625 1.125c0.84375 1.40625 2.4375 2.25 4.03125 2.25h6.1875c2.625 0 4.6875 -2.0625 4.6875 -4.6875v-6.84375c0 -0.9375 -0.75 -1.6875 -1.6875 -1.6875s-1.6875 0.75 -1.6875 1.6875v-0.9375c0 -0.9375 -0.75 -1.6875 -1.6875 -1.6875s-1.6875 0.75 -1.6875 1.6875v0.28125l0 -0.75c0 -0.9375 -0.75 -1.6875 -1.6875 -1.6875s-1.6875 0.75 -1.6875 1.6875l0 0.215625m0 0.5343749999999999 0 -0.5343749999999999m-3.375 4.753125000000001V2.390625c0 -0.9375 0.75 -1.6875 1.6875 -1.6875s1.6875 0.75 1.6875 1.6875l0 6.684375" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 827 B

View file

Before

Width:  |  Height:  |  Size: 587 B

After

Width:  |  Height:  |  Size: 587 B

View file

Before

Width:  |  Height:  |  Size: 469 B

After

Width:  |  Height:  |  Size: 469 B

View file

@ -0,0 +1 @@
<svg viewBox="-0.75 -0.75 24 24" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="M11.8640625 16.8684375a4.273125 4.273125 0 0 1 -5.6690625 2.041875h0a4.273125 4.273125 0 0 1 -2.041875 -5.6690625l1.2956249999999998 -2.7534375a4.2721875 4.2721875 0 0 1 5.668125 -2.041875h0a4.2590625 4.2590625 0 0 1 2.3540625 2.9915624999999997" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M11.105625 5.7253125a4.273125 4.273125 0 0 1 5.6690625 -2.041875h0a4.273125 4.273125 0 0 1 2.041875 5.668125l-1.2956249999999998 2.7534375a4.273125 4.273125 0 0 1 -5.6690625 2.041875h0a4.2496875 4.2496875 0 0 1 -2.205 -2.4553125000000002" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 815 B

View file

Before

Width:  |  Height:  |  Size: 848 B

After

Width:  |  Height:  |  Size: 848 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.75 -0.75 24 24" height="24" width="24"><g><path d="M12.01875 13.603125 14.399999999999999 11.25l1.65 0.440625a1.4625000000000001 1.4625000000000001 0 0 0 1.415625 -0.440625 1.4812500000000002 1.4812500000000002 0 0 0 0.346875 -1.396875l-0.440625 -1.640625 0.7687499999999999 -0.7125 1.65 0.440625A1.4625000000000001 1.4625000000000001 0 0 0 21.20625 7.5 1.4812500000000002 1.4812500000000002 0 0 0 21.5625 6.1125l-0.440625 -1.640625a2.203125 2.203125 0 0 0 -3.121875 -3.121875l-9.103125 9.13125a5.896875 5.896875 0 1 0 3.121875 3.121875Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M3.99375 16.725a1.78125 1.78125 0 1 0 3.5625 0 1.78125 1.78125 0 1 0 -3.5625 0" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></g></svg>

After

Width:  |  Height:  |  Size: 904 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="-0.75 -0.75 24 24" height="24" width="24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M6.305625 0.703125h9.84375" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M14.743125000000001 7.734375V0.703125h-7.03125v7.03125L1.3959375 17.451562499999998A2.8125 2.8125 0 0 0 3.75 21.796875h14.95125a2.8125 2.8125 0 0 0 2.3578125 -4.3453124999999995L14.743125000000001 7.734375Z" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M4.9696875 11.953125h12.515625" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M13.336875000000001 16.171875h2.8125" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M14.743125000000001 14.765625v2.8125" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M14.743125000000001 3.515625h-2.8125" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M14.743125000000001 6.328125h-2.8125" stroke-width="1.5"></path><path stroke="currentColor" d="M6.305625 18.6328125a0.3515625 0.3515625 0 0 1 0 -0.703125" stroke-width="1.5"></path><path stroke="currentColor" d="M6.305625 18.6328125a0.3515625 0.3515625 0 0 0 0 -0.703125" stroke-width="1.5"></path><g><path stroke="currentColor" d="M9.118125000000001 15.8203125a0.3515625 0.3515625 0 0 1 0 -0.703125" stroke-width="1.5"></path><path stroke="currentColor" d="M9.118125000000001 15.8203125a0.3515625 0.3515625 0 0 0 0 -0.703125" stroke-width="1.5"></path></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.75 -0.75 24 24" height="24" width="24"><g><path d="M2.109375 0.703125h8.4375s1.40625 0 1.40625 1.40625v8.4375s0 1.40625 -1.40625 1.40625h-8.4375s-1.40625 0 -1.40625 -1.40625v-8.4375s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M14.765625 10.546875h5.625a1.40625 1.40625 0 0 1 1.40625 1.40625v8.4375a1.40625 1.40625 0 0 1 -1.40625 1.40625h-8.4375a1.40625 1.40625 0 0 1 -1.40625 -1.40625v-5.625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m14.53125 16.875 3.28125 0" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><g><path d="m6.328125 3.515625 0 1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m3.515625 4.921875 5.625 0" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M7.734375 4.921875s-1.40625 4.21875 -4.21875 4.21875" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M6.328125 7.5a3.675 3.675 0 0 0 2.8125 1.621875" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></g><path d="M14.53125 18.984375v-3.75a1.640625 1.640625 0 0 1 3.28125 0v3.75" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></g></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

Before

Width:  |  Height:  |  Size: 636 B

After

Width:  |  Height:  |  Size: 636 B

View file

@ -0,0 +1 @@
<svg viewBox="-0.75 -0.75 24 24" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="M21.478125 6.5184375 11.90625 1.5675a1.4465625 1.4465625 0 0 0 -1.3275 0L1.00875 6.5184375a0.5765625 0.5765625 0 0 0 0 1.025625l9.5709375 4.950937499999999a1.4465625 1.4465625 0 0 0 1.3275 0L21.478125 7.544062500000001a0.5775 0.5775 0 0 0 0 -1.025625Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m0.7106250000000001 11.953125 9.8690625 4.760625a1.4465625 1.4465625 0 0 0 1.3275 0l9.897187500000001 -4.760625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m0.7106250000000001 16.171875 9.8690625 4.760625a1.4465625 1.4465625 0 0 0 1.3275 0l9.897187500000001 -4.760625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 924 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.75 -0.75 24 24" height="24" width="24"><defs></defs><title>layout-headline</title><path d="M2.109375 0.7003125h18.28125s1.40625 0 1.40625 1.40625v1.40625s0 1.40625 -1.40625 1.40625H2.109375s-1.40625 0 -1.40625 -1.40625v-1.40625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M2.109375 9.137812499999999h18.28125s1.40625 0 1.40625 1.40625v1.40625s0 1.40625 -1.40625 1.40625H2.109375s-1.40625 0 -1.40625 -1.40625v-1.40625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M2.109375 17.5753125h18.28125s1.40625 0 1.40625 1.40625v1.40625s0 1.40625 -1.40625 1.40625H2.109375s-1.40625 0 -1.40625 -1.40625v-1.40625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 995 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.75 -0.75 24 24" height="24" width="24"><defs></defs><title>layout-module-1</title><path d="M2.109375 0.7003125h5.625s1.40625 0 1.40625 1.40625v5.625s0 1.40625 -1.40625 1.40625h-5.625s-1.40625 0 -1.40625 -1.40625v-5.625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M2.109375 13.356562499999999h5.625s1.40625 0 1.40625 1.40625v5.625s0 1.40625 -1.40625 1.40625h-5.625s-1.40625 0 -1.40625 -1.40625v-5.625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M14.765625 0.7003125h5.625s1.40625 0 1.40625 1.40625v5.625s0 1.40625 -1.40625 1.40625h-5.625s-1.40625 0 -1.40625 -1.40625v-5.625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M14.765625 13.356562499999999h5.625s1.40625 0 1.40625 1.40625v5.625s0 1.40625 -1.40625 1.40625h-5.625s-1.40625 0 -1.40625 -1.40625v-5.625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="-0.75 -0.75 24 24" height="24" width="24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M0.78375 9.6103125h1.3031249999999999c1.966875 0 3.855 -0.0684375 5.257499999999999 -1.4465625a7.5 7.5 0 0 0 2.2424999999999997 -5.2190625c0 -3.1734375 4.010624999999999 -1.6875 4.010624999999999 1.14375v3.646875a1.875 1.875 0 0 0 1.875 1.875h4.414687499999999c0.9806250000000001 0 1.8046875 0.7565625 1.8234375 1.7371874999999999 0.061875 3.1275 -0.459375 5.4028125 -1.7240625 7.824375 -0.729375 1.396875 -2.2434374999999998 2.175 -3.8184375000000004 2.1403125C5.2228125 21.065624999999997 6.6384375 19.21875 0.78375 19.21875" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 741 B

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1 @@
<svg viewBox="-0.75 -0.75 24 24" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="M5.315625 21.215625H0.759375V8.15625h4.55625Zm9.459375 -8.803125000000001a2.00625 2.00625 0 0 0 -2.00625 2.00625v6.796875H7.9781249999999995V8.15625h4.790625v1.490625a6.3374999999999995 6.3374999999999995 0 0 1 4.0125 -1.5c2.971875 0 5.034375 2.203125 5.034375 6.3843749999999995v6.684375H16.78125v-6.796875a2.00625 2.00625 0 0 0 -2.00625 -2.015625Zm-9.375 -8.774999999999999a2.34375 2.34375 0 1 1 -2.34375 -2.34375 2.34375 2.34375 0 0 1 2.325 2.34375Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 667 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.75 -0.75 24 24" height="24" width="24"><defs></defs><title>layout-headline</title><path d="M2.109375 0.7003125h18.28125s1.40625 0 1.40625 1.40625v1.40625s0 1.40625 -1.40625 1.40625H2.109375s-1.40625 0 -1.40625 -1.40625v-1.40625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M2.109375 9.137812499999999h18.28125s1.40625 0 1.40625 1.40625v1.40625s0 1.40625 -1.40625 1.40625H2.109375s-1.40625 0 -1.40625 -1.40625v-1.40625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M2.109375 17.5753125h18.28125s1.40625 0 1.40625 1.40625v1.40625s0 1.40625 -1.40625 1.40625H2.109375s-1.40625 0 -1.40625 -1.40625v-1.40625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 995 B

View file

Before

Width:  |  Height:  |  Size: 543 B

After

Width:  |  Height:  |  Size: 543 B

Some files were not shown because too many files have changed in this diff Show more