Signed-off-by: Avior <florian.bouillon@delta-wings.net>
This commit is contained in:
Florian Bouillon 2020-01-04 17:35:30 +01:00
parent e5fb01eea8
commit a0f1799114
No known key found for this signature in database
GPG Key ID: B143FF27EF555D16
51 changed files with 879 additions and 261 deletions

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
*.ttf filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text

View File

@ -1,11 +1,13 @@
import React from 'react'
import Link from 'next/link'
import { ChevronRight } from 'react-feather'
import next from 'next'
interface Props {
title: string
date: Date
image?: string
alt?: string
link: string
}
@ -30,31 +32,41 @@ export default class Element extends React.Component<Props, {}> {
super(props)
}
render() {
let date = this.props.date
if (typeof this.props.date === "string") {
date = new Date(this.props.date)
}
const t = `${date.getDate()} ${months[date.getMonth()]} ${date.getFullYear()}`
return (
<div>
<Link href={this.props.link}>
{this.props.image ? (
<a><img src={this.props.image}/></a>
<a><img src={this.props.image} alt={this.props.alt}/></a>
) : (
<div></div>
)}
</Link>
<i>Le {this.props.date.getDate()} {months[this.props.date.getMonth()]} {this.props.date.getFullYear()}</i>
<i>Le {t}</i>
<span>
<Link href={this.props.link}>
<a>{this.props.title}</a>
</Link>
<Link href={this.props.link}>
<Link as={this.props.link} href="/[category]/[slug]">
<a><ChevronRight size={48}/></a>
</Link>
</span>
<style jsx>{`
div {
padding: 5% 5%;
min-width: 90%;
}
@media (min-width: 840px) {
div {
max-width: 40%;
min-width: 400px;
}
}
img {
width: 100%;
border-radius: 10px;

View File

@ -1,13 +1,11 @@
import React from 'react'
import Link from 'next/link'
import Category from './interfaces/Category'
import '../styl/styl.styl'
import { ChevronRight } from 'react-feather'
import Router from 'next/router'
import { ChevronRight, ChevronDown } from 'react-feather'
interface Props {
categories?: Category[]
onQuery?: (query: string) => void
categories?: string[]
onQuery?: (query: string, sort?: boolean) => void
onHeight?: (height: number) => void
}
@ -25,20 +23,10 @@ export default class Filters extends React.Component<Props, States> {
super(props)
}
setRef = element => {
this.aside = element
}
setInput = element => {
this.input = element
}
onScroll = () => {
this.setState({
follow: window.pageYOffset > 217
})
}
onKeyDown = (ev: React.KeyboardEvent<HTMLInputElement>) => {
setTimeout(() => {
this.submit()
@ -53,73 +41,65 @@ export default class Filters extends React.Component<Props, States> {
this.input.focus()
}
submit = () => {
if (this.props.onQuery) this.props.onQuery(this.input.value)
onChange = (ev) => {
this.submit(ev.target.value === "true")
}
componentDidMount() {
this.onScroll()
this.setState({
height: this.aside.clientHeight
})
if (this.props.onHeight) this.props.onHeight(this.aside.clientHeight)
window.addEventListener('scroll', this.onScroll)
}
componentWillUnmount() {
window.removeEventListener('scroll', this.onScroll)
submit = (sort?: boolean) => {
if (this.props.onQuery) this.props.onQuery(this.input.value, sort)
}
render() {
return (
<aside ref={this.setRef} className={this.state && this.state.follow ? "follow" : ""}>
<aside>
<div>Trier</div>
<div className="input icon-right">
<select>
<option value="plus récant au moins récent"></option>
<select onChangeCapture={this.onChange}>
<option value="true">plus récent au moins récent</option>
<option value="false">moins récent au plus récent</option>
</select>
<i>
<ChevronDown />
</i>
</div>
<div>Filtrer</div>
<div className="input icon-right">
<div className="input icon-right inline">
<input placeholder="ex: dzeio.com" type="text" ref={this.setInput} onKeyDownCapture={this.onKeyDown} />
<i>
<ChevronRight onClick={this.onClick} />
</i>
</div>
<p>Languages :</p>
<span>
{this.props.categories.map(cat => (
<Link key={cat.slug} href={cat.slug}>
<a>{cat.name}</a>
<Link key={cat} href="/tag/[tag]" as={`/tag/${cat.toLowerCase()}`}>
<a className="button">{cat}</a>
</Link>
))}
</span>
<style jsx>{`
aside {
padding: 5% 3% 0;
display: flex;
flex-direction: column;
max-width: 445px;
}
@media (min-width: 820px) and (min-height: ${this.state && this.state.height ? this.state.height+100 : 600}px) {
aside {
position: absolute;
min-width: calc(400px - 6%);
right: 0;
}
aside.follow {
position: fixed;
top: 70px;
}
}
.input {
/*.input {
display: flex;
justify-content: center;
margin: 20px 0;
}*/
span {
display: inline-flex;
flex-wrap: wrap;
}
a {
flex-grow: 1;
}
div:not(.input) {
@ -134,21 +114,8 @@ export default class Filters extends React.Component<Props, States> {
text-align: center;
border-radius: 10px;
}
a {
padding: 7px 12px;
background: #4285F4;
color: white;
text-transform: uppercase;
border-radius: 10px;
font-size: 20px;
text-decoration: none;
display: inline-block;
transition: background 200ms
}
a:hover {
background: #45CAFC;
.input {
align-self: center;
}
`}</style>

72
components/Footer.tsx Normal file
View File

@ -0,0 +1,72 @@
import React from 'react'
import { PhoneCall, Mail, GitHub, Twitter, Linkedin } from 'react-feather'
import Link from 'next/link'
interface Props {}
interface States {}
export default class Footer extends React.Component<Props, States> {
constructor(props: Props) {
super(props)
}
render() {
return (
<footer>
<div className="pre"></div>
<div className="footer">
<span>
<a href="mailto:contact@avior.me" target="_blank">
<Mail color="#4285F4" />
</a>
<a href="tel:+33672292683" target="_blank">
<PhoneCall color="#4285F4" />
</a>
<a href="https://git.delta-wings.net" target="_blank">
<GitHub color="#4285F4" />
</a>
<a href="https://twitter.com/aviortheking" target="_blank">
<Twitter color="#4285F4" />
</a>
<a href="https://www.linkedin.com/in/florian-bouillon/" target="_blank">
<Linkedin color="#4285F4" />
</a>
</span>
</div>
<style jsx>{`
footer {
padding-top: 50px;
}
.pre {
height: 20px;
background: #EEE;
}
.footer {
display: flex;
padding: 13px 10%;
justify-content: space-evenly;
}
.footer span {
display: flex;
justify-content: space-evenly;
width: 100%;
}
a {
padding: 10px
}
@media (min-width: 850px) {
.footer {
padding: 20px 0
}
.footer span {
width: 300px;
}
}
`}</style>
</footer>
)
}
}

View File

@ -10,20 +10,27 @@ export default class Header extends React.Component<Props, {}> {
render() {
return (
<div>
<img src="/clouds.svg" alt=""/>
{/* <p>Bienvenue sur le Portfolio de Florian BOUILLON !</p> */}
<style jsx>{`
div {
position: relative;
background: linear-gradient(90deg, #45CAFC 0%, #4285F4 92.19%);
height: 207px;
background: url('/clouds.svg'), linear-gradient(90deg, #45CAFC 0%, #4285F4 92.19%);
background-repeat: repeat-x;
background-position: bottom;
min-height: 207px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
color: white;
font-size: 35px;
text-transform: uppercase;
}
img {
position: absolute;
bottom: 0;
min-width: 100%;
height: 50px
height: 50px;
}
`}</style>
</div>

55
components/Layout.tsx Normal file
View File

@ -0,0 +1,55 @@
import React from 'react'
import Navbar from './Navbar'
import Menu from './Menu'
import Header from './Header'
import Footer from './Footer'
interface Props {
hasHeader?: boolean
headerChild?: JSX.Element
}
export default class Layout extends React.Component<Props, {}> {
constructor(props: Props) {
super(props)
}
render() {
return (
<div>
<Navbar>
<Menu />
</Navbar>
{this.props.hasHeader && this.props.headerChild ? (
<Header>{this.props.headerChild}</Header>
) : (
<Header />
)}
{this.props.children}
<Footer />
<style jsx>{`
div {
height: inherit;
width: inherit;
}
`}</style>
<style jsx global>{`
html {
height: 100%;
}
::selection {
background: #4285F4;
color: #FFF;
}
body {
height: calc(100% - 80px)
}
#root, #__next {
height: 100%;
width : 100%;
}
`}</style>
</div>
)
}
}

View File

@ -1,6 +1,7 @@
import React from 'react'
import { Menu } from 'react-feather'
import { timingSafeEqual } from 'crypto'
import Link from 'next/link'
interface Props {
height?: number
@ -59,11 +60,19 @@ export default class Navbar extends React.Component<Props, States> {
margin-top: ${height}px;
overflow-x: hidden;
}
.menu * {
pointer-events: none;
}
.menu.shown * {
pointer-events: initial;
}
`}</style>
<hr />
<div className="head">
<img src="/logo.svg" alt=""/>
<Link href="/">
<a><img src="/logo.svg" alt=""/></a>
</Link>
<span onClick={this.onClick} data-menu={this.refs.menu}>
<Menu size={30} />
</span>
@ -84,8 +93,11 @@ export default class Navbar extends React.Component<Props, States> {
nav.scrolled {
box-shadow: 0 0 10px 5px #00000040
}
img {
height: 80%;
a {
height: 100%;
flex-grow: 1;
display: flex;
justify-content: center;
}
hr {
height: 10px;
@ -94,10 +106,14 @@ export default class Navbar extends React.Component<Props, States> {
background: linear-gradient(90deg, #45CAFC 0%, #4285F4 92.19%);
}
.menu {
display: none
opacity: 0;
pointer-event: none;
height: 0;
transition: opacity 200ms cubic-bezier(.2,0,.6,1);
}
.menu.shown {
display: block
opacity: 1;
height: initial;
}
.head {
display: flex;
@ -109,10 +125,13 @@ export default class Navbar extends React.Component<Props, States> {
}
img {
flex-grow: 1;
height: 100%;
}
span {
width: ${height-10}px;
height: ${height-10}px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;

View File

@ -7,8 +7,15 @@ interface PostInterface {
content: any
}
interface PostHeader {
export interface PostHeader {
title: string
id: string
category: string
image?: string
imageAlt?: string
date: Date
url?: string
tags?: string[]
}
export default class Post implements PostInterface {
@ -18,23 +25,27 @@ export default class Post implements PostInterface {
public content: string
public isStarted = false
public header: PostHeader
constructor(slug: string) {
this.slug = slug
}
public async fetch() {
console.log(this.slug)
const content = await import(`../posts/${this.slug}.md`)
if (!this.slug.endsWith(".md")) this.slug = "portfolio/" + this.slug + ".md"
const content = await import(`../posts/${this.slug}`)
const md = matter(content.default)
this.title = md.data.title
this.header = (md.data as PostHeader)
this.content = md.content
}
public fetchSync() {
console.log(this.slug)
const content = require(`../posts/${this.slug}.md`)
if (!this.slug.endsWith(".md")) this.slug = "portfolio/" + this.slug + ".md"
const content = require(`../posts/${this.slug}`)
const md = matter(content.default)
this.title = md.data.title
this.header = (md.data as PostHeader)
this.content = md.content
}
@ -44,10 +55,7 @@ export default class Post implements PostInterface {
for (const file of files) {
posts.push(
new Post(
file.replace(/^.*[\\\/]/, '')
.split('.')
.slice(0, -1)
.join('.')
file.replace("./", '')
)
)
}

View File

@ -1,9 +1,10 @@
import React from 'react'
interface Props {
}
interface Props {}
export default class Name extends React.Component<Props, {}> {
interface States {}
export default class Name extends React.Component<Props, States> {
constructor(props: Props) {
super(props)
}

View File

@ -1,4 +0,0 @@
export default interface Category {
name: string
slug: string
}

View File

@ -36,13 +36,15 @@ const options3 = {
async function run() {
try {
const res3 = await replace(options3)
console.log(res3)
// console.log(res3)
const res2 = await replace(options2)
console.log(res2)
// console.log(res2)
const results = await replace(options)
console.log(results)
// console.log(results)
process.exit(0)
} catch (error) {
console.error(error)
process.exit(1)
}
}

View File

@ -1,6 +1,8 @@
const withCSS = require('@zeit/next-stylus')
const glob = require('glob')
const withOffline = require('next-offline')
const matter = require('gray-matter')
const fs = require('fs')
// import posts from './posts/pages.json.ts'
// const posts = require('./posts/pages.json.ts')
// const t = require('./pages/portfolio/')
@ -22,17 +24,20 @@ module.exports = withOffline(withCSS({
exportPathMap: async function() {
const paths = {
'/': { page: '/'},
'/portfolio': { page: '/portfolio'},
}
const posts = glob.sync('./posts/**/*.md')
posts.forEach(element => {
const datas = matter(fs.readFileSync(element)).data
element = element.replace(/^.*[\\\/]/, '')
.split('.')
.slice(0, -1)
.join('.')
paths[`/portfolio/${element}`] = { page: '/portfolio/[slug]', query: { slug: element}}
paths[`/${datas.category.toLowerCase()}/${datas.id}`] = { page: '/[category]/[slug]', query: {category: datas.category.toLowerCase(), slug: datas.id}}
for (const tg of datas.tags) {
paths[`/tag/${tg.toLowerCase()}`] = { page: '/tag/[tag]', query: {tag: tg}}
}
});
return paths

View File

@ -13,6 +13,7 @@
"dependencies": {
"@zeit/next-css": "^1.0.1",
"@zeit/next-stylus": "^1.0.1",
"fs": "^0.0.1-security",
"glob": "^7.1.6",
"gray-matter": "^4.0.2",
"next": "9.1.5",

128
pages/[category]/[slug].tsx Normal file
View File

@ -0,0 +1,128 @@
import { NextPageContext } from "next"
import{ Component } from 'react'
import Post from "../../components/Post"
import ReactMarkdown from 'react-markdown'
import Error from "../_error"
import Link from "next/link"
interface Props {
post: Post
}
interface States {
imgHeight: number
}
export default class PostPage extends Component<Props, States> {
public render() {
return (
<main>
{this.props.post === undefined ? (
<Error statusCode={404} />
) : (
<div>
<img src={this.props.post.header.image} alt={this.props.post.header.imageAlt} />
<ReactMarkdown source={this.props.post.content}/>
<h2>Détails</h2>
<p>Tags:</p>
<ul>
{this.props.post.header.tags.map((el) => (
<li>
<Link href="/tag/[tag]" as={'/tag/'+el.toLowerCase()}>
<a className="button">{el}</a>
</Link>
</li>
))}
</ul>
{this.props.post.header.url ? (
<a className="button outline large" href={this.props.post.header.url}>Visiter le site :D</a>
) : undefined}
</div>
)}
<style jsx global>{`
main h1 {
font-size: 50px;
text-align: center;
}
main h2 {
font-size: 40px;
text-align: center;
text-shadow: 4px 4px 0px rgba(66, 133, 244, 0.5);
}
main h2::selection {
text-shadow: 4px 4px 0px rgba(255, 255, 255, 0.5);
}
main p {
text-align: justify;
}
.header {
height: 250px;
}
@media (min-width: 820px) {
.header {
height: 0
}
}
`}</style>
<style jsx>{`
main div {
margin-top: -150px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 0 10%;
}
main a.button.outline {
align-self: center;
}
@media (min-width: 820px) {
main div {
margin-top: 0
}
main img {
max-width: 750px;
}
}
main img {
z-index: 999;
max-width: 100%;
border-radius: 10px;
max-height: 300px;
box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.4);
}
/*
li {
border-radius: 10px;
background: #4285F4;
color: white;
margin: 10px;
display: inline-block;
transition: background 200ms;
}
li:hover {
background: #45CAFC;
}
li a {
padding: 15px 20px;
color: white;
display: inline-block;
text-decoration: none;
}*/
li {
display: inline-block;
}
`}</style>
</main>
)
}
public static async getInitialProps(context: NextPageContext) {
const { slug } = context.query
if (typeof slug === "object" || slug === "[slug]") return {post: undefined}
const post = new Post(slug)
await post.fetch()
return {post}
}
}

View File

@ -26,7 +26,7 @@ const PortfolioIndex: NextPage<Props> = (props: Props) => {
<ul>
{props.files.map(post => (
<li key={post.slug}>
<Link href="/portfolio/[slug]" as={`/portfolio/${post.slug}`}>
<Link href="/[category]/[slug]" as={`/${post.slug}`}>
<a>{post.title}</a>
</Link>
</li>
@ -41,7 +41,7 @@ PortfolioIndex.getInitialProps = async (context: NextPageContext) => {
for (const post of await Post.fetchAll()) {
if (!post.isStarted) await post.fetch()
arr.push({
slug: post.slug,
slug: post.slug.replace(".md", ""),
title: post.title
})
}
@ -49,10 +49,3 @@ PortfolioIndex.getInitialProps = async (context: NextPageContext) => {
}
export default PortfolioIndex
function l(args: any) {
console.log(arguments)
}
async function test() {
}

View File

@ -1,22 +1,16 @@
import React from 'react'
import App from 'next/app'
import '../styl/styl.styl'
import Layout from '../components/Layout'
class MyApp extends App {
// Only uncomment this method if you have blocking data requirements for
// every single page in your application. This disables the ability to
// perform automatic static optimization, causing every page in your app to
// be server-side rendered.
//
// static async getInitialProps(appContext) {
// // calls page's `getInitialProps` and fills `appProps.pageProps`
// const appProps = await App.getInitialProps(appContext);
//
// return { ...appProps }
// }
render() {
const { Component, pageProps } = this.props
return <Component {...pageProps} />
return(
<Layout>
<Component {...pageProps} />
</Layout>
)
}
}

View File

@ -13,7 +13,9 @@ class MyDocument extends Document {
render() {
return (
<Html>
<Head />
<Head>
<link rel="manifest" href="/manifest.json" />
</Head>
<body>
<Main />
<NextScript />

58
pages/_error.tsx Normal file
View File

@ -0,0 +1,58 @@
import React, { Component } from 'react'
import { NextPageContext } from 'next'
import Head from 'next/head'
import Layout from '../components/Layout'
interface Props {
statusCode: number
}
const codesTexts = {
404: "Page non trouvé !",
500: "Le serveur n'a pas pu répondre a ta demande :O"
}
export default class Error extends Component<Props, {}> {
public render = () => {
const statusCode = this.props.statusCode
return (
<main>
<Head>
<title>Pouet :D</title>
</Head>
<div className="errorContainer">
<h1>{statusCode ? statusCode : "404"}</h1>
<h2>{statusCode ? codesTexts[statusCode] : codesTexts[404]}</h2>
</div>
<style jsx>{`
.errorContainer {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
height: 100%
}
.separator {
border: 1px solid black
}
h1 {
font-size: 250px;
margin: 10px;
color: transparent;
background: linear-gradient(90deg, #45CAFC 0%, #4285F4 92.19%);
background-clip: text;
-webkit-background-clip: text;
}
`}</style>
</main>
)
}
getInitialProps = ({ res, err }: NextPageContext) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404
return { statusCode }
}
}

View File

@ -1,124 +1,119 @@
import { NextPage } from 'next'
import Head from 'next/head'
import Link from 'next/link'
import '../styl/styl.styl'
import Element from '../components/Element'
import Navbar from '../components/Navbar'
import Header from '../components/Header'
import Menu from '../components/Menu'
import Filters from '../components/Filters'
import Category from '../components/interfaces/Category'
import { Component } from 'react'
import Post, { PostHeader } from '../components/Post'
interface Props {
userAgent?: string
}
interface el {
link: string,
title: string,
img: string,
date: Date
}
interface el extends PostHeader {}
interface States {
elements: el[]
loaded: boolean
asideHeight: number
categories: string[]
}
// export const config = {amp: 'hybrid'}
const categories: Category[] = [
{
name: "test",
slug: "pouet"
}
]
const elements: el[] = [
{link:"/studiomoto", title:"Studiomoto, Site de référencement d'événement moto en France", img:"/uploads/stm.png", date: new Date()},
{link:"/studiomoto", title:"Studiomoto, Site de référencement d'événement moto en France", img:"/uploads/stm.png", date: new Date()},
{link:"/studiomoto", title:"Studiomoto, Site de référencement d'événement moto en France", img:"/uploads/stm.png", date: new Date()},
{link:"/studiomoto", title:"Studiomoto, Site de référencement d'événement moto en France", img:"/uploads/stm.png", date: new Date()},
{link:"/studiomoto", title:"Studiomoto, Site de référencement d'événement moto en France", img:"/uploads/stm.png", date: new Date()},
{link:"/studiomoto", title:"Studiomoto, Site de référencement d'événement moto en France", img:"/uploads/stm.png", date: new Date()},
{link:"/studiomoto", title:"Dzeio.io, Services en ligne pour vous simplifier la vie !", img:"/sea.jpg", date: new Date()},
{link:"/studiomoto", title:"Loram ipsum dolor sit amet", img:"/uploads/stm.png", date: new Date()},
{link:"/studiomoto", title:"j'aime les licornes et leurs jolie corne", img:"/uploads/stm.png", date: new Date()},
{link:"/studiomoto", title:"Pokémon ! attrapez les tous !", img:"/uploads/stm.png", date: new Date()},
{link:"/studiomoto", title:"abcde", img:"/uploads/stm.png", date: new Date()},
{link:"/studiomoto", title:"def", img:"/uploads/stm.png", date: new Date()},
{link:"/studiomoto", title:"abc", img:"/uploads/stm.png", date: new Date()},
{link:"/studiomoto", title:"Studiomoto, Site de référencement d'événement moto en France", img:"/uploads/stm.png", date: new Date()},
{link:"/studiomoto", title:"Studiomoto, Site de référencement d'événement moto en France", img:"/uploads/stm.png", date: new Date()},
{link:"/studiomoto", title:"Studiomoto, Site de référencement d'événement moto en France", img:"/uploads/stm.png", date: new Date()},
{link:"/studiomoto", title:"Studiomoto, Site de référencement d'événement moto en France", img:"/uploads/stm.png", date: new Date()}
]
let elements: PostHeader[] = []
export default class Page extends Component<Props, States> {
onQuery = (query: string) => {
console.log(`query: ${query}`)
onQuery = async (query: string, recent: boolean = true) => {
// console.log(`query: ${query}`)
const t= elements.filter(el => {
return el.title.toLowerCase().includes(query.toLowerCase())
})
if (recent) {
t.sort((a, b) => {
return (a.date < b.date) ? 1 : -1
})
} else {
t.sort((a, b) => {
return (a.date > b.date) ? 1 : -1
})
}
this.setState({
elements: t
})
}
onHeight = (height: number) => {
onHeight = async (height: number) => {
this.setState({
asideHeight: height
})
}
componentDidMount() {
async componentDidMount() {
const posts = await Post.fetchAll()
const header: Array<PostHeader> = []
let cats: Array<string> = []
posts.forEach(el => {
el.fetchSync()
header.push(el.header)
cats.push(...el.header.tags)
})
header.sort((a, b) => {
return (a.date < b.date) ? 1 : -1
})
cats.sort((a, b) => (a < b) ? -1 : 1)
elements = header
this.setState({
elements
elements: header,
loaded: true,
categories: cats.filter((item, pos) => {return cats.indexOf(item) === pos})
})
}
render() {
return (
<div>
<Head>
<link rel="manifest" href="/manifest.json" />
</Head>
<Navbar>
<Menu />
</Navbar>
<Header />
<main>
<span>
{this.state && this.state.elements.map((el, index) => (
<Element key={index} link={el.link} title={el.title} image={el.img} date={el.date} />
))}
{this.state && this.state.elements && this.state.elements.length !== 0 ? this.state.elements.map((el, index) => (
<Element key={index} link={"/"+el.category.toLowerCase() + "/" + el.id} title={el.title} image={el.image} alt={el.imageAlt} date={el.date || new Date} />
)) : this.state && this.state.loaded ? (
<div>La recherche n'a rien donnée <span className="emoji">😢</span></div>
) : (
<div>Chargement en cours... <span className="emoji">😃</span></div>
)}
</span>
<Filters categories={categories} onQuery={this.onQuery} onHeight={this.onHeight}/>
</main>
<Filters categories={this.state && this.state.categories || []} onQuery={this.onQuery} onHeight={this.onHeight}/>
<style jsx>{`
span {
span:not(.emoji) {
display: flex;
flex-wrap: wrap;
justify-content: center;
flex-grow: 1;
}
main {
display: flex;
flex-direction: column;
flex-direction: column-reverse;
}
main span {
flex-grow: 1;
div {
font-size: 36px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
@media (min-width: 820px) and (min-height: ${this.state && this.state.asideHeight ? this.state.asideHeight+100 : 600}px) {
main span {
span {
max-width: calc(100% - 400px);
}
main {
flex-direction: row;
}
}
`}</style>
</div>
</main>
)
}
}

View File

@ -1,28 +0,0 @@
import { NextPage, NextPageContext } from "next"
import React from 'react'
import Post from "../../components/Post"
import ReactMarkdown from 'react-markdown'
interface Props {
post: Post
}
const PostPage: NextPage<Props> = (props: Props) => {
// React.
return (
<main>
<ReactMarkdown source={props.post.content}/>
</main>
)
}
PostPage.getInitialProps = async (context: NextPageContext) => {
const { slug } = context.query
if (typeof slug === "object") throw new Error("Slug is not correct")
const post = new Post(slug)
await post.fetch()
return {post}
}
export default PostPage

85
pages/tag/[tag].tsx Normal file
View File

@ -0,0 +1,85 @@
import { NextPage, NextPageContext } from "next"
import Link from 'next/link'
import Post from "../../components/Post"
import Element from '../../components/Element'
import Error from "../_error"
// import posts from '../../posts/pages.json'
// import firstline from 'firstline'
// import 'fs'
interface Props {
files: Post[],
tag: string
}
const PortfolioIndex: NextPage<Props> = (props: Props) => {
const el: JSX.Element[] = []
for (const post of props.files) {
el.push(
)
}
if (props.files.length === 0) {
return (
<Error statusCode={404} />
)
}
return (
<div>
<h2>Tag: {props && props.tag}</h2>
<div>
{props.files.map((post, index) => (
<Element key={index} link={"/"+post.header.category.toLowerCase() + "/" + post.header.id} title={post.header.title} image={post.header.image} date={post.header.date || new Date} />
))}
</div>
<style jsx>{`
span:not(.emoji) {
display: flex;
flex-wrap: wrap;
justify-content: center;
flex-grow: 1;
}
div div {
display: flex;
flex-direction: row;
}
div {
display: flex;
flex-direction: column;
}
h2 {
display: inline-block;
padding: 20px;
margin: auto;
background: linear-gradient(90deg, #45CAFC 0%, #4285F4 92.19%);
color: white;
font-size: 24px;
text-transform: uppercase;
font-weight: 500;
text-align: center;
border-radius: 10px;
}
`}</style>
</div>
)
}
PortfolioIndex.getInitialProps = async (context: NextPageContext) => {
const { tag } = context.query
if (typeof tag === "object" || tag === "[tag]") return {files: [], tag: ""}
const arr: Post[] = []
for (const post of await Post.fetchAll()) {
if (!post.isStarted) await post.fetch()
let tags = []
for (const tg of post.header.tags) {
tags.push(tg.toLowerCase())
}
if (!tags.includes(tag)) continue
arr.push(post)
}
return {files: arr, tag: tag} as Props
}
export default PortfolioIndex

12
posts/portfolio/dzeio.md Normal file
View File

@ -0,0 +1,12 @@
---
title: DZE.IO
id: dzeio
image: /uploads/dzeio.png
imageAlt: Logo & Description de dze.io
date: 2020-06-01
category: Portfolio
url: https://dze.io/
tags: ["Golang", "Javascript", "Typescript", "CSS", "Stylus"]
---
# Dze.io, Page en cours 😄

View File

@ -0,0 +1,28 @@
---
title: Studiomoto
id: studiomoto
image: /uploads/stm.png
imageAlt: Logo & Description de studiomoto
date: 2018-10-10
category: Portfolio
url: https://www.studiomoto.fr/
tags: ["PHP", "Symfony", "Javascript", "Typescript", "CSS", "Stylus"]
---
# Studiomoto, Site de référencement dévénement Moto en France
## Objectifs premiers
Lidée était de transformer un encien site sous Wordpress en un site construit de zéro afin de pouvoir en faire un système facilement ajustable, plus customisable et plus rapide.
Afin datteindre ces trois points il a été choisis de partir sur Symfony afin de pouvoir avoir un site fonctionnel rapidement.
## Développement
Le site est en constante évolution et la roadmap est la suivant :
- Visualisation des évènement en front pour le public et un dashboard pour les Administrateurs afin de pouvoir ajouter et modifier le évenements
- Les utilisateurs et organisations peuvent se connecter au site et peuvent rajouter des évènements
- Plus dinteraction entre le site et les utilisateurs en ajoutant une fonctionnalité de balade.

View File

@ -1,5 +0,0 @@
---
title: pouet :D
---
# Pouet :D

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b4ade4e081b12facd22a9e7ffddb10bdf32d978024ba4cd6a06cae5c971da0ef
size 38088

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:35ed03e1600c62124e5a967b2445565289dbff87363f091f72061ecbbb4a697f
size 39996

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8122bfa8c6570dfefbd6b38d1ae6a24f0c81c75a1a8e57b8e6a2f46c625ef979
size 38304

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9107717e3528638dd53a4f56aa0bdd5411ef277c071a4f42f8a3f9c86568538a
size 40620

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:71177b607e4053a8c472a68d94a6efba29919f54838d4724ba86cd362ccc6d51
size 38416

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2f43776004e78088aa1adf1c7935ccf70743f6e9032301f21f334be7a7dd72bf
size 40440

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d8eb6984408b33575359478bf531e78fd3535b08797784aef54b1847e5511705
size 41032

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:85c9c438bc11f0d54065e60f7c884f420f7487a4e921c04334ac944b1613b405
size 38876

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:05377820e3ee66a23bb0d40e0caefba8f1579bfb0dbcd2b0cc4e84d0c7de084a
size 41220

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:47d06f5a02e49f34766f0f8e746f3c6ec641b9930278772e84c26e6df25bf14d
size 38168

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cb795f523fcd07ffda06179b4649605d5552916e018dec9dce5e2a59ce64ae28
size 38612

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b73a0b7dbb381344b1f9b32406be8b4d065073f1fbbc586dcc686c4df292cb28
size 41184

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5d04db6398893af7c86bb0aff112589c4ff529b56fac128346ff72819a3132b9
size 39056

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:80d0f93f3f4f029ee0f45fe0ad68c8caf3fbaa739dc61912013d4bf718e34fc6
size 41308

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b68177274ef371eadec5cff5672c9230e94faea0afb1557af79a5eb95c4f53f7
size 39136

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0ddcffab417bd4cd18f31474363162cf5e0fb8165ede8ca50d9afde666cc1d84
size 41472

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:702d0521c6f58476abc9f16f09b6a2943850b1b54cf098f573f6ee4a5ff5db6a
size 8305868

2
public/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-agent: *
Allow: /

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 131 B

3
public/uploads/dzeio.png Normal file
View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1fdb6128c4505163e416216b96f2139214fa1d589f39e8ad01da842e42e4d319
size 1556838

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 130 B

110
styl/include/_aileron.styl Normal file
View File

@ -0,0 +1,110 @@
@font-face
font-family: 'Aileron'
src: local('Aileron Bold'), local('Aileron-Bold'), url('/fonts/Aileron-Bold.ttf') format('truetype')
font-weight: bold
font-style: normal
@font-face
font-family: 'Aileron'
src: local('Aileron Italic'), local('Aileron-Italic'), url('/fonts/Aileron-Italic.ttf') format('truetype')
font-weight: normal
font-style: italic
@font-face
font-family: 'Aileron'
src: local('Aileron Black Italic'), local('Aileron-BlackItalic'), url('/fonts/Aileron-BlackItalic.ttf') format('truetype')
font-weight: 900
font-style: italic
@font-face
font-family: 'Aileron'
src: local('Aileron Bold Italic'), local('Aileron-BoldItalic'), url('/fonts/Aileron-BoldItalic.ttf') format('truetype')
font-weight: bold
font-style: italic
@font-face
font-family: 'Aileron'
src: local('Aileron Light'), local('Aileron-Light'), url('/fonts/Aileron-Light.ttf') format('truetype')
font-weight: 300
font-style: normal
@font-face
font-family: 'Aileron'
src: local('Aileron Black'), local('Aileron-Black'), url('/fonts/Aileron-Black.ttf') format('truetype')
font-weight: 900
font-style: normal
@font-face
font-family: 'Aileron'
src: local('Aileron Heavy Italic'), local('Aileron-HeavyItalic'), url('/fonts/Aileron-HeavyItalic.ttf') format('truetype')
font-weight: 900
font-style: italic
@font-face
font-family: 'Aileron'
src: local('Aileron UltraLight Italic'), local('Aileron-UltraLightItalic'), url('/fonts/Aileron-UltraLightItalic.ttf') format('truetype')
font-weight: 200
font-style: italic
@font-face
font-family: 'Aileron'
src: local('Aileron Heavy'), local('Aileron-Heavy'), url('/fonts/Aileron-Heavy.ttf') format('truetype')
font-weight: 900
font-style: normal
@font-face
font-family: 'Aileron'
src: local('Aileron Thin'), local('Aileron-Thin'), url('/fonts/Aileron-Thin.ttf') format('truetype')
font-weight: 100
font-style: normal
@font-face
font-family: 'Aileron'
src: local('Aileron UltraLight'), local('Aileron-UltraLight'), url('/fonts/Aileron-UltraLight.ttf') format('truetype')
font-weight: 200
font-style: normal
@font-face
font-family: 'Aileron'
src: local('Aileron SemiBold'), local('Aileron-SemiBold'), url('/fonts/Aileron-SemiBold.ttf') format('truetype')
font-weight: 600
font-style: normal
@font-face
font-family: 'Aileron'
src: local('Aileron Regular'), local('Aileron-Regular'), url('/fonts/Aileron-Regular.ttf') format('truetype')
font-weight: normal
font-style: normal
@font-face
font-family: 'Aileron'
src: local('Aileron Thin Italic'), local('Aileron-ThinItalic'), url('/fonts/Aileron-ThinItalic.ttf') format('truetype')
font-weight: 100
font-style: italic
@font-face
font-family: 'Aileron'
src: local('Aileron Light Italic'), local('Aileron-LightItalic'), url('/fonts/Aileron-LightItalic.ttf') format('truetype')
font-weight: 300
font-style: italic
@font-face
font-family: 'Aileron'
src: local('Aileron SemiBold Italic'), local('Aileron-SemiBoldItalic'), url('/fonts/Aileron-SemiBoldItalic.ttf') format('truetype')
font-weight: 600
font-style: italic

View File

@ -0,0 +1,8 @@
@font-face
font-family: "Blobmoji"
// TODO: find a way to use it internally
src: local('Blobmoji')
src: url('/fonts/Blobmoji.ttf')
.emoji
font-family: 'Blobmoji'

View File

@ -12,7 +12,7 @@ button, a.button
cursor: pointer
color: #FFF
background-color: $color
border-radius: 5px
border-radius: 10px
border: none
padding: 7px 14px
transition-property: box-shadow, background-color
@ -26,6 +26,12 @@ button, a.button
line-height: 22px
text-decoration: none
box-sizing: border-box
justify-content center
&.large
padding 20px 25px
text-transform uppercase
font-size 20px
&:hover
background-color: $hover

View File

@ -11,8 +11,7 @@ $color = #4285F4
&:not(:last-of-type)
margin-right 10px
input
input, select
display block
border-radius 10px
background-color #EEE
@ -45,6 +44,13 @@ $color = #4285F4
background-color #FFF
box-shadow inset 0 0 0 2px $color, 0 0 0 3px rgba(204,204,204, .75)
select
-webkit-appearance none
-moz-appearance none
appearance none
display inline-flex
label
text-transform uppercase
margin auto
@ -60,13 +66,12 @@ $color = #4285F4
font-weight 300
font-style italic
// Icons
i
width 25px
width 24px
height 24px
position absolute
left 28px
left 20px
top 18px
// pointer-events none
color #666
@ -74,17 +79,25 @@ $color = #4285F4
&.icon-right i
left initial
right 28px
right 20px
&.icon-left input
&.icon-left input,
&.icon-left select
padding-left 50px
&.icon-right input
&.icon-right input,
&.icon-right select
padding-right 50px
&.icon-right input, &.icon-left input
&.icon-right input, &.icon-left input,
&.icon-right select, &.icon-left select
max-width calc(100% - 76px)
input:focus:not([readonly]) + i
&.icon-right select, &.icon-left select
max-width 100%
input:focus:not([readonly]) + i,
select:focus:not([readonly]) + i
color $color

View File

@ -1,6 +1,16 @@
@import "include/_aileron"
@import "include/_blobmoji"
@import "include/_button"
@import "include/_input"
html, body {
margin 0
}
div, span, a, p, h1, h2, h3, h4, h5, h6, i {
font-family Aileron, BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif
}
::selection
background #4285F4
color white

View File

@ -2911,6 +2911,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
fs@^0.0.1-security:
version "0.0.1-security"
resolved "https://registry.yarnpkg.com/fs/-/fs-0.0.1-security.tgz#8a7bd37186b6dddf3813f23858b57ecaaf5e41d4"
integrity sha1-invTcYa23d84E/I4WLV+yq9eQdQ=
fsevents@^1.2.7:
version "1.2.9"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f"