mirror of
https://github.com/boostorg/website-v2.git
synced 2026-01-19 04:42:17 +00:00
Integrate Wagtail CMS and set up landing page structure (#2014)
This commit is contained in:
@@ -89,6 +89,24 @@ INSTALLED_APPS += [
|
||||
"widget_tweaks",
|
||||
]
|
||||
|
||||
# Wagtail Apps
|
||||
INSTALLED_APPS += [
|
||||
"wagtail.contrib.forms",
|
||||
"wagtail.contrib.redirects",
|
||||
"wagtail.embeds",
|
||||
"wagtail.sites",
|
||||
"wagtail.users",
|
||||
"wagtail.snippets",
|
||||
"wagtail.documents",
|
||||
"wagtail.images",
|
||||
"wagtail.search",
|
||||
"wagtail.admin",
|
||||
"wagtail",
|
||||
"wagtailmarkdown",
|
||||
"modelcluster",
|
||||
"taggit",
|
||||
]
|
||||
|
||||
# Our Apps
|
||||
INSTALLED_APPS += [
|
||||
"ak",
|
||||
@@ -124,6 +142,7 @@ MIDDLEWARE = [
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"allauth.account.middleware.AccountMiddleware",
|
||||
"oauth2_provider.middleware.OAuth2TokenMiddleware",
|
||||
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
|
||||
]
|
||||
|
||||
if DEBUG:
|
||||
@@ -592,3 +611,31 @@ ALGOLIA = {
|
||||
"api_key": env("ALGOLIA_API_KEY", None),
|
||||
"region": env("ALGOLIA_APP_REGION", "us"),
|
||||
}
|
||||
|
||||
# Required by Wagtail
|
||||
DATA_UPLOAD_MAX_NUMBER_FIELDS = 10_000
|
||||
WAGTAIL_SITE_NAME = "Boost.org"
|
||||
WAGTAILADMIN_BASE_URL = env("WAGTAILADMIN_BASE_URL", default="https://www.boost.org/")
|
||||
WAGTAILDOCS_EXTENSIONS = [
|
||||
"csv",
|
||||
"docx",
|
||||
"key",
|
||||
"odt",
|
||||
"pdf",
|
||||
"pptx",
|
||||
"rtf",
|
||||
"txt",
|
||||
"xlsx",
|
||||
"zip",
|
||||
]
|
||||
WAGTAILMARKDOWN = {
|
||||
"autodownload_fontawesome": True,
|
||||
"allowed_tags": [], # optional. a list of HTML tags. e.g. ['div', 'p', 'a']
|
||||
"allowed_styles": [], # optional. a list of styles
|
||||
"allowed_attributes": {}, # optional. a dict with HTML tag as key and a list of attributes as value
|
||||
"allowed_settings_mode": "extend", # optional. Possible values: "extend" or "override". Defaults to "extend".
|
||||
"extensions": [], # optional. a list of python-markdown supported extensions
|
||||
"extension_configs": {}, # optional. a dictionary with the extension name as key, and its configuration as value
|
||||
"extensions_settings_mode": "extend", # optional. Possible values: "extend" or "override". Defaults to "extend".
|
||||
"tab_length": 4, # optional. Sets the length of tabs used by python-markdown to render the output. This is the number of spaces used to replace with a tab character. Defaults to 4.
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ from django.urls import include, path, re_path, register_converter, reverse_lazy
|
||||
from django.views.generic import TemplateView
|
||||
from django.views.generic.base import RedirectView
|
||||
from rest_framework import routers
|
||||
from wagtail import urls as wagtail_urls
|
||||
from wagtail.admin import urls as wagtailadmin_urls
|
||||
from wagtail.documents import urls as wagtaildocs_urls
|
||||
|
||||
from ak.views import (
|
||||
ForbiddenView,
|
||||
@@ -402,6 +405,10 @@ urlpatterns = (
|
||||
ModernizedDocsView.as_view(),
|
||||
name="modernized_docs",
|
||||
),
|
||||
# Wagtail stuff
|
||||
path("cms/", include(wagtailadmin_urls)),
|
||||
path("documents/", include(wagtaildocs_urls)),
|
||||
path("outreach/", include(wagtail_urls)),
|
||||
]
|
||||
+ [
|
||||
path(
|
||||
|
||||
@@ -201,6 +201,8 @@ Env:
|
||||
# postgres caching of s3 text file content
|
||||
- name: ENABLE_DB_CACHE
|
||||
value: "true"
|
||||
- name: WAGTAILADMIN_BASE_URL
|
||||
value: https://www.cppal-dev.boost.org/
|
||||
|
||||
# Volumes
|
||||
Volumes:
|
||||
|
||||
@@ -201,6 +201,8 @@ Env:
|
||||
# postgres caching of s3 text file content
|
||||
- name: ENABLE_DB_CACHE
|
||||
value: "true"
|
||||
- name: WAGTAILADMIN_BASE_URL
|
||||
value: https://www.boost.org/
|
||||
|
||||
# Volumes
|
||||
Volumes:
|
||||
|
||||
@@ -201,6 +201,8 @@ Env:
|
||||
# postgres caching of s3 text file content
|
||||
- name: ENABLE_DB_CACHE
|
||||
value: "true"
|
||||
- name: WAGTAILADMIN_BASE_URL
|
||||
value: https://www.stage.boost.org/
|
||||
|
||||
# Volumes
|
||||
Volumes:
|
||||
|
||||
@@ -6,4 +6,4 @@ from marketing.models import CapturedEmail
|
||||
@admin.register(CapturedEmail)
|
||||
class CapturedEmailAdmin(admin.ModelAdmin):
|
||||
model = CapturedEmail
|
||||
list_display = ("email", "referrer", "page_slug")
|
||||
list_display = ("email", "referrer", "page")
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
from django import forms
|
||||
|
||||
from .models import CapturedEmail
|
||||
|
||||
|
||||
class CapturedEmailForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = CapturedEmail
|
||||
fields = ["email"]
|
||||
widgets = {
|
||||
"email": forms.EmailInput(
|
||||
attrs={
|
||||
"placeholder": "your@email.com",
|
||||
"autocomplete": "email",
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-17 21:27
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import wagtail.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("marketing", "0001_initial"),
|
||||
("wagtailcore", "0095_groupsitepermission"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="DetailPage",
|
||||
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",
|
||||
),
|
||||
),
|
||||
(
|
||||
"email_capture_intro",
|
||||
models.TextField(
|
||||
default="Drop your email below to get engineering updates."
|
||||
),
|
||||
),
|
||||
(
|
||||
"privacy_blurb",
|
||||
models.TextField(
|
||||
default="Privacy: no spam, one step unsubscribe. We'll only send high-signal dev content re this and other Boost libraries."
|
||||
),
|
||||
),
|
||||
(
|
||||
"body",
|
||||
wagtail.fields.StreamField(
|
||||
[("rich", 0), ("md", 1)],
|
||||
blank=True,
|
||||
block_lookup={
|
||||
0: (
|
||||
"wagtail.blocks.RichTextBlock",
|
||||
(),
|
||||
{
|
||||
"features": [
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"bold",
|
||||
"italic",
|
||||
"link",
|
||||
"ol",
|
||||
"ul",
|
||||
"code",
|
||||
"blockquote",
|
||||
],
|
||||
"label": "Rich text",
|
||||
},
|
||||
),
|
||||
1: (
|
||||
"wagtailmarkdown.blocks.MarkdownBlock",
|
||||
(),
|
||||
{"label": "Markdown"},
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
bases=("wagtailcore.page",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="OutreachHomePage",
|
||||
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",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
bases=("wagtailcore.page",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ProgramPage",
|
||||
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",
|
||||
),
|
||||
),
|
||||
(
|
||||
"email_capture_intro",
|
||||
models.TextField(
|
||||
default="Drop your email below to get engineering updates."
|
||||
),
|
||||
),
|
||||
(
|
||||
"privacy_blurb",
|
||||
models.TextField(
|
||||
default="Privacy: no spam, one step unsubscribe. We'll only send high-signal dev content re this and other Boost libraries."
|
||||
),
|
||||
),
|
||||
(
|
||||
"body",
|
||||
wagtail.fields.StreamField(
|
||||
[("rich", 0), ("md", 1)],
|
||||
blank=True,
|
||||
block_lookup={
|
||||
0: (
|
||||
"wagtail.blocks.RichTextBlock",
|
||||
(),
|
||||
{
|
||||
"features": [
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"bold",
|
||||
"italic",
|
||||
"link",
|
||||
"ol",
|
||||
"ul",
|
||||
"code",
|
||||
"blockquote",
|
||||
],
|
||||
"label": "Rich text",
|
||||
},
|
||||
),
|
||||
1: (
|
||||
"wagtailmarkdown.blocks.MarkdownBlock",
|
||||
(),
|
||||
{"label": "Markdown"},
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
bases=("wagtailcore.page",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ProgramPageIndex",
|
||||
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",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
bases=("wagtailcore.page",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="TopicPage",
|
||||
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",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Topic",
|
||||
},
|
||||
bases=("wagtailcore.page",),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="capturedemail",
|
||||
name="page_slug",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="capturedemail",
|
||||
name="created_at",
|
||||
field=models.DateTimeField(
|
||||
auto_now_add=True, default=django.utils.timezone.now
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="capturedemail",
|
||||
name="page",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="captured_emails",
|
||||
to="wagtailcore.page",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,13 +1,210 @@
|
||||
from django import forms
|
||||
from django.contrib import messages
|
||||
from django.db import models
|
||||
from django.http import Http404
|
||||
from django.shortcuts import render, redirect
|
||||
from wagtail.admin.panels import FieldPanel
|
||||
from wagtail.blocks import RichTextBlock
|
||||
from wagtail.fields import StreamField
|
||||
from wagtail.models import Page
|
||||
from wagtail.url_routing import RouteResult
|
||||
from wagtailmarkdown.blocks import MarkdownBlock
|
||||
|
||||
RICH_TEXT_FEATURES = [
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"bold",
|
||||
"italic",
|
||||
"link",
|
||||
"ol",
|
||||
"ul",
|
||||
"code",
|
||||
"blockquote",
|
||||
]
|
||||
|
||||
|
||||
class CapturedEmail(models.Model):
|
||||
email = models.EmailField()
|
||||
referrer = models.CharField(blank=True, default="")
|
||||
page_slug = models.CharField(blank=True, default="")
|
||||
page = models.ForeignKey(
|
||||
Page,
|
||||
related_name="captured_emails",
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
default=None,
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.email
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__} ({self.pk}): {self}>"
|
||||
|
||||
|
||||
class CapturedEmailForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = CapturedEmail
|
||||
fields = ["email"]
|
||||
widgets = {
|
||||
"email": forms.EmailInput(
|
||||
attrs={"placeholder": "your@email.com", "autocomplete": "email"}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
class EmailCapturePage(Page):
|
||||
"""Abstract page with reusable logic for pages that capture an email."""
|
||||
|
||||
email_capture_intro = models.TextField(
|
||||
default="Drop your email below to get engineering updates."
|
||||
)
|
||||
privacy_blurb = models.TextField(
|
||||
default="Privacy: no spam, one step unsubscribe. We'll only send high-signal dev content re this and other Boost libraries."
|
||||
)
|
||||
body = StreamField(
|
||||
[
|
||||
("rich", RichTextBlock(features=RICH_TEXT_FEATURES, label="Rich text")),
|
||||
("md", MarkdownBlock(label="Markdown")),
|
||||
],
|
||||
use_json_field=True,
|
||||
blank=True,
|
||||
)
|
||||
content_panels = Page.content_panels + [
|
||||
FieldPanel("email_capture_intro"),
|
||||
FieldPanel("privacy_blurb"),
|
||||
FieldPanel("body"),
|
||||
]
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def get_referrer(self, request):
|
||||
original = request.session.get("original_referrer", "")
|
||||
return original or request.headers.get("referer", "")
|
||||
|
||||
def build_form(self, request) -> CapturedEmailForm:
|
||||
"""Create a form instance appropriate to the request method."""
|
||||
if request.method == "POST":
|
||||
return CapturedEmailForm(data=request.POST)
|
||||
return CapturedEmailForm()
|
||||
|
||||
def get_success_url(self, request):
|
||||
"""Redirect back to the same page after a successful POST."""
|
||||
return self.url
|
||||
|
||||
def handle_email_form(self, request, form):
|
||||
captured = form.save(commit=False)
|
||||
captured.referrer = self.get_referrer(request)
|
||||
captured.page = self
|
||||
captured.save()
|
||||
|
||||
messages.success(request, "Thanks! We'll be in touch.")
|
||||
|
||||
return redirect(self.get_success_url(request))
|
||||
|
||||
def serve(self, request, *args, **kwargs):
|
||||
"""
|
||||
Unified GET/POST handling:
|
||||
- On GET: render template with empty form.
|
||||
- On POST: validate, save CapturedEmail, redirect, or redisplay with errors.
|
||||
"""
|
||||
form = self.build_form(request)
|
||||
|
||||
if request.method == "POST" and form.is_valid():
|
||||
return self.handle_email_form(request, form)
|
||||
|
||||
# Fall through: GET, or invalid POST
|
||||
context = super().get_context(request, *args, **kwargs)
|
||||
context["form"] = form
|
||||
return render(request, self.get_template(request), context)
|
||||
|
||||
|
||||
class ProgramPage(EmailCapturePage):
|
||||
parent_page_types = ["marketing.ProgramPageIndex"]
|
||||
subpage_types = []
|
||||
|
||||
|
||||
class DetailPage(EmailCapturePage):
|
||||
parent_page_types = ["marketing.TopicPage"]
|
||||
subpage_types = []
|
||||
|
||||
|
||||
# ===================
|
||||
### Dummy pages ###
|
||||
# ===================
|
||||
class OutreachHomePage(Page):
|
||||
"""A dummy homepage to just return a 404 at the `/outreach/` url"""
|
||||
|
||||
parent_page_types = ["wagtailcore.Page"]
|
||||
subpage_types = ["marketing.ProgramPageIndex", "marketing.TopicPage"]
|
||||
max_count = 1 # one container
|
||||
|
||||
def route(self, request, path_components):
|
||||
"""
|
||||
Custom router so public URLs don't include container slugs.
|
||||
/outreach/program_page/<slug>/ => delegate to ProgramPageIndex -> ProgramPage
|
||||
/outreach/<topic>/<detail>/ => delegate to TopicPage -> DetailPage
|
||||
"""
|
||||
if not path_components:
|
||||
return RouteResult(self)
|
||||
|
||||
first, *rest = path_components
|
||||
|
||||
# Fixed segment for program pages
|
||||
if first == "program_page":
|
||||
try:
|
||||
program_page_index = ProgramPageIndex.objects.child_of(self).get()
|
||||
except ProgramPageIndex.DoesNotExist:
|
||||
raise Http404("Program index not found")
|
||||
# Delegate the remaining segments
|
||||
return program_page_index.route(request, rest)
|
||||
|
||||
# Otherwise, first segment should be a TopicPage slug
|
||||
try:
|
||||
topic = TopicPage.objects.child_of(self).get(slug=first)
|
||||
except TopicPage.DoesNotExist:
|
||||
raise Http404("Topic not found")
|
||||
|
||||
return topic.route(request, rest)
|
||||
|
||||
# Hide this page publicly: /outreach/ -> 404
|
||||
def serve(self, request, *args, **kwargs):
|
||||
raise Http404
|
||||
|
||||
def get_sitemap_urls(self, request=None):
|
||||
return []
|
||||
|
||||
|
||||
class ProgramPageIndex(Page):
|
||||
"""A dummy index page to facilitate our url scheme"""
|
||||
|
||||
parent_page_types = ["marketing.OutreachHomePage"]
|
||||
subpage_types = ["marketing.ProgramPage"]
|
||||
max_count = 1 # one container
|
||||
|
||||
# Hide index page: /outreach/program_page/ -> 404
|
||||
def serve(self, request, *args, **kwargs):
|
||||
raise Http404
|
||||
|
||||
def get_sitemap_urls(self, request=None):
|
||||
return []
|
||||
|
||||
|
||||
class TopicPage(Page):
|
||||
"""A dummy topic page that represents a given topic (e.g. a library)"""
|
||||
|
||||
parent_page_types = ["marketing.OutreachHomePage"]
|
||||
subpage_types = ["marketing.DetailPage"]
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Topic"
|
||||
|
||||
# Hide this page publicly: /outreach/ -> 404
|
||||
def serve(self, request, *args, **kwargs):
|
||||
raise Http404
|
||||
|
||||
def get_sitemap_urls(self, request=None):
|
||||
return []
|
||||
|
||||
@@ -8,8 +8,7 @@ from django.views.decorators.cache import never_cache
|
||||
from django.views.generic import CreateView
|
||||
|
||||
from core.views import logger
|
||||
from marketing.forms import CapturedEmailForm
|
||||
from marketing.models import CapturedEmail
|
||||
from marketing.models import CapturedEmail, CapturedEmailForm
|
||||
|
||||
|
||||
@method_decorator(never_cache, name="dispatch")
|
||||
|
||||
@@ -107,7 +107,7 @@ def moderator_user(db, make_user):
|
||||
image = Image.new("RGBA", size=(100, 100), color=(155, 0, 0))
|
||||
image.save(file, "png")
|
||||
file.seek(0)
|
||||
user.image.save(filename, file)
|
||||
user.profile_image.save(filename, file)
|
||||
user.save()
|
||||
return user
|
||||
|
||||
@@ -121,7 +121,7 @@ def regular_user(db, make_user):
|
||||
image = Image.new("RGBA", size=(100, 100), color=(155, 0, 0))
|
||||
image.save(file, "png")
|
||||
file.seek(0)
|
||||
user.image.save(filename, file)
|
||||
user.profile_image.save(filename, file)
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
@@ -388,9 +388,9 @@ def test_news_create_requirements(
|
||||
image = Image.new("RGB", size=(100, 100), color=(155, 0, 0))
|
||||
image.save(file, "jpeg")
|
||||
file.seek(0)
|
||||
user.image.save(filename, file)
|
||||
user.profile_image.save(filename, file)
|
||||
else:
|
||||
user.image = None
|
||||
user.profile_image = None
|
||||
|
||||
user.display_name = "Test User" if has_display_name else ""
|
||||
user.save()
|
||||
|
||||
@@ -282,7 +282,7 @@ class AllTypesCreateView(LoginRequiredMixin, TemplateView):
|
||||
if not request.user.display_name:
|
||||
missing_data.append("your name")
|
||||
|
||||
if not request.user.image:
|
||||
if not request.user.profile_image:
|
||||
missing_data.append("a profile photo")
|
||||
|
||||
if missing_data:
|
||||
|
||||
182
package-lock.json
generated
182
package-lock.json
generated
@@ -15,6 +15,7 @@
|
||||
"autoprefixer": "^10.4.12",
|
||||
"cssnano": "^5.1.14",
|
||||
"htmx": "^0.0.2",
|
||||
"marked": "^17.0.0",
|
||||
"tailwindcss": "3.2.1"
|
||||
}
|
||||
},
|
||||
@@ -54,9 +55,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@svgdotjs/svg.js": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.js/-/svg.js-3.2.4.tgz",
|
||||
"integrity": "sha512-BjJ/7vWNowlX3Z8O4ywT58DqbNRyYlkk6Yz/D13aB7hGmfQTvGX4Tkgtm/ApYlu9M7lCQi15xUEidqMUmdMYwg==",
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.js/-/svg.js-3.2.5.tgz",
|
||||
"integrity": "sha512-/VNHWYhNu+BS7ktbYoVGrCmsXDh+chFMaONMwGNdIBcFHrWqk2jY8fNyr3DLdtQUIalvkPfM554ZSFa3dm3nxQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -132,9 +133,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/alpinejs": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.1.tgz",
|
||||
"integrity": "sha512-ICar8UsnRZAYvv/fCNfNeKMXNoXGUfwHrjx7LqXd08zIP95G2d9bAOuaL97re+1mgt/HojqHsfdOLo/A5LuWgQ==",
|
||||
"version": "3.15.1",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.1.tgz",
|
||||
"integrity": "sha512-HLO1TtiE92VajFHtLLPK8BWaK1YepV/uj31UrfoGnQ00lyFOJZ+oVY3F0DghPAwvg8sLU79pmjGQSytERa2gEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "~3.1.1"
|
||||
@@ -160,9 +161,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.20",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
|
||||
"integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
|
||||
"version": "10.4.22",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz",
|
||||
"integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -179,11 +180,11 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.23.3",
|
||||
"caniuse-lite": "^1.0.30001646",
|
||||
"fraction.js": "^4.3.7",
|
||||
"browserslist": "^4.27.0",
|
||||
"caniuse-lite": "^1.0.30001754",
|
||||
"fraction.js": "^5.3.4",
|
||||
"normalize-range": "^0.1.2",
|
||||
"picocolors": "^1.0.1",
|
||||
"picocolors": "^1.1.1",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -196,6 +197,15 @@
|
||||
"postcss": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.8.27",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.27.tgz",
|
||||
"integrity": "sha512-2CXFpkjVnY2FT+B6GrSYxzYf65BJWEqz5tIRHCvNsZZ2F3CmsCB37h8SpYgKG7y9C4YAeTipIPWG7EmFmhAeXA==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"baseline-browser-mapping": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
@@ -227,9 +237,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.24.0",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz",
|
||||
"integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==",
|
||||
"version": "4.28.0",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz",
|
||||
"integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -246,10 +256,11 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001663",
|
||||
"electron-to-chromium": "^1.5.28",
|
||||
"node-releases": "^2.0.18",
|
||||
"update-browserslist-db": "^1.1.0"
|
||||
"baseline-browser-mapping": "^2.8.25",
|
||||
"caniuse-lite": "^1.0.30001754",
|
||||
"electron-to-chromium": "^1.5.249",
|
||||
"node-releases": "^2.0.27",
|
||||
"update-browserslist-db": "^1.1.4"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
@@ -280,9 +291,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001667",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz",
|
||||
"integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==",
|
||||
"version": "1.0.30001754",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz",
|
||||
"integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -398,9 +409,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/css-what": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
|
||||
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
@@ -603,9 +614,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.34",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.34.tgz",
|
||||
"integrity": "sha512-/TZAiChbAflBNjCg+VvstbcwAtIL/VdMFO3NgRFIzBjpvPzWOTIbbO8kNb6RwU4bt9TP7K+3KqBKw/lOU+Y+GA==",
|
||||
"version": "1.5.250",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.250.tgz",
|
||||
"integrity": "sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/entities": {
|
||||
@@ -627,16 +638,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
||||
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
"@nodelib/fs.walk": "^1.2.3",
|
||||
"glob-parent": "^5.1.2",
|
||||
"merge2": "^1.3.0",
|
||||
"micromatch": "^4.0.4"
|
||||
"micromatch": "^4.0.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6.0"
|
||||
@@ -655,9 +666,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
|
||||
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
||||
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"reusify": "^1.0.4"
|
||||
@@ -676,15 +687,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
|
||||
"version": "5.3.4",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
|
||||
"integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"type": "patreon",
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/rawify"
|
||||
}
|
||||
},
|
||||
@@ -757,9 +768,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
||||
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
@@ -822,6 +833,18 @@
|
||||
"integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "17.0.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-17.0.0.tgz",
|
||||
"integrity": "sha512-KkDYEWEEiYJw/KC+DVm1zzlpMQSMIu6YRltkcCvwheCp8HWPXCk9JwOmHJKBlGfzcpzcIt6x3sMnTsRm/51oDg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||
@@ -869,9 +892,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -887,9 +910,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
|
||||
"version": "2.0.27",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
|
||||
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
@@ -950,9 +973,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
|
||||
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
@@ -977,9 +1000,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.47",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -996,8 +1019,8 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.1.0",
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1117,9 +1140,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-js": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
|
||||
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
|
||||
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"camelcase-css": "^2.0.1"
|
||||
@@ -1127,10 +1160,6 @@
|
||||
"engines": {
|
||||
"node": "^12 || ^14 || >= 16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.4.21"
|
||||
}
|
||||
@@ -1566,26 +1595,29 @@
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||
"version": "1.22.11",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.13.0",
|
||||
"is-core-module": "^2.16.1",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/reusify": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
||||
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"iojs": ">=1.0.0",
|
||||
@@ -1743,9 +1775,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
|
||||
"integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
|
||||
"integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -1763,7 +1795,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"escalade": "^3.2.0",
|
||||
"picocolors": "^1.1.0"
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"update-browserslist-db": "cli.js"
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"autoprefixer": "^10.4.12",
|
||||
"cssnano": "^5.1.14",
|
||||
"htmx": "^0.0.2",
|
||||
"marked": "^17.0.0",
|
||||
"tailwindcss": "3.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ asgiref==3.10.0
|
||||
# via
|
||||
# -c ./requirements.txt
|
||||
# django
|
||||
django==5.2.7
|
||||
django==5.2.8
|
||||
# via
|
||||
# -c ./requirements.txt
|
||||
# django-debug-toolbar
|
||||
django-debug-toolbar==6.0.0
|
||||
django-debug-toolbar==6.1.0
|
||||
# via -r ./requirements-dev.in
|
||||
pydevd-pycharm==252.27397.106
|
||||
# via -r ./requirements-dev.in
|
||||
|
||||
@@ -38,6 +38,8 @@ lxml
|
||||
algoliasearch
|
||||
openai
|
||||
playwright
|
||||
wagtail
|
||||
wagtail-markdown
|
||||
|
||||
# Logging
|
||||
django-tracer
|
||||
|
||||
145
requirements.txt
145
requirements.txt
@@ -2,16 +2,18 @@
|
||||
# uv pip compile ./requirements.in --no-strip-extras --output-file ./requirements.txt
|
||||
aiohappyeyeballs==2.6.1
|
||||
# via aiohttp
|
||||
aiohttp==3.13.1
|
||||
aiohttp==3.13.2
|
||||
# via algoliasearch
|
||||
aiosignal==1.4.0
|
||||
# via aiohttp
|
||||
algoliasearch==4.30.0
|
||||
algoliasearch==4.33.0
|
||||
# via -r ./requirements.in
|
||||
amqp==5.3.1
|
||||
# via kombu
|
||||
annotated-types==0.7.0
|
||||
# via pydantic
|
||||
anyascii==0.3.3
|
||||
# via wagtail
|
||||
anyio==4.11.0
|
||||
# via
|
||||
# httpx
|
||||
@@ -37,16 +39,20 @@ attrs==25.4.0
|
||||
# aiohttp
|
||||
# interrogate
|
||||
beautifulsoup4==4.14.2
|
||||
# via -r ./requirements.in
|
||||
# via
|
||||
# -r ./requirements.in
|
||||
# wagtail
|
||||
billiard==4.2.2
|
||||
# via celery
|
||||
black==25.9.0
|
||||
black==25.11.0
|
||||
# via -r ./requirements.in
|
||||
boto3==1.40.56
|
||||
bleach==4.1.0
|
||||
# via wagtail-markdown
|
||||
boto3==1.40.74
|
||||
# via
|
||||
# -r ./requirements.in
|
||||
# django-bakery
|
||||
botocore==1.40.56
|
||||
botocore==1.40.74
|
||||
# via
|
||||
# boto3
|
||||
# s3transfer
|
||||
@@ -56,7 +62,7 @@ bumpversion==0.6.0
|
||||
# via -r ./requirements.in
|
||||
celery==5.5.3
|
||||
# via -r ./requirements.in
|
||||
certifi==2025.10.5
|
||||
certifi==2025.11.12
|
||||
# via
|
||||
# elasticsearch
|
||||
# httpcore
|
||||
@@ -92,7 +98,7 @@ colorama==0.4.6
|
||||
# via interrogate
|
||||
contourpy==1.3.3
|
||||
# via matplotlib
|
||||
coverage[toml]==7.11.0
|
||||
coverage[toml]==7.11.3
|
||||
# via pytest-cov
|
||||
cryptography==46.0.3
|
||||
# via
|
||||
@@ -103,6 +109,8 @@ cycler==0.12.1
|
||||
# via matplotlib
|
||||
decorator==5.2.1
|
||||
# via ipython
|
||||
defusedxml==0.7.1
|
||||
# via willow
|
||||
distlib==0.4.0
|
||||
# via virtualenv
|
||||
distro==1.9.0
|
||||
@@ -111,7 +119,7 @@ dj-database-url==3.0.1
|
||||
# via environs
|
||||
dj-email-url==1.0.6
|
||||
# via environs
|
||||
django==5.2.7
|
||||
django==5.2.8
|
||||
# via
|
||||
# -r ./requirements.in
|
||||
# dj-database-url
|
||||
@@ -121,21 +129,31 @@ django==5.2.7
|
||||
# django-cors-headers
|
||||
# django-db-geventpool
|
||||
# django-extensions
|
||||
# django-filter
|
||||
# django-haystack
|
||||
# django-health-check
|
||||
# django-js-asset
|
||||
# django-modelcluster
|
||||
# django-oauth-toolkit
|
||||
# django-permissionedforms
|
||||
# django-redis
|
||||
# django-storages
|
||||
# django-stubs-ext
|
||||
# django-taggit
|
||||
# django-tasks
|
||||
# django-treebeard
|
||||
# djangorestframework
|
||||
# laces
|
||||
# model-bakery
|
||||
# modelsearch
|
||||
# wagtail
|
||||
django-admin-env-notice==1.0.1
|
||||
# via -r ./requirements.in
|
||||
django-allauth[socialaccount]==65.12.1
|
||||
django-allauth[socialaccount]==65.13.0
|
||||
# via -r ./requirements.in
|
||||
django-anymail[mailgun]==13.1
|
||||
# via -r ./requirements.in
|
||||
django-appconf==1.1.0
|
||||
django-appconf==1.2.0
|
||||
# via django-imagekit
|
||||
django-bakery==0.13.5
|
||||
# via -r ./requirements.in
|
||||
@@ -145,12 +163,14 @@ django-click==2.4.1
|
||||
# via -r ./requirements.in
|
||||
django-cors-headers==4.9.0
|
||||
# via -r ./requirements.in
|
||||
django-countries==7.6.1
|
||||
django-countries==8.1.0
|
||||
# via -r ./requirements.in
|
||||
django-db-geventpool==4.0.8
|
||||
# via -r ./requirements.in
|
||||
django-extensions==4.1
|
||||
# via -r ./requirements.in
|
||||
django-filter==25.2
|
||||
# via wagtail
|
||||
django-haystack==3.3.0
|
||||
# via -r ./requirements.in
|
||||
django-health-check==3.20.0
|
||||
@@ -159,36 +179,58 @@ django-imagekit==6.0.0
|
||||
# via -r ./requirements.in
|
||||
django-js-asset==3.1.2
|
||||
# via django-mptt
|
||||
django-modelcluster==6.4
|
||||
# via wagtail
|
||||
django-mptt==0.14.0
|
||||
# via -r ./requirements.in
|
||||
django-oauth-toolkit==3.1.0
|
||||
# via -r ./requirements.in
|
||||
django-permissionedforms==0.1
|
||||
# via wagtail
|
||||
django-redis==6.0.0
|
||||
# via -r ./requirements.in
|
||||
django-storages==1.14.6
|
||||
# via -r ./requirements.in
|
||||
django-stubs-ext==5.2.7
|
||||
# via django-tasks
|
||||
django-taggit==6.1.0
|
||||
# via wagtail
|
||||
django-tasks==0.9.0
|
||||
# via
|
||||
# modelsearch
|
||||
# wagtail
|
||||
django-test-plus==2.3.0
|
||||
# via -r ./requirements.in
|
||||
django-tracer==0.9.3
|
||||
# via -r ./requirements.in
|
||||
django-upgrade==1.29.0
|
||||
django-treebeard==4.7.1
|
||||
# via wagtail
|
||||
django-upgrade==1.29.1
|
||||
# via -r ./requirements.in
|
||||
django-widget-tweaks==1.5.0
|
||||
# via -r ./requirements.in
|
||||
djangorestframework==3.16.1
|
||||
# via -r ./requirements.in
|
||||
# via
|
||||
# -r ./requirements.in
|
||||
# wagtail
|
||||
draftjs-exporter==5.1.0
|
||||
# via wagtail
|
||||
elasticsearch==7.9.1
|
||||
# via -r ./requirements.in
|
||||
environs[django]==14.3.0
|
||||
environs[django]==14.5.0
|
||||
# via -r ./requirements.in
|
||||
et-xmlfile==2.0.0
|
||||
# via openpyxl
|
||||
executing==2.2.1
|
||||
# via stack-data
|
||||
faker==37.11.0
|
||||
faker==38.0.0
|
||||
# via -r ./requirements.in
|
||||
fastcore==1.8.13
|
||||
fastcore==1.8.16
|
||||
# via ghapi
|
||||
filelock==3.20.0
|
||||
# via virtualenv
|
||||
filetype==1.2.0
|
||||
# via willow
|
||||
fonttools==4.60.1
|
||||
# via matplotlib
|
||||
frozenlist==1.8.0
|
||||
@@ -226,7 +268,7 @@ iniconfig==2.3.0
|
||||
# via pytest
|
||||
interrogate==1.7.0
|
||||
# via -r ./requirements.in
|
||||
ipython==9.6.0
|
||||
ipython==9.7.0
|
||||
# via -r ./requirements.in
|
||||
ipython-pygments-lexers==1.1.1
|
||||
# via ipython
|
||||
@@ -234,7 +276,7 @@ itsdangerous==2.2.0
|
||||
# via -r ./requirements.in
|
||||
jedi==0.19.2
|
||||
# via ipython
|
||||
jiter==0.11.1
|
||||
jiter==0.12.0
|
||||
# via openai
|
||||
jmespath==1.0.1
|
||||
# via
|
||||
@@ -250,13 +292,17 @@ kiwisolver==1.4.9
|
||||
# via matplotlib
|
||||
kombu==5.5.4
|
||||
# via celery
|
||||
laces==0.1.2
|
||||
# via wagtail
|
||||
lxml==6.0.2
|
||||
# via -r ./requirements.in
|
||||
marshmallow==4.0.1
|
||||
markdown==3.10
|
||||
# via wagtail-markdown
|
||||
marshmallow==4.1.0
|
||||
# via environs
|
||||
matplotlib==3.10.7
|
||||
# via wordcloud
|
||||
matplotlib-inline==0.1.7
|
||||
matplotlib-inline==0.2.1
|
||||
# via ipython
|
||||
minio==7.2.18
|
||||
# via -r ./requirements.in
|
||||
@@ -264,6 +310,8 @@ mistletoe==1.5.0
|
||||
# via -r ./requirements.in
|
||||
model-bakery==1.20.5
|
||||
# via -r ./requirements.in
|
||||
modelsearch==1.1.1
|
||||
# via wagtail
|
||||
multidict==6.7.0
|
||||
# via
|
||||
# aiohttp
|
||||
@@ -281,11 +329,14 @@ oauthlib==3.3.1
|
||||
# via
|
||||
# django-allauth
|
||||
# django-oauth-toolkit
|
||||
openai==2.6.0
|
||||
openai==2.8.0
|
||||
# via -r ./requirements.in
|
||||
openpyxl==3.1.5
|
||||
# via wagtail
|
||||
packaging==25.0
|
||||
# via
|
||||
# black
|
||||
# bleach
|
||||
# django-haystack
|
||||
# fastcore
|
||||
# ghapi
|
||||
@@ -306,18 +357,22 @@ pillow==12.0.0
|
||||
# -r ./requirements.in
|
||||
# matplotlib
|
||||
# pilkit
|
||||
# pillow-heif
|
||||
# wagtail
|
||||
# wordcloud
|
||||
pillow-heif==1.1.1
|
||||
# via willow
|
||||
platformdirs==4.5.0
|
||||
# via
|
||||
# black
|
||||
# virtualenv
|
||||
playwright==1.55.0
|
||||
playwright==1.56.0
|
||||
# via -r ./requirements.in
|
||||
pluggy==1.6.0
|
||||
# via
|
||||
# pytest
|
||||
# pytest-cov
|
||||
pre-commit==4.3.0
|
||||
pre-commit==4.4.0
|
||||
# via -r ./requirements.in
|
||||
prompt-toolkit==3.0.52
|
||||
# via
|
||||
@@ -341,11 +396,11 @@ pycparser==2.23
|
||||
# via cffi
|
||||
pycryptodome==3.23.0
|
||||
# via minio
|
||||
pydantic==2.12.3
|
||||
pydantic==2.12.4
|
||||
# via
|
||||
# algoliasearch
|
||||
# openai
|
||||
pydantic-core==2.41.4
|
||||
pydantic-core==2.41.5
|
||||
# via pydantic
|
||||
pyee==13.0.0
|
||||
# via playwright
|
||||
@@ -360,7 +415,7 @@ pyjwt[crypto]==2.10.1
|
||||
# redis
|
||||
pyparsing==3.2.5
|
||||
# via matplotlib
|
||||
pytest==8.4.2
|
||||
pytest==9.0.1
|
||||
# via
|
||||
# -r ./requirements.in
|
||||
# pytest-cov
|
||||
@@ -376,13 +431,13 @@ python-dateutil==2.9.0.post0
|
||||
# botocore
|
||||
# celery
|
||||
# matplotlib
|
||||
python-dotenv==1.1.1
|
||||
python-dotenv==1.2.1
|
||||
# via environs
|
||||
python-frontmatter==1.1.0
|
||||
# via -r ./requirements.in
|
||||
python-json-logger==4.0.0
|
||||
# via -r ./requirements.in
|
||||
pytokens==0.2.0
|
||||
pytokens==0.3.0
|
||||
# via black
|
||||
pyyaml==6.0.3
|
||||
# via
|
||||
@@ -401,20 +456,20 @@ requests==2.32.5
|
||||
# django-anymail
|
||||
# django-oauth-toolkit
|
||||
# responses
|
||||
# wagtail
|
||||
responses==0.25.8
|
||||
# via -r ./requirements.in
|
||||
s3transfer==0.14.0
|
||||
# via boto3
|
||||
setuptools==80.9.0
|
||||
# via
|
||||
# fs
|
||||
# zope-event
|
||||
# via fs
|
||||
six==1.17.0
|
||||
# via
|
||||
# bleach
|
||||
# django-bakery
|
||||
# fs
|
||||
# python-dateutil
|
||||
slack-sdk==3.37.0
|
||||
slack-sdk==3.38.0
|
||||
# via -r ./requirements.in
|
||||
sniffio==1.3.1
|
||||
# via
|
||||
@@ -426,10 +481,12 @@ sqlparse==0.5.3
|
||||
# via django
|
||||
stack-data==0.6.3
|
||||
# via ipython
|
||||
structlog==25.4.0
|
||||
structlog==25.5.0
|
||||
# via -r ./requirements.in
|
||||
tabulate==0.9.0
|
||||
# via interrogate
|
||||
telepath==0.3.1
|
||||
# via wagtail
|
||||
tokenize-rt==6.2.0
|
||||
# via django-upgrade
|
||||
tqdm==4.67.1
|
||||
@@ -442,6 +499,8 @@ typing-extensions==4.15.0
|
||||
# via
|
||||
# beautifulsoup4
|
||||
# django-countries
|
||||
# django-stubs-ext
|
||||
# django-tasks
|
||||
# jwcrypto
|
||||
# minio
|
||||
# openai
|
||||
@@ -466,26 +525,36 @@ urllib3==2.5.0
|
||||
# minio
|
||||
# requests
|
||||
# responses
|
||||
uv==0.9.5
|
||||
uv==0.9.9
|
||||
# via -r ./requirements.in
|
||||
vine==5.1.0
|
||||
# via
|
||||
# amqp
|
||||
# celery
|
||||
# kombu
|
||||
virtualenv==20.35.3
|
||||
virtualenv==20.35.4
|
||||
# via pre-commit
|
||||
wagtail==7.2
|
||||
# via
|
||||
# -r ./requirements.in
|
||||
# wagtail-markdown
|
||||
wagtail-markdown==0.13.0
|
||||
# via -r ./requirements.in
|
||||
wcwidth==0.2.14
|
||||
# via prompt-toolkit
|
||||
webencodings==0.5.1
|
||||
# via bleach
|
||||
wheel==0.45.1
|
||||
# via -r ./requirements.in
|
||||
whitenoise==6.11.0
|
||||
# via -r ./requirements.in
|
||||
willow[heif]==1.12.0
|
||||
# via wagtail
|
||||
wordcloud==1.9.4
|
||||
# via -r ./requirements.in
|
||||
yarl==1.22.0
|
||||
# via aiohttp
|
||||
zope-event==6.0
|
||||
zope-event==6.1
|
||||
# via gevent
|
||||
zope-interface==8.0.1
|
||||
zope-interface==8.1
|
||||
# via gevent
|
||||
|
||||
@@ -46,3 +46,80 @@ code, pre {
|
||||
.email-block {display:flex; align-items:center;margin:auto; justify-content:center;}
|
||||
.email-button {margin-left:1rem; background-color:#0284c7; color:white; font-weight:bold; padding:5px 10px; border:0;}
|
||||
.email-button:hover {background-color:#006394}
|
||||
|
||||
/* Pygments output */
|
||||
pre { line-height: 125%; }
|
||||
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
|
||||
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
|
||||
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
||||
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
||||
.codehilite .hll { background-color: #ffffcc }
|
||||
.codehilite { background: #f8f8f8; }
|
||||
.codehilite .c { color: #3D7B7B; font-style: italic } /* Comment */
|
||||
.codehilite .err { border: 1px solid #F00 } /* Error */
|
||||
.codehilite .k { color: #008000; font-weight: bold } /* Keyword */
|
||||
.codehilite .o { color: #666 } /* Operator */
|
||||
.codehilite .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
|
||||
.codehilite .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */
|
||||
.codehilite .cp { color: #9C6500 } /* Comment.Preproc */
|
||||
.codehilite .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */
|
||||
.codehilite .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */
|
||||
.codehilite .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */
|
||||
.codehilite .gd { color: #A00000 } /* Generic.Deleted */
|
||||
.codehilite .ge { font-style: italic } /* Generic.Emph */
|
||||
.codehilite .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
|
||||
.codehilite .gr { color: #E40000 } /* Generic.Error */
|
||||
.codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||
.codehilite .gi { color: #008400 } /* Generic.Inserted */
|
||||
.codehilite .go { color: #717171 } /* Generic.Output */
|
||||
.codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
|
||||
.codehilite .gs { font-weight: bold } /* Generic.Strong */
|
||||
.codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
||||
.codehilite .gt { color: #04D } /* Generic.Traceback */
|
||||
.codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
|
||||
.codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
|
||||
.codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
|
||||
.codehilite .kp { color: #008000 } /* Keyword.Pseudo */
|
||||
.codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
|
||||
.codehilite .kt { color: #B00040 } /* Keyword.Type */
|
||||
.codehilite .m { color: #666 } /* Literal.Number */
|
||||
.codehilite .s { color: #BA2121 } /* Literal.String */
|
||||
.codehilite .na { color: #687822 } /* Name.Attribute */
|
||||
.codehilite .nb { color: #008000 } /* Name.Builtin */
|
||||
.codehilite .nc { color: #00F; font-weight: bold } /* Name.Class */
|
||||
.codehilite .no { color: #800 } /* Name.Constant */
|
||||
.codehilite .nd { color: #A2F } /* Name.Decorator */
|
||||
.codehilite .ni { color: #717171; font-weight: bold } /* Name.Entity */
|
||||
.codehilite .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */
|
||||
.codehilite .nf { color: #00F } /* Name.Function */
|
||||
.codehilite .nl { color: #767600 } /* Name.Label */
|
||||
.codehilite .nn { color: #00F; font-weight: bold } /* Name.Namespace */
|
||||
.codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */
|
||||
.codehilite .nv { color: #19177C } /* Name.Variable */
|
||||
.codehilite .ow { color: #A2F; font-weight: bold } /* Operator.Word */
|
||||
.codehilite .w { color: #BBB } /* Text.Whitespace */
|
||||
.codehilite .mb { color: #666 } /* Literal.Number.Bin */
|
||||
.codehilite .mf { color: #666 } /* Literal.Number.Float */
|
||||
.codehilite .mh { color: #666 } /* Literal.Number.Hex */
|
||||
.codehilite .mi { color: #666 } /* Literal.Number.Integer */
|
||||
.codehilite .mo { color: #666 } /* Literal.Number.Oct */
|
||||
.codehilite .sa { color: #BA2121 } /* Literal.String.Affix */
|
||||
.codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */
|
||||
.codehilite .sc { color: #BA2121 } /* Literal.String.Char */
|
||||
.codehilite .dl { color: #BA2121 } /* Literal.String.Delimiter */
|
||||
.codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
|
||||
.codehilite .s2 { color: #BA2121 } /* Literal.String.Double */
|
||||
.codehilite .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */
|
||||
.codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */
|
||||
.codehilite .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */
|
||||
.codehilite .sx { color: #008000 } /* Literal.String.Other */
|
||||
.codehilite .sr { color: #A45A77 } /* Literal.String.Regex */
|
||||
.codehilite .s1 { color: #BA2121 } /* Literal.String.Single */
|
||||
.codehilite .ss { color: #19177C } /* Literal.String.Symbol */
|
||||
.codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */
|
||||
.codehilite .fm { color: #00F } /* Name.Function.Magic */
|
||||
.codehilite .vc { color: #19177C } /* Name.Variable.Class */
|
||||
.codehilite .vg { color: #19177C } /* Name.Variable.Global */
|
||||
.codehilite .vi { color: #19177C } /* Name.Variable.Instance */
|
||||
.codehilite .vm { color: #19177C } /* Name.Variable.Magic */
|
||||
.codehilite .il { color: #666 } /* Literal.Number.Integer.Long */
|
||||
|
||||
74
static/js/marked.js
Normal file
74
static/js/marked.js
Normal file
File diff suppressed because one or more lines are too long
7
static/js/marked.umd.js.map
Normal file
7
static/js/marked.umd.js.map
Normal file
File diff suppressed because one or more lines are too long
52
static/wagtail/markdown-paste.js
Normal file
52
static/wagtail/markdown-paste.js
Normal file
@@ -0,0 +1,52 @@
|
||||
(function () {
|
||||
// Runs on the page editor; attaches a paste handler to Draftail editors.
|
||||
function isMarkdown(text) {
|
||||
// Very light heuristic: backticks or fenced code or headings/bullets
|
||||
return /`[^`]+`|(^|\n)```|(^|\n)#{1,6}\s|(^|\n)[*-]\s/.test(text);
|
||||
}
|
||||
|
||||
function convertAndInsertMarkdown(evt, editorEl) {
|
||||
try {
|
||||
const md = evt.clipboardData.getData("text/markdown") || evt.clipboardData.getData("text/plain");
|
||||
if (!md || !isMarkdown(md)) return false;
|
||||
|
||||
// Convert markdown -> HTML
|
||||
const html = window.marked.parse(md, { mangle: false, headerIds: false });
|
||||
|
||||
// Stop the default paste; inject HTML instead
|
||||
evt.preventDefault();
|
||||
|
||||
// Use the browser to paste HTML so Draftail can apply its from_html rules.
|
||||
// Create a temp contenteditable to execCommand('insertHTML')
|
||||
const sel = window.getSelection();
|
||||
if (!sel || sel.rangeCount === 0) return false;
|
||||
|
||||
// Some Draftail builds support 'insertHTML' directly via execCommand:
|
||||
document.execCommand("insertHTML", false, html);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function attach() {
|
||||
// Draftail editor root elements have [data-draftail-input]
|
||||
document.querySelectorAll("[data-draftail-input]").forEach((wrapper) => {
|
||||
const editorEl = wrapper.querySelector("[contenteditable='true']");
|
||||
if (!editorEl || editorEl.__md_paste_bound) return;
|
||||
editorEl.__md_paste_bound = true;
|
||||
|
||||
editorEl.addEventListener("paste", (evt) => {
|
||||
// Prefer text/markdown if provided by the clipboard
|
||||
const hasMarkdownMime = evt.clipboardData && Array.from(evt.clipboardData.types || []).includes("text/markdown");
|
||||
if (hasMarkdownMime || isMarkdown(evt.clipboardData.getData("text/plain") || "")) {
|
||||
convertAndInsertMarkdown(evt, editorEl);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Attach when the editor loads and also after Wagtail re-initializes editors
|
||||
document.addEventListener("DOMContentLoaded", attach);
|
||||
document.addEventListener("wagtail:document-loaded", attach);
|
||||
})();
|
||||
1
templates/marketing/detail_page.html
Normal file
1
templates/marketing/detail_page.html
Normal file
@@ -0,0 +1 @@
|
||||
{% include 'marketing/program_page.html' %}
|
||||
37
templates/marketing/landing_page.html
Normal file
37
templates/marketing/landing_page.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{# Generic landing page; don't use directly #}
|
||||
{% load static wagtailcore_tags %}
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="{% static 'css/landing-style.css' %}" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{ page.email_capture_intro }}</h1>
|
||||
|
||||
{% if messages %}
|
||||
{# Expect a success message from submitting the form #}
|
||||
<div class="email-block">
|
||||
<div class="messages">
|
||||
{% for message in messages %}
|
||||
<p{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="email-block">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.non_field_errors }}
|
||||
{{ form.email.errors }}
|
||||
{{ form.email }}
|
||||
<button class="email-button" type="submit">Get C++ updates</button>
|
||||
</form>
|
||||
</div>
|
||||
<p>{{ page.privacy_blurb }}</p>
|
||||
{% endif %}
|
||||
|
||||
<hr/>
|
||||
|
||||
{{ page.body }}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
1
templates/marketing/program_page.html
Normal file
1
templates/marketing/program_page.html
Normal file
@@ -0,0 +1 @@
|
||||
{% include 'marketing/landing_page.html' %}
|
||||
@@ -39,7 +39,7 @@ class EmailUserAdmin(UserAdmin):
|
||||
{
|
||||
"fields": (
|
||||
"can_update_image",
|
||||
"image",
|
||||
"profile_image",
|
||||
)
|
||||
},
|
||||
),
|
||||
|
||||
@@ -119,11 +119,11 @@ class CustomClearableFileInput(forms.ClearableFileInput):
|
||||
|
||||
|
||||
class UserProfilePhotoForm(forms.ModelForm):
|
||||
image = forms.FileField(widget=CustomClearableFileInput, required=False)
|
||||
profile_image = forms.FileField(widget=CustomClearableFileInput, required=False)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ["image"]
|
||||
fields = ["profile_image"]
|
||||
|
||||
def clean(self):
|
||||
"""Ensure a user can't update their photo if they
|
||||
@@ -137,25 +137,24 @@ class UserProfilePhotoForm(forms.ModelForm):
|
||||
|
||||
def save(self, commit=True):
|
||||
# Temporarily store the old image
|
||||
old_image = self.instance.image
|
||||
old_image = self.instance.profile_image
|
||||
# Save the new image
|
||||
user = super().save(commit=False)
|
||||
if not old_image:
|
||||
# reset image on image delete checked
|
||||
user.image_uploaded = False
|
||||
elif self.cleaned_data["image"] != old_image:
|
||||
elif self.cleaned_data["profile_image"] != old_image:
|
||||
# Delete the old image file if there's a new image being uploaded
|
||||
old_image.delete(save=False)
|
||||
|
||||
if self.cleaned_data.get("image"):
|
||||
new_image = self.cleaned_data["image"]
|
||||
if new_image := self.cleaned_data.get("profile_image"):
|
||||
_, file_extension = os.path.splitext(new_image.name)
|
||||
|
||||
# Strip the leading period from the file extension.
|
||||
file_extension = file_extension.lstrip(".")
|
||||
|
||||
new_image.name = f"{user.profile_image_filename_root}.{file_extension}"
|
||||
user.image = new_image
|
||||
user.profile_image = new_image
|
||||
user.image_uploaded = True
|
||||
|
||||
if commit:
|
||||
|
||||
@@ -18,11 +18,11 @@ class Command(BaseCommand):
|
||||
see your photo in the admin for whoever your first user is.
|
||||
"""
|
||||
user = User.objects.first()
|
||||
user.image = None
|
||||
user.profile_image = None
|
||||
user.github_username = "testing"
|
||||
user.save()
|
||||
user.refresh_from_db()
|
||||
assert bool(user.image) is False
|
||||
assert bool(user.profile_image) is False
|
||||
user.save_image_from_github()
|
||||
user.refresh_from_db()
|
||||
assert bool(user.image) is True
|
||||
assert bool(user.profile_image) is True
|
||||
|
||||
18
users/migrations/0020_rename_image_user_profile_image.py
Normal file
18
users/migrations/0020_rename_image_user_profile_image.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-10 17:00
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("users", "0019_user_image_uploaded"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="user",
|
||||
old_name="image",
|
||||
new_name="profile_image",
|
||||
),
|
||||
]
|
||||
@@ -204,14 +204,14 @@ class User(BaseUser):
|
||||
is_commit_author_name_overridden = models.BooleanField(
|
||||
default=False, help_text="Select to override the commit author with Username"
|
||||
)
|
||||
image = models.FileField(
|
||||
profile_image = models.FileField(
|
||||
upload_to="profile-images",
|
||||
null=True,
|
||||
blank=True,
|
||||
validators=[image_validator, max_file_size_validator],
|
||||
)
|
||||
image_thumbnail = ImageSpecField(
|
||||
source="image",
|
||||
source="profile_image",
|
||||
processors=[ResizeToFill(100, 100)],
|
||||
format="JPEG",
|
||||
options={"quality": 90},
|
||||
@@ -269,7 +269,7 @@ class User(BaseUser):
|
||||
|
||||
response = requests.get(avatar_url)
|
||||
filename = f"{self.profile_image_filename_root}.png"
|
||||
self.image.save(filename, ContentFile(response.content), save=True)
|
||||
self.profile_image.save(filename, ContentFile(response.content), save=True)
|
||||
|
||||
@cached_property
|
||||
def profile_image_filename_root(self):
|
||||
@@ -285,7 +285,7 @@ class User(BaseUser):
|
||||
|
||||
def get_thumbnail_url(self):
|
||||
# convenience method for templates
|
||||
if self.image and self.image_thumbnail:
|
||||
if self.profile_image and self.image_thumbnail:
|
||||
with suppress(AttributeError, MissingSource):
|
||||
return getattr(self.image_thumbnail, "url", None)
|
||||
|
||||
@@ -324,9 +324,9 @@ class User(BaseUser):
|
||||
self.last_name = "Doe"
|
||||
self.display_name = "John Doe"
|
||||
self.email = "deleted-{}@example.com".format(uuid.uuid4())
|
||||
image = self.image
|
||||
image = self.profile_image
|
||||
transaction.on_commit(lambda: image.delete())
|
||||
self.image = None
|
||||
self.profile_image = None
|
||||
self.image_thumbnail = None
|
||||
self.delete_permanently_at = None
|
||||
self.save()
|
||||
|
||||
@@ -32,14 +32,14 @@ class CurrentUserSerializer(serializers.ModelSerializer):
|
||||
"id",
|
||||
"email",
|
||||
"display_name",
|
||||
"image",
|
||||
"profile_image",
|
||||
"date_joined",
|
||||
"data",
|
||||
)
|
||||
read_only_fields = (
|
||||
"id",
|
||||
"email", # Users shouldn't change their email this way
|
||||
"image",
|
||||
"profile_image",
|
||||
"date_joined",
|
||||
)
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ def user(db):
|
||||
image = Image.new("RGBA", size=(100, 100), color=(155, 0, 0))
|
||||
image.save(file, "png")
|
||||
file.seek(0)
|
||||
user.image.save(filename, file)
|
||||
user.profile_image.save(filename, file)
|
||||
|
||||
user.set_password("password")
|
||||
user.save()
|
||||
@@ -50,7 +50,7 @@ def staff_user(db):
|
||||
image = Image.new("RGBA", size=(100, 100), color=(155, 0, 0))
|
||||
image.save(file, "png")
|
||||
file.seek(0)
|
||||
user.image.save(filename, file)
|
||||
user.profile_image.save(filename, file)
|
||||
|
||||
user.set_password("password")
|
||||
user.save()
|
||||
@@ -76,7 +76,7 @@ def super_user(db):
|
||||
image = Image.new("RGBA", size=(100, 100), color=(155, 0, 0))
|
||||
image.save(file, "png")
|
||||
file.seek(0)
|
||||
user.image.save(filename, file)
|
||||
user.profile_image.save(filename, file)
|
||||
user.set_password("password")
|
||||
user.save()
|
||||
|
||||
|
||||
@@ -189,10 +189,10 @@ def test_user_profile_photo_form_save(user):
|
||||
return file
|
||||
|
||||
old_image = ImageFile(create_test_image_file(filename="initial_image.png"))
|
||||
user.image.save("initial_image.png", old_image)
|
||||
user.profile_image.save("initial_image.png", old_image)
|
||||
|
||||
# Make sure the initial image was saved
|
||||
initial_path = user.image.path
|
||||
initial_path = user.profile_image.path
|
||||
assert initial_path is not None
|
||||
|
||||
# Create new image for upload
|
||||
@@ -202,8 +202,8 @@ def test_user_profile_photo_form_save(user):
|
||||
content_type="image/jpeg",
|
||||
)
|
||||
|
||||
form = UserProfilePhotoForm({"image": new_image}, instance=user)
|
||||
form = UserProfilePhotoForm({"profile_image": new_image}, instance=user)
|
||||
assert form.is_valid()
|
||||
updated_user = form.save()
|
||||
updated_user.refresh_from_db()
|
||||
assert str(user.pk) in updated_user.image.path
|
||||
assert str(user.pk) in updated_user.profile_image.path
|
||||
|
||||
@@ -40,7 +40,7 @@ def test_user_model_image_validator(user):
|
||||
valid_image = SimpleUploadedFile(
|
||||
"test.jpg", b"file_content", content_type="image/jpeg"
|
||||
)
|
||||
user.image = valid_image
|
||||
user.profile_image = valid_image
|
||||
# This should not raise any errors
|
||||
user.full_clean()
|
||||
|
||||
@@ -48,7 +48,7 @@ def test_user_model_image_validator(user):
|
||||
invalid_image = SimpleUploadedFile(
|
||||
"test.pdf", b"file_content", content_type="application/pdf"
|
||||
)
|
||||
user.image = invalid_image
|
||||
user.profile_image = invalid_image
|
||||
# This should raise a ValidationError
|
||||
with pytest.raises(ValidationError):
|
||||
user.full_clean()
|
||||
@@ -61,7 +61,7 @@ def test_user_model_image_file_size(user):
|
||||
valid_image = SimpleUploadedFile(
|
||||
"test.jpg", b"a" * (1 * 1024 * 1024 - 1), content_type="image/jpeg"
|
||||
)
|
||||
user.image = valid_image
|
||||
user.profile_image = valid_image
|
||||
# This should not raise any errors
|
||||
user.full_clean()
|
||||
|
||||
@@ -69,7 +69,7 @@ def test_user_model_image_file_size(user):
|
||||
invalid_image = SimpleUploadedFile(
|
||||
"too_large.jpg", b"a" * (1 * 1024 * 1024 + 1), content_type="image/jpeg"
|
||||
)
|
||||
user.image = invalid_image
|
||||
user.profile_image = invalid_image
|
||||
# This should raise a ValidationError for file size
|
||||
with pytest.raises(ValidationError):
|
||||
user.full_clean()
|
||||
|
||||
@@ -130,7 +130,7 @@ def test_current_user_profile_view_post_valid_photo(user, tp):
|
||||
)
|
||||
assert response.status_code == 200
|
||||
user.refresh_from_db()
|
||||
assert user.image
|
||||
assert user.profile_image
|
||||
# confirm that password was not changed, as these are on the same screen
|
||||
assert user.check_password("password")
|
||||
|
||||
|
||||
171
yarn.lock
171
yarn.lock
@@ -24,9 +24,9 @@
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@svgdotjs/svg.js@^3.2.4":
|
||||
version "3.2.4"
|
||||
resolved "https://registry.npmjs.org/@svgdotjs/svg.js/-/svg.js-3.2.4.tgz"
|
||||
integrity sha512-BjJ/7vWNowlX3Z8O4ywT58DqbNRyYlkk6Yz/D13aB7hGmfQTvGX4Tkgtm/ApYlu9M7lCQi15xUEidqMUmdMYwg==
|
||||
version "3.2.5"
|
||||
resolved "https://registry.npmjs.org/@svgdotjs/svg.js/-/svg.js-3.2.5.tgz"
|
||||
integrity sha512-/VNHWYhNu+BS7ktbYoVGrCmsXDh+chFMaONMwGNdIBcFHrWqk2jY8fNyr3DLdtQUIalvkPfM554ZSFa3dm3nxQ==
|
||||
|
||||
"@tailwindcss/forms@0.5.3":
|
||||
version "0.5.3"
|
||||
@@ -72,9 +72,9 @@ acorn@^7.0.0:
|
||||
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
||||
|
||||
alpinejs@^3.10.2:
|
||||
version "3.14.1"
|
||||
resolved "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.1.tgz"
|
||||
integrity sha512-ICar8UsnRZAYvv/fCNfNeKMXNoXGUfwHrjx7LqXd08zIP95G2d9bAOuaL97re+1mgt/HojqHsfdOLo/A5LuWgQ==
|
||||
version "3.15.1"
|
||||
resolved "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.1.tgz"
|
||||
integrity sha512-HLO1TtiE92VajFHtLLPK8BWaK1YepV/uj31UrfoGnQ00lyFOJZ+oVY3F0DghPAwvg8sLU79pmjGQSytERa2gEg==
|
||||
dependencies:
|
||||
"@vue/reactivity" "~3.1.1"
|
||||
|
||||
@@ -92,17 +92,22 @@ arg@^5.0.2:
|
||||
integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==
|
||||
|
||||
autoprefixer@^10.4.12:
|
||||
version "10.4.20"
|
||||
resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz"
|
||||
integrity sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==
|
||||
version "10.4.22"
|
||||
resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz"
|
||||
integrity sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==
|
||||
dependencies:
|
||||
browserslist "^4.23.3"
|
||||
caniuse-lite "^1.0.30001646"
|
||||
fraction.js "^4.3.7"
|
||||
browserslist "^4.27.0"
|
||||
caniuse-lite "^1.0.30001754"
|
||||
fraction.js "^5.3.4"
|
||||
normalize-range "^0.1.2"
|
||||
picocolors "^1.0.1"
|
||||
picocolors "^1.1.1"
|
||||
postcss-value-parser "^4.2.0"
|
||||
|
||||
baseline-browser-mapping@^2.8.25:
|
||||
version "2.8.27"
|
||||
resolved "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.27.tgz"
|
||||
integrity sha512-2CXFpkjVnY2FT+B6GrSYxzYf65BJWEqz5tIRHCvNsZZ2F3CmsCB37h8SpYgKG7y9C4YAeTipIPWG7EmFmhAeXA==
|
||||
|
||||
binary-extensions@^2.0.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz"
|
||||
@@ -120,15 +125,16 @@ braces@^3.0.3, braces@~3.0.2:
|
||||
dependencies:
|
||||
fill-range "^7.1.1"
|
||||
|
||||
browserslist@^4.0.0, browserslist@^4.21.4, browserslist@^4.23.3, "browserslist@>= 4.21.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz"
|
||||
integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==
|
||||
browserslist@^4.0.0, browserslist@^4.21.4, browserslist@^4.27.0, "browserslist@>= 4.21.0":
|
||||
version "4.28.0"
|
||||
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz"
|
||||
integrity sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30001663"
|
||||
electron-to-chromium "^1.5.28"
|
||||
node-releases "^2.0.18"
|
||||
update-browserslist-db "^1.1.0"
|
||||
baseline-browser-mapping "^2.8.25"
|
||||
caniuse-lite "^1.0.30001754"
|
||||
electron-to-chromium "^1.5.249"
|
||||
node-releases "^2.0.27"
|
||||
update-browserslist-db "^1.1.4"
|
||||
|
||||
camelcase-css@^2.0.1:
|
||||
version "2.0.1"
|
||||
@@ -145,10 +151,10 @@ caniuse-api@^3.0.0:
|
||||
lodash.memoize "^4.1.2"
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001663:
|
||||
version "1.0.30001667"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz"
|
||||
integrity sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001754:
|
||||
version "1.0.30001754"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz"
|
||||
integrity sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==
|
||||
|
||||
chokidar@^3.5.3:
|
||||
version "3.6.0"
|
||||
@@ -205,9 +211,9 @@ css-tree@^1.1.2, css-tree@^1.1.3:
|
||||
source-map "^0.6.1"
|
||||
|
||||
css-what@^6.0.1:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz"
|
||||
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
|
||||
version "6.2.2"
|
||||
resolved "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz"
|
||||
integrity sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==
|
||||
|
||||
cssesc@^3.0.0:
|
||||
version "3.0.0"
|
||||
@@ -324,10 +330,10 @@ domutils@^2.8.0:
|
||||
domelementtype "^2.2.0"
|
||||
domhandler "^4.2.0"
|
||||
|
||||
electron-to-chromium@^1.5.28:
|
||||
version "1.5.34"
|
||||
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.34.tgz"
|
||||
integrity sha512-/TZAiChbAflBNjCg+VvstbcwAtIL/VdMFO3NgRFIzBjpvPzWOTIbbO8kNb6RwU4bt9TP7K+3KqBKw/lOU+Y+GA==
|
||||
electron-to-chromium@^1.5.249:
|
||||
version "1.5.250"
|
||||
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.250.tgz"
|
||||
integrity sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==
|
||||
|
||||
entities@^2.0.0:
|
||||
version "2.2.0"
|
||||
@@ -340,20 +346,20 @@ escalade@^3.2.0:
|
||||
integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
|
||||
|
||||
fast-glob@^3.2.12:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz"
|
||||
integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
|
||||
version "3.3.3"
|
||||
resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz"
|
||||
integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==
|
||||
dependencies:
|
||||
"@nodelib/fs.stat" "^2.0.2"
|
||||
"@nodelib/fs.walk" "^1.2.3"
|
||||
glob-parent "^5.1.2"
|
||||
merge2 "^1.3.0"
|
||||
micromatch "^4.0.4"
|
||||
micromatch "^4.0.8"
|
||||
|
||||
fastq@^1.6.0:
|
||||
version "1.17.1"
|
||||
resolved "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz"
|
||||
integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==
|
||||
version "1.19.1"
|
||||
resolved "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz"
|
||||
integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==
|
||||
dependencies:
|
||||
reusify "^1.0.4"
|
||||
|
||||
@@ -364,10 +370,10 @@ fill-range@^7.1.1:
|
||||
dependencies:
|
||||
to-regex-range "^5.0.1"
|
||||
|
||||
fraction.js@^4.3.7:
|
||||
version "4.3.7"
|
||||
resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz"
|
||||
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
|
||||
fraction.js@^5.3.4:
|
||||
version "5.3.4"
|
||||
resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz"
|
||||
integrity sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==
|
||||
|
||||
fsevents@~2.3.2:
|
||||
version "2.3.3"
|
||||
@@ -419,10 +425,10 @@ is-binary-path@~2.1.0:
|
||||
dependencies:
|
||||
binary-extensions "^2.0.0"
|
||||
|
||||
is-core-module@^2.13.0:
|
||||
version "2.15.1"
|
||||
resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz"
|
||||
integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==
|
||||
is-core-module@^2.16.1:
|
||||
version "2.16.1"
|
||||
resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz"
|
||||
integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==
|
||||
dependencies:
|
||||
hasown "^2.0.2"
|
||||
|
||||
@@ -458,6 +464,11 @@ lodash.uniq@^4.5.0:
|
||||
resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz"
|
||||
integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==
|
||||
|
||||
marked@^17.0.0:
|
||||
version "17.0.0"
|
||||
resolved "https://registry.npmjs.org/marked/-/marked-17.0.0.tgz"
|
||||
integrity sha512-KkDYEWEEiYJw/KC+DVm1zzlpMQSMIu6YRltkcCvwheCp8HWPXCk9JwOmHJKBlGfzcpzcIt6x3sMnTsRm/51oDg==
|
||||
|
||||
mdn-data@2.0.14:
|
||||
version "2.0.14"
|
||||
resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz"
|
||||
@@ -468,7 +479,7 @@ merge2@^1.3.0:
|
||||
resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
micromatch@^4.0.4, micromatch@^4.0.5:
|
||||
micromatch@^4.0.5, micromatch@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz"
|
||||
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
|
||||
@@ -486,15 +497,15 @@ minimist@^1.2.6:
|
||||
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz"
|
||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||
|
||||
nanoid@^3.3.7:
|
||||
version "3.3.7"
|
||||
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz"
|
||||
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
|
||||
nanoid@^3.3.11:
|
||||
version "3.3.11"
|
||||
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz"
|
||||
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
|
||||
|
||||
node-releases@^2.0.18:
|
||||
version "2.0.18"
|
||||
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz"
|
||||
integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==
|
||||
node-releases@^2.0.27:
|
||||
version "2.0.27"
|
||||
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz"
|
||||
integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==
|
||||
|
||||
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
version "3.0.0"
|
||||
@@ -528,10 +539,10 @@ path-parse@^1.0.7:
|
||||
resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
||||
picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz"
|
||||
integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==
|
||||
picocolors@^1.0.0, picocolors@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz"
|
||||
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
||||
version "2.3.1"
|
||||
@@ -599,9 +610,9 @@ postcss-import@^14.1.0:
|
||||
resolve "^1.1.7"
|
||||
|
||||
postcss-js@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz"
|
||||
integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz"
|
||||
integrity sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==
|
||||
dependencies:
|
||||
camelcase-css "^2.0.1"
|
||||
|
||||
@@ -785,12 +796,12 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
|
||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||
|
||||
postcss@^8.0.0, postcss@^8.0.9, postcss@^8.1.0, postcss@^8.2.14, postcss@^8.2.15, postcss@^8.2.2, postcss@^8.4.17, postcss@^8.4.21, postcss@>=8.0.9:
|
||||
version "8.4.47"
|
||||
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz"
|
||||
integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==
|
||||
version "8.5.6"
|
||||
resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz"
|
||||
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
|
||||
dependencies:
|
||||
nanoid "^3.3.7"
|
||||
picocolors "^1.1.0"
|
||||
nanoid "^3.3.11"
|
||||
picocolors "^1.1.1"
|
||||
source-map-js "^1.2.1"
|
||||
|
||||
queue-microtask@^1.2.2:
|
||||
@@ -818,18 +829,18 @@ readdirp@~3.6.0:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
resolve@^1.1.7, resolve@^1.22.1:
|
||||
version "1.22.8"
|
||||
resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz"
|
||||
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
|
||||
version "1.22.11"
|
||||
resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz"
|
||||
integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==
|
||||
dependencies:
|
||||
is-core-module "^2.13.0"
|
||||
is-core-module "^2.16.1"
|
||||
path-parse "^1.0.7"
|
||||
supports-preserve-symlinks-flag "^1.0.0"
|
||||
|
||||
reusify@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz"
|
||||
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz"
|
||||
integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==
|
||||
|
||||
run-parallel@^1.1.9:
|
||||
version "1.2.0"
|
||||
@@ -915,13 +926,13 @@ to-regex-range@^5.0.1:
|
||||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
update-browserslist-db@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz"
|
||||
integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==
|
||||
update-browserslist-db@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz"
|
||||
integrity sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==
|
||||
dependencies:
|
||||
escalade "^3.2.0"
|
||||
picocolors "^1.1.0"
|
||||
picocolors "^1.1.1"
|
||||
|
||||
util-deprecate@^1.0.2:
|
||||
version "1.0.2"
|
||||
|
||||
Reference in New Issue
Block a user