add support for page sections on generic pages

This commit is contained in:
2024-06-21 23:13:15 +02:00
parent 82f451cb14
commit 41f5297a12
16 changed files with 683 additions and 93 deletions

View File

@ -136,3 +136,50 @@ class FeaturedBlock(blocks.StructBlock):
class Meta: class Meta:
icon = "arrow-right-full" icon = "arrow-right-full"
class PageSectionNavigationBlock(blocks.StaticBlock):
class Meta:
icon = "order-down"
label = "Sideseksjonsnavigasjon"
admin_text = "Lager automatisk ankerlenker til alle sideseksjoner"
BASE_BLOCKS = [
("paragraph", blocks.RichTextBlock(label="Rik tekst")),
("image", ImageWithTextBlock(label="Bilde")),
("image_slider", ImageSliderBlock(label="Bildegalleri")),
("horizontal_rule", HorizontalRuleBlock(label="Skillelinje")),
("featured", FeaturedBlock(label="Fremhevet underside")),
("page_section_navigation", PageSectionNavigationBlock()),
]
@register_streamfield_block
class PageSectionBlock(blocks.StructBlock):
COLOR_CHOICES = (
("deepBrick", "Dyp tegl"),
("neufPink", "Griserosa"),
("goldenOrange", "Gyllen oransje"),
("goldenBeige", "Gyllen beige"),
("chateauBlue", "Slottsblå"),
)
title = blocks.CharBlock(max_length=64, required=True, label="Tittel")
background_color = blocks.ChoiceBlock(
label="Bakgrunnsfarge",
required=False,
choices=COLOR_CHOICES,
)
body = blocks.StreamBlock(
[block for block in BASE_BLOCKS if block[0] != "page_section_navigation"]
)
graphql_fields = [
GraphQLString("title", required=True),
GraphQLString("background_color", required=False),
GraphQLStreamfield("body", required=True),
]
class Meta:
icon = "folder-open-1"

View File

@ -1,15 +1,8 @@
from wagtail import blocks
from wagtail.fields import StreamField from wagtail.fields import StreamField
from dnscms.blocks import FeaturedBlock, HorizontalRuleBlock, ImageSliderBlock, ImageWithTextBlock from dnscms.blocks import BASE_BLOCKS
CommonStreamField = StreamField( CommonStreamField = StreamField(
[ BASE_BLOCKS,
("paragraph", blocks.RichTextBlock(label="Rik tekst")),
("image", ImageWithTextBlock(label="Bilde")),
("image_slider", ImageSliderBlock(label="Bildegalleri")),
("horizontal_rule", HorizontalRuleBlock(label="Skillelinje")),
("featured", FeaturedBlock(label="Fremhevet underside")),
],
default=[("paragraph", "")], default=[("paragraph", "")],
) )

View File

@ -0,0 +1,36 @@
# Generated by Django 5.0.6 on 2024-06-21 00:36
import django.db.models.deletion
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('generic', '0006_alter_genericpage_body'),
('wagtailcore', '0093_uploadedfile'),
]
operations = [
migrations.CreateModel(
name='GenericSectionedPage',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
('lead', wagtail.fields.RichTextField()),
('body', wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock(label='Rik tekst')), ('image', wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(label='Bilde')), ('image_format', wagtail.blocks.ChoiceBlock(choices=[('fullwidth', 'Fullbredde'), ('bleed', 'Utfallende'), ('original', 'Uendret størrelse')], icon='cup', label='Bildeformat')), ('text', wagtail.blocks.CharBlock(label='Tekst', max_length=512, required=False))], label='Bilde')), ('image_slider', wagtail.blocks.StructBlock([('images', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(label='Bilde')), ('text', wagtail.blocks.CharBlock(label='Tekst', max_length=512, required=False))]), label='Bilder', min_num=1))], label='Bildegalleri')), ('horizontal_rule', wagtail.blocks.StructBlock([('color', wagtail.blocks.ChoiceBlock(choices=[('deepBrick', 'Dyp tegl'), ('neufPink', 'Griserosa'), ('goldenOrange', 'Gyllen oransje'), ('goldenBeige', 'Gyllen beige'), ('chateauBlue', 'Slottsblå')], label='Farge', required=False))], label='Skillelinje')), ('featured', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(label='Tittel', max_length=64, required=True)), ('text', wagtail.blocks.RichTextBlock(features=['bold', 'italic', 'link'], label='Tekst', required=True)), ('featured_page', wagtail.blocks.PageChooserBlock(header='Fremhevet side', required=True)), ('link_text', wagtail.blocks.CharBlock(default='Les mer', help_text='Lenketeksten som tar deg videre til siden. Tips: Ikke start med "Trykk her"', label='Lenketekst', max_length=64, required=True)), ('image_position', wagtail.blocks.ChoiceBlock(choices=[('left', 'Venstre'), ('right', 'Høyre')], label='Bildeplassering')), ('featured_image_override', wagtail.images.blocks.ImageChooserBlock(header='Overstyr bilde', help_text='Bildet som er tilknyttet undersiden du vil fremheve, vil automatisk brukes. Om det mangler eller du vil overstyre hvilket bilde som et brukes, kan du velge et her.', required=False))], label='Fremhevet underside')), ('page_section', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(label='Tittel', max_length=64, required=True)), ('background_color', wagtail.blocks.ChoiceBlock(choices=[('deepBrick', 'Dyp tegl'), ('neufPink', 'Griserosa'), ('goldenOrange', 'Gyllen oransje'), ('goldenBeige', 'Gyllen beige'), ('chateauBlue', 'Slottsblå')], label='Bakgrunnsfarge', required=False)), ('blocks', wagtail.blocks.StreamBlock([('paragraph', wagtail.blocks.RichTextBlock(label='Rik tekst')), ('image', wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(label='Bilde')), ('image_format', wagtail.blocks.ChoiceBlock(choices=[('fullwidth', 'Fullbredde'), ('bleed', 'Utfallende'), ('original', 'Uendret størrelse')], icon='cup', label='Bildeformat')), ('text', wagtail.blocks.CharBlock(label='Tekst', max_length=512, required=False))], label='Bilde')), ('image_slider', wagtail.blocks.StructBlock([('images', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(label='Bilde')), ('text', wagtail.blocks.CharBlock(label='Tekst', max_length=512, required=False))]), label='Bilder', min_num=1))], label='Bildegalleri')), ('horizontal_rule', wagtail.blocks.StructBlock([('color', wagtail.blocks.ChoiceBlock(choices=[('deepBrick', 'Dyp tegl'), ('neufPink', 'Griserosa'), ('goldenOrange', 'Gyllen oransje'), ('goldenBeige', 'Gyllen beige'), ('chateauBlue', 'Slottsblå')], label='Farge', required=False))], label='Skillelinje')), ('featured', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(label='Tittel', max_length=64, required=True)), ('text', wagtail.blocks.RichTextBlock(features=['bold', 'italic', 'link'], label='Tekst', required=True)), ('featured_page', wagtail.blocks.PageChooserBlock(header='Fremhevet side', required=True)), ('link_text', wagtail.blocks.CharBlock(default='Les mer', help_text='Lenketeksten som tar deg videre til siden. Tips: Ikke start med "Trykk her"', label='Lenketekst', max_length=64, required=True)), ('image_position', wagtail.blocks.ChoiceBlock(choices=[('left', 'Venstre'), ('right', 'Høyre')], label='Bildeplassering')), ('featured_image_override', wagtail.images.blocks.ImageChooserBlock(header='Overstyr bilde', help_text='Bildet som er tilknyttet undersiden du vil fremheve, vil automatisk brukes. Om det mangler eller du vil overstyre hvilket bilde som et brukes, kan du velge et her.', required=False))], label='Fremhevet underside'))]))], label='Seksjon'))])),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
migrations.AddField(
model_name='genericpage',
name='lead',
field=wagtail.fields.RichTextField(default=''),
preserve_default=False,
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 5.0.6 on 2024-06-21 00:44
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('generic', '0007_genericsectionedpage_genericpage_lead'),
]
operations = [
migrations.AlterField(
model_name='genericpage',
name='body',
field=wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock(label='Rik tekst')), ('image', wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(label='Bilde')), ('image_format', wagtail.blocks.ChoiceBlock(choices=[('fullwidth', 'Fullbredde'), ('bleed', 'Utfallende'), ('original', 'Uendret størrelse')], icon='cup', label='Bildeformat')), ('text', wagtail.blocks.CharBlock(label='Tekst', max_length=512, required=False))], label='Bilde')), ('image_slider', wagtail.blocks.StructBlock([('images', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(label='Bilde')), ('text', wagtail.blocks.CharBlock(label='Tekst', max_length=512, required=False))]), label='Bilder', min_num=1))], label='Bildegalleri')), ('horizontal_rule', wagtail.blocks.StructBlock([('color', wagtail.blocks.ChoiceBlock(choices=[('deepBrick', 'Dyp tegl'), ('neufPink', 'Griserosa'), ('goldenOrange', 'Gyllen oransje'), ('goldenBeige', 'Gyllen beige'), ('chateauBlue', 'Slottsblå')], label='Farge', required=False))], label='Skillelinje')), ('featured', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(label='Tittel', max_length=64, required=True)), ('text', wagtail.blocks.RichTextBlock(features=['bold', 'italic', 'link'], label='Tekst', required=True)), ('featured_page', wagtail.blocks.PageChooserBlock(header='Fremhevet side', required=True)), ('link_text', wagtail.blocks.CharBlock(default='Les mer', help_text='Lenketeksten som tar deg videre til siden. Tips: Ikke start med "Trykk her"', label='Lenketekst', max_length=64, required=True)), ('image_position', wagtail.blocks.ChoiceBlock(choices=[('left', 'Venstre'), ('right', 'Høyre')], label='Bildeplassering')), ('featured_image_override', wagtail.images.blocks.ImageChooserBlock(header='Overstyr bilde', help_text='Bildet som er tilknyttet undersiden du vil fremheve, vil automatisk brukes. Om det mangler eller du vil overstyre hvilket bilde som et brukes, kan du velge et her.', required=False))], label='Fremhevet underside')), ('page_section', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(label='Tittel', max_length=64, required=True)), ('background_color', wagtail.blocks.ChoiceBlock(choices=[('deepBrick', 'Dyp tegl'), ('neufPink', 'Griserosa'), ('goldenOrange', 'Gyllen oransje'), ('goldenBeige', 'Gyllen beige'), ('chateauBlue', 'Slottsblå')], label='Bakgrunnsfarge', required=False)), ('blocks', wagtail.blocks.StreamBlock([('paragraph', wagtail.blocks.RichTextBlock(label='Rik tekst')), ('image', wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(label='Bilde')), ('image_format', wagtail.blocks.ChoiceBlock(choices=[('fullwidth', 'Fullbredde'), ('bleed', 'Utfallende'), ('original', 'Uendret størrelse')], icon='cup', label='Bildeformat')), ('text', wagtail.blocks.CharBlock(label='Tekst', max_length=512, required=False))], label='Bilde')), ('image_slider', wagtail.blocks.StructBlock([('images', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(label='Bilde')), ('text', wagtail.blocks.CharBlock(label='Tekst', max_length=512, required=False))]), label='Bilder', min_num=1))], label='Bildegalleri')), ('horizontal_rule', wagtail.blocks.StructBlock([('color', wagtail.blocks.ChoiceBlock(choices=[('deepBrick', 'Dyp tegl'), ('neufPink', 'Griserosa'), ('goldenOrange', 'Gyllen oransje'), ('goldenBeige', 'Gyllen beige'), ('chateauBlue', 'Slottsblå')], label='Farge', required=False))], label='Skillelinje')), ('featured', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(label='Tittel', max_length=64, required=True)), ('text', wagtail.blocks.RichTextBlock(features=['bold', 'italic', 'link'], label='Tekst', required=True)), ('featured_page', wagtail.blocks.PageChooserBlock(header='Fremhevet side', required=True)), ('link_text', wagtail.blocks.CharBlock(default='Les mer', help_text='Lenketeksten som tar deg videre til siden. Tips: Ikke start med "Trykk her"', label='Lenketekst', max_length=64, required=True)), ('image_position', wagtail.blocks.ChoiceBlock(choices=[('left', 'Venstre'), ('right', 'Høyre')], label='Bildeplassering')), ('featured_image_override', wagtail.images.blocks.ImageChooserBlock(header='Overstyr bilde', help_text='Bildet som er tilknyttet undersiden du vil fremheve, vil automatisk brukes. Om det mangler eller du vil overstyre hvilket bilde som et brukes, kan du velge et her.', required=False))], label='Fremhevet underside'))]))], label='Seksjon'))]),
),
migrations.DeleteModel(
name='GenericSectionedPage',
),
]

View File

@ -0,0 +1,21 @@
# Generated by Django 5.0.6 on 2024-06-21 01:13
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('generic', '0008_alter_genericpage_body_delete_genericsectionedpage'),
]
operations = [
migrations.AlterField(
model_name='genericpage',
name='body',
field=wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock(label='Rik tekst')), ('image', wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(label='Bilde')), ('image_format', wagtail.blocks.ChoiceBlock(choices=[('fullwidth', 'Fullbredde'), ('bleed', 'Utfallende'), ('original', 'Uendret størrelse')], icon='cup', label='Bildeformat')), ('text', wagtail.blocks.CharBlock(label='Tekst', max_length=512, required=False))], label='Bilde')), ('image_slider', wagtail.blocks.StructBlock([('images', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(label='Bilde')), ('text', wagtail.blocks.CharBlock(label='Tekst', max_length=512, required=False))]), label='Bilder', min_num=1))], label='Bildegalleri')), ('horizontal_rule', wagtail.blocks.StructBlock([('color', wagtail.blocks.ChoiceBlock(choices=[('deepBrick', 'Dyp tegl'), ('neufPink', 'Griserosa'), ('goldenOrange', 'Gyllen oransje'), ('goldenBeige', 'Gyllen beige'), ('chateauBlue', 'Slottsblå')], label='Farge', required=False))], label='Skillelinje')), ('featured', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(label='Tittel', max_length=64, required=True)), ('text', wagtail.blocks.RichTextBlock(features=['bold', 'italic', 'link'], label='Tekst', required=True)), ('featured_page', wagtail.blocks.PageChooserBlock(header='Fremhevet side', required=True)), ('link_text', wagtail.blocks.CharBlock(default='Les mer', help_text='Lenketeksten som tar deg videre til siden. Tips: Ikke start med "Trykk her"', label='Lenketekst', max_length=64, required=True)), ('image_position', wagtail.blocks.ChoiceBlock(choices=[('left', 'Venstre'), ('right', 'Høyre')], label='Bildeplassering')), ('featured_image_override', wagtail.images.blocks.ImageChooserBlock(header='Overstyr bilde', help_text='Bildet som er tilknyttet undersiden du vil fremheve, vil automatisk brukes. Om det mangler eller du vil overstyre hvilket bilde som et brukes, kan du velge et her.', required=False))], label='Fremhevet underside')), ('page_section', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(label='Tittel', max_length=64, required=True)), ('background_color', wagtail.blocks.ChoiceBlock(choices=[('deepBrick', 'Dyp tegl'), ('neufPink', 'Griserosa'), ('goldenOrange', 'Gyllen oransje'), ('goldenBeige', 'Gyllen beige'), ('chateauBlue', 'Slottsblå')], label='Bakgrunnsfarge', required=False)), ('body', wagtail.blocks.StreamBlock([('paragraph', wagtail.blocks.RichTextBlock(label='Rik tekst')), ('image', wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(label='Bilde')), ('image_format', wagtail.blocks.ChoiceBlock(choices=[('fullwidth', 'Fullbredde'), ('bleed', 'Utfallende'), ('original', 'Uendret størrelse')], icon='cup', label='Bildeformat')), ('text', wagtail.blocks.CharBlock(label='Tekst', max_length=512, required=False))], label='Bilde')), ('image_slider', wagtail.blocks.StructBlock([('images', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(label='Bilde')), ('text', wagtail.blocks.CharBlock(label='Tekst', max_length=512, required=False))]), label='Bilder', min_num=1))], label='Bildegalleri')), ('horizontal_rule', wagtail.blocks.StructBlock([('color', wagtail.blocks.ChoiceBlock(choices=[('deepBrick', 'Dyp tegl'), ('neufPink', 'Griserosa'), ('goldenOrange', 'Gyllen oransje'), ('goldenBeige', 'Gyllen beige'), ('chateauBlue', 'Slottsblå')], label='Farge', required=False))], label='Skillelinje')), ('featured', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(label='Tittel', max_length=64, required=True)), ('text', wagtail.blocks.RichTextBlock(features=['bold', 'italic', 'link'], label='Tekst', required=True)), ('featured_page', wagtail.blocks.PageChooserBlock(header='Fremhevet side', required=True)), ('link_text', wagtail.blocks.CharBlock(default='Les mer', help_text='Lenketeksten som tar deg videre til siden. Tips: Ikke start med "Trykk her"', label='Lenketekst', max_length=64, required=True)), ('image_position', wagtail.blocks.ChoiceBlock(choices=[('left', 'Venstre'), ('right', 'Høyre')], label='Bildeplassering')), ('featured_image_override', wagtail.images.blocks.ImageChooserBlock(header='Overstyr bilde', help_text='Bildet som er tilknyttet undersiden du vil fremheve, vil automatisk brukes. Om det mangler eller du vil overstyre hvilket bilde som et brukes, kan du velge et her.', required=False))], label='Fremhevet underside'))]))], label='Seksjon'))]),
),
]

View File

@ -1,20 +1,25 @@
from grapple.models import GraphQLStreamfield from grapple.models import GraphQLRichText, GraphQLStreamfield
from wagtail.admin.panels import FieldPanel from wagtail.admin.panels import FieldPanel
from wagtail.fields import RichTextField, StreamField
from wagtail.models import Page from wagtail.models import Page
from dnscms.fields import CommonStreamField from dnscms.blocks import PageSectionBlock
from dnscms.fields import BASE_BLOCKS
class GenericPage(Page): class GenericPage(Page):
subpage_types = ["generic.GenericPage"] subpage_types = ["generic.GenericPage"]
show_in_menus = True show_in_menus = True
body = CommonStreamField lead = RichTextField(features=["bold", "italic", "link"])
body = StreamField(BASE_BLOCKS + [("page_section", PageSectionBlock(label="Seksjon"))])
content_panels = Page.content_panels + [ content_panels = Page.content_panels + [
FieldPanel("lead", heading="Leder"),
FieldPanel("body", heading="Innhold"), FieldPanel("body", heading="Innhold"),
] ]
graphql_fields = [ graphql_fields = [
GraphQLRichText("lead"),
GraphQLStreamfield("body"), GraphQLStreamfield("body"),
] ]

52
web/package-lock.json generated
View File

@ -11,6 +11,7 @@
"@graphql-codegen/cli": "^5.0.2", "@graphql-codegen/cli": "^5.0.2",
"@graphql-codegen/client-preset": "^4.3.0", "@graphql-codegen/client-preset": "^4.3.0",
"@parcel/watcher": "^2.4.1", "@parcel/watcher": "^2.4.1",
"@sindresorhus/slugify": "^2.2.1",
"@urql/next": "^1.1.1", "@urql/next": "^1.1.1",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"date-fns-tz": "^3.1.3", "date-fns-tz": "^3.1.3",
@ -2661,6 +2662,57 @@
"integrity": "sha512-hw437iINopmQuxWPSUEvqE56NCPsiU8N4AYtfHmJFckclktzK9YQJieD3XkDCDH4OjL+C7zgPUh73R/nrcHrqw==", "integrity": "sha512-hw437iINopmQuxWPSUEvqE56NCPsiU8N4AYtfHmJFckclktzK9YQJieD3XkDCDH4OjL+C7zgPUh73R/nrcHrqw==",
"dev": true "dev": true
}, },
"node_modules/@sindresorhus/slugify": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz",
"integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==",
"dependencies": {
"@sindresorhus/transliterate": "^1.0.0",
"escape-string-regexp": "^5.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@sindresorhus/slugify/node_modules/escape-string-regexp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@sindresorhus/transliterate": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz",
"integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==",
"dependencies": {
"escape-string-regexp": "^5.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@sindresorhus/transliterate/node_modules/escape-string-regexp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@swc/counter": { "node_modules/@swc/counter": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",

View File

@ -13,6 +13,7 @@
"@graphql-codegen/cli": "^5.0.2", "@graphql-codegen/cli": "^5.0.2",
"@graphql-codegen/client-preset": "^4.3.0", "@graphql-codegen/client-preset": "^4.3.0",
"@parcel/watcher": "^2.4.1", "@parcel/watcher": "^2.4.1",
"@sindresorhus/slugify": "^2.2.1",
"@urql/next": "^1.1.1", "@urql/next": "^1.1.1",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"date-fns-tz": "^3.1.3", "date-fns-tz": "^3.1.3",

View File

@ -3,6 +3,7 @@ import { GenericFragment } from "@/gql/graphql";
import { getClient } from "@/app/client"; import { getClient } from "@/app/client";
import { Blocks } from "@/components/blocks/Blocks"; import { Blocks } from "@/components/blocks/Blocks";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { PageHeader } from "@/components/general/PageHeader";
export const dynamicParams = false; export const dynamicParams = false;
@ -12,6 +13,7 @@ const GenericFragmentDefinition = graphql(`
id id
urlPath urlPath
title title
lead
body { body {
...Blocks ...Blocks
} }
@ -73,9 +75,10 @@ export default async function Page({ params }: { params: { url: string[] } }) {
return ( return (
<main className="site-main" id="main"> <main className="site-main" id="main">
<section className="page-header"> <PageHeader
<h1>{page.title}</h1> heading={page.title}
</section> lead={page.lead}
/>
<Blocks blocks={page.body} /> <Blocks blocks={page.body} />
</main> </main>
); );

View File

@ -4,7 +4,7 @@ import { getClient } from "@/app/client";
import Link from "next/link"; import Link from "next/link";
import { PageHeader } from "@/components/general/PageHeader"; import { PageHeader } from "@/components/general/PageHeader";
import { IconListBlock } from "@/components/blocks/IconListBlock"; import { IconListBlock } from "@/components/blocks/IconListBlock";
import { PageSection } from "@/components/blocks/PageSection"; import { PageSectionBlock } from "@/components/blocks/PageSection";
export default async function Page() { export default async function Page() {
return ( return (
@ -61,12 +61,11 @@ export default async function Page() {
</li> </li>
</ul> </ul>
</div> </div>
<PageSection <PageSectionBlock
heading="Adkomst" block={{ title: "Adkomst" } as any}
subheading="Hvordan kommer man seg til Neuf?" // subheading="Hvordan kommer man seg til Neuf?"
/> />
<PageSection heading="Åpningstider" /> <PageSectionBlock block={{ title: "Åpningstider" } as any} />
<PageSection />
<section className="pageSection" id="adkomst"> <section className="pageSection" id="adkomst">
<h1>Adkomst</h1> <h1>Adkomst</h1>
<div className="pageSectionGroup"> <div className="pageSectionGroup">

View File

@ -3,9 +3,15 @@ import { ImageWithTextBlock } from "./ImageWithTextBlock";
import { ImageSliderBlock } from "./ImageSliderBlock"; import { ImageSliderBlock } from "./ImageSliderBlock";
import { HorizontalRuleBlock } from "./HorizontalRuleBlock"; import { HorizontalRuleBlock } from "./HorizontalRuleBlock";
import { FeaturedBlock } from "./FeaturedBlock"; import { FeaturedBlock } from "./FeaturedBlock";
import { PageSectionBlock, PageSectionNavigationBlock } from "./PageSection";
export const Blocks = ({ blocks }: any) => { export const Blocks = ({ blocks }: any) => {
const sections = blocks.filter(
(block: any) => block.__typename === "PageSectionBlock"
);
return blocks.map((block: any) => { return blocks.map((block: any) => {
console.log("block aaa", block);
switch (block.blockType) { switch (block.blockType) {
case "RichTextBlock": case "RichTextBlock":
return <RichTextBlock block={block} />; return <RichTextBlock block={block} />;
@ -22,6 +28,12 @@ export const Blocks = ({ blocks }: any) => {
case "FeaturedBlock": case "FeaturedBlock":
return <FeaturedBlock block={block} />; return <FeaturedBlock block={block} />;
break; break;
case "PageSectionBlock":
return <PageSectionBlock block={block} />;
break;
case "PageSectionNavigationBlock":
return <PageSectionNavigationBlock sections={sections} />;
break;
default: default:
return <div>Unsupported block type {block.blockType}</div>; return <div>Unsupported block type {block.blockType}</div>;
console.log("unsupported block", block); console.log("unsupported block", block);

View File

@ -1,14 +1,55 @@
import { PageSectionBlock as PageSectionBlockType } from "@/gql/graphql";
import styles from "./pageSection.module.scss"; import styles from "./pageSection.module.scss";
import { Blocks } from "./Blocks";
import slugify from "@sindresorhus/slugify";
export const PageSectionBlock = ({
block,
}: {
block: PageSectionBlockType;
}) => {
// TODO: add icon selection to model
// TODO: there's a block.backgroundColor
const anchor = slugify(block.title);
export const PageSection = ({ heading }: any) => {
return ( return (
<section className={styles.pageSection}> <section className={styles.pageSection} id={anchor}>
<div className={styles.sectionHeader}> <div className={styles.sectionHeader}>
<div className={styles.icon}> <div className={styles.icon}>
<img src="/assets/icons/neufneuf.svg" /> <img src="/assets/icons/neufneuf.svg" />
</div> </div>
<h1>{heading}</h1> <h1>{block.title}</h1>
</div> </div>
<Blocks blocks={block.body} />
</section> </section>
); );
}; };
export const PageSectionNavigationBlock = ({
sections,
}: {
sections: PageSectionBlockType[];
}) => {
if (!sections.length) {
return <></>;
}
return (
<div className="anchorLinks">
<span className="suphead">Hopp til</span>
<ul>
{sections.map((section) => {
const anchor = slugify(section.title);
return (
<li key={anchor}>
<a href={`#${anchor}`} className="button">
{section.title}
</a>
</li>
);
})}
</ul>
</div>
);
};

View File

@ -10,7 +10,9 @@ export const PageHeader = ({
return ( return (
<div className={styles.pageHeader}> <div className={styles.pageHeader}>
<h1 className={styles.title}>{heading}</h1> <h1 className={styles.title}>{heading}</h1>
{lead && <p className="lead">{lead}</p>} {lead && (
<p className="lead" dangerouslySetInnerHTML={{ __html: lead }} />
)}
</div> </div>
); );
}; };

View File

@ -13,7 +13,7 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/
* Therefore it is highly recommended to use the babel or swc plugin for production. * Therefore it is highly recommended to use the babel or swc plugin for production.
*/ */
const documents = { const documents = {
"\n fragment Generic on GenericPage {\n __typename\n id\n urlPath\n title\n body {\n ...Blocks\n }\n }\n": types.GenericFragmentDoc, "\n fragment Generic on GenericPage {\n __typename\n id\n urlPath\n title\n lead\n body {\n ...Blocks\n }\n }\n": types.GenericFragmentDoc,
"\n query allGenericSlugs {\n pages(contentType: \"generic.GenericPage\") {\n id\n urlPath\n }\n }\n ": types.AllGenericSlugsDocument, "\n query allGenericSlugs {\n pages(contentType: \"generic.GenericPage\") {\n id\n urlPath\n }\n }\n ": types.AllGenericSlugsDocument,
"\n query genericPageByUrl($urlPath: String!) {\n page: page(contentType: \"generic.GenericPage\", urlPath: $urlPath) {\n ... on GenericPage {\n ...Generic\n }\n }\n }\n ": types.GenericPageByUrlDocument, "\n query genericPageByUrl($urlPath: String!) {\n page: page(contentType: \"generic.GenericPage\", urlPath: $urlPath) {\n ... on GenericPage {\n ...Generic\n }\n }\n }\n ": types.GenericPageByUrlDocument,
"\n query allNewsSlugs {\n pages(contentType: \"news.NewsPage\") {\n id\n slug\n }\n }\n ": types.AllNewsSlugsDocument, "\n query allNewsSlugs {\n pages(contentType: \"news.NewsPage\") {\n id\n slug\n }\n }\n ": types.AllNewsSlugsDocument,
@ -31,7 +31,13 @@ const documents = {
"\n query allVenues {\n venues: pages(contentType: \"venues.VenuePage\") {\n ... on VenuePage {\n ...Venue\n }\n }\n }\n ": types.AllVenuesDocument, "\n query allVenues {\n venues: pages(contentType: \"venues.VenuePage\") {\n ... on VenuePage {\n ...Venue\n }\n }\n }\n ": types.AllVenuesDocument,
"\n fragment Home on HomePage {\n ... on HomePage {\n featuredEvents {\n id\n }\n }\n }\n": types.HomeFragmentDoc, "\n fragment Home on HomePage {\n ... on HomePage {\n featuredEvents {\n id\n }\n }\n }\n": types.HomeFragmentDoc,
"\n query home {\n events: eventIndex {\n ... on EventIndex {\n futureEvents {\n ... on EventPage {\n ...Event\n }\n }\n }\n }\n home: page(contentType: \"home.HomePage\", urlPath: \"/home/\") {\n ... on HomePage {\n ...Home\n }\n }\n news: pages(contentType: \"news.newsPage\", limit: 3) {\n ... on NewsPage {\n ...News\n }\n }\n }\n ": types.HomeDocument, "\n query home {\n events: eventIndex {\n ... on EventIndex {\n futureEvents {\n ... on EventPage {\n ...Event\n }\n }\n }\n }\n home: page(contentType: \"home.HomePage\", urlPath: \"/home/\") {\n ... on HomePage {\n ...Home\n }\n }\n news: pages(contentType: \"news.newsPage\", limit: 3) {\n ... on NewsPage {\n ...News\n }\n }\n }\n ": types.HomeDocument,
"\n fragment Blocks on StreamFieldInterface {\n id\n blockType\n field\n ... on RichTextBlock {\n rawValue\n value\n }\n ... on ImageWithTextBlock {\n image {\n ...Image\n }\n imageFormat\n text\n }\n ... on ImageSliderBlock {\n images {\n ... on ImageSliderItemBlock {\n image {\n ...Image\n }\n text\n }\n }\n }\n ... on HorizontalRuleBlock {\n color\n }\n ... on FeaturedBlock {\n title\n featuredBlockText: text\n linkText\n imagePosition\n featuredPage {\n contentType\n pageType\n url\n ... on EventPage {\n featuredImage {\n ...Image\n }\n }\n ... on NewsPage {\n featuredImage {\n ...Image\n }\n }\n }\n featuredImageOverride {\n ...Image\n }\n }\n }\n": types.BlocksFragmentDoc, "\n fragment RichTextBlock on RichTextBlock {\n rawValue\n value\n }\n": types.RichTextBlockFragmentDoc,
"\n fragment ImageWithTextBlock on ImageWithTextBlock {\n image {\n ...Image\n }\n imageFormat\n text\n }\n": types.ImageWithTextBlockFragmentDoc,
"\n fragment ImageSliderBlock on ImageSliderBlock {\n images {\n ... on ImageSliderItemBlock {\n image {\n ...Image\n }\n text\n }\n }\n }\n": types.ImageSliderBlockFragmentDoc,
"\n fragment HorizontalRuleBlock on HorizontalRuleBlock {\n color\n }\n": types.HorizontalRuleBlockFragmentDoc,
"\n fragment FeaturedBlock on FeaturedBlock {\n title\n featuredBlockText: text\n linkText\n imagePosition\n featuredPage {\n contentType\n pageType\n url\n ... on EventPage {\n featuredImage {\n ...Image\n }\n }\n ... on NewsPage {\n featuredImage {\n ...Image\n }\n }\n }\n featuredImageOverride {\n ...Image\n }\n }\n": types.FeaturedBlockFragmentDoc,
"\n fragment OneLevelOfBlocks on StreamFieldInterface {\n id\n blockType\n field\n ... on RichTextBlock {\n ...RichTextBlock\n }\n ... on ImageWithTextBlock {\n ...ImageWithTextBlock\n }\n ... on ImageSliderBlock {\n ...ImageSliderBlock\n }\n ... on HorizontalRuleBlock {\n ...HorizontalRuleBlock\n }\n ... on FeaturedBlock {\n ...FeaturedBlock\n }\n }\n": types.OneLevelOfBlocksFragmentDoc,
"\n fragment Blocks on StreamFieldInterface {\n ... on PageSectionBlock {\n title\n backgroundColor\n body {\n ...OneLevelOfBlocks\n }\n }\n ...OneLevelOfBlocks\n\n }\n": types.BlocksFragmentDoc,
"\n fragment Image on CustomImage {\n id\n url\n width\n height\n alt\n attribution\n }\n": types.ImageFragmentDoc, "\n fragment Image on CustomImage {\n id\n url\n width\n height\n alt\n attribution\n }\n": types.ImageFragmentDoc,
"\n fragment Event on EventPage {\n __typename\n id\n slug\n title\n body {\n id\n blockType\n field\n ... on RichTextBlock {\n rawValue\n value\n }\n }\n featuredImage {\n ...Image\n }\n pig\n facebookUrl\n ticketUrl\n free\n priceRegular\n priceMember\n priceStudent\n categories {\n ... on EventCategory {\n name\n slug\n pig\n }\n }\n occurrences {\n ... on EventOccurrence {\n __typename\n id\n start\n end\n venue {\n __typename\n id\n slug\n title\n preposition\n url\n }\n }\n }\n organizers {\n ... on EventOrganizer {\n id\n name\n slug\n externalUrl\n association {\n ... on AssociationPage {\n url\n }\n }\n }\n }\n }\n": types.EventFragmentDoc, "\n fragment Event on EventPage {\n __typename\n id\n slug\n title\n body {\n id\n blockType\n field\n ... on RichTextBlock {\n rawValue\n value\n }\n }\n featuredImage {\n ...Image\n }\n pig\n facebookUrl\n ticketUrl\n free\n priceRegular\n priceMember\n priceStudent\n categories {\n ... on EventCategory {\n name\n slug\n pig\n }\n }\n occurrences {\n ... on EventOccurrence {\n __typename\n id\n start\n end\n venue {\n __typename\n id\n slug\n title\n preposition\n url\n }\n }\n }\n organizers {\n ... on EventOrganizer {\n id\n name\n slug\n externalUrl\n association {\n ... on AssociationPage {\n url\n }\n }\n }\n }\n }\n": types.EventFragmentDoc,
"\n query futureEvents {\n events: eventIndex {\n ... on EventIndex {\n futureEvents {\n ... on EventPage {\n ...Event\n }\n }\n }\n }\n eventCategories: eventCategories {\n ... on EventCategory {\n name\n slug\n showInFilters\n }\n }\n eventOrganizers: eventOrganizers {\n ... on EventOrganizer {\n id\n name\n slug\n externalUrl\n association {\n ... on AssociationPage {\n url\n }\n }\n }\n }\n }\n": types.FutureEventsDocument, "\n query futureEvents {\n events: eventIndex {\n ... on EventIndex {\n futureEvents {\n ... on EventPage {\n ...Event\n }\n }\n }\n }\n eventCategories: eventCategories {\n ... on EventCategory {\n name\n slug\n showInFilters\n }\n }\n eventOrganizers: eventOrganizers {\n ... on EventOrganizer {\n id\n name\n slug\n externalUrl\n association {\n ... on AssociationPage {\n url\n }\n }\n }\n }\n }\n": types.FutureEventsDocument,
@ -57,7 +63,7 @@ export function graphql(source: string): unknown;
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
export function graphql(source: "\n fragment Generic on GenericPage {\n __typename\n id\n urlPath\n title\n body {\n ...Blocks\n }\n }\n"): (typeof documents)["\n fragment Generic on GenericPage {\n __typename\n id\n urlPath\n title\n body {\n ...Blocks\n }\n }\n"]; export function graphql(source: "\n fragment Generic on GenericPage {\n __typename\n id\n urlPath\n title\n lead\n body {\n ...Blocks\n }\n }\n"): (typeof documents)["\n fragment Generic on GenericPage {\n __typename\n id\n urlPath\n title\n lead\n body {\n ...Blocks\n }\n }\n"];
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
@ -129,7 +135,31 @@ export function graphql(source: "\n query home {\n events: eventIndex {\
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
export function graphql(source: "\n fragment Blocks on StreamFieldInterface {\n id\n blockType\n field\n ... on RichTextBlock {\n rawValue\n value\n }\n ... on ImageWithTextBlock {\n image {\n ...Image\n }\n imageFormat\n text\n }\n ... on ImageSliderBlock {\n images {\n ... on ImageSliderItemBlock {\n image {\n ...Image\n }\n text\n }\n }\n }\n ... on HorizontalRuleBlock {\n color\n }\n ... on FeaturedBlock {\n title\n featuredBlockText: text\n linkText\n imagePosition\n featuredPage {\n contentType\n pageType\n url\n ... on EventPage {\n featuredImage {\n ...Image\n }\n }\n ... on NewsPage {\n featuredImage {\n ...Image\n }\n }\n }\n featuredImageOverride {\n ...Image\n }\n }\n }\n"): (typeof documents)["\n fragment Blocks on StreamFieldInterface {\n id\n blockType\n field\n ... on RichTextBlock {\n rawValue\n value\n }\n ... on ImageWithTextBlock {\n image {\n ...Image\n }\n imageFormat\n text\n }\n ... on ImageSliderBlock {\n images {\n ... on ImageSliderItemBlock {\n image {\n ...Image\n }\n text\n }\n }\n }\n ... on HorizontalRuleBlock {\n color\n }\n ... on FeaturedBlock {\n title\n featuredBlockText: text\n linkText\n imagePosition\n featuredPage {\n contentType\n pageType\n url\n ... on EventPage {\n featuredImage {\n ...Image\n }\n }\n ... on NewsPage {\n featuredImage {\n ...Image\n }\n }\n }\n featuredImageOverride {\n ...Image\n }\n }\n }\n"]; export function graphql(source: "\n fragment RichTextBlock on RichTextBlock {\n rawValue\n value\n }\n"): (typeof documents)["\n fragment RichTextBlock on RichTextBlock {\n rawValue\n value\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment ImageWithTextBlock on ImageWithTextBlock {\n image {\n ...Image\n }\n imageFormat\n text\n }\n"): (typeof documents)["\n fragment ImageWithTextBlock on ImageWithTextBlock {\n image {\n ...Image\n }\n imageFormat\n text\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment ImageSliderBlock on ImageSliderBlock {\n images {\n ... on ImageSliderItemBlock {\n image {\n ...Image\n }\n text\n }\n }\n }\n"): (typeof documents)["\n fragment ImageSliderBlock on ImageSliderBlock {\n images {\n ... on ImageSliderItemBlock {\n image {\n ...Image\n }\n text\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment HorizontalRuleBlock on HorizontalRuleBlock {\n color\n }\n"): (typeof documents)["\n fragment HorizontalRuleBlock on HorizontalRuleBlock {\n color\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment FeaturedBlock on FeaturedBlock {\n title\n featuredBlockText: text\n linkText\n imagePosition\n featuredPage {\n contentType\n pageType\n url\n ... on EventPage {\n featuredImage {\n ...Image\n }\n }\n ... on NewsPage {\n featuredImage {\n ...Image\n }\n }\n }\n featuredImageOverride {\n ...Image\n }\n }\n"): (typeof documents)["\n fragment FeaturedBlock on FeaturedBlock {\n title\n featuredBlockText: text\n linkText\n imagePosition\n featuredPage {\n contentType\n pageType\n url\n ... on EventPage {\n featuredImage {\n ...Image\n }\n }\n ... on NewsPage {\n featuredImage {\n ...Image\n }\n }\n }\n featuredImageOverride {\n ...Image\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment OneLevelOfBlocks on StreamFieldInterface {\n id\n blockType\n field\n ... on RichTextBlock {\n ...RichTextBlock\n }\n ... on ImageWithTextBlock {\n ...ImageWithTextBlock\n }\n ... on ImageSliderBlock {\n ...ImageSliderBlock\n }\n ... on HorizontalRuleBlock {\n ...HorizontalRuleBlock\n }\n ... on FeaturedBlock {\n ...FeaturedBlock\n }\n }\n"): (typeof documents)["\n fragment OneLevelOfBlocks on StreamFieldInterface {\n id\n blockType\n field\n ... on RichTextBlock {\n ...RichTextBlock\n }\n ... on ImageWithTextBlock {\n ...ImageWithTextBlock\n }\n ... on ImageSliderBlock {\n ...ImageSliderBlock\n }\n ... on HorizontalRuleBlock {\n ...HorizontalRuleBlock\n }\n ... on FeaturedBlock {\n ...FeaturedBlock\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment Blocks on StreamFieldInterface {\n ... on PageSectionBlock {\n title\n backgroundColor\n body {\n ...OneLevelOfBlocks\n }\n }\n ...OneLevelOfBlocks\n\n }\n"): (typeof documents)["\n fragment Blocks on StreamFieldInterface {\n ... on PageSectionBlock {\n title\n backgroundColor\n body {\n ...OneLevelOfBlocks\n }\n }\n ...OneLevelOfBlocks\n\n }\n"];
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */

File diff suppressed because one or more lines are too long

View File

@ -37,8 +37,8 @@ export function unique<T>(array: any[]): any[] {
return Array.from(array.reduce((set, item) => set.add(item), new Set())); return Array.from(array.reduce((set, item) => set.add(item), new Set()));
} }
const BlockFragmentDefinition = graphql(` const OneLevelOfBlocksFragmentDefinition = graphql(`
fragment Blocks on StreamFieldInterface { fragment OneLevelOfBlocks on StreamFieldInterface {
id id
blockType blockType
field field
@ -93,6 +93,19 @@ const BlockFragmentDefinition = graphql(`
} }
`); `);
const BlockFragmentDefinition = graphql(`
fragment Blocks on StreamFieldInterface {
... on PageSectionBlock {
title
backgroundColor
body {
...OneLevelOfBlocks
}
}
...OneLevelOfBlocks
}
`);
const ImageFragmentDefinition = graphql(` const ImageFragmentDefinition = graphql(`
fragment Image on CustomImage { fragment Image on CustomImage {
id id