Big commit i'm sorry ...

This commit is contained in:
2026-05-09 01:53:54 +02:00
committed by kitoy
parent ced34339f9
commit 50829e9f60
184 changed files with 54564 additions and 588 deletions

View File

@@ -7,14 +7,15 @@ from wtforms import validators
from wtforms.validators import DataRequired, Length
class FormBloc(FlaskForm):
title = StringField('Titre', validators=[DataRequired()])
name = StringField('Nom du bloc', validators=[DataRequired()])
title = StringField('Titre', validators=[])
subtitle = StringField('Sous-titre', validators=[])
property = StringField('Classes CSS', validators=[])
bg_color = ColorField('Couleur de fond', validators=[])
fg_color = ColorField('Couleur du texte', validators=[])
text = TextAreaField('Présentation', validators=[Length(min=0, max=1000)])
text = TextAreaField('Présentation', validators=[Length(min=0, max=30000)])
text_button = StringField('Texte du bouton', validators=[])
link = StringField('Lien', validators=[])
image = FileField('image', validators=[FileAllowed(['jpg', 'png', 'gif'], 'Images only!')])
image = FileField('image', validators=[FileAllowed(['jpg','jpeg','png'])])
email = StringField('Email', validators=[])
phone = StringField('Téléphone', validators=[])
address = StringField('Addresse', validators=[])
@@ -35,8 +36,7 @@ class ImageUpload(FlaskForm):
subtitle = StringField('Sous-titre',validators=[])
text = StringField('Une description de votre image', validators=[])
category = StringField('Categories',validators=[])
image = FileField('image', validators=[FileRequired(),
FileAllowed(['jpg', 'png', 'gif'], 'Images only!')])
image = FileField('image', validators=[FileAllowed(['jpg', 'png', 'gif'], 'Images only!')])
class Login(FlaskForm):
login = StringField('Votre identifiant', validators=[DataRequired()])

View File

@@ -4,13 +4,15 @@ from werkzeug.utils import secure_filename
from app.forms import *
from PIL import Image
from os import remove, path
from pathlib import Path, PurePath, PurePosixPath
class MyBlocs:
def new(blocName, blocType):
def new(blocName, blocType, theme):
mybloc = None
if blocType in MyBlocs.listTypeBlocs():
if blocType in MyBlocs.listTypeBlocs(theme):
mybloc=dict()
mybloc['name'] = str(blocName)
mybloc['type'] = str(blocType)
@@ -26,4 +28,13 @@ class MyBlocs:
def newImgForm():
return ImageUpload()
def listTypeBlocs(theme):
typeBlocs = list()
folders = Path(PurePosixPath('./app').joinpath('templates', theme, 'blocs'))
for folder in folders.iterdir():
if not(folder.is_dir()):
typeBloc=folder.name.split('.')[0]
typeBlocs.append(typeBloc[1:])
return typeBlocs

View File

@@ -3,105 +3,179 @@ import os
from werkzeug.utils import secure_filename
from app.forms import *
from PIL import Image
from os import remove, path
from pathlib import Path, PurePath, PurePosixPath
class DataSite:
datas = ""
static_path = ""
user_folder = None
username = str()
datas = str()
static_path = None
trash = list()
filej= ""
filej= None
def __init__(self, username, users_folder ):
self.filej = os.path.join(users_folder, username,'ident.json')
with open(self.filej, 'r') as f:
def __init__(self, username :str , users_folder :str ):
self.username = username
self.user_folder = PurePosixPath(users_folder).joinpath(username)
self.filej = Path(PurePosixPath(self.user_folder).joinpath('ident.json'))
with self.filej.open(newline='', mode='r', encoding='utf-8') as f:
self.datas = json.load(f)
self.static_path = os.path.join(users_folder, username, 'public')
self.static_path = Path(PurePosixPath(self.user_folder.joinpath('public')))
self.static_path.mkdir(parents=True, exist_ok=True)
def loadFile(self):
with open(self.filej, 'r') as f:
self.datas = json.load(f)
def loadFile_tmp(self):
with open(self.filej_tmp, 'r') as f:
self.datas = json.load(f)
def updateBloc(bloc, form):
bloc['name'] = form.name.data
bloc['bg_color'] = form.bg_color.data
bloc['fg_color']=form.fg_color.data
bloc['title'] = form.title.data
bloc['text'] = form.text.data
bloc['text_button']=form.text_button.data
bloc['link'] = form.link.data
if form.image.data :
f=form.image.data
filename = secure_filename(f.filename)
extension = filename.rsplit('.', 1)[1].lower()
f.save(os.path.join(
self.static_path, 'img','bg_custom.'+ extension))
if not self.filej.isfile():
raise FileNotFoundError(f"Le fichier ident.json n'existe pas : {self.filej}")
bloc['bg_img']='bg_custom.'+extension
bloc['email'] = form.email.data
bloc['address']=form.address.data
bloc['phone'] = form.phone.data
bloc['postal_code'] = form.postal_code.data
bloc['city'] = form.city.data
with self.filej.open(newline='', mode='r', encoding='utf-8') as f:
self.datas = json.load(f)
def getBlocs(self, page: str):
if page != '':
page = Path(self.user_folder.joinpath(page, 'page.json'))
datas = ""
if not page.exists():
raise FileNotFoundError(f"Le fichier page.json n'existe pas : {page}")
datas = json.loads(page.read_text())
else:
datas = self.datas
return datas
def updateBloc(self, bloc, form, files=None):
print (form)
for field in form():
print(field)
if files != None:
f = files['image']
filename = secure_filename(f.filename)
extension = filename.rsplit('.', 1)[1].lower()
bg_custom = 'bg_custom_'+bloc['name']+'.'+extension
f.save(PurePath.joinpath(
self.static_path, 'img', bg_custom))
bloc['bg_img'] = bg_custom
return bloc
def updateImg(self, bloc, img, form):
filename = img['file']
img['title'] = form.title.data
img['subtitle'] = form.subtitle.data
img['description'] = form.description.data
img['category'] = form.category.data
self.datas['blocs'][bloc][filename]=img
def updateImg_tmp(self, bloc, img, form):
filename = img['file']
img['title'] = form.title.data
img['subtitle'] = form.subtitle.data
img['description'] = form.description.data
img['category'] = form.category.data
self.datas['blocs'][bloc][filename]=img
def addImg(self, bloc, image_uploaded):
def updateImg(self, image, form):
img = dict()
img['title'] = ""
img['subtitle']=""
img['text'] = ""
img['category'] = ""
img['file']=image_uploaded
self.datas['blocs'][bloc][image_uploaded]=img
img['title'] = form.title.data
img['subtitle'] = form.subtitle.data
img['description'] = form.description.data
img['category'] = form.category.data
img['file'] = image
return img
def addImg(self, image_uploaded):
img = dict()
def addImg_tmp(self, bloc, image_uploaded):
img = dict()
img['title'] = ""
img['subtitle']=""
img['text'] = ""
img['category'] = ""
img['file']=image_uploaded
self.datas_tmp['blocs'][bloc][image_uploaded]=img
img['file'] = image_uploaded
return img
def saveJson(self):
remove(self.filej)
with open (self.filej, 'w') as f:
f.write(json.dumps(self.datas, indent=5))
def delImg(self, bloc, img):
def delImg(self, img):
try:
images = self.datas['images']
images.pop(img)
except KeyError:
print("Le fichier n'est pas dans la liste")
if bloc['type'] == 'gallery':
img_file = Path(PurePosixPath(self.user_folder).joinpath('public', 'img', bloc[img]['file'] ))
img_thumb_file = Path(PurePosixPath(self.user_folder).joinpath('public','img', 'thumbnails', bloc[img]['file']))
if img_file.exists():
img_file.unlink()
if img_thumb_file.exists():
img_thumb_file.unlink()
bloc.pop(img)
else:
self.trash.append(img)
img_file = Path(PurePosixPath(self.user_folder).joinpath('public', 'img', img ))
return bloc
def saveMenu(self, menu:dict):
self.datas['menu'] = menu
self.filej.write_text(json.dumps(self.datas, indent=5))
def saveBlocs(self, page='', blocs=dict()):
if page != '':
datas = dict()
datas['blocs'] = blocs
page_info = Path(PurePosixPath(self.user_folder).joinpath(page, 'page.json'))
if not page_info.exists():
raise FileNotFoundError(f"Le fichier page.json n'existe pas : {page}")
page_info.write_text(json.dumps(datas, indent=3))
else:
self.datas['blocs']= blocs
self.filej.write_text(json.dumps(self.datas, indent=5))
def getPages(self):
mypages = list()
folders = Path(self.user_folder)
for folder in folders.iterdir():
if folder.name != 'public' and folder.is_dir():
mypages.append(folder.name)
return mypages
def new_page(self, pageName:str, pageType:str):
page_folder = Path(PurePosixPath(self.user_folder).joinpath(pageName))
page = Path(PurePosixPath(self.user_folder).joinpath(pageName, 'page.json'))
page_type = Path('./app/templates/themes/'+self.datas['config']['theme']+'/pages/'+pageType+'.json')
if not page_type.exists():
raise FileNotFoundError(f"Le type de page n'existe pas : {page_type}")
else:
page_folder.mkdir()
page.write_text(page_type.read_text())
if not page.exists():
raise FileNotFoundError(f"La page n'a pas pu etre créé : {page}")
return True
def rm_page(self, pageName :str):
page_folder = Path(PurePosixPath(self.user_folder).joinpath(pageName))
page = Path(PurePosixPath(self.user_folder).joinpath(pageName, 'page.json'))
#First delete the json page
try:
page.unlink()
print(f"Successfully deleted: {page}")
except FileNotFoundError:
print(f"File not found: {page}. Nothing to delete.")
# Delete the empty directory
if page_folder.is_dir():
try:
page_folder.rmdir()
print(f"Successfully removed empty directory: {page_folder}")
except OSError as e:
print(f"Error removing directory (might not be empty): {e}")
def writeHTML(self, pageName :str, htmlExport :str):
public_folder = Path(PurePosixPath(self.user_folder).joinpath('public'))
pageName = pageName+'.html'
page = Path(PurePosixPath(self.user_folder).joinpath('public', pageName))
page.write_text(htmlExport)
def emptyTrash(self):
for file in self.trash:

View File

@@ -12,7 +12,7 @@ class Users:
if auth_file != None:
self.__auth_file = auth_file
self.__auth_method = "file"
x
def getPasswd(self, username):
with open(self.auth_file, 'r') as f:
users_info = f.readlines()

View File

@@ -1,9 +1,10 @@
from app import app
from flask import render_template, url_for, request, flash, redirect, session, make_response
from flask import render_template, url_for, request, flash, redirect, session, make_response, send_from_directory
import json
from markupsafe import escape
from werkzeug.utils import secure_filename
import os
import re
from app.forms import ImageForm, ImageUpload
from app.models.sites import DataSite
from app.models.blocs import MyBlocs
@@ -17,20 +18,15 @@ from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
app.secret_key = 'your_secret_key'
app.config.from_pyfile('config.py')
THEMES_FOLDER = './themes/'
USERS_FOLDER = app.config['USERS_FOLDER']
bcrypt = Bcrypt(app)
EXT_IMG = {'.jpg', '.JPG', '.png', '.PNG', '.gif', '.GIF', '.bmp', '.BMP', '.jpeg', '.JPEG' }
@app.route('/', methods=['GET', 'POST'])
@app.route('/index.html', methods=['GET', 'POST'])
@app.route('/login', methods=['GET', 'POST'])
def login():
form = Login()
@@ -65,91 +61,79 @@ def logout():
return redirect(url_for('login'))
@app.route('/edit/save')
@login_required
def savefile():
user = '%s'% escape(session['username'])
db = DataSite(user, USERS_FOLDER)
db.saveJson(TMP.datas)
db.emptyTrash()
return redirect(url_for('edit'))
@app.route('/edit')
@login_required
def edit():
user = '%s'% escape(session['username'])
db = DataSite(user, USERS_FOLDER)
theme = db.datas['config']['theme']
menu = db.datas['menu']
myblocs = db.datas['blocs']
return render_template('/edit.html.tpl',
blocEdit="",
config=db.datas['config'],
menu=menu,
theme=theme,
myblocs=myblocs
)
@app.route('/edit/<page>')
@app.route('/', methods=['GET'], defaults={'page': ''})
@app.route('/<page>/', methods=['GET'])
@login_required
def edit(page):
user = '%s'% escape(session['username'])
page = '%s' % escape(page)
db = DataSite(user, USERS_FOLDER)
blocs_page = db.getpage()
mypages = db.getPages()
theme = db.datas['config']['theme']
menu = db.datas['menu']
myblocs = db.datas['blocs']
myblocs = db.getBlocs(page)['blocs']
return render_template('/edit.html.tpl',
blocEdit="",
config=db.datas['config'],
menu=menu,
theme=theme,
pagesList=mypages,
page=page,
myblocs=myblocs
)
@app.route('/edit/images/<bloc>/<image>', methods=['GET', 'POST'])
@app.route('/edit/images/<bloc>/<image>', methods=['GET', 'POST'], defaults={'page': ''})
@app.route('/<page>/edit/images/<bloc>/<image>', methods=['GET', 'POST'])
@login_required
def editPhoto(image, bloc):
def editPhoto(page, image, bloc):
user = '%s'% escape(session['username'])
page = '%s' % escape(page)
image = '%s' % escape(image)
bloc = '%s' % escape(bloc)
db = DataSite(user, USERS_FOLDER)
theme = db.datas['config']['theme']
form = ImageForm()
mesimages = db.datas['blocs'][bloc]
myblocs = db.getBlocs(page)['blocs']
if form.validate_on_submit():
if request.method == 'POST':
db.updateImg(bloc, mesimages[image], form)
db.saveJson()
myblocs[bloc][image] = db.updateImg(image, form)
db.saveBlocs(page, myblocs)
form = MyBlocs.makeForm(db.datas['blocs'][bloc])
if "hx-request" in request.headers:
return render_template(theme+'/blocs/edit/images_list.html',
theme=theme,
bloc=mesimages,
form=form)
if "hx-request" in request.headers:
return make_response(('Image modified', 200))
else:
return redirect ('/edit#'+bloc)
return redirect ('/#'+bloc)
else:
return render_template(theme+'/blocs/edit/_image-edit'+'.html', form=form, image=mesimages[image], bloc=mesimages)
return render_template(THEMES_FOLDER+theme+'/blocs/edit/_image-edit'+'.html', form=form, image=myblocs[bloc][image], bloc=myblocs[bloc])
@app.route('/upload-gallery/<bloc>', methods=['POST'])
@app.route('/upload-gallery/<bloc>', methods=['POST'], defaults={'page': ''})
@app.route('/<page>/upload-gallery/<bloc>', methods=['POST'])
@login_required
def drop_upload(bloc):
def drop_upload(page, bloc):
user = '%s'% escape(session['username'])
page = '%s' % escape(page)
db = DataSite(user, USERS_FOLDER)
datas = dict()
file = request.files['file']
filename = secure_filename(file.filename)
myblocs = db.getBlocs(page)['blocs']
ext = os.path.splitext(filename)[-1]
save_path = "./app/static/img/portfolio"
save_path = os.path.join(USERS_FOLDER, user, 'public', 'img' )
if ext not in EXT_IMG :
return make_response(('Le fichier n\'est pas une image', 400))
current_chunk = int(request.form['dzchunkindex'])
print (current_chunk)
if (os.path.isfile(os.path.join(save_path, 'fullsize', filename ))) and current_chunk == 0:
if (os.path.isfile(os.path.join(save_path, filename ))) and current_chunk == 0:
return make_response(('Un fichier avec le même nom existe déjà', 400))
try:
with open(os.path.join(save_path, 'fullsize', filename ), 'ab') as f:
with open(os.path.join(save_path, filename ), 'ab') as f:
f.seek(int(request.form['dzchunkbyteoffset']))
f.write(file.stream.read())
except OSError:
@@ -159,117 +143,231 @@ def drop_upload(bloc):
if current_chunk + 1 == total_chunks:
# This was the last chunk, the file should be complete and the size we expect
if os.path.getsize(os.path.join(save_path, 'fullsize', filename )) != int(request.form['dztotalfilesize']):
if os.path.getsize(os.path.join(save_path, filename )) != int(request.form['dztotalfilesize']):
return make_response(('La taille du fichier source est différentes', 500))
else:
with Image.open(os.path.join(save_path, 'fullsize', filename )) as img :
img.thumbnail((300,300))
with Image.open(os.path.join(save_path, filename )) as img :
img.thumbnail((640,480))
img.save(os.path.join(save_path, 'thumbnails', filename ) )
db.addImg(bloc, filename)
db.saveJson()
myblocs[bloc][filename] = db.addImg(filename)
db.saveBlocs(page, myblocs)
return make_response(('Chunk upload succesfull', 200))
@app.route('/edit/images/<bloc>/del/<image>', methods=['GET'])
@login_required
def delImage(bloc, image):
user = '%s'% escape(session['username'])
db = DataSite(user, USERS_FOLDER)
db.delImg(image)
db.saveJson()
return redirect ('/edit#'+bloc)
@app.route('/edit/<blocname>', methods=['GET', 'POST'])
@app.route('/edit/images/<bloc>/del/<image>', methods=['GET'], defaults={'page': ''})
@app.route('/<page>/edit/images/<bloc>/del/<image>', methods=['GET'])
@login_required
def editBloc(blocname):
def delImage(page, bloc, image):
user = '%s'% escape(session['username'])
page = '%s' % escape(page)
db = DataSite(user, USERS_FOLDER)
myblocs = db.getBlocs(page)['blocs']
myblocs[bloc] = db.delImg(myblocs[bloc], image)
db.saveBlocs(page, myblocs)
return make_response(('Image removed', 200))
@app.route('/imgs_list/<blocname>', methods=['GET'], defaults={'page': ''})
@app.route('/<page>/imgs_list/<blocname>', methods=['GET'])
@login_required
def imagesList(page, blocname):
user = '%s'% escape(session['username'])
page = '%s' % escape(page)
blocname = '%s' % escape(blocname)
db = DataSite(user, USERS_FOLDER)
theme = db.datas['config']['theme']
bloc = db.datas['blocs'][blocname]
form = MyBlocs.makeForm(bloc)
myblocs = db.getBlocs(page)['blocs']
bloc = myblocs[blocname]
return render_template(THEMES_FOLDER+theme+'/blocs/edit/images_list.html',page=page,
bloc=bloc)
@app.route('/edit/<blocname>', methods=['GET', 'POST'], defaults={'page': ''})
@app.route('/<page>/edit/<blocname>', methods=['GET', 'POST'])
@login_required
def editBloc(page, blocname):
user = '%s'% escape(session['username'])
page = '%s' % escape(page)
db = DataSite(user, USERS_FOLDER)
theme = db.datas['config']['theme']
myblocs = db.getBlocs(page)['blocs']
bloc = myblocs[blocname]
form = MyBlocs.makeForm(bloc)
if request.method == 'POST':
datas=dict()
if form.validate_on_submit():
print(form.name.data)
db.datas['blocs'][form.name.data]=DataSite.updateBloc(bloc, form)
db.saveJson()
for field, value in request.form.items():
print (field)
if field != 'csrf_token' and field != 'image':
myblocs[blocname][field] = value
if 'image' in request.files.keys():
file = request.files['image']
print ('filename: '+ file.filename)
filename = secure_filename(file.filename)
bg_custom = 'bg_custom_'+bloc['name']+filename
print('bg_custom : '+ bg_custom)
file.save(os.path.join(USERS_FOLDER, user, 'public', 'img', bg_custom ))
if 'bg_img' in bloc.keys():
db.delImg(bloc, bloc['bg_img'])
myblocs[blocname]['bg_img'] = bg_custom
db.saveBlocs(page, myblocs)
flash(u'Vos changements on été pris en compte et enregistré', 'success')
else:
print(form.errors)
if "hx-request" in request.headers:
return render_template(theme+'/blocs/_'+bloc['type']+'.html',
return render_template(THEMES_FOLDER+theme+'/blocs/_'+bloc['type']+'.html',
theme=theme,
bloc=db.datas['blocs'][blocname])
page=page,
bloc=myblocs[blocname])
else:
return redirect(url_for('edit'))
else:
if "hx-request" in request.headers:
return render_template(theme+'/blocs/edit/_'+bloc['type']+'.html',
return render_template(THEMES_FOLDER+theme+'/blocs/edit/_'+bloc['type']+'.html',
theme=theme,
bloc=bloc,
page=page,
form=form)
else:
return render_template('/edit.html.tpl',
config=db.datas['config'],
menu=db.datas['menu'],
myblocs=db.datas['blocs'],
myblocs=myblocs,
page=page,
theme=theme,
blocEdit=bloc['name'],
form=form)
@app.route('/view/<bloc>', methods=['GET'])
@app.route('/view/<bloc>', methods=['GET'], defaults={'page': ''})
@app.route('/<page>/view/<bloc>', methods=['GET'])
@login_required
def viewBloc(bloc):
def viewBloc(page, bloc):
user = '%s'% escape(session['username'])
page = '%s' % escape(page)
db = DataSite(user, USERS_FOLDER)
theme = db.datas['config']['theme']
myblocs = db.datas['blocs']
form = MyBlocs.makeForm(db.datas['blocs'][bloc])
bloc = db.datas['blocs'][bloc]
myblocs = db.getBlocs(page)['blocs']
bloc = myblocs[bloc]
return render_template(theme+'/blocs/_'+bloc['type']+'.html',
return render_template(THEMES_FOLDER+theme+'/blocs/_'+bloc['type']+'.html',
bloc=bloc)
@app.route('/addBloc', methods=['POST'])
@app.route('/addBloc', methods=['POST'], defaults={'page': ''})
@app.route('/<page>/addBloc', methods=['POST'])
@login_required
def addBloc():
def addBloc(page):
user = '%s'% escape(session['username'])
page = '%s' % escape(page)
db = DataSite(user, USERS_FOLDER)
for key in request.form:
print (key +':'+ request.form[key])
blocName = request.form['blocName']
blocType = request.form['blocType']
bloc = MyBlocs.new(blocName, blocType)
if bloc != None:
db.datas['blocs'][blocName] = { 'name': blocName, 'type': blocType}
flash (u'Bloc ajouté avec succès', 'succes');
else:
return make_response(('Invalid type of bloc ', 500))
datas = dict()
myblocs = db.getBlocs(page)['blocs']
blocName = '%s' % escape(request.form['blocName'])
blocType = request.form['blocType']
bloc = MyBlocs.new(blocName, blocType, db.datas['config']['theme'])
test_name = "^[A-Za-z0-9_-]*$"
if bloc != None:
if bool(re.match(test_name, blocName)) :
myblocs[blocName] = { 'name': blocName, 'type': blocType}
flash (u'Bloc ajouté avec succès', 'succes');
else:
return make_response(('Invalid bloc Name', 500))
db.saveBlocs(page, myblocs)
db.saveJson()
if "hx-request" in request.headers:
return render_template('/admin/_blocs-list.html',
myblocs=db.datas['blocs'])
else:
return redirect(url_for('edit'))
@app.route('/sortblocs', methods=['POST'])
@app.route('/addPage', methods=['POST'])
@login_required
def sortblocs():
def addPage():
user = '%s'% escape(session['username'])
db = DataSite(user, USERS_FOLDER)
datas = dict()
pageName = '%s' % escape(request.form['pageName'])
pageType = request.form['pageType']
test_name = "^[A-Za-z0-9_-]*$"
if bool(re.match(test_name, pageName)) :
try :
page = db.new_page(pageName, pageType)
except FileExistsError:
return make_response(('Page already exist', 500))
else:
flash (u'Page créé avec succès', 'success');
else:
return make_response(('Invalid type ', 500))
mypages = db.getPages()
return redirect(url_for('edit', page=pageName))
@app.route('/rmPage/<pageName>')
@login_required
def rmPage(pageName):
pageName = '%s' % escape(pageName)
user = '%s'% escape(session['username'])
db = DataSite(user, USERS_FOLDER)
try:
db.rm_page(pageName)
flash (u'Page supprimé avec succès', 'success');
except OSError as e:
flash (u"La page n'a pa pu être supprimer", 'error');
return redirect(url_for('edit'))
@app.route('/sortblocs', methods=['POST'], defaults={'page': ''})
@app.route('/<page>/sortblocs', methods=['POST'])
@login_required
def sortblocs(page):
user = '%s'% escape(session['username'])
page = '%s' % escape(page)
db = DataSite(user, USERS_FOLDER)
config = db.datas['config']
menu = db.datas['menu']
myblocs = deepcopy(TMP.datas['blocs'])
db.datas['blocs']=dict()
datas = dict()
myblocs = dict()
myblocs_orig = db.getBlocs(page)['blocs']
for bloc in request.form:
if bloc != 'csrf_token':
bloc_name = bloc.replace("bloc-", "", 2)
db.datas['blocs'][bloc_name] = myblocs[bloc_name]
myblocs[bloc_name] = myblocs_orig[bloc_name]
flash (u'Les blocs ont été réagencés', 'succes');
db.saveJson()
db.saveBlocs(page, myblocs)
return redirect(url_for('edit'))
@app.route('/editmenu', methods=['POST'])
@@ -291,7 +389,59 @@ def editMenu():
else:
db.datas['menu'][menu] = str(request.form[menu])
db.saveJson()
db.saveMenu(db.datas['menu'])
flash (u'Le menu a bien été modifier', 'success');
return redirect(url_for('edit'))
@app.route('/public/img/<filename>')
@login_required
def myimgs(filename):
user = '%s' % escape(session['username'])
return send_from_directory(
os.path.join(USERS_FOLDER, user, 'public', 'img'), filename )
@app.route('/public/img/thumbnails/<filename>')
@login_required
def mythumbnails(filename):
user = '%s' % escape(session['username'])
return send_from_directory(
os.path.join(USERS_FOLDER, user, 'public', 'img', 'thumbnails'), filename )
@app.route('/favicon.ico', methods=['GET'])
@app.route('/favicon.ico/', methods=['GET'])
def favicon():
return make_response(('Ok! ', 200))
@app.route('/publish', methods=['GET'])
@login_required
def publish():
user = '%s' % escape(session['username'])
db = DataSite(user, USERS_FOLDER)
listPages = db.getPages()
theme = db.datas['config']['theme']
menu = db.datas['menu']
for page in listPages:
myblocs = db.getBlocs(page)['blocs']
htmlExport = render_template('/index.html.tpl',
config=db.datas['config'],
menu=menu,
theme=theme,
page=page,
myblocs=myblocs)
db.writeHTML(theme, page, htmlExport)
# Export index.html landing page
myblocs = db.getBlocs('')['blocs']
htmlExport = render_template('/index.html.tpl',
config=db.datas['config'],
menu=menu,
theme=theme,
page='/',
myblocs=myblocs)
db.writeHTML(theme, 'index', htmlExport)
return redirect(url_for('edit'))

View File

@@ -63,6 +63,92 @@ h6 {
color: rgba(255, 255, 255, 0.7);
}
.ql-align-center {
text-align: center;
}
.ql-align-left {
text-align: left;
}
.ql-align-right {
text-align: right;
}
blockquote {
padding: 60px 80px 40px;
position: relative;
}
blockquote p {
font-size: 35px;
font-weight: 700px;
text-align: center;
}
/*blockquote p::before {
content: "\f095";
font-family: FontAwesome;
display: inline-block;
padding-right: 6px;
vertical-align: middle;
font-size: 180px;
}*/
blockquote:before {
position: absolute;
font-family: 'FontAwesome';
top: 0;
content:"\f10d";
font-size: 200px;
color: rgba(0,0,0,0.1);
}
blockquote::after {
content: "";
top: 20px;
left: 50%;
margin-left: -100px;
position: absolute;
height: 3px;
width: 200px;
}
blockquote{
font-size: 1.4em;
width:60%;
margin:50px auto;
font-style:italic;
color: #555555;
padding:1.2em 30px 1.2em 75px;
border-left:8px solid #f05f40;
line-height:1.6;
position: relative;
background:#EDEDED;
}
blockquote::before{
font-family:Arial;
content: "\201C";
color:#f05f40;
font-size:4em;
position: absolute;
left: 10px;
top:-10px;
}
blockquote::after{
content: '';
}
blockquote span{
display:block;
color:#333333;
font-style: normal;
font-weight: bold;
margin-top:1em;
}
section {
padding: 8rem 0;
z-index: -1;
@@ -141,7 +227,7 @@ img::-moz-selection {
@media (min-width: 992px) {
#mainNav {
border-color: transparent;
background-color: transparent;
background-color: rgba(0,0,0,0.7);
}
#mainNav .navbar-brand {
color: rgba(255, 255, 255, 0.7);
@@ -190,16 +276,27 @@ header.masthead {
background-size: cover;
}
header.masthead hr {
header.article-img {
padding-top: 10rem;
padding-bottom: calc(10rem - 56px);
background-position: center center;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
header.article-img header.masthead hr {
margin-top: 30px;
margin-bottom: 30px;
}
header.masthead h1 {
header.article-img header.masthead h1 {
font-size: 2rem;
}
header.masthead p {
header.article-img header.masthead p {
font-weight: 300;
}
@@ -210,19 +307,27 @@ header.masthead p {
}
@media (min-width: 992px) {
header.article-img {
height: 50vh;
min-height: 300px;
padding-top: 0;
padding-bottom: 0;
}
header.masthead {
height: 100vh;
min-height: 650px;
padding-top: 0;
padding-bottom: 0;
}
header.masthead h1 {
header.article-img header.masthead h1 {
font-size: 3rem;
}
}
@media (min-width: 1200px) {
header.masthead h1 {
header.article-img header.masthead h1 {
font-size: 4rem;
}
.btn-success {

View File

@@ -4,3 +4,37 @@
border-top-color: currentcolor;
border-color: black;
}
.visible {
display: block;
}
.hidden {
display: none;
}
.msginfo
{
color: white;
font-weight:bold;
}
.msginfo .success
{
background-color: green;
}
.msginfo .error{
background-color: red;
}
.ql-toolbar {
background-color: #fff;
color: #000;
}
.ql-editor {
background-color: rgba(0, 0, 0, 0.3);
}

3
app/static/css/prism.min.css vendored Normal file
View File

@@ -0,0 +1,3 @@
/* PrismJS 1.30.0
https://prismjs.com/download#themes=prism-okaidia&languages=markup+css+clike+javascript+bash+c+cpp+markdown+markup-templating+nginx+php+python */
code[class*=language-],pre[data-language*=language-]{color:#f8f8f2;background:0 0;text-shadow:0 1px rgba(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}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#272822}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8292a2}.token.punctuation{color:#f8f8f2}.token.namespace{opacity:.7}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#f92672}.token.boolean,.token.number{color:#ae81ff}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#a6e22e}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#e6db74}.token.keyword{color:#66d9ef}.token.important,.token.regex{color:#fd971f}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

15
app/static/js/prism.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -12,7 +12,7 @@
dictDefaultMessage: 'Déposez vos fichiers ici!',
paramName: "file",
maxFilesize: 1024, // MB
url: "/upload-dropzone",
url: "{% if page %}/{{ page }}{% endif %}/upload-gallery/{{bloc.name}}",
chunking: true,
forceChunking: true,
chunkSize: 1000000,

View File

@@ -0,0 +1,4 @@
{% if bloc.fg_color != bloc.bg_color %}
color: {{ bloc.fg_color }}; background-color: {{ bloc.bg_color }};
{% endif %}

View File

@@ -9,28 +9,40 @@
<button class="btn btn-outline-success" onclick="printForm()">+</button>
<div class="formAddBloc" id="addBloc">
<form action="/addBloc" method="POST" class="" >
<form action="/addBloc" id="addBloc" method="POST" class="" >
<input id="csrf_token" name="csrf_token" type="hidden" value="{{ csrf_token() }}">
<h3>Créé un nouveau bloc</H3>
<br>
<label for="nom"><b>Nom du bloc </b></label><br/>
<input type="text" name="blocName" placeholder="Nom" id="blocName" required="">
<label for="blocName">Nom du bloc (caractères sans accents uniquement) </label>
<div class="input-group">
<input type="text"
name="blocName"
class="form-control"
id="blocName"
placeholder="Bloc1"
required>
<div class="invalid-feedback">
Entrez le nom du bloc sans espaces ni caractères spéciaux
</div>
</div>
<br>
<label for="link"><b>Type de bloc</b></label>
<br/>
<select id="blocType" name="blocType" size="8">
<option value="presentation">Présentation</option>
<select id="blocType" name="blocType" size="8" required>
<option value="presentation"> Ecran d'Accueil </option>
<option value="presentation"> Image En-tête d'article </option>
<option value="text">Texte libre</option>
<option value="simple_article">Article Simple</option>
<option value="gallery">Galerie</option>
<option value="external_link">Lien externe</option>
<option value="contact">Contact</option>
</select>
<br/>
<button type="submit" class="btn btn-success"
hx-post="/addBloc"
hx-swap="outerHTML"
hx-target="#myblocs"> Créer le bloc </button>
onclick="valid_nameBloc()"
hx-post="/addBloc"
hx-swap="outerHTML"
hx-target="#myblocs"> Créer le bloc </button>
</form>
</div>
<hr/>
@@ -39,9 +51,20 @@
</div>
<script>
function valid_nameBloc()
{
var blocName = document.getElementById("blocName");
let name_field = blocName.value.trim();
let Valid = /^[a-z0-9]+$/i.test(name_field);
console.log(Valid);
if (!Valid) {
document.getElementById("blocName").setAttribute("class", "form-control is-invalid");
}
}
</script>

View File

@@ -2,10 +2,10 @@
<form class="sortblocs" action="/sortblocs" method="POST" id="myblocs">
<div id="myblocs" class="myblocs">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
{% for item in myblocs.keys() %}
<div class="bloc" id="bloc-{{ item }}" style="margin-top: 10px;">
<div class="bloc" id="bloc-{{ item }}" style="margin-top: 10px;">
<button class="btn"> {{ item }} - {{ myblocs[item]['type'] }} </button>
<input class="input-menu" id="bloc-{{ item }}" name="{{ item }}" value="" style="display:none;">
<button class="btn btn-danger" onclick="deleteItem(this)"> x </button>
@@ -29,5 +29,5 @@
fallbackOnBody: true,
swapThreshold: 0.65,
})};
</script>
</script>

View File

@@ -1,9 +0,0 @@
{% for item in myblocs.keys() %}
<div class="bloc" id="bloc-{{ item }}" style="margin-top: 10px;">
<button class="btn"> {{ item }} - {{ myblocs[item]['type'] }} </button>
<input class="input-menu" id="bloc-{{ item }}" name="{{ item }}" value="" style="display:none;">
<button class="btn btn-danger" onclick="deleteItem(this)"> x </button>
</div>
{% endfor %}

View File

@@ -6,14 +6,14 @@
type="button"
data-toggle="modal"
data-target="#addBloc">
Agencer es blocs
Blocs de cette page
</button>
<button class="btn btn-info nav-brand js-scroll-trigger"
type="button"
data-toggle="modal"
data-target="#mymenu">
Editer le menu
Mon menu
</button>
@@ -21,7 +21,7 @@
type="button"
data-toggle="modal"
data-target="#mypages">
Gérer mes pages
Mes pages
</button>
<button class="btn btn-secondary"
@@ -46,39 +46,19 @@
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title"> Agencer mes blocs </h4>
<h4 class="modal-title"> Mes blocs </h4>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body text-center">
{% include 'admin/_bloc-edit.html' %}
</div>
<div class="modal-footer mx-auto">
<a class="btn btn-danger" href="/edit" > Annuler </a>
<a class="btn btn-danger" href="/" > Annuler </a>
</div>
</div>
</div>
</div>
<div class="modal" id="mypages">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title"> Mes pages </h4>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body text-center">
{% include 'admin/_pages-edit.html' %}
</div>
<div class="modal-footer mx-auto">
<a class="btn btn-danger" href="/edit" > Annuler </a>
</div>
</div>
</div>
</div>
<div class="modal" id="mymenu">
<div class="modal-dialog modal-lg">
<div class="modal-content">
@@ -91,12 +71,32 @@
{% include 'admin/_menu-edit.html' %}
</div>
<div class="modal-footer mx-auto">
<a class="btn btn-danger" href="/edit" > Annuler </a>
<a class="btn btn-danger" href="/" > Annuler </a>
</div>
</div>
</div>
</div>
<div class="modal" id="mypages">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title"> Mes pages </h4>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body text-center">
{% include 'admin/_pages-edit.html' %}
</div>
<div class="modal-footer mx-auto">
<a class="btn btn-danger" href="/" > Annuler </a>
</div>
</div>
</div>
</div>
</div>
<div class="modal" id="parameters">
<div class="modal-dialog modal-lg">
@@ -134,8 +134,9 @@
<!-- Modal footer -->
<div class="modal-footer mx-auto">
<a class="btn btn-danger" href="/edit" > Annuler </a>
<a class="btn btn-danger" href="/" > Annuler </a>
</div>
</div>
</div>
</div>
</div>

View File

@@ -52,7 +52,6 @@
</form>
</div>
<script src="/static/vendors/Sortable/Sortable.min.js"></script>
<script type="text/javascript">

View File

@@ -1,35 +0,0 @@
<!--
<form class="sortblocs" action="/createpage" method="POST" id="mypages">
<div id="myblocs" class="myblocs">
{% for item in mypages %}
<div class="page" id="page" style="margin-top: 10px;">
<button class="btn"> {{ }} - {{ myblocs[item]['type'] }} </button>
<input class="input-menu" id="bloc-{{ item }}" name="{{ item }}" value="" style="display:none;">
<button class="btn btn-danger" onclick="deleteItem(this)"> x </button>
</div>
{% endfor %}
</div>
</form>
-->
<script src="/static/vendors/Sortable/Sortable.min.js"></script>
<script type="text/javascript">
var elements = document.getElementsByClassName('page');
for (var i = 0; i < elements.length; i++) {
new Sortable(elements[i], {
group: 'shared',
animation: 150,
invertSwap: true,
fallbackOnBody: true,
swapThreshold: 0.65,
})};
</script>

View File

@@ -1,24 +1,47 @@
<div id="mypages">
<ul style="list-style:none;" id="mypages" class="mypages">
<li><a href="/"> <button class="btn"> Page d'accueil </button> </a> </li>
<!--
<form class="sortblocs" action="/createpage" method="POST" id="mypages">
<div id="myblocs" class="myblocs">
</div>
</form>
{% for item in pagesList %}
-->
<script src="/static/vendors/Sortable/Sortable.min.js"></script>
<script type="text/javascript">
var elements = document.getElementsByClassName('page');
for (var i = 0; i < elements.length; i++) {
new Sortable(elements[i], {
group: 'shared',
animation: 150,
invertSwap: true,
fallbackOnBody: true,
swapThreshold: 0.65,
})};
</script>
<li style="padding-top: 10px;">
<a href="/{{ item }}/">
<button class="btn"> {{ item }} </button>
</a>
<a href="/rmPage/{{ item }}"><button class="btn btn-danger"> X </button></a>
</li>
{% endfor %}
</ul>
<div class="formAddPage" id="addPage">
<form action="/addPage" id="addPage" method="POST" class="" >
<input id="csrf_token" name="csrf_token" type="hidden" value="{{ csrf_token() }}">
<h3>Créé un nouveau bloc</H3>
<br>
<label for="blocName">Nom du bloc (caractères sans accents uniquement) </label>
<div class="input-group">
<input type="text"
name="pageName"
class="form-control"
id="pageName"
placeholder="Ma_Page"
required>
<div class="invalid-feedback">
Entrez le nom de la page sans espaces ni caractères spéciaux
</div>
</div>
<br>
<label for="link"><b>Type de bloc</b></label>
<br/>
<select id="pageType" name="pageType" size="8" required>
<option value="article_image"> Article avec une image d'en tête </option>
<option value="simple_article"> Page blanche Texte libre </option>
</select>
<br/>
<button type="submit" class="btn btn-success"
onclick="valid_nameBloc()"> Créer la page </button>
</form>
</div>
</div>

View File

@@ -1,41 +0,0 @@
<header class="masthead text-center text-white d-flex" style="background-image: url('/static/img/{{ bloc.bg_img }}');">
<div class="container my-auto">
<form method='POST' action='/edit/accueil' enctype="multipart/form-data">
{{ form.csrf_token }}
<div class="row">
<div class="col-lg-10 form-group mx-auto">
{{ form.titre(value=bloc.title,
class="form-control form-control-lg text-center text-uppercase")
}}
{% for error in form.titre.errors %}
<span style="color: red;">{{ error }}</span>
{% endfor %}
<hr>
</div>
<div class="col-lg-8 form-group mx-auto">
{% set f = form.presentation.process_data(bloc.text) %}
{{ form.presentation(
class="form-control text-center",
rows="7")
}}
{% for error in form.presentation.errors %}
<span style="color: red;">{{ error }}</span>
{% endfor %}
<br/>
<div class="col-lg-4 form-group mx-auto">
{{ form.texte_boutton(value=bloc.text_button,
class="form-control text-center") }}
{% for error in form.texte_boutton.errors %}
<span style="color: red;">
{{ error }}</span>
{% endfor %}
</div>
<div class="col-lg-3 form-group mx-auto">
{{ form.image(class="form-control" ) }}
</div>
{{ form.submit() }}
</div>
</div>
</form>
</div>
</header>

View File

@@ -1,16 +0,0 @@
<header class="masthead text-center text-white d-flex" style="background-image: url('/static/img/{{ mesinfos.presentation.bg_img }}');" >
<div class="container my-auto">
<div class="row">
<div class="col-lg-10 mx-auto">
<h1 class="text-uppercase">
<strong> {{ mesinfos.presentation.title }} </strong>
</h1>
<hr>
</div>
<div class="col-lg-8 mx-auto">
<p class="text-faded mb-5"> {{ mesinfos.presentation.text }} </p>
<a class="btn btn-primary btn-xl js-scroll-trigger" href="#about">{{ mesinfos.presentation.text_button }} </a>
</div>
</div>
</div>
</header>

View File

@@ -1,34 +0,0 @@
<div class="container-fluid p-0">
<form method='POST' action='/edit/images/{{ bloc.name }}/{{ image.file }}' hx-swap="outerHTML" hx-target="#{{ bloc.name }}">
{{ form.csrf_token }}
<div class="row">
<div class="col-lg-2 mx-auto my-4 text-center">
<img class="img-fluid" src="/static/img/portfolio/thumbnails/{{ image.file }}" alt="{{ image.description }}">
</div>
<div class="col-lg-4 mx-auto my-4 text-center">
<h3> Titre </h3>
{{ form.title(value=image.title,
class="form-control form-control-lg text-center") }}
<hr class="light my-4">
<h3>Sous-titre</h3>
{{ form.subtitle(value=image.subtitle, class="form-control text-center") }}
<br/>
<h3> Catégorie </h3>
{{ form.category(value=image.category, class="form-control text-center") }}
<hr class="light my-4">
<h3> Description de votre image </h3>
<p> Une description de l'image est utile pour les personnes mal-voyantes et au référecement de votre site </p>
{{ form.description(value=image.description, class="form-control text-center") }}
<br/>
<button class="btn btn-success" type="submit" data-dismiss="modal"
hx-post="/edit/images/{{ bloc.name }}/{{ image.file }}"
hx-swap="outerHTML"
hx-target="#{{ bloc.name }}"> Valider </button>
<a class="btn btn-danger mx-auto" href="/edit/images/del/{{ image.nom_fichier }}">Supprimer l'image</a>
</div>
</div>
</form>
</div>

View File

@@ -1,11 +0,0 @@
<section class="bg-primary" id="about">
<div class="container">
<div class="row">
<div class="col-lg-8 mx-auto text-center">
<h2 class="section-heading text-white">{{ mesinfos.text.title }}</h2>
<hr class="light my-4">
<p class="text-faded mb-4"> {{ mesinfos.text.text }} </p>
</div>
</div>
</div>
</section>

View File

@@ -1,20 +0,0 @@
<header class="masthead text-center text-white d-flex" style="background-image: url('/static/img/{{ bloc.bg_img }}');" id="{{ bloc.name }}" >
<div class="container my-auto">
<div class="row">
<div class="col-lg-10 mx-auto">
<h1 class="text-uppercase">
<strong> {{ bloc.title }} </strong>
</h1>
<hr>
</div>
<div class="col-lg-8 mx-auto">
<p class="text-faded mb-5"> {{ bloc.text }} </p>
<a class="btn btn-primary btn-xl js-scroll-trigger" href="#">{{ bloc.text_button }}
</a>
</div>
</div>
</div>
</header>

View File

@@ -1,6 +0,0 @@
<form id="uploader" methods="POST" class="dropzone dz-clickable"></form>
{% include theme+'/blocs/edit/images_list.html' %}
{% include '_js_dropzone.html' %}

View File

@@ -1,30 +0,0 @@
{% extends 'creative/_layout.html' %}
{% block body %}
{% include theme+'/_nav.html' %}
{% include theme+'/_navtool.html' %}
{% for bloc in myblocs.values() %}
{% if blocEdit == bloc.name %}
{% include theme+'/_'+bloc.type+'-edit.html' %}
{% else %}
{% include theme+'/_'+bloc.type+'.html' %}
<button class="btn btn-success btn-xl pull-right"
style="margin-top:-7rem; margin-right:1rem;"
hx-get="/edit/{{ bloc.name }}"
hx-target="#{{bloc.name}}"
hw-swap="none"
> Éditer </button>
{% endif %}
{% endfor %}
{% endblock %}

View File

@@ -1,15 +0,0 @@
{% extends "creative/layout.html" %}
{% block body %}
<!-- Navigation -->
{% include 'creative/_nav.html' %}
{% for bloc in myblocs.values() %}
{% include 'creative/_'+bloc.type+'.html' %}
{% endfor %}
{% endblock %}

View File

@@ -1,15 +0,0 @@
{% extends "creative/layout.html" %}
{% block body %}
<!-- Navigation -->
{% include 'creative/_nav.html' %}
{% for bloc in myblocs.values() %}
{% include 'creative/_'+bloc.type+'.html' %}
{% endfor %}
{% endblock %}

View File

@@ -1,19 +0,0 @@
{# creative/layout.html #}
<!DOCTYPE html>
<html lang="fr">
<head>
<!-- Style CSS -->
{% include 'creative/_head.html' %}
</head>
<body id="page-top">
{% block body %}
{% endblock %}
</body>
{% include 'creative/javascript.html' %}
</html>

View File

@@ -1,10 +1,31 @@
<div class="text-center mx-auto">
<button class="btn btn-success"
hx-post='/edit/{{ bloc.name }}'
hx-swap="outerHTML"
onclick="saveText()"
hx-post='{% if page %}/{{ page }}{% endif %}/edit/{{ bloc.name }}'
hx-swap="outerHTML swap:100ms"
hx-target="#{{ bloc.name }}" > Enregistrer </button>
<button class="btn btn-danger"
hx-get="/view/{{ bloc.name }}"
onclick="enableModBtn()"
hx-get="{% if page %}/{{ page }}{% endif %}/view/{{ bloc.name }}"
hx-swap="outerHTML"
hx-target="#{{ bloc.name }}" > Annuler </button>
</div>
<script>
function enableModBtn(){
document.getElementById("modify-{{ bloc.name }}").disabled = false;
}
function saveText(){
editor_content = document.getElementById("editor");
if (editor_content != null)
{
document.getElementById("text").innerHTML = quill.getSemanticHTML(0);
delete quill;
}
enableModBtn();
}
</script>

View File

@@ -0,0 +1,4 @@
<script>
document.getElementById("modify-{{ bloc.name }}").disabled = true;
</script>

View File

@@ -0,0 +1,52 @@
<div id="editor">
{{ bloc.text|safe }}
</div>
<br/>
<textarea style="display :none;" id="text" name="text"> </textarea>
<!-- Initialize Quill editor -->
<script>
var toolbarOptions = [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
['link', 'image'],
[{ 'header': 2 }, { 'header': 3 }, { 'header': 4},], // custom button values
[{ 'list': 'ordered'}, { 'list': 'bullet' }, { 'list': 'check' }],
[{ 'script': 'sub'}, { 'script': 'super' }], // superscript/subscript
[{ 'indent': '-1'}, { 'indent': '+1' }], // outdent/indent
[{ 'direction': 'rtl' }], // text direction
[{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown
[{ 'header': [ 2, 3, 4, 5, 6, false] }],
[{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme
[{ 'font': [] }],
[{ 'align': [] }],
['clean'] // remove formatting button
];
var quill = new Quill('#editor', {
modules: {
syntax: true, // Include syntax module
toolbar: toolbarOptions // Include button in toolbar
},
theme: 'snow'
});
</script>
<style>
.ql-container {
height: fit-content;
}
.ql-editor {
height: fit-content;
}
</style>

View File

@@ -1,18 +1,22 @@
<h3> Nom du bloc : {{ bloc.name }} </h3>
<div class="row mx-md-5">
<div class="col-auto">
<label> Nom du bloc </label>
{{ form.name(col=4,value=bloc.name,
<label> Classes CSS </label>
{{ form.property(col=4,value=bloc.property,
class="form-control text-center") }}
</div>
<div class="col-auto">
<label> Couleur de l'arrière plan </label>
{{ form.bg_color(col=4,
class="form-control form-control-color text-center") }}
class="form-control form-control-color text-center",
value=bloc.bg_color) }}
</div>
<div class="col-auto">
<label> Couleur du texte</label>
{{ form.fg_color(col=4,
class="form-control form-control-color text-center") }}
class="form-control form-control-color text-center",
value=bloc.fg_color) }}
</div>
</div>
<br/>

View File

@@ -15,4 +15,9 @@
.msginfo .error{
background-color: red;
}
.ql-toolbar {
background-color: #fff;
color: #000;
}
</style>

View File

@@ -1,18 +0,0 @@
<style>
.msginfo
{
color: white;
font-weight:bold;
}
.msginfo .success
{
background-color: green;
}
.msginfo .error{
background-color: red;
}
</style>

View File

@@ -2,27 +2,38 @@
{% block css %}
{% include 'edit-tools/css/dropzone' %}
<link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css" rel="stylesheet">
<link href="/static/css/edit-toolbar.css" rel="stylesheet">
<link href="/static/css/prism.min.css" rel="stylesheet">
<!-- Include the highlight.js library -->
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet" />
{% endblock %}
{% block body %}
{% include config.theme+'/_nav.html' %}
{% include config.theme+'/_navtool.html' %}
{% include 'themes/'+config.theme+'/_nav.html' %}
{% include 'admin/_menu-admin.html' %}
{% macro print_colors() %}
{% endmacro %}
{% for bloc in myblocs.values() %}
{% if blocEdit == bloc.name %}
{% include config.theme+'/blocs/edit/_'+bloc.type+'.html' %}
{% else %}
{% include config.theme+'/blocs/_'+bloc.type+'.html' %}
<button class="btn btn-success btn-xl pull-right"
style="margin-top:-7rem; margin-right:1rem;"
hx-get="/edit/{{ bloc.name }}"
hx-target="#{{bloc.name}}"
hx-swap="oob"> Modifier </button>
{% include 'themes/'+config.theme+'/blocs/edit/_'+bloc.type+'.html' %}
{% else %}
{% include 'themes/'+config.theme+'/blocs/_'+bloc.type+'.html' %}
<button class="btn btn-success btn-xl pull-right"
id="modify-{{ bloc.name }}"
style="margin-top:-7rem; margin-right:1rem;"
hx-get="{% if page %}/{{ page }}{% endif %}/edit/{{ bloc.name }}"
hx-target="#{{bloc.name}}"
hx-swap="oob"> Modifier </button>
{% endif %}
{% endfor %}
@@ -31,5 +42,13 @@
{% endblock %}
{% block js %}
<!-- Include the Quill library -->
<script src="/static/vendors/htmx/htmx.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/cpp.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
<script src="/static/js/prism.js"></script>
{% endblock %}

View File

@@ -3,7 +3,7 @@
<head>
<!-- Style CSS -->
{% include config.theme+'/_head.html' %}
{% include 'themes/'+config.theme+'/_head.html' %}
{% block css %}
{% endblock %}
</head>
@@ -11,17 +11,17 @@
<body id="page-top">
{% block body %}
{% include config.theme+'/_nav.html' %}
{% include 'themes/'+config.theme+'/_nav.html' %}
{% for bloc in myblocs.values() %}
{% include config.theme+'/blocs/_'+bloc.type+'.html' %}
{% include 'themes/'+config.theme+'/blocs/_'+bloc.type+'.html' %}
{% endfor %}
{% endblock %}
</body>
{% include config.theme+'/javascript.html' %}
{% include 'themes/'+config.theme+'/javascript.html' %}
{% block js %}
{% endblock js %}
</html>

View File

@@ -15,7 +15,7 @@
</a>
<div class="dropdown-menu" aria-labelledby="{{ item }}">
{% for subitem in menu[item].keys() %}
<a class="dropdown-item" href="{{item[subitem]}}">{{ subitem }}</a>
<a class="dropdown-item" href="{{menu[item][subitem]}}">{{ subitem }}</a>
{% endfor %}
</div>
</li>

View File

@@ -1,11 +1,10 @@
<section class="bg-primary" id="{{ bloc.name }}">
<section class="bg-primary {{ bloc.property }}" id="about">
<div class="container">
<div class="row">
<div class="col-lg-8 mx-auto text-center">
<h2 class="section-heading text-white">{{ bloc.title }}</h2>
<hr class="light my-4">
<p class="text-faded mb-4"> {{ bloc.text }} </p>
</div>
</div>
</div>

View File

@@ -1,4 +1,4 @@
<section id="contact" >
<section id="contact" class="{{ bloc.property }}" style="{% include '_print_colors.css' %}">
<div class="container" id="{{ bloc.name }}">
<div class="row">
<div class="col-lg-8 mx-auto text-center">

View File

@@ -1,7 +1,7 @@
<section class="bg-dark text-white" id="{{ bloc.name }}" hx-swap-oob="true" >
<section class="{{ bloc.property }}" style="{% include '_print_colors.css' %}" id="{{ bloc.name }}" >
<div class="container text-center">
<h2 class="mb-4">{{ bloc.title }}</h2>
<p> {{ bloc.text }} </p>
{{ bloc.text|safe }}
<a class="btn btn-light btn-xl sr-button" href="{{ bloc.link }}">
{{ bloc.text_button }}
</a>

View File

@@ -1,12 +1,14 @@
<section class="p-0" id="{{ bloc.name }}">
<section class="p-0 {{ bloc.property }}" id="{{ bloc.name }}">
<div class="container-fluid p-0">
<div class="row no-gutters popup-gallery">
{% for image in bloc.values() %}
{% if image.file %}
<div class="col-lg-4 col-sm-6 text-center">
<a class="portfolio-box" href="/static/img/portfolio/fullsize/{{ image.file }}" alt="{{ image.description }}">
<img class="img-fluid" src="/static/img/portfolio/thumbnails/{{ image.file }}" alt="{{ image.description }}">
<a class="portfolio-box" href="/public/img/{{ image.file }}"
alt="{{ image.description }}">
<img class="img-fluid" src="/public/img/thumbnails/{{ image.file }}"
alt="{{ image.description }}">
<div class="portfolio-box-caption">
<div class="portfolio-box-caption-content">
<div class="project-category text-faded">

View File

@@ -0,0 +1,14 @@
<header class="article-img text-center text-white d-flex" style="{% include '_print_colors.css' %} background-image: url('/public/img/{{ bloc.bg_img }}');" id="{{ bloc.name }}" >
<div class="container my-auto">
<div class="row" style="mix-blend-mode: hard-light;background-color: rgba(0,0,0,0.3);">
<div class="col-lg-10 mx-auto">
<h1 class="text-uppercase">
<strong> {{ bloc.title }} </strong>
</h1>
<hr>
<h3> {{ bloc.subtitle }} </h3>
</div>
</div>
</div>
</header>

View File

@@ -0,0 +1,25 @@
<header class="masthead text-center text-white d-flex" style="{% include '_print_colors.css' %} background-image: url('/public/img/{{ bloc.bg_img }}');" id="{{ bloc.name }}" >
<div class="container my-auto">
<div class="row" style="mix-blend-mode: hard-light;background-color: rgba(0,0,0,0.3);">
<div class="col-lg-10 mx-auto">
<h1 class="text-uppercase">
<strong> {{ bloc.title }} </strong>
</h1>
<hr>
</div>
<div class="col-lg-10 mx-auto">
<p class="text-faded mb-5"> {{ bloc.text|safe }} </p>
</div>
</div>
<div class="center mx-auto mt-5">
<a class="btn btn-primary btn-xl js-scroll-trigger" href="{{ bloc.link }}">
{{ bloc.text_button }}
</a>
</div>
</div>
</header>

View File

@@ -0,0 +1,11 @@
<section class="{{ bloc.property }}" id="{{ bloc.name }}" style="{{print_colors()}}" >
<h2 class="section-heading text-center">{{ bloc.title }}</h2>
<hr class="my-4">
<div class="container">
<div class="col-lg-8 mx-auto">
<p class="mb-4"> {{ bloc.text|safe }} </p>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,8 @@
<section class="{{ bloc.property }}" id="{{ bloc.name }}" style="{% include '_print_colors.css' %}">
<div class="container">
<div class="px-3 mx-auto">
{{ bloc.text|safe }}
</div>
</div>
</section>

View File

@@ -50,3 +50,4 @@
</form>
</div>
</section>
{% include 'edit-tools/_disable-modify-button.html' %}

View File

@@ -7,8 +7,8 @@
{{ form.title(value=bloc.title,
class="form-control form-control-lg text-center") }}
<h3> Description du lien </h3>
{{ form.text(value=bloc.text,
class="form-control text-center") }}
{% include 'edit-tools/_editor-wysiwyg.html' %}
<h3> Votre lien (https://exemple.com) </h3>
{{ form.link(value=bloc.link, class="form-control text-center") }}
<br />
@@ -23,4 +23,4 @@
{% include 'edit-tools/_btn-save-cancel.html' %}
</form>
</div>
{% include 'edit-tools/_disable-modify-button.html' %}

View File

@@ -0,0 +1,12 @@
<form id="uploader" methods="POST" class="dropzone dz-clickable">
{{ form.csrf_token }}
</form>
<div hx-get="{{ url_for('imagesList', page=page, blocname=bloc.name) }}" hx-trigger="load, every 7s">
</div>
<div id="editImage">
</div>
{% include '_js_dropzone.html' %}
{% include 'edit-tools/_disable-modify-button.html' %}

View File

@@ -0,0 +1,43 @@
<div class="container my-auto pt-5" style="background-color: rgba(0,0,0,0.3);">
<form method='POST' action='/edit/{{ bloc.name }}' enctype="multipart/form-data" hx-swap="outerHTML" hx-target="#{{ bloc.name }}" hx-encoding="multipart/form-data">
{{ form.csrf_token }}
<div class="container my-auto">
<div class="row">
<div class="col-lg-10 form-group mx-auto">
{% include 'edit-tools/_form-base.html' %}
{{ form.title(value=bloc.title,
class="form-control form-control-lg text-center text-uppercase")
}}
{% for error in form.title.errors %}
<span style="color: red;">{{ error }}</span>
{% endfor %}
<hr>
</div>
<div class="col-lg-10 form-group mx-auto">
{{ form.subtitle(value=bloc.subtitle,
class="form-control form-control-lg text-center text-uppercase")
}}
{% for error in form.subtitle.errors %}
<span style="color: red;">{{ error }}</span>
{% endfor %}
</div>
<div class="col-lg-8 form-group mx-auto">
{{ form.image(class="form-control" ) }}
</div>
</div>
{% include 'edit-tools/_btn-save-cancel.html' %}
</form>
</div>
{% include 'edit-tools/_disable-modify-button.html' %}

View File

@@ -1,10 +1,10 @@
<div class="container-fluid p-0">
<div class="container-fluid p-0" id="editImageInfo">
<form method='POST' action='/edit/images/{{ bloc.name }}/{{ image.file }}' hx-swap="outerHTML" hx-target="#editImg">
{{ form.csrf_token }}
<div class="row">
<div class="col-lg-2 mx-auto my-4 text-center">
<img class="img-fluid" src="/static/img/portfolio/thumbnails/{{ image.file }}" alt="{{ image.description }}">
<img class="img-fluid" src="/public/img/thumbnails/{{ image.file }}" alt="{{ image.description }}">
</div>
<div class="col-lg-4 mx-auto my-4 text-center">
<h3> Titre </h3>
@@ -22,10 +22,13 @@
{{ form.description(value=image.description, class="form-control text-center") }}
<br/>
<button class="btn btn-success" type="submit" data-dismiss="modal"
hx-post="/edit/images/{{ bloc.name }}/{{ image.file }}"
hx-post="/edit/images/{{ bloc.name }}/{{ image.file }}"
hx-swap="outerHTML"
hx-target="#editImg"> Valider </button>
<a class="btn btn-danger mx-auto" href="/edit/images/del/{{ image.nom_fichier }}">Supprimer l'image</a>
hx-target="#editImageInfo"
hx-swap="delete"> Valider </button>
<button class="btn btn-danger mx-auto" hx-get="{{ url_for('delImage', page=page, bloc=bloc.name, image=image.file) }}"
hx-swap="delete"
hx-target="#editImageInfo"> Supprimer l'image</button>
</div>
</div>
</form>

View File

@@ -1,5 +1,5 @@
<div class="container my-auto">
<form method='POST' action='/edit/{{ bloc.name }}' hx-swap="outerHTML" hx-target="#{{ bloc.name }}" enctype="multipart/form-data">
<div class="container mt-5 py-5 pb-5" style="width:100%;">
<form method='POST' action='/edit/{{ bloc.name }}' enctype="multipart/form-data" hx-swap="outerHTML" hx-target="#{{ bloc.name }}" hx-encoding="multipart/form-data">
{{ form.csrf_token }}
<div class="row">
@@ -15,8 +15,9 @@
</div>
<div class="col-lg-8 form-group mx-auto">
{% set f = form.text.process_data(bloc.text) %}
<textarea class="form-control text-center" id="text" name="text"> {{ bloc.text }} </textarea>
{% for error in form.text.errors %}
{% include 'edit-tools/_editor-wysiwyg.html' %}
{% for error in form.text.errors %}
<span style="color: red;">{{ error }}</span>
{% endfor %}
<br/>
@@ -27,11 +28,11 @@
<span style="color: red;">
{{ error }}</span>
{% endfor %}
<h4> cible du bouton </h4>
<h4> cible du bouton #bloc ou un lien (https://exemple.com) </h4>
{{ form.link(value=bloc.link, class="form-control text-center") }}
<br />
</div>
<div class="col-lg-3 form-group mx-auto">
<div class="col-lg-8 form-group mx-auto">
{{ form.image(class="form-control" ) }}
</div>
{% include 'edit-tools/_btn-save-cancel.html' %}
@@ -39,4 +40,4 @@
</div>
</form>
</div>
{% include 'edit-tools/_disable-modify-button.html' %}

View File

@@ -1,4 +1,4 @@
<section>
<div class="container">
<form method='POST' action='/edit/{{ bloc.name }}' >
{{ form.csrf_token }}
@@ -9,12 +9,12 @@
{{ form.title(col=5,value=bloc.title,
class="form-control form-control-lg text-center") }}
<hr class="light my-4">
<textarea class="form-control text-center" id="text" name="text"> {{ bloc.text }} </textarea>
<br/>
{% include 'edit-tools/_btn-save-cancel.html' %}
{% include 'edit-tools/_editor-wysiwyg.html' %}
{% include 'edit-tools/_btn-save-cancel.html' %}
</div>
</div>
</form>
</div>
</section>
{% include 'edit-tools/_disable-modify-button.html' %}

View File

@@ -0,0 +1,16 @@
<div class="container">
<form method='POST' action='/edit/{{ bloc.name }}' >
{{ form.csrf_token }}
<div class="row">
<div class="col-lg-8 mx-auto text-center">
{% include 'edit-tools/_form-base.html' %}
{% include 'edit-tools/_editor-wysiwyg.html' %}
</div>
</div>
{% include 'edit-tools/_btn-save-cancel.html' %}
</form>
</div>
</section>
{% include 'edit-tools/_disable-modify-button.html' %}

View File

@@ -1,11 +1,11 @@
<section class="p-0">
<section class="p-0" id="{{bloc.name}}">
<div class="container-fluid p-0" id="editImg">
<div class="row no-gutters ">
{% for image in bloc.values() %}
{% if image.file %}
<div class="col-lg-4 col-sm-6 text-center">
<a class="portfolio-box">
<img class="img-fluid" src="/static/img/portfolio/thumbnails/{{ image.file }}" alt="{{ image.description }}">
<img class="img-fluid" src="/public/img/thumbnails/{{ image.file }}" alt="{{ image.description }}">
<div class="portfolio-box-caption">
<div class="portfolio-box-caption-content">
<div class="project-category text-faded">
@@ -20,7 +20,7 @@
</a>
<button type="button" class="btn btn-success m-4"
hx-get="/edit/images/{{ bloc.name }}/{{ image.file }}"
hx-target="#editImg"
hx-target="#editImage"
hw-swap="none">
<i class="fa fa-pencil-square-o"></i>
</button>

View File

@@ -1,4 +1,9 @@
<script>
function rm_node(idName){
const element = document.getElementById(idName);
element.remove();
}
</script>
<!-- Bootstrap core JavaScript -->
<script src="/static/vendors/jquery/jquery.min.js"></script>

View File

@@ -0,0 +1,22 @@
{
"blocs": {
"bloc0": {
"type": "head_image",
"name": "bloc0",
"bg_color": "#000000",
"fg_color": "#000000",
"title": "PAge avec une image de presentation",
"bg_img": "bg_custom_bloc0image_entete_exemple.jpg",
"property": "",
"subtitle": "Votre sous-titre"
},
"bloc1": {
"name": "bloc1",
"type": "text",
"text": "<h3>Votre\u00a0texte\u00a0libre\u00a0!\u00a0</h3><p></p><p>Vous\u00a0pouvez\u00a0\u00e9crire\u00a0ce\u00a0que\u00a0vous\u00a0voulez\u00a0ici</p>",
"bg_color": "#000000",
"fg_color": "#000000",
"property": ""
}
}
}

View File

@@ -0,0 +1,20 @@
{
"blocs": {
"test_bloc": {
"name": "test_bloc",
"type": "text",
"title": "Salut c'est moi et ... voila quoi !!!!",
"text": "<h2 class=\"ql-align-center\">Votre\u00a0titre\u00a0</h2><pre data-language=\"bash\">\n#!/bin/bash\necho \"Un exemple de code\"\n</pre><p></p><h2>Titre\u00a02</h2><h3>Titre\u00a03</h3><h4>Titre\u00a04\u00a0</h4><p></p><p class=\"ql-align-right\"><span style=\"background-color: rgb(255, 153, 0);\">Youpi\u00a0!!!!!!!!!!!!!!!</span></p><p class=\"ql-align-center\"></p><blockquote>Une\u00a0citation</blockquote><p></p><p></p><p>Vous\u00a0pouvez\u00a0aussi\u00a0empiler\u00a0les\u00a0blocs\u00a0de\u00a0texte\u00a0poiur\u00a0les\u00a0mettre\u00a0de\u00a0couleur\u00a0diff\u00e9rentes\u00a0par\u00a0exemple\u00a0</p><p></p><p></p>",
"bg_color": "#000000",
"fg_color": "#000000",
"text_button": null,
"link": null,
"email": null,
"address": null,
"phone": null,
"postal_code": null,
"city": null,
"property": ""
}
}
}

View File

@@ -0,0 +1,451 @@
body,
html {
width: 100%;
height: 100%;
}
body {
font-family: 'Merriweather', 'Helvetica Neue', Arial, sans-serif;
}
hr {
max-width: 50px;
border-width: 3px;
border-color: #F05F40;
}
hr.light {
border-color: #fff;
}
a {
color: #F05F40;
-webkit-transition: all 0.2s;
-moz-transition: all 0.2s;
transition: all 0.2s;
}
.btn-success{
z-index: 999;
position: relative;
}
.img-fluid {
position: relative;
z-index:-30;
}
a:hover {
color: #f05f40;
}
.modal-backdrop{
z-index:auto
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif;
}
.bg-primary {
background-color: #F05F40 !important;
}
.bg-dark {
background-color: #212529 !important;
}
.text-faded {
color: rgba(255, 255, 255, 0.7);
}
.ql-align-center {
text-align: center;
}
.ql-align-left {
text-align: left;
}
.ql-align-right {
text-align: right;
}
blockquote {
padding: 60px 80px 40px;
position: relative;
}
blockquote p {
font-size: 35px;
font-weight: 700px;
text-align: center;
}
/*blockquote p::before {
content: "\f095";
font-family: FontAwesome;
display: inline-block;
padding-right: 6px;
vertical-align: middle;
font-size: 180px;
}*/
blockquote:before {
position: absolute;
font-family: 'FontAwesome';
top: 0;
content:"\f10d";
font-size: 200px;
color: rgba(0,0,0,0.1);
}
blockquote::after {
content: "";
top: 20px;
left: 50%;
margin-left: -100px;
position: absolute;
height: 3px;
width: 200px;
}
blockquote{
font-size: 1.4em;
width:60%;
margin:50px auto;
font-style:italic;
color: #555555;
padding:1.2em 30px 1.2em 75px;
border-left:8px solid #f05f40;
line-height:1.6;
position: relative;
background:#EDEDED;
}
blockquote::before{
font-family:Arial;
content: "\201C";
color:#f05f40;
font-size:4em;
position: absolute;
left: 10px;
top:-10px;
}
blockquote::after{
content: '';
}
blockquote span{
display:block;
color:#333333;
font-style: normal;
font-weight: bold;
margin-top:1em;
}
section {
padding: 8rem 0;
z-index: -1;
}
.section-heading {
margin-top: 0;
}
::-moz-selection {
color: #fff;
background: #212529;
text-shadow: none;
}
::selection {
color: #fff;
background: #212529;
text-shadow: none;
}
img::selection {
color: #fff;
background: transparent;
}
img::-moz-selection {
color: #fff;
background: transparent;
}
#mainNav {
border-bottom: 1px solid rgba(33, 37, 41, 0.1);
background-color: #fff;
font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif;
-webkit-transition: all 0.2s;
-moz-transition: all 0.2s;
transition: all 0.2s;
}
#mainNav .navbar-brand {
font-weight: 700;
text-transform: uppercase;
color: #F05F40;
font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif;
}
#mainNav .navbar-brand:focus, #mainNav .navbar-brand:hover {
color: #f05f40;
}
#mainNav .navbar-nav > li.nav-item > a.nav-link,
#mainNav .navbar-nav > li.nav-item > a.nav-link:focus {
font-size: .9rem;
font-weight: 700;
text-transform: uppercase;
color: #212529;
}
#mainNav .navbar-nav > li.nav-item > a.nav-link:hover,
#mainNav .navbar-nav > li.nav-item > a.nav-link:focus:hover {
color: #F05F40;
}
#mainNav .navbar-nav > li.nav-item > a.nav-link.active,
#mainNav .navbar-nav > li.nav-item > a.nav-link:focus.active {
color: #F05F40 !important;
background-color: transparent;
}
#mainNav .navbar-nav > li.nav-item > a.nav-link.active:hover,
#mainNav .navbar-nav > li.nav-item > a.nav-link:focus.active:hover {
background-color: transparent;
}
@media (min-width: 992px) {
#mainNav {
border-color: transparent;
background-color: rgba(0,0,0,0.7);
}
#mainNav .navbar-brand {
color: rgba(255, 255, 255, 0.7);
}
#mainNav .navbar-brand:focus, #mainNav .navbar-brand:hover {
color: #fff;
}
#mainNav .navbar-nav > li.nav-item > a.nav-link {
padding: 0.5rem 1rem;
}
#mainNav .navbar-nav > li.nav-item > a.nav-link,
#mainNav .navbar-nav > li.nav-item > a.nav-link:focus {
color: rgba(255, 255, 255, 0.7);
}
#mainNav .navbar-nav > li.nav-item > a.nav-link:hover,
#mainNav .navbar-nav > li.nav-item > a.nav-link:focus:hover {
color: #fff;
}
#mainNav.navbar-shrink {
border-bottom: 1px solid rgba(33, 37, 41, 0.1);
background-color: #fff;
}
#mainNav.navbar-shrink .navbar-brand {
color: #F05F40;
}
#mainNav.navbar-shrink .navbar-brand:focus, #mainNav.navbar-shrink .navbar-brand:hover {
color: #f05f40;
}
#mainNav.navbar-shrink .navbar-nav > li.nav-item > a.nav-link,
#mainNav.navbar-shrink .navbar-nav > li.nav-item > a.nav-link:focus {
color: #212529;
}
#mainNav.navbar-shrink .navbar-nav > li.nav-item > a.nav-link:hover,
#mainNav.navbar-shrink .navbar-nav > li.nav-item > a.nav-link:focus:hover {
color: #F05F40;
}
}
header.masthead {
padding-top: 10rem;
padding-bottom: calc(10rem - 56px);
background-position: center center;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
header.article-img {
padding-top: 10rem;
padding-bottom: calc(10rem - 56px);
background-position: center center;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
header.article-img header.masthead hr {
margin-top: 30px;
margin-bottom: 30px;
}
header.article-img header.masthead h1 {
font-size: 2rem;
}
header.article-img header.masthead p {
font-weight: 300;
}
@media (min-width: 768px) {
header.masthead p {
font-size: 1.15rem;
}
}
@media (min-width: 992px) {
header.article-img {
height: 50vh;
min-height: 300px;
padding-top: 0;
padding-bottom: 0;
}
header.masthead {
height: 100vh;
min-height: 650px;
padding-top: 0;
padding-bottom: 0;
}
header.article-img header.masthead h1 {
font-size: 3rem;
}
}
@media (min-width: 1200px) {
header.article-img header.masthead h1 {
font-size: 4rem;
}
.btn-success {
position: relative;
}
}
.service-box {
max-width: 400px;
}
.portfolio-box {
position: relative;
display: block;
max-width: 650px;
margin: 0 auto;
z-index: 0;
}
.portfolio-box .portfolio-box-caption {
position: absolute;
bottom: 0;
display: block;
width: 100%;
height: 100%;
text-align: center;
opacity: 0;
color: #fff;
background: rgba(240, 95, 64, 0.9);
-webkit-transition: all 0.2s;
-moz-transition: all 0.2s;
transition: all 0.2s;
z-index: 0;
}
.portfolio-box .portfolio-box-caption .portfolio-box-caption-content {
position: absolute;
top: 50%;
width: 100%;
transform: translateY(-50%);
text-align: center;
z-index:-10;
}
.portfolio-box .portfolio-box-caption .portfolio-box-caption-content .project-category,
.portfolio-box .portfolio-box-caption .portfolio-box-caption-content .project-name {
padding: 0 15px;
font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif;
}
.portfolio-box .portfolio-box-caption .portfolio-box-caption-content .project-category {
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
}
.portfolio-box .portfolio-box-caption .portfolio-box-caption-content .project-name {
font-size: 18px;
}
.portfolio-box:hover .portfolio-box-caption {
opacity: 1;
}
.portfolio-box:focus {
outline: none;
z-index: 0;
}
@media (min-width: 768px) {
.portfolio-box .portfolio-box-caption .portfolio-box-caption-content .project-category {
font-size: 16px;
}
.portfolio-box .portfolio-box-caption .portfolio-box-caption-content .project-name {
font-size: 22px;
}
}
.text-primary {
color: #F05F40 !important;
}
.btn {
font-weight: 700;
text-transform: uppercase;
border: none;
border-radius: 300px;
font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif;
z-index: 100;
}
.btn-xl {
padding: 1rem 2rem;
}
.btn-primary {
background-color: #F05F40;
border-color: #F05F40;
}
.btn-primary:hover, .btn-primary:focus, .btn-primary:active {
color: #fff;
background-color: #ee4b28 !important;
}
.btn-primary:active, .btn-primary:focus {
box-shadow: 0 0 0 0.2rem rgba(240, 95, 64, 0.5) !important;
}
section.bg-dark p {
padding-bottom: 3rem;
}
section.bg-dark {
padding-top: 3rem;
padding-bottom: 3rem;
}

View File

@@ -0,0 +1,73 @@
@font-face {
font-family: 'Merryweather';
src:url("../fontes/Merryweather/Merryweather-Regular.woff");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Merryweather';
src:url("../fontes/Merryweather/Merryweather-Bold.woff");
font-weight: bold;
font-style: bold;
}
@font-face {
font-family: 'Open Sans';
src:url("../fontes/OpenSans/open-sans-v15-latin-regular.woff2");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Open Sans';
src:url("../fontes/OpenSans/open-sans-v15-latin-italic.woff2");
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: 'Open Sans';
src:url("../fontes/OpenSans/open-sans-v15-latin-300.woff2");
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: 'Open Sans';
src:url("../fontes/OpenSans/open-sans-v15-latin-300italic.woff2");
font-weight: 300;
font-style: italic;
}
@font-face {
font-family: 'Open Sans';
src:url("../fontes/OpenSans/open-sans-v15-latin-600.woff2");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'Open Sans';
src:url("../fontes/OpenSans/open-sans-v15-latin-600italic.woff2");
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: 'Open Sans';
src:url("../fontes/OpenSans/open-sans-v15-latin-700.woff2");
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Open Sans';
src:url("../fontes/OpenSans/open-sans-v15-latin-700italic.woff2");
font-weight: 700;
font-style: italic;
}

View File

@@ -0,0 +1,31 @@
/*Import de fontes trouver sur font2u.com Bon site !!!! */
@font-face {
font-family: 'Lora';
src:url("../fonts/Lora-Regular.woff");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Raleway';
src:url("../fonts/Raleway-Regular.woff");
font-weight: normal;
}
@font-face {
font-family: 'Raleway';
src:url("../fonts/Raleway-Bold.woff");
font-weight: bold;
}
@font-face {
font-family:'Lora-Italic';
src:url("../fonts/Lora-Italic.woff");
}
@font-face {
font-family:'Roboto';
src:url("../fonts/Roboto-Regular.woff");
}

View File

@@ -0,0 +1,3 @@
/* PrismJS 1.30.0
https://prismjs.com/download#themes=prism-okaidia&languages=markup+css+clike+javascript+bash+c+cpp+markdown+markup-templating+nginx+php+python */
code[class*=language-],pre[data-language*=language-]{color:#f8f8f2;background:0 0;text-shadow:0 1px rgba(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}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#272822}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8292a2}.token.punctuation{color:#f8f8f2}.token.namespace{opacity:.7}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#f92672}.token.boolean,.token.number{color:#ae81ff}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#a6e22e}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#e6db74}.token.keyword{color:#66d9ef}.token.important,.token.regex{color:#fd971f}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}

View File

@@ -0,0 +1,106 @@
(function($) {
"use strict"; // Start of use strict
// Smooth scrolling using jQuery easing
$('a.js-scroll-trigger[href*="#"]:not([href="#"])').click(function() {
if (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') && location.hostname == this.hostname) {
var target = $(this.hash);
target = target.length ? target : $('[name=' + this.hash.slice(1) + ']');
if (target.length) {
$('html, body').animate({
scrollTop: (target.offset().top - 57)
}, 1000, "easeInOutExpo");
return false;
}
}
});
// Closes responsive menu when a scroll trigger link is clicked
$('.js-scroll-trigger').click(function() {
$('.navbar-collapse').collapse('hide');
});
// Activate scrollspy to add active class to navbar items on scroll
$('body').scrollspy({
target: '#mainNav',
offset: 57
});
// Collapse Navbar
var navbarCollapse = function() {
if ($("#mainNav").offset().top > 100) {
$("#mainNav").addClass("navbar-shrink");
} else {
$("#mainNav").removeClass("navbar-shrink");
}
};
// Collapse now if page is not at top
navbarCollapse();
// Collapse the navbar when page is scrolled
$(window).scroll(navbarCollapse);
// Scroll reveal calls
window.sr = ScrollReveal();
sr.reveal('.sr-icons', {
duration: 600,
scale: 0.3,
distance: '0px'
}, 200);
sr.reveal('.sr-button', {
duration: 1000,
delay: 200
});
sr.reveal('.sr-contact', {
duration: 600,
scale: 0.3,
distance: '0px'
}, 300);
// Magnific popup calls
$('.popup-gallery').magnificPopup({
delegate: 'a',
type: 'image',
tLoading: 'Loading image #%curr%...',
mainClass: 'mfp-img-mobile',
gallery: {
enabled: true,
navigateByImgClick: true,
preload: [0, 1]
},
image: {
tError: '<a href="%url%">The image #%curr%</a> could not be loaded.'
}
});
$(".obfuscate").each(function () {
$(this).html($(this).html()
.replace("__chez__", "@").replace(/\.\.\./g, ".")
.replace(/Un/g, "1")
.replace(/Deux/g, "2")
.replace(/Trois/g, "3")
.replace(/Quatre/g, "4")
.replace(/Cinq/g, "5")
.replace(/Six/g, "6")
.replace(/Sept/g, "7")
.replace(/Huit/g, "8")
.replace(/Neuf/g, "9")
.replace(/Zero/g, "0"))
$(this).attr("href", $(this).attr("href")
.replace("__chez__", "@").replace(/\.\.\./g, ".")
.replace(/Un/g, "1")
.replace(/Deux/g, "2")
.replace(/Trois/g, "3")
.replace(/Quatre/g, "4")
.replace(/Cinq/g, "5")
.replace(/Six/g, "6")
.replace(/Sept/g, "7")
.replace(/Huit/g, "8")
.replace(/Neuf/g, "9")
.replace(/Zero/g, "0"))
});
})(jQuery); // End of use strict

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,331 @@
/*!
* Bootstrap Reboot v4.1.3 (https://getbootstrap.com/)
* Copyright 2011-2018 The Bootstrap Authors
* Copyright 2011-2018 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
-ms-overflow-style: scrollbar;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
@-ms-viewport {
width: device-width;
}
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
[tabindex="-1"]:focus {
outline: 0 !important;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 0.5rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-original-title] {
text-decoration: underline;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
border-bottom: 0;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: .5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
dfn {
font-style: italic;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -.25em;
}
sup {
top: -.5em;
}
a {
color: #007bff;
text-decoration: none;
background-color: transparent;
-webkit-text-decoration-skip: objects;
}
a:hover {
color: #0056b3;
text-decoration: underline;
}
a:not([href]):not([tabindex]) {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):focus {
outline: 0;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
}
pre {
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
-ms-overflow-style: scrollbar;
}
figure {
margin: 0 0 1rem;
}
img {
vertical-align: middle;
border-style: none;
}
svg {
overflow: hidden;
vertical-align: middle;
}
table {
border-collapse: collapse;
}
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
color: #6c757d;
text-align: left;
caption-side: bottom;
}
th {
text-align: inherit;
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
button {
border-radius: 0;
}
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
button,
html [type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
-webkit-appearance: listbox;
}
textarea {
overflow: auto;
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
display: block;
width: 100%;
max-width: 100%;
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
color: inherit;
white-space: normal;
}
progress {
vertical-align: baseline;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
outline-offset: -2px;
-webkit-appearance: none;
}
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
summary {
display: list-item;
cursor: pointer;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
/*!
* Bootstrap Reboot v4.1.3 (https://getbootstrap.com/)
* Copyright 2011-2018 The Bootstrap Authors
* Copyright 2011-2018 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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