Skip to content

Commit 38feab8

Browse files
committed
Merge remote-tracking branch 'origin/main' into feature/integration-tests
2 parents 69a3c5c + 58fe884 commit 38feab8

File tree

22 files changed

+382
-158
lines changed

22 files changed

+382
-158
lines changed

docs/_docset.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ subs:
1616
ech: "Elastic Cloud Hosted"
1717
serverless-full: "Elastic Cloud Serverless"
1818
ecloud: "Elastic Cloud"
19+
dbuild: "docs-builder"
1920

2021
features:
2122
primary-nav: false

docs/contribute/locally.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ cd docs-content
145145
```
146146
:::::
147147

148-
:::::{step} Run docs-builder
148+
:::::{step} Run {{dbuild}}
149149

150150
Run the `docs-builder` binary with the `serve` command to build and serve the content set to [http://localhost:3000](http://localhost:3000). If necessary, specify the path to the `docset.yml` file that you want to build with `-p`.
151151

docs/syntax/images.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ The image carousel directive builds upon the image directive.
131131
::::{carousel}
132132

133133
:id: nested-carousel-example
134-
:fixed-height: small ## small, medium, auto (auto is default if fixed-height is not specified)
134+
:max-height: small ## small, medium, none (none is default if max-height is not specified)
135135

136136
:::{image} images/apm.png
137137
:alt: First image description
@@ -148,7 +148,7 @@ The image carousel directive builds upon the image directive.
148148
::::{carousel}
149149

150150
:id: nested-carousel-example
151-
:fixed-height: small
151+
:max-height: small
152152

153153
:::{image} images/apm.png
154154
:alt: First image description

src/Elastic.Documentation.Site/Assets/image-carousel.ts

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ class ImageCarousel {
1818
this.slides = Array.from(
1919
this.container.querySelectorAll('.carousel-slide')
2020
)
21+
22+
// Don't initialize if no slides
23+
if (this.slides.length === 0) {
24+
console.warn('No carousel slides found')
25+
return
26+
}
27+
2128
this.indicators = Array.from(
2229
this.container.querySelectorAll('.carousel-indicator')
2330
)
@@ -26,6 +33,7 @@ class ImageCarousel {
2633

2734
this.initializeSlides()
2835
this.setupEventListeners()
36+
this.positionControls()
2937
}
3038

3139
private initializeSlides(): void {
@@ -60,6 +68,23 @@ class ImageCarousel {
6068
indicator.addEventListener('click', () => this.goToSlide(index))
6169
})
6270

71+
// Handle image clicks for modal
72+
this.slides.forEach((slide) => {
73+
const imageLink = slide.querySelector('.carousel-image-reference')
74+
if (imageLink) {
75+
imageLink.addEventListener('click', (e) => {
76+
e.preventDefault()
77+
const modalId = imageLink.getAttribute('data-modal-id')
78+
if (modalId) {
79+
const modal = document.getElementById(modalId)
80+
if (modal) {
81+
modal.style.display = 'flex'
82+
}
83+
}
84+
})
85+
}
86+
})
87+
6388
// Keyboard navigation
6489
document.addEventListener('keydown', (e) => {
6590
if (!this.isInViewport()) return
@@ -125,6 +150,69 @@ class ImageCarousel {
125150
(window.innerWidth || document.documentElement.clientWidth)
126151
)
127152
}
153+
154+
private positionControls(): void {
155+
if (!this.prevButton || !this.nextButton) return
156+
157+
// Wait for images to load before positioning
158+
const images = Array.from(this.container.querySelectorAll('img'))
159+
if (images.length === 0) return
160+
161+
let loadedCount = 0
162+
const totalImages = images.length
163+
164+
const positionAfterLoad = () => {
165+
loadedCount++
166+
if (loadedCount === totalImages) {
167+
this.calculateControlPosition()
168+
}
169+
}
170+
171+
images.forEach((img) => {
172+
if (img.complete) {
173+
positionAfterLoad()
174+
} else {
175+
img.addEventListener('load', positionAfterLoad)
176+
img.addEventListener('error', positionAfterLoad) // Handle failed loads
177+
}
178+
})
179+
}
180+
181+
private calculateControlPosition(): void {
182+
if (!this.prevButton || !this.nextButton) return
183+
184+
const images = Array.from(this.container.querySelectorAll('img'))
185+
let minHeight = Infinity
186+
187+
// Find the smallest image height among all images
188+
images.forEach((img) => {
189+
const height = img.offsetHeight
190+
if (height > 0 && height < minHeight) {
191+
minHeight = height
192+
}
193+
})
194+
195+
// Position controls at 40% the height of the smallest image
196+
// But ensure a minimum distance from the top (50px) and don't go below 80% of the smallest image
197+
if (minHeight !== Infinity && minHeight > 0) {
198+
const fortyPercentHeight = Math.floor(minHeight * 0.4)
199+
const minTop = 50 // Minimum 50px from top
200+
const maxTop = Math.floor(minHeight * 0.8) // Maximum 80% down the smallest image
201+
202+
const controlTop = Math.max(
203+
minTop,
204+
Math.min(fortyPercentHeight, maxTop)
205+
)
206+
207+
this.prevButton.style.top = `${controlTop}px`
208+
this.nextButton.style.top = `${controlTop}px`
209+
210+
// Debug logging (remove in production)
211+
console.log(
212+
`Carousel controls positioned: minHeight=${minHeight}px, controlTop=${controlTop}px`
213+
)
214+
}
215+
}
128216
}
129217

130218
// Export function to initialize carousels
@@ -136,19 +224,22 @@ export function initImageCarousel(): void {
136224
carousels.forEach((carouselElement) => {
137225
const carousel = carouselElement as HTMLElement
138226

139-
// Get the existing track
227+
// Skip if carousel already has slides (server-rendered)
228+
const existingSlides = carousel.querySelectorAll('.carousel-slide')
229+
if (existingSlides.length > 0) {
230+
// Just initialize the existing carousel
231+
new ImageCarousel(carousel)
232+
return
233+
}
234+
235+
// Get the existing track for dynamic carousels
140236
let track = carousel.querySelector('.carousel-track')
141237
if (!track) {
142238
track = document.createElement('div')
143239
track.className = 'carousel-track'
144240
carousel.appendChild(track)
145241
}
146242

147-
// Clean up any existing slides - this prevents duplicates
148-
const existingSlides = Array.from(
149-
track.querySelectorAll('.carousel-slide')
150-
)
151-
152243
// Find all image links that might be related to this carousel
153244
const section = findSectionForCarousel(carousel)
154245
if (!section) return

src/Elastic.Documentation.Site/Assets/markdown/image-carousel.css

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22
position: relative;
33
width: 100%;
44
margin: 2rem 0;
5-
overflow: hidden;
65
}
76

87
.carousel-track {
98
width: 100%;
109
position: relative;
11-
min-height: 200px;
1210
}
1311

1412
.carousel-slide {
@@ -27,19 +25,25 @@
2725
}
2826

2927
.carousel-image-reference {
30-
display: block;
28+
display: flex;
29+
justify-content: center;
30+
align-items: center;
31+
text-decoration: none;
32+
}
33+
34+
.carousel-image-reference::after {
35+
display: none !important;
3136
}
3237

3338
.carousel-image-reference img {
34-
width: 100%;
39+
max-width: 100%;
3540
height: auto;
3641
display: block;
42+
margin: 0 auto;
3743
}
3844

3945
.carousel-control {
4046
position: absolute;
41-
top: 50%;
42-
transform: translateY(-50%);
4347
background-color: rgba(0, 0, 0, 0.5);
4448
border: none;
4549
color: white;
@@ -60,11 +64,11 @@
6064
}
6165

6266
.carousel-prev {
63-
left: 10px;
67+
left: 0;
6468
}
6569

6670
.carousel-next {
67-
right: 10px;
71+
right: 0;
6872
}
6973

7074
.carousel-indicators {
@@ -92,38 +96,61 @@
9296
background-color: black;
9397
}
9498

95-
/* Fixed height carousel styles */
96-
.carousel-container[data-fixed-height] .carousel-track {
97-
min-height: auto;
99+
/* Max height carousel styles */
100+
.carousel-container[data-max-height] {
101+
padding-bottom: 40px; /* Space for indicators */
102+
}
103+
104+
.carousel-container[data-max-height] .carousel-track {
105+
max-height: var(--carousel-max-height);
98106
overflow: hidden;
99107
}
100108

101-
.carousel-container[data-fixed-height] .carousel-slide {
102-
height: 100%;
103-
top: 0;
104-
left: 0;
109+
.carousel-container[data-max-height] .carousel-image-reference img {
110+
max-height: var(--carousel-max-height);
111+
width: auto;
105112
}
106113

107-
.carousel-container[data-fixed-height] .carousel-slide[data-active='true'] {
108-
position: relative;
109-
height: 100%;
110-
top: auto;
111-
left: auto;
114+
/* None height carousel styles - images at natural size */
115+
.carousel-container[data-none-height] {
116+
padding-bottom: 40px; /* Space for indicators */
112117
}
113118

114-
.carousel-container[data-fixed-height] .carousel-image-reference {
115-
height: 100%;
119+
/* Override modal styles for image carousels */
120+
.modal .modal-content {
121+
max-width: 95vw !important;
122+
max-height: 95vh !important;
123+
width: auto !important;
124+
height: auto !important;
125+
padding: 0 !important;
126+
background: transparent !important;
127+
box-shadow: none !important;
128+
}
129+
130+
.modal .modal-content img {
131+
max-width: 95vw;
132+
max-height: 95vh;
133+
width: auto;
134+
height: auto;
135+
object-fit: contain;
136+
display: block;
137+
}
138+
139+
/* Ensure the close button is visible */
140+
.modal .modal-close {
141+
background: rgba(0, 0, 0, 0.7);
142+
border-radius: 50%;
143+
width: 40px;
144+
height: 40px;
116145
display: flex;
117146
align-items: center;
118147
justify-content: center;
148+
top: -20px;
149+
right: -20px;
119150
}
120151

121-
.carousel-container[data-fixed-height] .carousel-image-reference img {
122-
width: auto;
123-
height: 100%;
124-
max-width: 100%;
125-
object-fit: contain;
126-
object-position: center;
152+
.modal .modal-close a {
153+
color: white;
127154
}
128155
@media (max-width: 768px) {
129156
.carousel-control {

src/Elastic.Markdown/IO/MarkdownFile.cs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -289,15 +289,25 @@ public static List<PageTocItem> GetAnchors(
289289
.OfType<StepBlock>()
290290
.Where(step => !string.IsNullOrEmpty(step.Title))
291291
.Where(step => !IsNestedInOtherDirective(step))
292-
.Select(step => new
292+
.Select(step =>
293293
{
294-
TocItem = new PageTocItem
294+
var processedTitle = step.Title;
295+
// Apply substitutions to step titles
296+
if (subs.Count > 0 && processedTitle.AsSpan().ReplaceSubstitutions(subs, set.Context.Collector, out var replacement))
295297
{
296-
Heading = step.Title,
297-
Slug = step.Anchor,
298-
Level = step.HeadingLevel // Use dynamic heading level
299-
},
300-
step.Line
298+
processedTitle = replacement;
299+
}
300+
301+
return new
302+
{
303+
TocItem = new PageTocItem
304+
{
305+
Heading = processedTitle,
306+
Slug = step.Anchor,
307+
Level = step.HeadingLevel // Use dynamic heading level
308+
},
309+
step.Line
310+
};
301311
});
302312

303313
var toc = headingTocs

src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ private static void WriteImageCarousel(HtmlRenderer renderer, ImageCarouselBlock
138138
Target = img.Target,
139139
ImageUrl = img.ImageUrl
140140
}).ToList(),
141-
FixedHeight = block.FixedHeight
141+
MaxHeight = block.MaxHeight
142142
});
143143
RenderRazorSlice(slice, renderer);
144144
}

src/Elastic.Markdown/Myst/Directives/Image/ImageCarouselBlock.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,23 @@ public class ImageCarouselBlock(DirectiveBlockParser parser, ParserContext conte
1414
{
1515
public List<ImageBlock> Images { get; } = [];
1616
public string? Id { get; set; }
17-
public string? FixedHeight { get; set; }
17+
public string? MaxHeight { get; set; }
1818

1919
public override string Directive => "carousel";
2020

2121
public override void FinalizeAndValidate(ParserContext context)
2222
{
2323
// Parse options
2424
Id = Prop("id");
25-
FixedHeight = Prop("fixed-height");
25+
MaxHeight = Prop("max-height");
2626

27-
// Validate fixed-height option
28-
if (!string.IsNullOrEmpty(FixedHeight))
27+
// Validate max-height option
28+
if (!string.IsNullOrEmpty(MaxHeight))
2929
{
30-
var validHeights = new[] { "auto", "small", "medium" };
31-
if (!validHeights.Contains(FixedHeight.ToLower()))
30+
var validHeights = new[] { "none", "small", "medium" };
31+
if (!validHeights.Contains(MaxHeight.ToLower()))
3232
{
33-
this.EmitWarning($"Invalid fixed-height value '{FixedHeight}'. Valid options are: auto, small, medium. Defaulting to 'auto'.");
33+
this.EmitWarning($"Invalid max-height value '{MaxHeight}'. Valid options are: none, small, medium. Defaulting to 'none'.");
3434
}
3535
}
3636

0 commit comments

Comments
 (0)