Skip to content
Commits on Source (7)
......@@ -57,7 +57,7 @@ If you don’t have any social media presence you can leave out the
`social.platforms[].id`:
Platform identifier for a social platform. One of `grouprise`, `mastodon`,
`facebook`, `instagram`, `twitter`, or `youtube`.
`facebook`, `instagram`, `twitter`, `website`, or `youtube`.
`social.platforms[].instance`:
Platform instance for platforms that are federated. Required for `grouprise`
......@@ -67,7 +67,7 @@ If you don’t have any social media presence you can leave out the
Username/Handle on the platform. Required for every platform except `youtube`.
`social.platforms[].url`:
URL to your page on the platform. Only used and required for `youtube`.
URL to your page on the platform. Required for `website` and `youtube`.
### Livestream
......@@ -121,12 +121,23 @@ network connection you’re advised to use WebSockets if you can.
It’s hard and time-intensive to write proper descriptions for recordings.
In the spirit of open participation users can submit content via mail.
`suggestContent.address`:
The email address the suggestion is send to.
`suggestContent.name`:
The name displayed in the frontend where content can be suggested.
Falls back to `suggestContent.address` if not provided.
`suggestContent.address`:
The email address the suggestion is send to.
`suggestContent.subjectTemplate`:
A template for the message subject. Variables are interpolated
within curly brackets. Guaranteed variables are `title` and `type`.
See the example for a better understanding.
`suggestContent.typeMapping`:
A simple key-value mapping for a type key (one of `broadcast `,
`recording`, `series`, and `tag`) to a label. This is used
to inject a localized `type` variable into the subject template
(see option above).
### User OS Integration
......@@ -171,6 +182,10 @@ experience on various platforms.
},
"social": {
"platforms": [
{
"id": "website",
"url": "https://www.lohro.de/"
},
{
"id": "grouprise",
"instance": "https://stadtgestalten.org",
......@@ -235,9 +250,14 @@ experience on various platforms.
}
},
"suggestContent": {
"subject": "Beschreibungsvorschlag für \"{title}\"",
"name": "musik@lohro.de",
"address": "musik@lohro.de"
"address": "musik@lohro.de",
"subjectTemplate": "Beschreibungsvorschlag für {type} \"{title}\"",
"typeMapping": {
"broadcast": "die Sendung",
"recording": "den Beitrag",
"series": "die Sendereihe",
"tag": "das Schlagwort"
}
},
"integration": {
"splashBackground": "#fff",
......
<template>
<app-markdown>
<i18n :path="missingTextTranslationKey">
<template v-slot:suggest_content
v-if="suggestData">{{ '\n' + $t('suggest_content.suggest', suggestData) }}</template>
</i18n>
</app-markdown>
</template>
<script>
import { renderSimpleTemplateString } from '../../util'
export default {
props: {
missingTextTranslationKey: String,
context: Object,
type: String
},
computed: {
suggestData () {
const { suggestContent: suggest } = this.env
if (suggest) {
const type = suggest.typeMapping ? suggest.typeMapping[this.type] || this.type : this.type
const subject = renderSimpleTemplateString(suggest.subjectTemplate, { ...this.context, type })
return {
suggest_name: suggest.name || suggest.address,
suggest_address: `mailto:${suggest.address}?subject=${encodeURIComponent(subject)}`
}
}
return null
}
},
inject: ['env']
}
</script>
<i18n lang="yaml">
en:
suggest_content.suggest: >
Maybe you can help by suggesting one?
Write to [{suggest_name}]({suggest_address}).
de:
suggest_content.suggest: >
Vielleicht kannst Du aushelfen und hast einen Vorschlag?
Schreib an [{suggest_name}]({suggest_address}).
</i18n>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm1 16.057v-3.057h2.994c-.059 1.143-.212 2.24-.456 3.279-.823-.12-1.674-.188-2.538-.222zm1.957 2.162c-.499 1.33-1.159 2.497-1.957 3.456v-3.62c.666.028 1.319.081 1.957.164zm-1.957-7.219v-3.015c.868-.034 1.721-.103 2.548-.224.238 1.027.389 2.111.446 3.239h-2.994zm0-5.014v-3.661c.806.969 1.471 2.15 1.971 3.496-.642.084-1.3.137-1.971.165zm2.703-3.267c1.237.496 2.354 1.228 3.29 2.146-.642.234-1.311.442-2.019.607-.344-.992-.775-1.91-1.271-2.753zm-7.241 13.56c-.244-1.039-.398-2.136-.456-3.279h2.994v3.057c-.865.034-1.714.102-2.538.222zm2.538 1.776v3.62c-.798-.959-1.458-2.126-1.957-3.456.638-.083 1.291-.136 1.957-.164zm-2.994-7.055c.057-1.128.207-2.212.446-3.239.827.121 1.68.19 2.548.224v3.015h-2.994zm1.024-5.179c.5-1.346 1.165-2.527 1.97-3.496v3.661c-.671-.028-1.329-.081-1.97-.165zm-2.005-.35c-.708-.165-1.377-.373-2.018-.607.937-.918 2.053-1.65 3.29-2.146-.496.844-.927 1.762-1.272 2.753zm-.549 1.918c-.264 1.151-.434 2.36-.492 3.611h-3.933c.165-1.658.739-3.197 1.617-4.518.88.361 1.816.67 2.808.907zm.009 9.262c-.988.236-1.92.542-2.797.9-.89-1.328-1.471-2.879-1.637-4.551h3.934c.058 1.265.231 2.488.5 3.651zm.553 1.917c.342.976.768 1.881 1.257 2.712-1.223-.49-2.326-1.211-3.256-2.115.636-.229 1.299-.435 1.999-.597zm9.924 0c.7.163 1.362.367 1.999.597-.931.903-2.034 1.625-3.257 2.116.489-.832.915-1.737 1.258-2.713zm.553-1.917c.27-1.163.442-2.386.501-3.651h3.934c-.167 1.672-.748 3.223-1.638 4.551-.877-.358-1.81-.664-2.797-.9zm.501-5.651c-.058-1.251-.229-2.46-.492-3.611.992-.237 1.929-.546 2.809-.907.877 1.321 1.451 2.86 1.616 4.518h-3.933z"/>
</svg>
</template>
......@@ -3,4 +3,5 @@ export { default as GroupriseIcon } from './GroupriseIcon'
export { default as InstagramIcon } from './InstagramIcon'
export { default as MastodonIcon } from './MastodonIcon'
export { default as TwitterIcon } from './TwitterIcon'
export { default as WebsiteIcon } from './WebsiteIcon'
export { default as YoutubeIcon } from './YoutubeIcon'
......@@ -2,7 +2,7 @@
<app-card :title="recording.title" :description="recording.description"
:link="{ to: { name: 'recording', params: { id: recording.id } } }" has-body-link>
<template v-slot:image>
<app-play-pause :tracks="tracks" :track-id="recording.id" :title="recording.title"/>
<app-play-pause :tracks="tracks" :track-id="recording.id" :title="recording.title" :image="coverImage"/>
</template>
<template v-slot:footer>
......@@ -26,6 +26,11 @@
recording: Object
},
computed: {
coverImage () {
return this.recording.cover_image
? this.dataSources.createImageUrl(this.recording.cover_image)
: null
},
productionDate () {
return new Date(this.recording.production_date)
},
......@@ -35,7 +40,8 @@
durationLabel () {
return secondsToDuration(this.duration)
}
}
},
inject: ['dataSources']
}
</script>
......
{}
{
"recording_page.missing_description": "Zwar hat sich eine Person die Mühe gemacht, diesen Beitrag in die Mediathek einzustellen, aber leider fehlte ihr die Zeit oder die Möglichkeiten eine Beschreibung hinzuzufügen.\n\n{suggest_content}",
"series_page.missing_description": "Leider hatte bisher niemand die Zeit oder die Möglichkeiten eine Beschreibung für diese Sendereihe hinzuzufügen.\n\n{suggest_content}"
}
{}
{
"recording_page.missing_description": "Though someone made the effort to add this post, they didn’t have the time or means to add a proper description.\n\n{suggest_content}",
"series_page.missing_description": "Nobody had the time or means to add a proper description for this program yet.\n\n{suggest_content}"
}
export const oneOf = values => value => values.indexOf(value) !== -1
export const renderSimpleTemplateString = (str, ctx) => {
return str.replace(/\{([a-z_]+)\}/gi, (match, key) => ctx[key])
return str.replace(/\{([a-z_]+)\}/gi, (match, key) => ctx[key] || '')
}
export const clamp = (value, min, max) => Math.min(max, Math.max(min, value))
......
import { FacebookIcon, GroupriseIcon, InstagramIcon, MastodonIcon, TwitterIcon, YoutubeIcon } from '../components/icons'
import {
FacebookIcon,
GroupriseIcon,
InstagramIcon,
MastodonIcon,
TwitterIcon,
WebsiteIcon,
YoutubeIcon
} from '../components/icons'
export const website = ({ url }) => {
return {
name: 'Website',
icon: WebsiteIcon,
href: url
}
}
export const youtube = ({ id, url }) => {
return {
......
......@@ -118,6 +118,6 @@
home_page_recent_recordings: Aktuelle Beiträge
home_page_recent_topics: Aktuelle Themen
home_page_search: Suche
home_page_series: Sendereihen
home_page_series: Programmübersicht
home_page_app: Web-App
</i18n>
......@@ -17,12 +17,8 @@
<app-header :title="$t('recording_page.info')" :level="2" :display-level="4" class="mb-3"/>
<div class="mb-2">
<app-markdown v-if="recording.description">{{ recording.description }}</app-markdown>
<app-markdown v-else>
<i18n path="recording_page.missing_description">
<template v-slot:suggest_content
v-if="suggestData">{{ '\n' + $t('recording_page.suggest_content', suggestData) }}</template>
</i18n>
</app-markdown>
<SuggestContent v-else missing-text-translation-key="recording_page.missing_description"
:context="recording" type="recording"/>
</div>
<app-field>
<ul class="d-inline hyphens ma-0 pa-0 opaque-7">
......@@ -62,13 +58,13 @@
<script>
import RecordingPreview from '../components/model/RecordingPreview'
import { renderSimpleTemplateString } from '../util'
import { recordingTracks } from '../components/model/mixins'
import PlayPauseButton from '../components/player/PlayPauseButton'
import License from '../components/generic/License'
import SuggestContent from '../components/generic/SuggestContent'
export default {
components: { License, PlayPauseButton, RecordingPreview },
components: { License, PlayPauseButton, RecordingPreview, SuggestContent },
mixins: [recordingTracks],
props: {
recording: {
......@@ -83,17 +79,6 @@
omit: this.recording.id,
is_available: 1
}
},
suggestData () {
if (this.env.suggestContent) {
const suggest = this.env.suggestContent
const subject = renderSimpleTemplateString(suggest.subject, this.recording)
return {
suggest_name: suggest.name,
suggest_address: `mailto:${suggest.address}?subject=${encodeURIComponent(subject)}`
}
}
return null
}
},
inject: ['dataSources', 'env']
......@@ -107,14 +92,6 @@
recording_page.similar_recordings: Similar Posts
recording_page.no_similar_recordings: >
Unfortunately we didn’t find any similar content for this post.
recording_page.missing_description: >
Though someone made the effort to add this post, they didn’t have the time or means
to add a proper description.
{suggest_content}
recording_page.suggest_content: >
Maybe you can help by suggesting one?
Write to [{suggest_name}]({suggest_address}).
de:
recording_page.download_name: Beitrag
recording_page.info: Über diesen Beitrag
......@@ -122,13 +99,4 @@
recording_page.no_similar_recordings: >
Leider haben wir zu diesem Beitrag keine ähnlichen Inhalte gefunden, die wir
Dir hier präsentieren könnten.
recording_page.missing_description: >
Zwar hat sich eine Person die Mühe gemacht, diesen Beitrag in die Mediathek
einzustellen, aber leider fehlte ihr die Zeit oder die Möglichkeiten eine
Beschreibung hinzuzufügen.
{suggest_content}
recording_page.suggest_content: >
Vielleicht kannst Du aushelfen und hast einen Vorschlag?
Schreib an [{suggest_name}]({suggest_address}).
</i18n>
<template>
<app-page :title="series.name">
<app-container class="my-5">
<app-header :title="series.name" class="mb-4"/>
<app-header :title="series.name" class="mb-3"/>
<div class="mb-2">
<app-markdown v-if="series.description">{{ series.description }}</app-markdown>
<SuggestContent v-else missing-text-translation-key="series_page.missing_description"
:context="series"/>
</div>
<app-auto-grid>
<div>
<h2 class="t-4 mt-5">Kommende Sendungen</h2>
......@@ -12,8 +17,10 @@
</template>
<script>
import SuggestContent from '../components/generic/SuggestContent'
export default {
name: 'SeriesPage',
components: { SuggestContent },
props: {
series: Object
},
......
......@@ -2,6 +2,10 @@ location / {
# try to resolve files and always fallback to the index.html file
try_files $uri $uri/ /index.html;
# if this domain participates in an origin trial we set the appropriate header
# see: https://googlechrome.github.io/OriginTrials/developer-guide.html
add_header Origin-Trial "$origin_trial";
location ~* "\.(?:[0-9a-f]{8,32})\.(?:[0-9a-z]+)$" {
# cache immutable files (indicated by the contained hash) indefinitely
add_header Cache-Control "public, immutable, max-age=365000000";
......