allow people to filter out replies and/or boosts

This commit is contained in:
fenwick67 2018-03-24 13:01:26 -05:00
parent e08008aaae
commit 0b567fe3a9
9 changed files with 1685 additions and 1379 deletions

142
index.js
View file

@ -1,64 +1,78 @@
var Express = require('express');
var convert = require('./lib/convert');
var serveStatic = require('serve-static');
var request = require('request');
var log = console.log;
var app = Express();
function logMiddleware(req,res,next){
log(req.method.toUpperCase() +' '+ req.url);
log( '\t'+ new Date().toISOString() );
if(req.headers && req.headers.referer){
log('\tReferer: '+req.headers.referer);
}
return next(null);
}
app.use(logMiddleware);
app.use(
serveStatic('static',{
maxAge:'1d'
})
);
app.get('/api/feed',function(req,res){
// get feed url
var feedUrl = req.query.url;
if (!feedUrl){
res.status(400);
res.send('You need to specify a feed URL');
}
var opts = {};
if (req.query.size){
opts.size = req.query.size;
}
if (req.query.theme){
opts.theme = req.query.theme;
}
if (req.query.header){
if (req.query.header.toLowerCase() == 'no' || req.query.header.toLowerCase() == 'false'){
opts.header = false;
}else{
opts.header = true;
}
}
var req = request.get(feedUrl);
convert(req,opts,function(er,data){
if (er){
res.status(500);
return res.send('Error fetching or parsing your feed.');
}
res.status(200);
res.send(data);
});
});
app.listen(process.env.PORT || 8000,function(){
log('listening on '+(process.env.PORT || 8000));
});
var Express = require('express');
var convert = require('./lib/convert');
var serveStatic = require('serve-static');
var request = require('request');
var log = console.log;
var app = Express();
function logMiddleware(req,res,next){
log(req.method.toUpperCase() +' '+ req.url);
log( '\t'+ new Date().toISOString() );
if(req.headers && req.headers.referer){
log('\tReferer: '+req.headers.referer);
}
return next(null);
}
app.use(logMiddleware);
app.use(
serveStatic('static',{
maxAge:'1d'
})
);
app.get('/api/feed',function(req,res){
// get feed url
var feedUrl = req.query.url;
if (!feedUrl){
res.status(400);
res.send('You need to specify a feed URL');
}
var opts = {};
if (req.query.size){
opts.size = req.query.size;
}
if (req.query.theme){
opts.theme = req.query.theme;
}
if (req.query.header){
if (req.query.header.toLowerCase() == 'no' || req.query.header.toLowerCase() == 'false'){
opts.header = false;
}else{
opts.header = true;
}
}
if (req.query.boosts){
if (req.query.boosts.toLowerCase() == 'no' || req.query.boosts.toLowerCase() == 'false'){
opts.boosts = false;
}else{
opts.boosts = true;
}
}
if (req.query.replies){
if (req.query.replies.toLowerCase() == 'no' || req.query.replies.toLowerCase() == 'false'){
opts.replies = false;
}else{
opts.replies = true;
}
}
var req = request.get(feedUrl);
convert(req,opts,function(er,data){
if (er){
res.status(500);
return res.send('Error fetching or parsing your feed.');
}
res.status(200);
res.send(data);
});
});
app.listen(process.env.PORT || 8000,function(){
log('listening on '+(process.env.PORT || 8000));
});

View file

@ -1,204 +1,229 @@
var pug = require('pug');
var FeedParser = require('feedparser');
var ejs = require('ejs');
var fs = require('fs');
var template = ejs.compile(fs.readFileSync('./lib/template.ejs','utf8'));
var timeAgo = require('timeago.js');
function isArray(a){
return Array.isArray(a);
}
// accumulate a stream of XML into a html file
module.exports = function(stream,opts,callback){
var callback = callback;
var opts = opts;
if (typeof opts == 'function'){
callback = opts;
opts = {};
}
// convert s from atom feed to a full html page for rendering
breakDown(stream,function(er,data){
if (er) {
return callback(er);
}
// try and build up
try{
var result = buildUp(data,opts)
}catch(e){
return callback(e);
}
return callback(null,result);
});
}
// break the xml into json
function breakDown(stream,callback){
var spent = false;
function cbOnce(er,data){
if (!spent){
callback(er,data);
spent = true;
}
}
stream.on('error',cbOnce);
var feedparser = new FeedParser();
feedparser.on('error', cbOnce);
stream.pipe(feedparser)
feedparser.items = [];
feedparser.on('readable', function () {
// This is where the action is!
var stream = this; // `this` is `feedparser`, which is a stream
var items = [];
var item;
while (item = stream.read()) {
feedparser.items.push(item);
}
});
feedparser.on('end',function(er){cbOnce(null,feedparser)});
}
// hydrate the json to html
function buildUp(jsonObj,opts){
// assign opts to the obj
jsonObj.opts = opts||{};
// iterate through the items
jsonObj.items = jsonObj.items.filter(function(item){
// get date
item.stringDate = getTimeDisplay(item.date);
item.content = getH(item,'atom:content');
if (!item.content ){// item was deleted
return false;
}
// make anchor tags have the "_top" target
item.content = item.content.replace(/\<\s*a\s*/ig,'<a target="_top"');
// get enclosures
item.enclosures = [];
function findEnclosure(link){
if (!link['@']){return;} // avoid keyerror
var rel = link['@'].rel;
var href = link['@'].href;
if (rel == 'enclosure'){
item.enclosures.push({
url:href,
type:link['@'].type||''
});
}
}
// find them in the item's links
if (item['atom:link']){
var links = item['atom:link'];
if (!isArray(links) && typeof links == 'object'){
links = [links];
}else if (!isArray(links)){
links = [];
}
links.forEach(findEnclosure);
}
// find them in activity:object
if (item["activity:object"] && item["activity:object"].link){
var enclosureLinks = item["activity:object"].link;
if (!isArray(enclosureLinks) && typeof enclosureLinks == 'object'){
enclosureLinks = [enclosureLinks];
}else if (!isArray(enclosureLinks)){
enclosureLinks = [];// not an object or array.
}
enclosureLinks.forEach(findEnclosure);
}
// get author info
item.author = {};
var _author = item.meta['atom:author'];
if ( item['activity:object'] && item['activity:object'].author){
_author = item['activity:object'].author;
}
item.author.name = getH(_author,'name');
item.author.uri = getH(_author,'uri');
item.author.fullName = getH(_author,'email');
item.author.displayName = getH(_author,'poco:displayname')||getH(_author,'poco:preferredUsername')||item.author.name;
var authorLinks = _author.link || [];
if (!isArray(authorLinks) && typeof authorLinks == 'object'){
authorLinks = [authorLinks];
}else if ( !isArray(authorLinks) ){
authorLinks = [];// not an object or string.
}
authorLinks.forEach(function(link){// todo: guard against authorLinks not being an array
if (!link['@']){return;} // avoid keyerror
var rel = link['@'].rel;
var href = link['@'].href;
if (rel == 'avatar'){
item.author.avatar = href;
}else if(rel == 'alternate'){
item.author.alternate = href;
}
});
return true;
});
return template(jsonObj);
}
// get obj[key]['#'] or ''
function getH(obj,key){
if (!obj[key]){return ''}
return obj[key]['#']||'';
}
function getTimeDisplay(d){
var d = d;
if (typeof d !== 'object'){
d = new Date(d);
}
// convert to number
dt = d.getTime();
var now = Date.now();
var delta = now - dt;
// over 6 days ago
if (delta > 1000*60*60*24*6){
return isoDateToEnglish(d.toISOString());
}else{
return timeAgo().format(dt);
}
}
function isoDateToEnglish(d){
var dt = d.split(/[t\-]/ig);
var months = [ "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" ];
return months[Number(dt[1])-1] +' '+dt[2]+ ', '+dt[0];
}
var pug = require('pug');
var FeedParser = require('feedparser');
var ejs = require('ejs');
var fs = require('fs');
var template = ejs.compile(fs.readFileSync('./lib/template.ejs','utf8'));
var timeAgo = require('timeago.js');
function isArray(a){
return Array.isArray(a);
}
// accumulate a stream of XML into a html file
module.exports = function(stream,opts,callback){
var callback = callback;
var opts = opts;
if (typeof opts == 'function'){
callback = opts;
opts = {};
}
// convert s from atom feed to a full html page for rendering
breakDown(stream,function(er,data){
if (er) {
return callback(er);
}
// try and build up
try{
var result = buildUp(data,opts)
}catch(e){
return callback(e);
}
return callback(null,result);
});
}
// break the xml into json
function breakDown(stream,callback){
var spent = false;
function cbOnce(er,data){
if (!spent){
callback(er,data);
spent = true;
}
}
stream.on('error',cbOnce);
var feedparser = new FeedParser();
feedparser.on('error', cbOnce);
stream.pipe(feedparser)
feedparser.items = [];
feedparser.on('readable', function () {
// This is where the action is!
var stream = this; // `this` is `feedparser`, which is a stream
var items = [];
var item;
while (item = stream.read()) {
feedparser.items.push(item);
}
});
feedparser.on('end',function(er){cbOnce(null,feedparser)});
}
// hydrate the json to html
function buildUp(jsonObj,opts){
// assign opts to the obj
jsonObj.opts = opts||{};
// iterate through the items
jsonObj.items = jsonObj.items.filter(function(item){
// get date
item.stringDate = getTimeDisplay(item.date);
item.content = getH(item,'atom:content');
if (!item.content ){// item was deleted
return false;
}
// make anchor tags have the "_top" target
item.content = item.content.replace(/\<\s*a\s*/ig,'<a target="_top"');
// get enclosures
item.enclosures = [];
function findEnclosure(link){
if (!link['@']){return;} // avoid keyerror
var rel = link['@'].rel;
var href = link['@'].href;
if (rel == 'enclosure'){
item.enclosures.push({
url:href,
type:link['@'].type||''
});
}
}
// find them in the item's links
if (item['atom:link']){
var links = item['atom:link'];
if (!isArray(links) && typeof links == 'object'){
links = [links];
}else if (!isArray(links)){
links = [];
}
links.forEach(findEnclosure);
}
// find them in activity:object
if (item["activity:object"] && item["activity:object"].link){
var enclosureLinks = item["activity:object"].link;
if (!isArray(enclosureLinks) && typeof enclosureLinks == 'object'){
enclosureLinks = [enclosureLinks];
}else if (!isArray(enclosureLinks)){
enclosureLinks = [];// not an object or array.
}
enclosureLinks.forEach(findEnclosure);
}
// get author info
item.author = {};
var _author = item.meta['atom:author'];
if ( item['activity:object'] && item['activity:object'].author){
_author = item['activity:object'].author;
}
item.author.name = getH(_author,'name');
item.author.uri = getH(_author,'uri');
item.author.fullName = getH(_author,'email');
item.author.displayName = getH(_author,'poco:displayname')||getH(_author,'poco:preferredUsername')||item.author.name;
var authorLinks = _author.link || [];
if (!isArray(authorLinks) && typeof authorLinks == 'object'){
authorLinks = [authorLinks];
}else if ( !isArray(authorLinks) ){
authorLinks = [];// not an object or string.
}
authorLinks.forEach(function(link){// todo: guard against authorLinks not being an array
if (!link['@']){return;} // avoid keyerror
var rel = link['@'].rel;
var href = link['@'].href;
if (rel == 'avatar'){
item.author.avatar = href;
}else if(rel == 'alternate'){
item.author.alternate = href;
}
});
// now detect if item is a reply or boost
item.isBoost = false;// <activity:verb>http://activitystrea.ms/schema/1.0/share</activity:verb>
item.isReply = false;
var v = getH(item,'activity:verb')
if (v.indexOf('share') > -1){
item.isBoost = true;
}
if (item['thr:in-reply-to']){
item.isReply = true;
}
return true;
});
if (jsonObj.meta && jsonObj.meta['atom:author'] && jsonObj.meta['atom:author'].link && Array.isArray(jsonObj.meta['atom:author'].link) ){
jsonObj.meta['atom:author'].link.forEach(link=>{
var l = link['@'];
if (l.rel=="header"){
jsonObj.meta.headerImage = l.href;
}
else if(l.rel=="avatar"){
jsonObj.meta.avatar = l.href;
}
});
}
return template(jsonObj);
}
// get obj[key]['#'] or ''
function getH(obj,key){
if (!obj[key]){return ''}
return obj[key]['#']||'';
}
function getTimeDisplay(d){
var d = d;
if (typeof d !== 'object'){
d = new Date(d);
}
// convert to number
dt = d.getTime();
var now = Date.now();
var delta = now - dt;
// over 6 days ago
if (delta > 1000*60*60*24*6){
return isoDateToEnglish(d.toISOString());
}else{
return timeAgo().format(dt);
}
}
function isoDateToEnglish(d){
var dt = d.split(/[t\-]/ig);
var months = [ "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" ];
return months[Number(dt[1])-1] +' '+dt[2]+ ', '+dt[0];
}

View file

@ -1,68 +1,84 @@
<html>
<head>
<meta charset="UTF-8"></meta>
<style type="text/css"></style>
<% if (opts.theme && opts.theme.toLowerCase() == 'light'){ %>
<link rel="stylesheet" href="/light.css"></link>
<% } else { %>
<link rel="stylesheet" href="/dark.css"></link>
<% } %>
<% if (opts.size){ %>
<style type="text/css">
html,body{
font-size: <%= opts.size.toString().slice(0,4) %>%;
}
</style>
<% } %>
</head>
<body>
<% if (opts.header !== false){ %>
<div class="meta">
<div class="title">
<a target="_top" href="<%= meta.link %>">
<%- meta.title %>'s Feed on Mastodon
</a>
</div>
<div class="description"><%- meta.description %></div>
</div>
<% } %>
<div class="container">
<% items.forEach(function(item){ %>
<div class="item">
<div class="item-title"> <%- item.title %> </div>
<div class="author">
<a target="_top" class="avatar" href="<%- item.author.uri %>">
<img class="avatar" src="<%- item.author.avatar %>"/>
</a>
<div class="author-info">
<a target="_top" class="author-displayname" href="<%- item.author.uri %>"> <%= item.author.displayName %> </a>
<div class="author-fullname"> <%= item.author.fullName %> </div>
</div>
</div>
<div class="item-content">
<%- item.content %>
</div>
<% if (item.enclosures.length > 0){ %>
<div class="enclosures">
<% for (var i = 0; i < item.enclosures.length; i ++){ var e = item.enclosures[i] %>
<a target="_top" class="enclosure" href="<%= e.url %>" >
<% if (e.type.indexOf('image') > -1){ %>
<img src="<%= e.url %>"/>
<% }else if (e.type.indexOf('video') > -1){ %>
<video autoplay loop muted src="<%= e.url %>"/>
<% } else { %>
<%= e.url %>
<% } %>
</a>
<% } %>
</div>
<% } %>
<div class="date"><%= item.stringDate %></div>
</div>
<% }); %>
</div>
</body>
</html>
<html>
<head>
<meta charset="UTF-8"></meta>
<style type="text/css"></style>
<% if (opts.theme && opts.theme.toLowerCase() == 'light'){ %>
<link rel="stylesheet" href="/light.css"></link>
<% } else { %>
<link rel="stylesheet" href="/dark.css"></link>
<% } %>
<% if (opts.size){ %>
<style type="text/css">
html,body{
font-size: <%= opts.size.toString().slice(0,4) %>%;
}
</style>
<% } %>
</head>
<body>
<% if (opts.header !== false){ %>
<div class="meta">
<div class="header" style="<%= meta.headerImage?`background-image:url(${meta.headerImage})`:'' %>">
<a class="header-left" target="_top" href="<%= meta.link %>">
<% if (meta.avatar){ %>
<img class="avatar circular" src="<%= meta.avatar %>"></img>
<% } %>
</a>
<div class="description header-right">
<a target="_top" href="<%= meta.link %>">
<%= meta.title %>
</a>
<br>
<%= meta.description %>
</div>
</div>
</div>
<% } %>
<div class="container">
<% var filtered = items.filter(function(item){return !((item.isBoost && !opts.boosts) || (item.isReply && !opts.replies)) })%>
<% if (filtered.length < 2){
filtered = items;// show all items if it was pared down too much
} %>
<% filtered.forEach(function(item){ %>
<div class="item">
<% if (item.isBoost) { %>
<div class="item-title"> <%- item.title %> </div>
<% } %>
<div class="author">
<a target="_top" class="avatar" href="<%- item.author.uri %>">
<img class="avatar" src="<%- item.author.avatar %>"/>
</a>
<div class="author-info">
<a target="_top" class="author-displayname" href="<%- item.author.uri %>"> <%= item.author.displayName %> </a>
<div class="author-fullname"> <%= item.author.fullName %> </div>
</div>
</div>
<div class="item-content">
<%- item.content %>
</div>
<% if (item.enclosures.length > 0){ %>
<div class="enclosures">
<% for (var i = 0; i < item.enclosures.length; i ++){ var e = item.enclosures[i] %>
<a target="_top" class="enclosure" href="<%= e.url %>" >
<% if (e.type.indexOf('image') > -1){ %>
<img src="<%= e.url %>"/>
<% }else if (e.type.indexOf('video') > -1){ %>
<video autoplay loop muted src="<%= e.url %>"/>
<% } else { %>
<%= e.url %>
<% } %>
</a>
<% } %>
</div>
<% } %>
<div class="date"><%= item.stringDate %></div>
</div>
<% }); %>
</div>
</body>
</html>

View file

@ -16,18 +16,35 @@ a * {
color: #2b90d9; }
.meta {
padding: 1rem;
background-color: #39404d; }
.meta * {
line-height: 2rem; }
.header {
display: flex;
background-size: cover;
min-height: 8rem;
color: #ffffff; }
.header .header-left, .header .header-right {
margin: 0; }
.header .header-left {
min-width: 8rem;
position: relative;
text-align: center;
background: rgba(40, 44, 55, 0.3); }
.header .header-left .avatar {
width: 6rem;
height: 6rem;
position: relative;
top: calc(50% - 3rem); }
.header .header-right {
font-size: 0.9rem;
padding: 0.9rem;
background: rgba(40, 44, 55, 0.85); }
.item {
padding: 1rem;
border-top: solid 1px #626d80; }
.item-content,
.description,
.title {
font-size: 1.1rem;
font-weight: lighter; }
@ -38,8 +55,7 @@ a * {
.item-title,
.date,
.author-fullname,
.description {
.author-fullname {
color: #9baec8;
font-size: 0.9rem; }
@ -67,6 +83,8 @@ a * {
height: 3rem;
border: none;
border-radius: 10%; }
.avatar.circular {
border-radius: 100%; }
.enclosures {
padding: 0.5em 0;

View file

@ -1,59 +1,69 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
<title>Mastofeed - embeddable Mastodon feeds</title>
<link rel="stylesheet" href="./stylesheet.css">
</head>
<body>
<br>
<div>
<h1>Mastofeed</h1>
<h4>Embedded Mastodon feeds for blogs etc.</h4>
<a href="https://github.com/fenwick67/mastofeed" class="cta button alt">Fork on Github <img class="link-logo after" src="github-logo.svg" alt="Github Logo" data-reactid="19"></a><br>
<br><hr><br>
<form action="javascript:genUrl()">
<label>Instance URL:<input required type="text" id="urlin" placeholder="octodon.social" oninvalid="this.setCustomValidity('Insert your instance URL. Example: octodon.social')" oninput="this.setCustomValidity('')"></label><br>
<label>Username:<input required type="text" id="usernamein" placeholder="fenwick67" oninvalid="this.setCustomValidity('Insert your username. Example: fenwick67')" oninput="this.setCustomValidity('')"></label><br>
<label>Width (px):<input required type="number" id="width" value="400" oninvalid="this.setCustomValidity('Insert width of generated feed. Default: 400')" oninput="this.setCustomValidity('')"></label><br>
<label>Height (px):<input required type="number" id="height" value="800" oninvalid="this.setCustomValidity('Insert height of generated feed. Default: 800')" oninput="this.setCustomValidity('')"></label><br>
<label>UI scale (percent):<input required type="number" id="size" value="100" oninvalid="this.setCustomValidity('Insert UI scale. Default: 100')" oninput="this.setCustomValidity('')"></label><br>
<label>Theme:
<select id="theme">
<option value="dark">dark</option>
<option value="light">light</option>
</select>
</label><br>
<label>Show header?<input id="header" type="checkbox" checked="checked"></label><br>
<button value="generate">Generate</button>
</form>
<br>
<br>
<label>Use this markup in your HTML: <br><textarea id="result" placeholder="result will go here"></textarea></label>
<br>
<h3>Live Preview:</h3>
<iframe id="frame" allowfullscreen sandbox="allow-top-navigation allow-scripts" width="400" height="800" src="/api/feed?url=https%3A%2F%2Foctodon.social%2Fusers%2Ffenwick67.atom&theme=dark&size=100&header=true"></iframe>
<br>
</div>
<script>
window.genUrl = function genUrl(){
function val(id){
return document.getElementById(id).value;
}
var inUrl = 'https://' + val('urlin') + '/users/'+val('usernamein')+'.atom';
var iframeUrl = window.location.protocol + '//'+ window.location.hostname +((window.location.port && window.location.port!=80)?(':'+window.location.port):'')
+"/api/feed?url="+encodeURIComponent(inUrl)+"&theme="+val('theme')+'&size='+val('size')
+ "&header="+(document.getElementById('header').checked.toString());
document.getElementById('result').value = '<iframe allowfullscreen sandbox="allow-top-navigation allow-scripts" width="'+val('width')+'" height="'+val('height')+'" src="'+iframeUrl+'"></iframe>';
var iframe = document.getElementById('frame');
iframe.src = iframeUrl;
iframe.width = val('width');
iframe.height = val('height');
}
</script>
</body>
</html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
<title>Mastofeed - embeddable Mastodon feeds</title>
<link rel="stylesheet" href="./stylesheet.css">
</head>
<body>
<br>
<div>
<h1>Mastofeed</h1>
<h4>Embedded Mastodon feeds for blogs etc.</h4>
<a href="https://github.com/fenwick67/mastofeed" class="cta button alt">Fork on Github <img class="link-logo after" src="github-logo.svg" alt="Github Logo" data-reactid="19"></a><br>
<br><hr><br>
<form action="javascript:genUrl()">
<label>Instance URL:<input required type="text" id="urlin" placeholder="octodon.social" oninvalid="this.setCustomValidity('Insert your instance URL. Example: octodon.social')" oninput="this.setCustomValidity('')"></label><br>
<label>Username:<input required type="text" id="usernamein" placeholder="fenwick67" oninvalid="this.setCustomValidity('Insert your username. Example: fenwick67')" oninput="this.setCustomValidity('')"></label><br>
<label>Width (px):<input required type="number" id="width" value="400" oninvalid="this.setCustomValidity('Insert width of generated feed. Default: 400')" oninput="this.setCustomValidity('')"></label><br>
<label>Height (px):<input required type="number" id="height" value="800" oninvalid="this.setCustomValidity('Insert height of generated feed. Default: 800')" oninput="this.setCustomValidity('')"></label><br>
<label>UI Scale (percent):<input required type="number" id="size" value="100" oninvalid="this.setCustomValidity('Insert UI scale. Default: 100')" oninput="this.setCustomValidity('')"></label><br>
<label>Theme:
<select id="theme">
<option value="dark">dark</option>
<option value="light">light</option>
</select>
</label><br>
<label>Show Header?<input id="header" type="checkbox" checked="checked"></label><br>
<label>Hide replies?<input type="checkbox" id="hidereplies"></label><br>
<label>Hide boosts?<input type="checkbox" id="hideboosts"></label><br>
<button value="generate">Generate</button>
</form>
<div id="warnings"></div>
<br>
<br>
<label>Use this markup in your HTML: <br><textarea id="result" placeholder="result will go here"></textarea></label>
<br>
<h3>Live Preview:</h3>
<iframe id="frame" allowfullscreen sandbox="allow-top-navigation allow-scripts" width="400" height="800" src="/api/feed?url=https%3A%2F%2Foctodon.social%2Fusers%2Ffenwick67.atom&theme=dark&size=100&header=true"></iframe>
<br>
</div>
<script>
window.genUrl = function genUrl(){
function val(id){
return document.getElementById(id).value;
}
var inUrl = 'https://' + val('urlin') + '/users/'+val('usernamein')+'.atom';
var showBoosts = (!document.getElementById('hideboosts').checked).toString();
var showReplies = (!document.getElementById('hidereplies').checked).toString();
var iframeUrl = window.location.protocol + '//'+ window.location.hostname +((window.location.port && window.location.port!=80)?(':'+window.location.port):'')
+"/api/feed?url="+encodeURIComponent(inUrl)+"&theme="+val('theme')+'&size='+val('size')
+ "&header="+(document.getElementById('header').checked.toString())+'&replies='+showReplies+'&boosts='+showBoosts;
document.getElementById('result').value = '<iframe allowfullscreen sandbox="allow-top-navigation allow-scripts" width="'+val('width')+'" height="'+val('height')+'" src="'+iframeUrl+'"></iframe>';
var iframe = document.getElementById('frame');
iframe.src = iframeUrl;
iframe.width = val('width');
iframe.height = val('height');
var warnings = (!showBoosts || !showReplies)?"<b>Friendly warning</b>: if you haven't posted lately, we may still show your boosts and/or replies":'';
document.getElementById('warnings').innerHTML = warnings;
}
</script>
</body>
</html>

View file

@ -16,18 +16,35 @@ a * {
color: #2b90d9; }
.meta {
padding: 1rem;
background-color: #ecf0f4; }
.meta * {
line-height: 2rem; }
.header {
display: flex;
background-size: cover;
min-height: 8rem;
color: #282c37; }
.header .header-left, .header .header-right {
margin: 0; }
.header .header-left {
min-width: 8rem;
position: relative;
text-align: center;
background: rgba(255, 255, 255, 0.3); }
.header .header-left .avatar {
width: 6rem;
height: 6rem;
position: relative;
top: calc(50% - 3rem); }
.header .header-right {
font-size: 0.9rem;
padding: 0.9rem;
background: rgba(255, 255, 255, 0.85); }
.item {
padding: 1rem;
border-top: solid 1px #8494ab; }
.item-content,
.description,
.title {
font-size: 1.1rem;
font-weight: lighter; }
@ -38,8 +55,7 @@ a * {
.item-title,
.date,
.author-fullname,
.description {
.author-fullname {
color: #90a1ba;
font-size: 0.9rem; }
@ -67,6 +83,8 @@ a * {
height: 3rem;
border: none;
border-radius: 10%; }
.avatar.circular {
border-radius: 100%; }
.enclosures {
padding: 0.5em 0;

View file

@ -1,118 +1,142 @@
$bg: #fff !default;
$bg2: darken($bg, 6) !default;
$fg: #000 !default;
$dim: lighten($fg, 50) !default;
$dimmer: darken($bg2, 10) !default;
$link: #09c !default;
html,
body {
background-color: $bg;
font-family: 'Roboto', roboto, Arial, sans-serif;
color: $fg;
font-weight: lighter;
overflow-x:hidden;
font-size:100%;
}
* {
margin: 0;
padding: 0;
}
a,
a * {
color: $link;
}
.meta {
padding: 1rem;
background-color: $bg2;
}
.meta * {
line-height: 2rem;
}
.item {
padding: 1rem;
border-top: solid 1px $dimmer;
}
.item-content,
.description,
.title {
font-size: 1.1rem;
font-weight:lighter;
}
.item-content * {
margin: 1rem 0;
line-height:1.4rem;
}
.item-title,
.date,
.author-fullname,
.description {
color: $dim;
font-size: 0.9rem;
}
.date{
margin: 1rem 0 0 0;
}
.author {
display: flex;
margin: 1rem 0;
}
.author-info {
margin: 0 1rem;
display: flex;
flex-direction: column;
justify-content: space-around;
.author-displayname {
font-size: 1.2rem;
color: $fg;
text-decoration: none;
display: block;
font-weight: bolder;
}
}
.avatar {
width: 3rem;
height: 3rem;
border: none;
border-radius: 10%;
}
.enclosures {
padding: 0.5em 0;
display: flex;
flex-wrap:wrap;
flex-direction: row;
overflow: hidden;
}
.enclosure{
display:flex;
flex: 1 1 auto;
width:50%;
display: inline-block;
border: none;
cursor: zoom-in;
max-height: 12rem;
}
.enclosure>*{
flex: 1 1 auto;
width:100%;
height:100%;
object-fit: cover;
}
.meta .title{
font-weight:bold;
}
$bg: #fff !default;
$bg2: darken($bg, 6) !default;
$fg: #000 !default;
$dim: lighten($fg, 50) !default;
$dimmer: darken($bg2, 10) !default;
$link: #09c !default;
html,
body {
background-color: $bg;
font-family: 'Roboto', roboto, Arial, sans-serif;
color: $fg;
font-weight: lighter;
overflow-x:hidden;
font-size:100%;
}
* {
margin: 0;
padding: 0;
}
a,
a * {
color: $link;
}
.meta {
//padding: 1rem;
background-color: $bg2;
}
.header{
display:flex;
background-size:cover;
min-height:8rem;
color:$fg;
.header-left,.header-right{
margin:0;
}
.header-left{
min-width:8rem;
position:relative;
text-align:center;
background:transparentize($bg,0.7);
.avatar{
width: 6rem;
height: 6rem;
position:relative;
top:calc(50% - 3rem);
}
}
.header-right{
font-size:0.9rem;
padding:0.9rem;
background:transparentize($bg,0.15);
}
}
.item {
padding: 1rem;
border-top: solid 1px $dimmer;
}
.item-content,
.title {
font-size: 1.1rem;
font-weight:lighter;
}
.item-content * {
margin: 1rem 0;
line-height:1.4rem;
}
.item-title,
.date,
.author-fullname {
color: $dim;
font-size: 0.9rem;
}
.date{
margin: 1rem 0 0 0;
}
.author {
display: flex;
margin: 1rem 0;
}
.author-info {
margin: 0 1rem;
display: flex;
flex-direction: column;
justify-content: space-around;
.author-displayname {
font-size: 1.2rem;
color: $fg;
text-decoration: none;
display: block;
font-weight: bolder;
}
}
.avatar {
width: 3rem;
height: 3rem;
border: none;
border-radius: 10%;
&.circular{
border-radius: 100%;
}
}
.enclosures {
padding: 0.5em 0;
display: flex;
flex-wrap:wrap;
flex-direction: row;
overflow: hidden;
}
.enclosure{
display:flex;
flex: 1 1 auto;
width:50%;
display: inline-block;
border: none;
cursor: zoom-in;
max-height: 12rem;
}
.enclosure>*{
flex: 1 1 auto;
width:100%;
height:100%;
object-fit: cover;
}
.meta .title{
font-weight:bold;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff