Skip to content

Commit eb61055

Browse files
BytePaulMakisH
andauthored
News cards on the landing page (#611)
Closes #402 Co-authored-by: Gerasimos Chourdakis <[email protected]>
1 parent 1014031 commit eb61055

File tree

7 files changed

+244
-23
lines changed

7 files changed

+244
-23
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Update Discourse data
2+
3+
on:
4+
workflow_dispatch:
5+
schedule:
6+
- cron: "0 6 * * *"
7+
8+
jobs:
9+
update-discourse-data:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: write
13+
14+
steps:
15+
- uses: actions/create-github-app-token@v1
16+
id: app-token
17+
with:
18+
app-id: ${{ vars.WEBSITE_UPDATER_APPID }}
19+
private-key: ${{ secrets.WEBSITE_UPDATER_KEY }}
20+
21+
- name: Checkout repository
22+
uses: actions/checkout@v4
23+
with:
24+
token: ${{ steps.app-token.outputs.token }}
25+
26+
- name: Set up Python
27+
uses: actions/setup-python@v6
28+
with:
29+
python-version: "3.14"
30+
31+
- name: Fetch latest news from Discourse
32+
run: python tools/fetch-news.py
33+
34+
- name: Configure Git author
35+
run: |
36+
git config --local user.name "precice-bot"
37+
git config --local user.email "[email protected]"
38+
39+
- name: Commit changes (if any)
40+
run: |
41+
git add assets/data/news.json
42+
git commit -m "Update Discourse news data [skip ci]" || echo "No changes to commit"
43+
44+
- name: Push commit
45+
uses: ad-m/github-push-action@master
46+
with:
47+
github_token: ${{ steps.app-token.outputs.token }}
48+
branch: ${{ github.ref }}

_layouts/landing_page.html

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
{% include footer.html %}
1818

19-
<script src="{{ "js/github-queries.js" }}"></script>
19+
<script src="{{ '/js/github-queries.js' | relative_url }}"></script>
20+
21+
<script src="{{ '/js/news-collect.js' | relative_url }}"></script>
2022

2123
</body>
2224

@@ -25,6 +27,3 @@
2527
{% endif %}
2628

2729
</html>
28-
29-
30-

content/index.html

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,32 @@ <h2 class="section-header">Ready-to-use with your favourite open-source solver</
197197
</div> <!-- section -->
198198
</div> <!-- container -->
199199

200+
<!-- Latest News Section -->
201+
<section id="latest-news" class="section">
202+
<div class="container">
203+
<h2 class="section-header text-center">Latest News</h2>
204+
205+
<div id="news-container" class="row equal">
206+
<!-- News cards will be inserted dynamically -->
207+
<p id="loading-news">Loading latest news...</p>
208+
</div>
209+
210+
211+
<div class="row" style="margin-top: 25px;">
212+
<div class="col-lg-12 text-center">
213+
<a href="https://precice.discourse.group/c/news/5"
214+
class="btn btn-primary no-icon"
215+
role="button"
216+
target="_blank"
217+
rel="noopener noreferrer">
218+
More news &nbsp;<i class="fas fa-chevron-right"></i>
219+
</a>
220+
</div>
221+
</div>
222+
223+
</div>
224+
</section>
225+
200226
<!-- Testimonials, full bleed layout -->
201227

202228
<div class="background-dark">

css/customstyles-precice.css

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
21
/* (Material) changes to the customstyles.css start here */
32

43
body {
54
font-size: 16px;
65
font-family: Roboto,"Helvetica Neue",Helvetica,Arial,sans-serif;
76
padding-top: 70px; /* https://getbootstrap.com/docs/3.4/components/#navbar-fixed-top */
87
}
9-
main {
10-
/*margin-top:-20px;*/
11-
}
8+
129
.banner-container {
1310
margin-top:-20px;
1411
}
@@ -69,7 +66,6 @@ details > summary {
6966

7067
/* General layout classes */
7168

72-
7369
h2.page-header {
7470
/*border-bottom: 1px solid #0A76BB;*/
7571
border-bottom: 1px solid rgba(10,118,187, 0);
@@ -230,7 +226,7 @@ ul.devlist>li {
230226
display: flex;
231227
flex-flow: row wrap;
232228
border-top: 1px solid #0a76bb;
233-
border-top-radius: 5px;
229+
border-radius: 5px;
234230
padding: 8px 16px;
235231
margin: 0;
236232
}
@@ -340,17 +336,17 @@ div#tg-sb-sidebar {
340336
div#tg-sb-sidebar {
341337
position: static;
342338
}
343-
/* style navtabs */
344-
.post-content ol li, .post-content ul li {
345-
margin: 0px;
346-
}
347-
div.tab-content {
348-
padding: 0px;
349-
background-color: white;
339+
/* style navtabs */
340+
.post-content ol li, .post-content ul li {
341+
margin: 0px;
342+
}
343+
div.tab-content {
344+
padding: 0px;
345+
background-color: white;
346+
}
347+
div.tab-content div.tab-pane pre {
348+
margin-top: 0px;
350349
}
351-
div.tab-content div.tab-pane pre {
352-
margin-top: 0px;
353-
}
354350
}
355351

356352
/* Hide default marker Chromium/WebKit */

css/landing-page.css

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
/* clear fixes */
55
/* idea is to not touch the docs part of the website, but start from a clear state */
66

7-
main { /* fixed topnav takes up 50 + 20 px */
8-
/* margin-top: -20px; */
9-
}
107
h1[id], h2[id], h3[id], h4[id], h5[id], h6[id],
118
dt[id] {
129
padding-top: 0px;
@@ -237,3 +234,57 @@ div.logo-holder > img {
237234
.no-padding {
238235
padding: 0;
239236
}
237+
238+
/* Latest News Section */
239+
240+
#latest-news {
241+
background-color: #e6f2ff;
242+
padding: 70px 0;
243+
}
244+
245+
#latest-news .section-header {
246+
margin-bottom: 40px;
247+
text-align: center;
248+
}
249+
250+
#latest-news a.no-external-marker::after {
251+
content: none !important;
252+
}
253+
254+
.news-card {
255+
background: white;
256+
border: 1px solid #ddd;
257+
border-radius: 6px;
258+
padding: 20px;
259+
margin-bottom: 20px;
260+
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
261+
}
262+
263+
.news-card h4 {
264+
overflow: hidden;
265+
text-overflow: ellipsis;
266+
display: -webkit-box;
267+
-webkit-line-clamp: 2;
268+
line-clamp: 2;
269+
-webkit-box-orient: vertical;
270+
height: 2.8em;
271+
}
272+
273+
.news-card p:first-of-type {
274+
overflow: hidden;
275+
text-overflow: ellipsis;
276+
display: -webkit-box;
277+
-webkit-line-clamp: 4;
278+
line-clamp: 4;
279+
-webkit-box-orient: vertical;
280+
}
281+
282+
283+
#news-container {
284+
display: flex;
285+
gap: 25px;
286+
}
287+
288+
#news-container .col-md-4 {
289+
flex: 1;
290+
}

js/news-collect.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
console.log(">>> USING JS FILE: news-collect.js LOADED <<<");
2+
3+
document.addEventListener("DOMContentLoaded", async function () {
4+
const newsContainer = document.getElementById("news-container");
5+
const loadingText = document.getElementById("loading-news");
6+
7+
try {
8+
const response = await fetch("/assets/data/news.json");
9+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
10+
const data = await response.json();
11+
let topics = data.topics || [];
12+
13+
if (!topics.length) throw new Error("No topics found");
14+
15+
topics.sort((a, b) => new Date(b.last_posted_at) - new Date(a.last_posted_at));
16+
loadingText.style.display = "none";
17+
18+
for (const topic of topics.slice(0, 3)) {
19+
const col = document.createElement("div");
20+
col.className = "col-md-4 col-sm-6 col-xs-12";
21+
22+
const card = document.createElement("div");
23+
card.className = "news-card";
24+
25+
card.innerHTML = `
26+
<h4><strong>${topic.title}</strong></h4>
27+
<p>${topic.description}</p>
28+
<p>
29+
<a href="${topic.url}" target="_blank" rel="noopener noreferrer" class="no-external-marker">
30+
Read more about this update
31+
</a>
32+
</p>
33+
<p class="text-muted"><small>
34+
Last activity: ${new Date(topic.last_posted_at).toLocaleDateString()}
35+
</small></p>
36+
`;
37+
38+
col.appendChild(card);
39+
newsContainer.appendChild(col);
40+
}
41+
42+
} catch (err) {
43+
console.error(err);
44+
loadingText.textContent = "Failed to load latest news.";
45+
}
46+
});

tools/fetch-news.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import json
2+
import os
3+
import re
4+
from urllib.request import urlopen
5+
6+
DISCOURSE_URL = "https://precice.discourse.group/c/news/5.json"
7+
OUTPUT_FILE = "./assets/data/news.json"
8+
9+
10+
def fetch_json(url: str):
11+
with urlopen(url) as res:
12+
return json.loads(res.read().decode("utf-8"))
13+
14+
15+
def strip_html(html: str) -> str:
16+
return re.sub(r"<[^>]*>", "", html)
17+
18+
19+
def main():
20+
try:
21+
data = fetch_json(DISCOURSE_URL)
22+
topics = data.get("topic_list", {}).get("topics", [])
23+
24+
news = []
25+
for topic in topics:
26+
detail = fetch_json(f"https://precice.discourse.group/t/{topic['id']}.json")
27+
cooked = detail.get("post_stream", {}).get("posts", [{}])[0].get("cooked", "")
28+
text = strip_html(cooked).strip()
29+
30+
excerpt = " ".join(text.split()[:30]) + "..."
31+
32+
news.append({
33+
"id": topic["id"],
34+
"title": topic["title"],
35+
"slug": topic["slug"],
36+
"url": f"https://precice.discourse.group/t/{topic['slug']}/{topic['id']}",
37+
"last_posted_at": topic.get("last_posted_at"),
38+
"like_count": topic.get("like_count"),
39+
"posts_count": topic.get("posts_count"),
40+
"views": topic.get("views"),
41+
"description": excerpt,
42+
})
43+
44+
os.makedirs(os.path.dirname(OUTPUT_FILE), exist_ok=True)
45+
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
46+
json.dump({"generated_at": __import__("datetime").datetime.utcnow().isoformat(), "topics": news}, f, indent=2)
47+
48+
print(f"News data saved to {OUTPUT_FILE}")
49+
50+
except Exception as e:
51+
print("Error fetching news:", e)
52+
53+
54+
if __name__ == "__main__":
55+
main()

0 commit comments

Comments
 (0)