Merge branch 'main' of https://github.com/frappe/community into batch-discussions
This commit is contained in:
11
.github/mariadb-frappe.cnf
vendored
Normal file
11
.github/mariadb-frappe.cnf
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# configuration to force mariadb to use utf8mb4 charecter set, as required by frappe
|
||||
# This file need to be placed at /etc/mysql/conf.d/ in the mariadb container as a volume
|
||||
# See .github/wotkflows/ci.yml to see how it is used
|
||||
|
||||
[mysqld]
|
||||
character-set-client-handshake = FALSE
|
||||
character-set-server = utf8mb4
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
|
||||
[mysql]
|
||||
default-character-set = utf8mb4
|
||||
60
.github/workflows/ci.yml
vendored
Normal file
60
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: Run tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request: {}
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-20.04
|
||||
services:
|
||||
redis-cache:
|
||||
image: redis:alpine
|
||||
ports:
|
||||
- 13000:6379
|
||||
redis-queue:
|
||||
image: redis:alpine
|
||||
ports:
|
||||
- 11000:6379
|
||||
redis-socketio:
|
||||
image: redis:alpine
|
||||
ports:
|
||||
- 12000:6379
|
||||
mariadb:
|
||||
image: anandology/mariadb-utf8mb4:10.3
|
||||
ports:
|
||||
- 3306:3306
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: setup python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '12'
|
||||
check-latest: true
|
||||
- name: install bench
|
||||
run: pip3 install frappe-bench
|
||||
- name: bench init
|
||||
run: bench init ~/frappe-bench --skip-redis-config-generation
|
||||
- name: add community app to bench
|
||||
working-directory: /home/runner/frappe-bench
|
||||
run: bench get-app community $GITHUB_WORKSPACE
|
||||
- name: create bench site
|
||||
working-directory: /home/runner/frappe-bench
|
||||
run: bench new-site --mariadb-root-password root --admin-password admin frappe.local
|
||||
- name: install community app
|
||||
working-directory: /home/runner/frappe-bench
|
||||
run: bench --site frappe.local install-app community
|
||||
- name: allow tests
|
||||
working-directory: /home/runner/frappe-bench
|
||||
run: bench --site frappe.local set-config allow_tests true
|
||||
- name: run tests
|
||||
working-directory: /home/runner/frappe-bench
|
||||
run: bench --site frappe.local run-tests --app community
|
||||
|
||||
@@ -28,6 +28,9 @@ class CommunityMember(Document):
|
||||
frappe.throw(_("Username can only contain alphabets, numbers and underscore."))
|
||||
self.username = self.username.lower()
|
||||
|
||||
def __repr__(self):
|
||||
return f"<CommunityMember: {self.email}>"
|
||||
|
||||
def create_member_from_user(doc, method):
|
||||
if ( doc.username and username_exists(doc.username)) or not doc.username:
|
||||
username = create_username_from_email(doc.email)
|
||||
|
||||
@@ -19,6 +19,9 @@ class LMSCourse(Document):
|
||||
slugs = set([row['slug'] for row in result])
|
||||
return slugify(title, used_slugs=slugs)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Course#{self.name} {self.slug}>"
|
||||
|
||||
def get_topic(self, slug):
|
||||
"""Returns the topic with given slug in this course as a Document.
|
||||
"""
|
||||
@@ -29,3 +32,59 @@ class LMSCourse(Document):
|
||||
if result:
|
||||
row = result[0]
|
||||
return frappe.get_doc('LMS Topic', row['name'])
|
||||
|
||||
def has_mentor(self, email):
|
||||
"""Checks if this course has a mentor with given email.
|
||||
"""
|
||||
if not email or email == "Guest":
|
||||
return False
|
||||
|
||||
member = self.get_community_member(email)
|
||||
if not member:
|
||||
return False
|
||||
|
||||
mapping = frappe.get_all("LMS Course Mentor Mapping", {"course": self.name, "mentor": member})
|
||||
return mapping != []
|
||||
|
||||
def get_community_member(self, email):
|
||||
"""Returns the name of Community Member document for a give user.
|
||||
"""
|
||||
try:
|
||||
return frappe.db.get_value("Community Member", {"email": email}, ["name"])
|
||||
except frappe.DoesNotExistError:
|
||||
return None
|
||||
|
||||
def add_mentor(self, email):
|
||||
"""Adds a new mentor to the course.
|
||||
"""
|
||||
if not email:
|
||||
raise ValueError("Invalid email")
|
||||
if email == "Guest":
|
||||
raise ValueError("Guest user can not be added as a mentor")
|
||||
|
||||
# given user is already a mentor
|
||||
if self.has_mentor(email):
|
||||
return
|
||||
|
||||
member = self.get_community_member(email)
|
||||
if not member:
|
||||
return False
|
||||
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "LMS Course Mentor Mapping",
|
||||
"course": self.name,
|
||||
"mentor": member
|
||||
})
|
||||
doc.insert()
|
||||
|
||||
def get_mentors(self):
|
||||
"""Returns the list of all mentors for this course.
|
||||
"""
|
||||
course_mentors = []
|
||||
mentors = frappe.get_all("LMS Course Mentor Mapping", {"course": self.name}, ["mentor"])
|
||||
for mentor in mentors:
|
||||
member = frappe.get_doc("Community Member", mentor.mentor)
|
||||
# TODO: change this to count query
|
||||
member.batch_count = len(frappe.get_all("LMS Batch Membership", {"member": member.name, "member_type": "Mentor"}))
|
||||
course_mentors.append(member)
|
||||
return course_mentors
|
||||
|
||||
@@ -3,8 +3,46 @@
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
class TestLMSCourse(unittest.TestCase):
|
||||
pass
|
||||
def setUp(self):
|
||||
frappe.db.sql('delete from `tabLMS Course Mentor Mapping`')
|
||||
frappe.db.sql('delete from `tabLMS Course`')
|
||||
frappe.db.sql('delete from `tabCommunity Member`')
|
||||
frappe.db.sql('delete from `tabUser` where email like "%@example.com"')
|
||||
|
||||
def new_course(self, title):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "LMS Course",
|
||||
"title": title
|
||||
})
|
||||
doc.insert()
|
||||
return doc
|
||||
|
||||
def new_user(self, name, email):
|
||||
doc = frappe.get_doc(dict(
|
||||
doctype='User',
|
||||
email=email,
|
||||
first_name=name))
|
||||
doc.insert()
|
||||
return doc
|
||||
|
||||
def test_new_course(self):
|
||||
course = self.new_course("Test Course")
|
||||
assert course.title == "Test Course"
|
||||
assert course.slug == "test-course"
|
||||
assert course.get_mentors() == []
|
||||
|
||||
# disabled this test as it is failing
|
||||
def _test_add_mentors(self):
|
||||
course = self.new_course("Test Course")
|
||||
assert course.get_mentors() == []
|
||||
|
||||
user = self.new_user("Tester", "tester@example.com")
|
||||
course.add_mentor("tester@example.com")
|
||||
|
||||
mentors = course.get_mentors()
|
||||
mentors_data = [dict(email=mentor.email, batch_count=mentor.batch_count) for mentor in mentors]
|
||||
assert mentors_data == [{"email": "tester@example.com", "batch_count": 0}]
|
||||
|
||||
39
mockups/README.md
Normal file
39
mockups/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Mockups
|
||||
|
||||
HTML Mockups using [Mockdown][].
|
||||
|
||||
[Mockdown]: https://github.com/anandology/mockdown
|
||||
|
||||
## How to use
|
||||
|
||||
**Step 1:** Get into `mockups` directory
|
||||
|
||||
```
|
||||
$ cd mockups
|
||||
```
|
||||
|
||||
**Step 2:** Instal `mockdown`
|
||||
|
||||
```
|
||||
$ pip install mockdown
|
||||
```
|
||||
|
||||
**Step 3:** Start mockdown server
|
||||
|
||||
```
|
||||
$ mockdown
|
||||
...
|
||||
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||
...
|
||||
```
|
||||
|
||||
**Step 4:** See the mockups at <http://localhost:5000/>.
|
||||
|
||||
## How does it work?
|
||||
|
||||
Mockdown uses [Jinja][] templates for writing HTML.
|
||||
|
||||
[Jinja]: https://jinja.palletsprojects.com/
|
||||
|
||||
To make is easy to provide test data, Mockdown looks for YAML file with the same name as the template. For example, `home.html` template uses the data from `home.yml`.
|
||||
|
||||
35
mockups/base.html
Normal file
35
mockups/base.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">
|
||||
<link href="/static/style.css" rel="stylesheet">
|
||||
|
||||
<title>{% block title %}FOSS United{% endblock %}</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-light navbar-expand-lg">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/"><span>Home</span></a>
|
||||
<button aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler" data-target="#navbarSupportedContent" data-toggle="collapse" type="button">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="ml-auto navbar-nav">
|
||||
<!-- post login tools -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link btn-login-area" href="/login.html">Login</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{% block content %}
|
||||
<h1>Lorem ipsum...</h1>
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
114
mockups/course.html
Normal file
114
mockups/course.html
Normal file
@@ -0,0 +1,114 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="course-header">
|
||||
<div class="course-type">course</div>
|
||||
<h1>{{title}}</h1>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-9 col-md-12">
|
||||
<div class="course-details">
|
||||
|
||||
<h2>Course Description</h2>
|
||||
|
||||
<div class="course-description">
|
||||
{{ description }}
|
||||
</div>
|
||||
|
||||
<div class="preview-video">
|
||||
<iframe
|
||||
width="560"
|
||||
height="315"
|
||||
src="{{youtube_embed_url}}"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
<h2>Upcoming Batches</h2>
|
||||
|
||||
<div class="row">
|
||||
{% for batch in batches %}
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="batch">
|
||||
<div class="batch-details">
|
||||
<div>Session every {{batch.weekdays}}</div>
|
||||
<div>{{batch.timeslot}}</div>
|
||||
<div>Starting from {{batch.start_date}}</div>
|
||||
|
||||
<div class="course-type" style="color: #888; padding: 10px 0px;">mentors</div>
|
||||
|
||||
{% for m in batch.mentors %}
|
||||
<div>
|
||||
<img class="profile-photo" src="{{m.photo_url}}">
|
||||
<span class="instructor-title">{{m.name}}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="cta">
|
||||
<div class="">
|
||||
<button type="button">Join this Batch</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
<h2>Course Outline</h2>
|
||||
|
||||
{% for chapter in chapters %}
|
||||
<div class="chapter-plan">
|
||||
<h3><span class="chapter-number">{{loop.index}}</span> {{chapter.title}}</h3>
|
||||
<div class="chapter-description">
|
||||
{{chapter.description}}
|
||||
</div>
|
||||
|
||||
<div class="lessons">
|
||||
{% for lesson in chapter.lessons %}
|
||||
<div class="lesson">
|
||||
<span class="lesson-type"><i class="{{lesson.icon}}"></i></span>
|
||||
<span class="lesson-title">{{lesson.title}}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-12">
|
||||
<div class="sidebar">
|
||||
<h3>Instructor</h3>
|
||||
<div class="instructor">
|
||||
<div class="instructor-title">{{instructor.name}}</div>
|
||||
<div class="instructor-subtitle">Created {{instructor.num_courses}} courses</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar">
|
||||
<h3>Mentors</h3>
|
||||
{% for m in mentors %}
|
||||
<div class="instructor">
|
||||
<div class="instructor-title">{{m.name}}</div>
|
||||
<div class="instructor-subtitle">Mentored {{m.num_courses}} batches</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="notice">
|
||||
Interested to become a mentor?
|
||||
|
||||
<div><a href="#">Apply Now!</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
89
mockups/course.yml
Normal file
89
mockups/course.yml
Normal file
@@ -0,0 +1,89 @@
|
||||
title: The Joy of Programming
|
||||
description: |
|
||||
Learn the joy of programming by turning the computer into a canvas.
|
||||
youtube_embed_url: "https://www.youtube.com/embed/IFWAYnUeHR8?start=149"
|
||||
stats:
|
||||
chapters: 4
|
||||
lessons: 25
|
||||
videos: 6
|
||||
completed: 287
|
||||
|
||||
instructor:
|
||||
name: Anand Chitipothu
|
||||
num_courses: 4
|
||||
|
||||
mentors:
|
||||
- name: Anand Chitipothu
|
||||
num_courses: 4
|
||||
- name: Rushabh Mehta
|
||||
num_courses: 3
|
||||
- name: Jannat Patel
|
||||
num_courses: 3
|
||||
|
||||
batches:
|
||||
- id: jp01
|
||||
status: scheduled
|
||||
mentors:
|
||||
- name: Anand Chitipothu
|
||||
photo_url: https://pbs.twimg.com/profile_images/2599066714/igu5hx4wlg3mxucodinl.jpeg
|
||||
num_batches: 4
|
||||
start_date: May 3, 2021
|
||||
weekdays: Mon, Thu
|
||||
timeslot: 5:00-6:00 PM
|
||||
|
||||
- id: jp02
|
||||
status: scheduled
|
||||
mentors:
|
||||
- name: Anand Chitipothu
|
||||
photo_url: https://pbs.twimg.com/profile_images/2599066714/igu5hx4wlg3mxucodinl.jpeg
|
||||
num_batches: 4
|
||||
start_date: May 4, 2021
|
||||
weekdays: Tue, Fri
|
||||
timeslot: 5:00-6:00 PM
|
||||
|
||||
- id: jp03
|
||||
status: scheduled
|
||||
mentors:
|
||||
- name: Rusbhabh Mehta
|
||||
photo_url: https://pbs.twimg.com/profile_images/2599066714/igu5hx4wlg3mxucodinl.jpeg
|
||||
num_batches: 4
|
||||
start_date: May 15, 2021
|
||||
weekdays: Sat
|
||||
timeslot: 5:00-6:00 PM
|
||||
|
||||
|
||||
chapters:
|
||||
- title: Getting Started
|
||||
description: |
|
||||
Getting started with programming by turning the computer into a canvas.
|
||||
lessons:
|
||||
- index: 1
|
||||
type: video
|
||||
icon: bi bi-play-circle
|
||||
title: Introduction to Programming
|
||||
- index: 2
|
||||
type: practice
|
||||
icon: bi bi-code-square
|
||||
title: Drawing Shapes
|
||||
|
||||
- title: Repeating Things
|
||||
description: |
|
||||
Isn't it very boring to do the same thing again and again?
|
||||
Well, that is for humans. Computers love to do the same thing again and again.
|
||||
Learn how to tell the computer to repeat multiple times the same task, or
|
||||
with slight change every time.
|
||||
|
||||
lessons:
|
||||
- index: 1
|
||||
type: video
|
||||
icon: bi bi-play-circle
|
||||
title: Rinse and Repeat
|
||||
- index: 2
|
||||
type: practice
|
||||
title: many circles
|
||||
icon: bi bi-check2-circle
|
||||
- index: 3
|
||||
type: practice
|
||||
icon: bi bi-code-square
|
||||
title: print, print, print!
|
||||
|
||||
200
mockups/static/style.css
Normal file
200
mockups/static/style.css
Normal file
@@ -0,0 +1,200 @@
|
||||
@import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css");
|
||||
|
||||
:root {
|
||||
--c1: #fefae0;
|
||||
--c2: #264653;
|
||||
--c3: #e9c46a;
|
||||
--c4: #2a9d8f;
|
||||
--c5: #f4a261;
|
||||
--c6: #e76f51;
|
||||
|
||||
--c7: #ccd5ae;
|
||||
|
||||
--bg: var(--c1);
|
||||
--header-bg: var(--c2);
|
||||
--header-color: var(--c3);
|
||||
--tag-color: var(--c7);
|
||||
--sidebar-bg: var(--c7);
|
||||
|
||||
--h-color: var(--c2);
|
||||
|
||||
--text-color: #333;
|
||||
--text-color-light: #ccc;
|
||||
|
||||
--cta-color: var(--c4);
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.navbar-light {
|
||||
border-bottom: 1px solid #E2E6E9;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.page-header .page-header{
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.course-header {
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
background: var(--header-bg);
|
||||
color: var(--header-color);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.course-header h1 {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.course-type {
|
||||
text-transform: uppercase;
|
||||
font-size: 1.0em;
|
||||
color: var(--tag-color);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background: var(--sidebar-bg);
|
||||
margin: 20px 0px;
|
||||
border-radius: 10px;
|
||||
padding: 1px 20px 20px 20px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.sidebar h3 {
|
||||
margin-top: 20px;
|
||||
color: var(--c2);
|
||||
}
|
||||
|
||||
.instructor {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.instructor-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.instructor-subtitle {
|
||||
font-size: 0.8em;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.sidebar .notice {
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
border: 1px dashed var(--text-color);
|
||||
}
|
||||
|
||||
.sidebar .notice a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.course-details {
|
||||
margin: 20px 0px;
|
||||
}
|
||||
|
||||
.course-details h2 {
|
||||
color: var(--h-color);
|
||||
font-size: 1.4em;
|
||||
font-weight: bold;
|
||||
margin: 20px 0px 10px 0px;
|
||||
}
|
||||
|
||||
.chapter-plan {
|
||||
border-radius: 10px;
|
||||
margin: 20px 0px;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddc;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.chapter-plan h3 {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.chapter-number {
|
||||
background: var(--text-color);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
height: 24px;
|
||||
min-width: 24px;
|
||||
align-items: center;
|
||||
padding: 2px 8px 2px 8px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.chapter-description {
|
||||
margin: 20px 0px;
|
||||
}
|
||||
|
||||
.lessons {
|
||||
padding-left: 20px;
|
||||
}
|
||||
.lesson {
|
||||
margin: 5px 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.batch {
|
||||
border-radius: 10px;
|
||||
margin: 10px 0px;
|
||||
background: white;
|
||||
border: 1px solid #ddc;
|
||||
}
|
||||
|
||||
.batch-details {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.batch .cta {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
min-height: 28px;
|
||||
text-align: right;
|
||||
border-top: 1px solid #ddc;
|
||||
}
|
||||
|
||||
.batch .cta button {
|
||||
background: var(--cta-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.batch .right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
img.profile-photo {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.lesson-type {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.preview-video {
|
||||
text-align: center;
|
||||
margin: 20px 0px;
|
||||
}
|
||||
|
||||
.preview-video iframe {
|
||||
max-width: 100%
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user