This commit is contained in:
j 2023-07-16 11:26:14 +05:30
parent 100fe79b1d
commit 4b157ed1d1
15 changed files with 305 additions and 133 deletions

View file

@ -14,7 +14,13 @@ admin.site.register(models.Item, ItemAdmin)
class CommentAdmin(admin.ModelAdmin):
search_fields = ['item__title', 'item__url', 'text', 'name', 'email']
search_fields = [
'item__title',
'item__url',
'text',
'name',
'email'
]
list_display = ['__str__', 'published']
list_filter = (
("published", admin.EmptyFieldListFilter),

View file

@ -1,6 +1,8 @@
from django.utils.timezone import datetime, timedelta
from django.utils import timezone
import json
import requests
import lxml.html
from django.conf import settings
from django.contrib.auth import get_user_model
@ -31,6 +33,10 @@ class Item(models.Model):
announced = models.DateTimeField(null=True, default=None, blank=True)
data = models.JSONField(default=dict, editable=False)
def save(self, *args, **kwargs):
if self.url and not self.data:
self.update_data()
super().save(*args, **kwargs)
def __str__(self):
return '%s (%s)' % (self.title, self.url)
@ -70,6 +76,26 @@ class Item(models.Model):
def get_absolute_url(self):
return reverse('item', kwargs={'id': self.id})
def update_data(self):
self.data.update(self.parse_url())
def parse_url(self):
content = requests.get(self.url).text
doc = lxml.html.fromstring(content)
data = {}
for meta in doc.cssselect('meta'):
key = meta.attrib.get('name')
if not key:
key = meta.attrib.get('property')
value = meta.attrib.get('content')
if key and value:
if key in ('viewport', ):
continue
key = key.replace('og:', '')
data[key] = value
return data
class Comment(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
@ -83,6 +109,10 @@ class Comment(models.Model):
data = models.JSONField(default=dict, editable=False)
published = models.DateTimeField(null=True, default=None)
@property
def is_published(self):
return bool(self.published)
def __str__(self):
return '%s: %s' % (self.item, self.user)

View file

@ -24,6 +24,7 @@ SECRET_KEY = "django-insecure-()6&rdheil=iyz%36dl-fnb)a+*7*^cb%isz6x%fi+ong5#*zz
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
COMPRESS_ENABLED = True
ALLOWED_HOSTS = []
@ -38,6 +39,9 @@ INSTALLED_APPS = [
"django.contrib.messages",
"django.contrib.staticfiles",
'compressor',
'sass_processor',
"app",
"app.user",
"app.item",
@ -118,6 +122,13 @@ USE_TZ = True
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = "static/"
STATIC_ROOT = "www/static"
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
"sass_processor.finders.CssFinder",
"compressor.finders.CompressorFinder",
)
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

View file

@ -1,43 +0,0 @@
.comments h3 {
font-weight: bold;
padding: 4px;
padding-left: 0;
margin: 0;
//display: none;
}
.comments.active h3 {
display: block;
}
.comments .icon svg {
width: 12px;
height: 12px;
}
.add-comment {
width: 100%;
height: 30vh;
text-align: center;
}
.add-comment input {
width: 100px;
}
.add-comment textarea {
width: 80%;
display: block;
background: black;
color: white;
margin: auto;
padding: 0;
border: 0;
}
.add-comment button {
background: red;
border: 0;
}
.comment .text {
white-space: pre-line;
}

View file

@ -0,0 +1,90 @@
.comments h3 {
font-weight: bold;
padding: 4px;
padding-left: 0;
margin: 0;
//display: none;
}
.comments.active h3 {
display: block;
}
.comments .icon svg {
width: 12px;
height: 12px;
}
.add-comment {
box-sizing: border-box;
width: 100%;
height: 30vh;
padding-top: 8px;
padding-left: 4px;
}
@media(max-width:768px) {
.add-comment {
padding-left: 4px;
}
}
.add-comment textarea,
.add-comment input {
padding: 4px;
margin-left: 0;
margin-top: 4px;
margin-bottom: 4px;
margin-right: 4px;
background: none;
color: white;
border: 1px solid green;
}
.add-comment input {
width: calc(50% - 8px);
}
.add-comment textarea {
width: calc(100% - 8px);
height: 100px;
display: block;
}
.add-comment button {
background: black;
color: white;
border: solid 1px green;
padding: 8px;
}
.add-comment button:hover {
border: solid 1px lightgreen;
cursor: pointer;
}
.add-comment {
.buttons {
&.login {
input {
width: calc(25% - 8px);
}
}
}
}
.comment {
padding: 4px;
border-bottom: 1px solid blueviolet;
}
.comment .text {
white-space: pre-line;
}
.comments h3 {
cursor: pointer;
}
.comments.collapsed .block {
display: none;
}
.comments .meta {
color: gray;
}

52
app/static/css/site.scss Normal file
View file

@ -0,0 +1,52 @@
header, footer {
max-width: 1000px;
padding-top: 16px;
margin: auto;
}
header {
a {
color: yellow;
text-decoration: none;
}
}
.index {
padding-top: 16px;
.item {
border-bottom: 1px solid blueviolet;
padding-bottom: 4px;
margin-bottom: 8px;
a {
text-decoration: none;
color: white;
h1 {
text-decoration: underline;
}
}
h1 {
margin: 0;
padding: 0;
}
h1, .info, .comments {
}
.image {
padding-top: 4px;
width: 100%;
img {
width: 100%;
aspect-ratio: 16/9;
object-fit: cover;
}
}
.content {
max-width: 1000px;
margin: auto;
}
}
.archive {
text-align: center;
}
}

View file

@ -9,6 +9,16 @@ body {
line-height: normal;
}
*, *::after, *::before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
*:focus {
outline: none;
}
a {
color: rgb(128, 128, 255)
}

View file

@ -1,26 +1,41 @@
function renderComments(data) {
var cdiv = div.querySelector('.comments')
function renderComments(cdiv, data) {
cdiv.innerHTML = `
<h3>
<span class="icon">${icon.down}</span>
Comments
</h3>
<div class="block">
<div class="comments-content">
</div>
</div>
`
const content = cdiv.querySelector('.comments-content')
comments.forEach(comment => {
var c = document.createElement('div')
c.className = 'comment'
c.innerHTML = `
<div class="comment">
<div class="name">${comment.name}</div>
<div class="date">${comment.date}</div>
<div class="text">${comment.text}</div>
<div class="meta">
by <span class="name">${comment.name}</span>
on
<span class="date">${comment.date}</span>
</div>
`
cdiv.append(c)
content.append(c)
})
var add = document.querySelector('.add-comment')
add.style.display = 'block'
cdiv.append(add)
cdiv.querySelector('.block').append(add)
cdiv.querySelector('h3').addEventListener("click", event => {
var img = cdiv.querySelector('h3 .icon')
if (cdiv.classList.contains("collapsed")) {
cdiv.classList.remove("collapsed")
img.innerHTML = icon.down
} else {
cdiv.classList.add("collapsed")
img.innerHTML = icon.right
}
})
}
document.querySelector('button#add-comment').addEventListener('click', event => {
@ -46,21 +61,20 @@ document.querySelector('button#add-comment').addEventListener('click', event =>
}).then(response => {
return response.json()
}).then(response => {
var comment= document.createElement('div')
var comment = document.createElement('div')
comment.classList.add('comment')
var name = document.createElement('div')
name.classList.add('name')
name.innerText = response.name
comment.append(name)
var date = document.createElement('div')
date.classList.add('date')
date.innerText = response.date
comment.append(date)
var text = document.createElement('div')
text.classList.add('name')
text.innerText = response.text
comment.append(text)
document.querySelector('.comments').append(comment)
comment.innerHTML = `
<div class="text"></div>
<div class="meta">
by <span class="name"></span>
on
<span class="date"></span>
</div>
`
comment.querySelector('.name').innerText = response.name
comment.querySelector('.date').innerText = response.date
comment.querySelector('.text').innerText = response.text
document.querySelector('.comments .comments-content').append(comment)
document.querySelector('.add-comment textarea').value = ''
})
})

View file

@ -30,16 +30,17 @@ function renderItem(data) {
<div class="player">
<div class="video"></div>
</div>
<div class="value">${data.value}</div>
<div class="value">
${data.value}
<div class="comments"></div>
</div>
<div class="more">
<a href="${data.link}">Open on ${data.site}</a>
</div>
`
var comments = div.querySelector('.comments')
if (window.renderComments) {
renderComments(comments, data)
renderComments(div.querySelector('.comments'), data)
} else {
comments.remove()
}

View file

@ -1,25 +1,7 @@
{% extends "base.html" %}
{% block content %}
{% for item in items %}
<div class="item">
<a href="{{ item.url }}">
<h1>{{ item.title }}</h1>
<figure>
<img src={{ item.icon }}>
<figcaption>
{{ item.data.title }}
{% if item.data.description %}
<br>
{{ item.data.description }}
{% endif %}
</figcaption>
</figure>
</a>
<div class="description">
{{ item.description | safe}}
</div>
<a href="{{ item.get_absolute_url }}">{{ item.public_comments.count }} comments</a>
</div>
{% include "listitem.html" with item=item %}
{% endfor %}
{% endblock %}

View file

@ -1,7 +1,22 @@
<!DOCTYPE html>
<!DOCTYPE html>{% load static sass_tags compress %}
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% compress css file site %}
<link rel="stylesheet" href="/static/css/reset.css"></link>
<link rel="stylesheet" href="{% sass_src 'css/site.scss' %}"></link>
<link rel="stylesheet" href="/static/css/style.css"></link>
<link rel="stylesheet" href="{% sass_src 'css/comments.scss' %}"></link>
{% endcompress %}
{% block head %}
{% endblock %}
</head>
<body>
{% block header %}
<header>
<a href="/">phantas.ma</a>
</header>
{% endblock %}
{% block main %}
<div class="content">
@ -11,5 +26,7 @@
{% endblock %}
{% block end %}
{% endblock %}
{% block footer %}
{% endblock %}
</body>
</html>

View file

@ -1,44 +1,15 @@
{% extends "base.html" %}
{% block content %}
<style>
img {
max-width: 100%;
}
h1 {
margin: 0;
padding: 0;
}
figure {
margin: 0;
padding: 0;
}
.content {
max-width: 1000px;
margin: auto;
}
</style>
<div class="index">
{% for item in items %}
<div class="item">
<a href="{{ item.get_absolute_url }}">
<h1>{{ item.title }}</h1>
<figure>
<img src={{ item.icon }}>
<figcaption>
{{ item.data.title }}
{% if item.data.description %}
<br>
{{ item.data.description }}
{% endif %}
</figcaption>
</figure>
</a>
<div class="description">
{{ item.description | safe }}
</div>
<a href="{{ item.get_absolute_url }}">{{ item.public_comments.count }} comments</a>
</div>
{% include "listitem.html" with item=item %}
{% endfor %}
{% if archive %}
<div class="archive">
<a href="/archive/">older items</a>
</div>
{% endif %}
</div>
{% endblock %}

View file

@ -1,27 +1,36 @@
{% extends "base.html" %}
{% load static sass_tags compress %}
{% block head %}
<link rel="stylesheet" href="/static/css/reset.css"></link>
<link rel="stylesheet" href="/static/css/style.css"></link>
<link rel="stylesheet" href="/static/css/comments.css"></link>
{% endblock %}
{% block main %}
<div class="content">
</div>
<div class="add-comment" style="display: none">
{% if request.user.is_anonymous %}
{% csrf_token %}
<textarea name="text" placeholder="your comment"></textarea>
{% if false and request.user.is_anonymous %}
<input name="name" type="text" placeholder="your name"></input>
<input name="email" type="email" placeholder="your email"></input>
<br>
{% endif %}
{% csrf_token %}
<textarea name="text" placeholder="your comment"></textarea>
<button id="add-comment">Add comment</button>
<div class="buttons">
<button id="add-comment">Add comment as guest</button>
<button>Login</button>
</div>
<div class="buttons login">
<input name="username" type="text" placeholder="your username"></input>
<input name="password" type="password" placeholder="your password"></input>
<button>Login</button>
<button>Register</button>
</div>
</div>
{% endblock %}
{% block end %}
<script>
var comments = {{ item.public_comments_json|safe }};
</script>
{% compress js file m %}
<script src="/static/js/utils.js"></script>
<script src="/static/js/api.js"></script>
<script src="/static/js/icons.js"></script>
@ -31,6 +40,7 @@
<script src="/static/js/edits.js"></script>
<script src="/static/js/item.js"></script>
<script src="/static/js/render.js"></script>
{% endcompress %}
<script>
pandora.url = new URL('{{ item.url|escapejs }}');

View file

@ -0,0 +1,15 @@
<div class="item">
<a href="{{ item.get_absolute_url }}">
<h1>{{ item.title }}</h1>
<div class="info">
{{ item.data.title }}
</div>
<div class="image">
<img src={{ item.data.thumbnail }}>
</div>
</a>
<div class="comments">
<a href="{{ item.get_absolute_url }}">{{ item.public_comments.count }} comments</a>
</div>
</div>

View file

@ -1 +1,7 @@
django
libsass
django-compressor
django-sass-processor
requests
lxml
cssselect