wagtail-cjkcms 24.11.1__py2.py3-none-any.whl → 24.12.2__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cjkcms/.DS_Store +0 -0
- cjkcms/__init__.py +1 -1
- cjkcms/blocks/__init__.py +2 -0
- cjkcms/blocks/content/countdown.py +116 -0
- cjkcms/static/vendor/simplycountdown/css/circle.css +73 -0
- cjkcms/static/vendor/simplycountdown/css/cyber.css +155 -0
- cjkcms/static/vendor/simplycountdown/css/dark.css +85 -0
- cjkcms/static/vendor/simplycountdown/css/light.css +85 -0
- cjkcms/static/vendor/simplycountdown/css/losange.css +83 -0
- cjkcms/static/vendor/simplycountdown/js/simplyCountdown.umd.js +2 -0
- cjkcms/static/vendor/simplycountdown/js/simplyCountdown.umd.js.map +1 -0
- cjkcms/templates/.DS_Store +0 -0
- cjkcms/templates/cjkcms/.DS_Store +0 -0
- cjkcms/templates/cjkcms/blocks/button_block.html +1 -1
- cjkcms/templates/cjkcms/blocks/countdown.html +24 -0
- cjkcms/templatetags/cjkcms_tags.py +5 -0
- cjkcms/tests/test_countdown_block.py +100 -0
- {wagtail_cjkcms-24.11.1.dist-info → wagtail_cjkcms-24.12.2.dist-info}/METADATA +1 -1
- {wagtail_cjkcms-24.11.1.dist-info → wagtail_cjkcms-24.12.2.dist-info}/RECORD +23 -18
- cjkcms/media/images/test.original.png +0 -0
- cjkcms/media/original_images/test.png +0 -0
- cjkcms/tests/media/images/test_oKdmwQn.original.png +0 -0
- cjkcms/tests/media/images/test_otfQve2.original.png +0 -0
- cjkcms/tests/media/images/test_qFhTLcX.original.png +0 -0
- cjkcms/tests/media/original_images/test_oKdmwQn.png +0 -0
- cjkcms/tests/media/original_images/test_otfQve2.png +0 -0
- cjkcms/tests/media/original_images/test_qFhTLcX.png +0 -0
- {wagtail_cjkcms-24.11.1.dist-info → wagtail_cjkcms-24.12.2.dist-info}/LICENSE +0 -0
- {wagtail_cjkcms-24.11.1.dist-info → wagtail_cjkcms-24.12.2.dist-info}/WHEEL +0 -0
- {wagtail_cjkcms-24.11.1.dist-info → wagtail_cjkcms-24.12.2.dist-info}/entry_points.txt +0 -0
- {wagtail_cjkcms-24.11.1.dist-info → wagtail_cjkcms-24.12.2.dist-info}/top_level.txt +0 -0
cjkcms/.DS_Store
ADDED
Binary file
|
cjkcms/__init__.py
CHANGED
cjkcms/blocks/__init__.py
CHANGED
@@ -34,6 +34,7 @@ from .content_blocks import ( # noqa
|
|
34
34
|
HighlightBlock,
|
35
35
|
)
|
36
36
|
from .content.events import PublicEventBlock, EventCalendarBlock
|
37
|
+
from .content.countdown import CountdownBlock
|
37
38
|
from .layout_blocks import CardGridBlock, GridBlock, HeroBlock
|
38
39
|
from cjkcms.settings import cms_settings
|
39
40
|
|
@@ -77,6 +78,7 @@ CONTENT_STREAMBLOCKS = HTML_STREAMBLOCKS + [
|
|
77
78
|
("reusable_content", ReusableContentBlock()),
|
78
79
|
("event_calendar", EventCalendarBlock()),
|
79
80
|
("highlight", HighlightBlock()),
|
81
|
+
("countdown", CountdownBlock()),
|
80
82
|
]
|
81
83
|
|
82
84
|
NAVIGATION_STREAMBLOCKS = [
|
@@ -0,0 +1,116 @@
|
|
1
|
+
from cjkcms.blocks.base_blocks import BaseBlock
|
2
|
+
from wagtail import blocks
|
3
|
+
from django.utils.translation import gettext_lazy as _
|
4
|
+
from datetime import datetime, timedelta, timezone
|
5
|
+
import re
|
6
|
+
|
7
|
+
|
8
|
+
def convert_to_utc(naive_dt, utc_offset_str):
|
9
|
+
# Parse the UTC offset string (e.g., "UTC+05:30" or "UTC-02:00")
|
10
|
+
match = re.match(r"UTC([+-])(\d{2}):(\d{2})", utc_offset_str)
|
11
|
+
if not match:
|
12
|
+
raise ValueError("Invalid UTC offset format. Use 'UTC±HH:MM'.")
|
13
|
+
|
14
|
+
sign = match.group(1)
|
15
|
+
hours = int(match.group(2))
|
16
|
+
minutes = int(match.group(3))
|
17
|
+
|
18
|
+
# Calculate the total offset in minutes
|
19
|
+
total_offset = timedelta(hours=hours, minutes=minutes)
|
20
|
+
if sign == "-":
|
21
|
+
total_offset = -total_offset
|
22
|
+
|
23
|
+
# Create a timezone with the given offset
|
24
|
+
tz = timezone(total_offset)
|
25
|
+
|
26
|
+
# Attach the offset to the naive datetime, making it timezone-aware
|
27
|
+
aware_local_dt = naive_dt.replace(tzinfo=tz)
|
28
|
+
|
29
|
+
# Convert the timezone-aware datetime to UTC
|
30
|
+
utc_dt = aware_local_dt.astimezone(timezone.utc)
|
31
|
+
|
32
|
+
return utc_dt
|
33
|
+
|
34
|
+
|
35
|
+
class CountdownBlock(BaseBlock):
|
36
|
+
"""
|
37
|
+
Display a countdown to a specific date
|
38
|
+
"""
|
39
|
+
|
40
|
+
title = blocks.CharBlock(
|
41
|
+
required=False,
|
42
|
+
max_length=255,
|
43
|
+
label=_("Title"),
|
44
|
+
)
|
45
|
+
|
46
|
+
start_date = blocks.DateTimeBlock(
|
47
|
+
required=True,
|
48
|
+
label=_("Start date and time"),
|
49
|
+
help_text=_("Format: YYYY-MM-DD HH:MM"),
|
50
|
+
)
|
51
|
+
|
52
|
+
timezone = blocks.CharBlock(
|
53
|
+
required=True,
|
54
|
+
default="UTC",
|
55
|
+
max_length=255,
|
56
|
+
label=_("Timezone"),
|
57
|
+
help_text=_("Timezone relative to UTC, e.g. UTC+01:00"),
|
58
|
+
)
|
59
|
+
|
60
|
+
url = blocks.URLBlock(
|
61
|
+
max_length=255,
|
62
|
+
label=_("Link"),
|
63
|
+
required=False,
|
64
|
+
help_text=_("Optional link"),
|
65
|
+
)
|
66
|
+
|
67
|
+
# hide_after_end_date = blocks.BooleanBlock(
|
68
|
+
# required=False,
|
69
|
+
# default=False,
|
70
|
+
# label=_("Hide after end date"),
|
71
|
+
# help_text=_("Hide this after end date, or show zeros if false"),
|
72
|
+
# )
|
73
|
+
|
74
|
+
theme = blocks.ChoiceBlock(
|
75
|
+
choices=[
|
76
|
+
("light", _("Light")),
|
77
|
+
("dark", _("Dark")),
|
78
|
+
("cyber", _("Cyberpunk")),
|
79
|
+
("losange", _("Losange")),
|
80
|
+
("circle", _("Circle")),
|
81
|
+
# ("flipbook", _("Flipbook")),
|
82
|
+
],
|
83
|
+
default="light",
|
84
|
+
label=_("Theme"),
|
85
|
+
)
|
86
|
+
|
87
|
+
def get_context(self, value, parent_context=None):
|
88
|
+
context = super().get_context(value, parent_context=parent_context)
|
89
|
+
sd = value.get("start_date")
|
90
|
+
|
91
|
+
naive_datetime = datetime(
|
92
|
+
sd.year, sd.month, sd.day, sd.hour, sd.minute
|
93
|
+
) # Naive datetime
|
94
|
+
|
95
|
+
tz = value.get("timezone")
|
96
|
+
if tz == "UTC" or tz == "":
|
97
|
+
tz = "UTC+00:00"
|
98
|
+
|
99
|
+
utc_offset_string = tz # String containing UTC offset
|
100
|
+
utc = convert_to_utc(naive_datetime, utc_offset_string)
|
101
|
+
|
102
|
+
# print(naive_datetime, utc, utc_offset_string)
|
103
|
+
context["year"] = utc.year
|
104
|
+
context["month"] = utc.month
|
105
|
+
context["day"] = utc.day
|
106
|
+
context["hour"] = utc.hour
|
107
|
+
context["minute"] = utc.minute
|
108
|
+
context["second"] = 0
|
109
|
+
return context
|
110
|
+
|
111
|
+
class Meta:
|
112
|
+
template = "cjkcms/blocks/countdown.html"
|
113
|
+
icon = "history"
|
114
|
+
label = "Countdown"
|
115
|
+
ordering = ["start_date"]
|
116
|
+
label_format = _("Countdown to {start_date}")
|
@@ -0,0 +1,73 @@
|
|
1
|
+
.simply-countdown-circle {
|
2
|
+
--sc-circle-primary: #6366f1;
|
3
|
+
--sc-circle-secondary: #818cf8;
|
4
|
+
--sc-circle-bg: #1e1b4b;
|
5
|
+
--sc-circle-text: #fff;
|
6
|
+
|
7
|
+
display: flex;
|
8
|
+
flex-wrap: wrap;
|
9
|
+
justify-content: center;
|
10
|
+
gap: 1.5rem;
|
11
|
+
font-family: "Inter", sans-serif;
|
12
|
+
}
|
13
|
+
|
14
|
+
.simply-countdown-circle > .simply-section {
|
15
|
+
position: relative;
|
16
|
+
width: 100px;
|
17
|
+
height: 100px;
|
18
|
+
padding: 1rem;
|
19
|
+
display: flex;
|
20
|
+
align-items: center;
|
21
|
+
justify-content: center;
|
22
|
+
flex-direction: column;
|
23
|
+
border-radius: 50%;
|
24
|
+
background: linear-gradient(45deg, var(--sc-circle-primary), var(--sc-circle-secondary));
|
25
|
+
box-shadow: 0 0 25px -5px var(--sc-circle-primary);
|
26
|
+
animation: pulse-circle 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
27
|
+
}
|
28
|
+
|
29
|
+
.simply-countdown-circle > .simply-section::before {
|
30
|
+
content: "";
|
31
|
+
position: absolute;
|
32
|
+
inset: 6px;
|
33
|
+
border-radius: 50%;
|
34
|
+
background: var(--sc-circle-bg);
|
35
|
+
z-index: 0;
|
36
|
+
}
|
37
|
+
|
38
|
+
.simply-countdown-circle > .simply-section > div {
|
39
|
+
position: relative;
|
40
|
+
z-index: 1;
|
41
|
+
color: var(--sc-circle-text);
|
42
|
+
text-align: center;
|
43
|
+
}
|
44
|
+
|
45
|
+
.simply-countdown-circle .simply-amount {
|
46
|
+
display: block;
|
47
|
+
font-size: 1.75rem;
|
48
|
+
font-weight: 700;
|
49
|
+
line-height: 1;
|
50
|
+
background: linear-gradient(to right, var(--sc-circle-primary), var(--sc-circle-secondary));
|
51
|
+
-webkit-background-clip: text;
|
52
|
+
background-clip: text;
|
53
|
+
-webkit-text-fill-color: transparent;
|
54
|
+
}
|
55
|
+
|
56
|
+
.simply-countdown-circle .simply-word {
|
57
|
+
font-size: 0.7rem;
|
58
|
+
text-transform: uppercase;
|
59
|
+
letter-spacing: 0.05em;
|
60
|
+
opacity: 0.8;
|
61
|
+
}
|
62
|
+
|
63
|
+
@keyframes pulse-circle {
|
64
|
+
0%,
|
65
|
+
100% {
|
66
|
+
transform: scale(1);
|
67
|
+
opacity: 1;
|
68
|
+
}
|
69
|
+
50% {
|
70
|
+
transform: scale(0.98);
|
71
|
+
opacity: 0.9;
|
72
|
+
}
|
73
|
+
}
|
@@ -0,0 +1,155 @@
|
|
1
|
+
/**
|
2
|
+
* Project : simply-countdown
|
3
|
+
* File : simplyCountdown.theme.cyberpunk
|
4
|
+
* Author : Vincent Loy <vincent.loy1@gmail.com>
|
5
|
+
* Theme : Modern Cyberpunk
|
6
|
+
*/
|
7
|
+
|
8
|
+
.simply-countdown-cyber {
|
9
|
+
overflow: visible;
|
10
|
+
display: flex;
|
11
|
+
flex-wrap: wrap;
|
12
|
+
justify-content: center;
|
13
|
+
gap: 1.75rem;
|
14
|
+
font-family: "Inter", system-ui, -apple-system, sans-serif;
|
15
|
+
perspective: 1000px;
|
16
|
+
}
|
17
|
+
|
18
|
+
.simply-countdown-cyber > .simply-section {
|
19
|
+
width: 70px;
|
20
|
+
height: 70px;
|
21
|
+
padding: 1.5rem;
|
22
|
+
position: relative;
|
23
|
+
display: flex;
|
24
|
+
align-items: center;
|
25
|
+
justify-content: center;
|
26
|
+
background: linear-gradient(135deg, rgba(23, 25, 35, 0.9), rgba(15, 17, 25, 0.95));
|
27
|
+
border-radius: 0.5rem;
|
28
|
+
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
29
|
+
backdrop-filter: blur(12px);
|
30
|
+
transform-style: preserve-3d;
|
31
|
+
}
|
32
|
+
|
33
|
+
.simply-countdown-cyber > .simply-section::before {
|
34
|
+
content: "";
|
35
|
+
position: absolute;
|
36
|
+
inset: -1px;
|
37
|
+
background: linear-gradient(135deg, rgba(120, 240, 255, 0.2), rgba(255, 90, 220, 0.2));
|
38
|
+
border-radius: 0.5rem;
|
39
|
+
z-index: -1;
|
40
|
+
opacity: 0;
|
41
|
+
transition: opacity 0.3s ease;
|
42
|
+
}
|
43
|
+
|
44
|
+
.simply-countdown-cyber > .simply-section::after {
|
45
|
+
content: "";
|
46
|
+
position: absolute;
|
47
|
+
inset: -2px;
|
48
|
+
background: linear-gradient(135deg, #78f0ff, #ff5adc);
|
49
|
+
border-radius: 0.5rem;
|
50
|
+
z-index: -2;
|
51
|
+
opacity: 0.15;
|
52
|
+
filter: blur(4px);
|
53
|
+
animation: pulse 4s ease-in-out infinite;
|
54
|
+
}
|
55
|
+
|
56
|
+
.simply-countdown-cyber > .simply-section .glass-overlay {
|
57
|
+
position: absolute;
|
58
|
+
inset: 0;
|
59
|
+
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05));
|
60
|
+
border-radius: 0.5rem;
|
61
|
+
}
|
62
|
+
|
63
|
+
.simply-countdown-cyber > .simply-section:hover {
|
64
|
+
transform: translateY(-4px) translateZ(10px) rotateX(5deg);
|
65
|
+
box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.5), 0 0 20px rgba(120, 240, 255, 0.2), 0 0 0 1px rgba(120, 240, 255, 0.1);
|
66
|
+
}
|
67
|
+
|
68
|
+
.simply-countdown-cyber > .simply-section:hover::before {
|
69
|
+
opacity: 1;
|
70
|
+
}
|
71
|
+
|
72
|
+
.simply-countdown-cyber > .simply-section > div {
|
73
|
+
display: flex;
|
74
|
+
flex-direction: column;
|
75
|
+
gap: 0.4rem;
|
76
|
+
align-items: center;
|
77
|
+
transform-style: preserve-3d;
|
78
|
+
}
|
79
|
+
|
80
|
+
.simply-countdown-cyber > .simply-section .simply-amount {
|
81
|
+
font-size: 1.75rem;
|
82
|
+
font-weight: 700;
|
83
|
+
background: linear-gradient(to bottom right, #78f0ff, #ff5adc);
|
84
|
+
-webkit-background-clip: text;
|
85
|
+
background-clip: text;
|
86
|
+
color: transparent;
|
87
|
+
text-shadow: 0 0 20px rgba(120, 240, 255, 0.3), 0 0 40px rgba(120, 240, 255, 0.2);
|
88
|
+
letter-spacing: -0.02em;
|
89
|
+
transform: translateZ(10px);
|
90
|
+
}
|
91
|
+
|
92
|
+
.simply-countdown-cyber > .simply-section .simply-word {
|
93
|
+
font-size: 0.6rem;
|
94
|
+
font-weight: 500;
|
95
|
+
text-transform: uppercase;
|
96
|
+
letter-spacing: 0.2em;
|
97
|
+
color: rgba(255, 255, 255, 0.7);
|
98
|
+
transform: translateZ(5px);
|
99
|
+
position: relative;
|
100
|
+
}
|
101
|
+
|
102
|
+
.simply-countdown-cyber > .simply-section .simply-word::after {
|
103
|
+
content: "";
|
104
|
+
position: absolute;
|
105
|
+
left: -10%;
|
106
|
+
bottom: -4px;
|
107
|
+
width: 120%;
|
108
|
+
height: 1px;
|
109
|
+
background: linear-gradient(to right, rgba(120, 240, 255, 0), rgba(120, 240, 255, 0.5), rgba(255, 90, 220, 0.5), rgba(255, 90, 220, 0));
|
110
|
+
}
|
111
|
+
|
112
|
+
@media (min-width: 640px) {
|
113
|
+
.simply-countdown-cyber > .simply-section {
|
114
|
+
width: 80px;
|
115
|
+
height: 80px;
|
116
|
+
padding: 1.75rem;
|
117
|
+
}
|
118
|
+
|
119
|
+
.simply-countdown-cyber > .simply-section .simply-amount {
|
120
|
+
font-size: 2rem;
|
121
|
+
}
|
122
|
+
|
123
|
+
.simply-countdown-cyber > .simply-section .simply-word {
|
124
|
+
font-size: 0.75rem;
|
125
|
+
}
|
126
|
+
}
|
127
|
+
|
128
|
+
@media (min-width: 1024px) {
|
129
|
+
.simply-countdown-cyber > .simply-section {
|
130
|
+
width: 100px;
|
131
|
+
height: 100px;
|
132
|
+
padding: 2rem;
|
133
|
+
}
|
134
|
+
|
135
|
+
.simply-countdown-cyber > .simply-section .simply-amount {
|
136
|
+
font-size: 2.5rem;
|
137
|
+
}
|
138
|
+
|
139
|
+
.simply-countdown-cyber > .simply-section .simply-word {
|
140
|
+
font-size: 0.8rem;
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
/* Add subtle animation for extra futuristic feel */
|
145
|
+
@keyframes pulse {
|
146
|
+
0%,
|
147
|
+
100% {
|
148
|
+
opacity: 0.15;
|
149
|
+
transform: scale(1);
|
150
|
+
}
|
151
|
+
50% {
|
152
|
+
opacity: 0.25;
|
153
|
+
transform: scale(1.05);
|
154
|
+
}
|
155
|
+
}
|
@@ -0,0 +1,85 @@
|
|
1
|
+
/**
|
2
|
+
* Project : simply-countdown
|
3
|
+
* File : simplyCountdown.theme.dark
|
4
|
+
* Author : Vincent Loy <vincent.loy1@gmail.com>
|
5
|
+
* Theme : Dark Modern
|
6
|
+
*/
|
7
|
+
|
8
|
+
.simply-countdown-dark {
|
9
|
+
overflow: hidden;
|
10
|
+
display: flex;
|
11
|
+
flex-wrap: wrap;
|
12
|
+
justify-content: center;
|
13
|
+
gap: 1.25rem;
|
14
|
+
font-family: "Inter", system-ui, -apple-system, sans-serif;
|
15
|
+
}
|
16
|
+
|
17
|
+
.simply-countdown-dark > .simply-section {
|
18
|
+
width: 65px;
|
19
|
+
height: 65px;
|
20
|
+
padding: 1.5rem;
|
21
|
+
display: flex;
|
22
|
+
align-items: center;
|
23
|
+
justify-content: center;
|
24
|
+
background: rgba(15, 23, 42, 0.75);
|
25
|
+
border: 1px solid rgba(51, 65, 85, 0.6);
|
26
|
+
border-radius: 1rem;
|
27
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2), 0 2px 4px -1px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.05);
|
28
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
29
|
+
backdrop-filter: blur(10px);
|
30
|
+
}
|
31
|
+
|
32
|
+
.simply-countdown-dark > .simply-section > div {
|
33
|
+
display: flex;
|
34
|
+
flex-direction: column;
|
35
|
+
line-height: 1;
|
36
|
+
align-items: center;
|
37
|
+
}
|
38
|
+
|
39
|
+
.simply-countdown-dark > .simply-section .simply-amount {
|
40
|
+
font-size: 1.5rem;
|
41
|
+
font-weight: 700;
|
42
|
+
color: #f1f5f9;
|
43
|
+
line-height: 1.2;
|
44
|
+
letter-spacing: -0.025em;
|
45
|
+
}
|
46
|
+
|
47
|
+
.simply-countdown-dark > .simply-section .simply-word {
|
48
|
+
font-size: 0.6rem;
|
49
|
+
font-weight: 500;
|
50
|
+
color: #94a3b8;
|
51
|
+
text-transform: uppercase;
|
52
|
+
letter-spacing: 0.1em;
|
53
|
+
}
|
54
|
+
|
55
|
+
@media (min-width: 640px) {
|
56
|
+
.simply-countdown-dark > .simply-section {
|
57
|
+
width: 75px;
|
58
|
+
height: 75px;
|
59
|
+
padding: 1.75rem;
|
60
|
+
}
|
61
|
+
|
62
|
+
.simply-countdown-dark > .simply-section .simply-amount {
|
63
|
+
font-size: 1.75rem;
|
64
|
+
}
|
65
|
+
|
66
|
+
.simply-countdown-dark > .simply-section .simply-word {
|
67
|
+
font-size: 0.75rem;
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
@media (min-width: 1024px) {
|
72
|
+
.simply-countdown-dark > .simply-section {
|
73
|
+
width: 90px;
|
74
|
+
height: 90px;
|
75
|
+
padding: 2rem;
|
76
|
+
}
|
77
|
+
|
78
|
+
.simply-countdown-dark > .simply-section .simply-amount {
|
79
|
+
font-size: 2rem;
|
80
|
+
}
|
81
|
+
|
82
|
+
.simply-countdown-dark > .simply-section .simply-word {
|
83
|
+
font-size: 0.8rem;
|
84
|
+
}
|
85
|
+
}
|
@@ -0,0 +1,85 @@
|
|
1
|
+
/**
|
2
|
+
* Project : simply-countdown
|
3
|
+
* File : simplyCountdown.theme.default
|
4
|
+
* Author : Vincent Loy <vincent.loy1@gmail.com>
|
5
|
+
* Theme : Light Modern
|
6
|
+
*/
|
7
|
+
|
8
|
+
.simply-countdown-light {
|
9
|
+
overflow: hidden;
|
10
|
+
display: flex;
|
11
|
+
flex-wrap: wrap;
|
12
|
+
justify-content: center;
|
13
|
+
gap: 1.25rem;
|
14
|
+
font-family: "Inter", system-ui, -apple-system, sans-serif;
|
15
|
+
}
|
16
|
+
|
17
|
+
.simply-countdown-light > .simply-section {
|
18
|
+
width: 65px;
|
19
|
+
height: 65px;
|
20
|
+
padding: 1.5rem;
|
21
|
+
display: flex;
|
22
|
+
align-items: center;
|
23
|
+
justify-content: center;
|
24
|
+
background: rgba(255, 255, 255, 0.9);
|
25
|
+
border: 1px solid rgba(226, 232, 240, 0.8);
|
26
|
+
border-radius: 1rem;
|
27
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03), 0 0 0 1px rgba(0, 0, 0, 0.02);
|
28
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
29
|
+
backdrop-filter: blur(10px);
|
30
|
+
}
|
31
|
+
|
32
|
+
.simply-countdown-light > .simply-section > div {
|
33
|
+
display: flex;
|
34
|
+
flex-direction: column;
|
35
|
+
line-height: 1;
|
36
|
+
align-items: center;
|
37
|
+
}
|
38
|
+
|
39
|
+
.simply-countdown-light > .simply-section .simply-amount {
|
40
|
+
font-size: 1.5rem;
|
41
|
+
font-weight: 700;
|
42
|
+
color: #1e293b;
|
43
|
+
line-height: 1.2;
|
44
|
+
letter-spacing: -0.025em;
|
45
|
+
}
|
46
|
+
|
47
|
+
.simply-countdown-light > .simply-section .simply-word {
|
48
|
+
font-size: 0.6rem;
|
49
|
+
font-weight: 500;
|
50
|
+
color: #64748b;
|
51
|
+
text-transform: uppercase;
|
52
|
+
letter-spacing: 0.1em;
|
53
|
+
}
|
54
|
+
|
55
|
+
@media (min-width: 640px) {
|
56
|
+
.simply-countdown-light > .simply-section {
|
57
|
+
width: 75px;
|
58
|
+
height: 75px;
|
59
|
+
padding: 1.75rem;
|
60
|
+
}
|
61
|
+
|
62
|
+
.simply-countdown-light > .simply-section .simply-amount {
|
63
|
+
font-size: 1.75rem;
|
64
|
+
}
|
65
|
+
|
66
|
+
.simply-countdown-light > .simply-section .simply-word {
|
67
|
+
font-size: 0.75rem;
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
@media (min-width: 1024px) {
|
72
|
+
.simply-countdown-light > .simply-section {
|
73
|
+
width: 90px;
|
74
|
+
height: 90px;
|
75
|
+
padding: 2rem;
|
76
|
+
}
|
77
|
+
|
78
|
+
.simply-countdown-light > .simply-section .simply-amount {
|
79
|
+
font-size: 2rem;
|
80
|
+
}
|
81
|
+
|
82
|
+
.simply-countdown-light > .simply-section .simply-word {
|
83
|
+
font-size: 0.8rem;
|
84
|
+
}
|
85
|
+
}
|
@@ -0,0 +1,83 @@
|
|
1
|
+
/**
|
2
|
+
* Project : simply-countdown
|
3
|
+
* File : simplyCountdown.theme.losange
|
4
|
+
* Author : Vincent Loy <vincent.loy1@gmail.com>
|
5
|
+
*/
|
6
|
+
|
7
|
+
.simply-countdown-losange {
|
8
|
+
overflow: visible;
|
9
|
+
display: flex;
|
10
|
+
flex-wrap: wrap;
|
11
|
+
justify-content: center;
|
12
|
+
gap: 3rem;
|
13
|
+
font-family: "Inter", sans-serif;
|
14
|
+
}
|
15
|
+
|
16
|
+
.simply-countdown-losange > .simply-section {
|
17
|
+
width: 70px;
|
18
|
+
height: 70px;
|
19
|
+
display: flex;
|
20
|
+
justify-content: center;
|
21
|
+
align-items: center;
|
22
|
+
transform: rotate(-45deg);
|
23
|
+
background: linear-gradient(135deg, #4f46e5, #7c3aed);
|
24
|
+
border-radius: 0.5rem;
|
25
|
+
transition: all 0.2s ease-in-out;
|
26
|
+
}
|
27
|
+
|
28
|
+
.simply-countdown-losange > .simply-section > div {
|
29
|
+
transform: rotate(45deg);
|
30
|
+
display: flex;
|
31
|
+
flex-direction: column;
|
32
|
+
line-height: 1.2;
|
33
|
+
}
|
34
|
+
|
35
|
+
.simply-countdown-losange > .simply-section .simply-amount,
|
36
|
+
.simply-countdown-losange > .simply-section .simply-word {
|
37
|
+
display: block;
|
38
|
+
text-align: center;
|
39
|
+
}
|
40
|
+
|
41
|
+
.simply-countdown-losange > .simply-section .simply-amount {
|
42
|
+
font-size: 1.25rem;
|
43
|
+
font-weight: 700;
|
44
|
+
color: #fff;
|
45
|
+
}
|
46
|
+
|
47
|
+
.simply-countdown-losange > .simply-section .simply-word {
|
48
|
+
font-size: 0.65rem;
|
49
|
+
font-weight: 500;
|
50
|
+
color: rgba(255, 255, 255, 0.9);
|
51
|
+
text-transform: uppercase;
|
52
|
+
letter-spacing: 0.05em;
|
53
|
+
}
|
54
|
+
|
55
|
+
@media (min-width: 640px) {
|
56
|
+
.simply-countdown-losange > .simply-section {
|
57
|
+
width: 80px;
|
58
|
+
height: 80px;
|
59
|
+
}
|
60
|
+
|
61
|
+
.simply-countdown-losange > .simply-section .simply-amount {
|
62
|
+
font-size: 1.5rem;
|
63
|
+
}
|
64
|
+
|
65
|
+
.simply-countdown-losange > .simply-section .simply-word {
|
66
|
+
font-size: 0.7rem;
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
@media (min-width: 1024px) {
|
71
|
+
.simply-countdown-losange > .simply-section {
|
72
|
+
width: 90px;
|
73
|
+
height: 90px;
|
74
|
+
}
|
75
|
+
|
76
|
+
.simply-countdown-losange > .simply-section .simply-amount {
|
77
|
+
font-size: 1.75rem;
|
78
|
+
}
|
79
|
+
|
80
|
+
.simply-countdown-losange > .simply-section .simply-word {
|
81
|
+
font-size: 0.75rem;
|
82
|
+
}
|
83
|
+
}
|
@@ -0,0 +1,2 @@
|
|
1
|
+
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define("simplyCountdown",n):(e="undefined"!=typeof globalThis?globalThis:e||self).simplyCountdown=n()}(this,(function(){"use strict";const e=(e,n,t,o,s,a)=>{const l=document.createElement("div");l.className=`${e} ${a.sectionClass}`;const r=document.createElement("div"),d=document.createElement("span"),i=document.createElement("span");return d.className=`${n} ${a.amountClass}`,i.className=`${t} ${a.wordClass}`,d.textContent=String(o),i.textContent=s,r.appendChild(d),r.appendChild(i),l.appendChild(r),l},n={year:2024,month:12,day:25,hours:0,minutes:0,seconds:0,words:{days:{lambda:(e,n)=>n>1?e+"s":e,root:"day"},hours:{lambda:(e,n)=>n>1?e+"s":e,root:"hour"},minutes:{lambda:(e,n)=>n>1?e+"s":e,root:"minute"},seconds:{lambda:(e,n)=>n>1?e+"s":e,root:"second"}},plural:!0,inline:!1,inlineSeparator:", ",enableUtc:!1,onEnd:()=>{},refresh:1e3,inlineClass:"simply-countdown-inline",sectionClass:"simply-section",amountClass:"simply-amount",wordClass:"simply-word",zeroPad:!1,countUp:!1,removeZeroUnits:!1,onStop:()=>{},onResume:()=>{},onUpdate:()=>{}};function t(e,n,t){return!t.removeZeroUnits||(0!==e.value||n.some((e=>0!==e.value)))}function o(e,n,o){const s=e.filter(((o,s)=>t(o,e.slice(0,s),n))).map((e=>function(e,n){return`${n.zeroPad?String(e.value).padStart(2,"0"):e.value} ${n.words[e.word].lambda(n.words[e.word].root,e.value)}`}(e,n))).join(n.inlineSeparator);o.innerHTML=s}function s(e,n,o){e.forEach(((s,a)=>{"seconds"===s.word||t(s,e.slice(0,a),n)?(((e,n,t)=>{const o=e.querySelector(".simply-amount"),s=e.querySelector(".simply-word");o&&(o.textContent=String(n)),s&&(s.textContent=t)})(o[s.word],n.zeroPad?String(s.value).padStart(2,"0"):s.value,n.words[s.word].lambda(n.words[s.word].root,s.value)),o[s.word].style.display=""):o[s.word].style.display="none"}))}const a=(n,t)=>{let a={isPaused:!1,interval:null,targetDate:new Date};const l=e=>e.enableUtc?new Date(Date.UTC(e.year,e.month-1,e.day,e.hours,e.minutes,e.seconds)):new Date(e.year,e.month-1,e.day,e.hours,e.minutes,e.seconds);a.targetDate=l(t);let r=null;t.inline&&(r=document.createElement("span"),r.className=t.inlineClass,n.appendChild(r));const d=t.inline?null:((n,t)=>{const o="simply-amount",s="simply-word",a=e("simply-section simply-days-section",o,s,0,"day",t),l=e("simply-section simply-hours-section",o,s,0,"hour",t),r=e("simply-section simply-minutes-section",o,s,0,"minute",t),d=e("simply-section simply-seconds-section",o,s,0,"second",t);return n.appendChild(a),n.appendChild(l),n.appendChild(r),n.appendChild(d),{days:a,hours:l,minutes:r,seconds:d}})(n,{sectionClass:t.sectionClass,amountClass:t.amountClass,wordClass:t.wordClass}),i=()=>{const e=t.enableUtc?new Date(Date.UTC((new Date).getUTCFullYear(),(new Date).getUTCMonth(),(new Date).getUTCDate(),(new Date).getUTCHours(),(new Date).getUTCMinutes(),(new Date).getUTCSeconds())):new Date;let n=t.countUp?e.getTime()-a.targetDate.getTime():a.targetDate.getTime()-e.getTime();n<=0&&!t.countUp&&(n=0,null!==a.interval&&clearInterval(a.interval),t.onEnd&&t.onEnd());const l=Math.floor(n/864e5);n-=1e3*l*60*60*24;const i=Math.floor(n/36e5);n-=1e3*i*60*60;const u=Math.floor(n/6e4);n-=1e3*u*60;const c=Math.floor(n/1e3);if(t.inline&&r){o([{value:l,word:"days"},{value:i,word:"hours"},{value:u,word:"minutes"},{value:c,word:"seconds"}],t,r)}else if(d){s([{value:l,word:"days"},{value:i,word:"hours"},{value:u,word:"minutes"},{value:c,word:"seconds"}],t,d)}},u=()=>{a.interval=setInterval(i,t.refresh),i()};u();const c=new MutationObserver((e=>{e.forEach((e=>{e.removedNodes.forEach((e=>{e===n&&(null!==a.interval&&clearInterval(a.interval),c.disconnect())}))}))}));return n.parentNode&&c.observe(n.parentNode,{childList:!0}),{stopCountdown:()=>{var e;null!==a.interval&&(clearInterval(a.interval),a.interval=null),a.isPaused=!0,null==(e=t.onStop)||e.call(t)},resumeCountdown:()=>{var e;a.isPaused&&(u(),a.isPaused=!1,null==(e=t.onResume)||e.call(t))},updateCountdown:e=>{var n;Object.assign(t,e),void 0===e.year&&void 0===e.month&&void 0===e.day&&void 0===e.hours&&void 0===e.minutes&&void 0===e.seconds||(a.targetDate=l(t)),null==(n=t.onUpdate)||n.call(t,e),a.isPaused||(a.interval&&clearInterval(a.interval),u())},getState:()=>({...a})}},l=e=>{const n=e;return n.stopCountdown=()=>e.forEach((e=>e.stopCountdown())),n.resumeCountdown=()=>e.forEach((e=>e.resumeCountdown())),n.updateCountdown=n=>e.forEach((e=>e.updateCountdown(n))),n.getState=()=>e.map((e=>e.getState())),n},r=(e,t=n)=>{const o={...n,...t};if("string"==typeof e){const n=document.querySelectorAll(e),t=Array.from(n).map((e=>a(e,o)));return 1===t.length?t[0]:l(t)}if((e=>e instanceof NodeList)(e)){const n=Array.from(e).map((e=>a(e,o)));return 1===n.length?n[0]:l(n)}return a(e,o)};return"function"==typeof define&&define.amd?define((function(){return r})):"object"==typeof module&&module.exports?module.exports=r:window.simplyCountdown=r,r}));
|
2
|
+
//# sourceMappingURL=simplyCountdown.umd.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"simplyCountdown.umd.js","sources":["../src/core/dom.ts","../src/core/simplyCountdown.ts","../src/core/simplyCountdown.umd.ts"],"sourcesContent":["/**\n * Creates a countdown section element\n */\nexport const createCountdownSection = (\n sectionClass: string,\n amountClass: string,\n wordClass: string,\n amount: number,\n word: string,\n params: {\n sectionClass: string;\n amountClass: string;\n wordClass: string;\n }\n): HTMLElement => {\n const section = document.createElement(\"div\");\n section.className = `${sectionClass} ${params.sectionClass}`;\n\n const wrap = document.createElement(\"div\");\n const amount_elem = document.createElement(\"span\");\n const word_elem = document.createElement(\"span\");\n\n amount_elem.className = `${amountClass} ${params.amountClass}`;\n word_elem.className = `${wordClass} ${params.wordClass}`;\n\n amount_elem.textContent = String(amount);\n word_elem.textContent = word;\n\n wrap.appendChild(amount_elem);\n wrap.appendChild(word_elem);\n section.appendChild(wrap);\n\n return section;\n};\n\n/**\n * Retrieves a countdown section element from a container\n */\nexport const getCountdownSection = (sectionClass: string, container: HTMLElement): HTMLElement | null => {\n return container.querySelector(`.simply-section.${sectionClass}`);\n};\n\n/**\n * Updates a countdown section element\n */\nexport const updateCountdownSection = (section: HTMLElement, amount: number | string, word: string): void => {\n const amountElement = section.querySelector(\".simply-amount\");\n const wordElement = section.querySelector(\".simply-word\");\n\n if (amountElement) {\n amountElement.textContent = String(amount);\n }\n if (wordElement) {\n wordElement.textContent = word;\n }\n};\n\n/**\n * Creates all countdown elements\n */\nexport const createCountdown = (\n container: HTMLElement,\n params: {\n sectionClass: string;\n amountClass: string;\n wordClass: string;\n }\n): {\n days: HTMLElement;\n hours: HTMLElement;\n minutes: HTMLElement;\n seconds: HTMLElement;\n} => {\n const amountCls = \"simply-amount\";\n const wordCls = \"simply-word\";\n\n const days = createCountdownSection(\"simply-section simply-days-section\", amountCls, wordCls, 0, \"day\", params);\n const hours = createCountdownSection(\"simply-section simply-hours-section\", amountCls, wordCls, 0, \"hour\", params);\n const minutes = createCountdownSection(\"simply-section simply-minutes-section\", amountCls, wordCls, 0, \"minute\", params);\n const seconds = createCountdownSection(\"simply-section simply-seconds-section\", amountCls, wordCls, 0, \"second\", params);\n\n container.appendChild(days);\n container.appendChild(hours);\n container.appendChild(minutes);\n container.appendChild(seconds);\n\n return {\n days,\n hours,\n minutes,\n seconds,\n };\n};\n","/*!\n * Project : simplyCountdown.js\n * Date : __SCD_BUILD_DATE__\n * License : MIT\n * Version : __SCD_VERSION__\n * Author : Vincent Loy-Serre\n * Contributors :\n * - Justin Beasley\n * - Nathan Smith\n * - Mehdi Rezaei\n * - mira01\n */\n\nimport type { CountdownParameters, CountdownSelector, CountdownState, CountdownController, CountdownControllerArray } from \"../types\";\nimport { createCountdown, updateCountdownSection } from \"./dom\";\n\nconst defaultParams: CountdownParameters = {\n year: 2024,\n month: 12,\n day: 25,\n hours: 0,\n minutes: 0,\n seconds: 0,\n words: {\n days: { lambda: (root, n) => (n > 1 ? root + \"s\" : root), root: \"day\" },\n hours: { lambda: (root, n) => (n > 1 ? root + \"s\" : root), root: \"hour\" },\n minutes: { lambda: (root, n) => (n > 1 ? root + \"s\" : root), root: \"minute\" },\n seconds: { lambda: (root, n) => (n > 1 ? root + \"s\" : root), root: \"second\" },\n },\n plural: true,\n inline: false,\n inlineSeparator: \", \",\n enableUtc: false,\n onEnd: () => {},\n refresh: 1000,\n inlineClass: \"simply-countdown-inline\",\n sectionClass: \"simply-section\",\n amountClass: \"simply-amount\",\n wordClass: \"simply-word\",\n zeroPad: false,\n countUp: false,\n removeZeroUnits: false,\n onStop: () => {},\n onResume: () => {},\n onUpdate: () => {},\n};\n\nconst isNodeList = (element: CountdownSelector): element is NodeListOf<HTMLElement> => {\n return element instanceof NodeList;\n};\n\ninterface TimeUnit {\n value: number;\n word: keyof CountdownParameters[\"words\"];\n element?: HTMLElement;\n}\n\n/**\n * Formats a time unit with optional zero padding and pluralization\n * @param unit - The time unit object containing value and word properties\n * @param params - The countdown parameters containing formatting options and word definitions\n * @returns A formatted string containing the value and pluralized word for the time unit\n * @example\n * // With zeroPad: true\n * formatTimeUnit({value: 5, word: 'days'}, params) // returns \"05 days\"\n * // With zeroPad: false\n * formatTimeUnit({value: 5, word: 'days'}, params) // returns \"5 days\"\n */\nfunction formatTimeUnit(unit: TimeUnit, params: CountdownParameters): string {\n const value = params.zeroPad ? String(unit.value).padStart(2, \"0\") : unit.value;\n return `${value} ${params.words[unit.word].lambda(params.words[unit.word].root, unit.value)}`;\n}\n\n/**\n * Determines whether a time unit should be displayed based on its value and the values of previous units\n * @param unit - The current time unit to evaluate\n * @param previousUnits - Array of time units that come before the current unit\n * @param params - Configuration parameters for the countdown\n * @returns True if the unit should be displayed, false otherwise\n *\n * If removeZeroUnits is false in params, always returns true.\n * Otherwise, returns true if either:\n * - The current unit value is not zero\n * - Any previous unit has a non-zero value\n */\nfunction shouldDisplay(unit: TimeUnit, previousUnits: TimeUnit[], params: CountdownParameters): boolean {\n if (!params.removeZeroUnits) return true;\n return unit.value !== 0 || previousUnits.some((u) => u.value !== 0);\n}\n\n/**\n * Displays the countdown timer inline within the specified HTML element.\n *\n * @param timeUnits - Array of time units containing values and labels for display\n * @param params - Configuration parameters for the countdown display\n * @param element - The HTML element where the countdown will be rendered\n *\n * @remarks\n * The function filters and formats time units based on display rules, then joins them with\n * the specified separator from params.inlineSeparator before setting the element's innerHTML.\n */\nfunction displayInline(timeUnits: TimeUnit[], params: CountdownParameters, element: HTMLElement): void {\n const displayStr = timeUnits\n .filter((unit, index) => shouldDisplay(unit, timeUnits.slice(0, index), params))\n .map((unit) => formatTimeUnit(unit as { value: number; word: keyof typeof params.words }, params))\n .join(params.inlineSeparator);\n\n element.innerHTML = displayStr;\n}\n\n/**\n * Updates the display of time units in the countdown based on their values and display conditions\n * @param timeUnits - Array of TimeUnit objects containing the time values and their corresponding words\n * @param params - Configuration parameters for the countdown display\n * @param countdown - DOM elements representing the countdown display sections\n * @returns void\n *\n * @remarks\n * This function iterates through each time unit and determines whether it should be shown based on:\n * - If it's the seconds unit (always shown)\n * - If it meets display criteria based on previous units\n *\n * For units that should be shown, it:\n * - Updates the display value (with optional zero padding)\n * - Updates the word label using the configured lambda function\n * - Shows the unit's DOM element\n *\n * For units that shouldn't be shown, it hides their DOM elements\n */\nfunction displayBlocks(timeUnits: TimeUnit[], params: CountdownParameters, countdown: any): void {\n timeUnits.forEach((unit, index) => {\n const shouldShow = unit.word === \"seconds\" || shouldDisplay(unit, timeUnits.slice(0, index), params);\n\n if (shouldShow) {\n updateCountdownSection(\n countdown[unit.word],\n params.zeroPad ? String(unit.value).padStart(2, \"0\") : unit.value,\n params.words[unit.word].lambda(params.words[unit.word].root, unit.value)\n );\n countdown[unit.word].style.display = \"\";\n } else {\n countdown[unit.word].style.display = \"none\";\n }\n });\n}\n\n/**\n * Creates a countdown instance that manages the countdown timer functionality.\n *\n * @param targetElement - The HTML element where the countdown will be rendered\n * @param parameters - Configuration parameters for the countdown\n *\n * @returns A controller object with methods to control the countdown:\n * - stopCountdown: Pauses the countdown and triggers onStop callback\n * - resumeCountdown: Resumes a paused countdown and triggers onResume callback\n * - updateCountdown: Updates countdown parameters and triggers onUpdate callback\n * - getState: Returns current state of the countdown\n */\nconst createCountdownInstance = (targetElement: HTMLElement, parameters: CountdownParameters): CountdownController => {\n let state: CountdownState = {\n isPaused: false,\n interval: null,\n targetDate: new Date(),\n };\n\n const getTargetDate = (params: CountdownParameters): Date => {\n return params.enableUtc\n ? new Date(Date.UTC(params.year, params.month - 1, params.day, params.hours, params.minutes, params.seconds))\n : new Date(params.year, params.month - 1, params.day, params.hours, params.minutes, params.seconds);\n };\n\n state.targetDate = getTargetDate(parameters);\n\n // Create span element for inline mode\n let inlineElement: HTMLElement | null = null;\n if (parameters.inline) {\n inlineElement = document.createElement(\"span\");\n inlineElement.className = parameters.inlineClass;\n targetElement.appendChild(inlineElement);\n }\n\n const countdown = parameters.inline\n ? null\n : createCountdown(targetElement, {\n sectionClass: parameters.sectionClass,\n amountClass: parameters.amountClass,\n wordClass: parameters.wordClass,\n });\n\n const refresh = () => {\n // Fix UTC current date handling\n const currentDate = parameters.enableUtc\n ? new Date(\n Date.UTC(\n new Date().getUTCFullYear(),\n new Date().getUTCMonth(),\n new Date().getUTCDate(),\n new Date().getUTCHours(),\n new Date().getUTCMinutes(),\n new Date().getUTCSeconds()\n )\n )\n : new Date();\n\n let diff = parameters.countUp ? currentDate.getTime() - state.targetDate.getTime() : state.targetDate.getTime() - currentDate.getTime();\n\n if (diff <= 0 && !parameters.countUp) {\n diff = 0;\n // Clear interval before calling onEnd to prevent multiple calls\n if (state.interval !== null) {\n clearInterval(state.interval);\n }\n\n if (parameters.onEnd) {\n parameters.onEnd();\n }\n }\n\n const days = Math.floor(diff / (1000 * 60 * 60 * 24));\n diff -= days * 1000 * 60 * 60 * 24;\n\n const hours = Math.floor(diff / (1000 * 60 * 60));\n diff -= hours * 1000 * 60 * 60;\n\n const minutes = Math.floor(diff / (1000 * 60));\n diff -= minutes * 1000 * 60;\n\n const seconds = Math.floor(diff / 1000);\n\n if (parameters.inline && inlineElement) {\n const timeUnits: TimeUnit[] = [\n { value: days, word: \"days\" as keyof CountdownParameters[\"words\"] },\n {\n value: hours,\n word: \"hours\" as keyof CountdownParameters[\"words\"],\n },\n {\n value: minutes,\n word: \"minutes\" as keyof CountdownParameters[\"words\"],\n },\n {\n value: seconds,\n word: \"seconds\" as keyof CountdownParameters[\"words\"],\n },\n ];\n displayInline(timeUnits, parameters, inlineElement);\n } else if (countdown) {\n const timeUnits: TimeUnit[] = [\n { value: days, word: \"days\" as keyof CountdownParameters[\"words\"] },\n {\n value: hours,\n word: \"hours\" as keyof CountdownParameters[\"words\"],\n },\n {\n value: minutes,\n word: \"minutes\" as keyof CountdownParameters[\"words\"],\n },\n {\n value: seconds,\n word: \"seconds\" as keyof CountdownParameters[\"words\"],\n },\n ];\n displayBlocks(timeUnits, parameters, countdown);\n }\n };\n\n const startInterval = () => {\n state.interval = setInterval(refresh, parameters.refresh);\n refresh();\n };\n\n const stopCountdown = () => {\n if (state.interval !== null) {\n clearInterval(state.interval);\n state.interval = null;\n }\n state.isPaused = true;\n parameters.onStop?.();\n };\n\n const resumeCountdown = () => {\n if (state.isPaused) {\n startInterval();\n state.isPaused = false;\n parameters.onResume?.();\n }\n };\n\n const updateCountdown = (newParams: Partial<CountdownParameters>) => {\n Object.assign(parameters, newParams);\n if (\n newParams.year !== undefined ||\n newParams.month !== undefined ||\n newParams.day !== undefined ||\n newParams.hours !== undefined ||\n newParams.minutes !== undefined ||\n newParams.seconds !== undefined\n ) {\n state.targetDate = getTargetDate(parameters);\n }\n\n parameters.onUpdate?.(newParams);\n\n if (!state.isPaused) {\n if (state.interval) {\n clearInterval(state.interval);\n }\n startInterval();\n }\n };\n\n const getState = () => ({ ...state });\n\n // Start the countdown\n startInterval();\n\n // Cleanup on element removal\n const observer = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n mutation.removedNodes.forEach((node) => {\n if (node === targetElement) {\n if (state.interval !== null) {\n clearInterval(state.interval);\n }\n observer.disconnect();\n }\n });\n });\n });\n\n if (targetElement.parentNode) {\n observer.observe(targetElement.parentNode, { childList: true });\n }\n\n // Return controller object\n return {\n stopCountdown,\n resumeCountdown,\n updateCountdown,\n getState,\n };\n};\n\n/**\n * Creates an enhanced array of countdown controllers with additional control methods.\n *\n * @param controllers - Array of individual countdown controllers to be combined\n * @returns An array of controllers enhanced with collective control methods:\n * - `stopCountdown()`: Stops all countdowns in the array\n * - `resumeCountdown()`: Resumes all countdowns in the array\n * - `updateCountdown(newParams)`: Updates all countdowns with new parameters\n * - `getState()`: Returns an array of states from all countdowns\n */\nconst createControllerArray = (controllers: CountdownController[]): CountdownControllerArray => {\n const array = controllers as CountdownControllerArray;\n\n array.stopCountdown = () => controllers.forEach((c) => c.stopCountdown());\n array.resumeCountdown = () => controllers.forEach((c) => c.resumeCountdown());\n array.updateCountdown = (newParams) => controllers.forEach((c) => c.updateCountdown(newParams));\n array.getState = () => controllers.map((c) => c.getState());\n\n return array;\n};\n\n/**\n * Creates a countdown timer on specified HTML elements\n * @param element - A CSS selector string, HTMLElement, or NodeList targeting the countdown container(s)\n * @param args - Optional configuration parameters for the countdown\n * @returns A CountdownController for single element or CountdownControllerArray for multiple elements\n */\nconst simplyCountdown = (\n element: CountdownSelector,\n args: Partial<CountdownParameters> = defaultParams\n): CountdownController | CountdownControllerArray => {\n const parameters: CountdownParameters = { ...defaultParams, ...args };\n\n if (typeof element === \"string\") {\n const elements = document.querySelectorAll<HTMLElement>(element);\n const controllers = Array.from(elements).map((el) => createCountdownInstance(el, parameters));\n return controllers.length === 1 ? controllers[0] : createControllerArray(controllers);\n }\n\n if (isNodeList(element)) {\n const controllers = Array.from(element).map((el) => createCountdownInstance(el, parameters));\n return controllers.length === 1 ? controllers[0] : createControllerArray(controllers);\n }\n\n return createCountdownInstance(element, parameters);\n};\n\nexport default simplyCountdown;\n","import simplyCountdownCore from \"./simplyCountdown\";\n\ndeclare const define: {\n (factory: () => any): void;\n amd: boolean;\n};\n\nif (typeof define === \"function\" && define.amd) {\n // AMD\n define(function () {\n return simplyCountdownCore;\n });\n} else if (typeof module === \"object\" && module.exports) {\n // Node\n module.exports = simplyCountdownCore;\n} else {\n // Browser\n (window as any).simplyCountdown = simplyCountdownCore;\n}\n\n// Export for Vite/Rollup\nexport default simplyCountdownCore;\n"],"names":["createCountdownSection","sectionClass","amountClass","wordClass","amount","word","params","section","document","createElement","className","wrap","amount_elem","word_elem","textContent","String","appendChild","defaultParams","year","month","day","hours","minutes","seconds","words","days","lambda","root","n","plural","inline","inlineSeparator","enableUtc","onEnd","refresh","inlineClass","zeroPad","countUp","removeZeroUnits","onStop","onResume","onUpdate","shouldDisplay","unit","previousUnits","value","some","u","displayInline","timeUnits","element","displayStr","filter","index","slice","map","padStart","formatTimeUnit","join","innerHTML","displayBlocks","countdown","forEach","amountElement","querySelector","wordElement","updateCountdownSection","style","display","createCountdownInstance","targetElement","parameters","state","isPaused","interval","targetDate","Date","getTargetDate","UTC","inlineElement","container","amountCls","wordCls","createCountdown","currentDate","getUTCFullYear","getUTCMonth","getUTCDate","getUTCHours","getUTCMinutes","getUTCSeconds","diff","getTime","clearInterval","Math","floor","startInterval","setInterval","observer","MutationObserver","mutations","mutation","removedNodes","node","disconnect","parentNode","observe","childList","stopCountdown","_a","call","resumeCountdown","updateCountdown","newParams","Object","assign","getState","createControllerArray","controllers","array","c","simplyCountdown","args","elements","querySelectorAll","Array","from","el","length","NodeList","isNodeList","define","amd","simplyCountdownCore","module","exports","window"],"mappings":"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"}
|
Binary file
|
Binary file
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
{% can_show_item value.visible_for as csi %}
|
4
4
|
{% if csi %}
|
5
|
-
<a href="{% if request.LANGUAGE_CODE and request.LANGUAGE_CODE
|
5
|
+
<a href="{% if request.LANGUAGE_CODE and request.LANGUAGE_CODE|not_starts_with:'en' %}/{{ request.LANGUAGE_CODE }}{% endif %}{{ self.url }}"
|
6
6
|
{% if settings.cjkcms.AnalyticsSettings.ga_track_button_clicks %}
|
7
7
|
data-ga-event-category='{{self.settings.ga_tracking_event_category|default:"Button"}}'
|
8
8
|
data-ga-event-label='{{self.settings.ga_tracking_event_label|default:self.button_title}}'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
{% load wagtailcore_tags static %}
|
2
|
+
|
3
|
+
{% block block_render %}
|
4
|
+
{% with 'vendor/simplycountdown/css/'|add:self.theme|add:'.css' as theme_css %}
|
5
|
+
<link rel="stylesheet" href="{% static theme_css %}">
|
6
|
+
{% endwith %}
|
7
|
+
{% if self.url %}<a href="{{self.url}}" class="text-decoration-none">{% endif %}
|
8
|
+
<div class="simply-countdown-{{self.theme}} {%if self.settings.custom_css_class%}{{self.settings.custom_css_class}}{% endif %}" id="mycountdown">
|
9
|
+
{% if self.title %}<h3 class="w-100 text-center">{{ self.title }}</h3>{% endif %}
|
10
|
+
</div>
|
11
|
+
{% if self.url %}</a>{% endif %}
|
12
|
+
<script src="{% static 'vendor/simplycountdown/js/simplyCountdown.umd.js' %}"></script>
|
13
|
+
<script>
|
14
|
+
simplyCountdown("#mycountdown", {
|
15
|
+
year: {{ year }},
|
16
|
+
month: {{ month }},
|
17
|
+
day: {{ day }},
|
18
|
+
hours: {{ hour }},
|
19
|
+
minutes: {{ minute }},
|
20
|
+
seconds: {{ second }},
|
21
|
+
enableUtc: true,
|
22
|
+
});
|
23
|
+
</script>
|
24
|
+
{% endblock %}
|
@@ -0,0 +1,100 @@
|
|
1
|
+
import pytest
|
2
|
+
from django.test import Client, TestCase, override_settings
|
3
|
+
from wagtail.models import Page
|
4
|
+
from cjkcms.models.cms_models import ArticlePage
|
5
|
+
from datetime import datetime, timedelta, timezone
|
6
|
+
|
7
|
+
|
8
|
+
@override_settings(
|
9
|
+
STORAGES={
|
10
|
+
"default": {
|
11
|
+
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
12
|
+
},
|
13
|
+
"staticfiles": {
|
14
|
+
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
15
|
+
},
|
16
|
+
}
|
17
|
+
)
|
18
|
+
@pytest.mark.django_db
|
19
|
+
class TestCountdownBlock(TestCase):
|
20
|
+
def setUp(self):
|
21
|
+
self.client = Client()
|
22
|
+
self.create_article_page()
|
23
|
+
|
24
|
+
def create_article_page(self):
|
25
|
+
home_page = Page.objects.get(path="00010001")
|
26
|
+
article_page = ArticlePage(title="Test Article", body=None)
|
27
|
+
home_page.add_child(instance=article_page)
|
28
|
+
article_page.save_revision().publish()
|
29
|
+
|
30
|
+
def set_article_body(self, content) -> None:
|
31
|
+
article_page = ArticlePage.objects.get(title="Test Article")
|
32
|
+
article_page.body = content
|
33
|
+
article_page.save_revision().publish()
|
34
|
+
|
35
|
+
def test_css_theme_title(self):
|
36
|
+
block_content = [
|
37
|
+
{
|
38
|
+
"type": "countdown",
|
39
|
+
"value": {
|
40
|
+
"title": "Counter Title",
|
41
|
+
"theme": "losange",
|
42
|
+
"settings": {"custom_css_class": "my-custom-class"},
|
43
|
+
"start_date": "2029-01-01 00:00",
|
44
|
+
},
|
45
|
+
"id": "countdown-block",
|
46
|
+
}
|
47
|
+
]
|
48
|
+
self.set_article_body(block_content)
|
49
|
+
response = self.client.get("/test-article/")
|
50
|
+
self.assertContains(response, "my-custom-class")
|
51
|
+
self.assertNotContains(response, "my-other-class")
|
52
|
+
self.assertContains(response, "losange")
|
53
|
+
self.assertContains(response, "Counter Title")
|
54
|
+
self.assertNotContains(response, "<a href=")
|
55
|
+
|
56
|
+
def test_url(self):
|
57
|
+
block_content = [
|
58
|
+
{
|
59
|
+
"type": "countdown",
|
60
|
+
"value": {
|
61
|
+
"theme": "light",
|
62
|
+
"url": "https://example.com",
|
63
|
+
"start_date": "2029-01-01 00:00",
|
64
|
+
"timezone": "UTC",
|
65
|
+
},
|
66
|
+
"id": "countdown-block2",
|
67
|
+
}
|
68
|
+
]
|
69
|
+
self.set_article_body(block_content)
|
70
|
+
response = self.client.get("/test-article/")
|
71
|
+
self.assertContains(response, '<a href="https://example.com"')
|
72
|
+
|
73
|
+
def test_time(self):
|
74
|
+
# generate start date that is one in the future
|
75
|
+
|
76
|
+
# Get the current UTC time
|
77
|
+
current_utc_time = datetime.now(timezone.utc)
|
78
|
+
# Add one minute to the current UTC time
|
79
|
+
one_minute_ahead = current_utc_time + timedelta(minutes=1)
|
80
|
+
# Remove timezone info from the one_minute_ahead datetime
|
81
|
+
one_minute_ahead_naive = one_minute_ahead.replace(tzinfo=None)
|
82
|
+
|
83
|
+
block_content = [
|
84
|
+
{
|
85
|
+
"type": "countdown",
|
86
|
+
"value": {
|
87
|
+
"theme": "light",
|
88
|
+
"start_date": one_minute_ahead_naive.strftime("%Y-%m-%d %H:%M"),
|
89
|
+
"timezone": "UTC",
|
90
|
+
},
|
91
|
+
"id": "countdown-block3",
|
92
|
+
}
|
93
|
+
]
|
94
|
+
self.set_article_body(block_content)
|
95
|
+
response = self.client.get("/test-article/")
|
96
|
+
self.assertContains(response, "year: " + str(one_minute_ahead.year))
|
97
|
+
self.assertContains(response, "month: " + str(one_minute_ahead.month))
|
98
|
+
self.assertContains(response, "day: " + str(one_minute_ahead.day))
|
99
|
+
self.assertContains(response, "hours: " + str(one_minute_ahead.hour))
|
100
|
+
self.assertContains(response, "minutes: " + str(one_minute_ahead.minute))
|
@@ -1,4 +1,5 @@
|
|
1
|
-
cjkcms
|
1
|
+
cjkcms/.DS_Store,sha256=cYcT7MpEwyMj1-CAvcue4ODbL87U2KGsmN7HYD4O8SY,6148
|
2
|
+
cjkcms/__init__.py,sha256=ZsUuUlcExnTWNhB9GZKCu2i_Ah9oeimsNC_IZ0Rpr6s,273
|
2
3
|
cjkcms/apps.py,sha256=VA5Z1YerImetvN8KsjPTMSn1fSo6O1JkBJdK5y5ubJY,173
|
3
4
|
cjkcms/fields.py,sha256=dE0DuNIjX7jhA-5GjSmR2l66EDH2kq3vuxL9WyRALCY,3191
|
4
5
|
cjkcms/forms.py,sha256=_uu_FR8odz40lD-Rmw0tlK7-xxxa8THHfV2-1ZJYsIM,361
|
@@ -14,13 +15,14 @@ cjkcms/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
15
|
cjkcms/api/mailchimp.py,sha256=pufnnu8n-c6kkWNKy-EsRLW5LLBn8_TmYrnRSQCPFdY,2832
|
15
16
|
cjkcms/bin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
17
|
cjkcms/bin/cjkcms.py,sha256=i11hhB11COQhTk05y7mkeIkUYcFjhI8-QxLkN3tCxgg,5763
|
17
|
-
cjkcms/blocks/__init__.py,sha256=
|
18
|
+
cjkcms/blocks/__init__.py,sha256=a54EtWyH8UPqszlVEeOtFCvBW2WmbaW1vR4sZuN9Hf0,3542
|
18
19
|
cjkcms/blocks/base_blocks.py,sha256=uKp6QO3iOPT4P0H0yOumvL7faPvLQK9Dj-D8iqlvqQ4,11616
|
19
20
|
cjkcms/blocks/content_blocks.py,sha256=db_CNBqpKdAq1e9Y4AdpRm1077ANIWD8DzqG56_rSNs,10098
|
20
21
|
cjkcms/blocks/html_blocks.py,sha256=rXdR1uxFYDYjOXpxndItxacWstiME8CdqZgB6d5qAYE,9543
|
21
22
|
cjkcms/blocks/layout_blocks.py,sha256=n2oVyL-ZQQaEA00HU6YraR59YF065o-OK7WuAahGuiw,3406
|
22
23
|
cjkcms/blocks/searchable_html_block.py,sha256=i1rWv4vwwvZ9fxkASwPKl87-TqVHkXOFyohH3ve7pYk,171
|
23
24
|
cjkcms/blocks/content/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
25
|
+
cjkcms/blocks/content/countdown.py,sha256=KWutgbPY_N49vrxqUPjbir-MgN0ro5u8WVXD8MyJUbI,3342
|
24
26
|
cjkcms/blocks/content/events.py,sha256=H3uIl4Gxfsm8yjdfaEMRLcNs5-HkoMZUP1qkxPd5Z9Q,1967
|
25
27
|
cjkcms/draftail/__init__.py,sha256=4-DXyDcnvAacmOzRW5IzmNb28kU71PJ18Q89vkxCv_8,240
|
26
28
|
cjkcms/draftail/draftail_extensions.py,sha256=Grf3vkuqyygt7vn3Fp1ALXLxjgSsYZ4-rLH4R8eFIEU,3250
|
@@ -32,8 +34,6 @@ cjkcms/management/commands/import-csv.py,sha256=5IR_GHVhuHdb0UX_TNLe4OuKhwRmKjU3
|
|
32
34
|
cjkcms/management/commands/init-collections.py,sha256=bTDVz3RyXUoCUXv-d2P3JWjySw0INvO_14IfNmPEG9A,951
|
33
35
|
cjkcms/management/commands/init-navbar.py,sha256=rO0O1MjzfvmnQoZllhY3g6KOHT3nFt5UR0WkIsb-4PA,3560
|
34
36
|
cjkcms/management/commands/init-website.py,sha256=DJPREmnv3srarhNWbiowluKAuUoa-PXDlENQQgnWZPY,3820
|
35
|
-
cjkcms/media/images/test.original.png,sha256=nLMtadXZhXXbt6DTm5enrysKX7cfWiUudtbS2X7mSzI,1938
|
36
|
-
cjkcms/media/original_images/test.png,sha256=xJDvcufuQ-AM1HT-zgMxYEORko4rdK_8MSHU-puJNW8,2306
|
37
37
|
cjkcms/migrations/0001_initial.py,sha256=hiDQKRqmKk65tS8jyXRRfffWLzLMB7P0r3qEqu88jAM,2070677
|
38
38
|
cjkcms/migrations/0002_alter_body_to_cjkcmsstreamfield.py,sha256=QYPXCirKSBBwB4JNcei_OhEx856Cq0_S-7ZXmEtcCAI,924
|
39
39
|
cjkcms/migrations/0003_alter_footer_content_alter_navbar_menu_items.py,sha256=N5vRtKBIlQ99zbBKTFoVAVeMH_K192c2dVVYOvHD4Nc,727
|
@@ -173,15 +173,24 @@ cjkcms/static/vendor/mdb/css/mdb.min.css,sha256=NwSiVkXzh8wtWxshP59CXHGRsNaM03bm
|
|
173
173
|
cjkcms/static/vendor/mdb/css/mdb.min.css.map,sha256=HCd-vGDs-t7EinsTSmawI-CdC92HmydrH0TqlB4Oh4s,546228
|
174
174
|
cjkcms/static/vendor/mdb/js/mdb.umd.min.js,sha256=6ExxHAv6V4GggQXwu1gkXc4MHtdiqKgVwBQ2m1HGr2M,124534
|
175
175
|
cjkcms/static/vendor/mdb/js/mdb.umd.min.js.map,sha256=1k9rRQNB3hrVj47Anhg19MtXioMnuffFMh34NEyrBcY,502496
|
176
|
+
cjkcms/static/vendor/simplycountdown/css/circle.css,sha256=p8XghcW_i3DReRzmivluCPvvTeufVcyzpQRlHtYPxwI,1632
|
177
|
+
cjkcms/static/vendor/simplycountdown/css/cyber.css,sha256=OECAvPFLHuUkgv9ENP27SzVaCimFS0T7g_5jT0l5TTg,3813
|
178
|
+
cjkcms/static/vendor/simplycountdown/css/dark.css,sha256=VEMMr_JIZ303FlNVc5j-_BstLolM2Ru-gZ0MEHUiNW0,1899
|
179
|
+
cjkcms/static/vendor/simplycountdown/css/light.css,sha256=WXbRsGaWA-xmc-tMeTNGvx6-FvLqgnGz67UvJv3OYN0,1926
|
180
|
+
cjkcms/static/vendor/simplycountdown/css/losange.css,sha256=qzyTDoj2LhR6JMQB7OAe3nwG1Wm3Te4rnDmq1qRBw5s,1803
|
181
|
+
cjkcms/static/vendor/simplycountdown/js/simplyCountdown.umd.js,sha256=Jiw45EkrYMAywPxoUK0G7sUaZWZo6Aucpw5gt8YIl7w,5024
|
182
|
+
cjkcms/static/vendor/simplycountdown/js/simplyCountdown.umd.js.map,sha256=qunXuRwA6mOaCihYJfsIg7J72p3XaurneVGSTNPez7Y,25873
|
183
|
+
cjkcms/templates/.DS_Store,sha256=dyZnb39fKIIkLjeDh-Q3jgmLIVt624Ouh-D6Bg_KNAU,6148
|
176
184
|
cjkcms/templates/404.html,sha256=GnIFZoXMcFzz_UTMChOgbO6-IIWQZKaAom7OJXn6dao,3245
|
177
185
|
cjkcms/templates/500.html,sha256=YWYel2g2SDKLwSv8C9VLPzVvkcRLo6X9WYwWjxnfz0U,4802
|
186
|
+
cjkcms/templates/cjkcms/.DS_Store,sha256=9gfOq7SmBsVA0HbZiVkZmy0Gc2DBCrPvHirRQ70X47Q,6148
|
178
187
|
cjkcms/templates/cjkcms/robots.txt,sha256=669-RWM9DZYUfyxUvl2Fxn6pcZ_7Efbb7HeZYoR0DWo,92
|
179
188
|
cjkcms/templates/cjkcms/blocks/accordion_block.html,sha256=9gsMZMflXqdYcFVqf8YOsyb3YYHgC26Sug_tzGvc6N8,1443
|
180
189
|
cjkcms/templates/cjkcms/blocks/article_block_card.html,sha256=37Xb0XNGVSyWP8gXVGh742eZmW-3hFSmEAfCe5QOrvo,974
|
181
190
|
cjkcms/templates/cjkcms/blocks/article_masonry_card.html,sha256=rW1fDJrERAw_1u391An7F8mvqmrvM2xLcXS9gAgfwuM,1132
|
182
191
|
cjkcms/templates/cjkcms/blocks/base_block.html,sha256=i1iRaLNqVF5Cd_Tv56TbXrA8uBCChnRMRagN047Rx2I,188
|
183
192
|
cjkcms/templates/cjkcms/blocks/base_link_block.html,sha256=EznK9jWFFC_k8mEyi3W_h0w3CNvB_jubYsTawAEvivY,2675
|
184
|
-
cjkcms/templates/cjkcms/blocks/button_block.html,sha256=
|
193
|
+
cjkcms/templates/cjkcms/blocks/button_block.html,sha256=skwQN6x0FWBYGEQbX1hIg1prgO0AarmzjzdsjmPyvdg,875
|
185
194
|
cjkcms/templates/cjkcms/blocks/card_block.html,sha256=Z5wzPM7-X3QWx5B_CAO2e5ei9OyNwUD4m4h_HV2Bj5c,746
|
186
195
|
cjkcms/templates/cjkcms/blocks/card_blurb.html,sha256=QUIK1Xv7cIvTA17wXSp0FKCSobQhBWqLqPIWMNvd2u8,856
|
187
196
|
cjkcms/templates/cjkcms/blocks/card_foot.html,sha256=VPGJRBTAf_n73wS_nNryccnrmrGNGy9NodPxgI_3ioc,786
|
@@ -198,6 +207,7 @@ cjkcms/templates/cjkcms/blocks/cardgrid_group.html,sha256=mCOBw0wmTOyHvRXrngI-lw
|
|
198
207
|
cjkcms/templates/cjkcms/blocks/cardgrid_zero.html,sha256=lECaWt-D_N1zd5U8xnYWu0H-M-vNnMnIvzlulBag_hg,276
|
199
208
|
cjkcms/templates/cjkcms/blocks/carousel_block.html,sha256=XC04uEPWjQKmN7dHoQVv9NmFPkBxLhCpwTzvVGeXUwk,2698
|
200
209
|
cjkcms/templates/cjkcms/blocks/column_block.html,sha256=7aiPizV-MrXS2LyV_fP1aCpNoC0nNAQphyVuAWbi2SI,406
|
210
|
+
cjkcms/templates/cjkcms/blocks/countdown.html,sha256=blU12tkfSUp1G3W7bEK0xBClVgj-baeCk9qGkWIpb9E,921
|
201
211
|
cjkcms/templates/cjkcms/blocks/document_link_block.html,sha256=cEJERsbEF4sSLyyBRSHtiePwc-Q9S02dF0BZo_CThZ0,137
|
202
212
|
cjkcms/templates/cjkcms/blocks/download_block.html,sha256=0uslQsNRhg0XYhLijfrToAFiMVQ_SsOBTxcKRYXScbc,920
|
203
213
|
cjkcms/templates/cjkcms/blocks/embed_video_block.html,sha256=qHstCQwbgTykxWUqfH81oOF37gfPa57r47_QiQ8ocWQ,103
|
@@ -305,7 +315,7 @@ cjkcms/templates/wagtailadmin/shared/cr_main_nav_2fix.html,sha256=BAhkDE8_8KbhUO
|
|
305
315
|
cjkcms/templates/wagtailadmin/tables/thumbnail_cell.html,sha256=RzMT-tBnnDOgL-zexKanZTTpCEX1bMybFL2p_zvOEV4,578
|
306
316
|
cjkcms/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
307
317
|
cjkcms/templatetags/auth_extras.py,sha256=IIxQ5n9FaQVZmhW-yW1XM5p2OjIemtqVoX-O-UcCns8,327
|
308
|
-
cjkcms/templatetags/cjkcms_tags.py,sha256=
|
318
|
+
cjkcms/templatetags/cjkcms_tags.py,sha256=qpJrxMYJKz6pQeuoMx5YBP_xkMcV65fwQCEplAV-WNM,9166
|
309
319
|
cjkcms/templatetags/friendly_loader.py,sha256=Zwopb9NNNN6IuDJCMFlwKuhmJC0hi4uTPEZSAxeakYY,4962
|
310
320
|
cjkcms/templatetags/gravatar.py,sha256=JG5MB6HUFso15J8gZ_te5FTMYAuo4km8rzMKKvLLh-E,1127
|
311
321
|
cjkcms/templatetags/txtutils_tags.py,sha256=UMojdiLt6Jpijc-2QLusPc-qXX9bqFZf0JbqqPsfnWQ,1223
|
@@ -315,6 +325,7 @@ cjkcms/tests/settings.py,sha256=WVu7tOcUH1E_DJ6YOEfi5_r5tJobUbX9O0VAmVl4ICg,2556
|
|
315
325
|
cjkcms/tests/test_advsettings.py,sha256=6EngxFbUtwpaKDrvdLdCR6ONfHR3K1XGpOYKZJTHBUg,6906
|
316
326
|
cjkcms/tests/test_articlepages.py,sha256=7V7tr2kCKiEU1y36PlB9yoP0tmSDb8cchf8VYof3GGM,4226
|
317
327
|
cjkcms/tests/test_bin.py,sha256=z-6vMYYqhv18UO00sUPrNpLLue0mbRr5vzqisu7UKAY,3506
|
328
|
+
cjkcms/tests/test_countdown_block.py,sha256=83RgYXvOLErcswXg13WRPD-Lf5VAykx_YRb27o2II4c,3712
|
318
329
|
cjkcms/tests/test_draftail_extensions.py,sha256=8WAf8LdvSxW7kgtJaXQ8V3rSRNkzRDc-JGnlKOWZr3k,2364
|
319
330
|
cjkcms/tests/test_finders_oembed.py,sha256=J-dG3aTsO2KXnUA0o4yKHh_r5Za9v_8gcXJQGwJrXpI,2489
|
320
331
|
cjkcms/tests/test_gravatar.py,sha256=UWu1cVi3Gj96yqeQu795gw6FVPbZ44eCkkH1NGEGgBo,2629
|
@@ -325,22 +336,16 @@ cjkcms/tests/test_urls.py,sha256=otiKZcs1WLDQM6AOfcdZgsOGDwea1qS3VEc8Yy-FV-Q,288
|
|
325
336
|
cjkcms/tests/test_webpage.py,sha256=PvXeXbawigObsdi_YpQNuHFx3Lqi6wM3ktVHZsxYbr4,1520
|
326
337
|
cjkcms/tests/urls.py,sha256=_ksKz7HBHJtQK3HxC9cmJMX_dt6n4alx3FXjcL-cu28,850
|
327
338
|
cjkcms/tests/media/images/test.original.png,sha256=nLMtadXZhXXbt6DTm5enrysKX7cfWiUudtbS2X7mSzI,1938
|
328
|
-
cjkcms/tests/media/images/test_oKdmwQn.original.png,sha256=nLMtadXZhXXbt6DTm5enrysKX7cfWiUudtbS2X7mSzI,1938
|
329
|
-
cjkcms/tests/media/images/test_otfQve2.original.png,sha256=nLMtadXZhXXbt6DTm5enrysKX7cfWiUudtbS2X7mSzI,1938
|
330
|
-
cjkcms/tests/media/images/test_qFhTLcX.original.png,sha256=nLMtadXZhXXbt6DTm5enrysKX7cfWiUudtbS2X7mSzI,1938
|
331
339
|
cjkcms/tests/media/original_images/test.png,sha256=xJDvcufuQ-AM1HT-zgMxYEORko4rdK_8MSHU-puJNW8,2306
|
332
|
-
cjkcms/tests/media/original_images/test_oKdmwQn.png,sha256=xJDvcufuQ-AM1HT-zgMxYEORko4rdK_8MSHU-puJNW8,2306
|
333
|
-
cjkcms/tests/media/original_images/test_otfQve2.png,sha256=xJDvcufuQ-AM1HT-zgMxYEORko4rdK_8MSHU-puJNW8,2306
|
334
|
-
cjkcms/tests/media/original_images/test_qFhTLcX.png,sha256=xJDvcufuQ-AM1HT-zgMxYEORko4rdK_8MSHU-puJNW8,2306
|
335
340
|
cjkcms/tests/testapp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
336
341
|
cjkcms/tests/testapp/apps.py,sha256=EwyrkWTu-_OoLoERBTlqkqfPIIrEsbHY3kbCxgrv_7I,169
|
337
342
|
cjkcms/tests/testapp/models.py,sha256=Rkn9KHrGbLzrKjP4y_gwtXma1_fJOZNU7ekb689fJEU,488
|
338
343
|
cjkcms/tests/testapp/migrations/0001_initial.py,sha256=hxr-r-42IQEGr_OsZkxXXCW7wbxAHuI_OLOkn-seJUU,4942
|
339
344
|
cjkcms/tests/testapp/migrations/0002_create_homepage.py,sha256=EfsxHh1oyqwahW9RVpTvaRDx_CHtFSJQahKEr7XC5Gg,1999
|
340
345
|
cjkcms/tests/testapp/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
341
|
-
wagtail_cjkcms-24.
|
342
|
-
wagtail_cjkcms-24.
|
343
|
-
wagtail_cjkcms-24.
|
344
|
-
wagtail_cjkcms-24.
|
345
|
-
wagtail_cjkcms-24.
|
346
|
-
wagtail_cjkcms-24.
|
346
|
+
wagtail_cjkcms-24.12.2.dist-info/LICENSE,sha256=KHsCh1fKOZzvcKe1a9h3FlDjTjK_UurO3wHK55TnHHo,1538
|
347
|
+
wagtail_cjkcms-24.12.2.dist-info/METADATA,sha256=7xCQjQfxWjcZtPgYzcAWemdjjn937AzzQIonjnQMkfg,3107
|
348
|
+
wagtail_cjkcms-24.12.2.dist-info/WHEEL,sha256=pxeNX5JdtCe58PUSYP9upmc7jdRPgvT0Gm9kb1SHlVw,109
|
349
|
+
wagtail_cjkcms-24.12.2.dist-info/entry_points.txt,sha256=FzoiFENdZ1uebNztyz6GlswkumQspd5VjWbR9MUIH_8,50
|
350
|
+
wagtail_cjkcms-24.12.2.dist-info/top_level.txt,sha256=8wJGOGo1pG5nO5akfcMzA7i3ndj5868I8w35vTT0JJM,7
|
351
|
+
wagtail_cjkcms-24.12.2.dist-info/RECORD,,
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|