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): 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_display = ['__str__', 'published']
list_filter = ( list_filter = (
("published", admin.EmptyFieldListFilter), ("published", admin.EmptyFieldListFilter),

View file

@ -1,6 +1,8 @@
from django.utils.timezone import datetime, timedelta from django.utils.timezone import datetime, timedelta
from django.utils import timezone from django.utils import timezone
import json import json
import requests
import lxml.html
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model 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) announced = models.DateTimeField(null=True, default=None, blank=True)
data = models.JSONField(default=dict, editable=False) 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): def __str__(self):
return '%s (%s)' % (self.title, self.url) return '%s (%s)' % (self.title, self.url)
@ -70,6 +76,26 @@ class Item(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('item', kwargs={'id': self.id}) 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): class Comment(models.Model):
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True)
@ -83,6 +109,10 @@ class Comment(models.Model):
data = models.JSONField(default=dict, editable=False) data = models.JSONField(default=dict, editable=False)
published = models.DateTimeField(null=True, default=None) published = models.DateTimeField(null=True, default=None)
@property
def is_published(self):
return bool(self.published)
def __str__(self): def __str__(self):
return '%s: %s' % (self.item, self.user) 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! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
COMPRESS_ENABLED = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = []
@ -38,6 +39,9 @@ INSTALLED_APPS = [
"django.contrib.messages", "django.contrib.messages",
"django.contrib.staticfiles", "django.contrib.staticfiles",
'compressor',
'sass_processor',
"app", "app",
"app.user", "app.user",
"app.item", "app.item",
@ -118,6 +122,13 @@ USE_TZ = True
# https://docs.djangoproject.com/en/4.2/howto/static-files/ # https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = "static/" 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 # Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field # 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; line-height: normal;
} }
*, *::after, *::before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
*:focus {
outline: none;
}
a { a {
color: rgb(128, 128, 255) color: rgb(128, 128, 255)
} }

View file

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

View file

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

View file

@ -1,25 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
{% for item in items %} {% for item in items %}
<div class="item"> {% include "listitem.html" with item=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>
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}

View file

@ -1,7 +1,22 @@
<!DOCTYPE html> <!DOCTYPE html>{% load static sass_tags compress %}
<html>
<head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <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 %} {% block head %}
{% endblock %}
</head>
<body>
{% block header %}
<header>
<a href="/">phantas.ma</a>
</header>
{% endblock %} {% endblock %}
{% block main %} {% block main %}
<div class="content"> <div class="content">
@ -11,5 +26,7 @@
{% endblock %} {% endblock %}
{% block end %} {% block end %}
{% endblock %} {% endblock %}
{% block footer %}
{% endblock %}
</body>
</html>

View file

@ -1,44 +1,15 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<style> <style>
img {
max-width: 100%;
}
h1 {
margin: 0;
padding: 0;
}
figure {
margin: 0;
padding: 0;
}
.content {
max-width: 1000px;
margin: auto;
}
</style> </style>
<div class="index">
{% for item in items %} {% for item in items %}
<div class="item"> {% include "listitem.html" with item=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>
{% endfor %} {% endfor %}
{% if archive %} {% if archive %}
<div class="archive">
<a href="/archive/">older items</a> <a href="/archive/">older items</a>
</div>
{% endif %} {% endif %}
</div>
{% endblock %} {% endblock %}

View file

@ -1,27 +1,36 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static sass_tags compress %}
{% block head %} {% 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 %} {% endblock %}
{% block main %} {% block main %}
<div class="content"> <div class="content">
</div> </div>
<div class="add-comment" style="display: none"> <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="name" type="text" placeholder="your name"></input>
<input name="email" type="email" placeholder="your email"></input> <input name="email" type="email" placeholder="your email"></input>
<br> <br>
{% endif %} {% endif %}
{% csrf_token %} <div class="buttons">
<textarea name="text" placeholder="your comment"></textarea> <button id="add-comment">Add comment as guest</button>
<button id="add-comment">Add comment</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> </div>
{% endblock %} {% endblock %}
{% block end %} {% block end %}
<script> <script>
var comments = {{ item.public_comments_json|safe }}; var comments = {{ item.public_comments_json|safe }};
</script> </script>
{% compress js file m %}
<script src="/static/js/utils.js"></script> <script src="/static/js/utils.js"></script>
<script src="/static/js/api.js"></script> <script src="/static/js/api.js"></script>
<script src="/static/js/icons.js"></script> <script src="/static/js/icons.js"></script>
@ -31,6 +40,7 @@
<script src="/static/js/edits.js"></script> <script src="/static/js/edits.js"></script>
<script src="/static/js/item.js"></script> <script src="/static/js/item.js"></script>
<script src="/static/js/render.js"></script> <script src="/static/js/render.js"></script>
{% endcompress %}
<script> <script>
pandora.url = new URL('{{ item.url|escapejs }}'); 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 django
libsass
django-compressor
django-sass-processor
requests
lxml
cssselect