Compare commits

..

109 commits

Author SHA1 Message Date
2cda2950ee
update dependencies
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/manual/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2025-04-22 14:16:21 +02:00
80b03bcc9e fix ci
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2025-02-24 11:47:33 +01:00
e4680d77ab fix ci 2025-02-24 11:46:57 +01:00
2ed096ce82 fix ci 2025-02-24 11:46:34 +01:00
d7d8974bd2 fix ci 2025-02-24 11:45:24 +01:00
1dcf735141 fix mesh cards 2025-02-24 11:44:03 +01:00
78f7bdd5ef app:ly lint (php) 2025-02-24 11:40:17 +01:00
5d22dd2b09 add preload
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
ci/woodpecker/manual/build Pipeline failed
2025-01-09 13:58:10 +01:00
8d67ec7f8a usage of deblan.fr instead of deblan.io
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2024-10-10 15:10:33 +02:00
dfb5fd2f7a add stats btn
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2024-09-04 09:09:37 +02:00
5888964a47 apply linter
Some checks are pending
ci/woodpecker/push/build Pipeline is pending approval
2024-09-02 21:33:32 +02:00
b519ff8e1b add mermaid
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2024-09-02 16:12:59 +02:00
0d583d9375 add mermaid
Some checks are pending
ci/woodpecker/push/build Pipeline is pending approval
2024-09-02 16:09:59 +02:00
0792dc8f73 update ci
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2024-09-02 15:47:30 +02:00
7b2b850f3d update ci
Some checks failed
ci/woodpecker/push/build Pipeline failed
2024-09-02 15:45:12 +02:00
f81fc6bdcf add mermaid
Some checks failed
ci/woodpecker/push/build Pipeline failed
2024-09-02 15:36:20 +02:00
e4788f0dc0
update logo
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2024-08-15 16:44:09 +02:00
266114943b
add form id
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2024-08-04 14:43:54 +02:00
36abd9b1d5
update ejs block aspect
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2024-07-22 16:22:48 +02:00
fd71b224eb
add murph filters when using block_to_html 2024-06-06 11:04:28 +02:00
4fd78d1303
add builder parser for rss
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2024-05-29 11:35:22 +02:00
7e6d230e17
add builder as post editor
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2024-05-17 22:11:13 +02:00
b86f3096e1
fix assets upgrade 2024-05-16 20:50:36 +02:00
ecb4ca177e
change markdown editor
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
fix editorjs
2024-05-13 21:01:11 +02:00
c8d99da2c2
change h1 font
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2024-04-18 22:48:19 +02:00
22ec3d036e
add border color on md editor
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2024-03-31 19:39:44 +02:00
676e9dfe67
update steps 2024-03-03 19:34:27 +01:00
508a816642 change aspect of header h1
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2024-01-27 19:47:49 +01:00
476dece659
remove novops
Some checks failed
ci/woodpecker/push/build Pipeline failed
2024-01-10 10:08:20 +01:00
1a7ea2e5a2
fix deploy step
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline failed
ci/woodpecker/manual/build Pipeline failed
2023-12-06 21:44:18 +01:00
6fa3aafed0
fix deploy step
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline failed
2023-12-06 21:37:08 +01:00
1041eba4cb
fix deploy step
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline failed
2023-12-06 21:30:32 +01:00
698c356c0e
fix deploy step
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline failed
2023-12-06 21:08:19 +01:00
86558dc76d Merge branch 'feature/vault' into develop
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline failed
2023-12-06 20:24:14 +01:00
2ee645bdab
update ci config
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2023-12-06 20:20:10 +01:00
83f7946d02
update ci config
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2023-12-06 20:18:21 +01:00
0e0f2688c7
update novops conf
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2023-12-06 19:41:35 +01:00
60c60016a1
update novops conf
Some checks failed
ci/woodpecker/push/build Pipeline failed
2023-12-06 19:40:30 +01:00
8eb6ba303c
add novops conf
Some checks failed
ci/woodpecker/push/build Pipeline failed
2023-12-06 19:38:45 +01:00
d83adf0473
update ci
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/manual/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2023-11-10 11:30:05 +01:00
Simon Vieille
b9f5785fa6
replace stop action in messenger
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
ci/woodpecker/manual/build Pipeline was successful
2023-10-18 22:48:00 +02:00
Simon Vieille
0d1f1e29b5
replace stop action in messenger
Some checks failed
ci/woodpecker/push/build Pipeline failed
2023-10-18 22:38:22 +02:00
Simon Vieille
28f8e5c583
replace stop action in messenger 2023-10-18 22:33:33 +02:00
Simon Vieille
1f9161feef
update build step
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline failed
2023-10-18 22:17:06 +02:00
Simon Vieille
7800a01499
update build step
Some checks failed
ci/woodpecker/push/build Pipeline failed
2023-10-18 22:16:10 +02:00
Simon Vieille
bbb1804f31
update deploy step
Some checks failed
ci/woodpecker/push/build Pipeline failed
2023-10-18 22:07:55 +02:00
Simon Vieille
a273a9aac1
update deploy step 2023-10-18 22:07:20 +02:00
Simon Vieille
6e005690da
fix project template
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline failed
2023-10-13 00:05:15 +02:00
Simon Vieille
b324bfcd97
fix ci syntax 2023-10-07 16:43:47 +02:00
Simon Vieille
850ebff7c0
fix ci syntax
Some checks are pending
ci/woodpecker/push/build Pipeline is pending
2023-09-29 16:02:54 +02:00
Simon Vieille
5b4b2b59be
refactoring 2023-09-28 21:29:51 +02:00
Simon Vieille
1a4bcf8755
upgrade search engine
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2023-09-24 19:35:27 +02:00
Simon Vieille
b11da225fb
upgrade search engine
Some checks failed
ci/woodpecker/push/build Pipeline failed
2023-09-24 19:28:27 +02:00
Simon Vieille
138b4f24ee
upgrade search engine
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline failed
2023-09-24 18:59:33 +02:00
Simon Vieille
45bd1b408d
update ci
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2023-09-23 23:09:01 +02:00
Simon Vieille
dfecbb0ea6
update ci
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2023-09-23 22:54:21 +02:00
Simon Vieille
ab692fe082
add margin to graph on dashboard
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2023-09-23 12:36:49 +02:00
Simon Vieille
c93754ecff
update dashboard including grafana
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2023-09-23 12:10:01 +02:00
Simon Vieille
869974c49b
fix missing variable in messenger wrapper 2023-09-23 12:09:46 +02:00
Simon Vieille
89cc5623d3
update mage conf
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2023-09-23 00:18:41 +02:00
Simon Vieille
b8cdec4d71
update messenger script
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline failed
2023-09-23 00:08:32 +02:00
Simon Vieille
d51366a5c6
update deploy steps (messenger)
Some checks failed
ci/woodpecker/push/build Pipeline failed
2023-09-23 00:05:24 +02:00
Simon Vieille
bfe3599054
use messenger to push influxdb stats 2023-09-23 00:03:48 +02:00
Simon Vieille
4a84b2db98 Merge branch 'feature/redis-requests' into develop
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/deployment/deploy Pipeline was successful
2023-09-22 22:07:26 +02:00
Simon Vieille
a9fe3c488f
add influxdb and stat of page view 2023-09-22 22:07:22 +02:00
Simon Vieille
f81e2a99a7
update ci 2023-09-22 22:07:17 +02:00
Simon Vieille
5612ea2acd
add influxdb and stat of page view 2023-09-22 22:06:56 +02:00
Simon Vieille
f4169d20e9
add influxdb and stat of page view 2023-09-22 22:06:44 +02:00
Simon Vieille
d39759f88e Merge branch 'feature/build' into develop
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-09-13 23:04:42 +02:00
Simon Vieille
86701a5578
remove cache-clean step 2023-09-13 23:04:35 +02:00
Simon Vieille
eec59acc29
[wip] deploy task
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline failed
2023-09-13 21:25:20 +02:00
Simon Vieille
b60a1acd57
[wip] deploy task
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline failed
2023-09-13 21:18:22 +02:00
Simon Vieille
dbbe5d1db5
[wip] tasks
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline failed
2023-09-13 21:12:13 +02:00
Simon Vieille
35fb777eaa
[wip] events
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-09-13 21:10:50 +02:00
Simon Vieille
e70764e63d
[wip] events 2023-09-13 21:10:18 +02:00
Simon Vieille
ed8319f727
[wip] tests
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline failed
2023-09-13 20:57:58 +02:00
Simon Vieille
40670b6bfc
[wip] tests
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-09-13 20:55:40 +02:00
Simon Vieille
d8f3979861
[wip] tests
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-13 20:53:15 +02:00
Simon Vieille
4bb2cc557f
[wip] tests
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-13 20:51:41 +02:00
Simon Vieille
9e951916a6
[wip] add volume for the build
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-13 20:50:20 +02:00
Simon Vieille
8ecb025df8
update view of forms
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
ci/woodpecker/cron/woodpecker Pipeline failed
2023-09-13 20:20:49 +02:00
Simon Vieille
3d6acca60a
add deprecated post feature
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-09-10 22:49:31 +02:00
Simon Vieille
bad87d913e
fix author block render
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-08-19 18:23:20 +02:00
Simon Vieille
dedf105f06
update footer setting type
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/manual/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-08-15 11:02:27 +02:00
Simon Vieille
fccf54ed5a
fix liip
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
add constraints
2023-07-30 16:58:31 +02:00
Simon Vieille
53aa16951e
fix liip
add constraints
2023-07-30 16:58:26 +02:00
Simon Vieille
70cef70bf4
update murph
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/manual/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-07-27 18:32:55 +02:00
Simon Vieille
2cd8d7fcca
update review css
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-05-22 18:05:31 +02:00
Simon Vieille
afe3c0ccbd
disable particuls
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-05-22 10:41:45 +02:00
Simon Vieille
6f09fbde0b
update dashboard
All checks were successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-05-21 13:06:23 +02:00
Simon Vieille
19027ed123
update dashboard
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-05-21 12:51:27 +02:00
Simon Vieille
0c7922eb9b
update dashboard
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-05-21 12:42:04 +02:00
Simon Vieille
0516d6e8c4
add post params
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
update ui
2023-05-12 20:56:54 +02:00
Simon Vieille
d2470b8d96
update favicon
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-05-11 14:46:15 +02:00
Simon Vieille
a1531a035a
update favicon
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-05-10 22:49:30 +02:00
Simon Vieille
6a346a693f
update favicon
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-05-10 19:08:37 +02:00
Simon Vieille
53323ffd09
update ci
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-04-17 13:57:19 +02:00
Simon Vieille
ccb74f436c
update ci
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-17 13:53:19 +02:00
Simon Vieille
f383ccdb3b
update dependencies
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-04-17 13:39:51 +02:00
Simon Vieille
22a7738e14
remove form elements of TextPage
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-04-16 22:57:36 +02:00
Simon Vieille
5df9611adc
update dependencies
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-04-15 17:43:30 +02:00
Simon Vieille
a1d889e7df
add inline forms
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-04-14 11:31:13 +02:00
Simon Vieille
e3a909f18c
update murph 2023-04-14 11:31:06 +02:00
Simon Vieille
a572705b35
add preview
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-04-02 18:52:31 +02:00
Simon Vieille
a95ac6ebea
allow webp 2023-04-02 18:52:25 +02:00
Simon Vieille
798b66c96c
fix constraints of file in project form
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-03-09 23:00:01 +01:00
Simon Vieille
8733a3498f
update og image for quick post
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-02-19 19:25:14 +01:00
Simon Vieille
8d01a277f1
update css for link
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-02-19 19:04:38 +01:00
Simon Vieille
d62403afca
change btn size (projects and meshes) + image preview of meshes
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/deployment/woodpecker Pipeline was successful
2023-02-13 13:22:52 +01:00
133 changed files with 11688 additions and 38686 deletions

7
.env
View file

@ -35,3 +35,10 @@ MAILER_SENDER=example@localhost
ASSET_BASE_URL=null ASSET_BASE_URL=null
UMAMI_URL=null UMAMI_URL=null
MESSENGER_TRANSPORT_DSN=doctrine://default
INFLUXDB_URL=
INFLUXDB_TOKEN=
INFLUXDB_BUCKET=
INFLUXDB_ORG=
INFLUXDB_DEBUG=1

1
.gitignore vendored
View file

@ -35,4 +35,3 @@ yarn-error.log
/migrations/*.php /migrations/*.php
/.build /.build
/appdata

View file

@ -18,6 +18,7 @@ magephp:
- "/var/cache/*" - "/var/cache/*"
- "/var/log/*" - "/var/log/*"
- "/public/media" - "/public/media"
- "/.secrets"
hosts: hosts:
- ssh_host - ssh_host
on-deploy: on-deploy:
@ -25,3 +26,4 @@ magephp:
- exec: { cmd: 'make doctrine-migration', desc: 'migration' } - exec: { cmd: 'make doctrine-migration', desc: 'migration' }
- exec: { cmd: 'php8.1 ./bin/console cache:warmup', desc: 'warmup' } - exec: { cmd: 'php8.1 ./bin/console cache:warmup', desc: 'warmup' }
- exec: { cmd: 'php8.1 ./bin/console cache:warmup', desc: 'warmup2' } - exec: { cmd: 'php8.1 ./bin/console cache:warmup', desc: 'warmup2' }
- exec: { cmd: './bin/messenger -a restart', desc: 'messenger' }

39
.novops.yml Normal file
View file

@ -0,0 +1,39 @@
environments:
build:
variables:
- name: MYSQLDUMP
value:
hvault_kv2:
mount: kv
path: deblan/deblan.io-murph
key: mysqldump
deploy:
variables:
- name: SSH_USER
value:
hvault_kv2:
mount: kv
path: deblan/deblan.io-murph
key: ssh_user
- name: SSH_HOST
value:
hvault_kv2:
mount: kv
path: deblan/deblan.io-murph
key: ssh_host
- name: SSH_PRIV_KEY
value:
hvault_kv2:
mount: kv
path: deblan/deblan.io-murph
key: ssh_priv_key
- name: APP_DIRECTORY
value:
hvault_kv2:
mount: kv
path: deblan/deblan.io-murph
key: app_directory

View file

@ -1,53 +1,56 @@
pipeline: variables:
db-wait: volumes: &volumes
- node_cache:/root/.npm
- /data/${CI_REPO}:/builds
when:
event: [push, pull_request, tag, manual]
branch: [master, master-*, develop, develop-*, feature/*]
steps:
"Wait the database":
image: gitnet.fr/deblan/timeout:latest image: gitnet.fr/deblan/timeout:latest
commands: commands:
- /bin/timeout -t 30 -v -c 'while true; do nc -z -v db 3306 2>&1 | grep succeeded && exit 0; sleep 0.5; done' - /bin/timeout -t 30 -v -c 'while true; do nc -z -v db 3306 2>&1 | grep succeeded && exit 0; sleep 0.5; done'
db-create: "Create database":
image: mariadb:10.3 image: mariadb:10.3
secrets: [mysqldump] environment:
MYSQLDUMP:
from_secret: mysqldump
commands: commands:
- mysql -hdb -uroot -proot -e "CREATE DATABASE app" - mysql -hdb -uroot -proot -e "CREATE DATABASE app"
- eval "$MYSQLDUMP" | mysql -hdb -uroot -proot app - eval "$MYSQLDUMP" | mysql -hdb -uroot -proot app
when:
branch: [master, master-*, develop, develop-*]
app-config: "Configure app":
image: deblan/php:8.1 image: deblan/php:8.1
commands: commands:
- echo APP_ENV=prod >> .env.local - echo APP_ENV=prod >> .env.local
- echo APP_SECRET=$(openssl rand -hex 32) >> .env.local - echo APP_SECRET=$(openssl rand -hex 32) >> .env.local
- echo DATABASE_URL=mysql://root:root@db/app >> .env.local - echo DATABASE_URL=mysql://root:root@db/app >> .env.local
when:
branch: [master, master-*, develop, develop-*]
php-composer: "Installs PHP dependencies":
image: deblan/php:8.1 image: deblan/php:8.1
commands: commands:
- apt-get update && apt-get -y install git - apt-get update && apt-get -y install git
- composer install --no-scripts - composer install --no-scripts
db-migrate: "Migrates database":
image: deblan/php:8.1 image: deblan/php:8.3
environment: environment:
- PHP=php PHP: php
commands: commands:
- ./bin/doctrine-migrate - ./bin/doctrine-migrate
when:
branch: [master, master-*, develop, develop-*, feature/*]
app-jsroutes: "Generates JS routes":
image: deblan/php:8.1 image: deblan/php:8.3
commands: commands:
- php bin/console fos:js-routing:dump --format=json --target=public/js/fos_js_routes.json - php bin/console fos:js-routing:dump --format=json --target=public/js/fos_js_routes.json
when:
branch: [master, master-*, develop, develop-*, feature/*]
node-build: "Build assets":
image: node:16-alpine image: node:20-alpine
environment: environment:
- CPU_COUNT=3 CPU_COUNT: 3
commands: commands:
- apk add --no-cache git - apk add --no-cache git
- npm install -g svg2ttf ttf2eot ttf2woff2 - npm install -g svg2ttf ttf2eot ttf2woff2
@ -58,32 +61,24 @@ pipeline:
- test -f public/js/fos_js_routes.json || echo "{}" > public/js/fos_js_routes.json - test -f public/js/fos_js_routes.json || echo "{}" > public/js/fos_js_routes.json
- npm run build - npm run build
security-check: "Check dependencies":
image: gitnet.fr/deblan/osv-detector:v0.9 image: gitnet.fr/deblan/osv-detector:v0.9
commands: commands:
- osv-detector composer.lock yarn.lock - osv-detector composer.lock yarn.lock
failure: ignore failure: ignore
app-deploy: "Build the cache":
image: deblan/php:8.1 image: deblan/mage
secrets: [ssh_user, ssh_host, ssh_priv_key, app_directory] volumes: *volumes
commands: commands:
- apt-get update && apt-get -y install rsync openssh-client - cd /builds
- mkdir "$HOME/.ssh" - rsync -az "$CI_WORKSPACE/" "$CI_COMMIT_SHA"
- echo "$SSH_PRIV_KEY" > "$HOME/.ssh/id_ed25519"
- chmod 700 "$HOME/.ssh"
- chmod 600 "$HOME/.ssh/id_ed25519"
- composer global require andres-montanez/magallanes
- cp .mage.yml.dist .mage.yml
- sed -i "s/ssh_user/$SSH_USER/g" .mage.yml
- sed -i "s/ssh_host/$SSH_HOST/g" .mage.yml
- sed -i "s#app_directory#$APP_DIRECTORY#g" .mage.yml
- /root/.config/composer/vendor/bin/mage deploy "$CI_BUILD_DEPLOY_TARGET"
when:
event: [deployment]
services: services:
db: db:
image: mariadb:10.3 image: mariadb:10.3
environment: environment:
- MARIADB_ROOT_PASSWORD=root MARIADB_ROOT_PASSWORD: root
volumes:
node_cache:

33
.woodpecker/deploy.yml Normal file
View file

@ -0,0 +1,33 @@
variables:
volumes: &volumes
- /data/${CI_REPO}:/builds
when:
event: [deployment]
skip_clone: true
steps:
"Deploy":
image: deblan/mage
environment:
SSH_PRIV_KEY:
from_secret: ssh_priv_key
SSH_USER:
from_secret: ssh_user
SSH_HOST:
from_secret: ssh_host
APP_DIRECTORY:
from_secret: app_directory
volumes: *volumes
commands:
- cd "/builds/$CI_COMMIT_SHA"
- mkdir "$HOME/.ssh"
- echo "$SSH_PRIV_KEY" > "$HOME/.ssh/id_ed25519"
- chmod 700 "$HOME/.ssh"
- chmod 600 "$HOME/.ssh/id_ed25519"
- cp .mage.yml.dist .mage.yml
- sed -i "s/ssh_user/$SSH_USER/g" .mage.yml
- sed -i "s/ssh_host/$SSH_HOST/g" .mage.yml
- sed -i "s#app_directory#$APP_DIRECTORY#g" .mage.yml
- mage deploy "$CI_PIPELINE_DEPLOY_TARGET"

View file

@ -1,65 +0,0 @@
FROM php:8.2-fpm-bullseye
WORKDIR /var/www/app
RUN apt-get update && \
apt-get install -y \
cron \
libyaml-dev \
libcurl4-openssl-dev \
libicu-dev \
libpqxx-dev \
libonig-dev \
libsqlite3-dev \
libedit-dev \
libtidy-dev \
libxml2-dev \
libzip-dev \
libxslt1-dev
RUN pecl channel-update pecl.php.net && \
pecl install apcu igbinary xmlrpc-beta yaml
RUN docker-php-ext-install \
curl \
intl \
mbstring \
opcache \
pdo_pgsql \
pdo_sqlite \
pdo_mysql \
tidy \
xml \
xsl \
zip
RUN docker-php-ext-enable \
curl \
intl \
mbstring \
opcache \
pdo_pgsql \
pdo_sqlite \
pdo_mysql \
tidy \
xml \
xsl \
xmlrpc \
apcu \
zip
RUN mkdir -p \
var/cache/prod \
var/cache/dev \
&& \
chmod 777 -R var/cache/*
RUN mkdir -p var/log \
&& \
touch var/log/prod.log var/log/dev.log \
&& \
chmod 777 -R var/log/*.log
EXPOSE 9000
CMD ["./entrypoint.sh"]

View file

@ -1,7 +1,6 @@
COMPOSER ?= composer COMPOSER ?= composer
PHP ?= php PHP_BIN ?= php8.1
SSH ?= ssh YARN_BIN ?= yarn
YARN ?= yarn
NPM_BIN ?= npm NPM_BIN ?= npm
all: build all: build
@ -20,10 +19,10 @@ js-routing: doctrine-migration
clean: clean:
rm -fr var/cache/dev/* rm -fr var/cache/dev/*
rm -fr var/cache/prod/* rm -fr var/cache/prod/*
$(PHP) bin/console $(PHP_BIN) bin/console
doctrine-migration: doctrine-migration:
PHP=$(PHP) ./bin/doctrine-migrate PHP=$(PHP_BIN) ./bin/doctrine-migrate
.ONESHELL: .ONESHELL:
lint: lint:

View file

@ -1,9 +1,6 @@
@import "../../vendor/murph/murph-core/src/core/Resources/assets/css/admin.scss"; @import "../../vendor/murph/murph-core/src/core/Resources/assets/css/admin.scss";
@import "~simplemde/dist/simplemde.min.css"; @import "@kangc/v-md-editor/lib/style/base-editor.css";
@import "@kangc/v-md-editor/lib/theme/style/vuepress.css";
.CodeMirror-fullscreen, .editor-toolbar.fullscreen {
z-index: 2000;
}
.ejs-link { .ejs-link {
margin: 10px auto; margin: 10px auto;
@ -55,3 +52,12 @@
.choices__list--dropdown { .choices__list--dropdown {
z-index: 3; z-index: 3;
} }
.v-md-editor {
border: 1px solid $input-border-color;
box-shadow: none;
}
.v-md-editor--fullscreen {
z-index: 3000;
}

View file

@ -1,29 +1,26 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@import "app/config"; @import "app/config";
@import 'app/prism'; @import "app/prism";
@import "app/typo"; @import "app/typo";
@import "~tingle.js/src/tingle.css"; @import "~tingle.js/src/tingle.css";
@font-face { @font-face {
font-family: "MainFont"; font-family: "MainFont";
src: src: url("../fonts/ubuntu/ubuntu-light.woff2?20211108") format("woff2"), url("../fonts/ubuntu/ubuntu-light.woff?20211108") format("woff");
url('../fonts/ubuntu/ubuntu-light.woff2?20211108') format('woff2'),
url('../fonts/ubuntu/ubuntu-light.woff?20211108') format('woff');
// url('../fonts/atkinson/WOFF2/Atkinson-Hyperlegible-Regular-102a.woff2?20220911') format('woff2'), // url('../fonts/atkinson/WOFF2/Atkinson-Hyperlegible-Regular-102a.woff2?20220911') format('woff2'),
// url('../fonts/atkinson/WOFF/Atkinson-Hyperlegible-Regular-102.woff?20211108w20220911') format('woff'); // url('../fonts/atkinson/WOFF/Atkinson-Hyperlegible-Regular-102.woff?20211108w20220911') format('woff');
} }
@font-face { @font-face {
font-family: "deblan-icon"; font-family: "deblan-icon";
src: url('../fonts/deblan/deblan-icon.eot?20211108'); src: url("../fonts/deblan/deblan-icon.eot?20211108");
src: src: url("../fonts/deblan/deblan-icon.woff2?20211108") format("woff2"), url("../fonts/deblan/deblan-icon.woff?20211108") format("woff"), url("../fonts/deblan/deblan-icon.ttf?20211108") format("truetype");
url('../fonts/deblan/deblan-icon.woff2?20211108') format('woff2'),
url('../fonts/deblan/deblan-icon.woff?20211108') format('woff'),
url('../fonts/deblan/deblan-icon.ttf?20211108') format('truetype');
font-style: normal; font-style: normal;
font-weight: normal; font-weight: normal;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
@ -157,8 +154,9 @@ pre[class*="language-"] {
height: 50px; height: 50px;
overflow: hidden; overflow: hidden;
display: inline-block; display: inline-block;
margin-bottom: -19px;i margin-bottom: -19px;
i
&:active, &:focus { &:active, &:focus {
background: none; background: none;
} }
@ -173,6 +171,7 @@ pre[class*="language-"] {
background: url(../images/Refresh_icon.svg); background: url(../images/Refresh_icon.svg);
} }
} }
// //
// &-captcha { // &-captcha {
// label { // label {
@ -304,9 +303,11 @@ pre[class*="language-"] {
0% { 0% {
background-position: 0 0%; background-position: 0 0%;
} }
50% { 50% {
background-position: 0 75%; background-position: 0 75%;
} }
100% { 100% {
background-position: 0 0%; background-position: 0 0%;
} }
@ -347,18 +348,20 @@ pre[class*="language-"] {
width: 100%; width: 100%;
height: 10px; height: 10px;
background: rgb(0, 0, 0); background: rgb(0, 0, 0);
background: linear-gradient(180deg, rgba(0, 0, 0, 0.3) 0%, rgba(255,255,255,0) 100%); background: linear-gradient(180deg, rgba(0, 0, 0, 0.3) 0%, rgba(255, 255, 255, 0) 100%);
} }
@keyframes HeaderGradient { @keyframes HeaderGradient {
0% { 0% {
background-position: 0 50% background-position: 0 50%;
} }
50% { 50% {
background-position: 100% 50% background-position: 100% 50%;
} }
100% { 100% {
background-position: 0 50% background-position: 0 50%;
} }
} }
@ -411,6 +414,9 @@ pre[class*="language-"] {
.h1 { .h1 {
font-weight: normal; font-weight: normal;
font-size: 40px; font-size: 40px;
font-family: MainFont;
text-shadow: none;
color: hsla(0, 0%, 100%, 0.7);
} }
.h3 { .h3 {
@ -471,8 +477,8 @@ pre[class*="language-"] {
color: #fff; color: #fff;
} }
p a, ul:not(.btn-group) a { p a:not(.btn), ul:not(.btn-group) a:not(.btn) {
background: url('../images/link.svg') bottom left repeat-x; background: url("../images/link.svg") bottom left repeat-x;
padding-bottom: 5px; padding-bottom: 5px;
} }
@ -503,6 +509,35 @@ pre[class*="language-"] {
@include make-pre-code; @include make-pre-code;
} }
&.mermaid {
box-shadow: none;
background: #343c53;
border: 0;
text, tspan {
fill: #fff !important;
}
.actor, .noteText {
text, tspan {
fill: #333 !important;
}
}
.labelText {
fill: #333 !important;
}
line, path {
stroke: #fff !important;
}
svg {
display: block;
margin: auto;
}
}
&.with-title { &.with-title {
margin-top: 0; margin-top: 0;
border-top-right-radius: 0; border-top-right-radius: 0;
@ -513,11 +548,41 @@ pre[class*="language-"] {
padding-bottom: 10px; padding-bottom: 10px;
padding-top: 10px; padding-top: 10px;
border: 1px solid $color-code-border; border: 1px solid $color-code-border;
// overflow: hidden; // overflow: hidden;
// //
// &:hover { // &:hover {
// overflow: auto; // overflow: auto;
// } // }
}
}
}
.post-author-wrapper {
.post-author-avatar {
vertical-align: top;
width: 100px;
padding: 10px;
display: inline-block;
}
.post-author {
display: inline-block;
width: calc(100% - 100px);
padding: 10px 10px 10px 30px;
}
@media screen and (max-width: 774px) {
.post-author-avatar {
display: block;
margin: auto;
}
.post-author {
display: block;
width: 100%;
padding-right: 20px;
} }
} }
} }
@ -577,12 +642,14 @@ pre[class*="language-"] {
@for $i from 1 through 6 { @for $i from 1 through 6 {
.review.offset-#{$i} { .review.offset-#{$i} {
margin-left: 5% * $i - 1%; margin-left: 5% * $i - 1%;
width: 100% - ($i * 5%); width: 100% - $i * 5%;
} }
} }
.review { .review {
width: 100%; width: 100%;
max-width: calc($content-max-width - 60px - 2rem);
overflow: auto;
.review-avatar, .review-avatar img { .review-avatar, .review-avatar img {
width: 60px; width: 60px;
@ -663,7 +730,7 @@ pre[class*="language-"] {
.code-window { .code-window {
height: 50px; height: 50px;
background: $color-code-title-background url('../images/window.svg') no-repeat center right; background: $color-code-title-background url("../images/window.svg") no-repeat center right;
padding-left: 15px; padding-left: 15px;
font-family: Monospace; font-family: Monospace;
color: #ccc; color: #ccc;
@ -697,7 +764,8 @@ pre[class*="language-"] {
width: 100%; width: 100%;
height: 450px; height: 450px;
background-position: center center; background-position: center center;
background: #f2f2f2 url('../images/quick-post-load.png') no-repeat center center; background: #f2f2f2 url("../images/quick-post-load.png") no-repeat center center;
// border: 2px solid $color-very-light-grey; // border: 2px solid $color-very-light-grey;
border-bottom: 0; border-bottom: 0;
cursor: pointer; cursor: pointer;
@ -836,19 +904,7 @@ pre[class*="language-"] {
border: 1px solid $color-white; border: 1px solid $color-white;
} }
$links: ( $links: (twitter: #20b8ff, rss: #fd9f13, linkedin: #006699, diaspora: #90b92e, github: #8cc345, code: #51d066, mastodon: #2984d2, pixelfed: #e72151, matrix: #1a588a, gpg: #42a73b, murph: #19b4db);
twitter: #20b8ff,
rss: #fd9f13,
linkedin: #006699,
diaspora: #90b92e,
github: #8cc345,
code: #51d066,
mastodon: #2984d2,
pixelfed: #e72151,
matrix: #1a588a,
gpg: #42a73b,
murph: #19b4db
);
@each $site, $bg in $links { @each $site, $bg in $links {
.link-#{$site} { .link-#{$site} {
@ -951,25 +1007,29 @@ $links: (
} }
@keyframes bounceIn { @keyframes bounceIn {
0%{ 0% {
opacity: 0; opacity: 0;
} }
50%{
50% {
opacity: 0.9; opacity: 0.9;
} }
80%{
80% {
opacity: 1; opacity: 1;
} }
100%{
100% {
opacity: 1; opacity: 1;
} }
} }
@keyframes knmc { @keyframes knmc {
0%{ 0% {
left: -256px;; left: -256px;
} }
100%{
100% {
left: 150vw; left: 150vw;
} }
} }
@ -1074,6 +1134,8 @@ $links: (
.ejs-link { .ejs-link {
margin: 10px auto; margin: 10px auto;
width: 80%;
margin-bottom: 20px;
&--anchor { &--anchor {
display: block; display: block;
@ -1198,6 +1260,8 @@ $links: (
} }
.ejs-link { .ejs-link {
width: auto;
&-content { &-content {
display: block; display: block;
width: 100% !important; width: 100% !important;
@ -1315,7 +1379,7 @@ $links: (
} }
.header-shadow { .header-shadow {
background: linear-gradient(180deg, rgba(0, 0, 0, 0.3) 0%, rgba(255,255,255,0) 100%); background: linear-gradient(180deg, rgba(0, 0, 0, 0.3) 0%, rgba(255, 255, 255, 0) 100%);
} }
} }
@ -1328,3 +1392,18 @@ $links: (
} }
} }
} }
.deprecated {
color: #fff;
background: #3abff8;
padding: 1rem;
border-radius: var(--rounded-box, 1rem);
text-align: center;
svg {
display: inline-block;
height: 25px;
vertical-align: top;
margin-right: 8px;
}
}

View file

@ -62,6 +62,7 @@ $color-code-text: #f8f8f2;
$color-code-mark-background: $color-light-blue; $color-code-mark-background: $color-light-blue;
$color-code-title-background: #1d2231; $color-code-title-background: #1d2231;
$color-code-title-text: #e0e0e0; $color-code-title-text: #e0e0e0;
/* --- */ /* --- */
:root { :root {

View file

@ -19,11 +19,9 @@ pre[class*="language-"] {
word-break: normal; word-break: normal;
word-wrap: normal; word-wrap: normal;
line-height: 1.5; line-height: 1.5;
-moz-tab-size: 4; -moz-tab-size: 4;
-o-tab-size: 4; -o-tab-size: 4;
tab-size: 4; tab-size: 4;
-webkit-hyphens: none; -webkit-hyphens: none;
-moz-hyphens: none; -moz-hyphens: none;
-ms-hyphens: none; -ms-hyphens: none;
@ -116,6 +114,7 @@ pre[class*="language-"] {
.token.bold { .token.bold {
font-weight: bold; font-weight: bold;
} }
.token.italic { .token.italic {
font-style: italic; font-style: italic;
} }
@ -123,4 +122,3 @@ pre[class*="language-"] {
.token.entity { .token.entity {
cursor: help; cursor: help;
} }

View file

@ -17,33 +17,34 @@
// //
.h1, h1 { .h1, h1 {
font-size: 3.2rem; font-size: 3.2rem;
line-height: 1.32 line-height: 1.32;
} }
.h2, h2 { .h2, h2 {
font-size: 2.6rem; font-size: 2.6rem;
line-height: 1.35 line-height: 1.35;
} }
.h3, h3 { .h3, h3 {
font-size: 2.0rem; font-size: 2.0rem;
line-height: 1.45 line-height: 1.45;
} }
.h4, h4 { .h4, h4 {
font-size: 1.4rem; font-size: 1.4rem;
line-height: 1.6 line-height: 1.6;
} }
.h5, h5 { .h5, h5 {
font-size: 1.1rem; font-size: 1.1rem;
line-height: 1.75 line-height: 1.75;
} }
.h6, h6 { .h6, h6 {
font-size: 1.0rem; font-size: 1.0rem;
line-height: 1.9 line-height: 1.9;
} }
// //
// p { // p {
// margin-top: 17px; // margin-top: 17px;
@ -56,7 +57,6 @@
// } // }
// } // }
.h1, .h1,
.h2, .h2,
.h3, .h3,
@ -71,6 +71,5 @@ h5,
h6 { h6 {
font-weight: 600; font-weight: 600;
margin-bottom: 1rem; margin-bottom: 1rem;
margin-top: 0 margin-top: 0;
} }

View file

@ -1,6 +1,6 @@
import '../../vendor/murph/murph-core/src/core/Resources/assets/js/admin.js' import '../../vendor/murph/murph-core/src/core/Resources/assets/js/admin.js'
require('./admin_modules/simplemde')() require('./admin_modules/md-editor')()
const $ = require('jquery') const $ = require('jquery')
const Sortable = require('sortablejs').Sortable const Sortable = require('sortablejs').Sortable

View file

@ -0,0 +1,34 @@
const Vue = require('vue').default
const VueMarkdownEditor = require('@kangc/v-md-editor')
const githubTheme = require('@kangc/v-md-editor/lib/theme/github.js')
const fr = require('@kangc/v-md-editor/lib/lang/fr-FR').default
const hljs = require('highlight.js')
VueMarkdownEditor.use(githubTheme, {Hljs: hljs})
VueMarkdownEditor.lang.use('fr-FR', fr)
Vue.use(VueMarkdownEditor)
module.exports = () => {
const components = document.querySelectorAll('.markdown-editor')
components.forEach((component) => {
return new Vue({
el: component,
template: `
<div>
<textarea :name="name" v-model="value" class="d-none"></textarea>
<v-md-editor v-model="value" mode="edit"></v-md-editor>
</div>
`,
data() {
return {
name: component.getAttribute('data-name'),
value: JSON.parse(component.getAttribute('data-value')),
}
},
components: {
VueMarkdownEditor
}
})
})
}

View file

@ -13,8 +13,7 @@ module.exports = () => {
autoDownloadFontAwesome: false, autoDownloadFontAwesome: false,
spellChecker: false, spellChecker: false,
styleSelectedText: false, styleSelectedText: false,
toolbar: [ toolbar: [{
{
name: 'bold', name: 'bold',
action: Editor.toggleBold, action: Editor.toggleBold,
className: 'fa fa-bold', className: 'fa fa-bold',

View file

@ -9,7 +9,7 @@ const Code = require('./app/code')
const Knmc = require('./app/knmc') const Knmc = require('./app/knmc')
const VideoRatio = require('./app/video-ratio') const VideoRatio = require('./app/video-ratio')
const Stats = require('./app/stats') const Stats = require('./app/stats')
const Particles = require('./app/particles') // const Particles = require('./app/particles')
const MeshViewer = require('./app/mesh-viewer') const MeshViewer = require('./app/mesh-viewer')
const SmallMenu = require('./app/small-menu') const SmallMenu = require('./app/small-menu')
@ -22,11 +22,11 @@ const app = new App([
new Knmc(window), new Knmc(window),
new VideoRatio(window), new VideoRatio(window),
// new Stats(), // new Stats(),
new Particles(window), // new Particles(window),
new MeshViewer(window), new MeshViewer(window),
new SmallMenu(window) new SmallMenu(window)
]) ])
window.addEventListener('load', function () { window.addEventListener('load', function() {
app.init() app.init()
}, false) }, false)

View file

@ -1,15 +1,15 @@
class App { class App {
constructor(components) { constructor (components) {
this.components = components || [] this.components = components || []
} }
add(c) { add (c) {
this.components.push(c) this.components.push(c)
return this return this
} }
init() { init () {
for (let u = 0, x = this.components.length; u < x; u++) { for (let u = 0, x = this.components.length; u < x; u++) {
this.components[u].init() this.components[u].init()
} }

View file

@ -22,6 +22,7 @@ require('prismjs/components/prism-shell-session')
require('prismjs/plugins/keep-markup/prism-keep-markup') require('prismjs/plugins/keep-markup/prism-keep-markup')
require('prismjs/plugins/line-highlight/prism-line-highlight') require('prismjs/plugins/line-highlight/prism-line-highlight')
require('prismjs/plugins/line-numbers/prism-line-numbers') require('prismjs/plugins/line-numbers/prism-line-numbers')
require('mermaid')
class Code { class Code {
constructor(w) { constructor(w) {

View file

@ -1,14 +1,14 @@
const Routing = require('./routing') const Routing = require('./routing')
class FormPnw { class FormPnw {
constructor(w) { constructor (w) {
this.window = w this.window = w
} }
init() { init () {
const doc = this.window.document const doc = this.window.document
doc.addEventListener('mousemove', function() { doc.addEventListener('mousemove', function () {
const forms = doc.querySelectorAll('form[data-form-bot]') const forms = doc.querySelectorAll('form[data-form-bot]')
for (let i = 0, len = forms.length; i < len; i++) { for (let i = 0, len = forms.length; i < len; i++) {

View file

@ -1,16 +1,16 @@
const Mario = require('../../images/mario.gif') const Mario = require('../../images/mario.gif')
class Knmc { class Knmc {
constructor(w) { constructor (w) {
this.window = w this.window = w
} }
init() { init () {
let chars = '' let chars = ''
const seq = '38384040373937396665' const seq = '38384040373937396665'
const body = this.window.document.querySelector('body') const body = this.window.document.querySelector('body')
body.addEventListener('keyup', function(e) { body.addEventListener('keyup', function (e) {
chars += e.keyCode.toString() chars += e.keyCode.toString()
if (chars.indexOf(seq) !== -1) { if (chars.indexOf(seq) !== -1) {
@ -22,7 +22,7 @@ class Knmc {
image.style.bottom = '-11px' image.style.bottom = '-11px'
image.style.left = '-256px' image.style.left = '-256px'
image.onload = function() { image.onload = function () {
image.classList.add('knmc') image.classList.add('knmc')
body.appendChild(image) body.appendChild(image)
} }

View file

@ -1,7 +1,7 @@
const lozad = require('lozad') const lozad = require('lozad')
class LazyLoad { class LazyLoad {
init() { init () {
const observer = lozad('.lazy-img') const observer = lozad('.lazy-img')
observer.observe() observer.observe()
} }

View file

@ -1,11 +1,11 @@
const Routing = require('./routing') const Routing = require('./routing')
class Post { class Post {
constructor(w) { constructor (w) {
this.window = w this.window = w
} }
commentsEvents() { commentsEvents () {
const document = this.window.document const document = this.window.document
const parentCommentIdField = document.getElementById('user_comment_parentCommentId') const parentCommentIdField = document.getElementById('user_comment_parentCommentId')
@ -17,7 +17,7 @@ class Post {
const isAnswerAlert = document.getElementById('answer-alert') const isAnswerAlert = document.getElementById('answer-alert')
const cancelAnswerButton = document.getElementById('cancel-answer') const cancelAnswerButton = document.getElementById('cancel-answer')
const toogleAnswerAlert = function() { const toogleAnswerAlert = function () {
if (parentCommentIdField.value) { if (parentCommentIdField.value) {
isAnswerAlert.classList.remove('hidden') isAnswerAlert.classList.remove('hidden')
} else { } else {
@ -30,13 +30,13 @@ class Post {
const answerButtons = document.querySelectorAll('a[data-answer]') const answerButtons = document.querySelectorAll('a[data-answer]')
for (let i = 0, len = answerButtons.length; i < len; i++) { for (let i = 0, len = answerButtons.length; i < len; i++) {
answerButtons[i].addEventListener('click', function(e) { answerButtons[i].addEventListener('click', function (e) {
parentCommentIdField.value = e.target.getAttribute('data-answer') parentCommentIdField.value = e.target.getAttribute('data-answer')
toogleAnswerAlert() toogleAnswerAlert()
}, false) }, false)
} }
cancelAnswerButton.addEventListener('click', function(e) { cancelAnswerButton.addEventListener('click', function (e) {
e.preventDefault() e.preventDefault()
parentCommentIdField.value = null parentCommentIdField.value = null
@ -46,7 +46,7 @@ class Post {
const previewButton = document.querySelector('.preview-button') const previewButton = document.querySelector('.preview-button')
const previewRender = document.querySelector('#preview') const previewRender = document.querySelector('#preview')
previewButton.addEventListener('click', function() { previewButton.addEventListener('click', function () {
if (previewRender.innerHTML === '') { if (previewRender.innerHTML === '') {
previewRender.innerHTML = '<p>Chargement en cours…</p>' previewRender.innerHTML = '<p>Chargement en cours…</p>'
} }
@ -54,7 +54,7 @@ class Post {
const content = document.querySelector('#user_comment_content').value const content = document.querySelector('#user_comment_content').value
const httpRequest = new XMLHttpRequest() const httpRequest = new XMLHttpRequest()
httpRequest.onreadystatechange = function(data) { httpRequest.onreadystatechange = function (data) {
if (httpRequest.readyState === 4 && httpRequest.status === 200) { if (httpRequest.readyState === 4 && httpRequest.status === 200) {
const json = JSON.parse(httpRequest.response) const json = JSON.parse(httpRequest.response)
previewRender.innerHTML = json.render previewRender.innerHTML = json.render
@ -71,12 +71,12 @@ class Post {
}, false) }, false)
} }
imagesEvents() { imagesEvents () {
const document = this.window.document const document = this.window.document
let isFullscreen = false let isFullscreen = false
const images = document.querySelectorAll('.body img') const images = document.querySelectorAll('.body img')
const handleClick = function(image) { const handleClick = function (image) {
if (isFullscreen) { if (isFullscreen) {
if (document.exitFullscreen) { if (document.exitFullscreen) {
document.exitFullscreen() document.exitFullscreen()
@ -105,15 +105,15 @@ class Post {
continue continue
} }
(function(i) { (function (i) {
i.addEventListener('click', function() { i.addEventListener('click', function () {
handleClick(i) handleClick(i)
}, false) }, false)
})(image) })(image)
} }
} }
init() { init () {
this.commentsEvents() this.commentsEvents()
this.imagesEvents() this.imagesEvents()
} }

View file

@ -1,9 +1,9 @@
class PxImage { class PxImage {
constructor(w) { constructor (w) {
this.window = w this.window = w
} }
init() { init () {
const doc = this.window.document const doc = this.window.document
const images = doc.querySelectorAll('.quick-image img, .card figure img') const images = doc.querySelectorAll('.quick-image img, .card figure img')
@ -16,13 +16,13 @@ class PxImage {
const loader = new Image() const loader = new Image()
loader.onload = () => { loader.onload = () => {
image.style.background = `${color ? color : null} url(${source})` image.style.background = `${color || null} url(${source})`
image.style.backgroundSize = 'cover' image.style.backgroundSize = 'cover'
image.style.backgroundPosition = 'center' image.style.backgroundPosition = 'center'
} }
loader.onerror = () => { loader.onerror = () => {
image.style.background = `${color ? color : null} url('${sourceError}') center center` image.style.background = `${color || null} url('${sourceError}') center center`
} }
loader.src = source loader.src = source

View file

@ -1,9 +1,9 @@
class VideoRatio { class VideoRatio {
constructor(w) { constructor (w) {
this.window = w this.window = w
} }
init() { init () {
const videos = this.window.document.querySelectorAll('.video-ratio') const videos = this.window.document.querySelectorAll('.video-ratio')
for (let i = 0, len = videos.length; i < len; i++) { for (let i = 0, len = videos.length; i < len; i++) {

View file

@ -2,10 +2,11 @@ import '../css/viewer.scss'
const container = document.getElementById('mesh-viewer') const container = document.getElementById('mesh-viewer')
const viewer = new StlViewer( const viewer = new StlViewer(
container, container, {
{
auto_rotate: true, auto_rotate: true,
allow_drag_and_drop: true, allow_drag_and_drop: true,
models: [{ filename: container.getAttribute('data-file') }] models: [{
filename: container.getAttribute('data-file')
}]
} }
) )

85
assets/webapp/favicon.svg Normal file
View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="99.39035mm"
height="96.711884mm"
viewBox="0 0 99.39035 96.711884"
version="1.1"
id="svg80"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
sodipodi:docname="favicon.svg">
<defs
id="defs74" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.4"
inkscape:cx="352.07698"
inkscape:cy="193.34964"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1918"
inkscape:window-height="1021"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1" />
<metadata
id="metadata77">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-198.33095,46.217785)">
<path
style="fill:#de332c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 296.12648,44.426171 c -0.37616,2.92328 -0.59758,6.08799 -2.85992,6.06783 l -40.67456,-0.72885 -51.81177,-0.81029 -2.44928,-93.098834 c 0,0 0.47161,-3.839144 5.78393,-1.070305 l 18.27773,0.460614 1.6558,-0.665961 1.41788,0.47684 9.38085,0.02311 c 0,0 24.49397,1.995856 27.60257,0.772835 0,0 2.57574,-1.103967 4.63033,-0.769364 l 9.40198,0.03935 c 0,0 -0.35343,1.705818 3.88286,0.190643 l 6.19128,-0.134168 10.25258,-0.187043 c 0.58109,0.575105 1.71697,2.678558 0.0146,3.046832 l -0.15717,2.656438 c 0,0 0.38691,1.577371 0.24294,5.257968 l -0.31455,8.041286 -0.20367,37.847704 -0.43783,27.062555 c 0.18945,-0.0794 -1.36944,2.21097 0.17342,5.52081"
id="path44"
sodipodi:nodetypes="ccccccccccccccccccscccc" />
<path
style="fill:#1a1a1a;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 235.82199,-35.0018 1.08754,-2.025235 17.77563,0.21013 1.85521,0.809043 2.16713,20.710101 -2.3198,2.469536 -19.71646,-1.002688 z"
id="path46" />
<path
style="fill:#1a1a1a;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 265.2653,-10.29825 14.69965,0.279887 c 0,0 3.15158,-1.833364 4.06674,0.18447 0.91514,2.017834 0.56845,8.636437 0.51746,13.685586 -0.051,5.04915 -0.051,6.985119 -0.051,6.985119 l -18.20101,0.958779 -1.39041,-1.835613 0.97744,-2.381084 z"
id="path48" />
<path
style="fill:#1a1a1a;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 210.46943,16.678368 1.70731,-0.587661 c 0,0 1.60823,-1.095195 5.34804,-0.168331 l 9.63629,-0.308391 c 0,0 2.8091,-0.446237 1.35679,3.165083 l -0.0385,18.005543 -2.95227,1.86033 -13.68848,-0.13491 -1.45506,-19.81506 z"
id="path50" />
<path
style="fill:#1a1a1a;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 238.07883,15.88924 c 0,0 14.17009,-0.559639 14.45321,-0.441349 0.28313,0.118293 3.50832,-0.319106 3.60718,3.740288 0.0989,4.059391 0.52015,14.301642 0.52015,14.301642 0,0 0.26091,3.34746 -1.81487,4.95965 -2.07577,1.61219 -15.224,1.10303 -15.224,1.10303 l -2.63971,-5.62585 1.26513,-2.20493 -1.05718,-3.333734 -0.45105,-10.060183 z"
id="path52" />
<path
style="fill:#1a1a1a;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 267.80047,15.783724 c 0,0 -4.50081,-1.216835 -3.29521,6.498831 1.2056,7.715665 0.28562,14.423636 0.28562,14.423636 0,0 0.53697,2.11476 3.01121,2.44549 2.47425,0.33073 13.16476,-0.3909 13.16476,-0.3909 0,0 2.95523,0.0701 3.12091,-3.31289 0.16567,-3.38299 -0.18686,-14.513584 -0.18686,-14.513584 l -10e-4,-3.864433 c 0,0 0.50004,-1.593741 -1.01212,-1.688235 -1.51215,-0.0945 -15.087,0.402085 -15.087,0.402085 z"
id="path54" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

129
bin/messenger Executable file
View file

@ -0,0 +1,129 @@
#!/bin/sh
set -eu
usage() {
printf "Usage: %s [-l DEBUG_LEVEL] [-h] start|stop|restart\n" "$0"
}
help() {
cat << EOH
SYNOPSIS
$0 [-l DEBUG_LEVEL] [-h] -a start|stop|restart
DESCRIPTION
$0 manages symfony messenger
OPTIONS
-h Show this help
-l debug|info|notice|warning|error
Debug level
-a start|stop|restart|status
EOH
}
on_interrupt() {
log -l notice ""
log -l notice "Process aborted!"
exit 130
}
start_messenger() {
nohup php8.1 bin/console messenger:consume 2>/dev/null >/dev/null &
log -t -l notice "Started"
}
stop_messenger() {
php8.1 bin/console messenger:stop-workers 2>/dev/null >/dev/null
log -t -l notice "Stopped"
}
get_pid() {
pgrep -f messenger:consume
}
main() {
cd "$(dirname "0")"
ACTION=
while getopts "l:ha:" option; do
case "${option}" in
h) help; exit 0;;
l) LOG_VERBOSE="$OPTARG";;
a) ACTION="$OPTARG";;
?) log -l error "$(usage)"; exit 1;;
esac
done
if [ "$ACTION" = "start" ]; then
start_messenger
elif [ "$ACTION" = "stop" ]; then
stop_messenger
elif [ "$ACTION" = "restart" ]; then
stop_messenger
start_messenger
elif [ "$ACTION" = "status" ]; then
get_pid
else
log -l error "Action is required."
fi
exit 0
}
log() {
LOG_VERBOSE="${LOG_VERBOSE:-info}"
LEVEL=info
TIME=
while getopts "tl:" option; do
case "${option}" in
l) LEVEL="$OPTARG"; shift $((OPTIND-1));;
t) TIME="$(printf "[%s] " "$(date +'%Y-%m-%dT%H:%M:%S.%s')")"; shift $((OPTIND-1));;
*) exit 1;;
esac
done
if [ -t 2 ] && [ -z "${NO_COLOR-}" ]; then
case "${LEVEL}" in
debug) COLOR="$(tput setaf 3)";;
notice) COLOR="$(tput setaf 4)";;
warning) COLOR="$(tput setaf 5)";;
error) COLOR="$(tput setaf 1)";;
*) COLOR="$(tput sgr0)";;
esac
fi
case "${LEVEL}" in
debug) LEVEL=100;;
notice) LEVEL=250;;
warning) LEVEL=300;;
error) LEVEL=400;;
*) LEVEL=200;;
esac
case "${LOG_VERBOSE}" in
debug) LOG_VERBOSE_VALUE=100;;
notice) LOG_VERBOSE_VALUE=250;;
warning) LOG_VERBOSE_VALUE=300;;
error) LOG_VERBOSE_VALUE=400;;
*) LOG_VERBOSE_VALUE=200;;
esac
if [ $LEVEL -ge $LOG_VERBOSE_VALUE ]; then
printf "%s\n" "$*" | while IFS='' read -r LINE; do
printf "%s%s%s\n" "${COLOR:-}" "${TIME:-}" "$LINE" >&2
done
fi
}
trap on_interrupt INT
main "$@"

View file

@ -8,9 +8,12 @@
"beberlei/doctrineextensions": "^1.3", "beberlei/doctrineextensions": "^1.3",
"friendsofsymfony/jsrouting-bundle": "^2.7", "friendsofsymfony/jsrouting-bundle": "^2.7",
"gregwar/captcha-bundle": "^2.2", "gregwar/captcha-bundle": "^2.2",
"guzzlehttp/guzzle": "^7.8",
"influxdata/influxdb-client-php": "^3.4",
"knplabs/knp-markdown-bundle": "^1.9", "knplabs/knp-markdown-bundle": "^1.9",
"knplabs/knp-menu-bundle": "^3.1", "knplabs/knp-menu-bundle": "^3.1",
"murph/murph-core": "^1.18", "murph/murph-core": "dev-master",
"symfony/messenger": "5.4.*",
"twig/intl-extra": "^3.5" "twig/intl-extra": "^3.5"
}, },
"require-dev": { "require-dev": {
@ -31,7 +34,8 @@
"sort-packages": true, "sort-packages": true,
"allow-plugins": { "allow-plugins": {
"symfony/flex": true, "symfony/flex": true,
"symfony/runtime": true "symfony/runtime": true,
"php-http/discovery": true
} }
}, },
"autoload": { "autoload": {

View file

@ -1,7 +1,7 @@
core: core:
site: site:
name: "Blog" name: "Blog"
logo: "build/images/core/logo.svg" logo: "build/webapp/favicon.svg"
controllers: controllers:
- {name: 'LinkController:links', action: 'App\Controller\LinkController:links'} - {name: 'LinkController:links', action: 'App\Controller\LinkController:links'}
- {name: 'ContactController::contact', action: 'App\Controller\ContactController::contact'} - {name: 'ContactController::contact', action: 'App\Controller\ContactController::contact'}
@ -81,6 +81,7 @@ core:
- image/jpg - image/jpg
- image/jpeg - image/jpeg
- image/gif - image/gif
- image/webp
- image/svg+xml - image/svg+xml
- video/mp4 - video/mp4
- audio/mpeg3 - audio/mpeg3

View file

@ -13,18 +13,21 @@ liip_imagine:
max: [600, 600] max: [600, 600]
crop: crop:
size: [600, 270] size: [600, 270]
start: [0, 0]
project_preview_filter: project_preview_filter:
filters: filters:
downscale: downscale:
max: [600, 600] max: [600, 600]
crop: crop:
size: [600, 270] size: [600, 270]
start: [0, 0]
post_preview_filter: post_preview_filter:
filters: filters:
downscale: downscale:
max: [600, 600] max: [600, 600]
crop: crop:
size: [600, 300] size: [600, 300]
start: [0, 0]
site_avatar: site_avatar:
filters: filters:
downscale: downscale:

View file

@ -0,0 +1,7 @@
framework:
messenger:
transports:
async: "%env(MESSENGER_TRANSPORT_DSN)%"
routing:
'App\Message\PageViewMessage': async

View file

@ -1,6 +1,6 @@
twig: twig:
default_path: '%kernel.project_dir%/templates' default_path: '%kernel.project_dir%/templates'
form_themes: ['@Core/form/bootstrap_4_form_theme.html.twig'] form_themes: ['form/bootstrap_4_form_theme.html.twig']
auto_reload: true auto_reload: true
paths: paths:
'%kernel.project_dir%/templates/core/': Core '%kernel.project_dir%/templates/core/': Core

View file

@ -4,6 +4,11 @@
# Put parameters here that don't need to change on each machine where the app is deployed # Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters: parameters:
influxdb_url: '%env(INFLUXDB_URL)%'
influxdb_token: '%env(INFLUXDB_TOKEN)%'
influxdb_bucket: '%env(INFLUXDB_BUCKET)%'
influxdb_org: '%env(INFLUXDB_ORG)%'
influxdb_debug: '%env(INFLUXDB_DEBUG)%'
services: services:
# default configuration for services in *this* file # default configuration for services in *this* file
@ -47,6 +52,14 @@ services:
resource: '../src/Controller/' resource: '../src/Controller/'
tags: ['controller.service_arguments'] tags: ['controller.service_arguments']
App\Api\InfluxDB:
arguments:
$url: '%influxdb_url%'
$token: '%influxdb_token%'
$bucket: '%influxdb_bucket%'
$org: '%influxdb_org%'
$debug: '%influxdb_debug%'
site.route_loader: site.route_loader:
class: App\Core\Router\SiteRouteLoader class: App\Core\Router\SiteRouteLoader
tags: [routing.loader] tags: [routing.loader]
@ -69,5 +82,9 @@ services:
tags: tags:
- {name: markdown.parser, alias: comment} - {name: markdown.parser, alias: comment}
App\EventListener\StatListener:
tags:
- { name: kernel.event_listener, event: kernel.request }
# add more service definitions when explicit configuration is needed # add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones # please note that last definitions always *replace* previous ones

View file

@ -1,40 +0,0 @@
version: '3'
services:
web:
image: nginx:latest
volumes:
- ./:/var/www/app
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
links:
- app
env_file:
- .env.local
networks:
- app
ports:
- "1080:80"
app:
build:
context: .
user: "1000:1000"
networks:
- db
- app
volumes:
- ./:/var/www/app
db:
image: mariadb:10.3
volumes:
- ./appdata/db:/var/lib/mysql
environment:
- MARIADB_ROOT_PASSWORD=root
- MYSQL_DATABASE=app
networks:
- db
networks:
db:
external: true
gateway:
external: true
app:

View file

@ -1,25 +0,0 @@
server {
listen 80;
index index.php index.html;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
root /var/www/app/public;
location / {
# try to serve file directly, fallback to index.php
try_files $uri /index.php$is_args$args;
}
location ~ \.php$ {
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
}
location ~ \.php$ {
return 404;
}
}

View file

@ -1,15 +0,0 @@
#!/bin/sh
cat << BANNER
┌───┬───┬───┐
│ │ ● │ │
├───┼───┼───┤
│ │ │ ● │
├───┼───┼───┤
│ ● │ ● │ ● │
└───┴───┴───┘
BANNER
php-fpm

28615
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -8,13 +8,16 @@
"build": "./node_modules/.bin/encore production" "build": "./node_modules/.bin/encore production"
}, },
"dependencies": { "dependencies": {
"@kangc/v-md-editor": "^1.7.12",
"daisyui": "^2.31.0", "daisyui": "^2.31.0",
"editorjs-hyperlink": "^1.0.6",
"editorjs-inline-tool": "^0.4.0",
"encore": "^0.0.30-beta", "encore": "^0.0.30-beta",
"lozad": "^1.16.0", "lozad": "^1.16.0",
"murph-project": "^1", "mermaid": "^11.0.2",
"murph-project": "^1.9.4",
"particles.js": "^2.0.0", "particles.js": "^2.0.0",
"prismjs": "^1.23.0", "prismjs": "^1.23.0",
"simplemde": "^1.11.2",
"tingle.js": "^0.16.0", "tingle.js": "^0.16.0",
"vanillajs-datepicker": "^1.1.4", "vanillajs-datepicker": "^1.1.4",
"vue": "^2.6.14" "vue": "^2.6.14"
@ -25,5 +28,6 @@
"postcss": "^8.4.16", "postcss": "^8.4.16",
"postcss-loader": "^7.0.1", "postcss-loader": "^7.0.1",
"tailwindcss": "^3.1.8" "tailwindcss": "^3.1.8"
} },
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
} }

View file

@ -29,7 +29,7 @@ class DateRangeAnalytic extends BaseDateRangeAnalytic
foreach ($entities as $key => $entity) { foreach ($entities as $key => $entity) {
if ('view' === $type) { if ('view' === $type) {
if ($this->path === null || str_starts_with($entity->getPath(), $this->path)) { if (null === $this->path || str_starts_with($entity->getPath(), $this->path)) {
$newEntities[] = $entity; $newEntities[] = $entity;
} }
} }

46
src/Api/InfluxDB.php Normal file
View file

@ -0,0 +1,46 @@
<?php
namespace App\Api;
use InfluxDB2\Client;
use InfluxDB2\Model\WritePrecision;
/**
* class InfluxDB.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class InfluxDB
{
protected ?Client $client = null;
public function __construct(
protected ?string $url,
protected ?string $token,
protected ?string $bucket,
protected ?string $org,
protected bool $debug = false
) {
if (isset($this->url, $this->token, $this->bucket, $this->org)) {
$this->client = new Client([
'url' => $this->url,
'token' => $this->token,
'bucket' => $this->bucket,
'org' => $this->org,
'debug' => $this->debug,
'precision' => WritePrecision::S,
'timeout' => 1,
]);
}
}
public function isAvailable(): bool
{
return null !== $this->getClient();
}
public function getClient(): ?Client
{
return $this->client;
}
}

View file

@ -14,7 +14,7 @@ class TTRssClient
$result = @file_get_contents('https://tiny.deblan.org/deblan_api/?itemsPerPage=10&page='.$page); $result = @file_get_contents('https://tiny.deblan.org/deblan_api/?itemsPerPage=10&page='.$page);
if ($result) { if ($result) {
$result = str_replace('\\u0092', "'", $result); $result = str_replace('\u0092', "'", $result);
$result = str_replace('&#039;', "'", $result); $result = str_replace('&#039;', "'", $result);
return json_decode($result, true); return json_decode($result, true);

View file

@ -43,6 +43,7 @@ class CategoryAdminController extends CrudController
->setMaxPerPage('index', 100) ->setMaxPerPage('index', 100)
->setView('form', 'blog/category_admin/_form.html.twig') ->setView('form', 'blog/category_admin/_form.html.twig')
->setDoubleClick('index', true)
->setDefaultSort('index', 'title', 'asc') ->setDefaultSort('index', 'title', 'asc')

View file

@ -2,27 +2,30 @@
namespace App\Controller\Blog; namespace App\Controller\Blog;
use App\Analytic\DateRangeAnalytic;
use App\Core\Controller\Admin\Crud\CrudController; use App\Core\Controller\Admin\Crud\CrudController;
use App\Core\Crud\CrudConfiguration; use App\Core\Crud\CrudConfiguration;
use App\Core\Crud\Field\DatetimeField; use App\Core\Crud\Field\DatetimeField;
use App\Core\Crud\Field\TextField; use App\Core\Crud\Field\TextField;
use App\Core\Entity\EntityInterface;
use App\Core\Form\FileUploadHandler; use App\Core\Form\FileUploadHandler;
use App\Core\Manager\EntityManager; use App\Core\Manager\EntityManager;
use App\Core\Repository\Site\NodeRepository;
use App\Entity\Blog\Post;
use App\Entity\Blog\Post as Entity; use App\Entity\Blog\Post as Entity;
use App\Factory\Blog\PostFactory as EntityFactory; use App\Factory\Blog\PostFactory as EntityFactory;
use App\Form\Blog\Filter\PostFilterType; use App\Form\Blog\Filter\PostFilterType;
use App\Form\Blog\PostType; use App\Form\Blog\PostType;
use App\Form\Blog\PostType as EntityType;
use App\Repository\Blog\PostRepositoryQuery as RepositoryQuery; use App\Repository\Blog\PostRepositoryQuery as RepositoryQuery;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Form\Form; use Symfony\Component\Validator\Constraints\NotBlank;
use App\Core\Entity\EntityInterface;
use App\Entity\Blog\Post;
use App\Analytic\DateRangeAnalytic;
use App\Core\Repository\Site\NodeRepository;
#[Route(path: '/admin/blog/post')] #[Route(path: '/admin/blog/post')]
class PostAdminController extends CrudController class PostAdminController extends CrudController
@ -37,6 +40,7 @@ class PostAdminController extends CrudController
->setPageRoute('index', 'admin_blog_post_index') ->setPageRoute('index', 'admin_blog_post_index')
->setPageRoute('edit', 'admin_blog_post_edit') ->setPageRoute('edit', 'admin_blog_post_edit')
->setPageRoute('inline_edit', 'admin_blog_post_inline_edit')
->setPageRoute('new', 'admin_blog_post_new') ->setPageRoute('new', 'admin_blog_post_new')
->setPageRoute('show', 'admin_blog_post_show') ->setPageRoute('show', 'admin_blog_post_show')
->setPageRoute('delete', 'admin_blog_post_delete') ->setPageRoute('delete', 'admin_blog_post_delete')
@ -54,6 +58,7 @@ class PostAdminController extends CrudController
->setView('edit', 'blog/post_admin/edit.html.twig') ->setView('edit', 'blog/post_admin/edit.html.twig')
->setView('show', 'blog/post_admin/show.html.twig') ->setView('show', 'blog/post_admin/show.html.twig')
->setView('index', 'blog/post_admin/index.html.twig') ->setView('index', 'blog/post_admin/index.html.twig')
->setDoubleClick('index', true)
->setDefaultSort('index', 'id', 'desc') ->setDefaultSort('index', 'id', 'desc')
->setField('index', 'Titre', TextField::class, [ ->setField('index', 'Titre', TextField::class, [
@ -62,7 +67,7 @@ class PostAdminController extends CrudController
'attr' => ['class' => 'miw-400'], 'attr' => ['class' => 'miw-400'],
]) ])
->setField('index', 'ID', TextField::class, [ ->setField('index', 'ID', TextField::class, [
'property_builder' => function(EntityInterface $entity) { 'property_builder' => function (EntityInterface $entity) {
return sprintf('#%d', $entity->getId()); return sprintf('#%d', $entity->getId());
}, },
'sort' => ['id', '.id'], 'sort' => ['id', '.id'],
@ -79,19 +84,55 @@ class PostAdminController extends CrudController
'format' => 'd/m/Y H:i', 'format' => 'd/m/Y H:i',
'sort' => ['publishedAt', '.publishedAt'], 'sort' => ['publishedAt', '.publishedAt'],
'attr' => ['class' => 'miw-200'], 'attr' => ['class' => 'miw-200'],
'inline_form' => function (FormBuilderInterface $builder) {
$builder->add(
'publishedAt',
DateTimeType::class,
[
'label' => 'Date de publication',
'required' => false,
'html5' => true,
'widget' => 'single_text',
'attr' => [
'data-datetime' => '',
],
'constraints' => [
],
]
);
},
]) ])
->setField('index', 'Status', TextField::class, [ ->setField('index', 'Status', TextField::class, [
'view' => 'blog/post_admin/field/status.html.twig', 'view' => 'blog/post_admin/field/status.html.twig',
'sort' => ['status', '.status'], 'sort' => ['status', '.status'],
'attr' => ['class' => 'miw-100'], 'attr' => ['class' => 'miw-100'],
'inline_form' => function (FormBuilderInterface $builder) {
$builder->add(
'status',
ChoiceType::class,
[
'label' => 'Statut',
'required' => true,
'choices' => [
'Brouillon' => Entity::DRAFT,
'Publié' => Entity::PUBLISHED,
],
'attr' => [
],
'constraints' => [
new NotBlank(),
],
]
);
},
]) ])
->setBatchAction('index', 'delete', 'Delete', function(EntityInterface $entity, EntityManager $manager) { ->setBatchAction('index', 'delete', 'Delete', function (EntityInterface $entity, EntityManager $manager) {
$manager->delete($entity); $manager->delete($entity);
}) })
->setBatchAction('index', 'draft', 'Statut : publier', function(EntityInterface $entity, EntityManager $manager) { ->setBatchAction('index', 'draft', 'Statut : publier', function (EntityInterface $entity, EntityManager $manager) {
$manager->update($entity->setStatus(Post::PUBLISHED)); $manager->update($entity->setStatus(Post::PUBLISHED));
}) })
->setBatchAction('index', 'publish', 'Statut : brouillon', function(EntityInterface $entity, EntityManager $manager) { ->setBatchAction('index', 'publish', 'Statut : brouillon', function (EntityInterface $entity, EntityManager $manager) {
$manager->update($entity->setStatus(Post::DRAFT)); $manager->update($entity->setStatus(Post::DRAFT));
}) })
; ;
@ -110,7 +151,7 @@ class PostAdminController extends CrudController
$factory->create($this->getUser()), $factory->create($this->getUser()),
$entityManager, $entityManager,
$request, $request,
function(Entity $entity, Form $form, Request $request) use ($fileUpload) { function (Entity $entity, Form $form, Request $request) use ($fileUpload) {
$directory = 'uploads/post/'.date('Y'); $directory = 'uploads/post/'.date('Y');
$fileUpload->handleForm( $fileUpload->handleForm(
@ -131,7 +172,7 @@ class PostAdminController extends CrudController
$entity, $entity,
$entityManager, $entityManager,
$request, $request,
function(Entity $entity, Form $form, Request $request) use ($fileUpload) { function (Entity $entity, Form $form, Request $request) use ($fileUpload) {
$directory = 'uploads/post/'.date('Y'); $directory = 'uploads/post/'.date('Y');
$fileUpload->handleForm( $fileUpload->handleForm(
@ -145,6 +186,12 @@ class PostAdminController extends CrudController
); );
} }
#[Route(path: '/inline_edit/{entity}/{context}/{label}', name: 'admin_blog_post_inline_edit', methods: ['GET', 'POST'])]
public function inlineEdit(string $context, string $label, Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doInlineEdit($context, $label, $entity, $entityManager, $request);
}
#[Route(path: '/show/{entity}', name: 'admin_blog_post_show')] #[Route(path: '/show/{entity}', name: 'admin_blog_post_show')]
public function show(Entity $entity): Response public function show(Entity $entity): Response
{ {
@ -220,8 +267,7 @@ class PostAdminController extends CrudController
DateRangeAnalytic $analytic, DateRangeAnalytic $analytic,
NodeRepository $nodeRepository, NodeRepository $nodeRepository,
string $range = '7days' string $range = '7days'
): Response ): Response {
{
if (!in_array($range, ['7days', '30days', '90days', '1year'])) { if (!in_array($range, ['7days', '30days', '90days', '1year'])) {
throw $this->createNotFoundException(); throw $this->createNotFoundException();
} }

View file

@ -7,19 +7,19 @@ use App\Core\Controller\Site\PageController;
use App\Core\Manager\EntityManager; use App\Core\Manager\EntityManager;
use App\Core\Site\SiteRequest; use App\Core\Site\SiteRequest;
use App\Core\Site\SiteStore; use App\Core\Site\SiteStore;
use App\Core\Twig\Extension\BuilderExtension;
use App\Core\Twig\Extension\EditorJsExtension;
use App\Entity\Blog\Category; use App\Entity\Blog\Category;
use App\Entity\Blog\Post; use App\Entity\Blog\Post;
use App\Factory\Blog\CommentFactory; use App\Factory\Blog\CommentFactory;
use App\Form\Blog\UserCommentType; use App\Form\Blog\UserCommentType;
use App\Manager\PostFollowManager;
use App\Markdown\Parser\Post as PostParser; use App\Markdown\Parser\Post as PostParser;
use App\Repository\Blog\PostRepositoryQuery; use App\Repository\Blog\PostRepositoryQuery;
use App\UrlGenerator\PostGenerator; use App\UrlGenerator\PostGenerator;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use App\Factory\Blog\PostFollowFactory;
use App\Manager\PostFollowManager;
use App\Core\Twig\Extension\EditorJsExtension;
class PostController extends PageController class PostController extends PageController
{ {
@ -92,9 +92,9 @@ class PostController extends PageController
]); ]);
} }
public function posts(int $page = 1): Response public function posts(Request $request, int $page = 1): Response
{ {
$entities = $this->createQuery() $entities = $this->createQuery($request->query->has('preview') && $this->getUser())
->paginate($page, 9) ->paginate($page, 9)
; ;
@ -149,26 +149,34 @@ class PostController extends PageController
]); ]);
} }
public function tag(string $tag, int $page = 1): Response public function tag(string $tag, int $page = 1): Response {}
{
}
public function createQuery(): PostRepositoryQuery public function createQuery(bool $isPreview = false): PostRepositoryQuery
{ {
return $this->postQuery->create() $query = $this->postQuery->create()
->orderBy('.publishedAt', 'DESC') ->orderBy('.publishedAt', 'DESC')
->published()
; ;
if (!$isPreview) {
$query->published();
} }
public function rss(PostParser $parser, EditorJsExtension $editorJsExtension): Response return $query;
{ }
public function rss(
PostParser $parser,
EditorJsExtension $editorJsExtension,
BuilderExtension $builderExtension
): Response {
$entities = $this->createQuery()->paginate(1, 20); $entities = $this->createQuery()->paginate(1, 20);
$items = []; $items = [];
foreach ($entities as $entity) { foreach ($entities as $entity) {
if ($entity->getContentFormat() === 'editorjs') { if ('editorjs' === $entity->getContentFormat()) {
$description = $editorJsExtension->buildHtml($entity->getContent()); $description = $editorJsExtension->buildHtml($entity->getContent());
} elseif ('builder' === $entity->getContentFormat()) {
$description = $builderExtension->buildHtml($entity->getContent());
} else { } else {
$description = $parser->transformMarkdown($entity->getContent()); $description = $parser->transformMarkdown($entity->getContent());
} }
@ -189,7 +197,7 @@ class PostController extends PageController
'post' => $entity->getId(), 'post' => $entity->getId(),
'slug' => $entity->getSlug(), 'slug' => $entity->getSlug(),
], UrlGeneratorInterface::ABSOLUTE_URL), ], UrlGeneratorInterface::ABSOLUTE_URL),
'linkGemini' => sprintf('gemini://deblan.io/posts/%d.gmi', $entity->getId()), 'linkGemini' => sprintf('gemini://deblan.fr/posts/%d.gmi', $entity->getId()),
]; ];
} }

View file

@ -4,10 +4,10 @@ namespace App\Controller;
use App\Core\Controller\Site\PageController; use App\Core\Controller\Site\PageController;
use App\Core\Notification\MailNotifier; use App\Core\Notification\MailNotifier;
use App\Core\Setting\SettingManager;
use App\Form\ContactType; use App\Form\ContactType;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use App\Core\Setting\SettingManager;
class ContactController extends PageController class ContactController extends PageController
{ {

View file

@ -3,6 +3,8 @@
namespace App\Controller; namespace App\Controller;
use App\Core\Controller\Dashboard\DashboardAdminController as Controller; use App\Core\Controller\Dashboard\DashboardAdminController as Controller;
use App\Repository\Blog\PostRepositoryQuery;
use App\Repository\ProjectRepositoryQuery;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
@ -10,8 +12,23 @@ use Symfony\Component\Routing\Annotation\Route;
class DashboardAdminController extends Controller class DashboardAdminController extends Controller
{ {
#[Route(path: '/', name: 'admin_dashboard_index')] #[Route(path: '/', name: 'admin_dashboard_index')]
public function index(): Response public function index(
{ PostRepositoryQuery $postQuery,
return $this->render('admin/dashboard.html.twig'); ProjectRepositoryQuery $projectQuery
): Response {
$posts = $postQuery->create()
->orderBy('.id', 'DESC')
->paginate(1, 4)
;
$projects = $projectQuery->create()
->orderBy('.id', 'DESC')
->paginate(1, 3)
;
return $this->render('admin/dashboard.html.twig', [
'posts' => $posts,
'projects' => $projects,
]);
} }
} }

View file

@ -7,6 +7,7 @@ use App\Core\Crud\CrudConfiguration;
use App\Core\Crud\Field; use App\Core\Crud\Field;
use App\Core\Entity\EntityInterface; use App\Core\Entity\EntityInterface;
use App\Core\Manager\EntityManager; use App\Core\Manager\EntityManager;
use App\Entity\Project;
use App\Entity\Project as Entity; use App\Entity\Project as Entity;
use App\Factory\ProjectFactory as Factory; use App\Factory\ProjectFactory as Factory;
use App\Form\ProjectType as Type; use App\Form\ProjectType as Type;
@ -15,7 +16,6 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use App\Entity\Project;
class ProjectAdminController extends CrudController class ProjectAdminController extends CrudController
{ {
@ -114,13 +114,13 @@ class ProjectAdminController extends CrudController
'attr' => ['class' => 'miw-100'], 'attr' => ['class' => 'miw-100'],
]) ])
->setBatchAction('index', 'delete', 'Delete', function(EntityInterface $entity, EntityManager $manager) { ->setBatchAction('index', 'delete', 'Delete', function (EntityInterface $entity, EntityManager $manager) {
$manager->delete($entity); $manager->delete($entity);
}) })
->setBatchAction('index', 'draft', 'Statut : publier', function(EntityInterface $entity, EntityManager $manager) { ->setBatchAction('index', 'draft', 'Statut : publier', function (EntityInterface $entity, EntityManager $manager) {
$manager->update($entity->setStatus(Project::PUBLISHED)); $manager->update($entity->setStatus(Project::PUBLISHED));
}) })
->setBatchAction('index', 'publish', 'Statut : brouillon', function(EntityInterface $entity, EntityManager $manager) { ->setBatchAction('index', 'publish', 'Statut : brouillon', function (EntityInterface $entity, EntityManager $manager) {
$manager->update($entity->setStatus(Project::DRAFT)); $manager->update($entity->setStatus(Project::DRAFT));
}) })
; ;

View file

@ -110,7 +110,7 @@ class StlMeshAdminController extends CrudController
'attr' => ['class' => 'col-md-12'], 'attr' => ['class' => 'col-md-12'],
]) ])
->setBatchAction('index', 'delete', 'Delete', function(EntityInterface $entity, EntityManager $manager) { ->setBatchAction('index', 'delete', 'Delete', function (EntityInterface $entity, EntityManager $manager) {
$manager->delete($entity); $manager->delete($entity);
}) })
; ;

View file

@ -2,13 +2,11 @@
namespace App\Controller; namespace App\Controller;
use App\Api\TTRssClient;
use App\Core\Controller\Site\PageController; use App\Core\Controller\Site\PageController;
use App\Markdown\Parser\Post as PostParser;
use Symfony\Component\HttpFoundation\Response;
use App\Repository\StlMeshRepositoryQuery;
use App\Entity\StlMesh; use App\Entity\StlMesh;
use App\Repository\StlMeshRepositoryQuery;
use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
@ -18,7 +16,8 @@ class StlMeshController extends PageController
{ {
$pager = $query->create() $pager = $query->create()
->orderBy('.sortOrder') ->orderBy('.sortOrder')
->paginate(1, 200); ->paginate(1, 200)
;
return $this->defaultRender($this->siteRequest->getPage()->getTemplate(), [ return $this->defaultRender($this->siteRequest->getPage()->getTemplate(), [
'pager' => $pager, 'pager' => $pager,

View file

@ -2,9 +2,7 @@
namespace App\Controller; namespace App\Controller;
use App\Api\TTRssClient;
use App\Core\Controller\Site\PageController; use App\Core\Controller\Site\PageController;
use App\Markdown\Parser\Post as PostParser;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
class TextController extends PageController class TextController extends PageController

View file

@ -0,0 +1,79 @@
<?php
namespace App\Controller;
use App\Core\Controller\User\UserAdminController as BaseUserAdminController;
use App\Core\Crud\CrudConfiguration;
use App\Core\Factory\UserFactory as Factory;
use App\Core\Manager\EntityManager;
use App\Core\Security\TokenGenerator;
use App\Entity\User as Entity;
use App\Repository\UserRepositoryQuery as RepositoryQuery;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Annotation\Route;
class UserAdminController extends BaseUserAdminController
{
#[Route(path: '/admin/user/{page}', name: 'admin_user_index', methods: ['GET'], requirements: ['page' => '\d+'])]
public function index(RepositoryQuery $query, Request $request, Session $session, int $page = 1): Response
{
return parent::index($query, $request, $session, $page);
}
#[Route(path: '/admin/user/new', name: 'admin_user_new', methods: ['GET', 'POST'])]
public function new(Factory $factory, EntityManager $entityManager, Request $request, TokenGenerator $tokenGenerator): Response
{
return parent::new($factory, $entityManager, $request, $tokenGenerator);
}
#[Route(path: '/admin/user/show/{entity}', name: 'admin_user_show', methods: ['GET'])]
public function show(Entity $entity): Response
{
return parent::show($entity);
}
#[Route(path: '/admin/user/filter', name: 'admin_user_filter', methods: ['GET'])]
public function filter(Session $session): Response
{
return parent::filter($session);
}
#[Route(path: '/admin/user/edit/{entity}', name: 'admin_user_edit', methods: ['GET', 'POST'])]
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return parent::edit($entity, $entityManager, $request);
}
#[Route(path: '/admin/user/inline_edit/{entity}/{context}/{label}', name: 'admin_user_inline_edit', methods: ['GET', 'POST'])]
public function inlineEdit(string $context, string $label, Entity $entity, EntityManager $entityManager, Request $request): Response
{
return parent::inlineEdit($context, $label, $entity, $entityManager, $request);
}
#[Route(path: '/admin/user/delete/{entity}', name: 'admin_user_delete', methods: ['DELETE', 'POST'])]
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return parent::delete($entity, $entityManager, $request);
}
#[Route(path: '/admin/user/resetting_request/{entity}', name: 'admin_user_resetting_request', methods: ['POST'])]
public function requestResetting(Entity $entity, EventDispatcherInterface $eventDispatcher, Request $request): Response
{
return parent::requestResetting($entity, $eventDispatcher, $request);
}
protected function getConfiguration(): CrudConfiguration
{
if ($this->configuration) {
return $this->configuration;
}
return parent::getConfiguration()
->setView('form', 'admin/user_admin/_form.html.twig')
->setView('show_entity', 'admin/user_admin/_show.html.twig')
;
}
}

View file

@ -7,9 +7,6 @@ use Symfony\Component\DependencyInjection\Extension\Extension;
class AppExtension extends Extension class AppExtension extends Extension
{ {
/**
* {@inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container) public function load(array $configs, ContainerBuilder $container)
{ {
$configuration = $this->getConfiguration($configs, $container); $configuration = $this->getConfiguration($configs, $container);
@ -18,9 +15,6 @@ class AppExtension extends Extension
$container->setParameter('app', $config); $container->setParameter('app', $config);
} }
/**
* {@inheritdoc}
*/
public function getConfiguration(array $configs, ContainerBuilder $container) public function getConfiguration(array $configs, ContainerBuilder $container)
{ {
return new Configuration(); return new Configuration();

View file

@ -55,6 +55,16 @@ class Comment implements EntityInterface
$this->postFollows = new ArrayCollection(); $this->postFollows = new ArrayCollection();
} }
public function __toString()
{
return sprintf(
'[%s] (%s) %s',
$this->getAuthor(),
$this->getCreatedAt()->format('d/m/Y'),
substr($this->getContent(), 0, 20).'…'
);
}
public function getId(): ?int public function getId(): ?int
{ {
return $this->id; return $this->id;
@ -189,25 +199,12 @@ class Comment implements EntityInterface
*/ */
public function getAvatar(): string public function getAvatar(): string
{ {
$mail = $this->getEmail() ?? sprintf('%d@deblan.io', $this->getId()); $mail = $this->getEmail() ?? sprintf('%d@deblan.fr', $this->getId());
$hash = md5($mail); $hash = md5($mail);
return 'https://cdn.libravatar.org/avatar/'.$hash.'?s=90&d=retro'; return 'https://cdn.libravatar.org/avatar/'.$hash.'?s=90&d=retro';
} }
/**
* {@inheritdoc}
*/
public function __toString()
{
return sprintf(
'[%s] (%s) %s',
$this->getAuthor(),
$this->getCreatedAt()->format('d/m/Y'),
substr($this->getContent(), 0, 20).'…'
);
}
/** /**
* @return Collection|PostFollow[] * @return Collection|PostFollow[]
*/ */

View file

@ -19,8 +19,8 @@ class Post implements EntityInterface
{ {
use Timestampable; use Timestampable;
const DRAFT = 0; public const DRAFT = 0;
const PUBLISHED = 1; public const PUBLISHED = 1;
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue] #[ORM\GeneratedValue]
@ -87,11 +87,21 @@ class Post implements EntityInterface
#[ORM\Column(type: 'string', length: 255, nullable: true)] #[ORM\Column(type: 'string', length: 255, nullable: true)]
private $image2; private $image2;
#[ORM\Column(type: 'array')]
private $parameters = [];
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'deprecatedPosts')]
private $recommandedPost;
#[ORM\OneToMany(mappedBy: 'recommandedPost', targetEntity: self::class)]
private $deprecatedPosts;
public function __construct() public function __construct()
{ {
$this->categories = new ArrayCollection(); $this->categories = new ArrayCollection();
$this->comments = new ArrayCollection(); $this->comments = new ArrayCollection();
$this->postFollows = new ArrayCollection(); $this->postFollows = new ArrayCollection();
$this->deprecatedPosts = new ArrayCollection();
} }
public function getId(): ?int public function getId(): ?int
@ -192,7 +202,7 @@ class Post implements EntityInterface
} }
/** /**
* @return Collection|Category[] * @return Category[]|Collection
*/ */
public function getCategories(): Collection public function getCategories(): Collection
{ {
@ -438,4 +448,80 @@ class Post implements EntityInterface
return $this; return $this;
} }
public function getParameters(): ?array
{
$params = is_array($this->parameters) ? $this->parameters : [];
$names = array_map(fn (array $param): string => $param['name'], $params);
$defaultParams = [
['name' => 'podcast', 'value' => 0],
];
foreach ($defaultParams as $defaultParam) {
if (!in_array($defaultParam['name'], $names)) {
$params[] = $defaultParam;
}
}
return $params;
}
public function setParameters(array $parameters): self
{
$this->parameters = $parameters;
return $this;
}
public function getParameter($name): ?string
{
return array_filter(
$this->getParameters(),
function (array $param) use ($name): bool {
return $name === $param['name'];
}
)[0]['value'] ?? null;
}
public function getRecommandedPost(): ?self
{
return $this->recommandedPost;
}
public function setRecommandedPost(?self $recommandedPost): self
{
$this->recommandedPost = $recommandedPost;
return $this;
}
/**
* @return Collection<int, self>
*/
public function getDeprecatedPosts(): Collection
{
return $this->deprecatedPosts;
}
public function addDeprecatedPost(self $deprecatedPost): self
{
if (!$this->deprecatedPosts->contains($deprecatedPost)) {
$this->deprecatedPosts[] = $deprecatedPost;
$deprecatedPost->setRecommandedPost($this);
}
return $this;
}
public function removeDeprecatedPost(self $deprecatedPost): self
{
if ($this->deprecatedPosts->removeElement($deprecatedPost)) {
// set the owning side to null (unless already changed)
if ($deprecatedPost->getRecommandedPost() === $this) {
$deprecatedPost->setRecommandedPost(null);
}
}
return $this;
}
} }

View file

@ -5,6 +5,4 @@ namespace App\Entity\Page;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity] #[ORM\Entity]
class CategoriesPage extends TitledPage class CategoriesPage extends TitledPage {}
{
}

View file

@ -5,6 +5,4 @@ namespace App\Entity\Page;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity] #[ORM\Entity]
class CategoryPage extends TitledPage class CategoryPage extends TitledPage {}
{
}

View file

@ -5,6 +5,4 @@ namespace App\Entity\Page;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity] #[ORM\Entity]
class ContactPage extends SimplePage class ContactPage extends SimplePage {}
{
}

View file

@ -5,6 +5,4 @@ namespace App\Entity\Page;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity] #[ORM\Entity]
class LinksPage extends SimplePage class LinksPage extends SimplePage {}
{
}

View file

@ -5,6 +5,4 @@ namespace App\Entity\Page;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity] #[ORM\Entity]
class MeshPage extends SimplePage class MeshPage extends SimplePage {}
{
}

View file

@ -5,6 +5,4 @@ namespace App\Entity\Page;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity] #[ORM\Entity]
class PostPage extends TitledPage class PostPage extends TitledPage {}
{
}

View file

@ -5,6 +5,4 @@ namespace App\Entity\Page;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity] #[ORM\Entity]
class PostsPage extends TitledPage class PostsPage extends TitledPage {}
{
}

View file

@ -5,6 +5,4 @@ namespace App\Entity\Page;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity] #[ORM\Entity]
class SearchPage extends TitledPage class SearchPage extends TitledPage {}
{
}

View file

@ -4,7 +4,7 @@ namespace App\Entity\Page;
use App\Core\Entity\Site\Page\Block; use App\Core\Entity\Site\Page\Block;
use App\Core\Entity\Site\Page\FileBlock; use App\Core\Entity\Site\Page\FileBlock;
use App\Form\Type\SimpleMdTextareaBlockType; use App\Form\MarkdownBlockType;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@ -17,7 +17,7 @@ class SimplePage extends TitledPage
$builder->add( $builder->add(
'content', 'content',
SimpleMdTextareaBlockType::class, MarkdownBlockType::class,
[ [
'label' => 'Contenu', 'label' => 'Contenu',
'options' => [ 'options' => [

View file

@ -3,11 +3,10 @@
namespace App\Entity\Page; namespace App\Entity\Page;
use App\Core\Entity\Site\Page\Block; use App\Core\Entity\Site\Page\Block;
use App\Core\Entity\Site\Page\FileBlock; use App\Core\Entity\Site\Page\Page;
use App\Core\Form\Site\Page\TextareaBlockType; use App\Core\Form\Site\Page\TextareaBlockType;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use App\Core\Entity\Site\Page\Page;
#[ORM\Entity] #[ORM\Entity]
class TextPage extends Page class TextPage extends Page
@ -28,6 +27,14 @@ class TextPage extends Page
], ],
] ]
); );
$builder
->remove('metaTitle')
->remove('metaDescription')
->remove('ogTitle')
->remove('ogDescription')
->remove('ogImage')
;
} }
public function setContent(Block $block) public function setContent(Block $block)

View file

@ -13,8 +13,8 @@ class Project implements EntityInterface
{ {
use Timestampable; use Timestampable;
const DRAFT = 0; public const DRAFT = 0;
const PUBLISHED = 1; public const PUBLISHED = 1;
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue] #[ORM\GeneratedValue]

View file

@ -2,9 +2,9 @@
namespace App\Entity; namespace App\Entity;
use App\Core\Entity\EntityInterface;
use App\Repository\StlMeshRepository; use App\Repository\StlMeshRepository;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use App\Core\Entity\EntityInterface;
#[ORM\Entity(repositoryClass: StlMeshRepository::class)] #[ORM\Entity(repositoryClass: StlMeshRepository::class)]
class StlMesh implements EntityInterface class StlMesh implements EntityInterface
@ -86,7 +86,7 @@ class StlMesh implements EntityInterface
{ {
$this->files = (array) $this->files; $this->files = (array) $this->files;
usort($this->files, function($a, $b) { usort($this->files, function ($a, $b) {
if ($a['position'] > $b['position']) { if ($a['position'] > $b['position']) {
return 1; return 1;
} }

View file

@ -50,9 +50,7 @@ class User implements PasswordAuthenticatedUserInterface, UserInterface, TwoFact
#[ORM\Column(type: 'boolean', options: ['default' => 0])] #[ORM\Column(type: 'boolean', options: ['default' => 0])]
private $isWriter; private $isWriter;
public function __construct() public function __construct() {}
{
}
public function __toString() public function __toString()
{ {

View file

@ -0,0 +1,22 @@
<?php
namespace App\EventListener;
use App\Message\PageViewMessage;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Messenger\MessageBusInterface;
/**
* class StatListener.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class StatListener
{
public function __construct(protected MessageBusInterface $bus) {}
public function onKernelRequest(RequestEvent $event)
{
$this->bus->dispatch(new PageViewMessage(time()));
}
}

View file

@ -4,11 +4,12 @@ namespace App\EventSubscriber;
use App\Core\Event\Setting\SettingEvent; use App\Core\Event\Setting\SettingEvent;
use App\Core\EventSubscriber\SettingEventSubscriber as EventSubscriber; use App\Core\EventSubscriber\SettingEventSubscriber as EventSubscriber;
use App\Core\Form\FileManager\FilePickerType;
use App\Core\Form\Type\TinymceTextareaType;
use App\Core\Setting\SettingManager; use App\Core\Setting\SettingManager;
use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use App\Core\Form\FileManager\FilePickerType;
/** /**
* class SettingEventSubscriber. * class SettingEventSubscriber.
@ -36,6 +37,7 @@ class SettingEventSubscriber extends EventSubscriber
$this->manager->init('stats_umami_url', '📊 Statistiques', 'Adresse tableau de bord Umami', ''); $this->manager->init('stats_umami_url', '📊 Statistiques', 'Adresse tableau de bord Umami', '');
$this->manager->init('stats_umami_tag', '📊 Statistiques', 'Script Umami', ''); $this->manager->init('stats_umami_tag', '📊 Statistiques', 'Script Umami', '');
$this->manager->init('stats_grafana_url', '📊 Statistiques', 'Adresse tableau de bord Grafana', '');
$this->manager->init('post_author_description', '🖊️ Article', 'Description auteur', ''); $this->manager->init('post_author_description', '🖊️ Article', 'Description auteur', '');
@ -68,7 +70,7 @@ class SettingEventSubscriber extends EventSubscriber
); );
} }
if (in_array($entity->getCode(), ['giphy_api_key', 'stats_umami_url'])) { if (in_array($entity->getCode(), ['giphy_api_key', 'stats_umami_url', 'stats_grafana_url'])) {
$builder->add( $builder->add(
'value', 'value',
TextType::class, TextType::class,
@ -93,7 +95,7 @@ class SettingEventSubscriber extends EventSubscriber
); );
} }
if (in_array($entity->getCode(), ['blog_footer', 'post_author_description'])) { if (in_array($entity->getCode(), ['post_author_description'])) {
$event->setOption('view', 'large'); $event->setOption('view', 'large');
$builder->add( $builder->add(
@ -107,5 +109,20 @@ class SettingEventSubscriber extends EventSubscriber
] ]
); );
} }
if (in_array($entity->getCode(), ['blog_footer'])) {
$event->setOption('view', 'large');
$builder->add(
'value',
TinymceTextareaType::class,
[
'label' => $entity->getLabel(),
'attr' => [
'rows' => 20,
],
]
);
}
} }
} }

View file

@ -9,6 +9,4 @@ use App\Core\Factory\UserFactory as BaseUserFactory;
* *
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class UserFactory extends BaseUserFactory class UserFactory extends BaseUserFactory {}
{
}

View file

@ -3,6 +3,8 @@
namespace App\Form\Blog; namespace App\Form\Blog;
use App\Entity\Blog\Category; use App\Entity\Blog\Category;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\Extension\Core\Type\TextareaType;
@ -10,8 +12,6 @@ use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotBlank;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
class CategoryType extends AbstractType class CategoryType extends AbstractType
{ {

View file

@ -0,0 +1,47 @@
<?php
namespace App\Form\Blog;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
class PostParameterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'name',
TextType::class,
[
'required' => true,
'attr' => [
],
'constraints' => [
new NotBlank(),
],
]
);
$builder->add(
'value',
TextType::class,
[
'required' => false,
'attr' => [
],
'constraints' => [
],
]
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => null,
]);
}
}

View file

@ -2,8 +2,13 @@
namespace App\Form\Blog; namespace App\Form\Blog;
use App\Core\Form\FileManager\FilePickerType;
use App\Core\Form\Type\BuilderType;
use App\Core\Form\Type\CollectionType as MurphCollectionType;
use App\Core\Form\Type\EditorJsTextareaType;
use App\Entity\Blog\Category; use App\Entity\Blog\Category;
use App\Entity\Blog\Post; use App\Entity\Blog\Post;
use App\Form\MarkdownType;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
@ -21,9 +26,6 @@ use Symfony\Component\Validator\Constraints\Image;
use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Range; use Symfony\Component\Validator\Constraints\Range;
use Symfony\Component\Validator\Constraints\Url; use Symfony\Component\Validator\Constraints\Url;
use App\Form\Type\SimpleMdTextareaType;
use App\Core\Form\Type\EditorJsTextareaType;
use App\Core\Form\FileManager\FilePickerType;
class PostType extends AbstractType class PostType extends AbstractType
{ {
@ -50,6 +52,7 @@ class PostType extends AbstractType
'required' => true, 'required' => true,
'choices' => [ 'choices' => [
'Markdown' => 'markdown', 'Markdown' => 'markdown',
'Builder' => 'builder',
'HTML' => 'html', 'HTML' => 'html',
'Editor JS' => 'editorjs', 'Editor JS' => 'editorjs',
], ],
@ -60,8 +63,9 @@ class PostType extends AbstractType
); );
$types = [ $types = [
'markdown' => SimpleMdTextareaType::class, 'markdown' => MarkdownType::class,
'html' => SimpleMdTextareaType::class, 'builder' => BuilderType::class,
'html' => MarkdownType::class,
'editorjs' => EditorJsTextareaType::class, 'editorjs' => EditorJsTextareaType::class,
]; ];
@ -112,6 +116,28 @@ class PostType extends AbstractType
] ]
); );
$builder->add(
'recommandedPost',
EntityType::class,
[
'label' => 'Article recommandé',
'class' => Post::class,
'choice_label' => 'title',
'required' => false,
'multiple' => false,
'attr' => [
'data-jschoice' => '',
],
'query_builder' => function (EntityRepository $repo) {
return $repo->createQueryBuilder('a')
->orderBy('a.title', 'ASC')
;
},
'constraints' => [
],
]
);
$builder->add( $builder->add(
'status', 'status',
ChoiceType::class, ChoiceType::class,
@ -171,6 +197,7 @@ class PostType extends AbstractType
'attr' => [ 'attr' => [
], ],
'constraints' => [ 'constraints' => [
new Image(),
], ],
] ]
); );
@ -298,6 +325,20 @@ class PostType extends AbstractType
], ],
] ]
); );
// $builder->add(
// 'parameters',
// MurphCollectionType::class,
// [
// 'label' => 'Paramètres',
// 'entry_type' => PostParameterType::class,
// 'by_reference' => false,
// 'allow_add' => true,
// 'allow_delete' => true,
// 'prototype' => true,
// 'row_attr' => ['class' => 'mb-3'],
// ]
// );
} }
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)

View file

@ -3,7 +3,9 @@
namespace App\Form\Blog; namespace App\Form\Blog;
use App\Entity\Blog\Comment; use App\Entity\Blog\Comment;
use Gregwar\CaptchaBundle\Type\CaptchaType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\Extension\Core\Type\TextareaType;
@ -14,8 +16,6 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Url; use Symfony\Component\Validator\Constraints\Url;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Gregwar\CaptchaBundle\Type\CaptchaType;
class UserCommentType extends AbstractType class UserCommentType extends AbstractType
{ {

View file

@ -2,6 +2,7 @@
namespace App\Form; namespace App\Form;
use Gregwar\CaptchaBundle\Type\CaptchaType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\Extension\Core\Type\TextareaType;
@ -10,7 +11,6 @@ use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotBlank;
use Gregwar\CaptchaBundle\Type\CaptchaType;
class ContactType extends AbstractType class ContactType extends AbstractType
{ {

View file

@ -0,0 +1,21 @@
<?php
namespace App\Form;
use App\Core\Form\Site\Page\TextareaBlockType;
use Symfony\Component\Form\FormBuilderInterface;
class MarkdownBlockType extends TextareaBlockType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'value',
MarkdownType::class,
array_merge([
'required' => false,
'label' => false,
], $options['options']),
);
}
}

13
src/Form/MarkdownType.php Normal file
View file

@ -0,0 +1,13 @@
<?php
namespace App\Form;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
class MarkdownType extends TextareaType
{
public function getBlockPrefix()
{
return 'markdown';
}
}

View file

@ -3,10 +3,10 @@
namespace App\Form; namespace App\Form;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Form\Extension\Core\Type\TextType;
class ProjectLinkType extends AbstractType class ProjectLinkType extends AbstractType
{ {

View file

@ -65,11 +65,12 @@ class ProjectType extends AbstractType
FilePickerType::class, FilePickerType::class,
[ [
'label' => 'Image', 'label' => 'Image',
'required' => false, 'required' => true,
'data_class' => null, 'data_class' => null,
'attr' => [ 'attr' => [
], ],
'constraints' => [ 'constraints' => [
new NotBlank(),
], ],
] ]
); );

View file

@ -2,13 +2,13 @@
namespace App\Form; namespace App\Form;
use App\Core\Form\FileManager\FilePickerType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use App\Core\Form\FileManager\FilePickerType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Form\Extension\Core\Type\TextType;
class StlFileType extends AbstractType class StlFileType extends AbstractType
{ {

View file

@ -8,9 +8,6 @@ use Symfony\Component\Form\FormView;
class SimpleMdTextareaType extends TextareaType class SimpleMdTextareaType extends TextareaType
{ {
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options) public function buildView(FormView $view, FormInterface $form, array $options)
{ {
if (!isset($view->vars['attr']['data-simplemde'])) { if (!isset($view->vars['attr']['data-simplemde'])) {

View file

@ -4,6 +4,4 @@ namespace App\Form;
use App\Core\Form\UserType as BaseUserType; use App\Core\Form\UserType as BaseUserType;
class UserType extends BaseUserType class UserType extends BaseUserType {}
{
}

View file

@ -2,7 +2,9 @@
namespace App; namespace App;
use App\Core\DependencyInjection\Compiler\BuilderBlockPass;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Kernel as BaseKernel; use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
@ -35,4 +37,9 @@ class Kernel extends BaseKernel
(require $path)($routes->withPath($path), $this); (require $path)($routes->withPath($path), $this);
} }
} }
protected function build(ContainerBuilder $container): void
{
$container->addCompilerPass(new BuilderBlockPass());
}
} }

View file

@ -11,15 +11,9 @@ use Knp\Bundle\MarkdownBundle\Parser\MarkdownParser;
*/ */
class Post extends MarkdownParser class Post extends MarkdownParser
{ {
/** // protected $id_class_attr_catch_re = '\{((?'.'>[ ]*[#.a-z][-_:a-zA-Z0-9="\'\/\*-]+){1,})[ ]*\}';
* {@inheritdoc}
*/
//protected $id_class_attr_catch_re = '\{((?'.'>[ ]*[#.a-z][-_:a-zA-Z0-9="\'\/\*-]+){1,})[ ]*\}';
protected $id_class_attr_catch_re = '\{([^\}]+)\}'; protected $id_class_attr_catch_re = '\{([^\}]+)\}';
/**
* {@inheritdoc}
*/
public function transformMarkdown($text) public function transformMarkdown($text)
{ {
$html = parent::transformMarkdown($text); $html = parent::transformMarkdown($text);
@ -27,9 +21,6 @@ class Post extends MarkdownParser
return str_replace(' role="doc-endnote"', '', $html); return str_replace(' role="doc-endnote"', '', $html);
} }
/**
* {@inheritdoc}
*/
protected function doExtraAttributes($tag_name, $attr, $defaultIdValue = null, $classes = []) protected function doExtraAttributes($tag_name, $attr, $defaultIdValue = null, $classes = [])
{ {
if (empty($attr) && !$defaultIdValue && empty($classes)) { if (empty($attr) && !$defaultIdValue && empty($classes)) {
@ -102,8 +93,11 @@ class Post extends MarkdownParser
$codeblock $codeblock
); );
$codeblock = preg_replace_callback('/^\n+/', $codeblock = preg_replace_callback(
[$this, '_doFencedCodeBlocks_newlines'], $codeblock); '/^\n+/',
[$this, '_doFencedCodeBlocks_newlines'],
$codeblock
);
$classes = []; $classes = [];
if ('' != $classname) { if ('' != $classname) {
@ -115,7 +109,12 @@ class Post extends MarkdownParser
$attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? 'pre' : 'code', $attrs, null, $classes); $attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? 'pre' : 'code', $attrs, null, $classes);
$pre_attr_str = $this->code_attr_on_pre ? $attr_str : ''; $pre_attr_str = $this->code_attr_on_pre ? $attr_str : '';
$code_attr_str = $this->code_attr_on_pre ? '' : $attr_str; $code_attr_str = $this->code_attr_on_pre ? '' : $attr_str;
if (preg_match('/mermaid/', $code_attr_str)) {
$codeblock = "<pre{$code_attr_str}>{$codeblock}</pre>";
} else {
$codeblock = "<pre{$pre_attr_str}><code{$code_attr_str}>{$codeblock}</code></pre>"; $codeblock = "<pre{$pre_attr_str}><code{$code_attr_str}>{$codeblock}</code></pre>";
}
return "\n\n".$this->hashBlock($codeblock)."\n\n"; return "\n\n".$this->hashBlock($codeblock)."\n\n";
} }

View file

@ -0,0 +1,13 @@
<?php
namespace App\Message;
final class PageViewMessage
{
public function __construct(public int $time) {}
public function getTime(): int
{
return $this->time;
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace App\MessageHandler;
use App\Api\InfluxDB;
use App\Message\PageViewMessage;
use InfluxDB2\Point;
use InfluxDB2\WriteType;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
final class PageViewMessageHandler implements MessageHandlerInterface
{
public function __construct(protected InfluxDB $influxDB) {}
public function __invoke(PageViewMessage $message)
{
if (!$this->influxDB->isAvailable()) {
return;
}
$client = $this->influxDB->getClient();
$writeApi = $client->createWriteApi(['writeType' => WriteType::SYNCHRONOUS]);
$pageView = new Point('page_view');
$pageView
->addTag('request', 'view')
->addField('value', 1)
->time($message->getTime())
;
$writeApi->write($pageView);
$writeApi->close();
$client->close();
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace App\Middleware;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;
final class PageViewMiddleware implements MiddlewareInterface
{
public function handle(Envelope $envelope, StackInterface $stack): Envelope
{
// ...
return $stack->next()->handle($envelope, $stack);
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace App\Repository;
use App\Entity\AppEntityBlogPost;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<AppEntityBlogPost>
*
* @method null|AppEntityBlogPost find($id, $lockMode = null, $lockVersion = null)
* @method null|AppEntityBlogPost findOneBy(array $criteria, array $orderBy = null)
* @method AppEntityBlogPost[] findAll()
* @method AppEntityBlogPost[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class AppEntityBlogPostRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, AppEntityBlogPost::class);
}
public function add(AppEntityBlogPost $entity, bool $flush = false): void
{
$this->getEntityManager()->persist($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
public function remove(AppEntityBlogPost $entity, bool $flush = false): void
{
$this->getEntityManager()->remove($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
// /**
// * @return AppEntityBlogPost[] Returns an array of AppEntityBlogPost objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('a')
// ->andWhere('a.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('a.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?AppEntityBlogPost
// {
// return $this->createQueryBuilder('a')
// ->andWhere('a.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View file

@ -7,8 +7,8 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
/** /**
* @method Comment|null find($id, $lockMode = null, $lockVersion = null) * @method null|Comment find($id, $lockMode = null, $lockVersion = null)
* @method Comment|null findOneBy(array $criteria, array $orderBy = null) * @method null|Comment findOneBy(array $criteria, array $orderBy = null)
* @method Comment[] findAll() * @method Comment[] findAll()
* @method Comment[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) * @method Comment[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/ */

View file

@ -3,8 +3,8 @@
namespace App\Repository\Blog; namespace App\Repository\Blog;
use App\Core\Repository\RepositoryQuery; use App\Core\Repository\RepositoryQuery;
use Knp\Component\Pager\PaginatorInterface;
use App\Repository\Blog\PostFollowRepository as Repository; use App\Repository\Blog\PostFollowRepository as Repository;
use Knp\Component\Pager\PaginatorInterface;
class PostFollowRepositoryQuery extends RepositoryQuery class PostFollowRepositoryQuery extends RepositoryQuery
{ {

View file

@ -54,15 +54,13 @@ class PostRepositoryQuery extends RepositoryQuery
; ;
} }
protected function filterHandler(string $name, $value)
{
if ('category' === $name) {
$this->inCategory($value);
}
}
public function search(?string $keywords, ?string $tag) public function search(?string $keywords, ?string $tag)
{ {
$keywords = explode(' ', $keywords);
$filterWords = fn ($keyword) => '' !== trim($keyword) && preg_match('/[a-zA-Z]+/', $keyword);
$keywords = array_filter($keywords, $filterWords);
if ($keywords) { if ($keywords) {
$conn = $this->repository->getEm()->getConnection(); $conn = $this->repository->getEm()->getConnection();
@ -70,42 +68,106 @@ class PostRepositoryQuery extends RepositoryQuery
'SELECT 'SELECT
post.id, post.id,
post.title, post.title,
MATCH(post.title) AGAINST(:search) AS MATCH_TITLE, post.content,
MATCH(post.content) AGAINST(:search) AS MATCH_CONTENT post.published_at
FROM post FROM post
WHERE WHERE
post.status = 1 AND post.status = 1 AND
post.published_at < :date post.published_at < :date
ORDER BY '
MATCH_TITLE DESC, );
MATCH_CONTENT DESC
');
$statement = $query->execute([ $statement = $query->execute([
':search' => $keywords,
':date' => (new \DateTime())->format('Y-m-d H:i:s'), ':date' => (new \DateTime())->format('Y-m-d H:i:s'),
]); ]);
$results = $statement->fetchAll(); $results = $statement->fetchAll();
$ids = []; $ids = [];
$matches = [];
foreach ($results as $k => $v) { foreach ($results as $k => $v) {
$rate = ($v['MATCH_TITLE'] * 2) + $v['MATCH_CONTENT']; $initWords = explode(' ', $v['title']);
$words = [];
if ($rate >= 7) { foreach ($initWords as $initWord) {
$ids[] = $v['id']; $words = array_merge($words, preg_split('/[:_\'-]+/', $initWord));
}
$words = array_filter($words, $filterWords);
foreach ($keywords as $keyword) {
if (str_contains(mb_strtolower($v['content']), mb_strtolower($keyword))) {
$similarity = 99;
if (isset($matches[$v['id']])) {
$matches[$v['id']]['similarity'] += $similarity;
++$matches[$v['id']]['count'];
} else {
$matches[$v['id']] = [
'id' => $v['id'],
'title' => $v['title'],
'published_at' => $v['published_at'],
'similarity' => $similarity,
'count' => 1,
];
} }
} }
if (0 == count($ids)) { foreach ($words as $word) {
foreach ($results as $k => $v) { if (str_contains(mb_strtolower($word), mb_strtolower($keyword))) {
$rate = ($v['MATCH_TITLE'] * 2) + $v['MATCH_CONTENT']; $similarity = 150;
if ($rate >= 6) { if (isset($matches[$v['id']])) {
$ids[] = $v['id']; $matches[$v['id']]['similarity'] += $similarity;
++$matches[$v['id']]['count'];
} else {
$matches[$v['id']] = [
'id' => $v['id'],
'title' => $v['title'],
'published_at' => $v['published_at'],
'similarity' => $similarity,
'count' => 1,
];
}
} else {
$lev = levenshtein($word, $keyword);
$similarity = 100 - ($lev * 100 / mb_strlen($word));
if ($similarity > 70) {
if (isset($matches[$v['id']])) {
$matches[$v['id']]['similarity'] += $similarity;
} else {
$matches[$v['id']] = [
'id' => $v['id'],
'title' => $v['title'],
'published_at' => $v['published_at'],
'similarity' => $similarity,
'count' => 1,
];
} }
} }
} }
}
}
}
$matches = array_filter($matches, function ($match) use ($keywords) {
return (100 * $match['count'] / count($keywords)) > 80;
});
usort($matches, function ($a, $b) {
if ($a['similarity'] > $b['similarity']) {
return -1;
}
if ($b['similarity'] > $a['similarity']) {
return 1;
}
return ($a['published_at'] != $b['published_at']) * -1;
});
$ids = array_column($matches, 'id');
if (!$ids) { if (!$ids) {
$ids = [-1]; $ids = [-1];
@ -127,4 +189,11 @@ class PostRepositoryQuery extends RepositoryQuery
return $this; return $this;
} }
protected function filterHandler(string $name, $value)
{
if ('category' === $name) {
$this->inCategory($value);
}
}
} }

View file

@ -9,8 +9,8 @@ use Doctrine\ORM\ORMException;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
/** /**
* @method Project|null find($id, $lockMode = null, $lockVersion = null) * @method null|Project find($id, $lockMode = null, $lockVersion = null)
* @method Project|null findOneBy(array $criteria, array $orderBy = null) * @method null|Project findOneBy(array $criteria, array $orderBy = null)
* @method Project[] findAll() * @method Project[] findAll()
* @method Project[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) * @method Project[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/ */

View file

@ -3,9 +3,9 @@
namespace App\Repository; namespace App\Repository;
use App\Core\Repository\RepositoryQuery; use App\Core\Repository\RepositoryQuery;
use Knp\Component\Pager\PaginatorInterface;
use App\Repository\ProjectRepository as Repository;
use App\Entity\Project; use App\Entity\Project;
use App\Repository\ProjectRepository as Repository;
use Knp\Component\Pager\PaginatorInterface;
class ProjectRepositoryQuery extends RepositoryQuery class ProjectRepositoryQuery extends RepositoryQuery
{ {

View file

@ -7,8 +7,8 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
/** /**
* @method StlMesh|null find($id, $lockMode = null, $lockVersion = null) * @method null|StlMesh find($id, $lockMode = null, $lockVersion = null)
* @method StlMesh|null findOneBy(array $criteria, array $orderBy = null) * @method null|StlMesh findOneBy(array $criteria, array $orderBy = null)
* @method StlMesh[] findAll() * @method StlMesh[] findAll()
* @method StlMesh[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) * @method StlMesh[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/ */

View file

@ -3,8 +3,8 @@
namespace App\Repository; namespace App\Repository;
use App\Core\Repository\RepositoryQuery; use App\Core\Repository\RepositoryQuery;
use Knp\Component\Pager\PaginatorInterface;
use App\Repository\StlMeshRepository as Repository; use App\Repository\StlMeshRepository as Repository;
use Knp\Component\Pager\PaginatorInterface;
class StlMeshRepositoryQuery extends RepositoryQuery class StlMeshRepositoryQuery extends RepositoryQuery
{ {

View file

@ -6,9 +6,8 @@ use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
{ {

View file

@ -40,8 +40,9 @@ class BlogExtension extends AbstractExtension
$text = str_replace('http://upload.deblan.fr', 'https://upload.deblan.org', $text); $text = str_replace('http://upload.deblan.fr', 'https://upload.deblan.org', $text);
$text = str_replace('http://dedi.geneweb.fr', 'http://kim.deblan.fr', $text); $text = str_replace('http://dedi.geneweb.fr', 'http://kim.deblan.fr', $text);
$text = str_replace('http://mediaplayer.deblan.fr', 'https://mediaplayer.deblan.org', $text); $text = str_replace('http://mediaplayer.deblan.fr', 'https://mediaplayer.deblan.org', $text);
$text = str_replace('http://blog.deblan.fr', 'https://www.deblan.io', $text); $text = str_replace('http://blog.deblan.fr', 'https://www.deblan.fr', $text);
$text = str_replace('http://www.deblan.tv', 'https://www.deblan.io', $text); $text = str_replace('http://www.deblan.tv', 'https://www.deblan.fr', $text);
$text = str_replace('http://www.deblan.io', 'https://www.deblan.fr', $text);
$text = preg_replace_callback( $text = preg_replace_callback(
'`<p([^>]*)>(.*)</p([^>]*)>`isU', '`<p([^>]*)>(.*)</p([^>]*)>`isU',
@ -134,10 +135,12 @@ class BlogExtension extends AbstractExtension
'#<code langage=\'([^\']*)\'>(.*)</code>#isU', '#<code langage=\'([^\']*)\'>(.*)</code>#isU',
], ],
function ($data) { function ($data) {
$lang = strtolower(str_replace( $lang = strtolower(
str_replace(
['console', 'texte', 'apache'], ['console', 'texte', 'apache'],
['bash', 'text', 'html'], ['bash', 'text', 'html'],
$data[1]) $data[1]
)
); );
$class = 'language-'.$lang; $class = 'language-'.$lang;

View file

@ -30,9 +30,9 @@ class ColorExtension extends AbstractExtension
return sprintf( return sprintf(
'#%s%s%s', '#%s%s%s',
strlen($red) != 2 ? '0'.$red : $red, 2 != strlen($red) ? '0'.$red : $red,
strlen($green) != 2 ? '0'.$green : $green, 2 != strlen($green) ? '0'.$green : $green,
strlen($blue) != 2 ? '0'.$blue : $blue, 2 != strlen($blue) ? '0'.$blue : $blue,
); );
} }
} }

View file

@ -21,7 +21,7 @@ class LazyLoadExtension extends AbstractExtension
public function lazyLoad($text) public function lazyLoad($text)
{ {
$text = preg_replace_callback( return preg_replace_callback(
'`<img src="([^"]+)" alt="([^"]+)" />`isU', '`<img src="([^"]+)" alt="([^"]+)" />`isU',
function ($data) { function ($data) {
$lazy = sprintf('<img class="lazy-img" src="#" data-src="%s" alt="%s" />', $data[1], $data[2]); $lazy = sprintf('<img class="lazy-img" src="#" data-src="%s" alt="%s" />', $data[1], $data[2]);
@ -31,7 +31,5 @@ class LazyLoadExtension extends AbstractExtension
}, },
$text $text
); );
return $text;
} }
} }

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