From 9a0a58fe00d269dad5ca97a517728e908ef59750 Mon Sep 17 00:00:00 2001 From: Jesse McConnell Date: Thu, 7 Dec 2023 10:15:58 -0600 Subject: [PATCH 1/2] initial support for blog cards --- home/modules/ROOT/pages/index.adoc | 4 + ui/src/css/cards.css | 116 +++++++++++++++++++++++++++++ ui/src/css/site.css | 1 + ui/src/css/vars.css | 7 ++ ui/src/js/07-add-wtblog.js | 64 ++++++++++++++++ 5 files changed, 192 insertions(+) create mode 100644 ui/src/css/cards.css create mode 100644 ui/src/js/07-add-wtblog.js diff --git a/home/modules/ROOT/pages/index.adoc b/home/modules/ROOT/pages/index.adoc index 773e8a3..ef3004a 100644 --- a/home/modules/ROOT/pages/index.adoc +++ b/home/modules/ROOT/pages/index.adoc @@ -2,3 +2,7 @@ Jetty provides a web server and servlet container, additionally providing support for HTTP/2, WebSocket, OSGi, JMX, JNDI, JAAS and many other integrations. These components are open source and are freely available for commercial use and distribution. + +[#wtb-id] +== Blog Entries +:wtblog: diff --git a/ui/src/css/cards.css b/ui/src/css/cards.css new file mode 100644 index 0000000..d55f1d2 --- /dev/null +++ b/ui/src/css/cards.css @@ -0,0 +1,116 @@ +.card-section::after { + content: ""; + background-image: var(--card-icon); + background-repeat: no-repeat; + background-size: contain; + display: inline-block; + vertical-align: middle; +} + +.card-section .sectionbody, +.card-section { + display: grid; + /* grid-template-columns: repeat(auto-fill, 230px); */ + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + grid-gap: 1rem; + margin-top: 1rem; +} + +.card-section-2col .sectionbody, +.card-section-2col { + grid-template-columns: repeat(auto-fill, minmax(calc(max(50% - 0.5rem, 250px)), 1fr)); +} + +.card-section .sectionbody > :not(.card), +.card-section > :not(.card) { + grid-column: 1/-1; +} + +.card { + max-height: 15rem; + height: 15rem; +} + +.card .content, +.card .paragraph, +.card p { + display: inline; +} + +.card-title, +.card-body { + display: block; +} + +.card a { + border-radius: var(--border-radius); + color: inherit; + text-decoration: none; + font-size: 0.9rem; + font-weight: normal; + display: inline-flex; + flex-direction: column; + justify-content: space-around; + padding: 1.2rem; + text-align: center; + height: 100%; + width: 100%; +} + +.card a .card-title { + font-family: var(--heading); + font-weight: var(--weight-bold); + font-size: 1rem; + line-height: 1.4; +} + +.card a .card-body { + hyphens: initial; +} + +.card a .card-content-overflow { + --max-lines: 5; + + max-height: calc(1.4rem * var(--max-lines)); + overflow: hidden; +} + +.card-index a { + border: 2px solid var(--color-card-border); + position: relative; + overflow: hidden; + text-decoration: none; +} + +.card-index a .card-title { + font-size: 1.2em; + text-align: center; + hyphens: initial; +} + +.card-index a::before { + transition: all 0.2s, transform 0.2s; + transform: translateY(0); + position: relative; + box-shadow: none; + top: 0; +} + +.card-index a:hover { + border: 2px solid var(--color-net-id); + background-color: var(--color-net-id); + color: var(--color-focused); + transform: translateY(-3px); + top: -3px; + box-shadow: 0 10px 20px 0 var(--color-card-shadow); + transition: all 0.3s ease-in-out; +} + +.card-secondary a { + border: solid 1px #e9e9ed; + color: var(--color-text-light); +} + +.card-secondary a:hover { + border-color: #dfdfe0; +} diff --git a/ui/src/css/site.css b/ui/src/css/site.css index 36e1767..cc7c8ff 100644 --- a/ui/src/css/site.css +++ b/ui/src/css/site.css @@ -5,6 +5,7 @@ @import "body.css"; @import "nav.css"; @import "main.css"; +@import "cards.css"; @import "toolbar.css"; @import "breadcrumbs.css"; @import "page-versions.css"; diff --git a/ui/src/css/vars.css b/ui/src/css/vars.css index 545094a..9a9bfa4 100644 --- a/ui/src/css/vars.css +++ b/ui/src/css/vars.css @@ -144,4 +144,11 @@ --z-index-toolbar: 2; --z-index-page-version-menu: 3; --z-index-navbar: 4; + + /* webtide blogs */ + --color-card-shadow: orange; + --color-focused: orange; + --color-net-id: orange; + --color-text-light: white; + --color-card-border: black; } diff --git a/ui/src/js/07-add-wtblog.js b/ui/src/js/07-add-wtblog.js new file mode 100644 index 0000000..dbcd238 --- /dev/null +++ b/ui/src/js/07-add-wtblog.js @@ -0,0 +1,64 @@ +;(function () { + 'use strict' + + /* configuration */ + const _wtbURL = 'https://webtide.com/blog/feed/' + const _wtbTitle = 'Jetty Blogs' + const _wtbId = 'wtb-id' + const _cardMaxNumber = 6 + var blogCard = document.getElementById(_wtbId) + var xhttp = new window.XMLHttpRequest() + + if (blogCard) { + window.addEventListener('load', function () { + insertWTBlog() + }) + } + + function insertWTBlog () { + var feedXML + + xhttp.open('GET', _wtbURL) + xhttp.send() + + xhttp.onload = (e) => { + console.log(xhttp.responseType) + + if (xhttp.readyState == null) { + console.log('response is null') + } + feedXML = xhttp.responseXML + const items = feedXML.querySelectorAll('item') + let html = ` +
+

${_wtbTitle}

+
+ ` + var cardCount = 0 + items.forEach((el) => { + if (cardCount < _cardMaxNumber) { + html += ` + + ` + } + ++cardCount + }) + html += ` +
+
+ ` + blogCard.insertAdjacentHTML('beforeend', html) + } + } +})() From f0b1a0eddf06df2c72e59035d3dcf9c4fa8e9d7b Mon Sep 17 00:00:00 2001 From: Dan Allen Date: Tue, 19 Dec 2023 17:14:22 -0700 Subject: [PATCH 2/2] rework feed integration to use Asciidoctor extension, a handlebars template, and a simpler HTML structure --- antora-playbook.yml | 2 + home/modules/ROOT/pages/index.adoc | 5 +- lib/feed-block-macro.js | 38 ++++++++++ ui/gulp.d/tasks/build-preview-pages.js | 4 +- ui/gulp.d/tasks/build.js | 2 +- ui/preview-src/home.adoc | 8 ++ ui/preview-src/ui-model.yml | 3 + ui/src/css/cards.css | 101 ++++++------------------- ui/src/css/vars.css | 2 +- ui/src/js/07-add-wtblog.js | 64 ---------------- ui/src/js/feeds.bundle.js | 35 +++++++++ ui/src/partials/feeds.hbs | 11 +++ ui/src/partials/footer-scripts.hbs | 3 + 13 files changed, 133 insertions(+), 145 deletions(-) create mode 100644 lib/feed-block-macro.js create mode 100644 ui/preview-src/home.adoc delete mode 100644 ui/src/js/07-add-wtblog.js create mode 100644 ui/src/js/feeds.bundle.js create mode 100644 ui/src/partials/feeds.hbs diff --git a/antora-playbook.yml b/antora-playbook.yml index 77fa0bb..5409f4a 100644 --- a/antora-playbook.yml +++ b/antora-playbook.yml @@ -16,6 +16,8 @@ asciidoc: idprefix: '' idseparator: '-' page-pagination: '' + extensions: + - ./lib/feed-block-macro.js ui: bundle: url: https://github.com/webtide/jetty.website/releases/download/ui-prod-latest/ui-bundle.zip diff --git a/home/modules/ROOT/pages/index.adoc b/home/modules/ROOT/pages/index.adoc index ef3004a..f450029 100644 --- a/home/modules/ROOT/pages/index.adoc +++ b/home/modules/ROOT/pages/index.adoc @@ -3,6 +3,7 @@ Jetty provides a web server and servlet container, additionally providing support for HTTP/2, WebSocket, OSGi, JMX, JNDI, JAAS and many other integrations. These components are open source and are freely available for commercial use and distribution. -[#wtb-id] == Blog Entries -:wtblog: + +.Jetty Blogs +feed::https://webtide.com/blog/feed/[id=jetty-feed,max=6] diff --git a/lib/feed-block-macro.js b/lib/feed-block-macro.js new file mode 100644 index 0000000..84db28d --- /dev/null +++ b/lib/feed-block-macro.js @@ -0,0 +1,38 @@ +'use strict' + +const toHash = (object) => object && !object.$$is_hash ? Opal.hash2(Object.keys(object), object) : object + +const toProc = (fn) => Object.defineProperty(fn, '$$arity', { value: fn.length }) + +function register (registry, { file } = {}) { + if (!registry) return this.register('feed', createExtensionGroup()) + registry.$groups().$store('feed', toProc(createExtensionGroup(file))) + return registry +} + +function createExtensionGroup (file) { + return function () { + this.blockMacro('feed', function () { + this.process((parent, target, attrs) => { + if (file) file.asciidoc.attributes['page-has-feeds'] = '' + let currentParent = parent + let sect + if ('title' in attrs) { + const title = attrs.title + delete attrs.title + attrs.role = `${attrs.role || ''} card-section`.trimStart() + currentParent.append((sect = this.$create_section(parent, title, toHash(attrs)))) + currentParent = sect + } + const dataset = { feed: target } + if ('max' in attrs) dataset.max = attrs.max + const dataAttrlist = Object.entries(dataset).reduce((accum, [name, val]) => `${accum} data-${name}="${val}"`, '') + const container = this.createPassBlock(currentParent, `
`, {}) + if (!sect) return container + sect.append(container) + }) + }) + } +} + +module.exports = { register, createExtensionGroup } diff --git a/ui/gulp.d/tasks/build-preview-pages.js b/ui/gulp.d/tasks/build-preview-pages.js index 6d66efb..7fffeeb 100644 --- a/ui/gulp.d/tasks/build-preview-pages.js +++ b/ui/gulp.d/tasks/build-preview-pages.js @@ -26,7 +26,9 @@ module.exports = ]) .then(([baseUiModel, { layouts }]) => { const extensions = ((baseUiModel.asciidoc || {}).extensions || []).map((request) => { - ASCIIDOC_ATTRIBUTES[request.replace(/^@|\.js$/, '').replace(/[/]/g, '-') + '-loaded'] = '' + const slug = (request.startsWith('./') ? path.basename(request) : request).replace(/^@|\.js$/, '') + ASCIIDOC_ATTRIBUTES[slug.replace(/[/]/g, '-') + '-loaded'] = '' + if (request.startsWith('./')) request = require.resolve(request, { paths: [process.cwd()] }) const extension = require(request) extension.register.call(Asciidoctor.Extensions) return extension diff --git a/ui/gulp.d/tasks/build.js b/ui/gulp.d/tasks/build.js index 326871e..2ffd66b 100644 --- a/ui/gulp.d/tasks/build.js +++ b/ui/gulp.d/tasks/build.js @@ -70,7 +70,7 @@ module.exports = (src, dest, preview) => () => { // NOTE concat already uses stat from newest combined file .pipe(concat('js/site.js')), vfs - .src('js/vendor/*([^.])?(.bundle).js', { ...opts, read: false }) + .src('js/**/*([^.])?(.bundle).js', { ...opts, read: false }) .pipe(bundle(opts)) .pipe(uglify({ output: { comments: /^! / } })), vfs diff --git a/ui/preview-src/home.adoc b/ui/preview-src/home.adoc new file mode 100644 index 0000000..46f7a04 --- /dev/null +++ b/ui/preview-src/home.adoc @@ -0,0 +1,8 @@ += Home +// page-has-feeds is automatically set by the feed block macro when used with Antora; must be set manually in the UI preview +:page-has-feeds: + +== Blog Entries + +.Jetty Blogs +feed::https://webtide.com/blog/feed/[id=jetty-feed,max=6] diff --git a/ui/preview-src/ui-model.yml b/ui/preview-src/ui-model.yml index 4f0efda..7839a55 100644 --- a/ui/preview-src/ui-model.yml +++ b/ui/preview-src/ui-model.yml @@ -1,4 +1,7 @@ antoraVersion: '1.0.0' +asciidoc: + extensions: + - ./../lib/feed-block-macro.js site: url: http://localhost:5252 title: Eclipse Jetty diff --git a/ui/src/css/cards.css b/ui/src/css/cards.css index d55f1d2..3f0ac9c 100644 --- a/ui/src/css/cards.css +++ b/ui/src/css/cards.css @@ -1,14 +1,4 @@ -.card-section::after { - content: ""; - background-image: var(--card-icon); - background-repeat: no-repeat; - background-size: contain; - display: inline-block; - vertical-align: middle; -} - -.card-section .sectionbody, -.card-section { +.card-entries { display: grid; /* grid-template-columns: repeat(auto-fill, 230px); */ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); @@ -16,79 +6,27 @@ margin-top: 1rem; } -.card-section-2col .sectionbody, -.card-section-2col { - grid-template-columns: repeat(auto-fill, minmax(calc(max(50% - 0.5rem, 250px)), 1fr)); -} - -.card-section .sectionbody > :not(.card), -.card-section > :not(.card) { - grid-column: 1/-1; -} - -.card { +.card-block { max-height: 15rem; height: 15rem; + hyphens: initial; } -.card .content, -.card .paragraph, -.card p { - display: inline; -} - -.card-title, -.card-body { - display: block; -} - -.card a { - border-radius: var(--border-radius); +.card-block .card-link { color: inherit; text-decoration: none; - font-size: 0.9rem; - font-weight: normal; - display: inline-flex; + display: flex; flex-direction: column; justify-content: space-around; padding: 1.2rem; - text-align: center; height: 100%; width: 100%; -} - -.card a .card-title { - font-family: var(--heading); - font-weight: var(--weight-bold); - font-size: 1rem; - line-height: 1.4; -} - -.card a .card-body { - hyphens: initial; -} - -.card a .card-content-overflow { - --max-lines: 5; - - max-height: calc(1.4rem * var(--max-lines)); - overflow: hidden; -} - -.card-index a { - border: 2px solid var(--color-card-border); + border: 1.5px solid var(--color-card-border); + border-radius: 0.25em; position: relative; - overflow: hidden; - text-decoration: none; -} - -.card-index a .card-title { - font-size: 1.2em; - text-align: center; - hyphens: initial; } -.card-index a::before { +.card-block .card-link::before { transition: all 0.2s, transform 0.2s; transform: translateY(0); position: relative; @@ -96,7 +34,7 @@ top: 0; } -.card-index a:hover { +.card-block .card-link:hover { border: 2px solid var(--color-net-id); background-color: var(--color-net-id); color: var(--color-focused); @@ -106,11 +44,22 @@ transition: all 0.3s ease-in-out; } -.card-secondary a { - border: solid 1px #e9e9ed; - color: var(--color-text-light); +.card-block .card-title { + font-size: calc(20 / var(--rem-base) * 1rem); + line-height: 1.4; + text-align: center; + overflow: hidden; + font-weight: var(--body-font-weight-bold); +} + +.card-block .card-content p { + font-size: calc(16 / var(--rem-base) * 1rem); + line-height: 1.3; } -.card-secondary a:hover { - border-color: #dfdfe0; +.card-block .overflow { + --max-lines: 5; + + max-height: calc(1.4rem * var(--max-lines)); + overflow: hidden; } diff --git a/ui/src/css/vars.css b/ui/src/css/vars.css index 9a9bfa4..786ed78 100644 --- a/ui/src/css/vars.css +++ b/ui/src/css/vars.css @@ -147,7 +147,7 @@ /* webtide blogs */ --color-card-shadow: orange; - --color-focused: orange; + --color-focused: blue; --color-net-id: orange; --color-text-light: white; --color-card-border: black; diff --git a/ui/src/js/07-add-wtblog.js b/ui/src/js/07-add-wtblog.js deleted file mode 100644 index dbcd238..0000000 --- a/ui/src/js/07-add-wtblog.js +++ /dev/null @@ -1,64 +0,0 @@ -;(function () { - 'use strict' - - /* configuration */ - const _wtbURL = 'https://webtide.com/blog/feed/' - const _wtbTitle = 'Jetty Blogs' - const _wtbId = 'wtb-id' - const _cardMaxNumber = 6 - var blogCard = document.getElementById(_wtbId) - var xhttp = new window.XMLHttpRequest() - - if (blogCard) { - window.addEventListener('load', function () { - insertWTBlog() - }) - } - - function insertWTBlog () { - var feedXML - - xhttp.open('GET', _wtbURL) - xhttp.send() - - xhttp.onload = (e) => { - console.log(xhttp.responseType) - - if (xhttp.readyState == null) { - console.log('response is null') - } - feedXML = xhttp.responseXML - const items = feedXML.querySelectorAll('item') - let html = ` -
-

${_wtbTitle}

-
- ` - var cardCount = 0 - items.forEach((el) => { - if (cardCount < _cardMaxNumber) { - html += ` - - ` - } - ++cardCount - }) - html += ` -
-
- ` - blogCard.insertAdjacentHTML('beforeend', html) - } - } -})() diff --git a/ui/src/js/feeds.bundle.js b/ui/src/js/feeds.bundle.js new file mode 100644 index 0000000..d91e944 --- /dev/null +++ b/ui/src/js/feeds.bundle.js @@ -0,0 +1,35 @@ +;(function () { + 'use strict' + + var feeds = [].slice.call(document.querySelectorAll('.doc [data-feed]')) + if (!feeds.length) return + + var template = document.getElementById('card-entry') + if (!(template && (template = template.content.querySelector('.card-block')))) return + + var XMLHttpRequest = window.XMLHttpRequest + + feeds.forEach(insertFeed) + + function insertFeed (feed) { + try { + var url = feed.dataset.feed + var max = Number(feed.dataset.max) || Infinity + var xhr = new XMLHttpRequest() + xhr.open('GET', url) + xhr.addEventListener('load', function () { + var items = [].slice.call(this.responseXML.querySelectorAll('item'), 0, max) + items.forEach(function (item) { + var entry = template.cloneNode(true) + entry.querySelector('.card-link').setAttribute('href', item.querySelector('link').textContent) + entry.querySelector('.card-title').innerHTML = item.querySelector('title').innerHTML + entry.querySelector('.card-content p').innerHTML = item.querySelector('description').textContent + feed.appendChild(entry) + }) + }) + } catch (e) { + console.error('Failed to retrieve feed: ' + url + '.', e) + } + xhr.send(null) + } +})() diff --git a/ui/src/partials/feeds.hbs b/ui/src/partials/feeds.hbs new file mode 100644 index 0000000..43c25a2 --- /dev/null +++ b/ui/src/partials/feeds.hbs @@ -0,0 +1,11 @@ + + diff --git a/ui/src/partials/footer-scripts.hbs b/ui/src/partials/footer-scripts.hbs index 3d9b577..9f9c5b3 100644 --- a/ui/src/partials/footer-scripts.hbs +++ b/ui/src/partials/footer-scripts.hbs @@ -1,4 +1,7 @@ +{{#unless (eq page.attributes.has-feeds undefined)}} +{{> feeds}} +{{/unless}} {{#if env.SITE_SEARCH_PROVIDER}} {{> search-scripts}}