Add home’s cover carousel

This is a separate carousel from the one displayed at the bottom with
location info; it is, i suppose, a carousel for the hero image.

For the database, it works exactly as the home carousel, but on the
front had to use AlpineJS instead of Slick because it needs to show a
text popping up from the bottom when the slide is show, something i do
not know how to do in Slick.

It now makes no sense to have the carousel inside the “nature” section,
because the heading is no longer in there, and moved it out into a new
“hero” div.

Since i now have two carousels in home, i had to add additional
attributes to carousel.AdminHandler to know which URL to point to when
POSTing, PUTting, or redirecting.
This commit is contained in:
jordi fita mas 2024-01-16 21:05:52 +01:00
parent 036bed2fa6
commit b1e3f5017f
38 changed files with 1276 additions and 217 deletions

View File

@ -74,6 +74,22 @@ select add_media(52, 'wooden_lodges_carouselA.avif', 'image/avif', decode('m4_es
select add_media(52, 'wooden_lodges_carouselB.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carouselB.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carouselC.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carouselC.avif]])', 'base64'));
select add_cover_carousel_slide(62, 'Qualitat Calma Natura');
select add_cover_carousel_slide(63, 'El plaer dacampar en plena natura…');
select add_cover_carousel_slide(64, '…amb serveis de 1r. classe');
select order_cover_carousel('{62, 63, 64}');
select translate_cover_carousel_slide(62, 'en', 'Quality Calm Nature');
select translate_cover_carousel_slide(63, 'en', 'The pleasure to camp in the middle of nature…');
select translate_cover_carousel_slide(64, 'en', '…with 1st. class services');
select translate_cover_carousel_slide(62, 'es', 'Calidad Calma Naturaleza');
select translate_cover_carousel_slide(63, 'es', 'El placer de acampar en medio de la naturaleza…');
select translate_cover_carousel_slide(64, 'es', '…con servicios de 1ª categoría.');
select translate_cover_carousel_slide(62, 'fr', 'Qualité Calme Nature');
select translate_cover_carousel_slide(63, 'fr', 'Le plaisir de camper en pleine nature ');
select translate_cover_carousel_slide(64, 'fr', ' services de 1ère. ordre');
select add_home_carousel_slide(67, 'Volcà de Santa Margarida');
select add_home_carousel_slide(68, 'Gorga fosca Sadernes');
select add_home_carousel_slide(69, 'Castellfollit de la Roca');

View File

@ -0,0 +1,25 @@
-- Deploy camper:add_cover_carousel_slide to pg
-- requires: roles
-- requires: schema_camper
-- requires: cover_carousel
begin;
set search_path to camper, public;
create or replace function add_cover_carousel_slide(media_id integer, caption text) returns integer as
$$
insert into cover_carousel (media_id, caption)
values (media_id, coalesce(caption, ''))
on conflict (media_id) do update
set caption = excluded.caption
returning media_id
;
$$
language sql
;
revoke execute on function add_cover_carousel_slide(integer, text) from public;
grant execute on function add_cover_carousel_slide(integer, text) to admin;
commit;

54
deploy/cover_carousel.sql Normal file
View File

@ -0,0 +1,54 @@
-- Deploy camper:cover_carousel to pg
-- requires: roles
-- requires: schema_camper
-- requires: company
-- requires: media
-- requires: user_profile
begin;
set search_path to camper, public;
create table cover_carousel (
media_id integer not null references media primary key,
caption text not null,
position integer not null default 2147483647
);
grant select on table cover_carousel to guest;
grant select on table cover_carousel to employee;
grant select, insert, update, delete on table cover_carousel to admin;
alter table cover_carousel enable row level security;
create policy guest_ok
on cover_carousel
for select
using (true)
;
create policy insert_to_company
on cover_carousel
for insert
with check (
exists (select 1 from media join user_profile using (company_id) where media.media_id = cover_carousel.media_id)
)
;
create policy update_company
on cover_carousel
for update
using (
exists (select 1 from media join user_profile using (company_id) where media.media_id = cover_carousel.media_id)
)
;
create policy delete_from_company
on cover_carousel
for delete
using (
exists (select 1 from media join user_profile using (company_id) where media.media_id = cover_carousel.media_id)
)
;
commit;

View File

@ -0,0 +1,22 @@
-- Deploy camper:cover_carousel_i18n to pg
-- requires: roles
-- requires: schema_camper
-- requires: cover_carousel
-- requires: language
begin;
set search_path to camper, public;
create table cover_carousel_i18n (
media_id integer not null references cover_carousel,
lang_tag text not null references language,
caption text not null,
primary key (media_id, lang_tag)
);
grant select on table cover_carousel_i18n to guest;
grant select on table cover_carousel_i18n to employee;
grant select, insert, update, delete on table cover_carousel_i18n to admin;
commit;

View File

@ -0,0 +1,24 @@
-- Deploy camper:order_cover_carousel to pg
-- requires: schema_camper
-- requires: roles
-- requires: cover_carousel
begin;
set search_path to camper, public;
create or replace function order_cover_carousel(positions integer[]) returns void as
$$
update cover_carousel
set position = cast(temp.position as integer)
from unnest(positions) with ordinality as temp(media_id, position)
where cover_carousel.media_id = temp.media_id
;
$$
language sql
;
revoke execute on function order_cover_carousel(integer[]) from public;
grant execute on function order_cover_carousel(integer[]) to admin;
commit;

View File

@ -0,0 +1,22 @@
-- Deploy camper:remove_cover_carousel_slide to pg
-- requires: roles
-- requires: schema_camper
-- requires: cover_carousel
-- requires: cover_carousel_i18n
begin;
set search_path to camper, public;
create or replace function remove_cover_carousel_slide (media_id integer) returns void as
$$
delete from cover_carousel_i18n where media_id = $1;
delete from cover_carousel where media_id = $1;
$$
language sql
;
revoke execute on function remove_cover_carousel_slide (integer) from public;
grant execute on function remove_cover_carousel_slide (integer) to admin;
commit;

View File

@ -0,0 +1,23 @@
-- Deploy camper:translate_cover_carousel_slide to pg
-- requires: roles
-- requires: schema_camper
-- requires: cover_carousel_i18n
begin;
set search_path to camper, public;
create or replace function translate_cover_carousel_slide (media_id integer, lang_tag text, caption text) returns void as
$$
insert into cover_carousel_i18n (media_id, lang_tag, caption)
values (media_id, lang_tag, coalesce(caption, ''))
on conflict (media_id, lang_tag) do update
set caption = excluded.caption
$$
language sql
;
revoke execute on function translate_cover_carousel_slide(integer, text, text) from public;
grant execute on function translate_cover_carousel_slide(integer, text, text) to admin;
commit;

View File

@ -22,6 +22,8 @@ import (
)
type AdminHandler struct {
IndexURL string
SlidesURL string
name string
indexHandler IndexHandler
}
@ -30,6 +32,8 @@ type IndexHandler func(w http.ResponseWriter, r *http.Request, user *auth.User,
func NewAdminHandler(name string, indexHandler IndexHandler) *AdminHandler {
return &AdminHandler{
IndexURL: "/admin/" + name,
SlidesURL: "/admin/" + name + "/slides",
name: name,
indexHandler: indexHandler,
}
@ -44,14 +48,14 @@ func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *dat
case "":
switch r.Method {
case http.MethodPost:
addSlide(w, r, user, company, conn, h.name)
addSlide(w, r, user, company, conn, h.name, h.IndexURL, h.SlidesURL)
default:
httplib.MethodNotAllowed(w, r, http.MethodPost)
}
case "new":
switch r.Method {
case http.MethodGet:
f := newSlideForm(h.name, company)
f := newSlideForm(h.name, h.IndexURL, h.SlidesURL, company)
f.MustRender(w, r, user, company)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet)
@ -69,7 +73,7 @@ func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *dat
http.NotFound(w, r)
return
}
f := newSlideForm(h.name, company)
f := newSlideForm(h.name, h.IndexURL, h.SlidesURL, company)
if err := f.FillFromDatabase(r.Context(), conn, id); err != nil {
if database.ErrorIsNotFound(err) {
http.NotFound(w, r)
@ -194,8 +198,8 @@ func CollectSlideEntries(ctx context.Context, company *auth.Company, conn *datab
return slides, nil
}
func addSlide(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, carouselName string) {
f := newSlideForm(carouselName, company)
func addSlide(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, carouselName string, indexURL, baseURL string) {
f := newSlideForm(carouselName, indexURL, baseURL, company)
f.process(w, r, user, company, conn, func(ctx context.Context, tx *database.Tx) error {
var err error
if f.ID, err = tx.GetInt(ctx, fmt.Sprintf("select add_%s_carousel_slide($1, $2)", f.CarouselName), f.Media, f.Caption[f.DefaultLang]); err != nil {
@ -245,21 +249,25 @@ func (h *AdminHandler) deleteSlide(w http.ResponseWriter, r *http.Request, user
return
}
conn.MustExec(r.Context(), fmt.Sprintf("select remove_%s_carousel_slide($1)", h.name), id)
httplib.Redirect(w, r, "/admin/"+h.name, http.StatusSeeOther)
httplib.Redirect(w, r, h.IndexURL, http.StatusSeeOther)
}
type slideForm struct {
DefaultLang string
CarouselName string
IndexURL string
SlidesURL string
ID int
Media *form.Media
Caption form.I18nInput
}
func newSlideForm(carouselName string, company *auth.Company) *slideForm {
func newSlideForm(carouselName string, indexURL string, baseURL string, company *auth.Company) *slideForm {
return &slideForm{
DefaultLang: company.DefaultLanguage.String(),
CarouselName: carouselName,
IndexURL: indexURL,
SlidesURL: baseURL,
Media: &form.Media{
Input: &form.Input{
Name: "media",
@ -320,7 +328,7 @@ func (f *slideForm) process(w http.ResponseWriter, r *http.Request, user *auth.U
}
panic(err)
}
httplib.Redirect(w, r, "/admin/"+f.CarouselName, http.StatusSeeOther)
httplib.Redirect(w, r, f.IndexURL, http.StatusSeeOther)
}
func (f *slideForm) Parse(r *http.Request) error {

View File

@ -15,16 +15,22 @@ import (
"dev.tandem.ws/tandem/camper/pkg/template"
)
const coverName = "cover"
const carouselName = "home"
type AdminHandler struct {
cover *carousel.AdminHandler
carousel *carousel.AdminHandler
}
func NewAdminHandler() *AdminHandler {
return &AdminHandler{
handler := &AdminHandler{
cover: carousel.NewAdminHandler(coverName, serveHomeIndex),
carousel: carousel.NewAdminHandler(carouselName, serveHomeIndex),
}
handler.cover.IndexURL = "/admin/home"
handler.cover.SlidesURL = "/admin/home/cover"
return handler
}
func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.Handler {
@ -40,6 +46,8 @@ func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *dat
default:
httplib.MethodNotAllowed(w, r, http.MethodGet)
}
case "cover":
h.cover.Handler(user, company, conn).ServeHTTP(w, r)
case "slides":
h.carousel.Handler(user, company, conn).ServeHTTP(w, r)
default:
@ -49,17 +57,23 @@ func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *dat
}
func serveHomeIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
cover, err := carousel.CollectSlideEntries(r.Context(), company, conn, coverName)
if err != nil {
panic(err)
}
slides, err := carousel.CollectSlideEntries(r.Context(), company, conn, carouselName)
if err != nil {
panic(err)
}
page := &homeIndex{
Cover: cover,
Slides: slides,
}
page.MustRender(w, r, user, company)
}
type homeIndex struct {
Cover []*carousel.SlideEntry
Slides []*carousel.SlideEntry
}

View File

@ -38,6 +38,7 @@ func (h *PublicHandler) Handler(user *auth.User, company *auth.Company, conn *da
type homePage struct {
*template.PublicPage
Cover []*carousel.Slide
CampsiteTypes []*campsiteType
Carousel []*carousel.Slide
}
@ -49,6 +50,7 @@ func newHomePage() *homePage {
func (p *homePage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
p.Setup(r, user, company, conn)
p.CampsiteTypes = mustCollectCampsiteTypes(r.Context(), company, conn, user.Locale)
p.Cover = carousel.MustCollectSlides(r.Context(), company, conn, user.Locale, coverName)
p.Carousel = carousel.MustCollectSlides(r.Context(), company, conn, user.Locale, carouselName)
template.MustRenderPublic(w, r, user, company, "home.gohtml", p)
}

134
po/ca.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2024-01-16 00:48+0100\n"
"POT-Creation-Date: 2024-01-16 20:32+0100\n"
"PO-Revision-Date: 2023-07-22 23:45+0200\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n"
@ -41,71 +41,71 @@ msgctxt "title"
msgid "Payment Failed"
msgstr "Ha fallat el pagament"
#: web/templates/public/services.gohtml:6
#: web/templates/public/services.gohtml:15
#: web/templates/public/layout.gohtml:66 web/templates/public/layout.gohtml:94
#: web/templates/public/services.gohtml:7
#: web/templates/public/services.gohtml:16
#: web/templates/public/layout.gohtml:67 web/templates/public/layout.gohtml:95
#: web/templates/admin/services/index.gohtml:56
msgctxt "title"
msgid "Services"
msgstr "Serveis"
#: web/templates/public/services.gohtml:18
#: web/templates/public/services.gohtml:19
msgid "The campsite offers many different services."
msgstr "El càmping disposa de diversos serveis."
#: web/templates/public/location.gohtml:6
#: web/templates/public/location.gohtml:12
#: web/templates/public/layout.gohtml:68 web/templates/public/layout.gohtml:96
#: web/templates/public/location.gohtml:7
#: web/templates/public/location.gohtml:13
#: web/templates/public/layout.gohtml:69 web/templates/public/layout.gohtml:97
#: web/templates/admin/layout.gohtml:61
msgctxt "title"
msgid "Location"
msgstr "Com arribar"
#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:52
#: web/templates/public/layout.gohtml:92
#: web/templates/public/home.gohtml:7 web/templates/public/layout.gohtml:53
#: web/templates/public/layout.gohtml:93
msgctxt "title"
msgid "Home"
msgstr "Inici"
#: web/templates/public/home.gohtml:17
msgid "The pleasure of camping in the middle of nature…"
msgstr "El plaer dacampar en plena natura…"
#: web/templates/public/home.gohtml:18
#: web/templates/public/home.gohtml:24
msgctxt "link"
msgid "Booking"
msgstr "Reserva"
#: web/templates/public/home.gohtml:31
msgid "The pleasure of camping in the middle of nature…"
msgstr "El plaer dacampar en plena natura…"
#: web/templates/public/home.gohtml:43
msgid "Our services"
msgstr "Els nostres serveis"
#: web/templates/public/home.gohtml:33
#: web/templates/public/home.gohtml:45
msgid "Come and enjoy!"
msgstr "Vine a gaudir!"
#: web/templates/public/home.gohtml:35
#: web/templates/public/surroundings.gohtml:6
#: web/templates/public/surroundings.gohtml:11
#: web/templates/public/layout.gohtml:67 web/templates/public/layout.gohtml:95
#: web/templates/public/home.gohtml:47
#: web/templates/public/surroundings.gohtml:7
#: web/templates/public/surroundings.gohtml:12
#: web/templates/public/layout.gohtml:68 web/templates/public/layout.gohtml:96
#: web/templates/admin/layout.gohtml:64
msgctxt "title"
msgid "Surroundings"
msgstr "Lentorn"
#: web/templates/public/home.gohtml:38
#: web/templates/public/home.gohtml:50
msgid "Located in <strong>Alta Garrotxa</strong>, between the <strong>Pyrenees</strong> and the <strong>Costa Brava</strong>."
msgstr "Situats a l<strong>Alta Garrotxa</strong>, entre els <strong>Pirineus</strong> i la <strong>Costa Brava</strong>."
#: web/templates/public/home.gohtml:39
#: web/templates/public/home.gohtml:51
msgid "Nearby there are the <strong>gorges of Sadernes</strong>, <strong>volcanoes</strong>, <strong>La Fageda den Jordà</strong>, the Jewish quarter of <strong>Besalú</strong>, the basaltic cliff of <strong>Castellfollit de la Roca</strong>… much to see and much to do."
msgstr "A prop teniu els <strong>gorgs de Sadernes</strong>, <strong>volcans</strong>, <strong>La Fageda den Jordà</strong>, el call jueu de <strong>Besalú</strong>, la cinglera basàltica de <strong>Castellfollit de la Roca</strong>… molt per veure i molt per fer."
#: web/templates/public/home.gohtml:40
#: web/templates/public/home.gohtml:52
msgid "Less than an hour from <strong>Girona</strong>, one from <strong>La Bisbal dEmpordà</strong>, and two from <strong>Barcelona</strong>."
msgstr "A menys duna hora de <strong>Girona</strong>, a una de <strong>La Bisbal dEmpordà</strong> i a dues de <strong>Barcelona</strong>."
#: web/templates/public/home.gohtml:41
#: web/templates/public/home.gohtml:53
msgid "Discover the surroundings"
msgstr "Descobreix lentorn"
@ -252,57 +252,57 @@ msgctxt "day"
msgid "Sun"
msgstr "dg"
#: web/templates/public/surroundings.gohtml:31
#: web/templates/public/surroundings.gohtml:32
msgctxt "title"
msgid "What to Do Outside the Campsite?"
msgstr "Què fer des del càmping?"
#: web/templates/public/surroundings.gohtml:51
#: web/templates/public/surroundings.gohtml:52
msgctxt "title"
msgid "Once at the Campsite, We Can Inform You about What Activities are Available"
msgstr "Un cop en el càmping, us podem informar de quines activitats fer"
#: web/templates/public/surroundings.gohtml:54
#: web/templates/public/surroundings.gohtml:55
msgid "Cycle routes"
msgstr "Rutes amb bicicleta"
#: web/templates/public/surroundings.gohtml:55
#: web/templates/public/surroundings.gohtml:56
msgid "There are many bicycle rental companies in Olot."
msgstr "A Olot podeu trobar empreses de lloguer de bicicletes."
#: web/templates/public/surroundings.gohtml:59
#: web/templates/public/surroundings.gohtml:60
msgid "Routes"
msgstr "Rutes"
#: web/templates/public/surroundings.gohtml:60
#: web/templates/public/surroundings.gohtml:61
msgid "Routes of all kinds, climbing, mountain passes, for all levels."
msgstr "Rutes de tota mena, escalada, ports de muntanya, per a tots els nivells."
#: web/templates/public/surroundings.gohtml:64
#: web/templates/public/surroundings.gohtml:65
msgid "Family outing"
msgstr "Excursions familiars"
#: web/templates/public/surroundings.gohtml:65
#: web/templates/public/surroundings.gohtml:66
msgid "Many outing possibilities, for all ages."
msgstr "Múltiples excursions per a totes les edats."
#: web/templates/public/surroundings.gohtml:69
#: web/templates/public/surroundings.gohtml:70
msgid "Kayak"
msgstr "Caiac"
#: web/templates/public/surroundings.gohtml:70
#: web/templates/public/surroundings.gohtml:71
msgid "There are several points where you can go by kayak, from sections of the Ter river as well as on the coast…."
msgstr "Hi ha diversos punts on poder anar amb caiac, des de trams del riu Ter com també a la costa…."
#: web/templates/public/campground.gohtml:6
#: web/templates/public/campground.gohtml:15
#: web/templates/public/layout.gohtml:53 web/templates/public/layout.gohtml:93
#: web/templates/public/campground.gohtml:7
#: web/templates/public/campground.gohtml:16
#: web/templates/public/layout.gohtml:54 web/templates/public/layout.gohtml:94
msgctxt "title"
msgid "Campground"
msgstr "El càmping"
#: web/templates/public/booking.gohtml:6 web/templates/public/booking.gohtml:11
#: web/templates/public/layout.gohtml:69
#: web/templates/public/layout.gohtml:70
msgctxt "title"
msgid "Booking"
msgstr "Reserva"
@ -390,20 +390,20 @@ msgctxt "input"
msgid "I have read and I accept <a href=\"%s\" target=\"_blank\">the reservation conditions</a>"
msgstr "He llegit i accepto <a href=\"%s\" target=\"_blank\">les condicions de reserves</a>"
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:47
#: web/templates/public/layout.gohtml:130
#: web/templates/public/layout.gohtml:12 web/templates/public/layout.gohtml:48
#: web/templates/public/layout.gohtml:131
msgid "Campsite Montagut"
msgstr "Càmping Montagut"
#: web/templates/public/layout.gohtml:23 web/templates/admin/layout.gohtml:21
#: web/templates/public/layout.gohtml:24 web/templates/admin/layout.gohtml:21
msgid "Skip to main content"
msgstr "Salta al contingut principal"
#: web/templates/public/layout.gohtml:49
#: web/templates/public/layout.gohtml:50
msgid "Menu"
msgstr "Menú"
#: web/templates/public/layout.gohtml:57 web/templates/public/layout.gohtml:103
#: web/templates/public/layout.gohtml:58 web/templates/public/layout.gohtml:104
#: web/templates/admin/campsite/index.gohtml:6
#: web/templates/admin/campsite/index.gohtml:12
#: web/templates/admin/layout.gohtml:46 web/templates/admin/layout.gohtml:86
@ -411,17 +411,17 @@ msgctxt "title"
msgid "Campsites"
msgstr "Allotjaments"
#: web/templates/public/layout.gohtml:90
#: web/templates/public/layout.gohtml:91
msgctxt "title"
msgid "Sections"
msgstr "Apartats"
#: web/templates/public/layout.gohtml:114
#: web/templates/public/layout.gohtml:115
msgctxt "title"
msgid "Opening"
msgstr "Obertura"
#: web/templates/public/layout.gohtml:121
#: web/templates/public/layout.gohtml:122
msgid "<abbr title=\"Catalonia Tourism Registry\">RTC</abbr> <abbr title=\"Number\">#</abbr>%s"
msgstr "<abbr title=\"Número\">Núm.</abbr> <abbr title=\"Registre de Turisme de Catalunya\">RTC</abbr> %s"
@ -612,7 +612,7 @@ msgstr "Carrusel del tipus dallotjament"
#: web/templates/admin/campsite/carousel/index.gohtml:12
#: web/templates/admin/services/index.gohtml:12
#: web/templates/admin/home/index.gohtml:12
#: web/templates/admin/home/index.gohtml:57
msgctxt "action"
msgid "Add slide"
msgstr "Afegeix diapositiva"
@ -621,6 +621,7 @@ msgstr "Afegeix diapositiva"
#: web/templates/admin/services/index.gohtml:25
#: web/templates/admin/surroundings/index.gohtml:25
#: web/templates/admin/home/index.gohtml:25
#: web/templates/admin/home/index.gohtml:70
msgctxt "header"
msgid "Image"
msgstr "Imatge"
@ -628,6 +629,7 @@ msgstr "Imatge"
#: web/templates/admin/campsite/carousel/index.gohtml:26
#: web/templates/admin/services/index.gohtml:26
#: web/templates/admin/home/index.gohtml:26
#: web/templates/admin/home/index.gohtml:71
msgctxt "header"
msgid "Caption"
msgstr "Llegenda"
@ -637,13 +639,14 @@ msgstr "Llegenda"
#: web/templates/admin/services/index.gohtml:72
#: web/templates/admin/surroundings/index.gohtml:27
#: web/templates/admin/home/index.gohtml:27
#: web/templates/admin/home/index.gohtml:72
msgctxt "header"
msgid "Actions"
msgstr "Accions"
#: web/templates/admin/campsite/carousel/index.gohtml:31
#: web/templates/admin/services/index.gohtml:31
#: web/templates/admin/home/index.gohtml:31
#: web/templates/admin/home/index.gohtml:76
msgid "Are you sure you wish to delete this slide?"
msgstr "Esteu segur de voler esborrar aquesta diapositiva?"
@ -652,13 +655,14 @@ msgstr "Esteu segur de voler esborrar aquesta diapositiva?"
#: web/templates/admin/services/index.gohtml:88
#: web/templates/admin/surroundings/index.gohtml:44
#: web/templates/admin/home/index.gohtml:44
#: web/templates/admin/home/index.gohtml:89
msgctxt "action"
msgid "Delete"
msgstr "Esborra"
#: web/templates/admin/campsite/carousel/index.gohtml:54
#: web/templates/admin/services/index.gohtml:53
#: web/templates/admin/home/index.gohtml:53
#: web/templates/admin/home/index.gohtml:98
msgid "No slides added yet."
msgstr "No sha afegit cap diapositiva encara."
@ -1000,7 +1004,7 @@ msgid "Services Page"
msgstr "Pàgina de serveis"
#: web/templates/admin/services/index.gohtml:11
#: web/templates/admin/home/index.gohtml:11
#: web/templates/admin/home/index.gohtml:56
msgctxt "title"
msgid "Carousel"
msgstr "Carrusel"
@ -1198,6 +1202,24 @@ msgctxt "action"
msgid "Logout"
msgstr "Surt"
#: web/templates/admin/home/index.gohtml:11
msgctxt "title"
msgid "Cover"
msgstr "Portada"
#: web/templates/admin/home/index.gohtml:12
msgctxt "action"
msgid "Add cover image"
msgstr "Afegeix imatge de portada"
#: web/templates/admin/home/index.gohtml:31
msgid "Are you sure you wish to delete this cover image?"
msgstr "Esteu segur de voler esborrar aquesta imatge de portada?"
#: web/templates/admin/home/index.gohtml:53
msgid "No images added to the cover yet."
msgstr "No sha afegit cap imatge de portada encara."
#: web/templates/admin/media/picker.gohtml:8
msgctxt "title"
msgid "Media Picker"
@ -1265,7 +1287,7 @@ msgstr "Pujada de mèdia"
#: pkg/legal/admin.go:258 pkg/app/user.go:249 pkg/campsite/types/option.go:344
#: pkg/campsite/types/feature.go:259 pkg/campsite/types/admin.go:463
#: pkg/season/admin.go:412 pkg/services/admin.go:314
#: pkg/season/admin.go:412 pkg/services/admin.go:316
#: pkg/surroundings/admin.go:321
msgid "Name can not be empty."
msgstr "No podeu deixar el nom en blanc."
@ -1275,22 +1297,22 @@ msgstr "No podeu deixar el nom en blanc."
msgid "Name must have at least one letter."
msgstr "El nom ha de tenir com a mínim una lletra."
#: pkg/carousel/admin.go:267 pkg/campsite/types/carousel.go:223
#: pkg/carousel/admin.go:275 pkg/campsite/types/carousel.go:223
msgctxt "input"
msgid "Slide image"
msgstr "Imatge de la diapositiva"
#: pkg/carousel/admin.go:268 pkg/campsite/types/carousel.go:224
#: pkg/carousel/admin.go:276 pkg/campsite/types/carousel.go:224
msgctxt "action"
msgid "Set slide image"
msgstr "Estableix la imatge de la diapositiva"
#: pkg/carousel/admin.go:337 pkg/campsite/types/carousel.go:297
#: pkg/carousel/admin.go:345 pkg/campsite/types/carousel.go:297
#: pkg/surroundings/admin.go:316
msgid "Slide image can not be empty."
msgstr "No podeu deixar la imatge de la diapositiva en blanc."
#: pkg/carousel/admin.go:338 pkg/campsite/types/carousel.go:298
#: pkg/carousel/admin.go:346 pkg/campsite/types/carousel.go:298
#: pkg/surroundings/admin.go:317
msgid "Slide image must be an image media type."
msgstr "La imatge de la diapositiva ha de ser un mèdia de tipus imatge."
@ -1370,7 +1392,7 @@ msgstr "El preu per nit ha de ser un número decimal."
msgid "Price per night must be zero or greater."
msgstr "El preu per nit ha de ser com a mínim zero."
#: pkg/campsite/types/feature.go:258 pkg/services/admin.go:313
#: pkg/campsite/types/feature.go:258 pkg/services/admin.go:315
msgid "Selected icon is not valid."
msgstr "La icona escollida no és vàlida."

134
po/es.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2024-01-16 00:48+0100\n"
"POT-Creation-Date: 2024-01-16 20:32+0100\n"
"PO-Revision-Date: 2023-07-22 23:46+0200\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n"
@ -41,71 +41,71 @@ msgctxt "title"
msgid "Payment Failed"
msgstr "Pago fallido"
#: web/templates/public/services.gohtml:6
#: web/templates/public/services.gohtml:15
#: web/templates/public/layout.gohtml:66 web/templates/public/layout.gohtml:94
#: web/templates/public/services.gohtml:7
#: web/templates/public/services.gohtml:16
#: web/templates/public/layout.gohtml:67 web/templates/public/layout.gohtml:95
#: web/templates/admin/services/index.gohtml:56
msgctxt "title"
msgid "Services"
msgstr "Servicios"
#: web/templates/public/services.gohtml:18
#: web/templates/public/services.gohtml:19
msgid "The campsite offers many different services."
msgstr "El camping dispone de varios servicios."
#: web/templates/public/location.gohtml:6
#: web/templates/public/location.gohtml:12
#: web/templates/public/layout.gohtml:68 web/templates/public/layout.gohtml:96
#: web/templates/public/location.gohtml:7
#: web/templates/public/location.gohtml:13
#: web/templates/public/layout.gohtml:69 web/templates/public/layout.gohtml:97
#: web/templates/admin/layout.gohtml:61
msgctxt "title"
msgid "Location"
msgstr "Cómo llegar"
#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:52
#: web/templates/public/layout.gohtml:92
#: web/templates/public/home.gohtml:7 web/templates/public/layout.gohtml:53
#: web/templates/public/layout.gohtml:93
msgctxt "title"
msgid "Home"
msgstr "Inicio"
#: web/templates/public/home.gohtml:17
msgid "The pleasure of camping in the middle of nature…"
msgstr "El placer de acampar en plena naturaleza…"
#: web/templates/public/home.gohtml:18
#: web/templates/public/home.gohtml:24
msgctxt "link"
msgid "Booking"
msgstr "Reservar"
#: web/templates/public/home.gohtml:31
msgid "The pleasure of camping in the middle of nature…"
msgstr "El placer de acampar en plena naturaleza…"
#: web/templates/public/home.gohtml:43
msgid "Our services"
msgstr "Nuestros servicios"
#: web/templates/public/home.gohtml:33
#: web/templates/public/home.gohtml:45
msgid "Come and enjoy!"
msgstr "¡Ven a disfrutar!"
#: web/templates/public/home.gohtml:35
#: web/templates/public/surroundings.gohtml:6
#: web/templates/public/surroundings.gohtml:11
#: web/templates/public/layout.gohtml:67 web/templates/public/layout.gohtml:95
#: web/templates/public/home.gohtml:47
#: web/templates/public/surroundings.gohtml:7
#: web/templates/public/surroundings.gohtml:12
#: web/templates/public/layout.gohtml:68 web/templates/public/layout.gohtml:96
#: web/templates/admin/layout.gohtml:64
msgctxt "title"
msgid "Surroundings"
msgstr "El entorno"
#: web/templates/public/home.gohtml:38
#: web/templates/public/home.gohtml:50
msgid "Located in <strong>Alta Garrotxa</strong>, between the <strong>Pyrenees</strong> and the <strong>Costa Brava</strong>."
msgstr "Situados en la <strong>Alta Garrotxa</strong>, entre los <strong>Pirineos</strong> y la <strong>Costa Brava</strong>."
#: web/templates/public/home.gohtml:39
#: web/templates/public/home.gohtml:51
msgid "Nearby there are the <strong>gorges of Sadernes</strong>, <strong>volcanoes</strong>, <strong>La Fageda den Jordà</strong>, the Jewish quarter of <strong>Besalú</strong>, the basaltic cliff of <strong>Castellfollit de la Roca</strong>… much to see and much to do."
msgstr "Cerca tenéis los <strong>piletones de Sadernes</strong>, <strong>volcanes</strong>, <strong>La Fageda den Jordà</strong>, la judería de <strong>Besalú</strong>, el riscal basáltico de <strong>Castellfollit de la Roca</strong>… mucho por ver y mucho por hacer."
#: web/templates/public/home.gohtml:40
#: web/templates/public/home.gohtml:52
msgid "Less than an hour from <strong>Girona</strong>, one from <strong>La Bisbal dEmpordà</strong>, and two from <strong>Barcelona</strong>."
msgstr "A menos de una hora de <strong>Girona</strong>, a una de <strong>La Bisbal dEmpordà</strong> y a dos de <strong>Barcelona</strong>."
#: web/templates/public/home.gohtml:41
#: web/templates/public/home.gohtml:53
msgid "Discover the surroundings"
msgstr "Descubre el entorno"
@ -252,57 +252,57 @@ msgctxt "day"
msgid "Sun"
msgstr "do"
#: web/templates/public/surroundings.gohtml:31
#: web/templates/public/surroundings.gohtml:32
msgctxt "title"
msgid "What to Do Outside the Campsite?"
msgstr "¿Qué hacer desde el camping?"
#: web/templates/public/surroundings.gohtml:51
#: web/templates/public/surroundings.gohtml:52
msgctxt "title"
msgid "Once at the Campsite, We Can Inform You about What Activities are Available"
msgstr "Una vez en el camping, os podemos informar de qué actividades hacer"
#: web/templates/public/surroundings.gohtml:54
#: web/templates/public/surroundings.gohtml:55
msgid "Cycle routes"
msgstr "Rutas en bicicleta"
#: web/templates/public/surroundings.gohtml:55
#: web/templates/public/surroundings.gohtml:56
msgid "There are many bicycle rental companies in Olot."
msgstr "A Olot podéis encontrar empresas de alquiler de bicicletas."
#: web/templates/public/surroundings.gohtml:59
#: web/templates/public/surroundings.gohtml:60
msgid "Routes"
msgstr "Rutas"
#: web/templates/public/surroundings.gohtml:60
#: web/templates/public/surroundings.gohtml:61
msgid "Routes of all kinds, climbing, mountain passes, for all levels."
msgstr "Rutas de todo tipo, escalada, puertos de montaña, para todos los niveles."
#: web/templates/public/surroundings.gohtml:64
#: web/templates/public/surroundings.gohtml:65
msgid "Family outing"
msgstr "Excusiones familiares"
#: web/templates/public/surroundings.gohtml:65
#: web/templates/public/surroundings.gohtml:66
msgid "Many outing possibilities, for all ages."
msgstr "Múltiples excursiones para todas las edades."
#: web/templates/public/surroundings.gohtml:69
#: web/templates/public/surroundings.gohtml:70
msgid "Kayak"
msgstr "Kayak"
#: web/templates/public/surroundings.gohtml:70
#: web/templates/public/surroundings.gohtml:71
msgid "There are several points where you can go by kayak, from sections of the Ter river as well as on the coast…."
msgstr "Hay diversos puntos dónde podéis ir en kayak, desde tramos del río Ter como también en la costa…."
#: web/templates/public/campground.gohtml:6
#: web/templates/public/campground.gohtml:15
#: web/templates/public/layout.gohtml:53 web/templates/public/layout.gohtml:93
#: web/templates/public/campground.gohtml:7
#: web/templates/public/campground.gohtml:16
#: web/templates/public/layout.gohtml:54 web/templates/public/layout.gohtml:94
msgctxt "title"
msgid "Campground"
msgstr "El camping"
#: web/templates/public/booking.gohtml:6 web/templates/public/booking.gohtml:11
#: web/templates/public/layout.gohtml:69
#: web/templates/public/layout.gohtml:70
msgctxt "title"
msgid "Booking"
msgstr "Reserva"
@ -390,20 +390,20 @@ msgctxt "input"
msgid "I have read and I accept <a href=\"%s\" target=\"_blank\">the reservation conditions</a>"
msgstr "He leído y acepto <a href=\"%s\" target=\"_blank\">las condiciones de reserva</a>"
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:47
#: web/templates/public/layout.gohtml:130
#: web/templates/public/layout.gohtml:12 web/templates/public/layout.gohtml:48
#: web/templates/public/layout.gohtml:131
msgid "Campsite Montagut"
msgstr "Camping Montagut"
#: web/templates/public/layout.gohtml:23 web/templates/admin/layout.gohtml:21
#: web/templates/public/layout.gohtml:24 web/templates/admin/layout.gohtml:21
msgid "Skip to main content"
msgstr "Saltar al contenido principal"
#: web/templates/public/layout.gohtml:49
#: web/templates/public/layout.gohtml:50
msgid "Menu"
msgstr "Menú"
#: web/templates/public/layout.gohtml:57 web/templates/public/layout.gohtml:103
#: web/templates/public/layout.gohtml:58 web/templates/public/layout.gohtml:104
#: web/templates/admin/campsite/index.gohtml:6
#: web/templates/admin/campsite/index.gohtml:12
#: web/templates/admin/layout.gohtml:46 web/templates/admin/layout.gohtml:86
@ -411,17 +411,17 @@ msgctxt "title"
msgid "Campsites"
msgstr "Alojamientos"
#: web/templates/public/layout.gohtml:90
#: web/templates/public/layout.gohtml:91
msgctxt "title"
msgid "Sections"
msgstr "Apartados"
#: web/templates/public/layout.gohtml:114
#: web/templates/public/layout.gohtml:115
msgctxt "title"
msgid "Opening"
msgstr "Apertura"
#: web/templates/public/layout.gohtml:121
#: web/templates/public/layout.gohtml:122
msgid "<abbr title=\"Catalonia Tourism Registry\">RTC</abbr> <abbr title=\"Number\">#</abbr>%s"
msgstr "<abbr title=\"Número\">Nº</abbr> <abbr title=\"Registro de Turismo de Cataluña\">RTC</abbr> %s"
@ -612,7 +612,7 @@ msgstr "Carrusel del tipo de alojamiento"
#: web/templates/admin/campsite/carousel/index.gohtml:12
#: web/templates/admin/services/index.gohtml:12
#: web/templates/admin/home/index.gohtml:12
#: web/templates/admin/home/index.gohtml:57
msgctxt "action"
msgid "Add slide"
msgstr "Añadir diapositiva"
@ -621,6 +621,7 @@ msgstr "Añadir diapositiva"
#: web/templates/admin/services/index.gohtml:25
#: web/templates/admin/surroundings/index.gohtml:25
#: web/templates/admin/home/index.gohtml:25
#: web/templates/admin/home/index.gohtml:70
msgctxt "header"
msgid "Image"
msgstr "Imagen"
@ -628,6 +629,7 @@ msgstr "Imagen"
#: web/templates/admin/campsite/carousel/index.gohtml:26
#: web/templates/admin/services/index.gohtml:26
#: web/templates/admin/home/index.gohtml:26
#: web/templates/admin/home/index.gohtml:71
msgctxt "header"
msgid "Caption"
msgstr "Leyenda"
@ -637,13 +639,14 @@ msgstr "Leyenda"
#: web/templates/admin/services/index.gohtml:72
#: web/templates/admin/surroundings/index.gohtml:27
#: web/templates/admin/home/index.gohtml:27
#: web/templates/admin/home/index.gohtml:72
msgctxt "header"
msgid "Actions"
msgstr "Acciones"
#: web/templates/admin/campsite/carousel/index.gohtml:31
#: web/templates/admin/services/index.gohtml:31
#: web/templates/admin/home/index.gohtml:31
#: web/templates/admin/home/index.gohtml:76
msgid "Are you sure you wish to delete this slide?"
msgstr "¿Estáis seguro de querer borrar esta diapositiva?"
@ -652,13 +655,14 @@ msgstr "¿Estáis seguro de querer borrar esta diapositiva?"
#: web/templates/admin/services/index.gohtml:88
#: web/templates/admin/surroundings/index.gohtml:44
#: web/templates/admin/home/index.gohtml:44
#: web/templates/admin/home/index.gohtml:89
msgctxt "action"
msgid "Delete"
msgstr "Borrar"
#: web/templates/admin/campsite/carousel/index.gohtml:54
#: web/templates/admin/services/index.gohtml:53
#: web/templates/admin/home/index.gohtml:53
#: web/templates/admin/home/index.gohtml:98
msgid "No slides added yet."
msgstr "No se ha añadido ninguna diapositiva todavía."
@ -1000,7 +1004,7 @@ msgid "Services Page"
msgstr "Página de servicios"
#: web/templates/admin/services/index.gohtml:11
#: web/templates/admin/home/index.gohtml:11
#: web/templates/admin/home/index.gohtml:56
msgctxt "title"
msgid "Carousel"
msgstr "Carrusel"
@ -1198,6 +1202,24 @@ msgctxt "action"
msgid "Logout"
msgstr "Salir"
#: web/templates/admin/home/index.gohtml:11
msgctxt "title"
msgid "Cover"
msgstr "Portada"
#: web/templates/admin/home/index.gohtml:12
msgctxt "action"
msgid "Add cover image"
msgstr "Añadir imagen de portada"
#: web/templates/admin/home/index.gohtml:31
msgid "Are you sure you wish to delete this cover image?"
msgstr "¿Estáis seguro de querer borrar esta imagen de portada?"
#: web/templates/admin/home/index.gohtml:53
msgid "No images added to the cover yet."
msgstr "No se ha añadido ninguna imagen de portada todavía."
#: web/templates/admin/media/picker.gohtml:8
msgctxt "title"
msgid "Media Picker"
@ -1265,7 +1287,7 @@ msgstr "Subida de medio"
#: pkg/legal/admin.go:258 pkg/app/user.go:249 pkg/campsite/types/option.go:344
#: pkg/campsite/types/feature.go:259 pkg/campsite/types/admin.go:463
#: pkg/season/admin.go:412 pkg/services/admin.go:314
#: pkg/season/admin.go:412 pkg/services/admin.go:316
#: pkg/surroundings/admin.go:321
msgid "Name can not be empty."
msgstr "No podéis dejar el nombre en blanco."
@ -1275,22 +1297,22 @@ msgstr "No podéis dejar el nombre en blanco."
msgid "Name must have at least one letter."
msgstr "El nombre tiene que tener como mínimo una letra."
#: pkg/carousel/admin.go:267 pkg/campsite/types/carousel.go:223
#: pkg/carousel/admin.go:275 pkg/campsite/types/carousel.go:223
msgctxt "input"
msgid "Slide image"
msgstr "Imagen de la diapositiva"
#: pkg/carousel/admin.go:268 pkg/campsite/types/carousel.go:224
#: pkg/carousel/admin.go:276 pkg/campsite/types/carousel.go:224
msgctxt "action"
msgid "Set slide image"
msgstr "Establecer la imagen de la diapositiva"
#: pkg/carousel/admin.go:337 pkg/campsite/types/carousel.go:297
#: pkg/carousel/admin.go:345 pkg/campsite/types/carousel.go:297
#: pkg/surroundings/admin.go:316
msgid "Slide image can not be empty."
msgstr "No podéis dejar la imagen de la diapositiva en blanco."
#: pkg/carousel/admin.go:338 pkg/campsite/types/carousel.go:298
#: pkg/carousel/admin.go:346 pkg/campsite/types/carousel.go:298
#: pkg/surroundings/admin.go:317
msgid "Slide image must be an image media type."
msgstr "La imagen de la diapositiva tiene que ser un medio de tipo imagen."
@ -1370,7 +1392,7 @@ msgstr "El precio por noche tiene que ser un número decimal."
msgid "Price per night must be zero or greater."
msgstr "El precio por noche tiene que ser como mínimo cero."
#: pkg/campsite/types/feature.go:258 pkg/services/admin.go:313
#: pkg/campsite/types/feature.go:258 pkg/services/admin.go:315
msgid "Selected icon is not valid."
msgstr "El icono escogido no es válido."

134
po/fr.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2024-01-16 00:48+0100\n"
"POT-Creation-Date: 2024-01-16 20:32+0100\n"
"PO-Revision-Date: 2023-12-20 10:13+0100\n"
"Last-Translator: Oriol Carbonell <info@oriolcarbonell.cat>\n"
"Language-Team: French <traduc@traduc.org>\n"
@ -42,71 +42,71 @@ msgctxt "title"
msgid "Payment Failed"
msgstr "Le paiement a échoué"
#: web/templates/public/services.gohtml:6
#: web/templates/public/services.gohtml:15
#: web/templates/public/layout.gohtml:66 web/templates/public/layout.gohtml:94
#: web/templates/public/services.gohtml:7
#: web/templates/public/services.gohtml:16
#: web/templates/public/layout.gohtml:67 web/templates/public/layout.gohtml:95
#: web/templates/admin/services/index.gohtml:56
msgctxt "title"
msgid "Services"
msgstr "Services"
#: web/templates/public/services.gohtml:18
#: web/templates/public/services.gohtml:19
msgid "The campsite offers many different services."
msgstr "Le camping propose de nombreux services différents."
#: web/templates/public/location.gohtml:6
#: web/templates/public/location.gohtml:12
#: web/templates/public/layout.gohtml:68 web/templates/public/layout.gohtml:96
#: web/templates/public/location.gohtml:7
#: web/templates/public/location.gohtml:13
#: web/templates/public/layout.gohtml:69 web/templates/public/layout.gohtml:97
#: web/templates/admin/layout.gohtml:61
msgctxt "title"
msgid "Location"
msgstr "Comment nous rejoindre"
#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:52
#: web/templates/public/layout.gohtml:92
#: web/templates/public/home.gohtml:7 web/templates/public/layout.gohtml:53
#: web/templates/public/layout.gohtml:93
msgctxt "title"
msgid "Home"
msgstr "Accueil"
#: web/templates/public/home.gohtml:17
msgid "The pleasure of camping in the middle of nature…"
msgstr "Le plaisir de camper en pleine nature…"
#: web/templates/public/home.gohtml:18
#: web/templates/public/home.gohtml:24
msgctxt "link"
msgid "Booking"
msgstr "Réservation"
#: web/templates/public/home.gohtml:31
msgid "The pleasure of camping in the middle of nature…"
msgstr "Le plaisir de camper en pleine nature…"
#: web/templates/public/home.gohtml:43
msgid "Our services"
msgstr "Nos services"
#: web/templates/public/home.gohtml:33
#: web/templates/public/home.gohtml:45
msgid "Come and enjoy!"
msgstr "Venez et profitez-en !"
#: web/templates/public/home.gohtml:35
#: web/templates/public/surroundings.gohtml:6
#: web/templates/public/surroundings.gohtml:11
#: web/templates/public/layout.gohtml:67 web/templates/public/layout.gohtml:95
#: web/templates/public/home.gohtml:47
#: web/templates/public/surroundings.gohtml:7
#: web/templates/public/surroundings.gohtml:12
#: web/templates/public/layout.gohtml:68 web/templates/public/layout.gohtml:96
#: web/templates/admin/layout.gohtml:64
msgctxt "title"
msgid "Surroundings"
msgstr "Entourage"
#: web/templates/public/home.gohtml:38
#: web/templates/public/home.gohtml:50
msgid "Located in <strong>Alta Garrotxa</strong>, between the <strong>Pyrenees</strong> and the <strong>Costa Brava</strong>."
msgstr "Situé dans <strong>lAlta Garrotxa</strong>, entre les <strong>Pyrénées</strong> et la <strong>Costa Brava</strong>."
#: web/templates/public/home.gohtml:39
#: web/templates/public/home.gohtml:51
msgid "Nearby there are the <strong>gorges of Sadernes</strong>, <strong>volcanoes</strong>, <strong>La Fageda den Jordà</strong>, the Jewish quarter of <strong>Besalú</strong>, the basaltic cliff of <strong>Castellfollit de la Roca</strong>… much to see and much to do."
msgstr "A proximité il y a les <strong>gorges de Sadernes</strong>, les <strong>volcans</strong>, <strong>La Fageda dâen Jordã </strong>, le quartier juif de <strong>Besalú</strong>, la falaise basaltique de <strong>Castellfollit de la Roca</strong>… beaucoup à voir et beaucoup à faire."
#: web/templates/public/home.gohtml:40
#: web/templates/public/home.gohtml:52
msgid "Less than an hour from <strong>Girona</strong>, one from <strong>La Bisbal dEmpordà</strong>, and two from <strong>Barcelona</strong>."
msgstr "À moins dune heure de <strong>Gérone</strong>, un de <strong>La Bisbal dEmpordà</strong>et deux de <strong>Barcelone</strong>."
#: web/templates/public/home.gohtml:41
#: web/templates/public/home.gohtml:53
msgid "Discover the surroundings"
msgstr "Découvrir les environs"
@ -253,57 +253,57 @@ msgctxt "day"
msgid "Sun"
msgstr "Dim."
#: web/templates/public/surroundings.gohtml:31
#: web/templates/public/surroundings.gohtml:32
msgctxt "title"
msgid "What to Do Outside the Campsite?"
msgstr "Que faire à lextérieur du camping ?"
#: web/templates/public/surroundings.gohtml:51
#: web/templates/public/surroundings.gohtml:52
msgctxt "title"
msgid "Once at the Campsite, We Can Inform You about What Activities are Available"
msgstr "Une fois au camping, nous pourrons vous informer sur les activités disponibles"
#: web/templates/public/surroundings.gohtml:54
#: web/templates/public/surroundings.gohtml:55
msgid "Cycle routes"
msgstr "Pistes cyclables"
#: web/templates/public/surroundings.gohtml:55
#: web/templates/public/surroundings.gohtml:56
msgid "There are many bicycle rental companies in Olot."
msgstr "Il existe de nombreuses sociétés de location de vélos à Olot."
#: web/templates/public/surroundings.gohtml:59
#: web/templates/public/surroundings.gohtml:60
msgid "Routes"
msgstr "Itinéraires"
#: web/templates/public/surroundings.gohtml:60
#: web/templates/public/surroundings.gohtml:61
msgid "Routes of all kinds, climbing, mountain passes, for all levels."
msgstr "Itinéraires de toutes sortes, escalade, cols de montagne, pour tous les niveaux."
#: web/templates/public/surroundings.gohtml:64
#: web/templates/public/surroundings.gohtml:65
msgid "Family outing"
msgstr "Sortie en famille"
#: web/templates/public/surroundings.gohtml:65
#: web/templates/public/surroundings.gohtml:66
msgid "Many outing possibilities, for all ages."
msgstr "Nombreuses possibilités de sorties, pour tous les âges."
#: web/templates/public/surroundings.gohtml:69
#: web/templates/public/surroundings.gohtml:70
msgid "Kayak"
msgstr "Kayak"
#: web/templates/public/surroundings.gohtml:70
#: web/templates/public/surroundings.gohtml:71
msgid "There are several points where you can go by kayak, from sections of the Ter river as well as on the coast…."
msgstr "Il y a plusieurs points où vous pouvez aller en kayak, à partir de sections de la rivière Ter ainsi que sur la côte…."
#: web/templates/public/campground.gohtml:6
#: web/templates/public/campground.gohtml:15
#: web/templates/public/layout.gohtml:53 web/templates/public/layout.gohtml:93
#: web/templates/public/campground.gohtml:7
#: web/templates/public/campground.gohtml:16
#: web/templates/public/layout.gohtml:54 web/templates/public/layout.gohtml:94
msgctxt "title"
msgid "Campground"
msgstr "Camping"
#: web/templates/public/booking.gohtml:6 web/templates/public/booking.gohtml:11
#: web/templates/public/layout.gohtml:69
#: web/templates/public/layout.gohtml:70
msgctxt "title"
msgid "Booking"
msgstr "Reservation"
@ -391,20 +391,20 @@ msgctxt "input"
msgid "I have read and I accept <a href=\"%s\" target=\"_blank\">the reservation conditions</a>"
msgstr "Jai lu et jaccepte <a href=\"%s\" target=\"_blank\">les conditions de réservation</a>"
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:47
#: web/templates/public/layout.gohtml:130
#: web/templates/public/layout.gohtml:12 web/templates/public/layout.gohtml:48
#: web/templates/public/layout.gohtml:131
msgid "Campsite Montagut"
msgstr "Camping Montagut"
#: web/templates/public/layout.gohtml:23 web/templates/admin/layout.gohtml:21
#: web/templates/public/layout.gohtml:24 web/templates/admin/layout.gohtml:21
msgid "Skip to main content"
msgstr "Passer au contenu principal"
#: web/templates/public/layout.gohtml:49
#: web/templates/public/layout.gohtml:50
msgid "Menu"
msgstr "Menu"
#: web/templates/public/layout.gohtml:57 web/templates/public/layout.gohtml:103
#: web/templates/public/layout.gohtml:58 web/templates/public/layout.gohtml:104
#: web/templates/admin/campsite/index.gohtml:6
#: web/templates/admin/campsite/index.gohtml:12
#: web/templates/admin/layout.gohtml:46 web/templates/admin/layout.gohtml:86
@ -412,17 +412,17 @@ msgctxt "title"
msgid "Campsites"
msgstr "Locatifs"
#: web/templates/public/layout.gohtml:90
#: web/templates/public/layout.gohtml:91
msgctxt "title"
msgid "Sections"
msgstr "Sections"
#: web/templates/public/layout.gohtml:114
#: web/templates/public/layout.gohtml:115
msgctxt "title"
msgid "Opening"
msgstr "Ouverture"
#: web/templates/public/layout.gohtml:121
#: web/templates/public/layout.gohtml:122
msgid "<abbr title=\"Catalonia Tourism Registry\">RTC</abbr> <abbr title=\"Number\">#</abbr>%s"
msgstr "<abbr title=\"Registre du tourisme de Catalogne\"># RTC</abbr> %s"
@ -613,7 +613,7 @@ msgstr "Type de camping Carrousel"
#: web/templates/admin/campsite/carousel/index.gohtml:12
#: web/templates/admin/services/index.gohtml:12
#: web/templates/admin/home/index.gohtml:12
#: web/templates/admin/home/index.gohtml:57
msgctxt "action"
msgid "Add slide"
msgstr "Ajouter la diapositive"
@ -622,6 +622,7 @@ msgstr "Ajouter la diapositive"
#: web/templates/admin/services/index.gohtml:25
#: web/templates/admin/surroundings/index.gohtml:25
#: web/templates/admin/home/index.gohtml:25
#: web/templates/admin/home/index.gohtml:70
msgctxt "header"
msgid "Image"
msgstr "Image"
@ -629,6 +630,7 @@ msgstr "Image"
#: web/templates/admin/campsite/carousel/index.gohtml:26
#: web/templates/admin/services/index.gohtml:26
#: web/templates/admin/home/index.gohtml:26
#: web/templates/admin/home/index.gohtml:71
msgctxt "header"
msgid "Caption"
msgstr "Légende"
@ -638,13 +640,14 @@ msgstr "Légende"
#: web/templates/admin/services/index.gohtml:72
#: web/templates/admin/surroundings/index.gohtml:27
#: web/templates/admin/home/index.gohtml:27
#: web/templates/admin/home/index.gohtml:72
msgctxt "header"
msgid "Actions"
msgstr "Actions"
#: web/templates/admin/campsite/carousel/index.gohtml:31
#: web/templates/admin/services/index.gohtml:31
#: web/templates/admin/home/index.gohtml:31
#: web/templates/admin/home/index.gohtml:76
msgid "Are you sure you wish to delete this slide?"
msgstr "Êtes-vous sûr de vouloir supprimer cette diapositive ?"
@ -653,13 +656,14 @@ msgstr "Êtes-vous sûr de vouloir supprimer cette diapositive ?"
#: web/templates/admin/services/index.gohtml:88
#: web/templates/admin/surroundings/index.gohtml:44
#: web/templates/admin/home/index.gohtml:44
#: web/templates/admin/home/index.gohtml:89
msgctxt "action"
msgid "Delete"
msgstr "Supprimer"
#: web/templates/admin/campsite/carousel/index.gohtml:54
#: web/templates/admin/services/index.gohtml:53
#: web/templates/admin/home/index.gohtml:53
#: web/templates/admin/home/index.gohtml:98
msgid "No slides added yet."
msgstr "Aucune diapositive na encore été ajoutée."
@ -1001,7 +1005,7 @@ msgid "Services Page"
msgstr "La page des services"
#: web/templates/admin/services/index.gohtml:11
#: web/templates/admin/home/index.gohtml:11
#: web/templates/admin/home/index.gohtml:56
msgctxt "title"
msgid "Carousel"
msgstr "Carrousel"
@ -1199,6 +1203,24 @@ msgctxt "action"
msgid "Logout"
msgstr "Déconnexion"
#: web/templates/admin/home/index.gohtml:11
msgctxt "title"
msgid "Cover"
msgstr "Couverture"
#: web/templates/admin/home/index.gohtml:12
msgctxt "action"
msgid "Add cover image"
msgstr "Ajouter une image de couverture"
#: web/templates/admin/home/index.gohtml:31
msgid "Are you sure you wish to delete this cover image?"
msgstr "Êtes-vous sûr de vouloir supprimer cette image de couverture ?"
#: web/templates/admin/home/index.gohtml:53
msgid "No images added to the cover yet."
msgstr "Aucune image de couverture na encore été ajoutée."
#: web/templates/admin/media/picker.gohtml:8
msgctxt "title"
msgid "Media Picker"
@ -1266,7 +1288,7 @@ msgstr "Envoyer un fichier"
#: pkg/legal/admin.go:258 pkg/app/user.go:249 pkg/campsite/types/option.go:344
#: pkg/campsite/types/feature.go:259 pkg/campsite/types/admin.go:463
#: pkg/season/admin.go:412 pkg/services/admin.go:314
#: pkg/season/admin.go:412 pkg/services/admin.go:316
#: pkg/surroundings/admin.go:321
msgid "Name can not be empty."
msgstr "Le nom ne peut pas être laissé vide."
@ -1276,22 +1298,22 @@ msgstr "Le nom ne peut pas être laissé vide."
msgid "Name must have at least one letter."
msgstr "Le nom doit comporter au moins une lettre."
#: pkg/carousel/admin.go:267 pkg/campsite/types/carousel.go:223
#: pkg/carousel/admin.go:275 pkg/campsite/types/carousel.go:223
msgctxt "input"
msgid "Slide image"
msgstr "Image du diaporama"
#: pkg/carousel/admin.go:268 pkg/campsite/types/carousel.go:224
#: pkg/carousel/admin.go:276 pkg/campsite/types/carousel.go:224
msgctxt "action"
msgid "Set slide image"
msgstr "Définir limage de la diapositive"
#: pkg/carousel/admin.go:337 pkg/campsite/types/carousel.go:297
#: pkg/carousel/admin.go:345 pkg/campsite/types/carousel.go:297
#: pkg/surroundings/admin.go:316
msgid "Slide image can not be empty."
msgstr "Limage de la diapositive ne peut pas être vide."
#: pkg/carousel/admin.go:338 pkg/campsite/types/carousel.go:298
#: pkg/carousel/admin.go:346 pkg/campsite/types/carousel.go:298
#: pkg/surroundings/admin.go:317
msgid "Slide image must be an image media type."
msgstr "Limage de la diapositive doit être de type média dimage."
@ -1371,7 +1393,7 @@ msgstr "Le prix par nuit doit être un nombre décimal."
msgid "Price per night must be zero or greater."
msgstr "Le prix par nuit doit être égal ou supérieur."
#: pkg/campsite/types/feature.go:258 pkg/services/admin.go:313
#: pkg/campsite/types/feature.go:258 pkg/services/admin.go:315
msgid "Selected icon is not valid."
msgstr "Licône sélectionnée nest pas valide."

View File

@ -0,0 +1,7 @@
-- Revert camper:add_cover_carousel_slide from pg
begin;
drop function if exists camper.add_cover_carousel_slide(integer, text);
commit;

View File

@ -0,0 +1,7 @@
-- Revert camper:cover_carousel from pg
begin;
drop table if exists camper.cover_carousel;
commit;

View File

@ -0,0 +1,7 @@
-- Revert camper:cover_carousel_i18n from pg
begin;
drop table if exists camper.cover_carousel_i18n;
commit;

View File

@ -0,0 +1,7 @@
-- Revert camper:order_cover_carousel from pg
begin;
drop function if exists camper.order_cover_carousel(integer[]);
commit;

View File

@ -0,0 +1,7 @@
-- Revert camper:remove_cover_carousel_slide from pg
begin;
drop function if exists camper.remove_cover_carousel_slide(integer);
commit;

View File

@ -0,0 +1,7 @@
-- Revert camper:translate_cover_carousel_slide from pg
begin;
drop function if exists camper.translate_cover_carousel_slide(integer, text, text);
commit;

View File

@ -148,3 +148,9 @@ surroundings_highlight_i18n [roles schema_camper surroundings_highlight language
translate_surroundings_highlight [roles schema_camper surroundings_highlight_i18n] 2024-01-15T20:09:40Z jordi fita mas <jordi@tandem.blog> # Add function to translate surroundings highlights
remove_surroundings_highlight [roles schema_camper surroundings_highlight surroundings_highlight_i18n] 2024-01-15T20:19:37Z jordi fita mas <jordi@tandem.blog> # Add function to delete surroundings highlight and their translations
order_surroundings_highlights [roles schema_camper surroundings_highlight] 2024-01-15T20:29:59Z jordi fita mas <jordi@tandem.blog> # Add function to order surrounding highlights
cover_carousel [roles schema_camper company media user_profile] 2024-01-16T17:16:34Z jordi fita mas <jordi@tandem.blog> # Add relation for cover pages image carousel
cover_carousel_i18n [roles schema_camper cover_carousel language] 2024-01-16T17:22:42Z jordi fita mas <jordi@tandem.blog> # Add relation for cover carousel translations
add_cover_carousel_slide [roles schema_camper cover_carousel] 2024-01-16T17:49:21Z jordi fita mas <jordi@tandem.blog> # Add function to create slides for the cover carousel
translate_cover_carousel_slide [roles schema_camper cover_carousel_i18n] 2024-01-16T18:17:36Z jordi fita mas <jordi@tandem.blog> # Add function to translate a cover carousel slider
remove_cover_carousel_slide [roles schema_camper cover_carousel cover_carousel_i18n] 2024-01-16T18:27:48Z jordi fita mas <jordi@tandem.blog> # Add function to remove sliders from the cover carousel
order_cover_carousel [schema_camper roles cover_carousel] 2024-01-16T18:40:12Z jordi fita mas <jordi@tandem.blog> # Add function to order cover carousel

View File

@ -0,0 +1,82 @@
-- Test add_cover_carousel_slide
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
set search_path to camper, public;
select plan(14);
select has_function('camper', 'add_cover_carousel_slide', array['integer', 'text']);
select function_lang_is('camper', 'add_cover_carousel_slide', array['integer', 'text'], 'sql');
select function_returns('camper', 'add_cover_carousel_slide', array['integer', 'text'], 'integer');
select isnt_definer('camper', 'add_cover_carousel_slide', array['integer', 'text']);
select volatility_is('camper', 'add_cover_carousel_slide', array['integer', 'text'], 'volatile');
select function_privs_are('camper', 'add_cover_carousel_slide', array['integer', 'text'], 'guest', array[]::text[]);
select function_privs_are('camper', 'add_cover_carousel_slide', array['integer', 'text'], 'employee', array[]::text[]);
select function_privs_are('camper', 'add_cover_carousel_slide', array['integer', 'text'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'add_cover_carousel_slide', array['integer', 'text'], 'authenticator', array[]::text[]);
set client_min_messages to warning;
truncate cover_carousel_i18n cascade;
truncate cover_carousel cascade;
truncate media cascade;
truncate media_content cascade;
truncate company cascade;
reset client_min_messages;
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, rtc_number, tourist_tax, country_code, currency_code, default_lang_tag)
values (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', '', 60, 'ES', 'EUR', 'ca')
;
insert into media_content (media_type, bytes)
values ('text/plain', 'hello, world!')
, ('image/svg+xml', '<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffffff","a"};')
;
insert into media (media_id, company_id, original_filename, content_hash)
values (5, 1, 'text.txt', sha256('hello, world!'))
, (6, 1, 'image.svg', sha256('<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>'))
, (7, 1, 'cover4.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};'))
;
insert into cover_carousel (media_id, caption)
values (5, 'Previous caption')
;
select lives_ok(
$$ select add_cover_carousel_slide(6, 'A caption') $$,
'Should be able to add a carousel slide with a caption'
);
select lives_ok(
$$ select add_cover_carousel_slide(7, null) $$,
'Should be able to add a carousel slide without caption'
);
select lives_ok(
$$ select add_cover_carousel_slide(5, 'New caption') $$,
'Should be able to overwrite a slide with a new caption'
);
select bag_eq(
$$ select media_id, caption from cover_carousel $$,
$$ values (5, 'New caption')
, (6, 'A caption')
, (7, '')
$$,
'Should have all three slides'
);
select is_empty(
$$ select * from cover_carousel_i18n $$,
'Should not have added any translation'
);
select *
from finish();
rollback;

192
test/cover_carousel.sql Normal file
View File

@ -0,0 +1,192 @@
-- Test cover_carousel
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(35);
set search_path to camper, public;
select has_table('cover_carousel');
select has_pk('cover_carousel');
select table_privs_are('cover_carousel', 'guest', array['SELECT']);
select table_privs_are('cover_carousel', 'employee', array['SELECT']);
select table_privs_are('cover_carousel', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
select table_privs_are('cover_carousel', 'authenticator', array[]::text[]);
select has_column('cover_carousel', 'media_id');
select col_is_pk('cover_carousel', 'media_id');
select col_is_fk('cover_carousel', 'media_id');
select fk_ok('cover_carousel', 'media_id', 'media', 'media_id');
select col_type_is('cover_carousel', 'media_id', 'integer');
select col_not_null('cover_carousel', 'media_id');
select col_hasnt_default('cover_carousel', 'media_id');
select has_column('cover_carousel', 'caption');
select col_type_is('cover_carousel', 'caption', 'text');
select col_not_null('cover_carousel', 'caption');
select col_hasnt_default('cover_carousel', 'caption');
select has_column('cover_carousel', 'position');
select col_type_is('cover_carousel', 'position', 'integer');
select col_not_null('cover_carousel', 'position');
select col_has_default('cover_carousel', 'position');
select col_default_is('cover_carousel', 'position', '2147483647');
set client_min_messages to warning;
truncate cover_carousel cascade;
truncate media cascade;
truncate media_content cascade;
truncate company_host cascade;
truncate company_user cascade;
truncate company cascade;
truncate auth."user" cascade;
reset client_min_messages;
insert into auth."user" (user_id, email, name, password, cookie, cookie_expires_at)
values (1, 'demo@tandem.blog', 'Demo', 'test', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month')
, (5, 'admin@tandem.blog', 'Demo', 'test', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month')
;
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, rtc_number, tourist_tax, country_code, currency_code, default_lang_tag)
values (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', '', 60, 'ES', 'EUR', 'ca')
, (4, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', '', 60, 'FR', 'USD', 'ca')
;
insert into company_user (company_id, user_id, role)
values (2, 1, 'admin')
, (4, 5, 'admin')
;
insert into company_host (company_id, host)
values (2, 'co2')
, (4, 'co4')
;
insert into media_content (media_type, bytes)
values ('text/plain', 'content2')
, ('text/plain', 'content3')
, ('text/plain', 'content4')
, ('text/plain', 'content5')
;
insert into media (media_id, company_id, original_filename, content_hash)
values ( 7, 2, 'text2.txt', sha256('content2'))
, ( 8, 2, 'text3.txt', sha256('content3'))
, ( 9, 4, 'text4.txt', sha256('content4'))
, (10, 4, 'text5.txt', sha256('content5'))
;
insert into cover_carousel (media_id, caption)
values (7, 'Caption 7')
, (9, 'Caption 9')
;
prepare carousel_data as
select media_id, caption
from cover_carousel
order by media_id, caption;
set role guest;
select bag_eq(
'carousel_data',
$$ values (7, 'Caption 7')
, (9, 'Caption 9')
$$,
'Everyone should be able to list all carousel media across all companies'
);
reset role;
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog', 'co2');
select lives_ok(
$$ insert into cover_carousel(media_id, caption)
values (8, 'Caption 8') $$,
'Admin from company 2 should be able to insert a new carousel media to that company.'
);
select bag_eq(
'carousel_data',
$$ values (7, 'Caption 7')
, (8, 'Caption 8')
, (9, 'Caption 9')
$$,
'The new row should have been added'
);
select lives_ok(
$$ update cover_carousel set caption = 'Caption 8.8' where media_id = 8 $$,
'Admin from company 2 should be able to update carousel media of that company.'
);
select bag_eq(
'carousel_data',
$$ values (7, 'Caption 7')
, (8, 'Caption 8.8')
, (9, 'Caption 9')
$$,
'The row should have been updated.'
);
select lives_ok(
$$ delete from cover_carousel where media_id = 8 $$,
'Admin from company 2 should be able to delete carousel media from that company.'
);
select bag_eq(
'carousel_data',
$$ values (7, 'Caption 7')
, (9, 'Caption 9')
$$,
'The row should have been deleted.'
);
select throws_ok(
$$ insert into cover_carousel (media_id, caption)
values (10, 'Caption 10') $$,
'42501', 'new row violates row-level security policy for table "cover_carousel"',
'Admin from company 2 should NOT be able to insert new media to company 4.'
);
select lives_ok(
$$ update cover_carousel set caption = 'Nope' where media_id = 9 $$,
'Admin from company 2 should not be able to update new carousel media of company 4, but no error if media_id is not changed.'
);
select bag_eq(
'carousel_data',
$$ values (7, 'Caption 7')
, (9, 'Caption 9')
$$,
'No row should have been changed.'
);
select throws_ok(
$$ update cover_carousel set media_id = 10 where media_id = 7 $$,
'42501', 'new row violates row-level security policy for table "cover_carousel"',
'Admin from company 2 should NOT be able to move carousel media to company 4'
);
select lives_ok(
$$ delete from cover_carousel where media_id = 9 $$,
'Admin from company 2 should NOT be able to delete carousel media from company 4, but no error is thrown'
);
select bag_eq(
'carousel_data',
$$ values (7, 'Caption 7')
, (9, 'Caption 9')
$$,
'No row should have been changed'
);
reset role;
select *
from finish();
rollback;

View File

@ -0,0 +1,44 @@
-- Test cover_carousel_i18n
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(23);
set search_path to camper, public;
select has_table('cover_carousel_i18n');
select has_pk('cover_carousel_i18n');
select col_is_pk('cover_carousel_i18n', array['media_id', 'lang_tag']);
select table_privs_are('cover_carousel_i18n', 'guest', array['SELECT']);
select table_privs_are('cover_carousel_i18n', 'employee', array['SELECT']);
select table_privs_are('cover_carousel_i18n', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
select table_privs_are('cover_carousel_i18n', 'authenticator', array[]::text[]);
select has_column('cover_carousel_i18n', 'media_id');
select col_is_fk('cover_carousel_i18n', 'media_id');
select fk_ok('cover_carousel_i18n', 'media_id', 'cover_carousel', 'media_id');
select col_type_is('cover_carousel_i18n', 'media_id', 'integer');
select col_not_null('cover_carousel_i18n', 'media_id');
select col_hasnt_default('cover_carousel_i18n', 'media_id');
select has_column('cover_carousel_i18n', 'lang_tag');
select col_is_fk('cover_carousel_i18n', 'lang_tag');
select fk_ok('cover_carousel_i18n', 'lang_tag', 'language', 'lang_tag');
select col_type_is('cover_carousel_i18n', 'lang_tag', 'text');
select col_not_null('cover_carousel_i18n', 'lang_tag');
select col_hasnt_default('cover_carousel_i18n', 'lang_tag');
select has_column('cover_carousel_i18n', 'caption');
select col_type_is('cover_carousel_i18n', 'caption', 'text');
select col_not_null('cover_carousel_i18n', 'caption');
select col_hasnt_default('cover_carousel_i18n', 'caption');
select *
from finish();
rollback;

View File

@ -0,0 +1,79 @@
-- Test order_cover_carousel
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(11);
set search_path to camper, public;
select has_function('camper', 'order_cover_carousel', array['integer[]']);
select function_lang_is('camper', 'order_cover_carousel', array['integer[]'], 'sql');
select function_returns('camper', 'order_cover_carousel', array['integer[]'], 'void');
select isnt_definer('camper', 'order_cover_carousel', array['integer[]']);
select volatility_is('camper', 'order_cover_carousel', array['integer[]'], 'volatile');
select function_privs_are('camper', 'order_cover_carousel', array ['integer[]'], 'guest', array[]::text[]);
select function_privs_are('camper', 'order_cover_carousel', array ['integer[]'], 'employee', array[]::text[]);
select function_privs_are('camper', 'order_cover_carousel', array ['integer[]'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'order_cover_carousel', array ['integer[]'], 'authenticator', array[]::text[]);
set client_min_messages to warning;
truncate cover_carousel_i18n cascade;
truncate cover_carousel cascade;
truncate media cascade;
truncate media_content cascade;
truncate company cascade;
reset client_min_messages;
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, rtc_number, tourist_tax, country_code, currency_code, default_lang_tag)
values (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', '', 60, 'ES', 'EUR', 'ca')
;
insert into media_content (media_type, bytes)
values ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffffff","a"};')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ff00ff","a"};')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffff00","a"};')
, ('text/plain', 'hello, world!')
, ('image/svg+xml', '<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>')
;
insert into media (media_id, company_id, original_filename, content_hash)
values (2, 1, 'cover2.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};'))
, (3, 1, 'cover3.xpm', sha256('static char *s[]={"1 1 1 1","a c #ff00ff","a"};'))
, (4, 1, 'cover4.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffff00","a"};'))
, (5, 1, 'text.txt', sha256('hello, world!'))
, (6, 1, 'image.svg', sha256('<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>'))
;
insert into cover_carousel (media_id, caption)
values (2, '1')
, (3, '2')
, (4, '3')
, (5, '4')
, (6, '5')
;
select lives_ok(
$$ select order_cover_carousel('{4,6,5,2,3}') $$,
'Should be able to sort cover slides using their ID'
);
select bag_eq(
$$ select media_id, position from cover_carousel $$,
$$ values (4, 1)
, (6, 2)
, (5, 3)
, (2, 4)
, (3, 5)
$$,
'Should have sorted all cover slides.'
);
select *
from finish();
rollback;

View File

@ -0,0 +1,87 @@
-- Test remove_cover_carousel_slide
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(12);
set search_path to camper, public;
select has_function('camper', 'remove_cover_carousel_slide', array['integer']);
select function_lang_is('camper', 'remove_cover_carousel_slide', array['integer'], 'sql');
select function_returns('camper', 'remove_cover_carousel_slide', array['integer'], 'void');
select isnt_definer('camper', 'remove_cover_carousel_slide', array['integer']);
select volatility_is('camper', 'remove_cover_carousel_slide', array['integer'], 'volatile');
select function_privs_are('camper', 'remove_cover_carousel_slide', array['integer'], 'guest', array[]::text[]);
select function_privs_are('camper', 'remove_cover_carousel_slide', array['integer'], 'employee', array[]::text[]);
select function_privs_are('camper', 'remove_cover_carousel_slide', array['integer'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'remove_cover_carousel_slide', array['integer'], 'authenticator', array[]::text[]);
set client_min_messages to warning;
truncate cover_carousel_i18n cascade;
truncate cover_carousel cascade;
truncate media cascade;
truncate media_content cascade;
truncate company cascade;
reset client_min_messages;
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, rtc_number, tourist_tax, country_code, currency_code, default_lang_tag)
values (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', '', 60, 'ES', 'EUR', 'ca')
;
insert into media_content (media_type, bytes)
values ('text/plain', 'hello, world!')
, ('image/svg+xml', '<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffffff","a"};')
;
insert into media (media_id, company_id, original_filename, content_hash)
values (5, 1, 'text.txt', sha256('hello, world!'))
, (6, 1, 'image.svg', sha256('<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>'))
, (7, 1, 'cover4.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};'))
;
insert into cover_carousel (media_id, caption)
values (5, 'Source caption')
, (6, 'Another caption')
, (7, 'N/A')
;
insert into cover_carousel_i18n (media_id, lang_tag, caption)
values (5, 'en', 'Target caption')
, (5, 'es', 'Target caption (spanish)')
, (6, 'en', 'Target caption')
, (6, 'es', 'Target caption (spanish)')
, (7, 'en', 'Target caption')
, (7, 'es', 'Target caption (spanish)')
;
select lives_ok(
$$ select remove_cover_carousel_slide(6) $$,
'Should be able to delete a slide'
);
select bag_eq(
$$ select media_id, caption from cover_carousel $$,
$$ values (5, 'Source caption')
, (7, 'N/A')
$$,
'Should have removed the slide'
);
select bag_eq(
$$ select media_id, lang_tag, caption from cover_carousel_i18n $$,
$$ values (5, 'en', 'Target caption')
, (5, 'es', 'Target caption (spanish)')
, (7, 'en', 'Target caption')
, (7, 'es', 'Target caption (spanish)')
$$,
'Should have removed the slides translations'
);
select *
from finish();
rollback;

View File

@ -0,0 +1,85 @@
-- Test translate_cover_carousel_slide
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(13);
set search_path to camper, public;
select has_function('camper', 'translate_cover_carousel_slide', array['integer', 'text', 'text']);
select function_lang_is('camper', 'translate_cover_carousel_slide', array['integer', 'text', 'text'], 'sql');
select function_returns('camper', 'translate_cover_carousel_slide', array['integer', 'text', 'text'], 'void');
select isnt_definer('camper', 'translate_cover_carousel_slide', array['integer', 'text', 'text']);
select volatility_is('camper', 'translate_cover_carousel_slide', array['integer', 'text', 'text'], 'volatile');
select function_privs_are('camper', 'translate_cover_carousel_slide', array['integer', 'text', 'text'], 'guest', array[]::text[]);
select function_privs_are('camper', 'translate_cover_carousel_slide', array['integer', 'text', 'text'], 'employee', array[]::text[]);
select function_privs_are('camper', 'translate_cover_carousel_slide', array['integer', 'text', 'text'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'translate_cover_carousel_slide', array['integer', 'text', 'text'], 'authenticator', array[]::text[]);
set client_min_messages to warning;
truncate cover_carousel_i18n cascade;
truncate cover_carousel cascade;
truncate media cascade;
truncate media_content cascade;
truncate company cascade;
reset client_min_messages;
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, rtc_number, tourist_tax, country_code, currency_code, default_lang_tag)
values (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', '', 60, 'ES', 'EUR', 'ca')
;
insert into media_content (media_type, bytes)
values ('text/plain', 'hello, world!')
, ('image/svg+xml', '<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffffff","a"};')
;
insert into media (media_id, company_id, original_filename, content_hash)
values (5, 1, 'text.txt', sha256('hello, world!'))
, (6, 1, 'image.svg', sha256('<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>'))
, (7, 1, 'cover4.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};'))
;
insert into cover_carousel (media_id, caption)
values (5, 'Source caption')
, (6, 'Another caption')
, (7, 'N/A')
;
insert into cover_carousel_i18n (media_id, lang_tag, caption)
values (5, 'en', 'Target caption')
;
select lives_ok(
$$ select translate_cover_carousel_slide(5, 'ca', 'Traducció') $$,
'Should be able to translate a carousel slide'
);
select lives_ok(
$$ select translate_cover_carousel_slide(6, 'es', null) $$,
'Should be able to “translate” a carousel slide to the empty string'
);
select lives_ok(
$$ select translate_cover_carousel_slide(5, 'en', 'Not anymore') $$,
'Should be able to overwrite a slides translation'
);
select bag_eq(
$$ select media_id, lang_tag, caption from cover_carousel_i18n $$,
$$ values (5, 'ca', 'Traducció')
, (5, 'en', 'Not anymore')
, (6, 'es', '')
$$,
'Should have all three slides'
);
select *
from finish();
rollback;

View File

@ -0,0 +1,7 @@
-- Verify camper:add_cover_carousel_slide on pg
begin;
select has_function_privilege('camper.add_cover_carousel_slide(integer, text)', 'execute');
rollback;

17
verify/cover_carousel.sql Normal file
View File

@ -0,0 +1,17 @@
-- Verify camper:cover_carousel on pg
begin;
select media_id
, caption
, position
from camper.cover_carousel
where false;
select 1 / count(*) from pg_class where oid = 'camper.cover_carousel'::regclass and relrowsecurity;
select 1 / count(*) from pg_policy where polname = 'guest_ok' and polrelid = 'camper.cover_carousel'::regclass;
select 1 / count(*) from pg_policy where polname = 'insert_to_company' and polrelid = 'camper.cover_carousel'::regclass;
select 1 / count(*) from pg_policy where polname = 'update_company' and polrelid = 'camper.cover_carousel'::regclass;
select 1 / count(*) from pg_policy where polname = 'delete_from_company' and polrelid = 'camper.cover_carousel'::regclass;
rollback;

View File

@ -0,0 +1,11 @@
-- Verify camper:cover_carousel_i18n on pg
begin;
select media_id
, lang_tag
, caption
from camper.cover_carousel_i18n
where false;
rollback;

View File

@ -0,0 +1,7 @@
-- Verify camper:order_cover_carousel on pg
begin;
select has_function_privilege('camper.order_cover_carousel(integer[])', 'execute');
rollback;

View File

@ -0,0 +1,7 @@
-- Verify camper:remove_cover_carousel_slide on pg
begin;
select has_function_privilege('camper.remove_cover_carousel_slide(integer)', 'execute');
rollback;

View File

@ -0,0 +1,7 @@
-- Verify camper:translate_cover_carousel_slide on pg
begin;
select has_function_privilege('camper.translate_cover_carousel_slide(integer, text, text)', 'execute');
rollback;

View File

@ -155,7 +155,7 @@ address {
font-style: normal;
}
body > a[href="#content"], .sr-only {
body > a[href="#content"], .sr-only, .hero button span {
border: 0;
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
@ -418,25 +418,70 @@ nav:last-of-type > ul > li:last-child {
padding: 2.5rem;
}
.nature div, .nature section a {
.hero div, .nature section a {
background: var(--background-image) center center no-repeat;
background-size: cover;
}
.nature div, .services {
.hero, .nature, .services {
margin-bottom: 5rem;
}
.nature div:first-child, .nature section, .services {
.hero div, .nature section, .services {
border-radius: 5px;
}
.nature div:first-child {
min-height: 65vh;
.hero {
height: 65vh;
position: relative;
overflow: hidden;
}
.hero div {
position: absolute;
inset: 0;
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 5rem;
padding: 5rem;
transition: opacity 1s ease-in-out;
}
.hero div[aria-hidden] {
opacity: 0;
}
.hero button {
position: absolute;
top: 50%;
opacity: 0;
z-index: 10;
transition: opacity .5s, transform .5s;
width: 3.2rem;
aspect-ratio: 1;
background-color: transparent;
background-repeat: no-repeat;
background-position: center center;
background-size: 3.2rem;
border: none;
}
.hero button:first-of-type {
left: 1rem;
transform: translate(-150%, -50%);
background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"%3E%3Crect width="256" height="256" fill="none"/%3E%3Cpolyline points="160 208 80 128 160 48" fill="none" stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/%3E%3C/svg%3E');
}
.hero button:last-of-type {
right: 1rem;
transform: translate(150%, -50%);
background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"%3E%3Crect width="256" height="256" fill="none"/%3E%3Cpolyline points="96 48 176 128 96 208" fill="none" stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/%3E%3C/svg%3E');
}
.hero:hover button {
transform: translate(0, -50%);
opacity: 1;
}
.services {
@ -447,48 +492,59 @@ nav:last-of-type > ul > li:last-child {
padding: 5rem;
}
.nature div:first-child {
justify-content: space-between;
}
.nature h2, .nature div:first-child a, .services h2, .surroundings .spiel a {
.hero div > span, .hero a, .services h2, .surroundings .spiel a {
font-weight: bold;
line-height: .9em;
}
.nature h2, .nature div:first-child a {
.hero div > span, .hero a {
border-radius: 5px;
background-color: var(--clar);
}
.nature h2 {
.hero div > span {
width: 50%;
min-height: 100%;
margin-bottom: 0;
font-size: calc(2rem + 4vw);
display: block;
background: transparent;
color: var(--base);
text-shadow: 0 1px 0 black;
transition: transform 1s 1s;
}
.nature div:first-child a, .services h2, .surroundings .spiel a {
.hero div[aria-hidden] > span {
transform: translateY(200%);
}
.hero a, .services h2, .surroundings .spiel a {
font-size: calc(1rem + 1.2vw);
}
.nature div:first-child a, .services a {
.hero a, .services a {
padding: 1.5rem 2rem;
}
.nature div:first-child a span, .services a span, .surroundings .spiel a:hover span, .campsite_type_booking button span {
.hero a span, .services a span, .surroundings .spiel a:hover span, .campsite_type_booking button span {
display: inline-block;
transition: transform 0.5s ease;
}
.nature div:first-child a:hover span, .services a:hover span, .spiel a:hover span, .campsite_type_booking button:hover span {
.hero a:hover span, .services a:hover span, .spiel a:hover span, .campsite_type_booking button:hover span {
transform: translateX(1.3rem);
}
dl, .nature div + div, .outside_activities > div {
@media (max-width: 48rem) {
.hero div {
flex-direction: column;
}
.hero div > span {
width: 100%;
}
}
dl, .nature > div, .outside_activities > div {
display: flex;
gap: 5rem;
}
@ -518,21 +574,11 @@ dl, .nature div + div, .outside_activities > div {
}
@media (max-width: 64rem) {
.nature div + div {
.nature > div {
flex-direction: column;
}
}
@media (max-width: 48rem) {
.nature div:first-child {
flex-direction: column;
}
.nature h2 {
width: 100%;
}
}
.services {
justify-content: end;
background-color: var(--accent);
@ -1522,3 +1568,7 @@ dt {
color: var(--accent);
text-decoration: var(--accent) wavy underline;
}
[x-cloak] {
display: none !important;
}

View File

@ -15,9 +15,9 @@
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/carousel.slideForm*/ -}}
<form
{{ if .ID }}
data-hx-put="/admin/{{ .CarouselName }}/slides/{{ .ID }}"
data-hx-put="{{ .SlidesURL }}/{{ .ID }}"
{{ else }}
action="/admin/{{ .CarouselName }}/slides" method="post"
action="{{ .SlidesURL }}" method="post"
{{ end }}
>
<h2>

View File

@ -8,6 +8,51 @@
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/home.homeIndex*/ -}}
<h2>{{( pgettext "Cover" "title" )}}</h2>
<a href="/admin/home/cover/new">{{( pgettext "Add cover image" "action" )}}</a>
{{ if .Cover -}}
<form id="slide-index"
class="sortable"
data-hx-post="/admin/home/cover/order"
data-hx-trigger="end"
data-hx-select="#slide-index"
data-hx-swap="outerHTML"
>
{{ CSRFInput }}
<table>
<thead>
<tr>
<th scope="col">{{( pgettext "Image" "header" )}}</th>
<th scope="col">{{( pgettext "Caption" "header" )}}</th>
<th scope="col">{{( pgettext "Actions" "header" )}}</th>
</tr>
</thead>
<tbody>
{{ $confirm := (gettext "Are you sure you wish to delete this cover image?")}}
{{ range $slide := .Cover -}}
<tr>
<td>
<span class="handle"></span>
<input type="hidden" name="media_id" value="{{ .ID }}">
<a href="/admin/home/cover/{{ .ID }}"><img src="{{ .Media }}" alt=""></a>
</td>
<td><a href="/admin/home/cover/{{ .ID }}">{{ .Caption }}</a></td>
<td>
<button data-hx-delete="/admin/home/cover/{{ .ID }}"
data-hx-confirm="{{ $confirm }}"
data-hx-headers='{ {{ CSRFHeader }} }'>
{{( pgettext "Delete" "action" )}}
</button>
</td>
</tr>
{{- end }}
</tbody>
</table>
</form>
{{ else -}}
<p>{{( gettext "No images added to the cover yet." )}}</p>
{{- end }}
<h2>{{( pgettext "Carousel" "title" )}}</h2>
<a href="/admin/home/slides/new">{{( pgettext "Add slide" "action" )}}</a>
{{ if .Slides -}}

View File

@ -9,7 +9,7 @@
{{ define "head" -}}
{{ template "carouselStyle" }}
<script src="/static/alpinejs@3.13.3.min.js" defer></script>
{{ template "alpineScript" }}
{{- end }}
{{ define "content" -}}

View File

@ -9,15 +9,26 @@
{{ define "head" -}}
{{ template "carouselStyle" }}
{{ template "alpineScript" }}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/home.homePage*/ -}}
<section class="nature">
<div style="--background-image:url('https://campingmontagut.tandem.ws/media/ead3c7fba2e5157d02d7f8069f4534ce0c0c799147e67969deaac957c50df55a/camping_campsite.jpg')">
<h2>{{ (gettext "The pleasure of camping in the middle of nature…")}}</h2>
<div class="hero" role="marquee"
x-data="{cur: 0, len: {{ len .Cover }}, next() { this.slide((this.cur + 1) % this.len); }, prev() { this.slide(this.cur > 0 ? this.cur - 1 : this.len - 1); }, slide(i) { this.cur = i; this.reset(); }, reset() { clearTimeout(this.timeout); this.timeout = setTimeout(this.next.bind(this), 5000); }}"
x-init="reset()"
>
{{ range $index, $slide := .Cover -}}
<div x-cloak :aria-hidden="cur !== {{ $index }}" style="--background-image:url('{{ .Media }}')">
<span>{{ .Caption }}</span>
<a href="/{{ currentLocale }}/booking">{{( pgettext "Booking" "link" )}} <span>→</span></a>
</div>
{{- end }}
<button @click="prev()"><span>Prev</span></button>
<button @click="next()"><span>Next</span></button>
</div>
<section class="nature">
<h2 class="sr-only">{{ (gettext "The pleasure of camping in the middle of nature…")}}</h2>
{{ with .CampsiteTypes -}}
<div>
{{ range . -}}

View File

@ -152,6 +152,10 @@
<link rel="stylesheet" media="screen" href="/static/slick@1.8.1.css">
{{- end }}
{{ define "alpineScript" -}}
<script src="/static/alpinejs@3.13.3.min.js" defer></script>
{{- end }}
{{ define "carouselInit" -}}
<script src="/static/jquery@3.7.1.min.js"></script>
<script src="/static/slick@1.8.1.min.js"></script>