Compare commits
2 Commits
feature/do
...
feature/br
Author | SHA1 | Date | |
---|---|---|---|
283c9ecb27 | |||
0e5d92258d |
@ -1,34 +0,0 @@
|
|||||||
**/*.log
|
|
||||||
**/*.md
|
|
||||||
**/*.php~
|
|
||||||
**/*.dist.php
|
|
||||||
**/*.dist
|
|
||||||
**/*.cache
|
|
||||||
**/._*
|
|
||||||
**/.dockerignore
|
|
||||||
**/.DS_Store
|
|
||||||
**/.git/
|
|
||||||
**/.gitattributes
|
|
||||||
**/.gitignore
|
|
||||||
**/.gitmodules
|
|
||||||
**/compose.*.yaml
|
|
||||||
**/compose.*.yml
|
|
||||||
**/compose.yaml
|
|
||||||
**/compose.yml
|
|
||||||
**/docker-compose.*.yaml
|
|
||||||
**/docker-compose.*.yml
|
|
||||||
**/docker-compose.yaml
|
|
||||||
**/docker-compose.yml
|
|
||||||
**/Dockerfile
|
|
||||||
**/Thumbs.db
|
|
||||||
.github/
|
|
||||||
docs/
|
|
||||||
public/bundles/
|
|
||||||
tests/
|
|
||||||
var/
|
|
||||||
vendor/
|
|
||||||
.editorconfig
|
|
||||||
.env.*.local
|
|
||||||
.env.local
|
|
||||||
.env.local.php
|
|
||||||
.env.test
|
|
76
.github/workflows/ci.yml
vendored
76
.github/workflows/ci.yml
vendored
@ -1,76 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request: ~
|
|
||||||
workflow_dispatch: ~
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
tests:
|
|
||||||
name: Tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
-
|
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
-
|
|
||||||
name: Build Docker images
|
|
||||||
uses: docker/bake-action@v6
|
|
||||||
with:
|
|
||||||
pull: true
|
|
||||||
load: true
|
|
||||||
files: |
|
|
||||||
compose.yaml
|
|
||||||
compose.override.yaml
|
|
||||||
set: |
|
|
||||||
*.cache-from=type=gha,scope=${{github.ref}}
|
|
||||||
*.cache-from=type=gha,scope=refs/heads/main
|
|
||||||
*.cache-to=type=gha,scope=${{github.ref}},mode=max
|
|
||||||
-
|
|
||||||
name: Start services
|
|
||||||
run: docker compose up --wait --no-build
|
|
||||||
-
|
|
||||||
name: Check HTTP reachability
|
|
||||||
run: curl -v --fail-with-body http://localhost
|
|
||||||
-
|
|
||||||
name: Check HTTPS reachability
|
|
||||||
if: false # Remove this line when the homepage will be configured, or change the path to check
|
|
||||||
run: curl -vk --fail-with-body https://localhost
|
|
||||||
-
|
|
||||||
name: Check Mercure reachability
|
|
||||||
run: curl -vkI --fail-with-body https://localhost/.well-known/mercure?topic=test
|
|
||||||
-
|
|
||||||
name: Create test database
|
|
||||||
if: false # Remove this line if Doctrine ORM is installed
|
|
||||||
run: docker compose exec -T php bin/console -e test doctrine:database:create
|
|
||||||
-
|
|
||||||
name: Run migrations
|
|
||||||
if: false # Remove this line if Doctrine Migrations is installed
|
|
||||||
run: docker compose exec -T php bin/console -e test doctrine:migrations:migrate --no-interaction
|
|
||||||
-
|
|
||||||
name: Run PHPUnit
|
|
||||||
if: false # Remove this line if PHPUnit is installed
|
|
||||||
run: docker compose exec -T php bin/phpunit
|
|
||||||
-
|
|
||||||
name: Doctrine Schema Validator
|
|
||||||
if: false # Remove this line if Doctrine ORM is installed
|
|
||||||
run: docker compose exec -T php bin/console -e test doctrine:schema:validate
|
|
||||||
lint:
|
|
||||||
name: Docker Lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
-
|
|
||||||
name: Lint Dockerfile
|
|
||||||
uses: hadolint/hadolint-action@v3.1.0
|
|
77
Dockerfile
77
Dockerfile
@ -1,77 +0,0 @@
|
|||||||
#syntax=docker/dockerfile:1
|
|
||||||
|
|
||||||
# Versions
|
|
||||||
FROM dunglas/frankenphp:1-php8.4 AS frankenphp_upstream
|
|
||||||
|
|
||||||
# The different stages of this Dockerfile are meant to be built into separate images
|
|
||||||
# https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage
|
|
||||||
# https://docs.docker.com/compose/compose-file/#target
|
|
||||||
|
|
||||||
|
|
||||||
# Base FrankenPHP image
|
|
||||||
FROM frankenphp_upstream AS frankenphp_base
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
VOLUME /app/var/
|
|
||||||
|
|
||||||
# persistent / runtime deps
|
|
||||||
# hadolint ignore=DL3008
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
acl \
|
|
||||||
file \
|
|
||||||
gettext \
|
|
||||||
git \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
RUN set -eux; \
|
|
||||||
install-php-extensions \
|
|
||||||
@composer \
|
|
||||||
apcu \
|
|
||||||
intl \
|
|
||||||
opcache \
|
|
||||||
zip \
|
|
||||||
;
|
|
||||||
|
|
||||||
# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser
|
|
||||||
ENV COMPOSER_ALLOW_SUPERUSER=1
|
|
||||||
|
|
||||||
# Transport to use by Mercure (default to Bolt)
|
|
||||||
ENV MERCURE_TRANSPORT_URL=bolt:///data/mercure.db
|
|
||||||
|
|
||||||
ENV PHP_INI_SCAN_DIR=":$PHP_INI_DIR/app.conf.d"
|
|
||||||
|
|
||||||
###> recipes ###
|
|
||||||
###> doctrine/doctrine-bundle ###
|
|
||||||
#RUN install-php-extensions pdo pdo_mysql
|
|
||||||
RUN docker-php-ext-install pdo pdo_mysql
|
|
||||||
RUN docker-php-ext-enable pdo pdo_mysql
|
|
||||||
###< doctrine/doctrine-bundle ###
|
|
||||||
###< recipes ###
|
|
||||||
|
|
||||||
COPY --link frankenphp/conf.d/10-app.ini $PHP_INI_DIR/app.conf.d/
|
|
||||||
COPY --link --chmod=755 frankenphp/docker-entrypoint.sh /usr/local/bin/docker-entrypoint
|
|
||||||
COPY --link frankenphp/Caddyfile /etc/frankenphp/Caddyfile
|
|
||||||
|
|
||||||
ENTRYPOINT ["docker-entrypoint"]
|
|
||||||
|
|
||||||
HEALTHCHECK --start-period=60s CMD curl -f http://localhost:2019/metrics || exit 1
|
|
||||||
CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile" ]
|
|
||||||
|
|
||||||
# Dev FrankenPHP image
|
|
||||||
FROM frankenphp_base AS frankenphp_dev
|
|
||||||
|
|
||||||
ENV APP_ENV=dev
|
|
||||||
ENV XDEBUG_MODE=off
|
|
||||||
#ENV FRANKENPHP_WORKER_CONFIG=watch
|
|
||||||
|
|
||||||
RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
|
|
||||||
|
|
||||||
RUN set -eux; \
|
|
||||||
install-php-extensions \
|
|
||||||
xdebug \
|
|
||||||
;
|
|
||||||
|
|
||||||
COPY --link frankenphp/conf.d/20-app.dev.ini $PHP_INI_DIR/app.conf.d/
|
|
||||||
|
|
||||||
CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile", "--watch" ]
|
|
@ -1,32 +0,0 @@
|
|||||||
# Development environment override
|
|
||||||
services:
|
|
||||||
php:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
target: frankenphp_dev
|
|
||||||
volumes:
|
|
||||||
- ./:/app
|
|
||||||
- ./frankenphp/Caddyfile:/etc/frankenphp/Caddyfile:ro
|
|
||||||
- ./frankenphp/conf.d/20-app.dev.ini:/usr/local/etc/php/app.conf.d/20-app.dev.ini:ro
|
|
||||||
# If you develop on Mac or Windows you can remove the vendor/ directory
|
|
||||||
# from the bind-mount for better performance by enabling the next line:
|
|
||||||
#- /app/vendor
|
|
||||||
environment:
|
|
||||||
#FRANKENPHP_WORKER_CONFIG: watch
|
|
||||||
MERCURE_EXTRA_DIRECTIVES: demo
|
|
||||||
# See https://xdebug.org/docs/all_settings#mode
|
|
||||||
XDEBUG_MODE: "${XDEBUG_MODE:-off}"
|
|
||||||
APP_ENV: "${APP_ENV:-dev}"
|
|
||||||
extra_hosts:
|
|
||||||
# Ensure that host.docker.internal is correctly defined on Linux
|
|
||||||
- host.docker.internal:host-gateway
|
|
||||||
tty: true
|
|
||||||
|
|
||||||
###> symfony/mercure-bundle ###
|
|
||||||
###< symfony/mercure-bundle ###
|
|
||||||
|
|
||||||
###> doctrine/doctrine-bundle ###
|
|
||||||
database:
|
|
||||||
ports:
|
|
||||||
- "3306"
|
|
||||||
###< doctrine/doctrine-bundle ###
|
|
@ -1,10 +0,0 @@
|
|||||||
# Production environment override
|
|
||||||
services:
|
|
||||||
php:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
target: frankenphp_prod
|
|
||||||
environment:
|
|
||||||
APP_SECRET: ${APP_SECRET}
|
|
||||||
MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET}
|
|
||||||
MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET}
|
|
69
compose.yaml
69
compose.yaml
@ -1,69 +0,0 @@
|
|||||||
version: "3"
|
|
||||||
services:
|
|
||||||
php:
|
|
||||||
image: ${IMAGES_PREFIX:-}app-php
|
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
- database
|
|
||||||
environment:
|
|
||||||
SERVER_NAME: ${SERVER_NAME:-localhost}, php:80
|
|
||||||
MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
|
|
||||||
MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
|
|
||||||
# Run "composer require symfony/orm-pack" to install and configure Doctrine ORM
|
|
||||||
#DATABASE_URL: postgresql://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD:-!ChangeMe!}@database:5432/${POSTGRES_DB:-app}?serverVersion=${POSTGRES_VERSION:-15}&charset=${POSTGRES_CHARSET:-utf8}
|
|
||||||
DATABASE_URL: mysql://root:password@database:3306/snips?serverVersion=11.7.2-MariaDB
|
|
||||||
# Run "composer require symfony/mercure-bundle" to install and configure the Mercure integration
|
|
||||||
MERCURE_URL: ${CADDY_MERCURE_URL:-http://php/.well-known/mercure}
|
|
||||||
MERCURE_PUBLIC_URL: ${CADDY_MERCURE_PUBLIC_URL:-https://${SERVER_NAME:-localhost}:${HTTPS_PORT:-443}/.well-known/mercure}
|
|
||||||
MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
|
|
||||||
# The two next lines can be removed after initial installation
|
|
||||||
SYMFONY_VERSION: ${SYMFONY_VERSION:-}
|
|
||||||
STABILITY: ${STABILITY:-stable}
|
|
||||||
volumes:
|
|
||||||
- caddy_data:/data
|
|
||||||
- caddy_config:/config
|
|
||||||
ports:
|
|
||||||
# HTTP
|
|
||||||
- target: 80
|
|
||||||
published: ${HTTP_PORT:-80}
|
|
||||||
protocol: tcp
|
|
||||||
# HTTPS
|
|
||||||
- target: 443
|
|
||||||
published: ${HTTPS_PORT:-443}
|
|
||||||
protocol: tcp
|
|
||||||
# HTTP/3
|
|
||||||
- target: 443
|
|
||||||
published: ${HTTP3_PORT:-443}
|
|
||||||
protocol: udp
|
|
||||||
|
|
||||||
# Mercure is installed as a Caddy module, prevent the Flex recipe from installing another service
|
|
||||||
###> symfony/mercure-bundle ###
|
|
||||||
###< symfony/mercure-bundle ###
|
|
||||||
|
|
||||||
###> doctrine/doctrine-bundle ###
|
|
||||||
database:
|
|
||||||
image: 'mariadb:latest'
|
|
||||||
environment:
|
|
||||||
MARIADB_USER: app
|
|
||||||
MARIADB_PASSWORD: password
|
|
||||||
MARIADB_DATABASE: snips
|
|
||||||
MARIADB_ROOT_PASSWORD: password
|
|
||||||
ports:
|
|
||||||
# To allow the host machine to access the ports below, modify the lines below.
|
|
||||||
# For example, to allow the host to connect to port 3306 on the container, you would change
|
|
||||||
# "3306" to "3306:3306". Where the first port is exposed to the host and the second is the container port.
|
|
||||||
# See https://docs.docker.com/compose/compose-file/compose-file-v3/#ports for more information.
|
|
||||||
- '3306'
|
|
||||||
volumes:
|
|
||||||
- database_data:/var/lib/mysql
|
|
||||||
###< doctrine/doctrine-bundle ###
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
caddy_data:
|
|
||||||
caddy_config:
|
|
||||||
###> symfony/mercure-bundle ###
|
|
||||||
###< symfony/mercure-bundle ###
|
|
||||||
|
|
||||||
###> doctrine/doctrine-bundle ###
|
|
||||||
database_data:
|
|
||||||
###< doctrine/doctrine-bundle ###
|
|
@ -82,8 +82,7 @@
|
|||||||
"extra": {
|
"extra": {
|
||||||
"symfony": {
|
"symfony": {
|
||||||
"allow-contrib": false,
|
"allow-contrib": false,
|
||||||
"require": "7.2.*",
|
"require": "7.2.*"
|
||||||
"docker": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
composer.lock
generated
2
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "4f33fd23b3dd7a367af9ebf6f69f9c0a",
|
"content-hash": "638cbc9841226f386ba27215e24c5410",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "dflydev/dot-access-data",
|
"name": "dflydev/dot-access-data",
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
{
|
|
||||||
{$CADDY_GLOBAL_OPTIONS}
|
|
||||||
|
|
||||||
frankenphp {
|
|
||||||
{$FRANKENPHP_CONFIG}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{$CADDY_EXTRA_CONFIG}
|
|
||||||
|
|
||||||
{$SERVER_NAME:localhost} {
|
|
||||||
log {
|
|
||||||
{$CADDY_SERVER_LOG_OPTIONS}
|
|
||||||
# Redact the authorization query parameter that can be set by Mercure
|
|
||||||
format filter {
|
|
||||||
request>uri query {
|
|
||||||
replace authorization REDACTED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
root /app/public
|
|
||||||
encode zstd br gzip
|
|
||||||
|
|
||||||
mercure {
|
|
||||||
# Publisher JWT key
|
|
||||||
publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
|
|
||||||
# Subscriber JWT key
|
|
||||||
subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG}
|
|
||||||
# Allow anonymous subscribers (double-check that it's what you want)
|
|
||||||
anonymous
|
|
||||||
# Enable the subscription API (double-check that it's what you want)
|
|
||||||
subscriptions
|
|
||||||
# Extra directives
|
|
||||||
{$MERCURE_EXTRA_DIRECTIVES}
|
|
||||||
}
|
|
||||||
|
|
||||||
vulcain
|
|
||||||
|
|
||||||
{$CADDY_SERVER_EXTRA_DIRECTIVES}
|
|
||||||
|
|
||||||
# Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics
|
|
||||||
header ?Permissions-Policy "browsing-topics=()"
|
|
||||||
|
|
||||||
@phpRoute {
|
|
||||||
not path /.well-known/mercure*
|
|
||||||
not file {path}
|
|
||||||
}
|
|
||||||
rewrite @phpRoute index.php
|
|
||||||
|
|
||||||
@frontController path index.php
|
|
||||||
php @frontController
|
|
||||||
|
|
||||||
file_server {
|
|
||||||
hide *.php
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
expose_php = 0
|
|
||||||
date.timezone = UTC
|
|
||||||
apc.enable_cli = 1
|
|
||||||
session.use_strict_mode = 1
|
|
||||||
zend.detect_unicode = 0
|
|
||||||
|
|
||||||
; https://symfony.com/doc/current/performance.html
|
|
||||||
realpath_cache_size = 4096K
|
|
||||||
realpath_cache_ttl = 600
|
|
||||||
opcache.interned_strings_buffer = 16
|
|
||||||
opcache.max_accelerated_files = 20000
|
|
||||||
opcache.memory_consumption = 256
|
|
||||||
opcache.enable_file_override = 1
|
|
@ -1,5 +0,0 @@
|
|||||||
; See https://docs.docker.com/desktop/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host
|
|
||||||
; See https://github.com/docker/for-linux/issues/264
|
|
||||||
; The `client_host` below may optionally be replaced with `discover_client_host=yes`
|
|
||||||
; Add `start_with_request=yes` to start debug session on each request
|
|
||||||
xdebug.client_host = host.docker.internal
|
|
@ -1,5 +0,0 @@
|
|||||||
; https://symfony.com/doc/current/performance.html#use-the-opcache-class-preloading
|
|
||||||
opcache.preload_user = root
|
|
||||||
opcache.preload = /app/config/preload.php
|
|
||||||
; https://symfony.com/doc/current/performance.html#don-t-check-php-files-timestamps
|
|
||||||
opcache.validate_timestamps = 0
|
|
@ -1,67 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -e
|
|
||||||
|
|
||||||
if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
|
|
||||||
# Install the project the first time PHP is started
|
|
||||||
# After the installation, the following block can be deleted
|
|
||||||
if [ ! -f composer.json ]; then
|
|
||||||
rm -Rf tmp/
|
|
||||||
composer create-project "symfony/skeleton $SYMFONY_VERSION" tmp --stability="$STABILITY" --prefer-dist --no-progress --no-interaction --no-install
|
|
||||||
|
|
||||||
cd tmp
|
|
||||||
cp -Rp . ..
|
|
||||||
cd -
|
|
||||||
rm -Rf tmp/
|
|
||||||
|
|
||||||
composer require "php:>=$PHP_VERSION" runtime/frankenphp-symfony
|
|
||||||
composer config --json extra.symfony.docker 'true'
|
|
||||||
|
|
||||||
if grep -q ^DATABASE_URL= .env; then
|
|
||||||
echo 'To finish the installation please press Ctrl+C to stop Docker Compose and run: docker compose up --build --wait'
|
|
||||||
sleep infinity
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$(ls -A 'vendor/' 2>/dev/null)" ]; then
|
|
||||||
composer install --prefer-dist --no-progress --no-interaction
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Display information about the current project
|
|
||||||
# Or about an error in project initialization
|
|
||||||
php bin/console -V
|
|
||||||
|
|
||||||
if grep -q ^DATABASE_URL= .env.local; then
|
|
||||||
echo 'Waiting for database to be ready...'
|
|
||||||
ATTEMPTS_LEFT_TO_REACH_DATABASE=60
|
|
||||||
until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(php bin/console dbal:run-sql -q "SELECT 1" 2>&1); do
|
|
||||||
if [ $? -eq 255 ]; then
|
|
||||||
# If the Doctrine command exits with 255, an unrecoverable error occurred
|
|
||||||
ATTEMPTS_LEFT_TO_REACH_DATABASE=0
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
ATTEMPTS_LEFT_TO_REACH_DATABASE=$((ATTEMPTS_LEFT_TO_REACH_DATABASE - 1))
|
|
||||||
echo "Still waiting for database to be ready... Or maybe the database is not reachable. $ATTEMPTS_LEFT_TO_REACH_DATABASE attempts left."
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ]; then
|
|
||||||
echo 'The database is not up or not reachable:'
|
|
||||||
echo "$DATABASE_ERROR"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo 'The database is now ready and reachable'
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$( find ./migrations -iname '*.php' -print -quit )" ]; then
|
|
||||||
echo 'Migrating database...'
|
|
||||||
php bin/console doctrine:migrations:migrate --no-interaction --all-or-nothing
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var
|
|
||||||
setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var
|
|
||||||
|
|
||||||
echo 'PHP app ready!'
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec docker-php-entrypoint "$@"
|
|
BIN
public/snips.png
BIN
public/snips.png
Binary file not shown.
Before Width: | Height: | Size: 1.2 MiB |
@ -81,15 +81,6 @@ class SnipController extends AbstractController
|
|||||||
{
|
{
|
||||||
$this->denyAccessUnlessGranted(SnipVoter::EDIT, $snip);
|
$this->denyAccessUnlessGranted(SnipVoter::EDIT, $snip);
|
||||||
|
|
||||||
/**
|
|
||||||
* Temporary solution to prevent editing of old versions
|
|
||||||
* It technically fully works, but rendering the version history needs an update first
|
|
||||||
*/
|
|
||||||
$isLatest = $snip->getActiveVersion() === $snip->getLatestVersion();
|
|
||||||
if (!$isLatest) {
|
|
||||||
$this->addFlash('error', 'Snip is not the latest version, changes will not be saved.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$form = $this->createForm(SnipType::class, $snip);
|
$form = $this->createForm(SnipType::class, $snip);
|
||||||
$form->add('Save', SubmitType::class);
|
$form->add('Save', SubmitType::class);
|
||||||
if ($snip->getId()) {
|
if ($snip->getId()) {
|
||||||
@ -98,11 +89,6 @@ class SnipController extends AbstractController
|
|||||||
|
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
if (!$isLatest) {
|
|
||||||
return $this->redirectToRoute('snip_single', [
|
|
||||||
'snip' => $snip->getId(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$this->repository->save($snip);
|
$this->repository->save($snip);
|
||||||
$contentService->update(
|
$contentService->update(
|
||||||
$snip,
|
$snip,
|
||||||
@ -112,9 +98,7 @@ class SnipController extends AbstractController
|
|||||||
|
|
||||||
$this->addFlash('success', sprintf('Snip "%s" saved', $snip));
|
$this->addFlash('success', sprintf('Snip "%s" saved', $snip));
|
||||||
|
|
||||||
return $this->redirectToRoute('snip_single', [
|
return $this->redirectToRoute('snip_single', ['snip' => $snip->getId()]);
|
||||||
'snip' => $snip->getId(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('snip/edit.html.twig', [
|
return $this->render('snip/edit.html.twig', [
|
||||||
@ -167,6 +151,6 @@ class SnipController extends AbstractController
|
|||||||
$this->addFlash('success', sprintf('Snip "%s" unarchived', $snip));
|
$this->addFlash('success', sprintf('Snip "%s" unarchived', $snip));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->redirectToRoute('snip_edit', ['snip' => $snip->getId()]);
|
return $this->redirectToRoute('snip_single', ['snip' => $snip->getId()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,6 +5,7 @@ namespace App\Controller;
|
|||||||
use App\Entity\Snip;
|
use App\Entity\Snip;
|
||||||
use App\Entity\SnipContent;
|
use App\Entity\SnipContent;
|
||||||
use App\Security\Voter\SnipVoter;
|
use App\Security\Voter\SnipVoter;
|
||||||
|
use App\Service\SnipContent\FlowChartTreeBuilder;
|
||||||
use App\Service\SnipContent\SnipContentService;
|
use App\Service\SnipContent\SnipContentService;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
@ -18,12 +19,15 @@ class VersionController extends AbstractController
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route('/', name: '_index')]
|
#[Route('/', name: '_index')]
|
||||||
public function index(Snip $snip): Response
|
public function index(Snip $snip, FlowChartTreeBuilder $builder): Response
|
||||||
{
|
{
|
||||||
$this->denyAccessUnlessGranted(SnipVoter::EDIT, $snip);
|
$this->denyAccessUnlessGranted(SnipVoter::EDIT, $snip);
|
||||||
|
|
||||||
|
$buildTree = $builder->buildTree($snip);
|
||||||
return $this->render('version/index.html.twig', [
|
return $this->render('version/index.html.twig', [
|
||||||
'snip' => $snip,
|
'snip' => $snip,
|
||||||
|
// 'versions' => new GitTreeBuilder($snip)->buildTree($snip->getSnipContents()->first()),
|
||||||
|
'versions' => $buildTree,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,11 @@ class SnipContent
|
|||||||
$this->children = new ArrayCollection();
|
$this->children = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->name ?? $this->id->toBase32();
|
||||||
|
}
|
||||||
|
|
||||||
public function getId(): ?Ulid
|
public function getId(): ?Ulid
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
|
37
src/Service/SnipContent/FlowChartTreeBuilder.php
Normal file
37
src/Service/SnipContent/FlowChartTreeBuilder.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service\SnipContent;
|
||||||
|
|
||||||
|
use App\Entity\Snip;
|
||||||
|
use Symfony\Component\Routing\RouterInterface;
|
||||||
|
|
||||||
|
readonly class FlowChartTreeBuilder
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private RouterInterface $router,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function buildTree(Snip $snip): array
|
||||||
|
{
|
||||||
|
$tree = [];
|
||||||
|
|
||||||
|
foreach ($snip->getSnipContents() as $content) {
|
||||||
|
if ($content->getParent()) {
|
||||||
|
$tree[] = sprintf('%s --> %s', $content->getParent(), $content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($snip->getSnipContents() as $content) {
|
||||||
|
$tree[] = sprintf(
|
||||||
|
'click %s href "%s"',
|
||||||
|
$content,
|
||||||
|
$this->router->generate('version_set', ['snip' => $snip->getId(), 'version' => $content->getId()])
|
||||||
|
);
|
||||||
|
$tree[] = sprintf('%s@{ shape: rounded }', $content);
|
||||||
|
}
|
||||||
|
|
||||||
|
$tree[] = sprintf('class %s active', $snip->getActiveVersion());
|
||||||
|
|
||||||
|
return $tree;
|
||||||
|
}
|
||||||
|
}
|
61
src/Service/SnipContent/GitTreeBuilder.php
Normal file
61
src/Service/SnipContent/GitTreeBuilder.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service\SnipContent;
|
||||||
|
|
||||||
|
use App\Entity\Snip;
|
||||||
|
use App\Entity\SnipContent;
|
||||||
|
|
||||||
|
class GitTreeBuilder
|
||||||
|
{
|
||||||
|
private string $activeBranch = 'main';
|
||||||
|
|
||||||
|
public function __construct(private readonly Snip $snip) {}
|
||||||
|
|
||||||
|
public function buildTree(SnipContent $content, string $branch = 'main'): array
|
||||||
|
{
|
||||||
|
$tree = [];
|
||||||
|
if ($this->activeBranch !== $branch) {
|
||||||
|
$tree[] = $this->checkout($branch);
|
||||||
|
}
|
||||||
|
$commit = sprintf('commit id:"%s"', $content);
|
||||||
|
if ($this->snip->getActiveVersion() === $content) {
|
||||||
|
$commit .= ' tag: "active" type: REVERSE';
|
||||||
|
}
|
||||||
|
$tree[] = $commit;
|
||||||
|
|
||||||
|
$first = true;
|
||||||
|
foreach ($content->getChildren() as $child) {
|
||||||
|
if (!$first) {
|
||||||
|
$tree[] = $this->branch($child);
|
||||||
|
$tree[] = $this->checkout($branch);
|
||||||
|
}
|
||||||
|
$first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$first = true;
|
||||||
|
foreach ($content->getChildren() as $child) {
|
||||||
|
if ($first) {
|
||||||
|
$myBranch = $branch;
|
||||||
|
} else {
|
||||||
|
$tree[] = $this->checkout($child);
|
||||||
|
$myBranch = $child;
|
||||||
|
}
|
||||||
|
$tree = array_merge($tree, self::buildTree($child, $myBranch));
|
||||||
|
$first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function branch(string $branch): string
|
||||||
|
{
|
||||||
|
$this->activeBranch = $branch;
|
||||||
|
return sprintf('branch %s', $branch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkout(string $branch): string
|
||||||
|
{
|
||||||
|
$this->activeBranch = $branch;
|
||||||
|
return sprintf('checkout %s', $branch);
|
||||||
|
}
|
||||||
|
}
|
11
symfony.lock
11
symfony.lock
@ -14,7 +14,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"doctrine/doctrine-migrations-bundle": {
|
"doctrine/doctrine-migrations-bundle": {
|
||||||
"version": "3.4",
|
"version": "3.2",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
@ -39,7 +39,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/debug-bundle": {
|
"symfony/debug-bundle": {
|
||||||
"version": "7.2",
|
"version": "6.2",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
@ -95,7 +95,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/maker-bundle": {
|
"symfony/maker-bundle": {
|
||||||
"version": "1.63",
|
"version": "1.48",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
@ -161,7 +161,8 @@
|
|||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "7.0",
|
"version": "7.0",
|
||||||
"ref": "0df5844274d871b37fc3816c57a768ffc60a43a5"
|
"ref": "0df5844274d871b37fc3816c57a768ffc60a43a5"
|
||||||
}
|
},
|
||||||
|
"files": []
|
||||||
},
|
},
|
||||||
"symfony/validator": {
|
"symfony/validator": {
|
||||||
"version": "7.2",
|
"version": "7.2",
|
||||||
@ -189,6 +190,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"twig/extra-bundle": {
|
"twig/extra-bundle": {
|
||||||
"version": "v3.21.0"
|
"version": "v3.5.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>
|
<title>{% if title is defined %}{{ title }}{% else %}SNIPS{% endif %}</title>
|
||||||
{% if app.environment == 'dev' %}D{% endif %}
|
<link rel="shortcut icon" type="image/jpg" href="/favicon.png">
|
||||||
{% if title is defined %}{{ title }}{% else %}SNIPS{% endif %}
|
|
||||||
</title>
|
|
||||||
<link rel="shortcut icon" type="image/jpg" href="/snips.png">
|
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
||||||
@ -46,7 +43,9 @@
|
|||||||
{# javascript block #}
|
{# javascript block #}
|
||||||
{% block js %}
|
{% block js %}
|
||||||
<script src="https://kit.fontawesome.com/3471b6556e.js" crossorigin="anonymous"></script>
|
<script src="https://kit.fontawesome.com/3471b6556e.js" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js"
|
||||||
|
integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"
|
||||||
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
|
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark" style="z-index: 1;">
|
<nav class="navbar navbar-expand-md navbar-dark bg-dark" style="z-index: 1;">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a title="Snips" class="navbar-brand" href="{{ path('home') }}">
|
<a title="Snips" class="navbar-brand" href="{{ path('home') }}">SNIPS</a>
|
||||||
<img src="/snips.png" width="30" height="30" class="d-inline-block align-top rounded" alt="">
|
|
||||||
SNIPS
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar"
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar"
|
||||||
aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
|
aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
{% extends 'base/one.column.html.twig' %}
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
{% if app.user and app.user is same as(snip.createdBy) %}
|
|
||||||
<a href="{{ path('snip_index') }}" class="btn btn-primary">
|
|
||||||
<i class="fa fa-arrow-left"></i> Index
|
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
<a href="{{ path('snip_public') }}" class="btn btn-primary">
|
|
||||||
<i class="fa fa-arrow-left"></i> Index
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% block buttons %}{% endblock %}
|
|
||||||
|
|
||||||
<br><br>
|
|
||||||
<div class="card" style="width: 100%;">
|
|
||||||
<div class="card-header d-flex justify-content-between">
|
|
||||||
<ul class="nav nav-tabs card-header-tabs">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link {% if active == 'single' %}active{% endif %}"
|
|
||||||
href="{{ path('snip_single', {'snip': snip.id}) }}">View</a>
|
|
||||||
</li>
|
|
||||||
{% if is_granted('edit', snip) %}
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link {% if active == 'edit' %}active{% endif %}"
|
|
||||||
href="{{ path('snip_edit', {'snip': snip.id}) }}">Edit</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link {% if active == 'versions' %}active{% endif %}"
|
|
||||||
href="{{ path('version_index', {'snip': snip.id}) }}">Versions</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
<span>
|
|
||||||
<span class="badge bg-secondary">
|
|
||||||
<i class="fa fa-hashtag"></i> {{ snip.id }}
|
|
||||||
</span>
|
|
||||||
{% for tag in snip.tags %}
|
|
||||||
<span class="badge bg-secondary">{{ tag }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
{{ include('user/badge.html.twig', {user: snip.createdBy}) }}
|
|
||||||
{{ include('snip/badge.html.twig', {snip: snip}) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
{% block cardbody %}{% endblock %}
|
|
||||||
</div>
|
|
||||||
<div class="card-footer">
|
|
||||||
<p class="card-text text-muted">
|
|
||||||
Current version: {{ snip.activeVersion.id }}
|
|
||||||
{% if snip.activeVersion == snip.latestVersion %}(latest){% endif %}
|
|
||||||
Created at {{ include('generic/datetime.badge.html.twig', {datetime: snip.activeVersion.id.dateTime}) }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block css %}
|
|
||||||
{{ parent() }}
|
|
||||||
<link rel="stylesheet"
|
|
||||||
href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block js %}
|
|
||||||
{{ parent() }}
|
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
|
|
||||||
<script>
|
|
||||||
const codeBlocks = document.querySelectorAll('code.hljs');
|
|
||||||
|
|
||||||
codeBlocks.forEach((block) => {
|
|
||||||
hljs.highlightElement(block);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
@ -1,27 +1,21 @@
|
|||||||
{% extends 'snip/base.html.twig' %}
|
{% extends 'base/one.column.html.twig' %}
|
||||||
|
|
||||||
{% if snip.id %}
|
{% if snip.id %}
|
||||||
{% set title %}{{ snip }} - Edit{% endset %}
|
{% set title = 'Edit Snip ' ~ snip %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% set title = 'Create Snip' %}
|
{% set title = 'Create Snip' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% set active = 'edit' %}
|
|
||||||
|
|
||||||
{% block buttons %}
|
{% block body %}
|
||||||
{% if is_granted('edit', snip) %}
|
{% if snip.id %}
|
||||||
<a href="{{ path('snip_archive', {snip: snip.id}) }}" class="btn btn-secondary">
|
<a href="{{ path('snip_single', {snip: snip.id}) }}" class="btn btn-primary">
|
||||||
{% if snip.archived %}
|
<i class="fa fa-arrow-left"></i>
|
||||||
<i class="fa fa-undo"></i> Unarchive
|
Back
|
||||||
{% else %}
|
|
||||||
<i class="fa fa-archive"></i> Archive
|
|
||||||
{% endif %}
|
|
||||||
</a>
|
|
||||||
<a href="{{ path('snip_delete', {snip: snip.id}) }}" class="btn btn-danger">
|
|
||||||
<i class="fa fa-trash"></i> Delete
|
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
<a href="{{ path('snip_index') }}" class="btn btn-info">
|
||||||
|
<i class="fa fa-list"></i>
|
||||||
{% block cardbody %}
|
Index
|
||||||
|
</a><br><br>
|
||||||
{{ form(form) }}
|
{{ form(form) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -1,16 +1,63 @@
|
|||||||
{% extends 'snip/base.html.twig' %}
|
{% extends 'base/one.column.html.twig' %}
|
||||||
|
|
||||||
{% set title %}{{ snip }} - View{% endset %}
|
{% set title %}Snip {{ snip }}{% endset %}
|
||||||
{% set active = 'single' %}
|
|
||||||
|
|
||||||
{% block buttons %}
|
{% block body %}
|
||||||
<a href="{{ path('snip_raw', {snip: snip.id}) }}" class="btn btn-info">
|
{% if app.user %}
|
||||||
|
<a href="{{ path('snip_index') }}" class="btn btn-primary">
|
||||||
|
<i class="fa fa-arrow-left"></i> Back
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ path('snip_public') }}" class="btn btn-primary">
|
||||||
|
<i class="fa fa-arrow-left"></i> Index
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('edit', snip) %}
|
||||||
|
<a class="btn btn-info" href="{{ path('version_index', {snip: snip.id}) }}">
|
||||||
|
<i class="fa fa-history" aria-hidden="true"></i> Versions
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-warning" href="{{ path('snip_edit', {snip: snip.id}) }}">
|
||||||
|
<i class="fa fa-pencil" aria-hidden="true"></i> Edit
|
||||||
|
</a>
|
||||||
|
<a href="{{ path('snip_archive', {snip: snip.id}) }}" class="btn btn-secondary">
|
||||||
|
{% if snip.archived %}
|
||||||
|
<i class="fa fa-undo"></i> Unarchive
|
||||||
|
{% else %}
|
||||||
|
<i class="fa fa-archive"></i> Archive
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
<a href="{{ path('snip_delete', {snip: snip.id}) }}" class="btn btn-danger">
|
||||||
|
<i class="fa fa-trash"></i> Delete
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{{ path('snip_raw', {snip: snip.id}) }}" class="btn btn-primary">
|
||||||
<i class="fa fa-eye"></i> Raw
|
<i class="fa fa-eye"></i> Raw
|
||||||
</a>
|
</a>
|
||||||
{% endblock %}
|
<br><br>
|
||||||
|
<div class="card" style="width: 100%;">
|
||||||
{% block cardbody %}
|
<h4 class="card-header d-flex justify-content-between">
|
||||||
|
<span>
|
||||||
|
{{ snip }} <small class="text-muted">#{{ snip.id }}</small>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{% for tag in snip.tags %}
|
||||||
|
<span class="badge bg-secondary">{{ tag }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
{{ include('user/badge.html.twig', {user: snip.createdBy}) }}
|
||||||
|
{{ include('snip/badge.html.twig', {snip: snip}) }}
|
||||||
|
</span>
|
||||||
|
</h4>
|
||||||
|
<div class="card-body">
|
||||||
{{ content|raw }}
|
{{ content|raw }}
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<p class="card-text text-muted">
|
||||||
|
Current version: {{ snip.activeVersion.id }}
|
||||||
|
{% if snip.activeVersion == snip.latestVersion %}(latest){% endif %}
|
||||||
|
Created at {{ include('generic/datetime.badge.html.twig', {datetime: snip.activeVersion.id.dateTime}) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
|
@ -1,3 +1 @@
|
|||||||
<span class="badge {% if user == app.user %}bg-success{% else %}bg-secondary{% endif %}">
|
<span class="badge {% if user == app.user %}bg-success{% else %}bg-secondary{% endif %}">{{ user }}</span>
|
||||||
<i class="fa fa-user"></i> {{ user }}
|
|
||||||
</span>
|
|
@ -1,28 +1,46 @@
|
|||||||
{% extends 'snip/base.html.twig' %}
|
{% extends 'base/one.column.html.twig' %}
|
||||||
|
|
||||||
{% set title %}{{ snip }} - Versions{% endset %}
|
{% set title = 'Snip ' ~ snip %}
|
||||||
{% set active = 'versions' %}
|
|
||||||
|
|
||||||
{% block buttons %}
|
{% block body %}
|
||||||
|
<a href="{{ path('snip_single', {snip: snip.id}) }}" class="btn btn-primary">
|
||||||
|
<i class="fa fa-arrow-left"></i> Back
|
||||||
|
</a>
|
||||||
<a href="{{ path('version_set', {version: snip.latestVersion.id, snip: snip.id}) }}" class="btn btn-warning">
|
<a href="{{ path('version_set', {version: snip.latestVersion.id, snip: snip.id}) }}" class="btn btn-warning">
|
||||||
<i class="fa fa-refresh"></i> Latest
|
<i class="fa fa-refresh"></i> Latest
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ path('content_compare', {to: snip.activeVersion.id}) }}" class="btn btn-info">
|
<a href="{{ path('content_compare', {to: snip.activeVersion.id}) }}" class="btn btn-info">
|
||||||
<i class="fa fa-left-right"></i> Compare
|
<i class="fa fa-left-right"></i> Compare
|
||||||
</a>
|
</a>
|
||||||
|
<pre class="mermaid">
|
||||||
|
flowchart BT
|
||||||
|
{% for versionData in versions %}
|
||||||
|
{{~ versionData ~}}
|
||||||
|
{% endfor %}
|
||||||
|
</pre>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block cardbody %}
|
{% block js %}
|
||||||
<div class="list-group">
|
{{ parent() }}
|
||||||
{% for version in snip.snipContents|reverse %}
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@11.6.0/dist/mermaid.min.js"></script>
|
||||||
<a class="list-group-item {% if version.id == snip.activeVersion.id %}list-group-item-success{% endif %} d-flex justify-content-between"
|
<script>
|
||||||
href="{{ path('version_set', {version: version.id, snip: snip.id}) }}">
|
mermaid.initialize({startOnLoad: true});
|
||||||
<span>
|
</script>
|
||||||
{{ include('generic/datetime.badge.html.twig', {datetime: version.id.dateTime}) }}
|
{% endblock %}
|
||||||
{% if version.name %}{{ version.name }}{% endif %}
|
|
||||||
</span>
|
{% block css %}
|
||||||
<span class="text-muted">{{ version.id }}</span>
|
{{ parent() }}
|
||||||
</a>
|
<style>
|
||||||
{% endfor %}
|
.node rect {
|
||||||
</div>
|
fill: var(--bs-secondary) !important;
|
||||||
|
stroke: var(--bs-secondary-text-emphasis) !important;
|
||||||
|
}
|
||||||
|
.node span {
|
||||||
|
color: var(--bs-light) !important;
|
||||||
|
}
|
||||||
|
.active rect {
|
||||||
|
fill: var(--bs-success) !important;
|
||||||
|
stroke: var(--bs-success-text-emphasis) !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
Reference in New Issue
Block a user