Skip to content

Commit

Permalink
Merge pull request #29 from hyphacoop/default-list
Browse files Browse the repository at this point in the history
feat: add conditional custom theme with persistence and defaults.json for default export list
  • Loading branch information
akhileshthite authored Oct 17, 2024
2 parents 4d52b28 + 033eee4 commit 033746c
Show file tree
Hide file tree
Showing 17 changed files with 437 additions and 84 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,38 @@
# Social Reader
A P2P and offline-first ActivityPub client for reading and following microblogs on the Fediverse, avoiding dependency on always-online HTTP servers, allowing access to content anytime, anywhere.

## Customizing Your Community's Reader

You can easily deploy a customized version of the reader by [forking](https://github.com/hyphacoop/reader.distributed.press/fork) this repository and modifying the `defaults.json` file located in the `./config` folder. This allows you to set the community name, define a custom theme, and specify the default followed actors for your community.

### Configuration Options:

- `communityName`: The name of your community, which will appear as the page title and sidebar branding.
- `theme`: Customize the appearance of the reader. You can specify:
- `bgColor`: Background color for the reader.
- `postBgColor`: Background color for individual posts.
- `postDetailsColor`: Color for post details (such as date and metadata).
- `postLinkColor`: Color for links within posts.
- `defaultFollowedActors`: A list of URLs representing the actors that will be followed by default when no actors are followed.

### Example `defaults.json`:

```json
{
"version": "1.2.0",
"communityName": "Akhi's Art Club",
"theme": {
"bgColor": "#ecfeff",
"postBgColor": "#cffafe",
"postDetailsColor": "#06b6d4",
"postLinkColor": "#0ea5e9"
},
"defaultFollowedActors": [
"https://mastodon.social/@akhileshthite"
]
}
```

![Akhi's Art Club on Social Reader](./custom-demo.png)

For more information, please visit [docs.distributed.press](https://docs.distributed.press/social-reader).
11 changes: 11 additions & 0 deletions about.html
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ <h2>FAQs</h2>
broader Fediverse allow for genuine reach and engagement.
</p>
</details>
<details>
<summary>Can I create a custom community using Social Reader?</summary>
<p>
Absolutely! You can customize your community's reader by modifying the
<code>defaults.json</code> file. This allows you to set your community
name, define custom themes, and specify default followed accounts. For detailed instructions on customizing your community, please refer to our
<a href="https://github.com/hyphacoop/reader.distributed.press#customizing-your-communitys-reader"
>GitHub guide on customizing your community's reader</a
>.
</p>
</details>
<details>
<summary>I found a bug. Where do I report it?</summary>
<p>
Expand Down
16 changes: 16 additions & 0 deletions config/defaults.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"version": "1.2.0",
"communityName": "Social Reader",
"theme": {
"bgColor": "#ffffff",
"postBgColor": "#ebf3f5",
"postDetailsColor": "#4d626a",
"postLinkColor": "#0ea5e9"
},
"defaultFollowedActors": [
"https://social.distributed.press/v1/@[email protected]/",
"ipns://distributed.press/about.ipns.jsonld",
"hyper://hypha.coop/about.hyper.jsonld",
"https://sutty.nl/about.jsonld"
]
}
Binary file added custom-demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions default-theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const defaultLightTheme = {
bgColor: '#ffffff',
postBgColor: '#ebf3f5',
postDetailsColor: '#4d626a',
postLinkColor: '#0ea5e9'
}
115 changes: 115 additions & 0 deletions defaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { db } from './dbInstance.js'
import { defaultLightTheme } from './default-theme.js'

export async function fetchDefaults () {
try {
const response = await fetch('./config/defaults.json')
if (!response.ok) {
throw new Error('Failed to load defaults.')
}
return await response.json()
} catch (error) {
console.error('Error fetching defaults:', error)
return {}
}
}

export async function applyDefaults () {
try {
const defaults = await fetchDefaults()

// Apply community name if available
if (defaults.communityName) {
document.title = defaults.communityName // Update the main title
const brandingElement = document.getElementById('header-branding') // Update sidebar branding
if (brandingElement) {
brandingElement.textContent = defaults.communityName
}
}

// Check if a theme is already set in the DB
const currentTheme = await db.getTheme()
if (currentTheme) {
// Theme is already set by the user; do not override
return
}

// Determine if the custom theme differs from the default light theme
const customTheme = defaults.theme || {}
const isCustomDifferent = isThemeDifferent(customTheme, defaultLightTheme)

// Apply custom theme if available and different
if (isCustomDifferent) {
await db.setTheme('custom') // Set the theme to custom if not already set
applyCustomTheme(customTheme)
} else {
await db.setTheme('light') // Ensure the theme is set to light
applyTheme('light')
}
} catch (error) {
console.error('Error applying defaults:', error)
}
}

function isThemeDifferent (custom, defaultTheme) {
// Compare each property; return true if any property differs
for (const key in defaultTheme) {
if (custom[key] !== defaultTheme[key]) {
return true
}
}
// Additionally, check if custom has extra properties
for (const key in custom) {
if (!(key in defaultTheme)) {
return true
}
}
return false
}

function applyTheme (themeName) {
const root = document.documentElement
root.setAttribute('data-theme', themeName)
}

async function applyCustomTheme (theme) {
const root = document.documentElement
root.setAttribute('data-theme', 'custom')

// Create or update a <style id="custom-theme"> element
let styleEl = document.getElementById('custom-theme')
if (!styleEl) {
styleEl = document.createElement('style')
styleEl.id = 'custom-theme'
document.head.appendChild(styleEl)
}

const themeVars = []

if (theme.bgColor) themeVars.push(` --bg-color: ${theme.bgColor};`)
if (theme.postBgColor) themeVars.push(` --rdp-bg-color: ${theme.postBgColor};`)
if (theme.postDetailsColor) themeVars.push(` --rdp-details-color: ${theme.postDetailsColor};`)
if (theme.postLinkColor) themeVars.push(` --rdp-link-color: ${theme.postLinkColor};`)

styleEl.textContent = `
:root[data-theme="custom"] {
${themeVars.join('\n')}
}
`
}

export async function initializeDefaultFollowedActors () {
const hasFollowedActors = await db.hasFollowedActors()

if (!hasFollowedActors) {
try {
const defaults = await fetchDefaults()
const defaultActors = defaults.defaultFollowedActors || []

// Follow default actors from the loaded configuration
await Promise.all(defaultActors.map((actorUrl) => db.followActor(actorUrl)))
} catch (error) {
console.error('Error loading default followed actors: ', error)
}
}
}
5 changes: 4 additions & 1 deletion followed-accounts.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { db } from './dbInstance.js'
import { applyDefaults } from './defaults.js'

export class FollowedActorsList extends HTMLElement {
constructor () {
super()
this.updateFollowedActors = this.updateFollowedActors.bind(this)
}

connectedCallback () {
async connectedCallback () {
await applyDefaults()

this.renderFollowedActors()

db.addEventListener('actorFollowed', this.updateFollowedActors)
Expand Down
9 changes: 8 additions & 1 deletion index.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,11 @@ html {

* {
box-sizing: border-box;
}
}

.loading-text {
font-size: 1rem;
color: var(--rdp-text-color);
text-align: center;
margin-top: 20px;
}
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Social Reader</title>
<title id="page-title">Social Reader</title>
<style>
@import url("./index.css");
@import url("./common.css");
Expand Down
5 changes: 4 additions & 1 deletion outbox.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { db } from './dbInstance.js'
import { applyDefaults } from './defaults.js'
import './post.js'

class DistributedOutbox extends HTMLElement {
Expand All @@ -14,7 +15,9 @@ class DistributedOutbox extends HTMLElement {
return ['url']
}

connectedCallback () {
async connectedCallback () {
await applyDefaults()

this.outboxUrl = this.getAttribute('url')
this.loadOutbox(this.outboxUrl)
}
Expand Down
5 changes: 4 additions & 1 deletion post.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* global customElements, HTMLElement */
import DOMPurify from './dependencies/dompurify/purify.js'
import { db } from './dbInstance.js'
import { applyDefaults } from './defaults.js'
import './p2p-media.js'
import './post-replies.js'

Expand Down Expand Up @@ -73,7 +74,9 @@ class DistributedPost extends HTMLElement {
return ['url']
}

connectedCallback () {
async connectedCallback () {
await applyDefaults()

this.loadAndRenderPost(this.getAttribute('url'))
}

Expand Down
16 changes: 16 additions & 0 deletions sidebar.css
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,22 @@ sidebar-nav nav a:hover {
text-decoration: none;
}


.release-version {
font-size: 0.75rem;
color: var(--rdp-details-color);
margin-top: 6px;
text-align: center;
}

.release-version a {
color: var(--rdp-details-color);
text-decoration: none;
}
.release-version a:hover {
text-decoration: underline;
}

@media screen and (max-width: 1280px) {
sidebar-nav {
flex: 0 0 200px;
Expand Down
6 changes: 4 additions & 2 deletions sidebar.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<a href="./" class="home-page-link">
<h1 class="header-branding">Social Reader</h1>
<h1 id="header-branding" class="header-branding">Social Reader</h1>
</a>
<div class="controls">
<a href="./followed-accounts.html">Following · <followed-count></followed-count></a>
Expand All @@ -10,4 +10,6 @@ <h1 class="header-branding">Social Reader</h1>
<a href="https://hypha.coop/dripline/announcing-dp-social-inbox/">Social Inbox</a>
<a href="https://distributed.press">Distributed Press</a>
</nav>
<distributed-search></distributed-search>
<distributed-search></distributed-search>

<div id="release-version" class="release-version"></div>
44 changes: 43 additions & 1 deletion sidebar.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
import './search.js'
import { applyDefaults } from './defaults.js'

const response = await fetch('./sidebar.html')
// Use import.meta.url to create a URL relative to the module's location
const defaultJsonUrl = new URL('./config/defaults.json', import.meta.url).href

// GitHub Releases Page URL
const githubReleasesPage = 'https://github.com/hyphacoop/reader.distributed.press/releases'

async function fetchLocalVersion () {
try {
const response = await fetch(defaultJsonUrl)
if (!response.ok) {
throw new Error(`Error fetching defaults.json: ${response.statusText}`)
}
const defaults = await response.json()
return defaults.version || 'Unknown Version'
} catch (error) {
console.error('Error fetching local version:', error)
return 'Unknown Version'
}
}

const response = await fetch(new URL('./sidebar.html', import.meta.url).href)
const text = await response.text()
const template = document.createElement('template')
template.innerHTML = text
Expand All @@ -15,6 +36,27 @@ class SidebarNav extends HTMLElement {
this.init()
}

async connectedCallback () {
await applyDefaults()

// Fetch the local version from defaults.json and display it in the sidebar
const versionElement = this.querySelector('#release-version')
if (versionElement) {
const localVersion = await fetchLocalVersion()

// Create the anchor element
const versionLink = document.createElement('a')
versionLink.href = githubReleasesPage
versionLink.textContent = `${localVersion}`
versionLink.target = '_blank' // Open in a new tab
versionLink.rel = 'noopener noreferrer' // Security best practices

// Clear any existing content and append the link
versionElement.innerHTML = ''
versionElement.appendChild(versionLink)
}
}

init () {
const instance = template.content.cloneNode(true)
this.appendChild(instance)
Expand Down
Loading

0 comments on commit 033746c

Please sign in to comment.