diff --git a/assets/css/admin.scss b/assets/css/admin.scss index 61ecf32..5eb8fd8 100644 --- a/assets/css/admin.scss +++ b/assets/css/admin.scss @@ -13,6 +13,7 @@ $pagination-active-bg: #343a40; @import "~choices.js/src/styles/choices.scss"; @import "~bootstrap/scss/bootstrap.scss"; @import "~@fortawesome/fontawesome-free/css/all.css"; +@import "~simplemde/dist/simplemde.min.css"; #logo { width: 30px; diff --git a/assets/css/app.scss b/assets/css/app.scss new file mode 100644 index 0000000..82e01d9 --- /dev/null +++ b/assets/css/app.scss @@ -0,0 +1,749 @@ +@import "app/config"; +@import "~wire.css/src/scss/wire"; +@import 'app/prism'; +@import 'app/alert'; + +@font-face { + font-family: "MainFont"; + src: url(../fonts/ubuntu/ubuntu-light.woff) format('woff'); +} + +@font-face { + font-family: "deblan-icon"; + src: url('../fonts/deblan/deblan-icon.eot?v=5'); + src: url('../fonts/deblan/deblan-icon.woff?vmatrix5') format('woff'), + url('../fonts/deblan/deblan-icon.ttf?v=5') format('truetype'); + + font-style: normal; + font-weight: normal; + text-rendering: optimizeLegibility; +} + +@mixin make-pre { + padding: 10px; + border: 1px solid $color-code-border; + overflow: auto; + background: $color-code-background; + color: $color-code-text; + font-size: 13px; +} + +@mixin make-pre-code { + color: $color-code-text; + display: inline-block; + max-width: 100%; +} + +html, body { + scroll-behavior: smooth; +} + +$dicons: coffee server search project share contact list response twitter diaspora github code rss linkedin mastodon pixelfed gpg matrix; + +.deblan-icon { + font-family: 'deblan-icon'; + vertical-align: middle; + + @each $dicon in $dicons { + &-#{$dicon}::before { + content: "#{$dicon}"; + } + } +} + +.button .deblan-icon { + margin-right: 5px; + margin-left: -3px; +} + +li, p { + -webkit-hyphens: auto; + -moz-hyphens: auto; + -ms-hyphens: auto; + -o-hyphens: auto; + hyphens: auto; +} + +code mark { + color: inherit; + background: $color-code-mark-background; +} + +a:focus .logo-svg * { + fill: $color-grey; +} + +.page { + min-height: 100vh; + background: $color-white; + font-family: "MainFont"; +} + +.no-margin { + margin: 0; +} + +.navigation { + padding-top: 35px; + font-size: 15px; + + .deblan-icon { + margin-right: 9px; + } + + ul { + margin: 0; + padding: 0; + list-style: none; + } + + li { + margin: 0 0 3px 0; + padding: 0; + } + + a { + color: $color-white; + display: block; + padding: 12px 15px; + background: $color-navigation-item-background; + + &:hover, &.active, &:focus { + background: $color-navigation-item-background-active; + } + } +} + +.small-menu { + background: $color-small-menu-background; + padding: 5px; + position: fixed; + bottom: 0; + width: 100%; + z-index: 1000; + + .navigation { + padding-top: 0; + float: right; + + a { + margin-left: 2px; + } + + .deblan-icon { + margin-right: 0; + } + } + + img { + padding: 10px; + } +} + +.wide-menu { + width: 230px; + background: linear-gradient(to bottom, $color-wide-menu-background-from 0%, $color-wide-menu-background-to 500px); + padding: 34px 25px; + min-height: calc(100vh - 130px); + + .navigation { + li { + $radius: 8px; + + &:first-child a { + border-top-right-radius: $radius; + border-top-left-radius: $radius; + } + + &:last-child a { + border-bottom-right-radius: $radius; + border-bottom-left-radius: $radius; + } + } + } +} + +@keyframes gradientBackground { + 0% { + background-position: 0 0%; + } + 50% { + background-position: 0 75%; + } + 100% { + background-position: 0 0%; + } +} + +.header { + padding: 30px; + background: linear-gradient(180deg, $color-header-background-from, $color-header-background-to); + background-size: 100% 200%; + color: $color-white; + animation: gradientBackground 10s ease infinite; +} + +.fixed-menu { + position: fixed; + width: calc(230px - 50px); +} + +.content { + background: $color-content-background; + width: calc(100% - 230px); +} + +.header { + color: $color-header-text; + + p, h1, ul { + position: relative; + z-index: 1000; + } + + #particles { + width: 100%; + top: 0; + overflow: hidden; + position: absolute; + z-index: 50; + } + + .h1 { + font-weight: normal; + font-size: 24px; + } + + .h3 { + font-size: 17px; + font-weight: normal; + } + + a { + color: $color-header-text !important; + + &:focus { + color: $color-blue !important; + background: $color-white; + } + } +} + +.body { + padding: 25px 40px; + max-width: 900px; + + iframe { + margin-bottom: 25px; + border: none; + } + + img { + width: auto; + cursor: zoom-in; + } + + a, h1, h2, h3, h4, h5, p, ul { + color: $color-body-text; + } + + p a, ul a { + border-bottom: 1px dotted $color-body-link-border; + } + + blockquote { + border-left: 2px solid $color-blockquote-border; + margin: 10px; + padding: 5px 20px; + + p { + margin: 0; + padding: 0; + } + } + + code { + color: $code-color; + } +} + + +.content hr { + border: 0; + border-bottom: 1px dashed $color-hr-border; + background: $color-hr-background; +} + +.reviews { + padding: 0 40px; + max-width: 900px; + + hr:first-child { + margin-bottom: 30px; + } + + blockquote { + border-left: 2px solid $color-blockquote-border; + margin: 10px; + padding: 5px 20px; + + p { + margin: 0; + padding: 0; + } + } +} + +.review-body { + code { + color: $code-color; + } + + pre { + @include make-pre; + + code { + @include make-pre-code; + } + } +} + +@for $i from 1 through 6 { + .review.offset-#{$i} { + margin-left: 5% * $i - 1%; + width: 100% - ($i * 5%); + } +} + +.review { + width: 100%; + + .review-avatar { + display: inline-block; + width: 60px; + margin-right: 10px; + } + + .review-content { + display: inline-block; + width: calc(100% - 75px); + } + + ul { + margin: 0; + padding: 0 0 25px 0; + } + + + a svg { + display: inline-block; + height: 21px; + width: 15px; + vertical-align: middle; + margin-right: 3px; + } + + .review-header { + border-bottom: 1px solid $color-very-light-grey; + padding-bottom: 5px; + margin-bottom: 5px; + color: $color-dark-grey; + + .review-anchor-link { + color: $color-dark-grey; + } + } + + .review-body { + color: $color-dark-grey; + margin-bottom: 10px; + + ul { + margin: 0 !important; + padding: 0 0 10px 10px !important; + } + + li { + list-style: disc; + margin: 0 0 0 15px; + padding: 0; + } + } +} + +.body { + pre { + @include make-pre; + + code { + @include make-pre-code; + } + + &.with-title { + margin-top: 0; + border-top-right-radius: 0; + border-top-left-radius: 0; + } + } +} + +.code-title { + background: $color-code-title-background; + padding: 10px 15px; + color: $color-code-title-text; +} + +.body-content { + line-height: 30px; +} + +.quick-image { + img { + width: 100%; + height: 350px; + background-position: center center; + background: #f2f2f2 url('../images/quick-post-load.png') no-repeat center center; + border: 2px solid $color-very-light-grey; + border-bottom: 0; + cursor: pointer; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + } +} + +.quick-video { + .video-ratio { + border: 2px solid $color-very-light-grey; + border-bottom: 0; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + + iframe { + border-top-left-radius: 5px; + border-top-right-radius: 5px; + } + } +} + +.video-ratio { + position: relative; + width: 100%; + height: 0; + + iframe { + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + } +} + +.quick-body { + padding: 10px 10px 0 10px; + border: 2px solid $color-very-light-grey; + margin-top: -7px; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + + > p:last-child { + margin-bottom: 8px; + } +} + +.error_list { + color: $color-red !important; + padding: 4px 0 !important; +} + +.content-footer { + font-size: 14px; + background: darken($color-dark-grey, 10%); + text-align: center; + border-top: 1px dotted $color-very-dark-grey; + color: $color-white; + padding-top: 20px; + padding-bottom: 20px; + + a { + font-weight: bold; + color: $color-white; + } + + ul { + margin: 0; + padding: 0 0 10px 0; + } + + li { + padding: 0; + margin: 0; + + &::after { + content: '-'; + margin: 0 5px; + } + + &:last-child::after { + display: none; + } + } + + p { + margin-bottom: 0; + margin-top: 10px; + padding: 0; + } +} + +.links { + margin: 0; + padding: 5px 0 0 0; + text-align: center; +} + +.link { + margin: 0 1px; + display: inline-block; + + a { + display: block; + font-size: 13px; + padding-top: 1px; + padding-right: 1px; + height: 20px; + width: 20px; + overflow: hidden; + color: $color-white; + + span { + display: none; + } + } +} + +.links a:focus, .wide-menu a:focus .fixed { + border: 1px solid $color-white; +} + +$links: ( + twitter: #20b8ff, + rss: #fd9f13, + linkedin: #006699, + diaspora: #90b92e, + github: #8cc345, + code: #51d066, + mastodon: #2984d2, + pixelfed: #e72151, + matrix: #1a588a, + gpg: #42a73b +); + +@each $site, $bg in $links { + .link-#{$site} { + background-color: $bg; + + &:hover a, &:focus a, a:focus, a:hover { + color: $color-very-light-grey; + } + } +} + +.form .field input[type="checkbox"] + label, .form .field input[type="radio"] + label { + margin-left: 1rem; + margin-top: -2px; +} + +.preview-button { + cursor: pointer; + margin-left: 3px; +} + +.content-footer, .body, .review, form { + a:focus { + background: $color-blue; + color: $color-white; + } +} + +.button { + &:focus { + background: $color-very-dark-grey; + } +} + +.button, .btn, input[type="submit"], input[type="button"] { + background: $color-blue; +} + +.button:hover, .btn:hover, input:hover[type="submit"], input:hover[type="button"] { + background: $color-blue2; +} + +.button:focus, .btn:focus, input:focus[type="submit"], input:focus[type="button"] { + background: $color-blue2; +} + +.pager { + padding: 20px 20px 0 20px; + + .pager-page { + padding: 0 2px 2px 2px; + + &.active { + font-weight: bold; + } + } + + .button { + padding: 8px; + } +} + + +@keyframes bounceIn { + 0%{ + opacity: 0; + } + 50%{ + opacity: 0.9; + } + 80%{ + opacity: 1; + } + 100%{ + opacity: 1; + } +} + +@keyframes knmc { + 0%{ + left: -256px;; + } + 100%{ + left: 150vw; + } +} + +.knmc { + animation-name: knmc; + animation-duration: 15s; + animation-iteration-count: 1; +} + +.logo-svg { + animation-name: bounceIn; + animation-duration: 1s; +} + +.logo-svg { + animation-name: bounceIn; + animation-duration: 1s; +} + +@media screen and (max-width: 980px) { + .quick-image img { + height: 200px; + } +} + +@media screen and (max-width: 719px) { + .content-footer { + margin-bottom: 40px; + } +} + +@media screen and (max-width: 550px) { + .body { + padding: 10px 10px 20px 10px; + } + + .reviews { + padding: 10px 10px 20px 10px; + } +} + +@media screen and (max-width: 719px) { + .content { + width: 100%; + } + + .review { + .review-avatar { + display: none; + } + + .review-content { + width: 100%; + margin: 0; + } + } +} + +@media screen and (max-width: 380px) { + .navigation { + a { + padding: 7px 10px; + } + } + + .small-menu { + img { + width: 20px; + margin-top: 11px; + padding: 0; + } + } +} + +@media (prefers-color-scheme: dark) { + .content { + background: #35363f; + } + + .body, .review, #form { + a, h1, h2, h3, h4, h5, p, ul, li, label, th, td, + .h1, .h2, .h3, .h4, .h5, .review-body { + color: #fff; + } + } + + .review .review-header .review-anchor-link { + color: #ccc; + } + + .body p a, .body ul a { + border-bolor: #ccc; + } + + img.border { + border-color: #32333b; + } + + .form .field { + input[type=text], + input[type=email], + input[type=url], + textarea { + border-color: #32333b; + background: #86899f; + } + } + + .content hr { + background: none; + } + + .quick-body, + .quick-video .video-ratio, + .quick-image img, + .content hr, + .alert + { + border-color: #86899f; + } + + .code-title { + background: #0f1017; + } +} diff --git a/assets/css/app/alert.scss b/assets/css/app/alert.scss new file mode 100644 index 0000000..e4cb112 --- /dev/null +++ b/assets/css/app/alert.scss @@ -0,0 +1,34 @@ +.alert { + padding: 20px; + border: 1px solid #333; + + &-success { + border-color: #9db024; + background: #c6ff69; + color: #415f29; + } + + &-notice { + border-color: $color-blue; + background: #66e6ff; + color: #254e5f; + } + + &-warning { + border-color: #b07f29; + background: #ffd465; + color: #5f4520; + } + + &-error { + border-color: #b02e2a; + background: #ff6363; + color: #5f2521; + } + + &-notice-light { + border-color: $color-blue; + background: #d9fffc; + color: #254e5f; + } +} diff --git a/assets/css/app/config.scss b/assets/css/app/config.scss new file mode 100644 index 0000000..597dee8 --- /dev/null +++ b/assets/css/app/config.scss @@ -0,0 +1,41 @@ +/*$wire-debug: false;*/ + +$color-white: #fff; +$color-very-light-grey: #e0e0e0; +$color-light-blue: #b9d4e3; +$color-blue: #0269a0; +$color-blue2: #48a8ce; +$color-grey: #ccc; +$color-dark-grey: #454651; +$color-very-dark-grey: #333; +$color-red: #b02e2a; + +$code-color: $color-red; + +$color-navigation-item-background: darken($color-dark-grey, 8%); +$color-navigation-item-background-active: lighten($color-dark-grey, 10%); + +$color-small-menu-background: $color-dark-grey; +$color-wide-menu-background-from: $color-dark-grey; +$color-wide-menu-background-to: darken($color-dark-grey, 5%); + +$color-header-background-from: $color-blue2; +$color-header-background-to: $color-blue; +$color-header-text: $color-white; + +$color-body-text: $color-dark-grey; +$color-body-link-border: $color-blue; +$color-blockquote-border: $color-grey; +$color-hr-border: $color-grey; +$color-hr-background: $color-grey; + +$color-content-background: $color-white; + +/* GIST compatiblity */ +$color-code-border: #3c3c3c; +$color-code-background: #222222; +$color-code-text: #f8f8f2; +$color-code-mark-background: $color-light-blue; +$color-code-title-background: #3c3c3c; +$color-code-title-text: #999; +/* --- */ diff --git a/assets/css/app/prism.scss b/assets/css/app/prism.scss new file mode 100644 index 0000000..1def76e --- /dev/null +++ b/assets/css/app/prism.scss @@ -0,0 +1,126 @@ +/* PrismJS 1.22.0 +https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript+bash+markdown+markup-templating+nginx+php+python+sql+yaml&plugins=keep-markup */ +/** + * okaidia theme for JavaScript, CSS and HTML + * Loosely based on Monokai textmate theme by http://www.monokai.nl/ + * @author ocodia + */ + +code[class*="language-"], +pre[class*="language-"] { + color: #f8f8f2; + background: none; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; + border-radius: 0.3em; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #272822; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #8292a2; +} + +.token.punctuation { + color: #f8f8f2; +} + +.token.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.constant, +.token.symbol, +.token.deleted { + color: #f92672; +} + +.token.boolean, +.token.number { + color: #ae81ff; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #a6e22e; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string, +.token.variable { + color: #f8f8f2; +} + +.token.atrule, +.token.attr-value, +.token.function, +.token.class-name { + color: #e6db74; +} + +.token.keyword { + color: #66d9ef; +} + +.token.regex, +.token.important { + color: #fd971f; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + diff --git a/assets/fonts/deblan/deblan-icon.eot b/assets/fonts/deblan/deblan-icon.eot new file mode 100644 index 0000000..247de67 Binary files /dev/null and b/assets/fonts/deblan/deblan-icon.eot differ diff --git a/assets/fonts/deblan/deblan-icon.ttf b/assets/fonts/deblan/deblan-icon.ttf new file mode 100644 index 0000000..809f7fd Binary files /dev/null and b/assets/fonts/deblan/deblan-icon.ttf differ diff --git a/assets/fonts/deblan/deblan-icon.woff b/assets/fonts/deblan/deblan-icon.woff new file mode 100644 index 0000000..6d5687a Binary files /dev/null and b/assets/fonts/deblan/deblan-icon.woff differ diff --git a/assets/fonts/deblan/src/deblan-icon.svg b/assets/fonts/deblan/src/deblan-icon.svg new file mode 100644 index 0000000..70ec511 --- /dev/null +++ b/assets/fonts/deblan/src/deblan-icon.svg @@ -0,0 +1,422 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + beer + + + + list-ul + + + + Search + + + + HDD + + + + magic + + + + Alternate Share + + + + Envelope + + + + Reply + + + + + + + + GitHub + + + + Alternate GitHub + + + + LinkedIn In + + + + LinkedIn + + + + Twitter Square + + + + Code + + + + WiFi + + + + key + + + + + + + + + + + + + Reply + + + + + + + + + + + + + + diff --git a/assets/fonts/ubuntu-font-family-0.83/CONTRIBUTING.txt b/assets/fonts/ubuntu-font-family-0.83/CONTRIBUTING.txt new file mode 100644 index 0000000..15bdc0c --- /dev/null +++ b/assets/fonts/ubuntu-font-family-0.83/CONTRIBUTING.txt @@ -0,0 +1,21 @@ +The Ubuntu Font Family is very long-term endeavour, and the first time +that a professionally-designed font has been funded specifically with +the intent of being an on-going community expanded project: + + http://font.ubuntu.com/ + +Development of the Ubuntu Font Family is undertaken on Launchpad: + + http://launchpad.net/ubuntu-font-family/ + +and this is where milestones, bug management and releases are handled. + +Contributions are welcomed. Your work will be used on millions of +computers every single day! Following the initial bootstrapping of +Latin, Cyrillic, Greek, Arabic and Hebrew expansion will be undertaken +by font designers from the font design and Ubuntu communities. + +To ensure that the Ubuntu Font Family can be re-licensed to future +widely-used libre font licences, copyright assignment is being required: + + https://launchpad.net/~uff-contributors diff --git a/assets/fonts/ubuntu-font-family-0.83/FONTLOG.txt b/assets/fonts/ubuntu-font-family-0.83/FONTLOG.txt new file mode 100644 index 0000000..83022be --- /dev/null +++ b/assets/fonts/ubuntu-font-family-0.83/FONTLOG.txt @@ -0,0 +1,292 @@ +This is the FONTLOG file for the Ubuntu Font Family and attempts to follow +the recommendations at: http://scripts.sil.org/OFL-FAQ_web#43cecb44 + + +Overview + +The new Ubuntu Font Family was started to enable the personality of +Ubuntu to be seen and felt in every menu, button and dialog. +The typeface is sans-serif, uses OpenType features and is manually +hinted for clarity on desktop and mobile computing screens. + +The scope of the Ubuntu Font Family includes all the languages used by +the various Ubuntu users around the world in tune with Ubuntu's +philosophy which states that every user should be able to use their +software in the language of their choice. So the Ubuntu Font Family +project will be extended to cover many more written languages. + + +History + +The Ubuntu Font Family has been creating during 2010 and 2011. As of +September 2011 coverage is provided for Latin, Cyrillic and Greek across +Regular, Italic, Bold and Bold-Italic. Further work was uptaken during +2015. + + +ChangeLog + +2015-08-21 (Paul Sladen) Ubuntu Font Family version 0.83 + + Note: This release was created by binary patching from the v0.80 + release using the scripts in 'sources/patch-0.80-0.83/' to rebuild + the necessary tables. The release selectively updates only those + proportional .ttf font files exhibiting the bug below bug number; + the Ubuntu Mono monospace font files remain unchanged, being the + original version 0.80 ones. + + [Marc Foley] + * [Engineering] Fixed wrong characters appear in some mac apps. (LP: #1334363) + + +2011-09-22 (Paul Sladen) Ubuntu Font Family version 0.80 + + [Vincent Connare/Dalton Maag] + * Wish for addition of a monospaced member to the family (LP: #640382) + * Mono: No hinting yet - Ubuntu Beta Mono font looks jagged in + Netbeans and terrible with ClearType (LP: #820493) + * Emacs: choosing normal monospace font in Emacs but gives bold-italic + (LP: #791076) + * PUA: ensure that Ubuntu Circle of Friends logo is full size: (LP: #853855) + + U+E0FF becomes large size in proportionals, remains small width in + monospaces + + U+F0FF becomes small size (proportionals only) + + U+F200 is full ubuntu logomark (proportionals only) + + [Paul Sladen] + * Monospace: Patch Family Name to be "Ubuntu Mono" + * Monospace: Patch U+EFFD version debugging glyph to be '0.8' + + [Cody Boisclair] + * Monospace: Force .null HDMX advance to 500 + * Monospace: Remap ASCII box-drawing characters (LP: #788757) + + [Júlio Reis] + * Date corrections to 'FONTLOG' (LP: #836595) + +2011-03-08 (Paul Sladen) Ubuntu Font Family version 0.71.2 + + * (Production) Adjust Medium WeightClass to 500 (Md, MdIt) (LP: #730912) + +2011-03-07 (Paul Sladen) Ubuntu Font Family version 0.71.1 + + * (Design) Add Capitalised version of glyphs and kern. (Lt, LtIt, + Md, MdIt) DM (LP: #677446) + * (Design) Re-space and tighen Regular and Italic by amount specified + by Mark Shuttleworth (minus 4 FUnits). (Rg, It) (LP: #677149) + * (Design) Design: Latin (U+0192) made straight more like l/c f with + tail (LP: #670768) + * (Design) (U+01B3) should have hook on right, as the lowercase + (U+01B4) (LP: #681026) + * (Design) Tail of Light Italic germandbls, longs and lowercase 'f' + to match Italic/BoldItalic (LP: #623925) + * (Production) Update feature (Lt, LtIt, Md, MdIt). DM + (LP: #676538, #676539) + * (Production) Remove Bulgarian locl feature for Italics. (LP: #708578) + * (Production) Update Description information with new string: + "The Ubuntu Font Family are libre fonts funded by Canonical Ltd + on behalf of the Ubuntu project. The font design work and + technical implementation is being undertaken by Dalton Maag. The + typeface is sans-serif, uses OpenType features and is manually + hinted for clarity on desktop and mobile computing screens. The + scope of the Ubuntu Font Family includes all the languages used + by the various Ubuntu users around the world in tune with + Ubuntu's philosophy which states that every user should be able + to use their software in the language of their choice. The + project is ongoing, and we expect the family will be extended to + cover many written languages in the coming years." + (Rg, It, Bd, BdIt, Lt, LtIt, Md, MdIt) (LP: #690590) + * (Production) Pixel per em indicator added at U+F000 (Lt, LtIt, Md, + MdIt) (LP: #615787) + * (Production) Version number indicator added at U+EFFD (Lt, LtIt, Md, + MdIt) (LP: #640623) + * (Production) fstype bit set to 0 - Editable (Lt, LtIt, Md, MdIt) + (LP: #648406) + * (Production) Localisation of name table has been removed because + of problems with Mac OS/X interpretation of localisation. DM + (LP: #730785) + * (Hinting) Regular '?' dot non-circular (has incorrect control + value). (LP: #654336) + * (Hinting) Too much space after latin capital 'G' in 13pt + regular. Now reduced. (LP: #683437) + * (Hinting) Balance Indian Rupee at 18,19pt (LP: #662177) + * (Hinting) Make Regular '£' less ambiguous at 13-15 ppm (LP: #685562) + * (Hinting) Regular capital 'W' made symmetrical at 31 ppem (LP: #686168) + +2010-12-14 (Paul Sladen) Ubuntu Font Family version 0.70.1 + + Packaging, rebuilt from '2010-12-08 UbuntuFontsSourceFiles_070.zip': + * (Midstream) Fstype bit != 0 (LP: #648406) + * (Midstream) Add unit test to validate fstype bits (LP: #648406) + * (Midstream) Add unit test to validate licence + +2010-12-14 (Paul Sladen) Ubuntu Font Family version 0.70 + + Release notes 0.70: + * (Design) Add Capitalised version of glyphs and kern. (Rg, It, Bd, + BdIt) DM (LP: #676538, #677446) + * (Design) Give acute and grave a slight upright move to more match + the Hungarian double acute angle. (Rg, It, Bd, BdIt) (LP: #656647) + * (Design) Shift Bold Italic accent glyphs to be consistent with the + Italic. (BdIt only) DM (LP: #677449) + * (Design) Check spacing and kerning of dcaron, lcaron and + tcaron. (Rg, It, Bd, BdIt) (LP: #664722) + * (Design) Add positive kerning to () {} [] to open out the + combinations so they are less like a closed box. (Rg, It, Bd, + BdIt) (LP: #671228) + * (Design) Change design of acute.asc and check highest points (Bd + and BdIt only) DM + * (Production) Update feature. DM (LP: #676538, #676539) + * (Production) Remove Romanian locl feature. (Rg, It, Bd, BdIt) + (LP: #635615) + * (Production) Update Copyright information with new + strings. "Copyright 2010 Canonical Ltd. Licensed under the Ubuntu + Font Licence 1.0" Trademark string "Ubuntu and Canonical are + registered trademarks of Canonical Ltd." (Rg, It, Bd, BdIt) DM + (LP: #677450) + * (Design) Check aligning of hyphen, math signs em, en, check braces + and other brackets. 16/11 (LP: #676465) + * (Production) Pixel per em indicator added at U+F000 (Rg, It, Bd, + BdIt) (LP: #615787) + * (Production) Version number indicator added at U+EFFD (Rg, It, Bd, + BdIt) (LP: #640623) + * (Production) fstype bit set to 0 - Editable (Rg, It, Bd, BdIt) + (LP: #648406) + +2010-10-05 (Paul Sladen) Ubuntu Font Family version 0.69 + + [Dalton Maag] + * Italic, + - Hinting on lowercase Italic l amended 19ppm (LP: #632451) + - Hinting on lowercase Italic u amended 12ppm (LP: #626376) + + * Regular, Italic, Bold, BoldItalic + - New Rupee Sign added @ U+20B9 (LP: #645987) + - Ubuntu Roundel added @ U+E0FF (LP: #651606) + + [Paul Sladen] + * All + - Removed "!ubu" GSUB.calt ligature for U+E0FF (LP: #651606) + + +Acknowledgements + +If you make modifications be sure to add your name (N), email (E), +web-address (if you have one) (W) and description (D). This list is in +alphabetical order. + +N: Ryan Abdullah +W: http://www.rayan.de/ +D: Arabic calligraphy and design in collaboration with Dalton Maag +D: Arabic testing + +N: Cody Boisclair +D: Monospace low-level debugging and patching ('fixboxdrawing-ft.py') + +N: Amélie Bonet +W: http://ameliebonet.com/ +D: Type design with Dalton Maag, particularly Ubuntu Mono and Ubuntu Condensed + +N: Jason Campbell +W: http://www.campbellgraphics.com/design/fonts.shtml +D: Monospace hinting (first phase) at Dalton Maag + +N: Pilar Cano +W: http://www.pilarcano.com/ +D: Hebrew realisation with Dalton Maag + +N: Fernando Caro +D: Type design with Dalton Maag, particularly Ubuntu Condensed + +N: Ron Carpenter +W: http://www.daltonmaag.com/ +D: Type design with Dalton Maag +D: Arabic realisation in collaboration with Ryan Abdullah + +N: Vincent Connare +W: http://www.connare.com/ +D: Type design, and engineering with Dalton Maag +D: Monospace hinting (second phase) at Dalton Maag + +N: Dave Crossland +E: dave@understandingfonts.com +W: http://understandingfonts.com/ +D: Documentation and libre licensing guidance +D: Google Webfont integration at Google + +N: Steve Edwards +W: http://www.madebymake.com/ +D: font.ubuntu.com revamp implementation with Canonical Web Team + +N: Iain Farrell +W: http://www.flickr.com/photos/iain +D: Ubuntu Font Family delivery for the Ubuntu UX team at Canonical + +N: Marc Foley +W: http://www.marcfoley.co/ +D: Font Engineer at Dalton Maag for the 2015 updates + +N: Shiraaz Gabru +W: http://www.daltonmaag.com/ +D: Ubuntu Font Family project management at Dalton Maag + +N: Marcus Haslam +W: http://design.canonical.com/author/marcus-haslam/ +D: Creative inspiration + +N: Ben Laenen +D: Inspiration behind the pixels-per-em (PPEM) readout debugging glyph at U+F000 + (for this font the concept was re-implemented from scratch by Dalton-Maag) + +N: Bruno Maag +W: http://www.daltonmaag.com/ +D: Stylistic direction of the Ubuntu Font Family, as head of Dalton Maag + +N: Ivanka Majic +W: http://www.ivankamajic.com/ +D: Guiding the UX team and Cyrillic feedback + +N: David Marshall +W: http://www.daltonmaag.com/ +D: Technical guidance and administration at Dalton Maag + +N: Malcolm Wooden +W: http://www.daltonmaag.com/ +D: Font Engineering at Dalton Maag + +N: Lukas Paltram +W: http://www.daltonmaag.com/ +D: Type design with Dalton Maag + +N: Júlio Reis +D: Date fixes to the documentation + +N: Rodrigo Rivas +D: Indian Rupee Sign glyph + +N: Mark Shuttleworth +E: mark@ubuntu.com +W: http://www.markshuttleworth.com/ +D: Executive quality-control and funding + +N: Paul Sladen +E: ubuntu@paul.sladen.org +W: http://www.paul.sladen.org/ +D: Bug triaging, packaging at Ubuntu and Canonical + +N: Nicolas Spalinger +W: http://planet.open-fonts.org +D: Continuous guidance on libre/open font licensing, best practises in source + tree layout, release and packaging (pkg-fonts Debian team) + +N: Kenneth Wimer +D: Initial PPA packaging + +* Canonical Ltd is the primary commercial sponsor of the Ubuntu and + Kubuntu operating systems +* Dalton Maag are a custom type foundry headed by Bruno Maag + +For further documentation, information on contributors, source code +downloads and those involved with the Ubuntu Font Family, visit: + + http://font.ubuntu.com/ diff --git a/assets/fonts/ubuntu-font-family-0.83/LICENCE-FAQ.txt b/assets/fonts/ubuntu-font-family-0.83/LICENCE-FAQ.txt new file mode 100644 index 0000000..776a25e --- /dev/null +++ b/assets/fonts/ubuntu-font-family-0.83/LICENCE-FAQ.txt @@ -0,0 +1,177 @@ + Ubuntu Font Family Licensing FAQ + + Stylistic Foundations + + The Ubuntu Font Family is the first time that a libre typeface has been + designed professionally and explicitly with the intent of developing a + public and long-term community-based development process. + + When developing an open project, it is generally necessary to have firm + foundations: a font needs to maintain harmony within itself even across + many type designers and writing systems. For the [1]Ubuntu Font Family, + the process has been guided with the type foundry Dalton Maag setting + the project up with firm stylistic foundation covering several + left-to-right scripts: Latin, Greek and Cyrillic; and right-to-left + scripts: Arabic and Hebrew (due in 2011). + + With this starting point the community will, under the supervision of + [2]Canonical and [3]Dalton Maag, be able to build on the existing font + sources to expand their character coverage. Ultimately everybody will + be able to use the Ubuntu Font Family in their own written languages + across the whole of Unicode (and this will take some time!). + + Licensing + + The licence chosen by any free software project is one of the + foundational decisions that sets out how derivatives and contributions + can occur, and in turn what kind of community will form around the + project. + + Using a licence that is compatible with other popular licences is a + powerful constraint because of the [4]network effects: the freedom to + share improvements between projects allows free software to reach + high-quality over time. Licence-proliferation leads to many + incompatible licences, undermining the network effect, the freedom to + share and ultimately making the libre movement that Ubuntu is a part of + less effective. For all kinds of software, writing a new licence is not + to be taken lightly and is a choice that needs to be thoroughly + justified if this path is taken. + + Today it is not clear to Canonical what the best licence for a font + project like the Ubuntu Font Family is: one that starts life designed + by professionals and continues with the full range of community + development, from highly commercial work in new directions to curious + beginners' experimental contributions. The fast and steady pace of the + Ubuntu release cycle means that an interim libre licence has been + necessary to enable the consideration of the font family as part of + Ubuntu 10.10 operating system release. + + Before taking any decision on licensing, Canonical as sponsor and + backer of the project has reviewed the many existing licenses used for + libre/open fonts and engaged the stewards of the most popular licenses + in detailed discussions. The current interim licence is the first step + in progressing the state-of-the-art in licensing for libre/open font + development. + + The public discussion must now involve everyone in the (comparatively + new) area of the libre/open font community; including font users, + software freedom advocates, open source supporters and existing libre + font developers. Most importantly, the minds and wishes of professional + type designers considering entering the free software business + community must be taken on board. + + Conversations and discussion has taken place, privately, with + individuals from the following groups (generally speaking personally on + behalf of themselves, rather than their affiliations): + * [5]SIL International + * [6]Open Font Library + * [7]Software Freedom Law Center + * [8]Google Font API + + Document embedding + + One issue highlighted early on in the survey of existing font licences + is that of document embedding. Almost all font licences, both free and + unfree, permit embedding a font into a document to a certain degree. + Embedding a font with other works that make up a document creates a + "combined work" and copyleft would normally require the whole document + to be distributed under the terms of the font licence. As beautiful as + the font might be, such a licence makes a font too restrictive for + useful general purpose digital publishing. + + The situation is not entirely unique to fonts and is encountered also + with tools such as GNU Bison: a vanilla GNU GPL licence would require + anything generated with Bison to be made available under the terms of + the GPL as well. To avoid this, Bison is [9]published with an + additional permission to the GPL which allows the output of Bison to be + made available under any licence. + + The conflict between licensing of fonts and licensing of documents, is + addressed in two popular libre font licences, the SIL OFL and GNU GPL: + * [10]SIL Open Font Licence: When OFL fonts are embedded in a + document, the OFL's terms do not apply to that document. (See + [11]OFL-FAQ for details. + * [12]GPL Font Exception: The situation is resolved by granting an + additional permission to allow documents to not be covered by the + GPL. (The exception is being reviewed). + + The Ubuntu Font Family must also resolve this conflict, ensuring that + if the font is embedded and then extracted it is once again clearly + under the terms of its libre licence. + + Long-term licensing + + Those individuals involved, especially from Ubuntu and Canonical, are + interested in finding a long-term libre licence that finds broad favour + across the whole libre/open font community. The deliberation during the + past months has been on how to licence the Ubuntu Font Family in the + short-term, while knowingly encouraging everyone to pursue a long-term + goal. + * [13]Copyright assignment will be required so that the Ubuntu Font + Family's licensing can be progressively expanded to one (or more) + licences, as best practice continues to evolve within the + libre/open font community. + * Canonical will support and fund legal work on libre font licensing. + It is recognised that the cost and time commitments required are + likely to be significant. We invite other capable parties to join + in supporting this activity. + + The GPL version 3 (GPLv3) will be used for Ubuntu Font Family build + scripts and the CC-BY-SA for associated documentation and non-font + content: all items which do not end up embedded in general works and + documents. + +Ubuntu Font Licence + + For the short-term only, the initial licence is the [14]Ubuntu Font + License (UFL). This is loosely inspired from the work on the SIL + OFL 1.1, and seeks to clarify the issues that arose during discussions + and legal review, from the perspective of the backers, Canonical Ltd. + Those already using established licensing models such as the GPL, OFL + or Creative Commons licensing should have no worries about continuing + to use them. The Ubuntu Font Licence (UFL) and the SIL Open Font + Licence (SIL OFL) are not identical and should not be confused with + each other. Please read the terms precisely. The UFL is only intended + as an interim license, and the overriding aim is to support the + creation of a more suitable and generic libre font licence. As soon as + such a licence is developed, the Ubuntu Font Family will migrate to + it—made possible by copyright assignment in the interium. Between the + OFL 1.1, and the UFL 1.0, the following changes are made to produce the + Ubuntu Font Licence: + * Clarification: + + 1. Document embedding (see [15]embedding section above). + 2. Apply at point of distribution, instead of receipt + 3. Author vs. copyright holder disambiguation (type designers are + authors, with the copyright holder normally being the funder) + 4. Define "Propagate" (for internationalisation, similar to the GPLv3) + 5. Define "Substantially Changed" + 6. Trademarks are explicitly not transferred + 7. Refine renaming requirement + + Streamlining: + 8. Remove "not to be sold separately" clause + 9. Remove "Reserved Font Name(s)" declaration + + A visual demonstration of how these points were implemented can be + found in the accompanying coloured diff between SIL OFL 1.1 and the + Ubuntu Font Licence 1.0: [16]ofl-1.1-ufl-1.0.diff.html + +References + + 1. http://font.ubuntu.com/ + 2. http://www.canonical.com/ + 3. http://www.daltonmaag.com/ + 4. http://en.wikipedia.org/wiki/Network_effect + 5. http://scripts.sil.org/ + 6. http://openfontlibrary.org/ + 7. http://www.softwarefreedom.org/ + 8. http://code.google.com/webfonts + 9. http://www.gnu.org/licenses/gpl-faq.html#CanIUseGPLToolsForNF + 10. http://scripts.sil.org/OFL_web + 11. http://scripts.sil.org/OFL-FAQ_web + 12. http://www.gnu.org/licenses/gpl-faq.html#FontException + 13. https://launchpad.net/~uff-contributors + 14. http://font.ubuntu.com/ufl/ubuntu-font-licence-1.0.txt + 15. http://font.ubuntu.com/ufl/FAQ.html#embedding + 16. http://font.ubuntu.com/ufl/ofl-1.1-ufl-1.0.diff.html diff --git a/assets/fonts/ubuntu-font-family-0.83/LICENCE.txt b/assets/fonts/ubuntu-font-family-0.83/LICENCE.txt new file mode 100644 index 0000000..ae78a8f --- /dev/null +++ b/assets/fonts/ubuntu-font-family-0.83/LICENCE.txt @@ -0,0 +1,96 @@ +------------------------------- +UBUNTU FONT LICENCE Version 1.0 +------------------------------- + +PREAMBLE +This licence allows the licensed fonts to be used, studied, modified and +redistributed freely. The fonts, including any derivative works, can be +bundled, embedded, and redistributed provided the terms of this licence +are met. The fonts and derivatives, however, cannot be released under +any other licence. The requirement for fonts to remain under this +licence does not require any document created using the fonts or their +derivatives to be published under this licence, as long as the primary +purpose of the document is not to be a vehicle for the distribution of +the fonts. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this licence and clearly marked as such. This may +include source files, build scripts and documentation. + +"Original Version" refers to the collection of Font Software components +as received under this licence. + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to +a new environment. + +"Copyright Holder(s)" refers to all individuals and companies who have a +copyright ownership of the Font Software. + +"Substantially Changed" refers to Modified Versions which can be easily +identified as dissimilar to the Font Software by users of the Font +Software comparing the Original Version with the Modified Version. + +To "Propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification and with or without charging +a redistribution fee), making available to the public, and in some +countries other activities as well. + +PERMISSION & CONDITIONS +This licence does not grant any rights under trademark law and all such +rights are reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of the Font Software, to propagate the Font Software, subject to +the below conditions: + +1) Each copy of the Font Software must contain the above copyright +notice and this licence. These can be included either as stand-alone +text files, human-readable headers or in the appropriate machine- +readable metadata fields within text or binary files as long as those +fields can be easily viewed by the user. + +2) The font name complies with the following: +(a) The Original Version must retain its name, unmodified. +(b) Modified Versions which are Substantially Changed must be renamed to +avoid use of the name of the Original Version or similar names entirely. +(c) Modified Versions which are not Substantially Changed must be +renamed to both (i) retain the name of the Original Version and (ii) add +additional naming elements to distinguish the Modified Version from the +Original Version. The name of such Modified Versions must be the name of +the Original Version, with "derivative X" where X represents the name of +the new work, appended to that name. + +3) The name(s) of the Copyright Holder(s) and any contributor to the +Font Software shall not be used to promote, endorse or advertise any +Modified Version, except (i) as required by this licence, (ii) to +acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with +their explicit written permission. + +4) The Font Software, modified or unmodified, in part or in whole, must +be distributed entirely under this licence, and must not be distributed +under any other licence. The requirement for fonts to remain under this +licence does not affect any document created using the Font Software, +except any version of the Font Software extracted from a document +created using the Font Software may only be distributed under this +licence. + +TERMINATION +This licence becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER +DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/fonts/ubuntu-font-family-0.83/README.txt b/assets/fonts/ubuntu-font-family-0.83/README.txt new file mode 100644 index 0000000..5602821 --- /dev/null +++ b/assets/fonts/ubuntu-font-family-0.83/README.txt @@ -0,0 +1,16 @@ + ---------------------- + Ubuntu Font Family + ====================== + +The Ubuntu Font Family are a set of matching new libre/open fonts in +development during 2010--2011. And with further expansion work and +bug fixing during 2015. The development is being funded by +Canonical Ltd on behalf the wider Free Software community and the +Ubuntu project. The technical font design work and implementation is +being undertaken by Dalton Maag. + +Both the final font Truetype/OpenType files and the design files used +to produce the font family are distributed under an open licence and +you are expressly encouraged to experiment, modify, share and improve. + + http://font.ubuntu.com/ diff --git a/assets/fonts/ubuntu-font-family-0.83/TRADEMARKS.txt b/assets/fonts/ubuntu-font-family-0.83/TRADEMARKS.txt new file mode 100644 index 0000000..d34265b --- /dev/null +++ b/assets/fonts/ubuntu-font-family-0.83/TRADEMARKS.txt @@ -0,0 +1,4 @@ +Ubuntu and Canonical are registered trademarks of Canonical Ltd. + +The licence accompanying these works does not grant any rights +under trademark law and all such rights are reserved. diff --git a/assets/fonts/ubuntu-font-family-0.83/Ubuntu-B.ttf b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-B.ttf new file mode 100644 index 0000000..b173da2 Binary files /dev/null and b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-B.ttf differ diff --git a/assets/fonts/ubuntu-font-family-0.83/Ubuntu-BI.ttf b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-BI.ttf new file mode 100644 index 0000000..72a5a99 Binary files /dev/null and b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-BI.ttf differ diff --git a/assets/fonts/ubuntu-font-family-0.83/Ubuntu-C.ttf b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-C.ttf new file mode 100644 index 0000000..602a3ee Binary files /dev/null and b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-C.ttf differ diff --git a/assets/fonts/ubuntu-font-family-0.83/Ubuntu-L.ttf b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-L.ttf new file mode 100644 index 0000000..ed0f5bc Binary files /dev/null and b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-L.ttf differ diff --git a/assets/fonts/ubuntu-font-family-0.83/Ubuntu-LI.ttf b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-LI.ttf new file mode 100644 index 0000000..c6cec55 Binary files /dev/null and b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-LI.ttf differ diff --git a/assets/fonts/ubuntu-font-family-0.83/Ubuntu-M.ttf b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-M.ttf new file mode 100644 index 0000000..ca9c03a Binary files /dev/null and b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-M.ttf differ diff --git a/assets/fonts/ubuntu-font-family-0.83/Ubuntu-MI.ttf b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-MI.ttf new file mode 100644 index 0000000..e8d186c Binary files /dev/null and b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-MI.ttf differ diff --git a/assets/fonts/ubuntu-font-family-0.83/Ubuntu-R.ttf b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-R.ttf new file mode 100644 index 0000000..d748728 Binary files /dev/null and b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-R.ttf differ diff --git a/assets/fonts/ubuntu-font-family-0.83/Ubuntu-RI.ttf b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-RI.ttf new file mode 100644 index 0000000..4f2d2bc Binary files /dev/null and b/assets/fonts/ubuntu-font-family-0.83/Ubuntu-RI.ttf differ diff --git a/assets/fonts/ubuntu-font-family-0.83/UbuntuMono-B.ttf b/assets/fonts/ubuntu-font-family-0.83/UbuntuMono-B.ttf new file mode 100644 index 0000000..7bd6665 Binary files /dev/null and b/assets/fonts/ubuntu-font-family-0.83/UbuntuMono-B.ttf differ diff --git a/assets/fonts/ubuntu-font-family-0.83/UbuntuMono-BI.ttf b/assets/fonts/ubuntu-font-family-0.83/UbuntuMono-BI.ttf new file mode 100644 index 0000000..6c5b8ba Binary files /dev/null and b/assets/fonts/ubuntu-font-family-0.83/UbuntuMono-BI.ttf differ diff --git a/assets/fonts/ubuntu-font-family-0.83/UbuntuMono-R.ttf b/assets/fonts/ubuntu-font-family-0.83/UbuntuMono-R.ttf new file mode 100644 index 0000000..fdd309d Binary files /dev/null and b/assets/fonts/ubuntu-font-family-0.83/UbuntuMono-R.ttf differ diff --git a/assets/fonts/ubuntu-font-family-0.83/UbuntuMono-RI.ttf b/assets/fonts/ubuntu-font-family-0.83/UbuntuMono-RI.ttf new file mode 100644 index 0000000..18f81a2 Binary files /dev/null and b/assets/fonts/ubuntu-font-family-0.83/UbuntuMono-RI.ttf differ diff --git a/assets/fonts/ubuntu-font-family-0.83/copyright.txt b/assets/fonts/ubuntu-font-family-0.83/copyright.txt new file mode 100644 index 0000000..7734070 --- /dev/null +++ b/assets/fonts/ubuntu-font-family-0.83/copyright.txt @@ -0,0 +1,5 @@ +Copyright 2010,2011 Canonical Ltd. + +This Font Software is licensed under the Ubuntu Font Licence, Version +1.0. https://launchpad.net/ubuntu-font-licence + diff --git a/assets/fonts/ubuntu/ubuntu-light.woff b/assets/fonts/ubuntu/ubuntu-light.woff new file mode 100644 index 0000000..a156998 Binary files /dev/null and b/assets/fonts/ubuntu/ubuntu-light.woff differ diff --git a/assets/images/links.png b/assets/images/links.png new file mode 100644 index 0000000..e7fab29 Binary files /dev/null and b/assets/images/links.png differ diff --git a/assets/images/logo-small.png b/assets/images/logo-small.png new file mode 100644 index 0000000..822dbfb Binary files /dev/null and b/assets/images/logo-small.png differ diff --git a/assets/images/logo.png b/assets/images/logo.png index 53eae46..bab1217 100644 Binary files a/assets/images/logo.png and b/assets/images/logo.png differ diff --git a/assets/images/mario.gif b/assets/images/mario.gif new file mode 100644 index 0000000..acd0f2a Binary files /dev/null and b/assets/images/mario.gif differ diff --git a/assets/images/px.png b/assets/images/px.png new file mode 100644 index 0000000..e5b5761 Binary files /dev/null and b/assets/images/px.png differ diff --git a/assets/images/quick-post-load.png b/assets/images/quick-post-load.png new file mode 100644 index 0000000..a345e42 Binary files /dev/null and b/assets/images/quick-post-load.png differ diff --git a/assets/images/sprite.svg b/assets/images/sprite.svg new file mode 100644 index 0000000..dbcd6cb --- /dev/null +++ b/assets/images/sprite.svg @@ -0,0 +1,7799 @@ + + +642 + +1197 + + + + 1196 + + + + + + + + + + + + + + + + + + 670 + +644 + + + + 86 + + + + + + + + + + +648 + + + + 1155 + + + + + + + + + + 1131 + + + + + + + + + + + + + + + + 1129 + + + + + + + + + + + + + + + + 1131 + + + + + + + + + + + + + + + + 1130 + + + + + + + + + + + + + +831 + + + + 682 + + + + + + + + + + Android + + + + + + + + + + + + +1185 + + 100 + + + + 900 + + + + + + + + + + 884 + + + + + + + + + + 1181 + + + + + + + + + + 950 + + + + + + + + + + + + + 973 + + + + + + + + + + + + 1190 + + 1182 + + + + 1068 + + + + + + + + + + 1231 + + + + + + + + + + 1112 + + + + + + + + + + + + + + + 777 + + + + + + + + + + 1225 + + + + + + + + + + 104 + + + + + + + + + + + + + + + + 802 + + + + + + + + + + + + + 124 + + + + + + + + + + + + + 123 + + + + + + + + + + + + + 125 + + + + + + + + + + + + + 126 + + + + + + + + + + + + + 1213 + + + + + + + + + + 1214 + + + + + + + + + + 1178 + + + + + + + + + + 757 + + + + + + + + + + + + + + 1212 + + + + + + + + + + 1210 + + + + + + + + + + 1209 + + + + + + + + + + 741 + + + + + + + + + + + + + 740 + + + + + + + + + + + + + 1211 + + + + + + + + + + 1161 + + + + + + + + + + 1175 + + + + + + + + + + 1179 + + + + + + + + + + 1158 + + + + + + + + + + 1176 + + + + + + + + + + 1180 + + + + + + + + + + 1159 + + + + + + + + + + 1160 + + + + + + + + + + Arrow-three-way + + + + + + + + + + + + + + 883 + + + + + + + + + + + + + + 667 + + + + + + + + + + + + + + 951 + + + + + + + + + + + + + 1116 + + + + + + + + + + + + + 1154 + + + + + + + + + + 1103 + + + + + + + + + + 138 + + + + + + + + + + + + + Arrow-two-way + + + + + + + + + + 776 + + + + + + + + + + 1191 + + + + + + + + + + 1029 + + + + + + + +1199 + + + + 1132 + + + + + + + + + + 914 + + + + + + + + + + +762 + + + + 926 + + + + + + + + + + + + + Baby-stroller + + + + + + + + + + + + + + + 678 + + + + + + + + + + 787 + + + + + + + + + + + + + + + + 672 + + + + + + + + + + + + + 87 + + + + + + + + + + + + + 673 + + + + + + + + + + +611 + +610 + + + + 609 + + + + + + + + + + + + + + + + + 892 + + + + + + + + + + + + + + + + + + + 113 + + + + + + + + + + Bandage + + + + + + + + + + + + + + 896 + + + + + + + + + + +874 + + + + 909 + + + + + + + + + + 879 + + + + + + + + + + 888 + + + + + + + + + + + + + 622 + + + + + + + + + + + + + 612 + + + + + + + + + + + + + 608 + + + + + + + + + + + + + 623 + + + + + + + + + + +615 + + + + 624 + + + + + + + + + + + + + 625 + + + + + + + + + + +627 + + + + 604 + + + + + + + + + + Basketball + + + + + + + + + + + + + + + + + + + 834 + + + + + + + + + + + + + 1166 + + + + + + + + + + 1170 + + + + + + + + + + + + + 1169 + + + + + + + + + + + + + 1167 + + + + + + + + + + + + + 1168 + + + + + + + + + + +829 + + + + 1080 + + + + + + + + + + + + + + 638 + + + + + + + + + + + + + + 613 + + + + + + + + + + + + +1102 + + + + 696 + + + + + + + + + + +917 + +78 + +92 + +607 + + + + 833 + + + + + + + + + + + + + 675 + + + + + + + + + + + + + 39 + + + + + + + + + + + + + + 852 + + + + + + + + + + 790 + + + + + + + + + + + + + + 106 + + + + + + + + + + 710 + + + + + + + + + + + + + + + + + 663 + + + + + + + + + + + + + 1042 + + + + + + + + + + 90 + + + + + + + + + + + + + 873 + + + + + + + + + + 1092 + + + + + + + +Book-@ + + + + 153 + + + + + + + + + + + + + +1153 + + + + 677 + + + + + + + + + + 132 + + + + + + + +987 + + + + Botl + + + + + + + + + + +1089 + + + + 898 + + + + + + + + + + + + + 899 + + + + + + + + + + +1122 + + + + 811 + + + + + + + + + + Briefcase-person + + + + + + + + + + + + + 1108 + + + + + + + + + + + + +954 + + + + 880 + + + + + + + + + + + + + 800 + + + + + + + + + + + + + Brush-and-pencil + + + + + + + + + + + + + + 116 + + + + + + + + + + + + + 1049 + + + + + + + + + + 1088 + + + + + + + + + + 1036 + + + + + + + + + + 1152 + + + + + + + + + + 639 + + + + + + + + + + + + + 95 + + + + + + + + + + 1082 + + + + + + + +915 + + + + 84 + + + + + + + + + + 907 + + + + + + + + + + +112 + +975 + +111 + +817 + + + + 1186 + + + + + + + + + + + + + 1188 + + + + + + + + + + + + + 1189 + + + + + + + + + + + + + 1187 + + + + + + + + + + +760 + + + + 970 + + + + + + + + + + + + + 1022 + + + + + + + + + + + + + 1021 + + + + + + + + + + 971 + + + + + + + + + + + + + 972 + + + + + + + + + + +686 + + + + 685 + + + + + + + + + + 966 + + + + + + + + + + 1025 + + + + + + + + + + 1020 + + + + + + + + + + 707 + + + + + + + + + + +1075 + + + + 890 + + + + + + + + + + 958 + + + + + + + + + + + + + + + 1115 + + + + + + + + + + + +1072 + +Calendar-empty + + + + 1027 + + + + + + + + + + + + + 1028 + + + + + + + + + + + + + 1018 + + + + + + + +1149 + +488 + +871 + + + + 792 + + + + + + + +877 + +Candy-stick + + + + 1055 + + + + + + + + + + + +car-garage + +859 + +662 + + + + 708 + + + + + + + + + + + + 181 + + + + + + + + + + caterpillar-machine + + + + + + + + + + + + + + + + + + 1163 + + + + + + + + + + + + + + + + 1011 + + + + + + + + + + + + + + + + + 819 + + + + + + + + + + + + + + + + 109 + + + + + + + +1216 + +1233 + + + + 872 + + + + + + + + + + + + + + + 1171 + + + + + + + + + + + + + 839 + + + + + + + + + + + + + 1228 + + + + + + + + + + 788 + + + + + + + + + + 1039 + + + + + + + +173 + + + + Christmass-ball + + + + + + + + + + + + + + + + 653 + + + + + + + + + + + + + + + 1070 + + + + + + + + + + +652 + + + + 841 + + + + + + + + + + 103 + + + + + + + + + + 1015 + + + + + + + + + + + + + + + + + + + 1048 + + + + + + + + + + 840 + + + + + + + + + + 976 + + + + + + + + + + 1045 + + + + + + + + + + 827 + + + + + + + + + + 826 + + + + + + + + + + 933 + + + + + + + + + + 1046 + + + + + + + + + + 1047 + + + + + + + + + + 1023 + + + + + + + + + + 1024 + + + + + + + + + + 1026 + + + + + + + +1243 + + + + 717 + + + + + + + + + + + + + + 925 + + + + + + + + + + 617 + + + + + + + + + + +818 + + + + 614 + + + + + + + + + + + + + + + 968 + + + + + + + + + + + + + + + + 967 + + + + + + + + + + + + + +918 + + + + 1177 + + + + + + + + + + + + + + + + 754 + + + + + + + + + + + + + 136 + + + + + + + + + + + + + 725 + + + + + + + + + + + + + + + + 853 + + + + + + + + + + + + + 816 + + + + + + + + + + + + + + + + + +854 + +726 + + + + 734 + + + + + + + + + + + + + 850 + + + + + + + + + + + + + 137 + + + + + + + + + + + + + 849 + + + + + + + + + + 1106 + + + + + + + + + + + + +743 + + + + 131 + + + + + + + + + + + + + 1007 + + + + + + + + + + 815 + + + + + + + + + + + + + + 691 + + + + + + + + + + + + + + 1150 + + + + + + + + + + + + + 803 + + + + + + + + + + + + + + + + 804 + + + + + + + + + + + + + + + + 778 + + + + + + + + + + + + + + + 720 + + + + + + + + + + 640 + + + + + + + + + + + + + 903 + + + + + + + + + + + + + + congratulation-hat + + + + + + + + + + + + + + + + Connect-1 + + + + + + + + + + 812 + + + + + + + +Contact-book + + + + 1076 + + + + + + + + + + + + + 1145 + + + + + + + + + + 1005 + + + + + + + + + + + + + 1041 + + + + + + + + + + + + + 699 + + + + + + + + + + +1192 + +1086 + +716 + +1067 + + + + 723 + + + + + + + + + + + + + 1101 + + + + + + + + + + + + +962 + + + + 1016 + + + + + + + + + + +1065 + + + + 649 + + + + + + + + + + + + + 876 + + + + + + + + + + + + + + Cup-cake + + + + + + + + + + + + + Curtain + + + + + + + + + + + + + + 715 + + + + + + + + + + + + + +1013 + + + + 789 + + + + + + + + + + +635 + +636 + +657 + +655 + +101 + +626 + +645 + + + + 1227 + + + + + + + + + + 945 + + + + + + + + + + 799 + + + + + + + + + + 633 + + + + + + + + + + +1208 + + + + 105 + + + + + + + + + + + + + + + + + + + + 821 + + + + + + + + + + 1172 + + + + + + + + + + 1173 + + + + + + + + + + 1174 + + + + + + + + + + 1119 + + + + + + + + + + 1121 + + + + + + + + + + 1118 + + + + + + + +905 + + + + 605 + + + + + + + + + + + + + + 603 + + + + + + + + + + + +904 + +1137 + + + + Disc-play 2 + + + + + + + + + + + + + Disc-play + + + + + + + + + + + + + + + + + + + +906 + +779 + + + + 598 + + + + + + + + + + + + + + 1138 + + + + + + + + + + + + + + + + + + 1147 + + + + + + + + + + + + + 146 + + + + + + + + + + + + + + DNA + + + + + + + + + + 122 + + + + + + + + + + + + + + Document-backward + + + + + + + + + + + + + + Document-forward + + + + + + + + + + + + + + + + 121 + + + + + + + + + + + + + + 128 + + + + + + + + + + + + + 149 + + + + + + + + + + + + + 118 + + + + + + + + + + + + + + 1087 + + + + + + + + + + + + + +171 + +Document-edit + + + + 731 + + + + + + + + + + + + + + 127 + + + + + + + + + + + + + Document-help + + + + + + + + + + + + + 747 + + + + + + + + + + +172 + + + + 745 + + + + + + + + + + + + + + + + 1032 + + + + + + + + + + + + + + Document-RSS + + + + + + + + + + + + + 150 + + + + + + + + + + + + + + 119 + + + + + + + + + + +148 + + + + 1083 + + + + + + + + + + + + + 114 + + + + + + + + + + +1144 + + + + 1051 + + + + + + + + + + + + + 1219 + + + + + + + + + + + + + 692 + + + + + + + + + + 953 + + + + + + + + + + Dropbox + + + + + + + + + + + + + + +810 + + + + 643 + + + + + + + + + + Edit + + + + + + + + + + + + + 948 + + + + + + + + + + + + + 1240 + + + + + + + + + + +798 + +730 + +729 + + + + 946 + + + + + + + + + + 141 + + + + + + + + + + + + + 797 + + + + + + + + + + + + + 1012 + + + + + + + + + + 159 + + + + + + + + + + + + + 869 + + + + + + + + + + 963 + + + + + + + + + + + + + 878 + + + + + + + + + + + + + + Factory + + + + + + + + + + + + + + 727 + + + + + + + + + + + + + 891 + + + + + + + + + + 965 + + + + + + + + + + 702 + + + + + + + + + + 1084 + + + + + + + +862 + + + + 1157 + + + + + + + + + + + + + 1126 + + + + + + + + + + + + + 98 + + + + + + + + + + + + + + + 780 + + + + + + + +182 + + + + 703 + + + + + + + + + + + + + 748 + + + + + + + + + + + + + 769 + + + + + + + + + + + + + + + + 701 + + + + + + + + + + + + + + + 986 + + + + + + + + + + 1100 + + + + + + + + + + + + + 1135 + + + + + + + +96 + + + + 1059 + + + + + + + + + + 832 + + + + + + + + + + + + + 1184 + + + + + + + + + + + + + 1056 + + + + + + + +Folder-contact + + + + 938 + + + + + + + + + + + + + Folder-music + + + + + + + + + + + + + 82 + + + + + + + + + + 939 + + + + + + + + + + + + + 940 + + + + + + + + + + + + + 936 + + + + + + + + + + + + + Folder-search + + + + + + + + + + + + + + +772 + +937 + + + + 941 + + + + + + + + + + + + + 860 + + + + + + + + + + + + + + + + + 1242 + + + + + + + + + + + + +80 + + + + 88 + + + + + + + + + + + + + 889 + + + + + + + + + + +1244 + + + + 1079 + + + + + + + + + + 632 + + + + + + + + + + + + + 1183 + + + + + + + + + + 697 + + + + + + + + + + 887 + + + + + + + + + + + + + 759 + + + + + + + + + + 1109 + + + + + + + +654 + + + + 875 + + + + + + + + + + 108 + + + + + + + + + + + + +856 + +Glove + + + + 147 + + + + + + + + + + +1009 + + + + 949 + + + + + + + +1037 + + + + Hammer-and-wrench + + + + + + + + + + + + + + + + + 164 + + + + + + + + + + + + + 602 + + + + + + + + + + 960 + + + + + + + + + + + + 712 + + + + + + + + + + 65 + + + + + + + + + + + + + + + + 908 + + + + + + + +Hardware + +902 + + + + 629 + + + + + + + + + + + + + 813 + + + + + + + + + + Head + + + + + + + + + + + + + + + + 155 + + + + + + + + + + +154 + +156 + + + + 158 + + + + + + + + + + + + + 930 + + + + + + + + + + 773 + + + + + + + + + + + + + 709 + + + + + + + + + + + + + + + 151 + + + + + + + + + + 952 + + + + + + + + + + 809 + + + + + + + + + + 688 + + + + + + + + + + +690 + + + + 142 + + + + + + + + + + +1038 + + + + 931 + + + + + + + + + + 679 + + + + + + + + + + 984 + + + + + + + + + + + + + 1090 + + + + + + + + + + + + + 81 + + + + + + + +805 + +1232 + + + + 844 + + + + + + + + + + + + + + 843 + + + + + + + + + + + + + 828 + + + + + + + + + + 455 + + + + + + + +117 + +791 + + + + 770 + + + + + + + + + + 1239 + + + + + + + + + + + + + 957 + + + + + + + + + + + + + 1238 + + + + + + + + + + +659 + +1058 + +742 + +990 + + + + 991 + + + + + + + + + + 922 + + + + + + + + + + 751 + + + + + + + + + + + + + 669 + + + + + + + + + + + + + + + + + + 631 + + + + + + + + + + + + + + 801 + + + + + + + + + + +784 + + + + 1148 + + + + + + + +1207 + +1206 + + + + 1200 + + + + + + + + + + + + + + + + 961 + + + + + + + + + + + + + + + 97 + + + + + + + + + + + + + 1164 + + + + + + + + + + + + + + + + 1218 + + + + + + + + + + +Light-alarm + +825 + +1140 + + + + 1141 + + + + + + + + + + + + + 863 + + + + + + + + + + + + + 974 + + + + + + + +820 + + + + 93 + + + + + + + + + + + + + + + 660 + + + + + + + + + + 830 + + + + + + + + + + + + + + + + + + + + + + + 782 + + + + + + + + + + 781 + + + + + + + +932 + + + + 646 + + + + + + + + + + + + +1074 + + + + 897 + + + + + + + + + + + + + 1142 + + + + + + + + + + + + + + 753 + + + + + + + + + + 1003 + + + + + + + + + + 165 + + + + + + + + + + + + + +796 + +795 + + + + 1001 + + + + + + + + + + 982 + + + + + + + + + + 599 + + + + + + + + + + + + + + 1156 + + + + + + + + + + + + + + 175 + + + + + + + + + + + + + + + + + + + + + 689 + + + + + + + + + + + + + + + 1064 + + + + + + + + + + 1063 + + + + + + + + + + 942 + + + + + + + + + + + + + 728 + + + + + + + + + + + + + + + + 1008 + + + + + + + + + + + + + + 705 + + + + + + + + + + + + + 1043 + + + + + + + + + + 666 + + + + + + + + + + 893 + + + + + + + +916 + +718 + +1139 + + + + 1143 + + + + + + + + + + + + + 761 + + + + + + + + + + + + + 845 + + + + + + + + + + 664 + + + + + + + + + + + + + 1091 + + + + + + + + + + + + +Multifunction-knife + + + + 739 + + + + + + + + + + + + + 1114 + + + + + + + + + + 115 + + + + + + + + + + 1123 + + + + + + + +619 + + + + 91 + + + + + + + + + + + + + + + + + + 923 + + + + + + + +695 + +1019 + + + + 1098 + + + + + + + +842 + +176 + +Open + + + + 806 + + + + + + + + + + Paper-clip + + + + + + + + + + 924 + + + + + + + + + + 943 + + + + + + + + + + + + + 1057 + + + + + + + + + + + + + + + + + + + + + 1229 + + + + + + + + + + + + + 1044 + + + + + + + + + + 901 + + + + + + + + + + + + + 163 + + + + + + + + + + +1136 + + + + 992 + + + + + + + + + + 947 + + + + + + + + + + + + + + 867 + + + + + + + + + + + + + 178 + + + + + + + + + + + +714 + + + + 179 + + + + + + + + + + + + + + 737 + + + + + + + + + + + + + + + 786 + + + + + + + + + + + + + 177 + + + + + + + + + + + + + + + + 771 + + + + + + + + + + + + + + + + + + + + 601 + + + + + + + + + + + + + + +180 + + + + 139 + + + + + + + + + + + + + + 738 + + + + + + + + + + + + + + + 866 + + + + + + + + + + + + + 721 + + + + + + + + + + 1071 + + + + + + + + + + + + + + 129 + + + + + + + + + + + + + + + + + + + + + + + + + 920 + + + + + + + +785 + + + + 1194 + + + + + + + + + + 89 + + + + + + + + + + +661 + + + + 83 + + + + + + + + + + 814 + + + + + + + +Pin-location-1 + + + + Pin-location-2 + + + + + + + + + + + + + 978 + + + + + + + + + + 980 + + + + + + + + + + 977 + + + + + + + + + + Pin-location-map + + + + + + + + + + + + + + + 979 + + + + + + + + + + 1060 + + + + + + + + + + +983 + + + + 783 + + + + + + + + + + + + + Pipe + + + + + + + + + + Pizza + + + + + + + + + + + + + + + + + + + + + + + + + 881 + + + + + + + + + + + + + 1245 + + + + + + + + + + 774 + + + + + + + + + + 618 + + + + + + + + + + 620 + + + + + + + + + + 621 + + + + + + + + + + 684 + + + + + + + +1066 + + + + 1085 + + + + + + + + + + 1030 + + + + + + + + + + + + + 1031 + + + + + + + + + + +41 + + + + 824 + + + + + + + + + + + + + + + + + 857 + + + + + + + + + + 143 + + + + + + + + + + + + + + + 152 + + + + + + + + + + + + + + 9 + + + + + + + + + + + + + 1226 + + + + + + + + + + 110 + + + + + + + + + + 895 + + + + + + + + + + + + + + 910 + + + + + + + + + + + +694 + + + + 665 + + + + + + + + + + + + + + 1162 + + + + + + + + + + + + + + + + 1217 + + + + + + + + + + + + + 700 + + + + + + + + + + 959 + + + + + + + + + + 668 + + + + + + + + + + 1062 + + + + + + + + + + + + + 1069 + + + + + + + + + + + + + + 999 + + + + + + + + + + + + + 1081 + + + + + + + +894 + + + + 1125 + + + + + + + +650 + + + + 1004 + + + + + + + + + + +693 + + + + 130 + + + + + + + +1205 + +1204 + + + + 94 + + + + + + + + + + + + + 681 + + + + + + + + + + + + + +886 + + + + 775 + + + + + + + + + + 698 + + + + + + + +911 + + + + Share-1 + + + + + + + + + + 40 + + + + + + + + + + 1054 + + + + + + + + + + 1078 + + + + + + + + + + 724 + + + + + + + + + + 1033 + + + + + + + + + + 752 + + + + + + + + + + 600 + + + + + + + + + + 1151 + + + + + + + + + + 868 + + + + + + + +704 + +736 + +120 + +912 + +913 + +711 + + + + 1111 + + + + + + + + + + 145 + + + + + + + + + + Signal-1 + + + + + + + + + + Signal-2 + + + + + + + + + + + + + + 1014 + + + + + + + + + + 755 + + + + + + + + + + + + + + + + 822 + + + + + + + + + + + + + +99 + + + + 944 + + + + + + + + + + 157 + + + + + + + + + + +Slide-show + + + + 1128 + + + + + + + + + + 838 + + + + + + + + + + + + + + Snow + + + + + + + + + + 763 + + + + + + + + + + + + + + + + 658 + + + + + + + + + + + + +1040 + +162 + + + + 837 + + + + + + + + + + + +1002 + + + + 1193 + + + + + + + + + + Rocket + + + + + + + + + + + + + 858 + + + + + + + + + + 85 + + + + + + + + + + + + + 768 + + + + + + + + + + + + + + 628 + + + + + + + + + + + + + 1201 + + + + + + + + + + + + + + + + + + + + + + + 1198 + + + + + + + + + + + + + + + 993 + + + + + + + + + + 1202 + + + + + + + + + + 1203 + + + + + + + + + + 1195 + + + + + + + + + + + + + + + 133 + + + + + + + + + + 956 + + + + + + + + + + 1230 + + + + + + + +1099 + + + + 107 + + + + + + + + + + 746 + + + + + + + + + + + + + + + +634 + +735 + + + + 174 + + + + + + + + + + + + + + + + 676 + + + + + + + +861 + + + + 995 + + + + + + + + + + + + + 161 + + + + + + + + + + + + + 1146 + + + + + + + +1096 + +1097 + +1095 + +1094 + +1093 + +998 + + + + 807 + + + + + + + + + + + + + 1107 + + + + + + + + + + + + + + + 144 + + + + + + + + + + + + + + + + + + + 1052 + + + + + + + + + + + + + 882 + + + + + + + + + + 955 + + + + + + + +1073 + + + + Tag-price + + + + + + + + + + + + + + 1124 + + + + + + + +713 + +Targer + +885 + + + + 1110 + + + + + + + + + + 756 + + + + + + + + + + Telescope + + + + + + + + + + + + + 981 + + + + + + + +719 + + + + 793 + + + + + + + + + + 794 + + + + + + + + + + 606 + + + + + + + + + + + + + 1061 + + + + + + + + + + Test-tube + + + + + + + + + + +102 + + + + 870 + + + + + + + + + + 647 + + + + + + + + + + + + + + 851 + + + + + + + + + + + + + + + 1113 + + + + + + + + + + + + + + 687 + + + + + + + + + + +637 + +919 + + + + 1017 + + + + + + + + + + + +641 + +855 + + + + 671 + + + + + + + + + + + + + + + 630 + + + + + + + + + + + + + 929 + + + + + + + + + + 1034 + + + + + + + + + + 1224 + + + + + + + + + + + + + 1223 + + + + + + + + + + + + + 1222 + + + + + + + + + + + + + 1221 + + + + + + + + + + + + + 1237 + + + + + + + + + + 1235 + + + + + + + + + + 1234 + + + + + + + + + + 1236 + + + + + + + +767 + +765 + +764 + +985 + +749 + +Trolley-full + +750 + +766 + +836 + + + + 706 + + + + + + + + + + + + + 1105 + + + + + + + + + + + + + 1117 + + + + + + + + + + + + + 934 + + + + + + + + + + + + + 935 + + + + + + + + + + + + + 1134 + + + + + + + + + + + + + 1133 + + + + + + + + + + +1053 + +994 + + + + 1077 + + + + + + + + + + 1000 + + + + + + + + + + + + + 988 + + + + + + + + + + 1050 + + + + + + + + + + + + + 1220 + + + + + + + + + + +1104 + +1006 + +1127 + +1035 + +846 + +969 + +996 + + + + 997 + + + + + + + + + + + + + + 680 + + + + + + + + + + +651 + +656 + +Weight-kilograms + + + + 674 + + + + + + + +823 + + + + Wheel-steel + + + + + + + + + + + + + 1010 + + + + + + + +Wifi-1 + +683 + + + + 921 + + + + + + + + + + + + + + + 722 + + + + + + + + + + + + + 140 + + + + + + + + + + + + + + + + 964 + + + + + + + + + + 808 + + + + + + + +927 + +1215 + + + + 865 + + + + + + + + + + + + + + 864 + + + + + + + + + + + diff --git a/assets/js/admin.js b/assets/js/admin.js index 858e8f7..163eec0 100644 --- a/assets/js/admin.js +++ b/assets/js/admin.js @@ -8,21 +8,22 @@ imagesContext.keys().forEach(imagesContext);*/ import '../css/admin.scss'; require('../../node_modules/bootstrap/dist/js/bootstrap.min.js'); -// require('./addons/table-selectable.js')(); -require('./addons/table-fixed.js')(); -// require('./addons/document-selector.js')(); -require('./addons/form-confirm.js')(); -require('./addons/form.js')(); -require('./addons/dbclick.js')(); -require('./addons/toast.js')(); -require('./addons/modal.js')(); -require('./addons/push-state.js')(); -require('./addons/password.js')(); -require('./addons/tooltip.js')(); -require('./addons/editor.js')(); -require('./addons/panel.js')(); -require('./addons/choices.js')(); -require('./addons/checkbox-checker.js')(); -require('./addons/rest-choices.js')(); -require('./addons/form-collection.js')(); -require('./addons/datepicker.js')(); +// require('./admin/table-selectable.js')(); +require('./admin/table-fixed.js')(); +// require('./admin/document-selector.js')(); +require('./admin/form-confirm.js')(); +require('./admin/form.js')(); +require('./admin/dbclick.js')(); +require('./admin/toast.js')(); +require('./admin/modal.js')(); +require('./admin/push-state.js')(); +require('./admin/password.js')(); +require('./admin/tooltip.js')(); +require('./admin/editor.js')(); +require('./admin/panel.js')(); +require('./admin/choices.js')(); +require('./admin/checkbox-checker.js')(); +require('./admin/rest-choices.js')(); +require('./admin/form-collection.js')(); +require('./admin/datepicker.js')(); +require('./admin/simplemde.js')(); diff --git a/assets/js/addons/checkbox-checker.js b/assets/js/admin/checkbox-checker.js similarity index 100% rename from assets/js/addons/checkbox-checker.js rename to assets/js/admin/checkbox-checker.js diff --git a/assets/js/addons/choices.js b/assets/js/admin/choices.js similarity index 100% rename from assets/js/addons/choices.js rename to assets/js/admin/choices.js diff --git a/assets/js/addons/datepicker.js b/assets/js/admin/datepicker.js similarity index 100% rename from assets/js/addons/datepicker.js rename to assets/js/admin/datepicker.js diff --git a/assets/js/addons/dbclick.js b/assets/js/admin/dbclick.js similarity index 100% rename from assets/js/addons/dbclick.js rename to assets/js/admin/dbclick.js diff --git a/assets/js/addons/document-selector.js b/assets/js/admin/document-selector.js similarity index 100% rename from assets/js/addons/document-selector.js rename to assets/js/admin/document-selector.js diff --git a/assets/js/addons/editor.js b/assets/js/admin/editor.js similarity index 100% rename from assets/js/addons/editor.js rename to assets/js/admin/editor.js diff --git a/assets/js/addons/form-collection.js b/assets/js/admin/form-collection.js similarity index 100% rename from assets/js/addons/form-collection.js rename to assets/js/admin/form-collection.js diff --git a/assets/js/addons/form-confirm.js b/assets/js/admin/form-confirm.js similarity index 100% rename from assets/js/addons/form-confirm.js rename to assets/js/admin/form-confirm.js diff --git a/assets/js/addons/form.js b/assets/js/admin/form.js similarity index 100% rename from assets/js/addons/form.js rename to assets/js/admin/form.js diff --git a/assets/js/addons/modal.js b/assets/js/admin/modal.js similarity index 100% rename from assets/js/addons/modal.js rename to assets/js/admin/modal.js diff --git a/assets/js/addons/panel.js b/assets/js/admin/panel.js similarity index 100% rename from assets/js/addons/panel.js rename to assets/js/admin/panel.js diff --git a/assets/js/addons/password.js b/assets/js/admin/password.js similarity index 100% rename from assets/js/addons/password.js rename to assets/js/admin/password.js diff --git a/assets/js/addons/push-state.js b/assets/js/admin/push-state.js similarity index 100% rename from assets/js/addons/push-state.js rename to assets/js/admin/push-state.js diff --git a/assets/js/addons/rest-choices.js b/assets/js/admin/rest-choices.js similarity index 100% rename from assets/js/addons/rest-choices.js rename to assets/js/admin/rest-choices.js diff --git a/assets/js/admin/simplemde.js b/assets/js/admin/simplemde.js new file mode 100644 index 0000000..a143563 --- /dev/null +++ b/assets/js/admin/simplemde.js @@ -0,0 +1,15 @@ +const Editor = require('simplemde') +const $ = require('jquery'); + +module.exports = () => { + elements = $('textarea[data-simplemde]') + + elements.each((i, element) => { + $(element).removeClass('form-control') + + new Editor({ + element: element, + forceSync: true + }) + }) +} diff --git a/assets/js/addons/table-fixed.js b/assets/js/admin/table-fixed.js similarity index 100% rename from assets/js/addons/table-fixed.js rename to assets/js/admin/table-fixed.js diff --git a/assets/js/addons/table-selectable.js b/assets/js/admin/table-selectable.js similarity index 100% rename from assets/js/addons/table-selectable.js rename to assets/js/admin/table-selectable.js diff --git a/assets/js/addons/toast.js b/assets/js/admin/toast.js similarity index 100% rename from assets/js/addons/toast.js rename to assets/js/admin/toast.js diff --git a/assets/js/addons/tooltip.js b/assets/js/admin/tooltip.js similarity index 100% rename from assets/js/addons/tooltip.js rename to assets/js/admin/tooltip.js diff --git a/assets/js/app.js b/assets/js/app.js new file mode 100644 index 0000000..3d92424 --- /dev/null +++ b/assets/js/app.js @@ -0,0 +1,3 @@ +import '../css/app.scss'; + + diff --git a/assets/js/app/app.js b/assets/js/app/app.js new file mode 100644 index 0000000..66c8c34 --- /dev/null +++ b/assets/js/app/app.js @@ -0,0 +1,15 @@ +var App = function(components) { + this.components = components || []; +} + +App.prototype.add = function(c) { + this.components.push(c); + + return this; +} + +App.prototype.init = function() { + for (var u = 0, x = this.components.length; u < x; u++) { + this.components[u].init(); + } +} diff --git a/assets/js/app/code.js b/assets/js/app/code.js new file mode 100644 index 0000000..189ee6f --- /dev/null +++ b/assets/js/app/code.js @@ -0,0 +1,42 @@ +var Code = function(w) { + this.window = w; +} + +Code.prototype.init = function() { + Prism.highlightAllUnder(document); + + var elements = this.window.document.querySelectorAll('code[data-title], div[data-title]'); + + for (var i = 0, len = elements.length; i < len; i++) { + var element = elements[i]; + + if (element.tagName === 'CODE') { + var code = element; + var pre = code.parentNode; + var post = pre.parentNode; + } else { + var code = element.querySelector('code'); + + if (!code) { + continue; + } + + var pre = code.parentNode; + var post = pre.parentNode; + } + + if (!pre || !post) { + continue; + } + + pre.classList.add('with-title'); + + var title = this.window.document.createElement('div'); + title.classList.add('code-title'); + title.textContent = element.getAttribute('data-title'); + + post.insertBefore(title, pre); + } +} + +module.exports = Code diff --git a/assets/js/app/form-pwn.js b/assets/js/app/form-pwn.js new file mode 100644 index 0000000..b39b632 --- /dev/null +++ b/assets/js/app/form-pwn.js @@ -0,0 +1,23 @@ +var FormPnw = function(w) { + this.window = w; +} + +FormPnw.prototype.init = function() { + var doc = this.window.document; + + doc.addEventListener('mousemove', function() { + var forms = doc.querySelectorAll('form[data-form-bot]'); + + for (var i = 0, len = forms.length; i < len; i++) { + var form = forms[i]; + var action = form.getAttribute('action'); + action = action.replace(Routing.generate('form_without_javascript') + '?page=', ''); + action = decodeURIComponent(action); + + form.setAttribute('action', action); + form.removeAttribute('data-form-bot'); + } + }); +} + +module.exports = FormPnw diff --git a/assets/js/app/knmc.js b/assets/js/app/knmc.js new file mode 100644 index 0000000..69a58a6 --- /dev/null +++ b/assets/js/app/knmc.js @@ -0,0 +1,32 @@ +var Knmc = function(w) { + this.window = w; +} + +Knmc.prototype.init = function() { + var chars = ''; + var seq = '38384040373937396665'; + var body = this.window.document.querySelector('body'); + + body.addEventListener('keyup', function(e) { + chars += e.keyCode.toString(); + + if (chars.indexOf(seq) !== -1) { + chars = ''; + var url = '/bundles/deblanblog/skin2018/frontoffice/img/mario.gif'; + var image = new Image(); + image.classList.add('fixed'); + image.style.position = 'fixed'; + image.style.bottom = '-11px'; + image.style.left = '-256px'; + + image.onload = function() { + image.classList.add('knmc'); + body.appendChild(image); + } + + image.src = url; + } + }); +} + +module.exports = Knmc diff --git a/assets/js/app/lazy-load.js b/assets/js/app/lazy-load.js new file mode 100644 index 0000000..dae8bfe --- /dev/null +++ b/assets/js/app/lazy-load.js @@ -0,0 +1,9 @@ +var LazyLoad = function() { +} + +LazyLoad.prototype.init = function() { + var observer = lozad('.lazy-img'); + observer.observe(); +} + +module.exports = LazyLoad diff --git a/assets/js/app/particles.js b/assets/js/app/particles.js new file mode 100644 index 0000000..44107d0 --- /dev/null +++ b/assets/js/app/particles.js @@ -0,0 +1,44 @@ +var Particles = function(w) { + this.window = w; +} + +Particles.prototype.start = function() { + if (this.window.innerWidth < 708) { + return; + } + + var height = this.header.offsetHeight; + var width = this.header.offsetWidth - 40; + var canvas; + + this.particles.style.maxHeight = height + 'px'; + this.particles.style.maxWidth = width + 'px'; + + particlesJS.load('particles', '/js/particles.json?v=3'); +} + +Particles.prototype.clean = function() { + this.particles.innerHTML = ''; +} + +Particles.prototype.init = function() { + this.particles = this.window.document.getElementById('particles'); + + if (!this.particles) { + return; + } + + this.header = this.particles.parentNode; + + this.clean(); + this.start(); + + var that = this; + + this.window.addEventListener('resize', function() { + that.clean(); + that.start(); + }, false); +} + +module.exports = Particles diff --git a/assets/js/app/post.js b/assets/js/app/post.js new file mode 100644 index 0000000..690fe3a --- /dev/null +++ b/assets/js/app/post.js @@ -0,0 +1,118 @@ +var Post = function(w) { + this.window = w; +} + +Post.prototype.commentsEvents = function() { + var document = this.window.document; + + var parentCommentIdField = document.getElementById('comment_parentCommentId'); + + if (!parentCommentIdField) { + return; + } + + var isAnswerAlert = document.getElementById('answer-alert'); + var cancelAnswerButton = document.getElementById('cancel-answer'); + + var toogleAnswerAlert = function() { + if (parentCommentIdField.value) { + isAnswerAlert.classList.remove('hidden'); + } else { + isAnswerAlert.classList.add('hidden'); + } + } + + toogleAnswerAlert(); + + var answerButtons = document.querySelectorAll('a[data-answer]'); + + for (var i = 0, len = answerButtons.length; i < len; i++) { + answerButtons[i].addEventListener('click', function(e) { + parentCommentIdField.value = e.target.getAttribute('data-answer'); + toogleAnswerAlert(); + }, false); + } + + cancelAnswerButton.addEventListener('click', function(e) { + e.preventDefault(); + + parentCommentIdField.value = null; + toogleAnswerAlert(); + }, false); + + var previewButton = document.querySelector('.preview-button'); + var previewRender = document.querySelector('#preview'); + + previewButton.addEventListener('click', function() { + if (previewRender.innerHTML === '') { + previewRender.innerHTML = '

Chargement en cours…

'; + } + + var content = document.querySelector('#comment_content').value; + var httpRequest = new XMLHttpRequest(); + + httpRequest.onreadystatechange = function(data) { + if (httpRequest.readyState === 4 && httpRequest.status === 200) { + var json = JSON.parse(httpRequest.response); + previewRender.innerHTML = json.render; + document.location.href = '#preview'; + } + }; + + httpRequest.open('POST', Routing.generate('api_comment_preview')); + httpRequest.setRequestHeader( + 'Content-Type', + 'application/x-www-form-urlencoded' + ); + httpRequest.send('content=' + encodeURIComponent(content)); + }, false); +} + +Post.prototype.imagesEvents = function() { + var document = this.window.document; + var isFullscreen = false; + var images = document.querySelectorAll('.body img'); + + var handleClick = function(image) { + if (isFullscreen) { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } + } else { + if (image.requestFullscreen) { + image.requestFullscreen(); + } else if (image.webkitRequestFullscreen) { + image.webkitRequestFullscreen(); + } else if (image.mozRequestFullScreen) { + image.mozRequestFullScreen(); + } + } + + isFullscreen = !isFullscreen; + }; + + for (var i = 0, len = images.length; i < len; i++) { + var image = images[i]; + + if (image.parentNode.tagName === 'A') { + continue; + } + + (function(i) { + i.addEventListener('click', function() { + handleClick(i); + }, false); + })(image); + } +} + +Post.prototype.init = function() { + this.commentsEvents(); + this.imagesEvents(); +} + +module.exports = Post diff --git a/assets/js/app/quick-post.js b/assets/js/app/quick-post.js new file mode 100644 index 0000000..3e0becb --- /dev/null +++ b/assets/js/app/quick-post.js @@ -0,0 +1,25 @@ +var QuickPost = function(w) { + this.window = w; +} + +QuickPost.prototype.init = function() { + var doc = this.window.document; + + var images = doc.querySelectorAll('.quick-image img'); + + for (var i = 0, len = images.length; i < len; i++) { + (function(image) { + var source = image.getAttribute('data-src'); + var loader = new Image(); + + loader.onload = function() { + image.style.backgroundImage = 'url(' + source + ')'; + image.style.backgroundSize = 'cover'; + } + + loader.src = source; + })(images[i]); + } +} + +module.exports = QuickPost diff --git a/assets/js/app/stats.js b/assets/js/app/stats.js new file mode 100644 index 0000000..79f5764 --- /dev/null +++ b/assets/js/app/stats.js @@ -0,0 +1,38 @@ +var Stats = function() { +} + +Stats.prototype.init = function() { + (function(f, a, t, h, o, m){ + a[h]=a[h]||function(){ + (a[h].q=a[h].q||[]).push(arguments) + }; + o=f.createElement('script'), + m=f.getElementsByTagName('script')[0]; + o.async=1; o.src=t; o.id='fathom-script'; + m.parentNode.insertBefore(o,m) + })(document, window, '//ftm.deblan.org/tracker.js', 'fathom'); + fathom('set', 'siteId', 'HQAWS'); + fathom('trackPageview'); + + /* + var _paq = _paq || []; + _paq.push(['trackPageView']); + _paq.push(['enableLinkTracking']); + + var u="//piwik.deblan.org/"; + _paq.push(['setTrackerUrl', u+'piwik.php']); + _paq.push(['setSiteId', '1']); + + var d= document; + var g= d.createElement('script'); + var s= d.getElementsByTagName('script')[0]; + + g.type='text/javascript'; + g.async=true; + g.defer=true; + g.src=u+'piwik.js'; + s.parentNode.insertBefore(g,s); + */ +} + +module.exports = Stats diff --git a/assets/js/app/video-ratio.js b/assets/js/app/video-ratio.js new file mode 100644 index 0000000..afdc2b4 --- /dev/null +++ b/assets/js/app/video-ratio.js @@ -0,0 +1,13 @@ +var VideoRatio = function(w) { + this.window = w; +} + +VideoRatio.prototype.init = function() { + var videos = this.window.document.querySelectorAll('.video-ratio'); + + for (var i = 0, len = videos.length; i < len; i++) { + videos[i].style.paddingBottom = videos[i].getAttribute('data-ratio'); + } +} + +module.exports = VideoRatio diff --git a/composer.json b/composer.json index ea84152..ce721b7 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,8 @@ "doctrine/doctrine-bundle": "^2.2", "doctrine/doctrine-migrations-bundle": "^3.0", "doctrine/orm": "^2.8", + "knplabs/knp-markdown-bundle": "^1.9", + "knplabs/knp-menu-bundle": "^3.1", "knplabs/knp-paginator-bundle": "^5.4", "phpdocumentor/reflection-docblock": "^5.2", "scheb/2fa-google-authenticator": "^5.7", diff --git a/config/bundles.php b/config/bundles.php index 7ecd493..33bdb5d 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -19,4 +19,6 @@ return [ Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true], App\Core\Bundle\CoreBundle::class => ['all' => true], App\Bundle\AppBundle::class => ['all' => true], + Knp\Bundle\MarkdownBundle\KnpMarkdownBundle::class => ['all' => true], + Knp\Bundle\MenuBundle\KnpMenuBundle::class => ['all' => true], ]; diff --git a/config/packages/app.yaml b/config/packages/app.yaml index 2770a20..d32abb5 100644 --- a/config/packages/app.yaml +++ b/config/packages/app.yaml @@ -1,9 +1,21 @@ core: site: - name: "Murph" + name: "Blog" logo: "build/images/core/logo.svg" pages: App\Entity\Page\SimplePage: - name: 'Page simple' + name: 'Page de contenu' templates: - - {name: "Template 1", file: "page/simple/page.html.twig"} + - {name: "Par défaut", file: "page/simple/default.html.twig"} + App\Entity\Page\ContactPage: + name: 'Contact' + templates: + - {name: "Par défaut", file: "page/contact/default.html.twig"} + App\Entity\Page\LinksPage: + name: 'Liens partagés' + templates: + - {name: "Par défaut", file: "page/links/default.html.twig"} + App\Entity\Page\TitledPage: + name: 'Page titrée' + templates: + - {name: "Par défaut", file: "page/titled/default.html.twig"} diff --git a/config/packages/knp.yaml b/config/packages/knp.yaml new file mode 100644 index 0000000..894d10d --- /dev/null +++ b/config/packages/knp.yaml @@ -0,0 +1,11 @@ +knp_paginator: + page_range: 4 + default_options: + page_name: page # page query parameter name + sort_field_name: sort # sort field query parameter name + sort_direction_name: direction # sort direction query parameter name + distinct: true # ensure distinct results, useful when ORM queries are using GROUP BY statements + template: + pagination: '@Core/pager/sliding.html.twig' # sliding pagination controls template + sortable: '@KnpPaginator/Pagination/sortable_link.html.twig' # sort link template + filtration: '@KnpPaginator/Pagination/filtration.html.twig' # filters template diff --git a/config/packages/translation.yaml b/config/packages/translation.yaml index be89150..01b30ac 100644 --- a/config/packages/translation.yaml +++ b/config/packages/translation.yaml @@ -1,5 +1,5 @@ framework: - default_locale: en + default_locale: fr translator: default_path: '%kernel.project_dir%/translations' paths: diff --git a/config/services.yaml b/config/services.yaml index 0787193..82c2f9a 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -47,8 +47,16 @@ services: calls: - [ setAnnotationReader, [ "@annotation_reader" ] ] - App\UrlGenerator\FooUrlGenerator: + App\UrlGenerator\PostGenerator: public: true + App\Markdown\Parser\Post: + tags: + - {name: markdown.parser, alias: post} + + App\Markdown\Parser\Comment: + tags: + - {name: markdown.parser, alias: comment} + # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones diff --git a/core/Cache/SymfonyCacheManager.php b/core/Cache/SymfonyCacheManager.php new file mode 100644 index 0000000..da72bad --- /dev/null +++ b/core/Cache/SymfonyCacheManager.php @@ -0,0 +1,51 @@ + + */ +class SymfonyCacheManager +{ + protected KernelInterface $kernel; + + public function __construct(KernelInterface $kernel) + { + $this->kernel = $kernel; + } + + public function cleanRouting() + { + $finder = new Finder(); + $finder + ->in($this->kernel->getCacheDir()) + ->depth('== 0') + ->name('url_*.php*') + ; + + foreach ($finder as $file) { + unlink((string) $file->getPathname()); + } + } + + public function cleanAll() + { + $application = new Application($this->kernel); + $application->setAutoExit(false); + + $input = new ArrayInput([ + 'command' => 'cache:clear', + ]); + + $output = new BufferedOutput(); + $application->run($input, $output); + } +} diff --git a/core/Controller/Site/PageController.php b/core/Controller/Site/PageController.php index bf08f41..644fd83 100644 --- a/core/Controller/Site/PageController.php +++ b/core/Controller/Site/PageController.php @@ -19,13 +19,13 @@ class PageController extends AbstractController $this->siteStore = $siteStore; } - public function show(Request $request, SiteRequest $siteRequest): Response + public function show(): Response { - if (!$siteRequest->getPage()) { + if (!$this->siteRequest->getPage()) { throw $this->createNotFoundException(); } - return $this->defaultRender($siteRequest->getPage()->getTemplate()); + return $this->defaultRender($this->siteRequest->getPage()->getTemplate()); } protected function defaultRender(string $view, array $parameters = [], Response $response = null): Response diff --git a/core/EventSuscriber/Account/PasswordRequestEventSubscriber.php b/core/EventSuscriber/Account/PasswordRequestEventSubscriber.php index 70e0c47..909cb22 100644 --- a/core/EventSuscriber/Account/PasswordRequestEventSubscriber.php +++ b/core/EventSuscriber/Account/PasswordRequestEventSubscriber.php @@ -53,7 +53,7 @@ class PasswordRequestEventSubscriber implements EventSubscriberInterface $this->entityManager->update($user); $this->notifier - ->setSubject($translator->trans('Mot de passe perdu')) + ->setSubject($this->translator->trans('Mot de passe perdu')) ->addRecipient($user->getEmail()) ->notify('@Core/mail/account/resetting_request.html.twig', [ 'reseting_update_link' => $this->urlGenerator->generate( diff --git a/core/EventSuscriber/Site/MenuEventSubscriber.php b/core/EventSuscriber/Site/MenuEventSubscriber.php index 205165f..72335c3 100644 --- a/core/EventSuscriber/Site/MenuEventSubscriber.php +++ b/core/EventSuscriber/Site/MenuEventSubscriber.php @@ -10,6 +10,7 @@ use App\Core\Factory\Site\NodeFactory; use App\Core\Manager\EntityManager; use App\Core\Repository\Site\NodeRepository; use App\Core\Slugify\CodeSlugify; +use App\Core\Cache\SymfonyCacheManager; /** * class MenuEventSubscriber. @@ -22,17 +23,20 @@ class MenuEventSubscriber extends EntityManagerEventSubscriber protected NodeRepository $nodeRepository; protected EntityManager $entityManager; protected CodeSlugify $slugify; + protected SymfonyCacheManager $cacheManager; public function __construct( NodeFactory $nodeFactory, NodeRepository $nodeRepository, EntityManager $entityManager, - CodeSlugify $slugify + CodeSlugify $slugify, + SymfonyCacheManager $cacheManager ) { $this->nodeFactory = $nodeFactory; $this->nodeRepository = $nodeRepository; $this->entityManager = $entityManager; $this->slugify = $slugify; + $this->cacheManager = $cacheManager; } public function support(EntityInterface $entity) @@ -58,11 +62,16 @@ class MenuEventSubscriber extends EntityManagerEventSubscriber $menu = $event->getEntity(); - if (0 !== count($menu->getNodes())) { + if (count($menu->getNodes()) > 2) { return; } - $rootNode = $this->nodeFactory->create($menu); + $rootNode = $menu->getRootNode(); + + if (!$rootNode) { + $rootNode = $this->nodeFactory->create($menu); + } + $childNode = $this->nodeFactory->create($menu, '/'); $childNode ->setParent($rootNode) @@ -78,6 +87,8 @@ class MenuEventSubscriber extends EntityManagerEventSubscriber $this->entityManager->flush(); $this->nodeRepository->persistAsFirstChild($childNode, $rootNode); + + $this->cacheManager->cleanAll(); } public function onUpdate(EntityManagerEvent $event) diff --git a/core/EventSuscriber/Site/NodeEventSubscriber.php b/core/EventSuscriber/Site/NodeEventSubscriber.php index cd1e252..215ff41 100644 --- a/core/EventSuscriber/Site/NodeEventSubscriber.php +++ b/core/EventSuscriber/Site/NodeEventSubscriber.php @@ -13,6 +13,7 @@ use App\Core\Slugify\CodeSlugify; use App\Core\Slugify\RouteParameterSlugify; use App\Core\Slugify\Slugify; use Symfony\Component\HttpKernel\KernelInterface; +use function Symfony\Component\String\u; /** * class NodeEventSubscriber. @@ -83,11 +84,13 @@ class NodeEventSubscriber extends EntityManagerEventSubscriber } $parameters = $node->getParameters(); + $routeParameters = []; foreach ($parameters as $key => $parameter) { $parameter['name'] = $this->routeParameterSlugify->slugify($parameter['name']); $routeParameter = sprintf('{%s}', $parameter['name']); $regex = '/'.preg_quote($routeParameter).'/'; + $routeParameters[] = $parameter['name']; if (!preg_match($regex, $generatedUrl)) { $generatedUrl .= '/'.$routeParameter; @@ -96,6 +99,22 @@ class NodeEventSubscriber extends EntityManagerEventSubscriber $parameters[$key] = $parameter; } + preg_match_all('/\{(.*)\}/isU', $generatedUrl, $matches, PREG_SET_ORDER); + + foreach ($matches as $match) { + if (!in_array($match[1], $routeParameters)) { + $parameters[] = [ + 'name' => $this->routeParameterSlugify->slugify($match[1]), + 'defaultValue' => null, + 'requirement' => null, + ]; + } + } + + if (!u($generatedUrl)->startsWith('https://') && !u($generatedUrl)->startsWith('http://')) { + $generatedUrl = str_replace('//', '/', $generatedUrl); + } + $node->setParameters($parameters); $attributes = $node->getAttributes(); diff --git a/core/EventSuscriber/Site/SiteEventSubscriber.php b/core/EventSuscriber/Site/SiteEventSubscriber.php index cf95a5d..6c6a8c3 100644 --- a/core/EventSuscriber/Site/SiteEventSubscriber.php +++ b/core/EventSuscriber/Site/SiteEventSubscriber.php @@ -8,10 +8,9 @@ use App\Core\Entity\Site\Navigation; use App\Core\Entity\Site\Node; use App\Core\Event\EntityManager\EntityManagerEvent; use App\Core\EventSuscriber\EntityManagerEventSubscriber; -use Symfony\Bundle\FrameworkBundle\Console\Application; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\KernelInterface; +use App\Core\Cache\SymfonyCacheManager; /** * class SiteEventSubscriber. @@ -21,10 +20,12 @@ use Symfony\Component\HttpKernel\KernelInterface; class SiteEventSubscriber extends EntityManagerEventSubscriber { protected KernelInterface $kernel; + protected SymfonyCacheManager $cacheManager; - public function __construct(KernelInterface $kernel) + public function __construct(KernelInterface $kernel, SymfonyCacheManager $cacheManager) { $this->kernel = $kernel; + $this->cacheManager = $cacheManager; } public function support(EntityInterface $entity) @@ -38,7 +39,11 @@ class SiteEventSubscriber extends EntityManagerEventSubscriber return; } - $this->cleanCache(); + if ($event->getEntity() instanceof Node) { + $this->cacheManager->cleanRouting(); + } else { + $this->cacheManager->cleanAll(); + } } public function onCreate(EntityManagerEvent $event) @@ -50,17 +55,4 @@ class SiteEventSubscriber extends EntityManagerEventSubscriber { return $this->onUpdate($event); } - - protected function cleanCache() - { - $application = new Application($this->kernel); - $application->setAutoExit(false); - - $input = new ArrayInput([ - 'command' => 'cache:clear', - ]); - - $output = new BufferedOutput(); - $application->run($input, $output); - } } diff --git a/core/Resources/translations/messages.fr.yaml b/core/Resources/translations/messages.fr.yaml index c3de162..bc61624 100644 --- a/core/Resources/translations/messages.fr.yaml +++ b/core/Resources/translations/messages.fr.yaml @@ -67,7 +67,7 @@ "New parameter": "Nouveau paramètre" "New attribut": "Nouveau attribut" "New menu": "Nouveau menu" -"New": "Nouveau menu" +"New": "Nouveau" "No action": "Aucune action" "Route name: %routeName%": "Nom de la route : %routeName%" "Content": "Contenu" @@ -120,3 +120,4 @@ "Enter your account username or email address. An e-mail will be sent to you to initiate the password change procedure. ": "Saisissez le nom d'utilisateur ou l'adresse e-mail de votre compte. Un e-mail vous sera envoyé pour enclancher la procédure de changement de mot de passe." "Show the login page": "Afficher la page de connexion" "A password reset request has been made. If you are the source of this request, click on the link below or copy and paste the address if the link does not work.": "Une demande de réinitialisation de mot de passe a été réalisée. Si vous êtes à l'origine de cette demande, cliquer sur le lien ci-dessous ou copier et coller l'adresse si le lien ne fonctionne pas." +"Edit the routing": "Éditez le routage" diff --git a/core/Resources/views/form/bootstrap_4_form_theme.html.twig b/core/Resources/views/form/bootstrap_4_form_theme.html.twig index cb8652f..d309a9a 100644 --- a/core/Resources/views/form/bootstrap_4_form_theme.html.twig +++ b/core/Resources/views/form/bootstrap_4_form_theme.html.twig @@ -6,7 +6,7 @@ {% set value = form.vars.data %} {% if value %} - {% if value and value.extension in ['jpg', 'gif', 'png'] %} + {% if value and value.extension in ['jpeg', 'jpg', 'gif', 'png'] %}
diff --git a/core/Resources/views/pager/sliding.html.twig b/core/Resources/views/pager/sliding.html.twig new file mode 100644 index 0000000..409417d --- /dev/null +++ b/core/Resources/views/pager/sliding.html.twig @@ -0,0 +1,76 @@ +{% if pageCount > 1 %} +
    + {% if previous is defined %} +
  • + +
  • + {% else %} +
  • + + + +
  • + {% endif %} + + {% if startPage > 1 %} +
  • + 1 +
  • + {% if startPage == 3 %} +
  • + 2 +
  • + {% elseif startPage != 2 %} +
  • + +
  • + {% endif %} + {% endif %} + + {% for page in pagesInRange %} + {% if page != current %} +
  • + {{ page }} +
  • + {% else %} +
  • + {{ page }} +
  • + {% endif %} + + {% endfor %} + + {% if pageCount > endPage %} + {% if pageCount > (endPage + 1) %} + {% if pageCount > (endPage + 2) %} +
  • + +
  • + {% else %} +
  • + {{ pageCount -1 }} +
  • + {% endif %} + {% endif %} +
  • + {{ pageCount }} +
  • + {% endif %} + + {% if next is defined %} +
  • + +
  • + {% else %} +
  • + + + +
  • + {% endif %} +
+{% endif %} diff --git a/core/Resources/views/site/navigation_admin/index.html.twig b/core/Resources/views/site/navigation_admin/index.html.twig index f388a67..347b15e 100644 --- a/core/Resources/views/site/navigation_admin/index.html.twig +++ b/core/Resources/views/site/navigation_admin/index.html.twig @@ -1,7 +1,7 @@ {% extends '@Core/admin/layout.html.twig' %} {% block body %} -
+

diff --git a/core/Resources/views/site/page_admin/index.html.twig b/core/Resources/views/site/page_admin/index.html.twig index 7101be6..58876b5 100644 --- a/core/Resources/views/site/page_admin/index.html.twig +++ b/core/Resources/views/site/page_admin/index.html.twig @@ -1,7 +1,7 @@ {% extends '@Core/admin/layout.html.twig' %} {% block body %} -
+

{{ 'Pages'|trans }}

diff --git a/core/Resources/views/site/tree_admin/navigation.html.twig b/core/Resources/views/site/tree_admin/navigation.html.twig index f4ce512..9a17559 100644 --- a/core/Resources/views/site/tree_admin/navigation.html.twig +++ b/core/Resources/views/site/tree_admin/navigation.html.twig @@ -113,10 +113,8 @@ {% if node.hasExternalUrl %} - + - - {{ node.url }} {% else %} {% if node.parameters|length %} @@ -124,9 +122,19 @@ {{ node.url }} {% else %} - - {{ node.url }} - + {% set url = safe_node_url(node) %} + + {% if url %} + + {{ node.url }} + + {% endif %} + + {% if url is same as(null) %} + + + + {% endif %} {% endif %} {% if node.controller %} diff --git a/core/Resources/views/user/user_admin/index.html.twig b/core/Resources/views/user/user_admin/index.html.twig index 9b130e8..cd5796a 100644 --- a/core/Resources/views/user/user_admin/index.html.twig +++ b/core/Resources/views/user/user_admin/index.html.twig @@ -1,7 +1,7 @@ {% extends '@Core/admin/layout.html.twig' %} {% block body %} -
+

{{ 'Users'|trans }}

diff --git a/core/Router/SiteRouteLoader.php b/core/Router/SiteRouteLoader.php index 885d765..4646528 100644 --- a/core/Router/SiteRouteLoader.php +++ b/core/Router/SiteRouteLoader.php @@ -39,6 +39,10 @@ class SiteRouteLoader extends Loader continue; } + if (null === $node->getUrl()) { + continue; + } + if ($node->hasExternalUrl()) { continue; } diff --git a/core/Sitemap/SitemapBuilder.php b/core/Sitemap/SitemapBuilder.php index ba6f02a..30a59c5 100644 --- a/core/Sitemap/SitemapBuilder.php +++ b/core/Sitemap/SitemapBuilder.php @@ -38,10 +38,6 @@ class SitemapBuilder foreach ($rootNode->getAllChildren() as $node) { $parameters = $node->getSitemapParameters(); - if (!$parameters['isVisible']) { - continue; - } - if ($node->hasExternalUrl()) { continue; } diff --git a/core/Twig/Extension/RoutingExtension.php b/core/Twig/Extension/RoutingExtension.php new file mode 100644 index 0000000..287ea93 --- /dev/null +++ b/core/Twig/Extension/RoutingExtension.php @@ -0,0 +1,125 @@ +generator = $generator; + } + + /** + * {@inheritdoc} + */ + public function getFunctions(): array + { + return [ + new TwigFunction('node_url', [$this, 'getNodeUrl'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), + new TwigFunction('node_path', [$this, 'getNodePath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), + new TwigFunction('safe_node_url', [$this, 'getSafeNodeUrl'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), + new TwigFunction('safe_node_path', [$this, 'getSafeNodePath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), + new TwigFunction('safe_url', [$this, 'getSafeUrl'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), + new TwigFunction('safe_path', [$this, 'getSafePath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), + ]; + } + + public function getSafePath(string $route, array $parameters = [], bool $relative = false): ?string + { + try { + return $this->generator->generate( + $route, + $parameters, + $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH + ); + } catch (\Exception $e) { + return null; + } + } + + public function getSafeUrl(string $route, array $parameters = [], bool $schemeRelative = false): ?string + { + try { + return $this->generator->generate( + $route, + $parameters, + $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL + ); + } catch (\Exception $e) { + return null; + } + } + + public function getNodePath(Node $node, array $parameters = [], bool $relative = false): ?string + { + if ($node->hasExternalUrl()) { + return $node->getUrl(); + } + + return $this->generator->generate( + $node->getRouteName(), + $parameters, + $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH + ); + } + + public function getNodeUrl(Node $node, array $parameters = [], bool $schemeRelative = false): ?string + { + if ($node->hasExternalUrl()) { + return $node->getUrl(); + } + + return $this->generator->generate( + $node->getRouteName(), + $parameters, + $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL + ); + } + + public function getSafeNodePath(Node $node, array $parameters = [], bool $relative = false): ?string + { + try { + return $this->getNodePath($node, $parameters, $relative); + } catch (\Exception $e) { + return null; + } + } + + public function getSafeNodeUrl(Node $node, array $parameters = [], bool $schemeRelative = false): ?string + { + try { + return $this->getNodeUrl($node, $parameters, $schemeRelative); + } catch (\Exception $e) { + return null; + } + } + + /** + * @see Symfony\Bridge\Twig\Extension\RoutingExtension::isUrlGenerationSafe + */ + public function isUrlGenerationSafe(TwigNode $argsNode): array + { + // support named arguments + $paramsNode = $argsNode->hasNode('parameters') ? $argsNode->getNode('parameters') : ( + $argsNode->hasNode(1) ? $argsNode->getNode(1) : null + ); + + if (null === $paramsNode || $paramsNode instanceof ArrayExpression && \count($paramsNode) <= 2 && + (!$paramsNode->hasNode(1) || $paramsNode->getNode(1) instanceof ConstantExpression) + ) { + return ['html']; + } + + return []; + } +} diff --git a/package.json b/package.json index d8ad923..1b9bd66 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,13 @@ "bootstrap": "^4.3.1", "choices.js": "^9.0.1", "jquery": "^3.6.0", + "particles.js": "^2.0.0", "popper.js": "^1.16.0", "qrcodejs": "^1.0.0", + "simplemde": "^1.11.2", "tinymce": "^5.7.1", "vanillajs-datepicker": "^1.1.2", + "wire.css": "^1.2.5", "zxcvbn": "^4.4.2" } } diff --git a/src/Command/MigrateDataCommand.php b/src/Command/MigrateDataCommand.php new file mode 100644 index 0000000..9a87e73 --- /dev/null +++ b/src/Command/MigrateDataCommand.php @@ -0,0 +1,110 @@ +entityManager = $entityManager; + + parent::__construct(); + } + + protected function configure() + { + $this + ->setDescription(self::$defaultDescription) + // ->addArgument('arg1', InputArgument::OPTIONAL, 'Argument description') + // ->addOption('option1', null, InputOption::VALUE_NONE, 'Option description') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + require 'data/category.php'; + require 'data/comment.php'; + require 'data/post.php'; + require 'data/post_has_category.php'; + + $categories = []; + $posts = []; + $comments = []; + + foreach ($oldCategories as $v) { + $category = new Category(); + $category->id = (int) $v['id']; + $category->setTitle($v['title']); + $category->setSubTitle(null); + $category->setDescription($v['description'] ?? ''); + $category->setIsActive((bool) $v['active']); + + $categories[$category->id] = $category; + + $this->entityManager->create($category); + } + + foreach ($oldPosts as $v) { + $post = new Post(); + $post->id = (int) $v['id']; + $post->setTitle($v['title']); + $post->setContent(str_replace("\r\n", "\n", $v['content'])); + $post->setContentFormat($v['content_format']); + $post->setTags(explode(',', str_replace(', ', ',', $v['tags']))); + $post->setStatus((int) $v['active']); + $post->setImage($v['picture'] ? ('uploads/posts/2007-2021/'.$v['picture']) : null); + $post->setCreatedAt(new \DateTime($v['created_at'])); + $post->setPublishedAt($v['published_at'] ? new \DateTime($v['published_at']) : null); + $post->setIsQuick((int) $v['quick']); + $post->setQuickUrl($v['quick_url']); + $post->setQuickImage($v['quick_image']); + $post->setQuickVideo($v['quick_video']); + $post->setQuickVideoWidth($v['quick_video_width']); + $post->setQuickVideoHeight($v['quick_video_height']); + $post->setSlug($v['slug']); + + $posts[$post->id] = $post; + + $this->entityManager->create($post); + } + + foreach ($post_has_category as $v) { + $posts[(int) $v['post_id']]->addCategory($categories[(int) $v['category_id']]); + + $this->entityManager->update($posts[(int) $v['post_id']]); + } + + foreach ($oldComments as $v) { + $comment = new Comment(); + $comment->id = (int) $v['id']; + $comment->setPost($posts[(int) $v['post_id']]); + $comment->setParentComment($comments[(int) $v['parent_comment_id']] ?? null); + $comment->setAuthor($v['author']); + $comment->setWebsite($v['website']); + $comment->setEmail($v['email']); + $comment->setContent(str_replace("\r\n", "\n", $v['content'])); + $comment->setIsActive((bool) $v['active']); + $comment->setCreatedAt(new \DateTime($v['created_at'])); + + $comments[$comment->id] = $comment; + + $this->entityManager->create($comment); + } + + return Command::SUCCESS; + } +} diff --git a/src/Controller/Blog/CategoryAdminController.php b/src/Controller/Blog/CategoryAdminController.php new file mode 100644 index 0000000..4c8d383 --- /dev/null +++ b/src/Controller/Blog/CategoryAdminController.php @@ -0,0 +1,124 @@ +paginate($page); + + return $this->render('blog/category_admin/index.html.twig', [ + 'pager' => $pager, + ]); + } + + /** + * @Route("/new", name="admin_blog_category_new") + */ + public function new(EntityFactory $factory, EntityManager $entityManager, Request $request): Response + { + $entity = $factory->create(); + $form = $this->createForm(EntityType::class, $entity); + + if ($request->isMethod('POST')) { + $form->handleRequest($request); + + if ($form->isValid()) { + $entityManager->create($entity); + $this->addFlash('success', 'Donnée enregistrée.'); + + return $this->redirectToRoute('admin_blog_category_edit', [ + 'entity' => $entity->getId(), + ]); + } + $this->addFlash('warning', 'Le formulaire est invalide.'); + } + + return $this->render('blog/category_admin/new.html.twig', [ + 'form' => $form->createView(), + 'entity' => $entity, + ]); + } + + /** + * @Route("/edit/{entity}", name="admin_blog_category_edit") + */ + public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response + { + $form = $this->createForm(EntityType::class, $entity); + + if ($request->isMethod('POST')) { + $form->handleRequest($request); + + if ($form->isValid()) { + $entityManager->update($entity); + $this->addFlash('success', 'Donnée enregistrée.'); + + return $this->redirectToRoute('admin_blog_category_edit', [ + 'entity' => $entity->getId(), + ]); + } + $this->addFlash('warning', 'Le formulaire est invalide.'); + } + + return $this->render('blog/category_admin/edit.html.twig', [ + 'form' => $form->createView(), + 'entity' => $entity, + ]); + } + + /** + * @Route("/show/{entity}", name="admin_blog_category_show") + */ + public function show(Entity $entity, PostRepositoryQuery $postQuery): Response + { + $posts = $postQuery->create() + ->orderBy('.publishedAt', 'DESC') + ->orderBy('.createdAt', 'DESC') + ->inCategory($entity) + ->paginate(1, 10) + ; + + return $this->render('blog/category_admin/show.html.twig', [ + 'entity' => $entity, + 'posts' => $posts, + ]); + } + + /** + * @Route("/delete/{entity}", name="admin_blog_category_delete", methods={"DELETE"}) + */ + public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response + { + if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) { + $entityManager->delete($entity); + + $this->addFlash('success', 'Données supprimée..'); + } + + return $this->redirectToRoute('admin_blog_category_index'); + } + + public function getSection(): string + { + return 'blog_category'; + } +} diff --git a/src/Controller/Blog/CategoryController.php b/src/Controller/Blog/CategoryController.php new file mode 100644 index 0000000..42dd7b7 --- /dev/null +++ b/src/Controller/Blog/CategoryController.php @@ -0,0 +1,42 @@ +categoryQuery = $categoryQuery; + } + + public function categories(): Response + { + $entities = $this->createQuery()->find(); + + return $this->defaultRender('blog/post/categories.html.twig', [ + 'categories' => $entities, + ]); + } + + public function createQuery(): CategoryRepositoryQuery + { + return $this->categoryQuery->create() + ->orderBy('.title', 'ASC') + ->andWhere('.isActive = 1') + ; + } +} diff --git a/src/Controller/Blog/PostAdminController.php b/src/Controller/Blog/PostAdminController.php new file mode 100644 index 0000000..0184a6f --- /dev/null +++ b/src/Controller/Blog/PostAdminController.php @@ -0,0 +1,176 @@ +updateFilters($request, $session); + + $pager = $query + ->orderBy('.id', 'DESC') + ->useFilters($this->filters) + ->paginate($page); + + return $this->render('blog/post_admin/index.html.twig', [ + 'pager' => $pager, + 'hasFilters' => !empty($this->filters), + ]); + } + + /** + * @Route("/new", name="admin_blog_post_new") + */ + public function new(EntityFactory $factory, EntityManager $entityManager, Request $request): Response + { + $entity = $factory->create($this->getUser()); + $form = $this->createForm(EntityType::class, $entity); + + if ($request->isMethod('POST')) { + $form->handleRequest($request); + + if ($form->isValid()) { + $entityManager->create($entity); + $this->addFlash('success', 'The data has been saved.'); + + return $this->redirectToRoute('admin_blog_post_edit', [ + 'entity' => $entity->getId(), + ]); + } + $this->addFlash('warning', 'The form is not valid.'); + } + + return $this->render('blog/post_admin/new.html.twig', [ + 'form' => $form->createView(), + 'entity' => $entity, + ]); + } + + /** + * @Route("/edit/{entity}", name="admin_blog_post_edit") + */ + public function edit(Entity $entity, EntityManager $entityManager, FileUploadHandler $fileUpload, Request $request): Response + { + $form = $this->createForm(EntityType::class, $entity); + + if ($request->isMethod('POST')) { + $form->handleRequest($request); + + if ($form->isValid()) { + $directory = 'uploads/post/'.date('Y'); + + $fileUpload->handleForm( + $form->get('image')->getData(), + $directory, + function ($filename) use ($entity, $directory) { + $entity->setImage($directory.'/'.$filename); + } + ); + + $entityManager->update($entity); + $this->addFlash('success', 'The form is not valid.'); + + return $this->redirectToRoute('admin_blog_post_edit', [ + 'entity' => $entity->getId(), + ]); + } + $this->addFlash('warning', 'The form is not valid.'); + } + + return $this->render('blog/post_admin/edit.html.twig', [ + 'form' => $form->createView(), + 'entity' => $entity, + ]); + } + + /** + * @Route("/show/{entity}", name="admin_blog_post_show") + */ + public function show(Entity $entity): Response + { + return $this->render('blog/post_admin/show.html.twig', [ + 'entity' => $entity, + ]); + } + + /** + * @Route("/filters", name="admin_blog_post_filters") + */ + public function filters(Session $session): Response + { + $form = $this->createForm(PostFilterType::class); + $form->submit($session->get('post_filter')); + + return $this->render('blog/post_admin/filters.html.twig', [ + 'form' => $form->createView(), + ]); + } + + /** + * @Route("/delete/{entity}", name="admin_blog_post_delete", methods={"DELETE"}) + */ + public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response + { + if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) { + $entityManager->delete($entity); + + $this->addFlash('success', 'The data has been removed.'); + } + + return $this->redirectToRoute('admin_blog_post_index'); + } + + protected function updateFilters(Request $request, Session $session) + { + if ($request->query->has('post_filter')) { + $filters = $request->query->get('post_filter'); + + if ($filters === '0') { + $filters = []; + } + } elseif ($session->has('post_filter')) { + $filters = $session->get('post_filter'); + } else { + $filters = []; + } + + $form = $this->createForm(PostFilterType::class); + $form->submit($filters); + + if (empty($filters)) { + $this->filters = $filters; + $session->set('post_filter', $this->filters); + } elseif ($form->isValid()) { + $this->filters = $form->getData(); + $session->set('post_filter', $this->filters); + } + } + + public function getSection(): string + { + return 'blog_post'; + } +} diff --git a/src/Controller/Blog/PostController.php b/src/Controller/Blog/PostController.php new file mode 100644 index 0000000..5d64b84 --- /dev/null +++ b/src/Controller/Blog/PostController.php @@ -0,0 +1,102 @@ +postQuery = $postQuery; + } + + /** + * @UrlGenerator(service=PostGenerator::class, method="post") + */ + public function post(Post $post, string $slug): Response + { + if (Post::DRAFT === $post->getStatus() && !$this->getUser()) { + throw $this->createNotFoundException(); + } + + if ($slug !== $post->getSlug()) { + return $this->redirectToRoute( + $this->siteRequest->getNode()->getRouteName(), + [ + 'post' => $post->getId(), + 'slug' => $post->getSlug(), + ] + ); + } + + return $this->defaultRender('blog/post/post.html.twig', [ + 'post' => $post, + ]); + } + + public function posts(int $page = 1): Response + { + $entities = $this->createQuery() + ->paginate($page, 5) + ; + + return $this->defaultRender('blog/post/posts.html.twig', [ + 'pager' => $entities, + ]); + } + + /** + * @UrlGenerator(service=PostGenerator::class, method="category") + */ + public function category(Category $category, string $slug, int $page = 1): Response + { + $entities = $this->createQuery() + ->inCategory($category) + ->paginate($page, 5) + ; + + if (!$category->getIsActive() && !$this->getUser()) { + throw $this->createNotFoundException(); + } + + if ($slug !== $category->getSlug()) { + return $this->redirectToRoute( + $this->siteRequest->getNode()->getRouteName(), + [ + 'category' => $category->getId(), + 'slug' => $category->getSlug(), + ] + ); + } + + return $this->defaultRender('blog/post/category.html.twig', [ + 'category' => $category, + 'pager' => $entities, + ]); + } + + public function tag(string $tag, int $page = 1): Response + { + } + + public function createQuery(): PostRepositoryQuery + { + return $this->postQuery->create() + ->orderBy('.publishedAt', 'DESC') + ->published() + ; + } +} diff --git a/src/Controller/ContactController.php b/src/Controller/ContactController.php new file mode 100644 index 0000000..9eedf0c --- /dev/null +++ b/src/Controller/ContactController.php @@ -0,0 +1,17 @@ +defaultRender($this->siteRequest->getPage()->getTemplate(), [ + ]); + } +} diff --git a/src/Entity/Blog/Category.php b/src/Entity/Blog/Category.php new file mode 100644 index 0000000..42a673d --- /dev/null +++ b/src/Entity/Blog/Category.php @@ -0,0 +1,153 @@ +posts = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(string $title): self + { + $this->title = $title; + + return $this; + } + + public function getSubTitle(): ?string + { + return $this->subTitle; + } + + public function setSubTitle(?string $subTitle): self + { + $this->subTitle = $subTitle; + + return $this; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(string $description): self + { + $this->description = $description; + + return $this; + } + + public function getSlug(): ?string + { + return $this->slug; + } + + public function setSlug(?string $slug): self + { + $this->slug = $slug; + + return $this; + } + + /** + * @return Collection|Post[] + */ + public function getPosts(): Collection + { + return $this->posts; + } + + public function addPost(Post $post): self + { + if (!$this->posts->contains($post)) { + $this->posts[] = $post; + $post->addCategory($this); + } + + return $this; + } + + public function removePost(Post $post): self + { + if ($this->posts->removeElement($post)) { + $post->removeCategory($this); + } + + return $this; + } + + public function getIsActive(): ?bool + { + return $this->isActive; + } + + public function setIsActive(?bool $isActive): self + { + $this->isActive = $isActive; + + return $this; + } +} diff --git a/src/Entity/Blog/Comment.php b/src/Entity/Blog/Comment.php new file mode 100644 index 0000000..e43263c --- /dev/null +++ b/src/Entity/Blog/Comment.php @@ -0,0 +1,190 @@ +comments = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getAuthor(): ?string + { + return $this->author; + } + + public function setAuthor(string $author): self + { + $this->author = $author; + + return $this; + } + + public function getWebsite(): ?string + { + return $this->website; + } + + public function setWebsite(?string $website): self + { + $this->website = $website; + + return $this; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function setEmail(?string $email): self + { + $this->email = $email; + + return $this; + } + + public function getContent(): ?string + { + return $this->content; + } + + public function setContent(?string $content): self + { + $this->content = $content; + + return $this; + } + + public function getIsActive(): ?bool + { + return $this->isActive; + } + + public function setIsActive(bool $isActive): self + { + $this->isActive = $isActive; + + return $this; + } + + public function getPost(): ?Post + { + return $this->post; + } + + public function setPost(?Post $post): self + { + $this->post = $post; + + return $this; + } + + public function getParentComment(): ?self + { + return $this->parentComment; + } + + public function setParentComment(?self $parentComment): self + { + $this->parentComment = $parentComment; + + return $this; + } + + /** + * @return Collection|self[] + */ + public function getComments(): Collection + { + return $this->comments; + } + + public function addComment(self $comment): self + { + if (!$this->comments->contains($comment)) { + $this->comments[] = $comment; + $comment->setParentComment($this); + } + + return $this; + } + + public function removeComment(self $comment): self + { + if ($this->comments->removeElement($comment)) { + // set the owning side to null (unless already changed) + if ($comment->getParentComment() === $this) { + $comment->setParentComment(null); + } + } + + return $this; + } +} diff --git a/src/Entity/Blog/Post.php b/src/Entity/Blog/Post.php new file mode 100644 index 0000000..2e0ab5d --- /dev/null +++ b/src/Entity/Blog/Post.php @@ -0,0 +1,368 @@ +categories = new ArrayCollection(); + $this->comments = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(string $title): self + { + $this->title = $title; + + return $this; + } + + public function getContent(): ?string + { + return $this->content; + } + + public function setContent(?string $content): self + { + $this->content = $content; + + return $this; + } + + public function getStatus(): ?int + { + return $this->status; + } + + public function setStatus(int $status): self + { + $this->status = $status; + + return $this; + } + + public function getImage() + { + if (is_string($this->image)) { + if (file_exists($this->image)) { + return new File($this->image); + } + + return null; + } + + return $this->image; + } + + public function setImage(?string $image, bool $force = false): self + { + if (false === $force && null === $image) { + return $this; + } + + $this->image = $image; + + return $this; + } + + public function getSlug(): ?string + { + return $this->slug; + } + + public function setSlug(?string $slug): self + { + $this->slug = $slug; + + return $this; + } + + public function getPublishedAt(): ?\DateTimeInterface + { + return $this->publishedAt; + } + + public function setPublishedAt(?\DateTimeInterface $publishedAt): self + { + $this->publishedAt = $publishedAt; + + return $this; + } + + public function getImageCaption(): ?string + { + return $this->imageCaption; + } + + public function setImageCaption(?string $imageCaption): self + { + $this->imageCaption = $imageCaption; + + return $this; + } + + /** + * @return Collection|Category[] + */ + public function getCategories(): Collection + { + return $this->categories; + } + + public function addCategory(Category $category): self + { + if (!$this->categories->contains($category)) { + $this->categories[] = $category; + } + + return $this; + } + + public function removeCategory(Category $category): self + { + $this->categories->removeElement($category); + + return $this; + } + + public function getContentFormat(): ?string + { + return $this->contentFormat; + } + + public function setContentFormat(string $contentFormat): self + { + $this->contentFormat = $contentFormat; + + return $this; + } + + public function getTags(): ?array + { + return $this->tags; + } + + public function setTags(?array $tags): self + { + $this->tags = $tags; + + return $this; + } + + public function getIsQuick(): ?bool + { + return $this->isQuick; + } + + public function setIsQuick(bool $isQuick): self + { + $this->isQuick = $isQuick; + + return $this; + } + + public function getQuickUrl(): ?string + { + return $this->quickUrl; + } + + public function setQuickUrl(?string $quickUrl): self + { + $this->quickUrl = $quickUrl; + + return $this; + } + + public function getQuickImage(): ?string + { + return $this->quickImage; + } + + public function setQuickImage(?string $quickImage): self + { + $this->quickImage = $quickImage; + + return $this; + } + + public function getQuickVideo(): ?string + { + return $this->quickVideo; + } + + public function setQuickVideo(?string $quickVideo): self + { + $this->quickVideo = $quickVideo; + + return $this; + } + + public function getQuickVideoWidth(): ?int + { + return $this->quickVideoWidth; + } + + public function setQuickVideoWidth(?int $quickVideoWidth): self + { + $this->quickVideoWidth = $quickVideoWidth; + + return $this; + } + + public function getQuickVideoHeight(): ?int + { + return $this->quickVideoHeight; + } + + public function setQuickVideoHeight(?int $quickVideoHeight): self + { + $this->quickVideoHeight = $quickVideoHeight; + + return $this; + } + + /** + * @return Collection|Comment[] + */ + public function getComments(): Collection + { + return $this->comments; + } + + public function addComment(Comment $comment): self + { + if (!$this->comments->contains($comment)) { + $this->comments[] = $comment; + $comment->setPost($this); + } + + return $this; + } + + public function removeComment(Comment $comment): self + { + if ($this->comments->removeElement($comment)) { + // set the owning side to null (unless already changed) + if ($comment->getPost() === $this) { + $comment->setPost(null); + } + } + + return $this; + } +} diff --git a/src/Entity/Page/ContactPage.php b/src/Entity/Page/ContactPage.php new file mode 100644 index 0000000..3e90d0d --- /dev/null +++ b/src/Entity/Page/ContactPage.php @@ -0,0 +1,19 @@ +add( - 'title', - TextBlockType::class, - [ - 'label' => 'Titre', - 'required' => true, - 'attr' => [ - ], - 'constraints' => [ - ], - ] - ); + parent::buildForm($builder); $builder->add( 'content', TextareaBlockType::class, [ - 'label' => 'Content', + 'label' => 'Contenu', 'options' => [ 'attr' => [ - 'data-tinymce' => '', - 'rows' => '18', + 'data-simplemde' => '', + 'rows' => '50', ], 'constraints' => [ ], ], ] ); - - $builder->add( - 'image', - ImageBlockType::class, - [ - 'label' => 'Image', - 'options' => [ - 'attr' => [ - ], - 'constraints' => [ - ], - ], - ] - ); - } - - public function setTitle(Block $block) - { - return $this->setBlock($block); - } - - public function getTitle() - { - return $this->getBlock('title'); } public function setContent(Block $block) diff --git a/src/Entity/Page/TitledPage.php b/src/Entity/Page/TitledPage.php new file mode 100644 index 0000000..3af5048 --- /dev/null +++ b/src/Entity/Page/TitledPage.php @@ -0,0 +1,67 @@ +add( + 'title', + TextBlockType::class, + [ + 'label' => 'Titre', + 'required' => true, + 'attr' => [ + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'subTitle', + TextBlockType::class, + [ + 'label' => 'Sous-titre', + 'required' => true, + 'attr' => [ + ], + 'constraints' => [ + ], + ] + ); + } + + public function setTitle(Block $block) + { + return $this->setBlock($block); + } + + public function getTitle() + { + return $this->getBlock('title'); + } + + public function setSubTitle(Block $block) + { + return $this->setBlock($block); + } + + public function getSubTitle() + { + return $this->getBlock('subTitle'); + } +} diff --git a/src/EventSuscriber/Blog/CategoryEventSubscriber.php b/src/EventSuscriber/Blog/CategoryEventSubscriber.php new file mode 100644 index 0000000..fc9c3e9 --- /dev/null +++ b/src/EventSuscriber/Blog/CategoryEventSubscriber.php @@ -0,0 +1,51 @@ + + */ +class CategoryEventSubscriber extends EntityManagerEventSubscriber +{ + protected Slugify $slugify; + + public function __construct(Slugify $slugify) + { + $this->slugify = $slugify; + } + + public function support(EntityInterface $entity) + { + return $entity instanceof Category; + } + + public function onPreUpdate(EntityManagerEvent $event) + { + if (!$this->support($event->getEntity())) { + return; + } + + if ($event->getEntity()->getSlug()) { + return; + } + + $event->getEntity()->setSlug($this->slugify->slugify($event->getEntity()->getTitle())); + } + + public function onPreCreate(EntityManagerEvent $event) + { + return $this->onPreUpdate($event); + } +} diff --git a/src/EventSuscriber/Blog/PostEventSubscriber.php b/src/EventSuscriber/Blog/PostEventSubscriber.php new file mode 100644 index 0000000..79e11f4 --- /dev/null +++ b/src/EventSuscriber/Blog/PostEventSubscriber.php @@ -0,0 +1,94 @@ + + */ +class PostEventSubscriber extends EntityManagerEventSubscriber +{ + protected Filesystem $filesystem; + protected PostRepositoryQuery $query; + protected Slugify $slugify; + + public function __construct(Filesystem $filesystem, PostRepositoryQuery $query, Slugify $slugify) + { + $this->filesystem = $filesystem; + $this->query = $query; + $this->slugify = $slugify; + } + + public function support(EntityInterface $entity) + { + return $entity instanceof Post; + } + + public function onUpdate(EntityManagerEvent $event) + { + if (!$this->support($event->getEntity())) { + return; + } + + $this->removeOrphanUploads(); + } + + public function onDelete(EntityManagerEvent $event) + { + if (!$this->support($event->getEntity())) { + return; + } + + $this->removeOrphanUploads(); + } + + public function onPreUpdate(EntityManagerEvent $event) + { + if (!$this->support($event->getEntity())) { + return; + } + + if ($event->getEntity()->getSlug()) { + return; + } + + $event->getEntity()->setSlug($this->slugify->slugify($event->getEntity()->getTitle())); + } + + public function onPreCreate(EntityManagerEvent $event) + { + return $this->onPreUpdate($event); + } + + protected function removeOrphanUploads() + { + /* + $finder = new Finder(); + $finder->files()->in('uploads/post'); + + foreach ($finder as $file) { + $image = $file->getPathname(); + + $post = $this->query->create() + ->where('.image = :image') + ->setParameter(':image', $image) + ->findOne() + ; + + if (null === $post) { + $this->filesystem->remove($file->getRealPath()); + } + } + */ + } +} diff --git a/src/Factory/Blog/CategoryFactory.php b/src/Factory/Blog/CategoryFactory.php new file mode 100644 index 0000000..eb21c80 --- /dev/null +++ b/src/Factory/Blog/CategoryFactory.php @@ -0,0 +1,18 @@ + + */ +class CategoryFactory +{ + public function create(): Category + { + return new Category(); + } +} diff --git a/src/Factory/Blog/PostFactory.php b/src/Factory/Blog/PostFactory.php new file mode 100644 index 0000000..702256e --- /dev/null +++ b/src/Factory/Blog/PostFactory.php @@ -0,0 +1,25 @@ + + */ +class PostFactory +{ + public function create(): Post + { + $entity = new Post(); + + $entity + ->setStatus(0) + ; + + return $entity; + } +} diff --git a/src/Form/Blog/CategoryType.php b/src/Form/Blog/CategoryType.php new file mode 100644 index 0000000..72fbe1e --- /dev/null +++ b/src/Form/Blog/CategoryType.php @@ -0,0 +1,93 @@ +add( + 'title', + TextType::class, + [ + 'label' => 'Titre', + 'required' => true, + 'attr' => [ + ], + 'constraints' => [ + new NotBlank(), + ], + ] + ); + + $builder->add( + 'subTitle', + TextareaType::class, + [ + 'label' => 'Sous-titre', + 'required' => false, + 'attr' => [ + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'description', + TextareaType::class, + [ + 'label' => 'Description', + 'required' => false, + 'attr' => [ + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'slug', + TextType::class, + [ + 'label' => 'Slug', + 'required' => false, + 'help' => 'Laisser vide pour une génération automatique', + 'attr' => [ + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'isActive', + CheckboxType::class, + [ + 'label' => 'Activée', + 'required' => false, + 'attr' => [ + ], + 'constraints' => [ + ], + ] + ); + + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => Category::class, + ]); + } +} diff --git a/src/Form/Blog/Filter/PostFilterType.php b/src/Form/Blog/Filter/PostFilterType.php new file mode 100644 index 0000000..970ec86 --- /dev/null +++ b/src/Form/Blog/Filter/PostFilterType.php @@ -0,0 +1,122 @@ +add( + 'title', + TextType::class, + [ + 'label' => 'Titre', + 'required' => false, + 'attr' => [ + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'contentFormat', + ChoiceType::class, + [ + 'label' => 'Format', + 'required' => false, + 'choices' => [ + 'HTML' => 'html', + 'Markdown' => 'markdown', + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'category', + EntityType::class, + [ + 'label' => 'Catégories', + 'class' => Category::class, + 'choice_label' => 'title', + 'required' => false, + 'attr' => [ + 'data-jschoice' => '', + ], + 'query_builder' => function (EntityRepository $repo) { + return $repo->createQueryBuilder('a') + ->orderBy('a.title', 'ASC') + ; + }, + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'status', + ChoiceType::class, + [ + 'label' => 'Statut', + 'required' => false, + 'choices' => [ + 'Brouillon' => 0, + 'Publié' => 1, + ], + 'attr' => [ + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'isQuick', + ChoiceType::class, + [ + 'label' => 'Quick', + 'required' => false, + 'choices' => [ + 'Anyway' => null, + 'Yes' => true, + 'No' => false, + ], + 'attr' => [ + ], + 'constraints' => [ + ] + ] + ); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => null, + 'csrf_protection' => false, + ]); + } +} diff --git a/src/Form/Blog/PostType.php b/src/Form/Blog/PostType.php new file mode 100644 index 0000000..7c43722 --- /dev/null +++ b/src/Form/Blog/PostType.php @@ -0,0 +1,259 @@ +add( + 'title', + TextType::class, + [ + 'label' => 'Titre', + 'required' => true, + 'attr' => [ + ], + 'constraints' => [ + new NotBlank(), + ], + ] + ); + + $builder->add( + 'contentFormat', + ChoiceType::class, + [ + 'label' => 'Format', + 'required' => true, + 'choices' => [ + 'HTML' => 'html', + 'Markdown' => 'markdown', + ], + 'constraints' => [ + new NotBlank(), + ], + ] + ); + + $builder->add( + 'content', + TextareaType::class, + [ + 'label' => 'Contenu', + 'required' => false, + 'attr' => [ + 'data-simplemde' => '', + 'rows' => 20, + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'slug', + TextType::class, + [ + 'label' => 'Slug', + 'required' => false, + 'help' => 'Laisser vide pour une génération automatique', + 'attr' => [ + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'categories', + EntityType::class, + [ + 'label' => 'Catégories', + 'class' => Category::class, + 'choice_label' => 'title', + 'required' => false, + 'multiple' => true, + 'attr' => [ + 'data-jschoice' => '', + ], + 'query_builder' => function (EntityRepository $repo) { + return $repo->createQueryBuilder('a') + ->orderBy('a.title', 'ASC') + ; + }, + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'status', + ChoiceType::class, + [ + 'label' => 'Statut', + 'required' => true, + 'choices' => [ + 'Brouillon' => Post::DRAFT, + 'Publié' => Post::PUBLISHED, + ], + 'attr' => [ + ], + 'constraints' => [ + new NotBlank(), + ], + ] + ); + + $builder->add( + 'publishedAt', + DateType::class, + [ + 'label' => 'Date de publication', + 'required' => false, + 'html5' => true, + 'widget' => 'single_text', + 'attr' => [ + ], + 'constraints' => [ + new Date(), + ], + ] + ); + + $builder->add( + 'image', + FileType::class, + [ + 'label' => 'Image', + 'required' => false, + 'data_class' => null, + 'attr' => [ + ], + 'constraints' => [ + new Image(), + ], + ] + ); + + // $builder->add( + // 'tags', + // 'text', + // [ + // 'constraints' => [ + // ], + // ] + // ); + + $builder->add( + 'isQuick', + CheckboxType::class, + [ + 'label' => 'Quick', + 'required' => false, + 'attr' => [ + ], + 'constraints' => [ + ] + ] + ); + + $builder->add( + 'quickUrl', + TextType::class, + [ + 'label' => 'Url', + 'required' => false, + 'attr' => [ + ], + 'constraints' => [ + new Url(), + ], + ] + ); + + $builder->add( + 'quickImage', + TextType::class, + [ + 'label' => 'Image', + 'required' => false, + 'attr' => [ + ], + 'constraints' => [ + new Url(), + ], + ] + ); + + $builder->add( + 'quickVideo', + TextType::class, + [ + 'label' => 'Vidéo', + 'required' => false, + 'attr' => [ + ], + 'constraints' => [ + new Url(), + ], + ] + ); + + $builder->add( + 'quickVideoWidth', + NumberType::class, + [ + 'label' => 'Vidéo (largeur)', + 'required' => false, + 'attr' => [ + ], + 'constraints' => [ + new Range(['min' => 0]), + ], + ] + ); + + $builder->add( + 'quickVideoHeight', + NumberType::class, + [ + 'label' => 'Vidéo (hauteur)', + 'required' => false, + 'attr' => [ + ], + 'constraints' => [ + new Range(['min' => 0]), + ], + ] + ); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => Post::class, + ]); + } +} diff --git a/src/Markdown/Parser/Comment.php b/src/Markdown/Parser/Comment.php new file mode 100644 index 0000000..ed9b670 --- /dev/null +++ b/src/Markdown/Parser/Comment.php @@ -0,0 +1,94 @@ + + */ +class Comment extends MarkdownParser +{ + /** + * @var array Enabled features + */ + protected $features = array( + 'header' => true, + 'list' => true, + 'horizontal_rule' => false, + 'table' => false, + 'foot_note' => false, + 'fenced_code_block' => true, + 'abbreviation' => false, + 'definition_list' => false, + 'inline_link' => true, // [link text](url "optional title") + 'reference_link' => false, // [link text] [id] + 'shortcut_link' => false, // [link text] + 'images' => false, + 'html_block' => false, + 'block_quote' => true, + 'code_block' => true, + 'auto_link' => true, + 'auto_mailto' => false, + 'entities' => false, + 'no_html' => true, + ); + + /** + * Disable mailto unless auto_mailto. + */ + public function doAutoLinks($text) + { + if (!$this->features['auto_mailto']) { + return preg_replace_callback('{((https?|ftp|dict):[^\'">\s]+)}i', array(&$this, '_doAutoLinks_url_callback'), $text); + } + + return parent::doAutoLinks($text); + } + + /** + * Callback for setext headers. + * + * @param array $matches + * + * @return string + */ + protected function _doHeaders_callback_setext($matches) + { + if ($matches[3] == '-' && preg_match('{^- }', $matches[1])) { + return $matches[0]; + } + + $level = $matches[3][0] == '=' ? 1 : 2; + + $level += 2; + + $defaultId = is_callable($this->header_id_func) ? call_user_func($this->header_id_func, $matches[1]) : null; + + $attr = $this->doExtraAttributes("h$level", $dummy = &$matches[2], $defaultId); + $block = "
".$this->runSpanGamut($matches[1]).'
'; + + return "\n".$this->hashBlock($block)."\n\n"; + } + + /** + * Callback for atx headers. + * + * @param array $matches + * + * @return string + */ + protected function _doHeaders_callback_atx($matches) + { + $level = strlen($matches[1]); + + $defaultId = is_callable($this->header_id_func) ? call_user_func($this->header_id_func, $matches[2]) : null; + $attr = $this->doExtraAttributes("h$level", $dummy = &$matches[3], $defaultId); + $level += 2; + $block = "
".$this->runSpanGamut($matches[2]).'
'; + + return "\n".$this->hashBlock($block)."\n\n"; + } +} diff --git a/src/Markdown/Parser/Post.php b/src/Markdown/Parser/Post.php new file mode 100644 index 0000000..be58c9a --- /dev/null +++ b/src/Markdown/Parser/Post.php @@ -0,0 +1,123 @@ + + */ +class Post extends MarkdownParser +{ + /** + * {@inheritdoc} + */ + //protected $id_class_attr_catch_re = '\{((?'.'>[ ]*[#.a-z][-_:a-zA-Z0-9="\'\/\*-]+){1,})[ ]*\}'; + protected $id_class_attr_catch_re = '\{([^\}]+)\}'; + + /** + * {@inheritdoc} + */ + public function transformMarkdown($text) + { + $html = parent::transformMarkdown($text); + $html = str_replace(' role="doc-endnote"', '', $html); + + return $html; + } + + /** + * {@inheritdoc} + */ + protected function doExtraAttributes($tag_name, $attr, $defaultIdValue = null, $classes = array()) + { + if (empty($attr) && !$defaultIdValue && empty($classes)) { + return ''; + } + + // Split on components + preg_match_all('/[#.a-z][-_:a-zA-Z0-9]+/', $attr, $matches); + $elements = $matches[0]; + + // Handle classes and IDs (only first ID taken into account) + $attributes = array(); + $id = false; + foreach ($elements as $element) { + if ($element{0} == '.') { + $classes[] = substr($element, 1); + } elseif ($element{0} == '#') { + if ($id === false) { + $id = substr($element, 1); + } + } + } + + preg_match_all('/[#.]{0}([a-z-]+)=([:a-zA-Z0-9]+)/', $attr, $matches, PREG_SET_ORDER); + + foreach ($matches as $match) { + $attributes[] = sprintf('%s="%s"', $match[1], htmlspecialchars($match[2])); + } + + preg_match_all('/[#.]{0}([a-z-]+)=(["\']{1})(.+)\2/isUu', $attr, $matches, PREG_SET_ORDER); + + foreach ($matches as $match) { + $attributes[] = sprintf('%s="%s"', $match[1], htmlspecialchars($match[3])); + } + + if (!$id) { + $id = $defaultIdValue; + } + + // Compose attributes as string + $attr_str = ''; + if (!empty($id)) { + $attr_str .= ' id="'.$this->encodeAttribute($id).'"'; + } + if (!empty($classes)) { + $attr_str .= ' class="'.implode(' ', $classes).'"'; + } + if (!$this->no_markup && !empty($attributes)) { + $attr_str .= ' '.implode(' ', $attributes); + } + + return $attr_str; + } + + protected function _doFencedCodeBlocks_callback($matches) + { + $classname = &$matches[2]; + $attrs = &$matches[3]; + $codeblock = $matches[4]; + + if ($this->code_block_content_func) { + $codeblock = call_user_func($this->code_block_content_func, $codeblock, $classname); + } else { + $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES); + } + + $codeblock = str_replace( + ['<mark>', '</mark>'], + ['', ''], + $codeblock + ); + + $codeblock = preg_replace_callback('/^\n+/', + array($this, '_doFencedCodeBlocks_newlines'), $codeblock); + + $classes = array(); + if ($classname != '') { + if ($classname{0} == '.') { + $classname = substr($classname, 1); + } + $classes[] = $this->code_class_prefix.$classname; + } + $attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? 'pre' : 'code', $attrs, null, $classes); + $pre_attr_str = $this->code_attr_on_pre ? $attr_str : ''; + $code_attr_str = $this->code_attr_on_pre ? '' : $attr_str; + $codeblock = "$codeblock"; + + return "\n\n".$this->hashBlock($codeblock)."\n\n"; + } +} diff --git a/src/Repository/Blog/CategoryRepository.php b/src/Repository/Blog/CategoryRepository.php new file mode 100644 index 0000000..2703c9a --- /dev/null +++ b/src/Repository/Blog/CategoryRepository.php @@ -0,0 +1,15 @@ + + */ +class CategoryRepositoryQuery extends RepositoryQuery +{ + public function __construct(CategoryRepository $repository, PaginatorInterface $paginator) + { + parent::__construct($repository, 'c', $paginator); + } +} diff --git a/src/Repository/Blog/CommentRepository.php b/src/Repository/Blog/CommentRepository.php new file mode 100644 index 0000000..306d21f --- /dev/null +++ b/src/Repository/Blog/CommentRepository.php @@ -0,0 +1,50 @@ +createQueryBuilder('c') + ->andWhere('c.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('c.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?Comment + { + return $this->createQueryBuilder('c') + ->andWhere('c.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Repository/Blog/PostRepository.php b/src/Repository/Blog/PostRepository.php new file mode 100644 index 0000000..d6a3032 --- /dev/null +++ b/src/Repository/Blog/PostRepository.php @@ -0,0 +1,15 @@ + + */ +class PostRepositoryQuery extends RepositoryQuery +{ + public function __construct(PostRepository $repository, PaginatorInterface $paginator) + { + parent::__construct($repository, 'p', $paginator); + } + + public function inCategory(Category $category) + { + $c = 'c'.mt_rand(); + + $this + ->innerJoin('p.categories', $c) + ->andWhere($c.'.id = :category') + ->setParameter(':category', $category->getId()) + ; + + return $this; + } + + public function published() + { + return $this + ->andWhere('.status = 1') + ->andWhere('.publishedAt <= :now') + ->setParameter(':now', (new \DateTime('now'))->format('Y-m-d H:i:s')); + } + + public function useFilters(array $filters) + { + foreach ($filters as $name => $value) { + if ($value === null) { + continue; + } + + if (is_int($value)) { + $this->andWhere('.'.$name.' = :'.$name); + $this->setParameter(':'.$name, $value); + } elseif (is_string($value)) { + $this->andWhere('.'.$name.' LIKE :'.$name); + $this->setParameter(':'.$name, '%'.$value.'%'); + } else { + if ($name === 'category') { + $this->inCategory($value); + } + } + } + + return $this; + } +} diff --git a/src/Twig/Extension/BlogExtension.php b/src/Twig/Extension/BlogExtension.php new file mode 100644 index 0000000..2130d52 --- /dev/null +++ b/src/Twig/Extension/BlogExtension.php @@ -0,0 +1,191 @@ + array('html'))), + new TwigFilter('post', [$this, 'post'], array('is_safe' => array('html'))), + ); + } + + public function comment($text) + { + $text = htmlspecialchars(trim($text)).' '; + + $text = preg_replace_callback( + '`(s?(http|ftp|irc)+s?://[^\s\)]+)(\s|\))+`iU', + function ($data) { + return sprintf('%s%s', htmlspecialchars($data[1]), htmlspecialchars($data[1]), $data[3]); + }, + $text + ); + + return nl2br($text); + } + + public function post($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://mediaplayer.deblan.fr', 'https://mediaplayer.deblan.org', $text); + $text = str_replace('http://blog.deblan.fr', 'https://www.deblan.io', $text); + $text = str_replace('http://www.deblan.tv', 'https://www.deblan.io', $text); + + $text = preg_replace_callback( + '`]*)>(.*)]*)>`isU', + function ($data) { + if (preg_match('`^[ ]+`', $data[1])) { + return ''.nl2br(trim($data[2])).'

'; + } elseif (isset($data[1]) && $data[1] === '') { + return '

'.nl2br(trim($data[2])).'

'; + } else { + return $data[0]; + } + }, + $text + ); + + foreach (['centre', 'droite', 'flgauche', 'fldroite', 'clear'] as $tag) { + $regex = sprintf('`<%1$s>(.*)`', $tag); + + $text = preg_replace_callback( + $regex, + function ($data) { + return '
'.$data[1].'
'; + }, + $text + ); + } + + foreach (['titre', 'titre'] as $k => $tag) { + $regex = sprintf('`<%1$s>(.*)`', $tag); + + $text = preg_replace_callback( + $regex, + function ($data) use ($k) { + return '
'.$data[1].'
'; + }, + $text + ); + } + + foreach ([ + '`([0-9]+)`isU', + '``isU', + '`smiley:([a-zA-Z]*)`is', + '##isU', + "#onclick='window\.open\(this\.href\); return false'#", + '##isU', + '#<timestamp>#isU', + ] as $regex) { + $text = preg_replace($regex, '', $text); + } + + $text = preg_replace_callback('`
  • (.*)
  • `isU', function ($data) { + return '
  • '.trim($data[1]).'
  • '; + }, $text); + + $text = preg_replace_callback( + '`(.*)`isU', + function ($data) { + return sprintf('

    %s

    %s

    ', nl2br(trim($data[2])), $data[1]); + }, + $text + ); + + $text = preg_replace_callback( + '`(.*)`isU', + function ($data) { + return sprintf('

    %s

    ', nl2br(trim($data[1]))); + }, + $text + ); + + $text = preg_replace_callback('`(.*)`isU', function ($data) { + return html_entity_decode($data[1]); + }, $text); + + $text = preg_replace_callback('`(.*)`isU', function ($data) { + return htmlspecialchars($data[1]); + }, $text); + + $text = preg_replace('`([0-9]+)`isU', '', $text); + + $text = preg_replace_callback('`(.*)`isU', function () { + return str_replace('
    ', '', $data[1]); + }, $text); + + $text = preg_replace_callback( + array( + '#(.*)#isU', + '#(.*)#isU', + ), + function ($data) { + $lang = strtolower(str_replace( + array('console', 'texte', 'apache'), + array('bash', 'text', 'html'), + $data[1]) + ); + $class = 'language-'.$lang; + $html = sprintf('
    %s
    ', $class, trim(htmlentities($data[2]))); + + return $html; + }, + $text + ); + + $text = preg_replace_callback('`([^<]+)`isU', function ($data) { + return ''.$data[1].''; + }, $text); + + $text = preg_replace_callback('`(.*)`isU', function ($data) { + $lines = explode("\n", $data[1]); + $pictures = []; + + foreach ($lines as $line) { + $line = trim($line); + + if (empty($line)) { + continue; + } + + $elements = explode('|', $line); + + if (count($elements) === 2 || count($elements) === 3) { + $pictures[] = sprintf( + '
  • %3$s
  • ', + $elements[0], + $elements[1], + isset($elements[2]) ? $elements[2] : '' + ); + } + } + + if (empty($pictures)) { + return ''; + } + + return '
      '.implode('', $pictures).'
    '; + }, $text); + + foreach (['`"http://www.youtube.com/[^"]+"`i', '`"http://www.dailymotion.com/[^"]+"`i'] as $regex) { + $text = preg_replace_callback($regex, function ($data) { + return str_replace('&', '&', $data[0]); + }, $text); + } + + return $text; + } +} diff --git a/src/Twig/Extension/LazyLoadExtension.php b/src/Twig/Extension/LazyLoadExtension.php new file mode 100644 index 0000000..33ff12c --- /dev/null +++ b/src/Twig/Extension/LazyLoadExtension.php @@ -0,0 +1,37 @@ + + */ +class LazyLoadExtension extends AbstractExtension +{ + public function getFilters() + { + return [ + new TwigFilter('lazy_load', [$this, 'lazyLoad'], ['is_safe' => ['html']]), + ]; + } + + public function lazyLoad($text) + { + $text = preg_replace_callback( + '`([^`isU', + function ($data) { + $lazy = sprintf('%s', $data[1], $data[2]); + $noScript = sprintf('', $data[0]); + + return $lazy.$noScript; + }, + $text + ); + + return $text; + } +} diff --git a/src/UrlGenerator/PostGenerator.php b/src/UrlGenerator/PostGenerator.php new file mode 100644 index 0000000..37a0470 --- /dev/null +++ b/src/UrlGenerator/PostGenerator.php @@ -0,0 +1,76 @@ + + */ +class PostGenerator +{ + protected PostRepositoryQuery $postQuery; + protected CategoryRepositoryQuery $categoryQuery; + protected UrlGeneratorInterface $urlGenerator; + + public function __construct( + PostRepositoryQuery $postQuery, + CategoryRepositoryQuery $categoryQuery, + UrlGeneratorInterface $urlGenerator + ) { + $this->postQuery = $postQuery; + $this->categoryQuery = $categoryQuery; + $this->urlGenerator = $urlGenerator; + } + + public function post(): array + { + $entities = $this->postQuery->create() + ->published() + ->find() + ; + + $urls = []; + + foreach ($entities as $entity) { + $urls[] = $this->urlGenerator->generate( + 'blog_menu_post', + [ + 'post' => $entity->getId(), + 'slug' => $entity->getSlug(), + ], + UrlGeneratorInterface::ABSOLUTE_URL + ); + } + + return $urls; + } + + public function category(): array + { + $entities = $this->categoryQuery->create() + ->andWhere('.isActive = 1') + ->find() + ; + + $urls = []; + + foreach ($entities as $entity) { + $urls[] = $this->urlGenerator->generate( + 'blog_menu_category', + [ + 'category' => $entity->getId(), + 'slug' => $entity->getSlug(), + ], + UrlGeneratorInterface::ABSOLUTE_URL + ); + } + + return $urls; + } +} diff --git a/symfony.lock b/symfony.lock index 5db99ec..5d9ad95 100644 --- a/symfony.lock +++ b/symfony.lock @@ -114,6 +114,15 @@ "knplabs/knp-components": { "version": "v3.0.1" }, + "knplabs/knp-markdown-bundle": { + "version": "1.9.0" + }, + "knplabs/knp-menu": { + "version": "v3.1.3" + }, + "knplabs/knp-menu-bundle": { + "version": "v3.1.0" + }, "knplabs/knp-paginator-bundle": { "version": "v5.4.2" }, @@ -126,6 +135,9 @@ "laminas/laminas-zendframework-bridge": { "version": "1.2.0" }, + "michelf/php-markdown": { + "version": "1.9.0" + }, "monolog/monolog": { "version": "2.2.0" }, diff --git a/templates/admin/menu.html.twig b/templates/admin/menu.html.twig index 08f4f93..f193842 100644 --- a/templates/admin/menu.html.twig +++ b/templates/admin/menu.html.twig @@ -1,27 +1,23 @@ {% import "@Core/admin/macros/menu.html.twig" as macros_menu %} -{# -{% if is_granted('ROLE_WRITER') %} - + - diff --git a/templates/base.html.twig b/templates/base.html.twig index 16d7273..ffcefbc 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -1,19 +1,136 @@ - - - - - {% block title %}Welcome!{% endblock %} - {# Run `composer require symfony/webpack-encore-bundle` - and uncomment the following Encore helpers to start using Symfony UX #} - {% block stylesheets %} - {#{{ encore_entry_link_tags('app') }}#} - {% endblock %} +{% set assets_host = '' %} - {% block javascripts %} - {#{{ encore_entry_script_tags('app') }}#} - {% endblock %} - - - {% block body %}{% endblock %} - + + +{% apply spaceless %} + + + + + + Blog - {% block meta_title %}{{ _page ? _page.title.value : null }}{% endblock %} + + {% block metas %} + + + + + + {% endblock %} + + {% block css %} + {{ encore_entry_link_tags('app') }} + {% endblock %} + + {# + + + + + + + + + + + + + + + + + + + + + #} + + +
    + {{ include('module/_navigation.html.twig') }} + +
    +
    +
    +
    + +

    + {%- block page_title -%} + {{- 'Simon Vieille' -}} + {%- endblock -%} +

    + + {% block page_subtitle %} +

    + {# {{- 'IT director at Zenitude Groupe, symfony expert and debian addict' -}} #} + {{- 'DevOp animé par la culture du libre et du hacking' -}} +

    + {% endblock %} +
    + +
    + {% set flashes = app.flashes %} + + {% if flashes|length %} + {% for level, messages in appflashes %} + {% for message in messages %} +
    + {{- message|nl2br -}} +
    + {% endfor %} + {% endfor %} + {% endif %} +
    + + {% block body %} + + {% endblock %} +
    +
    + + {% set c %}Je soutiens Tinternet & cie, les Chatons et l'April{% endset %} + + +
    + + {% block js %} + {{ encore_entry_script_tags('app') }} + {% endblock %} + +{% endapply %} diff --git a/templates/blog/category/index.html.twig b/templates/blog/category/index.html.twig new file mode 100644 index 0000000..7f0f83e --- /dev/null +++ b/templates/blog/category/index.html.twig @@ -0,0 +1,20 @@ +{% extends 'base.html.twig' %} + +{% block title %}Hello CategoryController!{% endblock %} + +{% block body %} + + +
    +

    Hello {{ controller_name }}! ✅

    + + This friendly message is coming from: + +
    +{% endblock %} diff --git a/templates/blog/category_admin/_form.html.twig b/templates/blog/category_admin/_form.html.twig new file mode 100644 index 0000000..870f740 --- /dev/null +++ b/templates/blog/category_admin/_form.html.twig @@ -0,0 +1,17 @@ +
    +
    +
    + {% for item in ['title', 'subTitle', 'slug', 'isActive'] %} +
    + {{ form_row(form[item]) }} +
    + {% endfor %} +
    +
    +
    + {% for item in ['description'] %} + {{ form_row(form[item]) }} + {% endfor %} +
    +
    + diff --git a/templates/blog/category_admin/edit.html.twig b/templates/blog/category_admin/edit.html.twig new file mode 100644 index 0000000..b7be271 --- /dev/null +++ b/templates/blog/category_admin/edit.html.twig @@ -0,0 +1,57 @@ +{% extends '@Core/admin/layout.html.twig' %} + +{% block body %} +
    +
    +
    +

    {{ entity.title }}

    +
    + +
    +
    + + + Retour à la liste + + + + Voir + + + + + + +
    +
    +
    +
    + +
    +
    +
    +
    + {{ include('blog/category_admin/_form.html.twig') }} +
    +
    +
    + + {{ form_rest(form) }} +
    + +
    + + +
    +{% endblock %} diff --git a/templates/blog/category_admin/index.html.twig b/templates/blog/category_admin/index.html.twig new file mode 100644 index 0000000..c74d72d --- /dev/null +++ b/templates/blog/category_admin/index.html.twig @@ -0,0 +1,77 @@ +{% extends '@Core/admin/layout.html.twig' %} + +{% block body %} +
    +
    +
    +

    Catégories

    +
    + + +
    + + {{ knp_pagination_render(pager) }} +
    + + + + + + + + + + + {% for item in pager %} + {% set edit = path('admin_blog_category_edit', {entity: item.id}) %} + {% set show = path('admin_blog_category_show', {entity: item.id}) %} + + + + + + + {% else %} + + + + {% endfor %} + +
    TitreArticlesActions
    + + {{ item.title }} + + + + {% set postsCount = item.posts|length %} + + {{ postsCount }} {{ postsCount < 2 ? 'article' : 'articles' }} + + + + + + + +
    + + +
    +
    +
    + +
    +
    + Aucun résultat +
    +
    +{% endblock %} diff --git a/templates/blog/category_admin/new.html.twig b/templates/blog/category_admin/new.html.twig new file mode 100644 index 0000000..06aa0e5 --- /dev/null +++ b/templates/blog/category_admin/new.html.twig @@ -0,0 +1,39 @@ +{% extends '@Core/admin/layout.html.twig' %} + +{% block body %} +
    +
    +
    +

    Nouvelle catégorie

    +
    + +
    +
    + + + + Retour à la liste + + + +
    +
    +
    +
    + +
    +
    +
    +
    + {{ include('blog/category_admin/_form.html.twig') }} +
    +
    +
    + + {{ form_rest(form) }} +
    +{% endblock %} diff --git a/templates/blog/category_admin/show.html.twig b/templates/blog/category_admin/show.html.twig new file mode 100644 index 0000000..a493ea5 --- /dev/null +++ b/templates/blog/category_admin/show.html.twig @@ -0,0 +1,90 @@ +{% extends '@Core/admin/layout.html.twig' %} + +{% block body %} +
    +
    +
    +

    {{ entity.title }}

    +
    + + +
    +
    + +
    +
    +
      +
    • + Titre + + {{ entity.title }} +
    • +
    • + Sous-titre + + {{ entity.subTitle }} +
    • +
    • + Slug + + {{ absolute_url(entity.slug) }} +
    • +
    • + En ligne + + {{ entity.isActive ? 'oui' : 'non' }} +
    • +
    +
    +
    +
    Description
    + + {{ entity.description|raw|nl2br }} +
    + +
    + + + + + + + + {% for item in posts %} + + + + {% else %} + + + + {% endfor %} + +
    Derniers articles
    + + {{ item.title }} + +
    +
    + +
    +
    + Aucun résultat +
    +
    +
    +
    +{% endblock %} diff --git a/templates/blog/post/_pager.html.twig b/templates/blog/post/_pager.html.twig new file mode 100644 index 0000000..c3fe56c --- /dev/null +++ b/templates/blog/post/_pager.html.twig @@ -0,0 +1,60 @@ +{% if pages > 1 %} + +{% endif %} diff --git a/templates/blog/post/_post.html.twig b/templates/blog/post/_post.html.twig new file mode 100644 index 0000000..a8b892d --- /dev/null +++ b/templates/blog/post/_post.html.twig @@ -0,0 +1,218 @@ +{% set full = full is defined and full %} + +
    +
    + {% if post.isQuick %} +
    + {% set image = post.quickImage %} + {% set url = post.quickUrl %} + {% set title = post.title %} + {% set content = post.content %} + + {% if false and post.quickVideo and post.quickVideoHeight and post.quickVideoWidth %} +
    +
    + +
    +
    + {% elseif post.quickImage %} +
    + + {{ title }} + +
    + {% endif %} + +
    +

    + + {{- title -}} + +

    + +

    + {{ url }} +

    + + {% if post.contentFormat == 'html' %} + {{- post.content|post -}} + {% endif %} + + {% if post.contentFormat == 'markdown' %} + {{- post.content|markdown('post') -}} + {% endif %} + + {% if not full %} +

    + + {{- 'Lien permanent' -}} + +

    + {% endif %} +
    +
    + {% else %} + {% if not full %} +

    + + {{- post.title -}} + +

    + + + {% endif %} + +
    + {% if post.contentFormat == 'html' %} + {{- post.content|post -}} + {% endif %} + + {% if post.contentFormat == 'markdown' %} + {{- post.content|markdown('post')|lazy_load -}} + {% endif %} +
    + {% endif %} +
    +
    + +{% if full %} + {# +
    +
    +
    + + {% set hasActiveComments = post.hasActiveComments(true) %} + + {% if hasActiveComments %} +
    + {% for comment in post.orderedComments(true) %} + {{ include('DeblanBlogBundle:skin2018:_comment.html.twig', {comment: comment, level: 1}) }} + {% endfor %} +
    + {% endif %} + +
    +
    + {% if hasActiveComments %} +
    + {% endif %} + +
    Ajouter un commentaire
    + +
    +
    + {{ form_label(form.author) }} + {{ form_widget(form.author) }} + {{ form_errors(form.author) }} +
    +
    + {{ form_label(form.website) }} + {{ form_widget(form.website) }} + {{ form_errors(form.website) }} +
    +
    +
    +
    + {{ form_label(form.email) }} + {{ form_widget(form.email) }} + {{ form_errors(form.email) }} +
    +
    +
    +

    + {{- 'Votre commentaire - Vous pouvez utiliser du markdown ' }} + [?] +

    + + {{ form_errors(form.content) }} + + {{ form_widget(form.content, {attr: {cols: 30, rows: 10}}) }} + + +
    +
    + {{ form_errors(form.follow) }} + {{ form_widget(form.follow) }} + {{ form_label(form.follow) }} +
    + +
    + + +
    + +
    + + +
    + + {{ form_rest(form) }} +
    + +
    +
    +
    +
    + #} + + {% if not post.isQuick %} + {% + set ld = { + '@context': 'http://schema.org/', + '@type': 'BlogPosting', + 'headline': post.title, + 'author': { + '@type': 'Person', + 'name': 'Simon Vieille' + }, + 'publisher': { + '@type': 'Organization', + 'name': 'Deblan blog', + 'logo': { + '@type': 'imageObject', + 'url': absolute_url(asset('build/images/logo.png')) + } + }, + 'datePublished': post.publishedAt|date("c"), + 'dateModified': post.updatedAt|date("c"), + 'image': absolute_url(asset(post.image)) + } + %} + + + {% endif %} +{% endif %} diff --git a/templates/blog/post/categories.html.twig b/templates/blog/post/categories.html.twig new file mode 100644 index 0000000..df69e6a --- /dev/null +++ b/templates/blog/post/categories.html.twig @@ -0,0 +1,27 @@ +{% extends "base.html.twig" %} + +{% block page_title %} + Catégories +{% endblock %} + +{% block page_subtitle %} +{% endblock %} + +{% block body %} +
    +
    + +
    +
    +{% endblock %} diff --git a/templates/blog/post/category.html.twig b/templates/blog/post/category.html.twig new file mode 100644 index 0000000..9ac5b7d --- /dev/null +++ b/templates/blog/post/category.html.twig @@ -0,0 +1,27 @@ +{% extends 'blog/post/posts.html.twig' %} + +{%- block meta_title -%} + {{- category.title -}} +{% endblock %} + +{% block page_title %} + {{- category.title -}} +{% endblock %} + +{% block page_subtitle %} +

    + {{- category.description -}} +

    +{% endblock %} + +{% block pager %} + {{ include('blog/post/_pager.html.twig', { + route: _node.routeName, + routeParams: { + category: category.id, + slug: category.slug + }, + pages: pager.paginationData.endPage, + currentPage: pager.paginationData.current + }) }} +{% endblock %} diff --git a/templates/blog/post/post.html.twig b/templates/blog/post/post.html.twig new file mode 100644 index 0000000..9f518ca --- /dev/null +++ b/templates/blog/post/post.html.twig @@ -0,0 +1,63 @@ +{% extends "base.html.twig" %} + +{%- block meta_title -%} + {{- post.title -}} +{% endblock %} + +{%- block page_title -%} + {{- post.title -}} +{% endblock %} + +{%- block page_subtitle -%} + +{% endblock %} + +{%- block body -%} + {{ include('blog/post/_post.html.twig', {full: true}) }} +{% endblock %} + +{%- block metas -%} + {# + {{ parent() }} + + {% if not page.object.quick %} + {% set name = 'Deblan Blog' %} + {% set title = 'Blog - ' ~ block('meta_title') %} + {% set image = app.request.getSchemeAndHttpHost() ~ asset(page.object.getWebPathForPicture) %} + {% else %} + {% set image = page.object.quickImage %} + {% set title = block('meta_title') %} + {% set name = '' %} + {% endif %} + + + + + + + + + + #} +{% endblock %} diff --git a/templates/blog/post/posts.html.twig b/templates/blog/post/posts.html.twig new file mode 100644 index 0000000..f51dcd7 --- /dev/null +++ b/templates/blog/post/posts.html.twig @@ -0,0 +1,42 @@ +{% extends 'base.html.twig' %} + +{% block body %} + {% for post in pager %} + {% if not loop.first %} +
    + {% endif %} + + {{ include('blog/post/_post.html.twig', {post: post}) }} + {% endfor %} + + {% if pager.getPaginationData.pageCount > 1 %} +
    +
    +
    + {% block pager %} + {{ include('blog/post/_pager.html.twig', { + route: _node.routeName, + routeParams: {}, + pages: pager.paginationData.endPage, + currentPage: pager.paginationData.current + }) }} + {% endblock %} +
    +
    +
    + {% endif %} +{% endblock %} + +{% block metas %} + {# + {{- parent() -}} + + {% if page.pager.hasPreviousPage %} + + {% endif %} + + {% if page.pager.hasNextPage %} + + {% endif %} + #} +{% endblock %} diff --git a/templates/blog/post_admin/_form.html.twig b/templates/blog/post_admin/_form.html.twig new file mode 100644 index 0000000..84911d7 --- /dev/null +++ b/templates/blog/post_admin/_form.html.twig @@ -0,0 +1,61 @@ +
    +
    +
    + {% for item in ['title', 'categories', 'slug'] %} +
    + {{ form_row(form[item]) }} +
    + {% endfor %} +
    +
    +
    +
    + + +
    +
    +
    + {% for item in ['content'] %} +
    + {{ form_row(form[item]) }} +
    + {% endfor %} +
    +
    +
    +
    + {% for item in ['isQuick', 'quickUrl', 'quickImage', 'quickVideo'] %} +
    + {{ form_row(form[item]) }} +
    + {% endfor %} + + {% for item in ['quickVideoWidth', 'quickVideoHeight'] %} +
    + {{ form_row(form[item]) }} +
    + {% endfor %} +
    +
    +
    + +
    +
    +
    +
    + {% for item in ['image', 'status', 'contentFormat', 'publishedAt'] %} +
    + {{ form_row(form[item]) }} +
    + {% endfor %} +
    +
    +
    + diff --git a/templates/blog/post_admin/edit.html.twig b/templates/blog/post_admin/edit.html.twig new file mode 100644 index 0000000..0ee3c72 --- /dev/null +++ b/templates/blog/post_admin/edit.html.twig @@ -0,0 +1,57 @@ +{% extends '@Core/admin/layout.html.twig' %} + +{% block body %} +
    +
    +
    +

    {{ entity.title }}

    +
    + +
    +
    + + + Retour à la liste + + + + Voir + + + + + + +
    +
    +
    +
    + +
    +
    +
    +
    + {{ include('blog/post_admin/_form.html.twig') }} +
    +
    +
    + + {{ form_rest(form) }} +
    + +
    + + +
    +{% endblock %} diff --git a/templates/blog/post_admin/filters.html.twig b/templates/blog/post_admin/filters.html.twig new file mode 100644 index 0000000..967de02 --- /dev/null +++ b/templates/blog/post_admin/filters.html.twig @@ -0,0 +1,21 @@ + diff --git a/templates/blog/post_admin/index.html.twig b/templates/blog/post_admin/index.html.twig new file mode 100644 index 0000000..02250d7 --- /dev/null +++ b/templates/blog/post_admin/index.html.twig @@ -0,0 +1,112 @@ +{% extends '@Core/admin/layout.html.twig' %} + +{% block body %} +
    +
    +
    +

    Articles

    +
    + + +
    + +
    +
    + +
    +
    + {{ knp_pagination_render(pager) }} +
    +
    +
    + + + + + + + + + + + + {% for item in pager %} + {% set edit = path('admin_blog_post_edit', {entity: item.id}) %} + {% set show = path('admin_blog_post_show', {entity: item.id}) %} + + + + + + + + {% else %} + + + + {% endfor %} + +
    TitreMise à jourStatutActions
    + {% if item.image %} + {% set image = asset(item.image.pathname) %} + {% else %} + {% set image = asset('build/images/no-image.png') %} + {% endif %} + + + + + {{ item.title }} + + + {% set categories = [] %} + + {% for category in item.categories %} + {% set url = path('admin_blog_category_show', {entity: category.id}) %} + {% set categories = categories|merge(['' ~ category.title ~ '']) %} + {% endfor %} + + Dans {{ categories|join(', ')|raw }} + + + + {{ item.updatedAt|date('d/m/Y H:i') }} + + + {% set map = { + 0: ['warning', 'Brouillon'], + 1: ['success', 'Publié'], + } %} + + + + + + + +
    + + +
    +
    +
    + +
    +
    + Aucun résultat +
    +
    +{% endblock %} diff --git a/templates/blog/post_admin/new.html.twig b/templates/blog/post_admin/new.html.twig new file mode 100644 index 0000000..fe77e89 --- /dev/null +++ b/templates/blog/post_admin/new.html.twig @@ -0,0 +1,39 @@ +{% extends '@Core/admin/layout.html.twig' %} + +{% block body %} +
    +
    +
    +

    Nouvel article

    +
    + +
    +
    + + + + Retour à la liste + + + +
    +
    +
    +
    + +
    +
    +
    +
    + {{ include('blog/post_admin/_form.html.twig') }} +
    +
    +
    + + {{ form_rest(form) }} +
    +{% endblock %} diff --git a/templates/blog/post_admin/show.html.twig b/templates/blog/post_admin/show.html.twig new file mode 100644 index 0000000..28086a6 --- /dev/null +++ b/templates/blog/post_admin/show.html.twig @@ -0,0 +1,135 @@ +{% extends '@Core/admin/layout.html.twig' %} + +{% block body %} +
    +
    +
    +

    {{ entity.title }}

    +
    + + +
    +
    + +
    +
    +
      +
    • + Titre + + {{ entity.title }} +
    • +
    • + Catégories + + {% for category in entity.categories %} + {{ category.title }} + {% endfor %} +
    • +
    • + Slug + + {{ entity.slug }} +
    • +
    + {% if entity.isQuick %} +
      +
    • + QUICK +
    • +
    • + URL + + + {{ entity.quickUrl }} + +
    • +
    • + Image + + {% if entity.quickImage %} + + + + {% else %} + - + {% endif %} +
    • +
    • + Vidéo + + {% if entity.quickVideo %} + + {{ entity.quickVideo }} + ({{ [entity.quickVideoWidth, entity.quickVideoHeight]|join(' x ') }}) + {% else %} + - + {% endif %} +
    • +
    + {% endif %} +
    +
    +
    Contenu
    + + {% if entity.contentFormat == 'html' %} + {{ entity.content|raw|nl2br }} + {% else %} + {{ entity.content|markdown('post') }} + {% endif %} +
    +
    +
      +
    • + Image + + {% if entity.image %} +
      + +
      + {% else %} + - + {% endif %} +
    • + +
    • + Statut + + {% if entity.status == 0 %} + Brouillon + {% else %} + Publié + {% endif %} +
    • +
    • + Date de publication + + {{ entity.publishedAt ? entity.publishedAt|date('d/m/Y H:i') : '-' }} +
    • +
    +
    +
    +{% endblock %} + +{% block css %} + {{ parent() }} + + +{% endblock %} diff --git a/templates/contact/index.html.twig b/templates/contact/index.html.twig new file mode 100644 index 0000000..dc611d1 --- /dev/null +++ b/templates/contact/index.html.twig @@ -0,0 +1,20 @@ +{% extends 'base.html.twig' %} + +{% block title %}Hello ContactController!{% endblock %} + +{% block body %} + + +
    +

    Hello {{ controller_name }}! ✅

    + + This friendly message is coming from: + +
    +{% endblock %} diff --git a/templates/module/_logo.html.twig b/templates/module/_logo.html.twig new file mode 100644 index 0000000..ccdbf8f --- /dev/null +++ b/templates/module/_logo.html.twig @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/templates/module/_navigation.html.twig b/templates/module/_navigation.html.twig new file mode 100644 index 0000000..30c345a --- /dev/null +++ b/templates/module/_navigation.html.twig @@ -0,0 +1,63 @@ +{% set menu = _navigation.menu('menu') %} +{% set network = _navigation.menu('network') %} + +
    +
    + + + {% if menu %} + + {% endif %} + + {% if network %} + + {% endif %} +
    +
    +
    + + Deblan blog + + + +
    diff --git a/templates/page/contact/default.html.twig b/templates/page/contact/default.html.twig new file mode 100644 index 0000000..53ac3a5 --- /dev/null +++ b/templates/page/contact/default.html.twig @@ -0,0 +1,23 @@ +{% extends 'base.html.twig' %} + +{% block page_title %} + {{- _page.title.value -}} +{% endblock %} + +{% block page_subtitle %} +

    + {{- _page.subTitle.value -}} +

    +{% endblock %} + +{% block body %} +
    +
    +
    + {{- _page.content.value|markdown('post') -}} + + CONTACT +
    +
    +
    +{% endblock %} diff --git a/templates/page/links/default.html.twig b/templates/page/links/default.html.twig new file mode 100644 index 0000000..ac12576 --- /dev/null +++ b/templates/page/links/default.html.twig @@ -0,0 +1,21 @@ +{% extends 'base.html.twig' %} + +{% block page_title %} + {{- _page.title.value -}} +{% endblock %} + +{% block page_subtitle %} +

    + {{- _page.subTitle.value -}} +

    +{% endblock %} + +{% block body %} +
    +
    +
    + {{- _page.content.value|markdown('post') -}} +
    +
    +
    +{% endblock %} diff --git a/templates/page/simple/default.html.twig b/templates/page/simple/default.html.twig new file mode 100644 index 0000000..ac12576 --- /dev/null +++ b/templates/page/simple/default.html.twig @@ -0,0 +1,21 @@ +{% extends 'base.html.twig' %} + +{% block page_title %} + {{- _page.title.value -}} +{% endblock %} + +{% block page_subtitle %} +

    + {{- _page.subTitle.value -}} +

    +{% endblock %} + +{% block body %} +
    +
    +
    + {{- _page.content.value|markdown('post') -}} +
    +
    +
    +{% endblock %} diff --git a/templates/page/simple/page.html.twig b/templates/page/simple/page.html.twig deleted file mode 100644 index ed9c500..0000000 --- a/templates/page/simple/page.html.twig +++ /dev/null @@ -1,79 +0,0 @@ -{% import _self as macros %} - -{% macro item(node, store) %} - {% set isActive = store.isActiveNode(node, true) %} - - {% if node.isVisible %} - {% if node.code == 'post' %} - - {{ node.label }} - - {% elseif node.page %} - {% set url = node.hasExternalUrl ? node.url : url(node.routeName) %} - - - {{ node.label }} - - {% else %} - - {{ node.label }} - - {% endif %} - - {{ dump(node.attributes) }} - - {% if node.children|length %} -
      - {% for child in node.children %} - {% if child.isVisible %} -
    • - {{ macros.item(child, store) }} -
    • - {% endif %} - {% endfor %} -
    - {% endif %} - {% endif %} -{% endmacro %} - -{% macro menu(menu, store) %} -
      - {% for child in menu.rootNode.children %} - {% if child.isVisible %} -
    • - {{ macros.item(child, store) }} -
    • - {% endif %} - {% endfor %} -
    -{% endmacro %} - -

    {{ _page.title.value }}

    - -
    {{ _page.content.value }}
    - -{% set image = _page.image.value %} - -{% if image %} - -{% endif %} - -
      -
    • - Node : {{ _node.label }} -
    • -
    • - Menu : {{ _menu.label }} -
    • -
    • - Navigation : {{ _navigation.label }} -
    • -
    - -

    Menu

    - -{% set menu = _navigation.menu('top') %} - -{% if menu %} - {{ macros.menu(menu, _store) }} -{% endif %} diff --git a/templates/page/titled/default.html.twig b/templates/page/titled/default.html.twig new file mode 100644 index 0000000..00b83ca --- /dev/null +++ b/templates/page/titled/default.html.twig @@ -0,0 +1,14 @@ +{% extends 'base.html.twig' %} + +{% block page_title %} + {{- _page.title.value -}} +{% endblock %} + +{% block page_subtitle %} +

    + {{- _page.subTitle.value -}} +

    +{% endblock %} + +{% block body %} +{% endblock %} diff --git a/webpack.config.js b/webpack.config.js index b430453..839a70e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -21,6 +21,7 @@ Encore * and one CSS file (e.g. app.css) if your JavaScript imports CSS. */ .addEntry('admin', './assets/js/admin.js') + .addEntry('app', './assets/js/app.js') // When enabled, Webpack "splits" your files into smaller pieces for greater optimization. .splitEntryChunks() @@ -56,10 +57,16 @@ Encore from: './assets/images', to: 'images/[path][name].[hash:8].[ext]' }) + .copyFiles({ + from: './assets/fonts', + to: 'fonts/[path][name].[hash:8].[ext]' + }) // enables Sass/SCSS support .enableSassLoader() + .enableVersioning() + // uncomment if you use TypeScript //.enableTypeScriptLoader() diff --git a/yarn.lock b/yarn.lock index f4b7751..379d698 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1775,6 +1775,18 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +codemirror-spell-checker@*: + version "1.1.2" + resolved "https://registry.yarnpkg.com/codemirror-spell-checker/-/codemirror-spell-checker-1.1.2.tgz#1c660f9089483ccb5113b9ba9ca19c3f4993371e" + integrity sha1-HGYPkIlIPMtRE7m6nKGcP0mTNx4= + dependencies: + typo-js "*" + +codemirror@*: + version "5.60.0" + resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.60.0.tgz#00a8cfd287d5d8737ceb73987f04aee2fe5860da" + integrity sha512-AEL7LhFOlxPlCL8IdTcJDblJm8yrAGib7I+DErJPdZd4l6imx8IMgKK3RblVgBQqz3TZJR4oknQ03bz+uNjBYA== + color-convert@^1.9.0, color-convert@^1.9.1: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -3759,6 +3771,11 @@ map-obj@^1.0.0, map-obj@^1.0.1: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= +marked@*: + version "2.0.1" + resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.1.tgz#5e7ed7009bfa5c95182e4eb696f85e948cefcee3" + integrity sha512-5+/fKgMv2hARmMW7DOpykr2iLhl0NgjyELk5yn92iE7z8Se1IS9n3UsFm86hFXIkvMBmVxki8+ckcpjBeyo/hw== + mdn-data@2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" @@ -4329,6 +4346,11 @@ parseurl@~1.3.2, parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +particles.js@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/particles.js/-/particles.js-2.0.0.tgz#21386c4328d6c7f96780a201e96eedfc09c736f6" + integrity sha1-IThsQyjWx/lngKIB6W7t/AnHNvY= + path-exists@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" @@ -5371,6 +5393,15 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +simplemde@^1.11.2: + version "1.11.2" + resolved "https://registry.yarnpkg.com/simplemde/-/simplemde-1.11.2.tgz#a23a35d978d2c40ef07dec008c92f070d8e080e3" + integrity sha1-ojo12XjSxA7wfewAjJLwcNjggOM= + dependencies: + codemirror "*" + codemirror-spell-checker "*" + marked "*" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -5865,6 +5896,11 @@ type@^2.0.0: resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== +typo-js@*: + version "1.2.0" + resolved "https://registry.yarnpkg.com/typo-js/-/typo-js-1.2.0.tgz#dbe58de3a6dcbbe260b78bf290ee761b008a28e8" + integrity sha512-dELuLBVa2jvWdU/CHTKi2L/POYaRupv942k+vRsFXsM17acXesQGAiGCio82RW7fvcr7bkuD/Zj8XpUh6aPC2A== + unbox-primitive@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.0.tgz#eeacbc4affa28e9b3d36b5eaeccc50b3251b1d3f" @@ -6256,6 +6292,11 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== +wire.css@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/wire.css/-/wire.css-1.2.5.tgz#376dde81d015f5b996b00fc385cce7f6695f76df" + integrity sha1-N23egdAV9bmWsA/Dhczn9mlfdt8= + wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"