| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 5a0c22c commit b9064c4
11 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,6 @@ | |||
| 1 | 1 | package controllers | |
| 2 | 2 | ||
| 3 | + import _root_.liveblog.LiveBlogPageModel | ||
| 3 | 4 | import com.gu.contentapi.client.model.ItemResponse | |
| 4 | 5 | import com.gu.contentapi.client.model.v1.{Content => ApiContent} | |
| 5 | 6 | import com.gu.util.liveblogs.{Block, BlockToText} | |
@@ -8,9 +9,8 @@ import conf.LiveContentApi.getResponse | |||
| 8 | 9 | import conf._ | |
| 9 | 10 | import conf.switches.Switches | |
| 10 | 11 | import conf.switches.Switches.LongCacheSwitch | |
| 11 | - import liveblog.BodyBlocks | ||
| 12 | 12 | import model._ | |
| 13 | - import model.liveblog.KeyEventData | ||
| 13 | + import model.liveblog.{BodyBlock, KeyEventData} | ||
| 14 | 14 | import org.joda.time.DateTime | |
| 15 | 15 | import performance.MemcachedAction | |
| 16 | 16 | import play.api.libs.functional.syntax._ | |
@@ -19,6 +19,7 @@ import play.api.mvc._ | |||
| 19 | 19 | import views.support._ | |
| 20 | 20 | ||
| 21 | 21 | import scala.concurrent.Future | |
| 22 | + import scala.util.parsing.combinator.RegexParsers | ||
| 22 | 23 | ||
| 23 | 24 | trait PageWithStoryPackage extends ContentPage { | |
| 24 | 25 | def article: Article | |
@@ -35,7 +36,7 @@ object ArticleController extends Controller with RendersItemResponse with Loggin | |||
| 35 | 36 | override def canRender(i: ItemResponse): Boolean = i.content.exists(isSupported) | |
| 36 | 37 | override def renderItem(path: String)(implicit request: RequestHeader): Future[Result] = mapModel(path, blocks = true)(render(path, _, None)) | |
| 37 | 38 | ||
| 38 | - private def renderLatestFrom(page: PageWithStoryPackage, lastUpdateBlockId: String, isLivePage: Option[Boolean])(implicit request: RequestHeader) = { | ||
| 39 | + private def renderNewerUpdates(page: PageWithStoryPackage, lastUpdateBlockId: String, isLivePage: Option[Boolean])(implicit request: RequestHeader) = { | ||
| 39 | 40 | val newBlocks = page.article.fields.blocks.takeWhile(block => s"block-${block.id}" != lastUpdateBlockId) | |
| 40 | 41 | val blocksHtml = views.html.liveblog.liveBlogBlocks(newBlocks, page.article, Edition(request).timezone) | |
| 41 | 42 | val timelineHtml = views.html.liveblog.keyEvents("", KeyEventData(newBlocks, Edition(request).timezone)) | |
@@ -72,19 +73,37 @@ object ArticleController extends Controller with RendersItemResponse with Loggin | |||
| 72 | 73 | ||
| 73 | 74 | } | |
| 74 | 75 | ||
| 75 | - private def render(path: String, page: PageWithStoryPackage, pageNo: Option[Int])(implicit request: RequestHeader) = page match { | ||
| 76 | + private def renderPageWithBlock( | ||
| 77 | + maybeRequiredBlockId: Option[String], | ||
| 78 | + blog: LiveBlogPage, | ||
| 79 | + modelGen: (Option[(BodyBlock) => Boolean], (BodyBlock) => String) => Option[LiveBlogPageModel[BodyBlock]] | ||
| 80 | + )(implicit request: RequestHeader) = | ||
| 81 | + modelGen( | ||
| 82 | + maybeRequiredBlockId.map(blockId => block => blockId == block.id), | ||
| 83 | + _.id | ||
| 84 | + ) match { | ||
| 85 | + case Some(blocks) => | ||
| 86 | + val htmlResponse = () => views.html.liveBlog (blog, blocks) | ||
| 87 | + val jsonResponse = () => views.html.liveblog.liveBlogBody (blog, blocks) | ||
| 88 | + renderFormat(htmlResponse, jsonResponse, blog, Switches.all) | ||
| 89 | + case None => NotFound | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + private def render(path: String, page: PageWithStoryPackage, pageParam: Option[String])(implicit request: RequestHeader) = page match { | ||
| 76 | 93 | case blog: LiveBlogPage => | |
| 77 | 94 | if (request.isAmp) { | |
| 78 | 95 | NotFound | |
| 79 | 96 | } else { | |
| 80 | 97 | val pageSize = if (blog.article.content.tags.tags.map(_.id).contains("sport/sport")) 50 else 10 | |
| 81 | - val blocks = BodyBlocks(pageSize = pageSize, extrasOnFirstPage = 10)(blog.article.content.fields.blocks, pageNo) | ||
| 82 | - blocks match { | ||
| 83 | - case Some(blocks) => | ||
| 84 | - val htmlResponse = () => views.html.liveBlog (blog, blocks) | ||
| 85 | - val jsonResponse = () => views.html.liveblog.liveBlogBody (blog, blocks) | ||
| 86 | - renderFormat(htmlResponse, jsonResponse, blog, Switches.all) | ||
| 87 | - case None => NotFound | ||
| 98 | + val modelGen = LiveBlogPageModel( | ||
| 99 | + pageSize = pageSize, | ||
| 100 | + extrasOnFirstPage = 10, | ||
| 101 | + blog.article.content.fields.blocks | ||
| 102 | + )_ | ||
| 103 | + pageParam.map(new PageParser().blockId) match { | ||
| 104 | + case Some(None) => NotFound | ||
| 105 | + case Some(Some(requiredBlockId)) => renderPageWithBlock(Some(requiredBlockId), blog, modelGen) | ||
| 106 | + case None => renderPageWithBlock(None, blog, modelGen) | ||
| 88 | 107 | } | |
| 89 | 108 | } | |
| 90 | 109 | ||
@@ -100,7 +119,7 @@ object ArticleController extends Controller with RendersItemResponse with Loggin | |||
| 100 | 119 | renderFormat(htmlResponse, jsonResponse, article, Switches.all) | |
| 101 | 120 | } | |
| 102 | 121 | ||
| 103 | - def renderLiveBlog(path: String, page: Option[Int] = None) = | ||
| 122 | + def renderLiveBlog(path: String, page: Option[String] = None) = | ||
| 104 | 123 | LongCacheAction { implicit request => | |
| 105 | 124 | mapModel(path, blocks = true) {// temporarily only ask for blocks too for things we know are new live blogs until until the migration is done and we can always use blocks | |
| 106 | 125 | render(path, _, page) | |
@@ -111,7 +130,7 @@ object ArticleController extends Controller with RendersItemResponse with Loggin | |||
| 111 | 130 | LongCacheAction { implicit request => | |
| 112 | 131 | mapModel(path, blocks = true) { model => | |
| 113 | 132 | (lastUpdate, rendered) match { | |
| 114 | - case (Some(lastUpdate), _) => renderLatestFrom(model, lastUpdate, isLivePage) | ||
| 133 | + case (Some(lastUpdate), _) => renderNewerUpdates(model, lastUpdate, isLivePage) | ||
| 115 | 134 | case (None, Some(false)) => blockText(model, 6) | |
| 116 | 135 | case (_, _) => render(path, model, None) | |
| 117 | 136 | } | |
@@ -184,3 +203,17 @@ object LongCacheAction { | |||
| 184 | 203 | } | |
| 185 | 204 | } | |
| 186 | 205 | } | |
| 206 | + | ||
| 207 | + class PageParser extends RegexParsers { | ||
| 208 | + def blockId(input: String): Option[String] = { | ||
| 209 | + def withParser: Parser[Unit] = "with:" ^^ { _ => () } | ||
| 210 | + def block: Parser[Unit] = "block-" ^^ { _ => () } | ||
| 211 | + def id: Parser[String] = "[a-zA-Z0-9]+".r | ||
| 212 | + def expr: Parser[String] = withParser ~> block ~> id | ||
| 213 | + | ||
| 214 | + parse(expr, input) match { | ||
| 215 | + case Success(matched, _) => Some(matched) | ||
| 216 | + case _ => None | ||
| 217 | + } | ||
| 218 | + } | ||
| 219 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,39 @@ | |||
| 1 | + package liveblog | ||
| 2 | + | ||
| 3 | + object LiveBlogPageModel { | ||
| 4 | + | ||
| 5 | + def apply[B](pageSize: Int, extrasOnFirstPage: Int, blocks: Seq[B])(isRequestedBlock: Option[B => Boolean], id: B => String): Option[LiveBlogPageModel[_ <: B]] = { | ||
| 6 | + val (main, pages) = getPages(pageSize, extrasOnFirstPage, blocks) | ||
| 7 | + val noPage = BlockInfo(Nil/*ignored*/, NoPage) | ||
| 8 | + val endedPages = noPage :: BlockInfo(main, FirstPage) :: (pages.map(page => BlockInfo(page, BlockPage(id(page.head)))) :+ noPage) | ||
| 9 | + | ||
| 10 | + def hasRequestedBlock(page: LiveBlogPageModel[_ <: B]): Boolean = { | ||
| 11 | + page.blocks.exists(isRequestedBlock.getOrElse(_ => true)) | ||
| 12 | + } | ||
| 13 | + | ||
| 14 | + endedPages.sliding(3).toList.map { | ||
| 15 | + case List(later, curr, earlier) => | ||
| 16 | + LiveBlogPageModel(curr.page, main, later.self, earlier.self, curr.self) | ||
| 17 | + }.find(hasRequestedBlock) | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + // returns the pages, newest at the end, newest at the start | ||
| 21 | + def getPages[B](pageSize: Int, extrasOnFirstPage: Int, blocks: Seq[B]): (Seq[B], List[Seq[B]]) = { | ||
| 22 | + val length = blocks.size | ||
| 23 | + val remainder = extrasOnFirstPage + (length % pageSize) | ||
| 24 | + val (main, rest) = blocks.splitAt(remainder + pageSize) | ||
| 25 | + (main, rest.grouped(pageSize).toList) | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + case class BlockInfo[B](page: Seq[B], self: PageReference) | ||
| 31 | + | ||
| 32 | + case class LiveBlogPageModel[+B](blocks: Seq[B], main: Seq[B]/*for key events - TODO remove*/, later: PageReference, earlier: PageReference, canonical: PageReference) | ||
| 33 | + | ||
| 34 | + sealed trait PageReference { | ||
| 35 | + def suffix: Option[String] | ||
| 36 | + } | ||
| 37 | + case object NoPage extends PageReference { val suffix = None } | ||
| 38 | + case object FirstPage extends PageReference { val suffix = Some("") } | ||
| 39 | + case class BlockPage(blockId: String) extends PageReference { val suffix = Some(s"?page=with:block-$blockId") } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,12 +1,12 @@ | |||
| 1 | - @import _root_.liveblog.BodyBlocks | ||
| 1 | + @import _root_.liveblog.LiveBlogPageModel | ||
| 2 | 2 | @import model.liveblog.BodyBlock | |
| 3 | - @(model: LiveBlogPage, blocks: BodyBlocks[BodyBlock])(implicit request: RequestHeader) | ||
| 3 | + @(model: LiveBlogPage, blocks: LiveBlogPageModel[BodyBlock])(implicit request: RequestHeader) | ||
| 4 | 4 | ||
| 5 | 5 | @import common.LinkTo | |
| 6 | 6 | ||
| 7 | 7 | @main(model){ | |
| 8 | - @blocks.earlier.map { earlierBlock => | ||
| 9 | - <link rel="next" href="@LinkTo{ /@{model.article.content.metadata.id}?page=@earlierBlock }"> | ||
| 8 | + @blocks.earlier.suffix.map { suffix => | ||
| 9 | + <link rel="next" href="@LinkTo{ /@{model.article.content.metadata.id}@suffix }"> | ||
| 10 | 10 | } | |
| 11 | 11 | @blocks.later.suffix.map { suffix => | |
| 12 | 12 | <link rel="prev" href="@LinkTo{ /@{model.article.content.metadata.id}@suffix }"> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,7 +10,7 @@ | |||
| 10 | 10 | @blocks.map { block => | |
| 11 | 11 | <div id="block-@block.id" class="block@block.eventClass" itemprop="liveBlogUpdate" itemscope="" itemtype="http://schema.org/BlogPosting"> | |
| 12 | 12 | <p class="block-time published-time"> | |
| 13 | - <a href="#block-@block.id" itemprop="url" class="block-time__link"> | ||
| 13 | + <a href="/@article.metadata.id?page=with:block-@block.id#block-@block.id" itemprop="url" class="block-time__link"> | ||
| 14 | 14 | @views.html.liveblog.dateBlock(block.publishedCreatedDate(timezone)) | |
| 15 | 15 | </a> | |
| 16 | 16 | </p> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,7 +1,7 @@ | |||
| 1 | - @import _root_.liveblog.BodyBlocks | ||
| 1 | + @import _root_.liveblog.LiveBlogPageModel | ||
| 2 | 2 | @import model.liveblog.BodyBlock | |
| 3 | 3 | @import model.liveblog.KeyEventData | |
| 4 | - @(model: LiveBlogPage, blocks: BodyBlocks[BodyBlock])(implicit request: RequestHeader) | ||
| 4 | + @(model: LiveBlogPage, blocks: LiveBlogPageModel[BodyBlock])(implicit request: RequestHeader) | ||
| 5 | 5 | ||
| 6 | 6 | @import common.LinkTo | |
| 7 | 7 | @import layout.{FaciaCardAndIndex, ItemClasses} | |
@@ -87,8 +87,8 @@ <h1 itemprop="headline" class="content__headline js-score">@Html(article.trail.h | |||
| 87 | 87 | </div> | |
| 88 | 88 | ||
| 89 | 89 | <div class="live-navigation"> | |
| 90 | - @blocks.earlier.map { earlierBlock => | ||
| 91 | - <a href="/@{article.content.metadata.id}?page=@earlierBlock" | ||
| 90 | + @blocks.earlier.suffix.map { suffix => | ||
| 91 | + <a href="/@{article.content.metadata.id}@suffix" | ||
| 92 | 92 | class="u-button-reset button button--large button--show-more liveblog__show-more" | |
| 93 | 93 | data-link-name="next page"> | |
| 94 | 94 | Go to older updates | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,5 +25,5 @@ GET /*path.json controllers.ArticleController.renderJson(pat | |||
| 25 | 25 | GET /*path/amp controllers.ArticleController.renderArticle(path) | |
| 26 | 26 | GET /*path/email controllers.ArticleController.renderArticle(path) | |
| 27 | 27 | # temp route for live blogs so we can paginate without getting the blocks for all articles | |
| 28 | - GET /$path<[^/]+/([^/]+/)?live/.*> controllers.ArticleController.renderLiveBlog(path, page: Option[Int]) | ||
| 28 | + GET /$path<[^/]+/([^/]+/)?live/.*> controllers.ArticleController.renderLiveBlog(path, page: Option[String]) | ||
| 29 | 29 | GET /*path controllers.ArticleController.renderArticle(path) | |
| Back | FazBrowse Home | New Git URL |
0 commit comments