Eluza133 commited on
Commit
4e2652c
·
verified ·
1 Parent(s): 742e853

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +214 -92
app.py CHANGED
@@ -119,7 +119,7 @@ def update_last_seen(data, username):
119
  data['users'][username]['last_seen'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
120
  save_data(data)
121
 
122
- # Новый стиль 2025 года
123
  BASE_STYLE = '''
124
  :root {
125
  --primary: #ff3b8e;
@@ -264,11 +264,13 @@ body {
264
 
265
  .container {
266
  margin: 2rem 2rem 2rem 340px;
267
- padding: 2rem;
268
  background: var(--surface);
269
  border-radius: 20px;
270
  box-shadow: var(--shadow);
271
  transition: var(--transition);
 
 
272
  }
273
 
274
  .btn {
@@ -376,9 +378,141 @@ input:focus, textarea:focus, select:focus {
376
  display: block;
377
  }
378
  .container {
379
- margin: 5rem 1rem 1rem;
380
  }
381
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
  '''
383
 
384
  NAV_HTML = '''
@@ -671,9 +805,21 @@ def feed():
671
  user_count = len(data['users'])
672
  is_online = is_user_online(data, username) if is_authenticated else False
673
 
674
- search_query = request.form.get('search', '').strip().lower() if request.method == 'POST' else request.args.get('search', '').strip().lower()
675
- if search_query:
676
- posts = [post for post in posts if search_query in post['title'].lower() or search_query in post['description'].lower()]
 
 
 
 
 
 
 
 
 
 
 
 
677
 
678
  html = '''
679
  <!DOCTYPE html>
@@ -686,66 +832,6 @@ def feed():
686
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
687
  <style>
688
  ''' + BASE_STYLE + '''
689
- h1 {
690
- font-size: 2.5rem;
691
- background: var(--gradient);
692
- -webkit-background-clip: text;
693
- color: transparent;
694
- margin-bottom: 2rem;
695
- }
696
- .search-container {
697
- margin-bottom: 2rem;
698
- display: flex;
699
- gap: 1rem;
700
- }
701
- .search-input {
702
- flex-grow: 1;
703
- box-shadow: var(--shadow);
704
- }
705
- .post-grid {
706
- display: grid;
707
- grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
708
- gap: 1.5rem;
709
- }
710
- .post-item {
711
- background: var(--glass);
712
- padding: 1.5rem;
713
- border-radius: 20px;
714
- box-shadow: var(--shadow);
715
- transition: var(--transition);
716
- backdrop-filter: blur(10px);
717
- }
718
- .post-item:hover {
719
- transform: translateY(-5px) scale(1.02);
720
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
721
- }
722
- .post-preview {
723
- width: 100%;
724
- height: 220px;
725
- object-fit: cover;
726
- border-radius: 15px;
727
- transition: var(--transition);
728
- }
729
- .post-item h2 {
730
- font-size: 1.4rem;
731
- margin: 1rem 0 0.5rem;
732
- }
733
- .post-item p {
734
- font-size: 0.9rem;
735
- opacity: 0.8;
736
- }
737
- .stats {
738
- font-size: 0.85rem;
739
- opacity: 0.6;
740
- }
741
- .username-link {
742
- color: var(--primary);
743
- text-decoration: none;
744
- font-weight: 600;
745
- }
746
- .username-link:hover {
747
- color: var(--accent);
748
- }
749
  </style>
750
  </head>
751
  <body>
@@ -753,28 +839,54 @@ def feed():
753
  ''' + NAV_HTML + '''
754
  <button class="theme-toggle" onclick="toggleTheme()">🌙</button>
755
  <div class="container">
756
- <h1>Content Feed</h1>
757
- <div class="search-container">
758
- <form method="POST" class="search-form">
759
- <input type="text" name="search" class="search-input" placeholder="Search posts..." value="{{ search_query }}">
760
- <button type="submit" class="btn">🔍</button>
761
- </form>
762
- </div>
763
- <div class="post-grid">
764
  {% for post in posts %}
765
- <a href="{{ url_for('post_page', post_id=post['id']) }}" class="post-item">
766
  {% if post['type'] == 'video' %}
767
- <video class="post-preview" preload="metadata" muted>
768
  <source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" type="video/mp4">
769
  </video>
770
  {% else %}
771
- <img class="post-preview" src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" alt="{{ post['title'] }}" onclick="openModal(this.src, event, '{{ post['id'] }}')">
772
  {% endif %}
773
- <h2>{{ post['title'] }}</h2>
774
- <p>{{ post['description']|truncate(100) }}</p>
775
- <p>By: <a href="{{ url_for('user_profile', username=post['uploader']) }}" class="username-link">{{ post['uploader'] }}</a> <span class="status-dot {{ 'online' if is_user_online(post['uploader']) else 'offline' }}"></span> | {{ post['upload_date'] }}</p>
776
- <p class="stats">Views: {{ post['views'] }} | Likes: {{ post['likes']|length }}</p>
777
- </a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
778
  {% endfor %}
779
  </div>
780
  </div>
@@ -787,30 +899,40 @@ def feed():
787
  document.body.classList.toggle('dark');
788
  localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
789
  }
790
- function openModal(src, event, postId) {
791
- event.preventDefault();
 
 
 
 
 
 
 
792
  document.getElementById('imageModal').style.display = 'flex';
793
  document.getElementById('modalImage').src = src;
794
- fetch('/increment_view/' + postId, { method: 'POST' });
795
  }
796
  function closeModal(event) {
797
  if (event.target.tagName !== 'IMG') document.getElementById('imageModal').style.display = 'none';
798
  }
799
  window.onload = () => {
800
  if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
801
- document.querySelectorAll('.post-preview').forEach(video => {
802
- if (video.tagName === 'VIDEO') {
803
- video.addEventListener('loadedmetadata', () => {
804
- video.currentTime = Math.random() * video.duration;
805
- });
806
- }
 
 
 
 
807
  });
808
  };
809
  </script>
810
  </body>
811
  </html>
812
  '''
813
- return render_template_string(html, posts=posts, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query, unread_count=unread_count, user_count=user_count, private_unread_count=private_unread_count, is_online=is_online, is_user_online=lambda u: is_user_online(data, u))
814
 
815
  @app.route('/post/<post_id>', methods=['GET', 'POST'])
816
  def post_page(post_id):
 
119
  data['users'][username]['last_seen'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
120
  save_data(data)
121
 
122
+ # Новый стиль 2025 года с изменениями для TikTok-подобной ленты
123
  BASE_STYLE = '''
124
  :root {
125
  --primary: #ff3b8e;
 
264
 
265
  .container {
266
  margin: 2rem 2rem 2rem 340px;
267
+ padding: 0;
268
  background: var(--surface);
269
  border-radius: 20px;
270
  box-shadow: var(--shadow);
271
  transition: var(--transition);
272
+ height: calc(100vh - 4rem);
273
+ overflow: hidden;
274
  }
275
 
276
  .btn {
 
378
  display: block;
379
  }
380
  .container {
381
+ margin: 2rem 1rem;
382
  }
383
  }
384
+
385
+ /* Стили для TikTok-подобной ленты */
386
+ .feed-container {
387
+ height: 100%;
388
+ overflow-y: scroll;
389
+ scroll-snap-type: y mandatory;
390
+ -webkit-overflow-scrolling: touch;
391
+ }
392
+
393
+ .post-slide {
394
+ height: 100vh;
395
+ scroll-snap-align: start;
396
+ position: relative;
397
+ display: flex;
398
+ flex-direction: column;
399
+ justify-content: center;
400
+ align-items: center;
401
+ background: var(--glass);
402
+ }
403
+
404
+ .post-media {
405
+ width: 100%;
406
+ height: 80%;
407
+ object-fit: contain;
408
+ border-radius: 20px;
409
+ box-shadow: var(--shadow);
410
+ }
411
+
412
+ .post-overlay {
413
+ position: absolute;
414
+ bottom: 0;
415
+ left: 0;
416
+ width: 100%;
417
+ padding: 1rem;
418
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent);
419
+ }
420
+
421
+ .post-info {
422
+ padding: 1rem;
423
+ width: 100%;
424
+ }
425
+
426
+ .post-info h2 {
427
+ font-size: 1.5rem;
428
+ margin-bottom: 0.5rem;
429
+ }
430
+
431
+ .post-info p {
432
+ font-size: 1rem;
433
+ opacity: 0.8;
434
+ }
435
+
436
+ .actions {
437
+ position: absolute;
438
+ right: 1rem;
439
+ top: 50%;
440
+ transform: translateY(-50%);
441
+ display: flex;
442
+ flex-direction: column;
443
+ gap: 1rem;
444
+ align-items: center;
445
+ }
446
+
447
+ .action-btn {
448
+ background: var(--glass);
449
+ border: none;
450
+ border-radius: 50%;
451
+ width: 50px;
452
+ height: 50px;
453
+ display: flex;
454
+ align-items: center;
455
+ justify-content: center;
456
+ cursor: pointer;
457
+ transition: var(--transition);
458
+ backdrop-filter: blur(10px);
459
+ }
460
+
461
+ .action-btn:hover {
462
+ background: var(--primary);
463
+ transform: scale(1.1);
464
+ }
465
+
466
+ .action-btn.liked {
467
+ background: var(--secondary);
468
+ }
469
+
470
+ .action-count {
471
+ font-size: 0.9rem;
472
+ margin-top: 0.3rem;
473
+ }
474
+
475
+ .comments-container {
476
+ position: absolute;
477
+ right: 0;
478
+ top: 0;
479
+ height: 100%;
480
+ width: 350px;
481
+ background: rgba(0, 0, 0, 0.9);
482
+ padding: 1rem;
483
+ overflow-y: auto;
484
+ transform: translateX(100%);
485
+ transition: var(--transition);
486
+ z-index: 10;
487
+ }
488
+
489
+ .comments-container.active {
490
+ transform: translateX(0);
491
+ }
492
+
493
+ .comment {
494
+ background: var(--glass);
495
+ padding: 0.8rem;
496
+ border-radius: 10px;
497
+ margin-bottom: 0.8rem;
498
+ }
499
+
500
+ .comment-form {
501
+ position: sticky;
502
+ bottom: 0;
503
+ background: rgba(0, 0, 0, 0.9);
504
+ padding: 1rem;
505
+ }
506
+
507
+ .username-link {
508
+ color: var(--primary);
509
+ text-decoration: none;
510
+ font-weight: 600;
511
+ }
512
+
513
+ .username-link:hover {
514
+ color: var(--accent);
515
+ }
516
  '''
517
 
518
  NAV_HTML = '''
 
805
  user_count = len(data['users'])
806
  is_online = is_user_online(data, username) if is_authenticated else False
807
 
808
+ if request.method == 'POST' and is_authenticated:
809
+ post_id = request.form.get('post_id')
810
+ action = request.form.get('action')
811
+ post = next((p for p in data['posts'] if p['id'] == post_id), None)
812
+ if post:
813
+ if action == 'like':
814
+ if username not in post.get('likes', []):
815
+ post['likes'] = post.get('likes', []) + [username]
816
+ else:
817
+ post['likes'] = [user for user in post.get('likes', []) if user != username]
818
+ elif action == 'comment':
819
+ comment_text = request.form.get('comment')
820
+ if comment_text:
821
+ post['comments'] = post.get('comments', []) + [{'user': username, 'text': comment_text, 'date': datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]
822
+ save_data(data)
823
 
824
  html = '''
825
  <!DOCTYPE html>
 
832
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
833
  <style>
834
  ''' + BASE_STYLE + '''
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
835
  </style>
836
  </head>
837
  <body>
 
839
  ''' + NAV_HTML + '''
840
  <button class="theme-toggle" onclick="toggleTheme()">🌙</button>
841
  <div class="container">
842
+ <div class="feed-container">
 
 
 
 
 
 
 
843
  {% for post in posts %}
844
+ <div class="post-slide" id="post-{{ post['id'] }}">
845
  {% if post['type'] == 'video' %}
846
+ <video class="post-media" controls autoplay muted loop>
847
  <source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" type="video/mp4">
848
  </video>
849
  {% else %}
850
+ <img class="post-media" src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" alt="{{ post['title'] }}">
851
  {% endif %}
852
+ <div class="post-overlay">
853
+ <div class="post-info">
854
+ <h2>{{ post['title'] }}</h2>
855
+ <p>{{ post['description']|truncate(100) }}</p>
856
+ <p>By: <a href="{{ url_for('user_profile', username=post['uploader']) }}" class="username-link">{{ post['uploader'] }}</a>
857
+ <span class="status-dot {{ 'online' if is_user_online(post['uploader']) else 'offline' }}"></span> | {{ post['upload_date'] }}</p>
858
+ </div>
859
+ </div>
860
+ <div class="actions">
861
+ <form method="POST" style="display: inline;">
862
+ <input type="hidden" name="post_id" value="{{ post['id'] }}">
863
+ <input type="hidden" name="action" value="like">
864
+ <button type="submit" class="action-btn {% if is_authenticated and username in post.get('likes', []) %}liked{% endif %}" {% if not is_authenticated %}disabled{% endif %}>
865
+ ❤️
866
+ </button>
867
+ </form>
868
+ <span class="action-count">{{ post.get('likes', [])|length }}</span>
869
+ <button class="action-btn" onclick="toggleComments('{{ post['id'] }}')">💬</button>
870
+ <span class="action-count">{{ post.get('comments', [])|length }}</span>
871
+ <button class="action-btn" onclick="sharePost('{{ post['id'] }}')">↪️</button>
872
+ </div>
873
+ <div class="comments-container" id="comments-{{ post['id'] }}">
874
+ {% for comment in post.get('comments', []) %}
875
+ <div class="comment">
876
+ <p><strong><a href="{{ url_for('user_profile', username=comment['user']) }}" class="username-link">{{ comment['user'] }}</a></strong>
877
+ <span class="status-dot {{ 'online' if is_user_online(comment['user']) else 'offline' }}"></span> ({{ comment['date'] }}): {{ comment['text'] }}</p>
878
+ </div>
879
+ {% endfor %}
880
+ {% if is_authenticated %}
881
+ <form method="POST" class="comment-form">
882
+ <input type="hidden" name="post_id" value="{{ post['id'] }}">
883
+ <input type="hidden" name="action" value="comment">
884
+ <textarea name="comment" placeholder="Add a comment..." rows="2"></textarea>
885
+ <button type="submit" class="btn">Send</button>
886
+ </form>
887
+ {% endif %}
888
+ </div>
889
+ </div>
890
  {% endfor %}
891
  </div>
892
  </div>
 
899
  document.body.classList.toggle('dark');
900
  localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
901
  }
902
+ function toggleComments(postId) {
903
+ const comments = document.getElementById(`comments-${postId}`);
904
+ comments.classList.toggle('active');
905
+ }
906
+ function sharePost(postId) {
907
+ const url = `${window.location.origin}/post/${postId}`;
908
+ navigator.clipboard.writeText(url).then(() => alert('Link copied!'));
909
+ }
910
+ function openModal(src) {
911
  document.getElementById('imageModal').style.display = 'flex';
912
  document.getElementById('modalImage').src = src;
 
913
  }
914
  function closeModal(event) {
915
  if (event.target.tagName !== 'IMG') document.getElementById('imageModal').style.display = 'none';
916
  }
917
  window.onload = () => {
918
  if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
919
+ const feed = document.querySelector('.feed-container');
920
+ feed.addEventListener('scroll', () => {
921
+ const slides = document.querySelectorAll('.post-slide');
922
+ slides.forEach(slide => {
923
+ const rect = slide.getBoundingClientRect();
924
+ if (rect.top >= 0 && rect.bottom <= window.innerHeight) {
925
+ const postId = slide.id.split('-')[1];
926
+ fetch('/increment_view/' + postId, { method: 'POST' });
927
+ }
928
+ });
929
  });
930
  };
931
  </script>
932
  </body>
933
  </html>
934
  '''
935
+ return render_template_string(html, posts=posts, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, unread_count=unread_count, user_count=user_count, private_unread_count=private_unread_count, is_online=is_online, is_user_online=lambda u: is_user_online(data, u))
936
 
937
  @app.route('/post/<post_id>', methods=['GET', 'POST'])
938
  def post_page(post_id):