diff --git a/antora-playbook.yml b/antora-playbook.yml index c1f61fc..26b95c7 100644 --- a/antora-playbook.yml +++ b/antora-playbook.yml @@ -19,6 +19,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 773e8a3..f450029 100644 --- a/home/modules/ROOT/pages/index.adoc +++ b/home/modules/ROOT/pages/index.adoc @@ -2,3 +2,8 @@ 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. + +== Blog Entries + +.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 new file mode 100644 index 0000000..3f0ac9c --- /dev/null +++ b/ui/src/css/cards.css @@ -0,0 +1,65 @@ +.card-entries { + 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-block { + max-height: 15rem; + height: 15rem; + hyphens: initial; +} + +.card-block .card-link { + color: inherit; + text-decoration: none; + display: flex; + flex-direction: column; + justify-content: space-around; + padding: 1.2rem; + height: 100%; + width: 100%; + border: 1.5px solid var(--color-card-border); + border-radius: 0.25em; + position: relative; +} + +.card-block .card-link::before { + transition: all 0.2s, transform 0.2s; + transform: translateY(0); + position: relative; + box-shadow: none; + top: 0; +} + +.card-block .card-link: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-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-block .overflow { + --max-lines: 5; + + max-height: calc(1.4rem * var(--max-lines)); + overflow: hidden; +} 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..786ed78 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: blue; + --color-net-id: orange; + --color-text-light: white; + --color-card-border: black; } 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}}