page.go (23544B)
1 // Copyright 2019 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package hugolib 15 16 import ( 17 "bytes" 18 "fmt" 19 "os" 20 "path" 21 "path/filepath" 22 "sort" 23 "strings" 24 25 "go.uber.org/atomic" 26 27 "github.com/gohugoio/hugo/identity" 28 29 "github.com/gohugoio/hugo/markup/converter" 30 31 "github.com/gohugoio/hugo/tpl" 32 33 "github.com/gohugoio/hugo/hugofs/files" 34 35 "github.com/bep/gitmap" 36 37 "github.com/gohugoio/hugo/helpers" 38 39 "github.com/gohugoio/hugo/common/herrors" 40 "github.com/gohugoio/hugo/parser/metadecoders" 41 42 "errors" 43 44 "github.com/gohugoio/hugo/parser/pageparser" 45 46 "github.com/gohugoio/hugo/output" 47 48 "github.com/gohugoio/hugo/media" 49 "github.com/gohugoio/hugo/source" 50 51 "github.com/gohugoio/hugo/common/collections" 52 "github.com/gohugoio/hugo/common/text" 53 "github.com/gohugoio/hugo/resources" 54 "github.com/gohugoio/hugo/resources/page" 55 "github.com/gohugoio/hugo/resources/resource" 56 ) 57 58 var ( 59 _ page.Page = (*pageState)(nil) 60 _ collections.Grouper = (*pageState)(nil) 61 _ collections.Slicer = (*pageState)(nil) 62 ) 63 64 var ( 65 pageTypesProvider = resource.NewResourceTypesProvider(media.OctetType, pageResourceType) 66 nopPageOutput = &pageOutput{ 67 pagePerOutputProviders: nopPagePerOutput, 68 ContentProvider: page.NopPage, 69 TableOfContentsProvider: page.NopPage, 70 } 71 ) 72 73 // pageContext provides contextual information about this page, for error 74 // logging and similar. 75 type pageContext interface { 76 posOffset(offset int) text.Position 77 wrapError(err error) error 78 getContentConverter() converter.Converter 79 addDependency(dep identity.Provider) 80 } 81 82 // wrapErr adds some context to the given error if possible. 83 func wrapErr(err error, ctx any) error { 84 if pc, ok := ctx.(pageContext); ok { 85 return pc.wrapError(err) 86 } 87 return err 88 } 89 90 type pageSiteAdapter struct { 91 p page.Page 92 s *Site 93 } 94 95 func (pa pageSiteAdapter) GetPageWithTemplateInfo(info tpl.Info, ref string) (page.Page, error) { 96 p, err := pa.GetPage(ref) 97 if p != nil { 98 // Track pages referenced by templates/shortcodes 99 // when in server mode. 100 if im, ok := info.(identity.Manager); ok { 101 im.Add(p) 102 } 103 } 104 return p, err 105 } 106 107 func (pa pageSiteAdapter) GetPage(ref string) (page.Page, error) { 108 p, err := pa.s.getPageNew(pa.p, ref) 109 if p == nil { 110 // The nil struct has meaning in some situations, mostly to avoid breaking 111 // existing sites doing $nilpage.IsDescendant($p), which will always return 112 // false. 113 p = page.NilPage 114 } 115 return p, err 116 } 117 118 type pageState struct { 119 // This slice will be of same length as the number of global slice of output 120 // formats (for all sites). 121 pageOutputs []*pageOutput 122 123 // Used to determine if we can reuse content across output formats. 124 pageOutputTemplateVariationsState *atomic.Uint32 125 126 // This will be shifted out when we start to render a new output format. 127 *pageOutput 128 129 // Common for all output formats. 130 *pageCommon 131 } 132 133 func (p *pageState) reusePageOutputContent() bool { 134 return p.pageOutputTemplateVariationsState.Load() == 1 135 } 136 137 func (p *pageState) Err() resource.ResourceError { 138 return nil 139 } 140 141 // Eq returns whether the current page equals the given page. 142 // This is what's invoked when doing `{{ if eq $page $otherPage }}` 143 func (p *pageState) Eq(other any) bool { 144 pp, err := unwrapPage(other) 145 if err != nil { 146 return false 147 } 148 149 return p == pp 150 } 151 152 func (p *pageState) GetIdentity() identity.Identity { 153 return identity.NewPathIdentity(files.ComponentFolderContent, filepath.FromSlash(p.Pathc())) 154 } 155 156 func (p *pageState) GitInfo() *gitmap.GitInfo { 157 return p.gitInfo 158 } 159 160 func (p *pageState) CodeOwners() []string { 161 return p.codeowners 162 } 163 164 // GetTerms gets the terms defined on this page in the given taxonomy. 165 // The pages returned will be ordered according to the front matter. 166 func (p *pageState) GetTerms(taxonomy string) page.Pages { 167 if p.treeRef == nil { 168 return nil 169 } 170 171 m := p.s.pageMap 172 173 taxonomy = strings.ToLower(taxonomy) 174 prefix := cleanSectionTreeKey(taxonomy) 175 self := strings.TrimPrefix(p.treeRef.key, "/") 176 177 var pas page.Pages 178 179 m.taxonomies.WalkQuery(pageMapQuery{Prefix: prefix}, func(s string, n *contentNode) bool { 180 key := s + self 181 if tn, found := m.taxonomyEntries.Get(key); found { 182 vi := tn.(*contentNode).viewInfo 183 pas = append(pas, pageWithOrdinal{pageState: n.p, ordinal: vi.ordinal}) 184 } 185 return false 186 }) 187 188 page.SortByDefault(pas) 189 190 return pas 191 } 192 193 func (p *pageState) MarshalJSON() ([]byte, error) { 194 return page.MarshalPageToJSON(p) 195 } 196 197 func (p *pageState) getPages() page.Pages { 198 b := p.bucket 199 if b == nil { 200 return nil 201 } 202 return b.getPages() 203 } 204 205 func (p *pageState) getPagesRecursive() page.Pages { 206 b := p.bucket 207 if b == nil { 208 return nil 209 } 210 return b.getPagesRecursive() 211 } 212 213 func (p *pageState) getPagesAndSections() page.Pages { 214 b := p.bucket 215 if b == nil { 216 return nil 217 } 218 return b.getPagesAndSections() 219 } 220 221 func (p *pageState) RegularPagesRecursive() page.Pages { 222 p.regularPagesRecursiveInit.Do(func() { 223 var pages page.Pages 224 switch p.Kind() { 225 case page.KindSection: 226 pages = p.getPagesRecursive() 227 default: 228 pages = p.RegularPages() 229 } 230 p.regularPagesRecursive = pages 231 }) 232 return p.regularPagesRecursive 233 } 234 235 func (p *pageState) PagesRecursive() page.Pages { 236 return nil 237 } 238 239 func (p *pageState) RegularPages() page.Pages { 240 p.regularPagesInit.Do(func() { 241 var pages page.Pages 242 243 switch p.Kind() { 244 case page.KindPage: 245 case page.KindSection, page.KindHome, page.KindTaxonomy: 246 pages = p.getPages() 247 case page.KindTerm: 248 all := p.Pages() 249 for _, p := range all { 250 if p.IsPage() { 251 pages = append(pages, p) 252 } 253 } 254 default: 255 pages = p.s.RegularPages() 256 } 257 258 p.regularPages = pages 259 }) 260 261 return p.regularPages 262 } 263 264 func (p *pageState) Pages() page.Pages { 265 p.pagesInit.Do(func() { 266 var pages page.Pages 267 268 switch p.Kind() { 269 case page.KindPage: 270 case page.KindSection, page.KindHome: 271 pages = p.getPagesAndSections() 272 case page.KindTerm: 273 pages = p.bucket.getTaxonomyEntries() 274 case page.KindTaxonomy: 275 pages = p.bucket.getTaxonomies() 276 default: 277 pages = p.s.Pages() 278 } 279 280 p.pages = pages 281 }) 282 283 return p.pages 284 } 285 286 // RawContent returns the un-rendered source content without 287 // any leading front matter. 288 func (p *pageState) RawContent() string { 289 if p.source.parsed == nil { 290 return "" 291 } 292 start := p.source.posMainContent 293 if start == -1 { 294 start = 0 295 } 296 return string(p.source.parsed.Input()[start:]) 297 } 298 299 func (p *pageState) sortResources() { 300 sort.SliceStable(p.resources, func(i, j int) bool { 301 ri, rj := p.resources[i], p.resources[j] 302 if ri.ResourceType() < rj.ResourceType() { 303 return true 304 } 305 306 p1, ok1 := ri.(page.Page) 307 p2, ok2 := rj.(page.Page) 308 309 if ok1 != ok2 { 310 return ok2 311 } 312 313 if ok1 { 314 return page.DefaultPageSort(p1, p2) 315 } 316 317 // Make sure not to use RelPermalink or any of the other methods that 318 // trigger lazy publishing. 319 return ri.Name() < rj.Name() 320 }) 321 } 322 323 func (p *pageState) Resources() resource.Resources { 324 p.resourcesInit.Do(func() { 325 p.sortResources() 326 if len(p.m.resourcesMetadata) > 0 { 327 resources.AssignMetadata(p.m.resourcesMetadata, p.resources...) 328 p.sortResources() 329 } 330 }) 331 return p.resources 332 } 333 334 func (p *pageState) HasShortcode(name string) bool { 335 if p.shortcodeState == nil { 336 return false 337 } 338 339 return p.shortcodeState.hasName(name) 340 } 341 342 func (p *pageState) Site() page.Site { 343 return p.s.Info 344 } 345 346 func (p *pageState) String() string { 347 if sourceRef := p.sourceRef(); sourceRef != "" { 348 return fmt.Sprintf("Page(%s)", sourceRef) 349 } 350 return fmt.Sprintf("Page(%q)", p.Title()) 351 } 352 353 // IsTranslated returns whether this content file is translated to 354 // other language(s). 355 func (p *pageState) IsTranslated() bool { 356 p.s.h.init.translations.Do() 357 return len(p.translations) > 0 358 } 359 360 // TranslationKey returns the key used to map language translations of this page. 361 // It will use the translationKey set in front matter if set, or the content path and 362 // filename (excluding any language code and extension), e.g. "about/index". 363 // The Page Kind is always prepended. 364 func (p *pageState) TranslationKey() string { 365 p.translationKeyInit.Do(func() { 366 if p.m.translationKey != "" { 367 p.translationKey = p.Kind() + "/" + p.m.translationKey 368 } else if p.IsPage() && !p.File().IsZero() { 369 p.translationKey = path.Join(p.Kind(), filepath.ToSlash(p.File().Dir()), p.File().TranslationBaseName()) 370 } else if p.IsNode() { 371 p.translationKey = path.Join(p.Kind(), p.SectionsPath()) 372 } 373 }) 374 375 return p.translationKey 376 } 377 378 // AllTranslations returns all translations, including the current Page. 379 func (p *pageState) AllTranslations() page.Pages { 380 p.s.h.init.translations.Do() 381 return p.allTranslations 382 } 383 384 // Translations returns the translations excluding the current Page. 385 func (p *pageState) Translations() page.Pages { 386 p.s.h.init.translations.Do() 387 return p.translations 388 } 389 390 func (ps *pageState) initCommonProviders(pp pagePaths) error { 391 if ps.IsPage() { 392 ps.posNextPrev = &nextPrev{init: ps.s.init.prevNext} 393 ps.posNextPrevSection = &nextPrev{init: ps.s.init.prevNextInSection} 394 ps.InSectionPositioner = newPagePositionInSection(ps.posNextPrevSection) 395 ps.Positioner = newPagePosition(ps.posNextPrev) 396 } 397 398 ps.OutputFormatsProvider = pp 399 ps.targetPathDescriptor = pp.targetPathDescriptor 400 ps.RefProvider = newPageRef(ps) 401 ps.SitesProvider = ps.s.Info 402 403 return nil 404 } 405 406 func (p *pageState) getLayoutDescriptor() output.LayoutDescriptor { 407 p.layoutDescriptorInit.Do(func() { 408 var section string 409 sections := p.SectionsEntries() 410 411 switch p.Kind() { 412 case page.KindSection: 413 if len(sections) > 0 { 414 section = sections[0] 415 } 416 case page.KindTaxonomy, page.KindTerm: 417 b := p.getTreeRef().n 418 section = b.viewInfo.name.singular 419 default: 420 } 421 422 p.layoutDescriptor = output.LayoutDescriptor{ 423 Kind: p.Kind(), 424 Type: p.Type(), 425 Lang: p.Language().Lang, 426 Layout: p.Layout(), 427 Section: section, 428 } 429 }) 430 431 return p.layoutDescriptor 432 } 433 434 func (p *pageState) resolveTemplate(layouts ...string) (tpl.Template, bool, error) { 435 f := p.outputFormat() 436 437 if len(layouts) == 0 { 438 selfLayout := p.selfLayoutForOutput(f) 439 if selfLayout != "" { 440 templ, found := p.s.Tmpl().Lookup(selfLayout) 441 return templ, found, nil 442 } 443 } 444 445 d := p.getLayoutDescriptor() 446 447 if len(layouts) > 0 { 448 d.Layout = layouts[0] 449 d.LayoutOverride = true 450 } 451 452 return p.s.Tmpl().LookupLayout(d, f) 453 } 454 455 // This is serialized 456 func (p *pageState) initOutputFormat(isRenderingSite bool, idx int) error { 457 if err := p.shiftToOutputFormat(isRenderingSite, idx); err != nil { 458 return err 459 } 460 461 return nil 462 } 463 464 // Must be run after the site section tree etc. is built and ready. 465 func (p *pageState) initPage() error { 466 if _, err := p.init.Do(); err != nil { 467 return err 468 } 469 return nil 470 } 471 472 func (p *pageState) renderResources() (err error) { 473 p.resourcesPublishInit.Do(func() { 474 var toBeDeleted []int 475 476 for i, r := range p.Resources() { 477 478 if _, ok := r.(page.Page); ok { 479 // Pages gets rendered with the owning page but we count them here. 480 p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Pages) 481 continue 482 } 483 484 src, ok := r.(resource.Source) 485 if !ok { 486 err = fmt.Errorf("Resource %T does not support resource.Source", src) 487 return 488 } 489 490 if err := src.Publish(); err != nil { 491 if os.IsNotExist(err) { 492 // The resource has been deleted from the file system. 493 // This should be extremely rare, but can happen on live reload in server 494 // mode when the same resource is member of different page bundles. 495 toBeDeleted = append(toBeDeleted, i) 496 } else { 497 p.s.Log.Errorf("Failed to publish Resource for page %q: %s", p.pathOrTitle(), err) 498 } 499 } else { 500 p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Files) 501 } 502 } 503 504 for _, i := range toBeDeleted { 505 p.deleteResource(i) 506 } 507 }) 508 509 return 510 } 511 512 func (p *pageState) deleteResource(i int) { 513 p.resources = append(p.resources[:i], p.resources[i+1:]...) 514 } 515 516 func (p *pageState) getTargetPaths() page.TargetPaths { 517 return p.targetPaths() 518 } 519 520 func (p *pageState) setTranslations(pages page.Pages) { 521 p.allTranslations = pages 522 page.SortByLanguage(p.allTranslations) 523 translations := make(page.Pages, 0) 524 for _, t := range p.allTranslations { 525 if !t.Eq(p) { 526 translations = append(translations, t) 527 } 528 } 529 p.translations = translations 530 } 531 532 func (p *pageState) AlternativeOutputFormats() page.OutputFormats { 533 f := p.outputFormat() 534 var o page.OutputFormats 535 for _, of := range p.OutputFormats() { 536 if of.Format.NotAlternative || of.Format.Name == f.Name { 537 continue 538 } 539 540 o = append(o, of) 541 } 542 return o 543 } 544 545 type renderStringOpts struct { 546 Display string 547 Markup string 548 } 549 550 var defaultRenderStringOpts = renderStringOpts{ 551 Display: "inline", 552 Markup: "", // Will inherit the page's value when not set. 553 } 554 555 func (p *pageState) addDependency(dep identity.Provider) { 556 if !p.s.running() || p.pageOutput.cp == nil { 557 return 558 } 559 p.pageOutput.cp.dependencyTracker.Add(dep) 560 } 561 562 // wrapError adds some more context to the given error if possible/needed 563 func (p *pageState) wrapError(err error) error { 564 if err == nil { 565 panic("wrapError with nil") 566 } 567 568 if p.File().IsZero() { 569 // No more details to add. 570 return fmt.Errorf("%q: %w", p.Pathc(), err) 571 } 572 573 filename := p.File().Filename() 574 575 // Check if it's already added. 576 for _, ferr := range herrors.UnwrapFileErrors(err) { 577 errfilename := ferr.Position().Filename 578 if errfilename == filename { 579 if ferr.ErrorContext() == nil { 580 f, ioerr := p.s.SourceSpec.Fs.Source.Open(filename) 581 if ioerr != nil { 582 return err 583 } 584 defer f.Close() 585 ferr.UpdateContent(f, nil) 586 } 587 return err 588 } 589 } 590 591 return herrors.NewFileErrorFromFile(err, filename, p.s.SourceSpec.Fs.Source, herrors.NopLineMatcher) 592 593 } 594 595 func (p *pageState) getContentConverter() converter.Converter { 596 var err error 597 p.m.contentConverterInit.Do(func() { 598 markup := p.m.markup 599 if markup == "html" { 600 // Only used for shortcode inner content. 601 markup = "markdown" 602 } 603 p.m.contentConverter, err = p.m.newContentConverter(p, markup) 604 }) 605 606 if err != nil { 607 p.s.Log.Errorln("Failed to create content converter:", err) 608 } 609 return p.m.contentConverter 610 } 611 612 func (p *pageState) mapContent(bucket *pagesMapBucket, meta *pageMeta) error { 613 p.cmap = &pageContentMap{ 614 items: make([]any, 0, 20), 615 } 616 617 return p.mapContentForResult( 618 p.source.parsed, 619 p.shortcodeState, 620 p.cmap, 621 meta.markup, 622 func(m map[string]interface{}) error { 623 return meta.setMetadata(bucket, p, m) 624 }, 625 ) 626 } 627 628 func (p *pageState) mapContentForResult( 629 result pageparser.Result, 630 s *shortcodeHandler, 631 rn *pageContentMap, 632 markup string, 633 withFrontMatter func(map[string]any) error, 634 ) error { 635 636 iter := result.Iterator() 637 638 fail := func(err error, i pageparser.Item) error { 639 if fe, ok := err.(herrors.FileError); ok { 640 return fe 641 } 642 return p.parseError(err, iter.Input(), i.Pos) 643 } 644 645 // the parser is guaranteed to return items in proper order or fail, so … 646 // … it's safe to keep some "global" state 647 var currShortcode shortcode 648 var ordinal int 649 var frontMatterSet bool 650 651 Loop: 652 for { 653 it := iter.Next() 654 655 switch { 656 case it.Type == pageparser.TypeIgnore: 657 case it.IsFrontMatter(): 658 f := pageparser.FormatFromFrontMatterType(it.Type) 659 m, err := metadecoders.Default.UnmarshalToMap(it.Val, f) 660 if err != nil { 661 if fe, ok := err.(herrors.FileError); ok { 662 pos := fe.Position() 663 // Apply the error to the content file. 664 pos.Filename = p.File().Filename() 665 // Offset the starting position of front matter. 666 offset := iter.LineNumber() - 1 667 if f == metadecoders.YAML { 668 offset -= 1 669 } 670 pos.LineNumber += offset 671 672 fe.UpdatePosition(pos) 673 674 return fe 675 } else { 676 return err 677 } 678 } 679 680 if withFrontMatter != nil { 681 if err := withFrontMatter(m); err != nil { 682 return err 683 } 684 } 685 686 frontMatterSet = true 687 688 next := iter.Peek() 689 if !next.IsDone() { 690 p.source.posMainContent = next.Pos 691 } 692 693 if !p.s.shouldBuild(p) { 694 // Nothing more to do. 695 return nil 696 } 697 698 case it.Type == pageparser.TypeLeadSummaryDivider: 699 posBody := -1 700 f := func(item pageparser.Item) bool { 701 if posBody == -1 && !item.IsDone() { 702 posBody = item.Pos 703 } 704 705 if item.IsNonWhitespace() { 706 p.truncated = true 707 708 // Done 709 return false 710 } 711 return true 712 } 713 iter.PeekWalk(f) 714 715 p.source.posSummaryEnd = it.Pos 716 p.source.posBodyStart = posBody 717 p.source.hasSummaryDivider = true 718 719 if markup != "html" { 720 // The content will be rendered by Goldmark or similar, 721 // and we need to track the summary. 722 rn.AddReplacement(internalSummaryDividerPre, it) 723 } 724 725 // Handle shortcode 726 case it.IsLeftShortcodeDelim(): 727 // let extractShortcode handle left delim (will do so recursively) 728 iter.Backup() 729 730 currShortcode, err := s.extractShortcode(ordinal, 0, iter) 731 if err != nil { 732 return fail(err, it) 733 } 734 735 currShortcode.pos = it.Pos 736 currShortcode.length = iter.Current().Pos - it.Pos 737 if currShortcode.placeholder == "" { 738 currShortcode.placeholder = createShortcodePlaceholder("s", currShortcode.ordinal) 739 } 740 741 if currShortcode.name != "" { 742 s.addName(currShortcode.name) 743 } 744 745 if currShortcode.params == nil { 746 var s []string 747 currShortcode.params = s 748 } 749 750 currShortcode.placeholder = createShortcodePlaceholder("s", ordinal) 751 ordinal++ 752 s.shortcodes = append(s.shortcodes, currShortcode) 753 754 rn.AddShortcode(currShortcode) 755 756 case it.Type == pageparser.TypeEmoji: 757 if emoji := helpers.Emoji(it.ValStr()); emoji != nil { 758 rn.AddReplacement(emoji, it) 759 } else { 760 rn.AddBytes(it) 761 } 762 case it.IsEOF(): 763 break Loop 764 case it.IsError(): 765 err := fail(errors.New(it.ValStr()), it) 766 currShortcode.err = err 767 return err 768 769 default: 770 rn.AddBytes(it) 771 } 772 } 773 774 if !frontMatterSet && withFrontMatter != nil { 775 // Page content without front matter. Assign default front matter from 776 // cascades etc. 777 if err := withFrontMatter(nil); err != nil { 778 return err 779 } 780 } 781 782 return nil 783 } 784 785 func (p *pageState) errorf(err error, format string, a ...any) error { 786 if herrors.UnwrapFileError(err) != nil { 787 // More isn't always better. 788 return err 789 } 790 args := append([]any{p.Language().Lang, p.pathOrTitle()}, a...) 791 args = append(args, err) 792 format = "[%s] page %q: " + format + ": %w" 793 if err == nil { 794 return fmt.Errorf(format, args...) 795 } 796 return fmt.Errorf(format, args...) 797 } 798 799 func (p *pageState) outputFormat() (f output.Format) { 800 if p.pageOutput == nil { 801 panic("no pageOutput") 802 } 803 return p.pageOutput.f 804 } 805 806 func (p *pageState) parseError(err error, input []byte, offset int) error { 807 pos := p.posFromInput(input, offset) 808 return herrors.NewFileErrorFromName(err, p.File().Filename()).UpdatePosition(pos) 809 } 810 811 func (p *pageState) pathOrTitle() string { 812 if !p.File().IsZero() { 813 return p.File().Filename() 814 } 815 816 if p.Pathc() != "" { 817 return p.Pathc() 818 } 819 820 return p.Title() 821 } 822 823 func (p *pageState) posFromPage(offset int) text.Position { 824 return p.posFromInput(p.source.parsed.Input(), offset) 825 } 826 827 func (p *pageState) posFromInput(input []byte, offset int) text.Position { 828 if offset < 0 { 829 return text.Position{ 830 Filename: p.pathOrTitle(), 831 } 832 } 833 lf := []byte("\n") 834 input = input[:offset] 835 lineNumber := bytes.Count(input, lf) + 1 836 endOfLastLine := bytes.LastIndex(input, lf) 837 838 return text.Position{ 839 Filename: p.pathOrTitle(), 840 LineNumber: lineNumber, 841 ColumnNumber: offset - endOfLastLine, 842 Offset: offset, 843 } 844 } 845 846 func (p *pageState) posOffset(offset int) text.Position { 847 return p.posFromInput(p.source.parsed.Input(), offset) 848 } 849 850 // shiftToOutputFormat is serialized. The output format idx refers to the 851 // full set of output formats for all sites. 852 func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error { 853 if err := p.initPage(); err != nil { 854 return err 855 } 856 857 if len(p.pageOutputs) == 1 { 858 idx = 0 859 } 860 861 p.pageOutput = p.pageOutputs[idx] 862 if p.pageOutput == nil { 863 panic(fmt.Sprintf("pageOutput is nil for output idx %d", idx)) 864 } 865 866 // Reset any built paginator. This will trigger when re-rendering pages in 867 // server mode. 868 if isRenderingSite && p.pageOutput.paginator != nil && p.pageOutput.paginator.current != nil { 869 p.pageOutput.paginator.reset() 870 } 871 872 if isRenderingSite { 873 cp := p.pageOutput.cp 874 if cp == nil && p.reusePageOutputContent() { 875 // Look for content to reuse. 876 for i := 0; i < len(p.pageOutputs); i++ { 877 if i == idx { 878 continue 879 } 880 po := p.pageOutputs[i] 881 882 if po.cp != nil { 883 cp = po.cp 884 break 885 } 886 } 887 } 888 889 if cp == nil { 890 var err error 891 cp, err = newPageContentOutput(p, p.pageOutput) 892 if err != nil { 893 return err 894 } 895 } 896 p.pageOutput.initContentProvider(cp) 897 } else { 898 // We attempt to assign pageContentOutputs while preparing each site 899 // for rendering and before rendering each site. This lets us share 900 // content between page outputs to conserve resources. But if a template 901 // unexpectedly calls a method of a ContentProvider that is not yet 902 // initialized, we assign a LazyContentProvider that performs the 903 // initialization just in time. 904 if lcp, ok := (p.pageOutput.ContentProvider.(*page.LazyContentProvider)); ok { 905 lcp.Reset() 906 } else { 907 lcp = page.NewLazyContentProvider(func() (page.OutputFormatContentProvider, error) { 908 cp, err := newPageContentOutput(p, p.pageOutput) 909 if err != nil { 910 return nil, err 911 } 912 return cp, nil 913 }) 914 p.pageOutput.ContentProvider = lcp 915 p.pageOutput.TableOfContentsProvider = lcp 916 p.pageOutput.PageRenderProvider = lcp 917 } 918 } 919 920 return nil 921 } 922 923 // sourceRef returns the reference used by GetPage and ref/relref shortcodes to refer to 924 // this page. It is prefixed with a "/". 925 // 926 // For pages that have a source file, it is returns the path to this file as an 927 // absolute path rooted in this site's content dir. 928 // For pages that do not (sections without content page etc.), it returns the 929 // virtual path, consistent with where you would add a source file. 930 func (p *pageState) sourceRef() string { 931 if !p.File().IsZero() { 932 sourcePath := p.File().Path() 933 if sourcePath != "" { 934 return "/" + filepath.ToSlash(sourcePath) 935 } 936 } 937 938 if len(p.SectionsEntries()) > 0 { 939 // no backing file, return the virtual source path 940 return "/" + p.SectionsPath() 941 } 942 943 return "" 944 } 945 946 func (s *Site) sectionsFromFile(fi source.File) []string { 947 dirname := fi.Dir() 948 949 dirname = strings.Trim(dirname, helpers.FilePathSeparator) 950 if dirname == "" { 951 return nil 952 } 953 parts := strings.Split(dirname, helpers.FilePathSeparator) 954 955 if fii, ok := fi.(*fileInfo); ok { 956 if len(parts) > 0 && fii.FileInfo().Meta().Classifier == files.ContentClassLeaf { 957 // my-section/mybundle/index.md => my-section 958 return parts[:len(parts)-1] 959 } 960 } 961 962 return parts 963 } 964 965 var ( 966 _ page.Page = (*pageWithOrdinal)(nil) 967 _ collections.Order = (*pageWithOrdinal)(nil) 968 _ pageWrapper = (*pageWithOrdinal)(nil) 969 ) 970 971 type pageWithOrdinal struct { 972 ordinal int 973 *pageState 974 } 975 976 func (p pageWithOrdinal) Ordinal() int { 977 return p.ordinal 978 } 979 980 func (p pageWithOrdinal) page() page.Page { 981 return p.pageState 982 }