Compare commits
1 commit
develop
...
feature/do
Author | SHA1 | Date | |
---|---|---|---|
6048742af4 |
7
.env
7
.env
|
@ -35,10 +35,3 @@ MAILER_SENDER=example@localhost
|
|||
ASSET_BASE_URL=null
|
||||
UMAMI_URL=null
|
||||
|
||||
MESSENGER_TRANSPORT_DSN=doctrine://default
|
||||
|
||||
INFLUXDB_URL=
|
||||
INFLUXDB_TOKEN=
|
||||
INFLUXDB_BUCKET=
|
||||
INFLUXDB_ORG=
|
||||
INFLUXDB_DEBUG=1
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -35,3 +35,4 @@ yarn-error.log
|
|||
|
||||
/migrations/*.php
|
||||
/.build
|
||||
/appdata
|
|
@ -18,7 +18,6 @@ magephp:
|
|||
- "/var/cache/*"
|
||||
- "/var/log/*"
|
||||
- "/public/media"
|
||||
- "/.secrets"
|
||||
hosts:
|
||||
- ssh_host
|
||||
on-deploy:
|
||||
|
@ -26,4 +25,3 @@ magephp:
|
|||
- 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: 'warmup2' }
|
||||
- exec: { cmd: './bin/messenger -a restart', desc: 'messenger' }
|
||||
|
|
39
.novops.yml
39
.novops.yml
|
@ -1,39 +0,0 @@
|
|||
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
|
|
@ -1,55 +1,53 @@
|
|||
variables:
|
||||
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":
|
||||
pipeline:
|
||||
db-wait:
|
||||
image: gitnet.fr/deblan/timeout:latest
|
||||
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'
|
||||
|
||||
"Create database":
|
||||
db-create:
|
||||
image: mariadb:10.3
|
||||
secrets: [mysqldump]
|
||||
commands:
|
||||
- mysql -hdb -uroot -proot -e "CREATE DATABASE app"
|
||||
- eval "$MYSQLDUMP" | mysql -hdb -uroot -proot app
|
||||
when:
|
||||
branch: [master, master-*, develop, develop-*]
|
||||
|
||||
"Configure app":
|
||||
app-config:
|
||||
image: deblan/php:8.1
|
||||
commands:
|
||||
- echo APP_ENV=prod >> .env.local
|
||||
- echo APP_SECRET=$(openssl rand -hex 32) >> .env.local
|
||||
- echo DATABASE_URL=mysql://root:root@db/app >> .env.local
|
||||
when:
|
||||
branch: [master, master-*, develop, develop-*]
|
||||
|
||||
"Installs PHP dependencies":
|
||||
php-composer:
|
||||
image: deblan/php:8.1
|
||||
commands:
|
||||
- apt-get update && apt-get -y install git
|
||||
- composer install --no-scripts
|
||||
|
||||
"Migrates database":
|
||||
db-migrate:
|
||||
image: deblan/php:8.1
|
||||
environment:
|
||||
- PHP=php
|
||||
commands:
|
||||
- ./bin/doctrine-migrate
|
||||
when:
|
||||
branch: [master, master-*, develop, develop-*, feature/*]
|
||||
|
||||
"Generates JS routes":
|
||||
app-jsroutes:
|
||||
image: deblan/php:8.1
|
||||
commands:
|
||||
- php bin/console fos:js-routing:dump --format=json --target=public/js/fos_js_routes.json
|
||||
when:
|
||||
branch: [master, master-*, develop, develop-*, feature/*]
|
||||
|
||||
"Build assets":
|
||||
node-build:
|
||||
image: node:16-alpine
|
||||
environment:
|
||||
- CPU_COUNT=3
|
||||
volumes: *volumes
|
||||
commands:
|
||||
- apk add --no-cache git
|
||||
- npm install -g svg2ttf ttf2eot ttf2woff2
|
||||
|
@ -60,24 +58,32 @@ steps:
|
|||
- test -f public/js/fos_js_routes.json || echo "{}" > public/js/fos_js_routes.json
|
||||
- npm run build
|
||||
|
||||
"Check dependencies":
|
||||
security-check:
|
||||
image: gitnet.fr/deblan/osv-detector:v0.9
|
||||
commands:
|
||||
- osv-detector composer.lock yarn.lock
|
||||
failure: ignore
|
||||
|
||||
"Build the cache":
|
||||
image: deblan/mage
|
||||
volumes: *volumes
|
||||
app-deploy:
|
||||
image: deblan/php:8.1
|
||||
secrets: [ssh_user, ssh_host, ssh_priv_key, app_directory]
|
||||
commands:
|
||||
- cd /builds
|
||||
- rsync -az "$CI_WORKSPACE/" "$CI_COMMIT_SHA"
|
||||
- apt-get update && apt-get -y install rsync openssh-client
|
||||
- mkdir "$HOME/.ssh"
|
||||
- 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:
|
||||
db:
|
||||
image: mariadb:10.3
|
||||
environment:
|
||||
- MARIADB_ROOT_PASSWORD=root
|
||||
|
||||
volumes:
|
||||
node_cache:
|
|
@ -1,25 +0,0 @@
|
|||
variables:
|
||||
volumes: &volumes
|
||||
- /data/${CI_REPO}:/builds
|
||||
|
||||
when:
|
||||
event: [deployment]
|
||||
|
||||
skip_clone: true
|
||||
|
||||
steps:
|
||||
"Deploy":
|
||||
image: deblan/mage
|
||||
secrets: [ssh_priv_key, ssh_user, ssh_host, 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"
|
65
Dockerfile
Normal file
65
Dockerfile
Normal file
|
@ -0,0 +1,65 @@
|
|||
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"]
|
9
Makefile
9
Makefile
|
@ -1,6 +1,7 @@
|
|||
COMPOSER ?= composer
|
||||
PHP_BIN ?= php8.1
|
||||
YARN_BIN ?= yarn
|
||||
PHP ?= php
|
||||
SSH ?= ssh
|
||||
YARN ?= yarn
|
||||
NPM_BIN ?= npm
|
||||
|
||||
all: build
|
||||
|
@ -19,10 +20,10 @@ js-routing: doctrine-migration
|
|||
clean:
|
||||
rm -fr var/cache/dev/*
|
||||
rm -fr var/cache/prod/*
|
||||
$(PHP_BIN) bin/console
|
||||
$(PHP) bin/console
|
||||
|
||||
doctrine-migration:
|
||||
PHP=$(PHP_BIN) ./bin/doctrine-migrate
|
||||
PHP=$(PHP) ./bin/doctrine-migrate
|
||||
|
||||
.ONESHELL:
|
||||
lint:
|
||||
|
|
|
@ -1,63 +1,57 @@
|
|||
@import '../../vendor/murph/murph-core/src/core/Resources/assets/css/admin.scss';
|
||||
@import '@kangc/v-md-editor/lib/style/base-editor.css';
|
||||
@import '@kangc/v-md-editor/lib/theme/style/vuepress.css';
|
||||
@import "../../vendor/murph/murph-core/src/core/Resources/assets/css/admin.scss";
|
||||
@import "~simplemde/dist/simplemde.min.css";
|
||||
|
||||
.CodeMirror-fullscreen, .editor-toolbar.fullscreen {
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.ejs-link {
|
||||
margin: 10px auto;
|
||||
max-width: 80%;
|
||||
border: 2px solid #333;
|
||||
border-radius: 5px;
|
||||
margin: 10px auto;
|
||||
max-width: 80%;
|
||||
border: 2px solid #333;
|
||||
border-radius: 5px;
|
||||
|
||||
&--anchor {
|
||||
display: block;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
&-content {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
|
||||
&--title {
|
||||
font-weight: bold;
|
||||
&--anchor {
|
||||
display: block;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
&--description {
|
||||
font-size: 15px;
|
||||
&-content {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
|
||||
&--title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&--description {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
&--link {
|
||||
padding-top: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&--link {
|
||||
padding-top: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
$image-size: 85px;
|
||||
|
||||
&--anchor--with-image &-content {
|
||||
width: calc(100% - $image-size - 5px);
|
||||
padding-right: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
$image-size: 85px;
|
||||
|
||||
&--anchor--with-image &-content {
|
||||
width: calc(100% - $image-size - 5px);
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
&--image {
|
||||
display: inline-block;
|
||||
width: $image-size;
|
||||
height: $image-size;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
&--image {
|
||||
display: inline-block;
|
||||
width: $image-size;
|
||||
height: $image-size;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.choices__list--dropdown {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.v-md-editor {
|
||||
border: 1px solid $input-border-color;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.v-md-editor--fullscreen {
|
||||
z-index: 3000;
|
||||
}
|
||||
|
|
|
@ -411,9 +411,6 @@ pre[class*="language-"] {
|
|||
.h1 {
|
||||
font-weight: normal;
|
||||
font-size: 40px;
|
||||
font-family: MainFont;
|
||||
text-shadow: none;
|
||||
color: hsla(0, 0%, 100%, 0.7);
|
||||
}
|
||||
|
||||
.h3 {
|
||||
|
@ -474,7 +471,7 @@ pre[class*="language-"] {
|
|||
color: #fff;
|
||||
}
|
||||
|
||||
p a:not(.btn), ul:not(.btn-group) a:not(.btn) {
|
||||
p a, ul:not(.btn-group) a {
|
||||
background: url('../images/link.svg') bottom left repeat-x;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
@ -525,34 +522,6 @@ pre[class*="language-"] {
|
|||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content hr, .content .hr {
|
||||
border: 0;
|
||||
border-bottom: 1px dashed $color-hr-border;
|
||||
|
@ -614,9 +583,6 @@ pre[class*="language-"] {
|
|||
|
||||
.review {
|
||||
width: 100%;
|
||||
max-width: calc($content-max-width - 60px - 2rem);
|
||||
overflow: auto;
|
||||
|
||||
|
||||
.review-avatar, .review-avatar img {
|
||||
width: 60px;
|
||||
|
@ -1108,8 +1074,6 @@ $links: (
|
|||
|
||||
.ejs-link {
|
||||
margin: 10px auto;
|
||||
width: 80%;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&--anchor {
|
||||
display: block;
|
||||
|
@ -1234,8 +1198,6 @@ $links: (
|
|||
}
|
||||
|
||||
.ejs-link {
|
||||
width: auto;
|
||||
|
||||
&-content {
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
|
@ -1366,18 +1328,3 @@ $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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import '../../vendor/murph/murph-core/src/core/Resources/assets/js/admin.js'
|
||||
|
||||
require('./admin_modules/md-editor')()
|
||||
require('./admin_modules/simplemde')()
|
||||
|
||||
const $ = require('jquery')
|
||||
const Sortable = require('sortablejs').Sortable
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
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
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
|
@ -9,7 +9,7 @@ const Code = require('./app/code')
|
|||
const Knmc = require('./app/knmc')
|
||||
const VideoRatio = require('./app/video-ratio')
|
||||
const Stats = require('./app/stats')
|
||||
// const Particles = require('./app/particles')
|
||||
const Particles = require('./app/particles')
|
||||
const MeshViewer = require('./app/mesh-viewer')
|
||||
const SmallMenu = require('./app/small-menu')
|
||||
|
||||
|
@ -22,7 +22,7 @@ const app = new App([
|
|||
new Knmc(window),
|
||||
new VideoRatio(window),
|
||||
// new Stats(),
|
||||
// new Particles(window),
|
||||
new Particles(window),
|
||||
new MeshViewer(window),
|
||||
new SmallMenu(window)
|
||||
])
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
<?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>
|
Before Width: | Height: | Size: 4.8 KiB |
129
bin/messenger
129
bin/messenger
|
@ -1,129 +0,0 @@
|
|||
#!/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 "$@"
|
||||
|
|
@ -8,12 +8,9 @@
|
|||
"beberlei/doctrineextensions": "^1.3",
|
||||
"friendsofsymfony/jsrouting-bundle": "^2.7",
|
||||
"gregwar/captcha-bundle": "^2.2",
|
||||
"guzzlehttp/guzzle": "^7.8",
|
||||
"influxdata/influxdb-client-php": "^3.4",
|
||||
"knplabs/knp-markdown-bundle": "^1.9",
|
||||
"knplabs/knp-menu-bundle": "^3.1",
|
||||
"murph/murph-core": "dev-master",
|
||||
"symfony/messenger": "5.4.*",
|
||||
"murph/murph-core": "^1.18",
|
||||
"twig/intl-extra": "^3.5"
|
||||
},
|
||||
"require-dev": {
|
||||
|
@ -34,8 +31,7 @@
|
|||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"symfony/flex": true,
|
||||
"symfony/runtime": true,
|
||||
"php-http/discovery": true
|
||||
"symfony/runtime": true
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
core:
|
||||
site:
|
||||
name: "Blog"
|
||||
logo: "build/webapp/favicon.svg"
|
||||
logo: "build/images/core/logo.svg"
|
||||
controllers:
|
||||
- {name: 'LinkController:links', action: 'App\Controller\LinkController:links'}
|
||||
- {name: 'ContactController::contact', action: 'App\Controller\ContactController::contact'}
|
||||
|
@ -81,7 +81,6 @@ core:
|
|||
- image/jpg
|
||||
- image/jpeg
|
||||
- image/gif
|
||||
- image/webp
|
||||
- image/svg+xml
|
||||
- video/mp4
|
||||
- audio/mpeg3
|
||||
|
|
|
@ -13,21 +13,18 @@ liip_imagine:
|
|||
max: [600, 600]
|
||||
crop:
|
||||
size: [600, 270]
|
||||
start: [0, 0]
|
||||
project_preview_filter:
|
||||
filters:
|
||||
downscale:
|
||||
max: [600, 600]
|
||||
crop:
|
||||
size: [600, 270]
|
||||
start: [0, 0]
|
||||
post_preview_filter:
|
||||
filters:
|
||||
downscale:
|
||||
max: [600, 600]
|
||||
crop:
|
||||
size: [600, 300]
|
||||
start: [0, 0]
|
||||
site_avatar:
|
||||
filters:
|
||||
downscale:
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
framework:
|
||||
messenger:
|
||||
transports:
|
||||
async: "%env(MESSENGER_TRANSPORT_DSN)%"
|
||||
|
||||
routing:
|
||||
'App\Message\PageViewMessage': async
|
|
@ -1,6 +1,6 @@
|
|||
twig:
|
||||
default_path: '%kernel.project_dir%/templates'
|
||||
form_themes: ['form/bootstrap_4_form_theme.html.twig']
|
||||
form_themes: ['@Core/form/bootstrap_4_form_theme.html.twig']
|
||||
auto_reload: true
|
||||
paths:
|
||||
'%kernel.project_dir%/templates/core/': Core
|
||||
|
|
|
@ -4,11 +4,6 @@
|
|||
# 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
|
||||
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:
|
||||
# default configuration for services in *this* file
|
||||
|
@ -52,14 +47,6 @@ services:
|
|||
resource: '../src/Controller/'
|
||||
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:
|
||||
class: App\Core\Router\SiteRouteLoader
|
||||
tags: [routing.loader]
|
||||
|
@ -82,9 +69,5 @@ services:
|
|||
tags:
|
||||
- {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
|
||||
# please note that last definitions always *replace* previous ones
|
||||
|
|
40
docker-compose.yml
Normal file
40
docker-compose.yml
Normal file
|
@ -0,0 +1,40 @@
|
|||
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:
|
25
docker/nginx/default.conf
Normal file
25
docker/nginx/default.conf
Normal file
|
@ -0,0 +1,25 @@
|
|||
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;
|
||||
}
|
||||
}
|
15
entrypoint.sh
Executable file
15
entrypoint.sh
Executable file
|
@ -0,0 +1,15 @@
|
|||
#!/bin/sh
|
||||
|
||||
cat << BANNER
|
||||
|
||||
┌───┬───┬───┐
|
||||
│ │ ● │ │
|
||||
├───┼───┼───┤
|
||||
│ │ │ ● │
|
||||
├───┼───┼───┤
|
||||
│ ● │ ● │ ● │
|
||||
└───┴───┴───┘
|
||||
|
||||
BANNER
|
||||
|
||||
php-fpm
|
30
nohup.out
30
nohup.out
|
@ -1,30 +0,0 @@
|
|||
|
||||
[OK] Consuming messages from transport "async".
|
||||
|
||||
// The worker will automatically exit once it has received a stop signal via
|
||||
// the messenger:stop-workers command.
|
||||
|
||||
// Quit the worker with CONTROL-C.
|
||||
|
||||
// Re-run the command with a -vv option to see logs about consumed messages.
|
||||
|
||||
|
||||
[OK] Consuming messages from transport "async".
|
||||
|
||||
// The worker will automatically exit once it has received a stop signal via
|
||||
// the messenger:stop-workers command.
|
||||
|
||||
// Quit the worker with CONTROL-C.
|
||||
|
||||
// Re-run the command with a -vv option to see logs about consumed messages.
|
||||
|
||||
|
||||
[OK] Consuming messages from transport "async".
|
||||
|
||||
// The worker will automatically exit once it has received a stop signal via
|
||||
// the messenger:stop-workers command.
|
||||
|
||||
// Quit the worker with CONTROL-C.
|
||||
|
||||
// Re-run the command with a -vv option to see logs about consumed messages.
|
||||
|
28615
package-lock.json
generated
Normal file
28615
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -8,15 +8,13 @@
|
|||
"build": "./node_modules/.bin/encore production"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kangc/v-md-editor": "^1.7.12",
|
||||
"daisyui": "^2.31.0",
|
||||
"editorjs-hyperlink": "^1.0.6",
|
||||
"editorjs-inline-tool": "^0.4.0",
|
||||
"encore": "^0.0.30-beta",
|
||||
"lozad": "^1.16.0",
|
||||
"murph-project": "^1.9.4",
|
||||
"murph-project": "^1",
|
||||
"particles.js": "^2.0.0",
|
||||
"prismjs": "^1.23.0",
|
||||
"simplemde": "^1.11.2",
|
||||
"tingle.js": "^0.16.0",
|
||||
"vanillajs-datepicker": "^1.1.4",
|
||||
"vue": "^2.6.14"
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
<?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 $this->getClient() !== null;
|
||||
}
|
||||
|
||||
public function getClient(): ?Client
|
||||
{
|
||||
return $this->client;
|
||||
}
|
||||
}
|
|
@ -43,7 +43,6 @@ class CategoryAdminController extends CrudController
|
|||
->setMaxPerPage('index', 100)
|
||||
|
||||
->setView('form', 'blog/category_admin/_form.html.twig')
|
||||
->setDoubleClick('index', true)
|
||||
|
||||
->setDefaultSort('index', 'title', 'asc')
|
||||
|
||||
|
|
|
@ -2,30 +2,27 @@
|
|||
|
||||
namespace App\Controller\Blog;
|
||||
|
||||
use App\Analytic\DateRangeAnalytic;
|
||||
use App\Core\Controller\Admin\Crud\CrudController;
|
||||
use App\Core\Crud\CrudConfiguration;
|
||||
use App\Core\Crud\Field\DatetimeField;
|
||||
use App\Core\Crud\Field\TextField;
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Core\Form\FileUploadHandler;
|
||||
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\Factory\Blog\PostFactory as EntityFactory;
|
||||
use App\Form\Blog\Filter\PostFilterType;
|
||||
use App\Form\Blog\PostType;
|
||||
use App\Form\Blog\PostType as EntityType;
|
||||
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\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use Symfony\Component\Form\Form;
|
||||
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')]
|
||||
class PostAdminController extends CrudController
|
||||
|
@ -40,7 +37,6 @@ class PostAdminController extends CrudController
|
|||
|
||||
->setPageRoute('index', 'admin_blog_post_index')
|
||||
->setPageRoute('edit', 'admin_blog_post_edit')
|
||||
->setPageRoute('inline_edit', 'admin_blog_post_inline_edit')
|
||||
->setPageRoute('new', 'admin_blog_post_new')
|
||||
->setPageRoute('show', 'admin_blog_post_show')
|
||||
->setPageRoute('delete', 'admin_blog_post_delete')
|
||||
|
@ -58,7 +54,6 @@ class PostAdminController extends CrudController
|
|||
->setView('edit', 'blog/post_admin/edit.html.twig')
|
||||
->setView('show', 'blog/post_admin/show.html.twig')
|
||||
->setView('index', 'blog/post_admin/index.html.twig')
|
||||
->setDoubleClick('index', true)
|
||||
|
||||
->setDefaultSort('index', 'id', 'desc')
|
||||
->setField('index', 'Titre', TextField::class, [
|
||||
|
@ -67,7 +62,7 @@ class PostAdminController extends CrudController
|
|||
'attr' => ['class' => 'miw-400'],
|
||||
])
|
||||
->setField('index', 'ID', TextField::class, [
|
||||
'property_builder' => function (EntityInterface $entity) {
|
||||
'property_builder' => function(EntityInterface $entity) {
|
||||
return sprintf('#%d', $entity->getId());
|
||||
},
|
||||
'sort' => ['id', '.id'],
|
||||
|
@ -84,55 +79,19 @@ class PostAdminController extends CrudController
|
|||
'format' => 'd/m/Y H:i',
|
||||
'sort' => ['publishedAt', '.publishedAt'],
|
||||
'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, [
|
||||
'view' => 'blog/post_admin/field/status.html.twig',
|
||||
'sort' => ['status', '.status'],
|
||||
'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);
|
||||
})
|
||||
->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));
|
||||
})
|
||||
->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));
|
||||
})
|
||||
;
|
||||
|
@ -151,7 +110,7 @@ class PostAdminController extends CrudController
|
|||
$factory->create($this->getUser()),
|
||||
$entityManager,
|
||||
$request,
|
||||
function (Entity $entity, Form $form, Request $request) use ($fileUpload) {
|
||||
function(Entity $entity, Form $form, Request $request) use ($fileUpload) {
|
||||
$directory = 'uploads/post/'.date('Y');
|
||||
|
||||
$fileUpload->handleForm(
|
||||
|
@ -172,7 +131,7 @@ class PostAdminController extends CrudController
|
|||
$entity,
|
||||
$entityManager,
|
||||
$request,
|
||||
function (Entity $entity, Form $form, Request $request) use ($fileUpload) {
|
||||
function(Entity $entity, Form $form, Request $request) use ($fileUpload) {
|
||||
$directory = 'uploads/post/'.date('Y');
|
||||
|
||||
$fileUpload->handleForm(
|
||||
|
@ -186,12 +145,6 @@ 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')]
|
||||
public function show(Entity $entity): Response
|
||||
{
|
||||
|
@ -267,7 +220,8 @@ class PostAdminController extends CrudController
|
|||
DateRangeAnalytic $analytic,
|
||||
NodeRepository $nodeRepository,
|
||||
string $range = '7days'
|
||||
): Response {
|
||||
): Response
|
||||
{
|
||||
if (!in_array($range, ['7days', '30days', '90days', '1year'])) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
|||
use App\Factory\Blog\PostFollowFactory;
|
||||
use App\Manager\PostFollowManager;
|
||||
use App\Core\Twig\Extension\EditorJsExtension;
|
||||
use App\Core\Twig\Extension\BuilderExtension;
|
||||
|
||||
class PostController extends PageController
|
||||
{
|
||||
|
@ -93,9 +92,9 @@ class PostController extends PageController
|
|||
]);
|
||||
}
|
||||
|
||||
public function posts(Request $request, int $page = 1): Response
|
||||
public function posts(int $page = 1): Response
|
||||
{
|
||||
$entities = $this->createQuery($request->query->has('preview') && $this->getUser())
|
||||
$entities = $this->createQuery()
|
||||
->paginate($page, 9)
|
||||
;
|
||||
|
||||
|
@ -154,23 +153,15 @@ class PostController extends PageController
|
|||
{
|
||||
}
|
||||
|
||||
public function createQuery(bool $isPreview = false): PostRepositoryQuery
|
||||
public function createQuery(): PostRepositoryQuery
|
||||
{
|
||||
$query = $this->postQuery->create()
|
||||
->orderBy('.publishedAt', 'DESC');
|
||||
|
||||
if (!$isPreview) {
|
||||
$query->published();
|
||||
}
|
||||
|
||||
return $query;
|
||||
return $this->postQuery->create()
|
||||
->orderBy('.publishedAt', 'DESC')
|
||||
->published()
|
||||
;
|
||||
}
|
||||
|
||||
public function rss(
|
||||
PostParser $parser,
|
||||
EditorJsExtension $editorJsExtension,
|
||||
BuilderExtension $builderExtension
|
||||
): Response
|
||||
public function rss(PostParser $parser, EditorJsExtension $editorJsExtension): Response
|
||||
{
|
||||
$entities = $this->createQuery()->paginate(1, 20);
|
||||
$items = [];
|
||||
|
@ -178,8 +169,6 @@ class PostController extends PageController
|
|||
foreach ($entities as $entity) {
|
||||
if ($entity->getContentFormat() === 'editorjs') {
|
||||
$description = $editorJsExtension->buildHtml($entity->getContent());
|
||||
} elseif ($entity->getContentFormat() === 'builder') {
|
||||
$description = $builderExtension->buildHtml($entity->getContent());
|
||||
} else {
|
||||
$description = $parser->transformMarkdown($entity->getContent());
|
||||
}
|
||||
|
|
|
@ -5,31 +5,13 @@ namespace App\Controller;
|
|||
use App\Core\Controller\Dashboard\DashboardAdminController as Controller;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use App\Repository\Blog\PostRepositoryQuery;
|
||||
use App\Repository\ProjectRepositoryQuery;
|
||||
|
||||
#[Route(path: '/admin')]
|
||||
class DashboardAdminController extends Controller
|
||||
{
|
||||
#[Route(path: '/', name: 'admin_dashboard_index')]
|
||||
public function index(
|
||||
PostRepositoryQuery $postQuery,
|
||||
ProjectRepositoryQuery $projectQuery
|
||||
): Response
|
||||
public function index(): 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,
|
||||
]);
|
||||
return $this->render('admin/dashboard.html.twig');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
<?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')
|
||||
;
|
||||
}
|
||||
}
|
|
@ -19,8 +19,8 @@ class Post implements EntityInterface
|
|||
{
|
||||
use Timestampable;
|
||||
|
||||
public const DRAFT = 0;
|
||||
public const PUBLISHED = 1;
|
||||
const DRAFT = 0;
|
||||
const PUBLISHED = 1;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
|
@ -87,21 +87,11 @@ class Post implements EntityInterface
|
|||
#[ORM\Column(type: 'string', length: 255, nullable: true)]
|
||||
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()
|
||||
{
|
||||
$this->categories = new ArrayCollection();
|
||||
$this->comments = new ArrayCollection();
|
||||
$this->postFollows = new ArrayCollection();
|
||||
$this->deprecatedPosts = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
|
@ -448,80 +438,4 @@ class Post implements EntityInterface
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ use App\Core\Entity\Site\Page\FileBlock;
|
|||
use App\Form\Type\SimpleMdTextareaBlockType;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use App\Form\MarkdownBlockType;
|
||||
|
||||
#[ORM\Entity]
|
||||
class SimplePage extends TitledPage
|
||||
|
@ -18,7 +17,7 @@ class SimplePage extends TitledPage
|
|||
|
||||
$builder->add(
|
||||
'content',
|
||||
MarkdownBlockType::class,
|
||||
SimpleMdTextareaBlockType::class,
|
||||
[
|
||||
'label' => 'Contenu',
|
||||
'options' => [
|
||||
|
|
|
@ -28,14 +28,6 @@ class TextPage extends Page
|
|||
],
|
||||
]
|
||||
);
|
||||
|
||||
$builder
|
||||
->remove('metaTitle')
|
||||
->remove('metaDescription')
|
||||
->remove('ogTitle')
|
||||
->remove('ogDescription')
|
||||
->remove('ogImage')
|
||||
;
|
||||
}
|
||||
|
||||
public function setContent(Block $block)
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
<?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()));
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@ use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
|||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use App\Core\Form\FileManager\FilePickerType;
|
||||
use App\Core\Form\Type\TinymceTextareaType;
|
||||
|
||||
/**
|
||||
* class SettingEventSubscriber.
|
||||
|
@ -37,7 +36,6 @@ class SettingEventSubscriber extends EventSubscriber
|
|||
|
||||
$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_grafana_url', '📊 Statistiques', 'Adresse tableau de bord Grafana', '');
|
||||
|
||||
$this->manager->init('post_author_description', '🖊️ Article', 'Description auteur', '');
|
||||
|
||||
|
@ -70,7 +68,7 @@ class SettingEventSubscriber extends EventSubscriber
|
|||
);
|
||||
}
|
||||
|
||||
if (in_array($entity->getCode(), ['giphy_api_key', 'stats_umami_url', 'stats_grafana_url'])) {
|
||||
if (in_array($entity->getCode(), ['giphy_api_key', 'stats_umami_url'])) {
|
||||
$builder->add(
|
||||
'value',
|
||||
TextType::class,
|
||||
|
@ -95,7 +93,7 @@ class SettingEventSubscriber extends EventSubscriber
|
|||
);
|
||||
}
|
||||
|
||||
if (in_array($entity->getCode(), ['post_author_description'])) {
|
||||
if (in_array($entity->getCode(), ['blog_footer', 'post_author_description'])) {
|
||||
$event->setOption('view', 'large');
|
||||
|
||||
$builder->add(
|
||||
|
@ -109,20 +107,5 @@ 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,
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -24,9 +24,6 @@ use Symfony\Component\Validator\Constraints\Url;
|
|||
use App\Form\Type\SimpleMdTextareaType;
|
||||
use App\Core\Form\Type\EditorJsTextareaType;
|
||||
use App\Core\Form\FileManager\FilePickerType;
|
||||
use App\Core\Form\Type\CollectionType as MurphCollectionType;
|
||||
use App\Form\MarkdownType;
|
||||
use App\Core\Form\Type\BuilderType;
|
||||
|
||||
class PostType extends AbstractType
|
||||
{
|
||||
|
@ -53,7 +50,6 @@ class PostType extends AbstractType
|
|||
'required' => true,
|
||||
'choices' => [
|
||||
'Markdown' => 'markdown',
|
||||
'Builder' => 'builder',
|
||||
'HTML' => 'html',
|
||||
'Editor JS' => 'editorjs',
|
||||
],
|
||||
|
@ -64,9 +60,8 @@ class PostType extends AbstractType
|
|||
);
|
||||
|
||||
$types = [
|
||||
'markdown' => MarkdownType::class,
|
||||
'builder' => BuilderType::class,
|
||||
'html' => MarkdownType::class,
|
||||
'markdown' => SimpleMdTextareaType::class,
|
||||
'html' => SimpleMdTextareaType::class,
|
||||
'editorjs' => EditorJsTextareaType::class,
|
||||
];
|
||||
|
||||
|
@ -117,28 +112,6 @@ 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(
|
||||
'status',
|
||||
ChoiceType::class,
|
||||
|
@ -198,7 +171,6 @@ class PostType extends AbstractType
|
|||
'attr' => [
|
||||
],
|
||||
'constraints' => [
|
||||
new Image(),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
@ -326,20 +298,6 @@ 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)
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
<?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']),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
|
||||
class MarkdownType extends TextareaType
|
||||
{
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return 'markdown';
|
||||
}
|
||||
}
|
|
@ -65,12 +65,11 @@ class ProjectType extends AbstractType
|
|||
FilePickerType::class,
|
||||
[
|
||||
'label' => 'Image',
|
||||
'required' => true,
|
||||
'required' => false,
|
||||
'data_class' => null,
|
||||
'attr' => [
|
||||
],
|
||||
'constraints' => [
|
||||
new NotBlank(),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
namespace App;
|
||||
|
||||
use App\Core\DependencyInjection\Compiler\BuilderBlockPass;
|
||||
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
|
||||
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
|
||||
|
@ -37,9 +35,4 @@ class Kernel extends BaseKernel
|
|||
(require $path)($routes->withPath($path), $this);
|
||||
}
|
||||
}
|
||||
|
||||
protected function build(ContainerBuilder $container): void
|
||||
{
|
||||
$container->addCompilerPass(new BuilderBlockPass());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Message;
|
||||
|
||||
final class PageViewMessage
|
||||
{
|
||||
public function __construct(public int $time)
|
||||
{
|
||||
}
|
||||
|
||||
public function getTime(): int
|
||||
{
|
||||
return $this->time;
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\MessageHandler;
|
||||
|
||||
use App\Message\PageViewMessage;
|
||||
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
|
||||
use App\Api\InfluxDB;
|
||||
use InfluxDB2\WriteType;
|
||||
use InfluxDB2\Point;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\AppEntityBlogPost;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<AppEntityBlogPost>
|
||||
*
|
||||
* @method AppEntityBlogPost|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method AppEntityBlogPost|null 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()
|
||||
// ;
|
||||
// }
|
||||
}
|
|
@ -54,13 +54,15 @@ class PostRepositoryQuery extends RepositoryQuery
|
|||
;
|
||||
}
|
||||
|
||||
protected function filterHandler(string $name, $value)
|
||||
{
|
||||
if ('category' === $name) {
|
||||
$this->inCategory($value);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
$conn = $this->repository->getEm()->getConnection();
|
||||
|
||||
|
@ -68,106 +70,42 @@ class PostRepositoryQuery extends RepositoryQuery
|
|||
'SELECT
|
||||
post.id,
|
||||
post.title,
|
||||
post.content,
|
||||
post.published_at
|
||||
MATCH(post.title) AGAINST(:search) AS MATCH_TITLE,
|
||||
MATCH(post.content) AGAINST(:search) AS MATCH_CONTENT
|
||||
FROM post
|
||||
WHERE
|
||||
post.status = 1 AND
|
||||
post.published_at < :date
|
||||
'
|
||||
);
|
||||
ORDER BY
|
||||
MATCH_TITLE DESC,
|
||||
MATCH_CONTENT DESC
|
||||
');
|
||||
|
||||
$statement = $query->execute([
|
||||
':search' => $keywords,
|
||||
':date' => (new \DateTime())->format('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
$results = $statement->fetchAll();
|
||||
$ids = [];
|
||||
$matches = [];
|
||||
|
||||
foreach ($results as $k => $v) {
|
||||
$initWords = explode(' ', $v['title']);
|
||||
$words = [];
|
||||
$rate = ($v['MATCH_TITLE'] * 2) + $v['MATCH_CONTENT'];
|
||||
|
||||
foreach ($initWords as $initWord) {
|
||||
$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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($words as $word) {
|
||||
if (str_contains(mb_strtolower($word), mb_strtolower($keyword))) {
|
||||
$similarity = 150;
|
||||
|
||||
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,
|
||||
];
|
||||
}
|
||||
} 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,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($rate >= 7) {
|
||||
$ids[] = $v['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$matches = array_filter($matches, function($match) use ($keywords) {
|
||||
return (100 * $match['count'] / count($keywords)) > 80;
|
||||
});
|
||||
if (0 == count($ids)) {
|
||||
foreach ($results as $k => $v) {
|
||||
$rate = ($v['MATCH_TITLE'] * 2) + $v['MATCH_CONTENT'];
|
||||
|
||||
usort($matches, function ($a, $b) {
|
||||
if ($a['similarity'] > $b['similarity']) {
|
||||
return -1;
|
||||
if ($rate >= 6) {
|
||||
$ids[] = $v['id'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($b['similarity'] > $a['similarity']) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return ($a['published_at'] != $b['published_at']) * -1;
|
||||
});
|
||||
|
||||
$ids = array_column($matches, 'id');
|
||||
}
|
||||
|
||||
if (!$ids) {
|
||||
$ids = [-1];
|
||||
|
@ -189,11 +127,4 @@ class PostRepositoryQuery extends RepositoryQuery
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function filterHandler(string $name, $value)
|
||||
{
|
||||
if ('category' === $name) {
|
||||
$this->inCategory($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
{{ include('@Core/user/user_admin/_form.html.twig') }}
|
|
@ -1 +0,0 @@
|
|||
{{ include('@Core/user/user_admin/_show.html.twig') }}
|
|
@ -4,72 +4,6 @@
|
|||
|
||||
{% block body %}
|
||||
<div class="row">
|
||||
<div class="col-12 pt-3 pl-3 pr-3">
|
||||
<h3>Bienvenue, {{ app.user.displayName }} 👋</h3>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-3 pt-1 pr-3 pl-3">
|
||||
<div class="list-group mb-4">
|
||||
<div class="list-group-item list-group-item-action bg-dark-blue">
|
||||
<a href="{{ path('admin_blog_post_index') }}" class="text-white">
|
||||
<span class="fa fa-pen"></span>
|
||||
{{ 'Articles'|trans }}
|
||||
</a>
|
||||
</div>
|
||||
{% for entity in posts %}
|
||||
<div class="list-group-item">
|
||||
{{ include('blog/post_admin/field/title.html.twig') }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="list-group mb-4">
|
||||
<div class="list-group-item list-group-item-action bg-dark-blue">
|
||||
<a href="{{ path('admin_project_index') }}" class="text-white">
|
||||
<span class="fa fa-hat-wizard"></span>
|
||||
{{ 'Projets'|trans }}
|
||||
</a>
|
||||
</div>
|
||||
{% for entity in projects %}
|
||||
<div class="list-group-item">
|
||||
<a href="{{ path('admin_project_edit', {entity: entity.id}) }}" class="d-block text-dark">
|
||||
{{ entity.label }}
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-9 pt-1">
|
||||
<ul class="nav nav-pills" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" href="#tab-stats-umami">
|
||||
{{ 'Umami'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#tab-stats-grafana">
|
||||
{{ 'Grafana'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content pt-4">
|
||||
<div class="tab-pane show active" id="tab-stats-umami">
|
||||
<iframe
|
||||
src="{{ setting('stats_umami_url') }}"
|
||||
frameborder="0"
|
||||
style="width: 100%; height: calc(100vh - 220px)"
|
||||
></iframe>
|
||||
</div>
|
||||
<div class="tab-pane" id="tab-stats-grafana">
|
||||
<iframe
|
||||
src="{{ setting('stats_grafana_url') }}"
|
||||
frameborder="0" style="width: 100%; height: calc(100vh - 220px)"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<iframe src="{{ setting('stats_umami_url') }}" class="col-12" frameborder="0" style="height: calc(100vh - 60px)"></iframe>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
<?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')
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
{{ include('@Core/user/user_admin/_form.html.twig') }}
|
|
@ -1 +0,0 @@
|
|||
{{ include('@Core/user/user_admin/_show.html.twig') }}
|
|
@ -50,7 +50,6 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|||
|
||||
<link rel="alternate" type="application/rss+xml" title="Flux RSS" href="{{ safe_url('blog_network_rss') }}">
|
||||
|
||||
{#
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="{{ asset('build/webapp/apple-icon-57x57.png') }}">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="{{ asset('build/webapp/apple-icon-60x60.png') }}">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="{{ asset('build/webapp/apple-icon-72x72.png') }}">
|
||||
|
@ -60,16 +59,15 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|||
<link rel="apple-touch-icon" sizes="144x144" href="{{ asset('build/webapp/apple-icon-144x144.png') }}">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="{{ asset('build/webapp/apple-icon-152x152.png') }}">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ asset('build/webapp/apple-icon-180x180.png') }}">
|
||||
<link rel="icon" type="image/png" href="{{ asset('build/webapp/favicon.png') }}" >
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="{{ asset('build/webapp/android-icon-192x192.png') }}">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ asset('build/webapp/favicon.png') }}">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="{{ asset('build/webapp/favicon-96x96.png') }}">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ asset('build/webapp/favicon-16x16.png') }}">
|
||||
<link rel="manifest" href="{{ asset('build/webapp/manifest.json') }}">
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="msapplication-TileImage" content="{{ asset('build/webapp/ms-icon-144x144.png') }}">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
#}
|
||||
<link rel="icon" href="{{ asset('build/webapp/favicon.svg') }}" >
|
||||
<link rel="manifest" href="{{ asset('build/webapp/manifest.json') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="flex items-stretch blog">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<div class="row">
|
||||
<div class="col-md-3 p-3">
|
||||
<div class="row">
|
||||
{% for item in ['categories', 'slug', 'recommandedPost'] %}
|
||||
{% for item in ['categories', 'slug'] %}
|
||||
<div class="col-md-12">
|
||||
{{ form_row(form[item]) }}
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{#
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="{{ asset('build/webapp/apple-icon-57x57.png') }}">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="{{ asset('build/webapp/apple-icon-60x60.png') }}">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="{{ asset('build/webapp/apple-icon-72x72.png') }}">
|
||||
|
@ -15,9 +14,7 @@
|
|||
<link rel="icon" type="image/png" sizes="32x32" href="{{ asset('build/webapp/favicon.png') }}">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="{{ asset('build/webapp/favicon-96x96.png') }}">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ asset('build/webapp/favicon-16x16.png') }}">
|
||||
<link rel="manifest" href="{{ asset('build/webapp/manifest.json') }}">
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="msapplication-TileImage" content="{{ asset('build/webapp/ms-icon-144x144.png') }}">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
#}
|
||||
<link rel="manifest" href="{{ asset('build/webapp/manifest.json') }}">
|
||||
<link rel="icon" href="{{ asset('build/webapp/favicon.svg') }}" >
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
{% extends '@Core/form/bootstrap_4_form_theme.html.twig' %}
|
||||
|
||||
{% block markdown_widget %}
|
||||
<div {% for attr, value in row_attr %}{{ attr }}="{{ value }}" {% endfor %}>
|
||||
<div class="markdown-editor" data-value="{{ value|json_encode }}" data-name="{{ full_name }}" data-id="{{ id }}">
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -5,7 +5,7 @@
|
|||
<div class="wide-menu hidden md:block">
|
||||
<div class="fixed-menu">
|
||||
<div class="text-center">
|
||||
<a href="{{- safe_path('blog_menu_posts', {_domain: _domain}) -}}" class="avatar-logo">
|
||||
<a href="{{- safe_path('blog_menu_posts', {_domain: _domain}) -}}">
|
||||
{%- if avatar -%}
|
||||
<img src="{{- asset(avatar)|imagine_filter('site_avatar') -}}" class="rounded-full inline mb-2" alt="{{- avatar|file_attribute('title') -}}">
|
||||
{%- endif -%}
|
||||
|
|
|
@ -10,50 +10,46 @@
|
|||
</div>
|
||||
|
||||
{% if showForm %}
|
||||
<div class="reviews">
|
||||
<div class="rounded-2xl shadow-md p-8 mb-8 bg-box form" id="grid">
|
||||
<div class="h4">M'envoyer un message</div>
|
||||
<div class="body">
|
||||
<form class="form grid grid-flow-row-dens grid-cols-2 gap-5" method="POST" data-form-bot action="{{ safe_url('blog_tech_form_without_javascript', {page: app.request.uri, _domain: _domain}) }}">
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
{{ form_label(form.name, null, {label_attr: {class: 'label'}}) }}
|
||||
{{ form_widget(form.name, {attr: {class: 'input input-bordered w-full'}}) }}
|
||||
{{ form_errors(form.name) }}
|
||||
</div>
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
{{ form_label(form.email, null, {label_attr: {class: 'label'}}) }}
|
||||
{{ form_widget(form.email, {attr: {class: 'input input-bordered w-full'}}) }}
|
||||
{{ form_errors(form.email) }}
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
{{ form_label(form.subject, null, {label_attr: {class: 'label'}}) }}
|
||||
{{ form_widget(form.subject, {attr: {class: 'input input-bordered w-full'}}) }}
|
||||
{{ form_errors(form.subject) }}
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
{{ form_label(form.message, null, {label_attr: {class: 'label'}}) }}
|
||||
{{ form_errors(form.message) }}
|
||||
{{ form_widget(form.message, {attr: {cols: 30, rows: 10, class: 'textarea textarea-bordered w-full'}}) }}
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<div class="md:flex justify-start gap-3">
|
||||
{{ form_label(form.captcha, null, {label_attr: {class: 'label'}}) }}
|
||||
{{ form_widget(form.captcha, {attr: {class: 'input input-bordered'}}) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="label justify-start gap-3" for="rgpd">
|
||||
<input type="checkbox" id="rgpd" class="checkbox" required>
|
||||
<span class="label">En validant ce formulaire, vous acceptez que j'utilise votre e-mail pour vous fournir une réponse.</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<input type="submit" class="btn btn-primary" value="Envoyer" />
|
||||
</div>
|
||||
|
||||
<form class="form grid grid-flow-row-dens grid-cols-2 gap-5" method="POST" data-form-bot action="{{ safe_url('blog_tech_form_without_javascript', {page: app.request.uri, _domain: _domain}) }}">
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
{{ form_label(form.name, null, {label_attr: {class: 'label'}}) }}
|
||||
{{ form_widget(form.name, {attr: {class: 'input input-bordered w-full'}}) }}
|
||||
{{ form_errors(form.name) }}
|
||||
</div>
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
{{ form_label(form.email, null, {label_attr: {class: 'label'}}) }}
|
||||
{{ form_widget(form.email, {attr: {class: 'input input-bordered w-full'}}) }}
|
||||
{{ form_errors(form.email) }}
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
{{ form_label(form.subject, null, {label_attr: {class: 'label'}}) }}
|
||||
{{ form_widget(form.subject, {attr: {class: 'input input-bordered w-full'}}) }}
|
||||
{{ form_errors(form.subject) }}
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
{{ form_label(form.message, null, {label_attr: {class: 'label'}}) }}
|
||||
{{ form_errors(form.message) }}
|
||||
{{ form_widget(form.message, {attr: {cols: 30, rows: 10, class: 'textarea textarea-bordered w-full'}}) }}
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<div class="md:flex justify-start gap-3">
|
||||
{{ form_label(form.captcha, null, {label_attr: {class: 'label'}}) }}
|
||||
{{ form_widget(form.captcha, {attr: {class: 'input input-bordered'}}) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="label justify-start gap-3" for="rgpd">
|
||||
<input type="checkbox" id="rgpd" class="checkbox" required>
|
||||
<span class="label">En validant ce formulaire, vous acceptez que j'utilise votre e-mail pour vous fournir une réponse.</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<input type="submit" class="btn btn-primary" value="Envoyer" />
|
||||
</div>
|
||||
|
||||
{{ form_rest(form) }}
|
||||
</form>
|
||||
</div>
|
||||
{{ form_rest(form) }}
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -27,28 +27,32 @@
|
|||
</div>
|
||||
|
||||
{% if pager.items is defined %}
|
||||
<div class="col-12">
|
||||
<div class="body">
|
||||
{% for item in pager.items %}
|
||||
{% if item.metas|length and item.metas.meta.title %}
|
||||
{{ {blocks: [{type: 'link', data: item.metas}]}|editorjs_to_html|raw }}
|
||||
{% else %}
|
||||
<div class="ejs-link rounded-2xl shadow-md p-2 md:p-8 flex justify-start bg-box">
|
||||
<a href="{{ item.link }}" class="ejs-link--anchor" target="_blank">
|
||||
<div class="ejs-link-content">
|
||||
<div class="ejs-link-content--title mb-8">{{- item.title -}}</div>
|
||||
{% for item in pager.items %}
|
||||
{% if item.metas|length and item.metas.meta.title %}
|
||||
<div class="col-12">
|
||||
<div class="body">
|
||||
{{ {blocks: [{type: 'link', data: item.metas}]}|editorjs_to_html|raw }}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
<div class="body">
|
||||
<div class="ejs-link rounded-2xl shadow-md p-2 md:p-8 flex justify-start bg-box">
|
||||
<a href="{{ item.link }}" class="ejs-link--anchor" target="_blank">
|
||||
<div class="ejs-link-content">
|
||||
<div class="ejs-link-content--title mb-8">{{- item.title -}}</div>
|
||||
|
||||
<div class="ejs-link-content--link">
|
||||
<span class="deblan-icon deblan-icon-link"></span>
|
||||
{{- item.link -}}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="ejs-link-content--link">
|
||||
<span class="deblan-icon deblan-icon-link"></span>
|
||||
{{- item.link -}}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if pager.maxPage %}
|
||||
<div class="col-12">
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<div class="card shadow-md col-span-12 md:col-span-6 lg:col-span-4 m-3 bg-box">
|
||||
<div class="card">
|
||||
<figure>
|
||||
<img src="{{ asset('build/images/px.png') }}" data-color="{{ generate_color_by_string(mesh.id) }}" data-src="{{- asset(mesh.preview)|imagine_filter('mesh_preview_filter') -}}" data-src-error="{{ asset('build/images/post-image-logo.png') }}" alt="{{ mesh.label }}">
|
||||
<img src="{{ asset(mesh.preview)|imagine_filter('mesh_preview_filter') }}" alt="{{ mesh.label }}">
|
||||
</figure>
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{{ mesh.label }}</h2>
|
||||
|
@ -24,7 +24,7 @@
|
|||
<div class="card-actions mt-5">
|
||||
{% for key, item in mesh.files %}
|
||||
<div class="dropdown dropdown-top">
|
||||
<label tabindex="0" class="btn btn-xs">{{ item.name }}</label>
|
||||
<label tabindex="0" class="btn">{{ item.name }}</label>
|
||||
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52">
|
||||
<li>
|
||||
<a class="mesh-viewer" data-modal href="{{ path('mesh_viewer', {stlMesh: mesh.id, key: key + 1}) }}">Voir en 3D</a>
|
||||
|
|
|
@ -39,24 +39,10 @@
|
|||
{% endif %}
|
||||
|
||||
<div class="body-content">
|
||||
{%- if post.recommandedPost -%}
|
||||
<div class="deprecated">
|
||||
<svg stroke-width="2" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff"><path d="M20.043 21H3.957c-1.538 0-2.5-1.664-1.734-2.997l8.043-13.988c.77-1.337 2.699-1.337 3.468 0l8.043 13.988C22.543 19.336 21.58 21 20.043 21zM12 9v4" stroke="#ffffff" stroke-width="2" stroke-linecap="round"></path><path d="M12 17.01l.01-.011" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
||||
<strong>Cet article est déprécié</strong><br>
|
||||
Article recommandé : <a href="{{ safe_path('blog_menu_post', {
|
||||
post: post.recommandedPost.id,
|
||||
slug: post.recommandedPost.slug,
|
||||
_domain: _domain})
|
||||
}}"><strong>{{ post.recommandedPost.title }}</strong></a>
|
||||
</div>
|
||||
{%- endif -%}
|
||||
|
||||
{% if post.contentFormat == 'html' %}
|
||||
{{- post.content|murph_url|file_attributes|post -}}
|
||||
{% elseif post.contentFormat == 'markdown' %}
|
||||
{{- post.content|murph_url|file_attributes|markdown('post')|lazy_load -}}
|
||||
{% elseif post.contentFormat == 'builder' %}
|
||||
{{- post.content|block_to_html|lazy_load -}}
|
||||
{% elseif post.contentFormat == 'editorjs' %}
|
||||
{{- post.content|murph_url|file_attributes|editorjs_to_html|raw -}}
|
||||
{% endif %}
|
||||
|
@ -69,14 +55,14 @@
|
|||
{% set description = setting('post_author_description') %}
|
||||
|
||||
{%- if description and not post.isQuick -%}
|
||||
<div class="body post-author-wrapper">
|
||||
<div class="rounded-2xl shadow-md p-2 md:p-8 bg-box">
|
||||
<div class="body">
|
||||
<div class="rounded-2xl shadow-md p-2 md:p-8 flex justify-start bg-box">
|
||||
{%- set avatar = setting('avatar_image') -%}
|
||||
|
||||
{%- if avatar -%}
|
||||
<div class="post-author-avatar">
|
||||
<p class="mr-8">
|
||||
<img src="{{ asset(avatar)|imagine_filter('site_avatar') }}" alt="Simon Vieille" title="Simon Vieille" class="rounded-full">
|
||||
</div>
|
||||
</p>
|
||||
{%- endif -%}
|
||||
|
||||
<div class="post-author">
|
||||
|
@ -115,8 +101,12 @@
|
|||
|
||||
{% form_theme form with "form_div_layout.html.twig" %}
|
||||
|
||||
<div class="rounded-2xl shadow-md p-8 mb-8 bg-box form" id="grid">
|
||||
<div class="grid" id="form">
|
||||
<form class="form" method="POST" data-form-bot action="{{ safe_url('blog_tech_form_without_javascript', {page: app.request.uri, _domain: _domain}) }}">
|
||||
{% if comments|length %}
|
||||
<hr>
|
||||
{% endif %}
|
||||
|
||||
<div class="h4">Ajouter un commentaire</div>
|
||||
|
||||
<div class="grid grid-flow-row-dens grid-cols-2 gap-5">
|
||||
|
|
|
@ -50,10 +50,10 @@
|
|||
{% else %}
|
||||
{% set image = null %}
|
||||
|
||||
{% if post.image %}
|
||||
{% set image = absolute_url(asset(post.image)) %}
|
||||
{% elseif post.quickImage %}
|
||||
{% if post.quickImage %}
|
||||
{% set image = post.quickImage %}
|
||||
{% elseif post.image %}
|
||||
{% set image = absolute_url(asset(post.image)) %}
|
||||
{% endif %}
|
||||
|
||||
{% set title = block('meta_title') %}
|
||||
|
|
|
@ -13,27 +13,29 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-flow-row-dens grid-cols-12 md:p-8 gap-5">
|
||||
{% for project in projects %}
|
||||
<div class="card shadow-md col-span-12 md:col-span-6 lg:col-span-4 m-3 bg-box">
|
||||
{% if project.image %}
|
||||
<figure>
|
||||
<img src="{{ asset(project.image)|imagine_filter('project_preview_filter') }}" alt="{{ project.label }}">
|
||||
</figure>
|
||||
{% endif %}
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{{ project.label }}</h2>
|
||||
<div class="grid grid-flow-row-dens grid-cols-12 md:p-8 gap-5">
|
||||
{% for project in projects %}
|
||||
<div class="card shadow-md col-span-12 md:col-span-6 lg:col-span-4 m-3 bg-box">
|
||||
<div class="card">
|
||||
{% if project.image %}
|
||||
<figure>
|
||||
<img src="{{ asset(project.image)|imagine_filter('project_preview_filter') }}" alt="{{ project.label }}">
|
||||
</figure>
|
||||
{% endif %}
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{{ project.label }}</h2>
|
||||
|
||||
{{- project.description|murph_url|markdown('post') -}}
|
||||
{{- project.description|murph_url|markdown('post') -}}
|
||||
|
||||
<div class="card-actions mt-5">
|
||||
{% for link in project.links %}
|
||||
<a class="btn btn-xs" href="{{ link.url|murph_url }}" target="_blank">
|
||||
{{- link.label -}}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-actions mt-5">
|
||||
{% for link in project.links %}
|
||||
<a class="btn" href="{{ link.url|murph_url }}" target="_blank">
|
||||
{{- link.label -}}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue