feat: course list
This commit is contained in:
+17
-7
@@ -142,10 +142,20 @@ def add_mentor_to_subgroup(subgroup, email):
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_courses():
|
||||
"""Returns the list of courses."""
|
||||
return frappe.get_all(
|
||||
"LMS Course",
|
||||
fields=["name", "title", "short_introduction", "image"],
|
||||
filters={"published": True},
|
||||
)
|
||||
def get_user_info(user=None):
|
||||
print(user)
|
||||
if frappe.session.user == "Guest":
|
||||
frappe.throw("Authentication failed", exc=frappe.AuthenticationError)
|
||||
filters = {}
|
||||
if user:
|
||||
filters["name"] = user
|
||||
|
||||
users = frappe.qb.get_query(
|
||||
"User",
|
||||
filters=filters,
|
||||
fields=["name", "email", "enabled", "user_image", "full_name", "user_type"],
|
||||
order_by="full_name asc",
|
||||
distinct=True,
|
||||
).run(as_dict=1)
|
||||
print(users)
|
||||
return users
|
||||
|
||||
+84
-1
@@ -196,7 +196,7 @@ def get_instructors(course):
|
||||
frappe.db.get_value(
|
||||
"User",
|
||||
instructor,
|
||||
["name", "username", "full_name", "user_image"],
|
||||
["name", "username", "full_name", "user_image", "first_name"],
|
||||
as_dict=True,
|
||||
)
|
||||
)
|
||||
@@ -1147,3 +1147,86 @@ def change_currency(amount, currency, country=None):
|
||||
amount = cint(amount)
|
||||
amount, currency = check_multicurrency(amount, currency, country)
|
||||
return fmt_money(amount, 0, currency)
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_courses():
|
||||
"""Returns the list of courses."""
|
||||
courses = frappe.get_all(
|
||||
"LMS Course",
|
||||
fields=[
|
||||
"name",
|
||||
"title",
|
||||
"short_introduction",
|
||||
"image",
|
||||
"published",
|
||||
"upcoming",
|
||||
"status",
|
||||
"paid_course",
|
||||
"course_price",
|
||||
"currency",
|
||||
],
|
||||
filters={"published": True},
|
||||
)
|
||||
|
||||
courses = get_course_details(courses)
|
||||
courses = get_categorized_courses(courses)
|
||||
return courses
|
||||
|
||||
|
||||
def get_course_details(courses):
|
||||
for course in courses:
|
||||
course.tags = get_tags(course.name)
|
||||
course.lesson_count = get_lesson_count(course.name)
|
||||
|
||||
course.enrollment_count = frappe.db.count(
|
||||
"LMS Enrollment", {"course": course.name, "member_type": "Student"}
|
||||
)
|
||||
|
||||
avg_rating = get_average_rating(course.name) or 0
|
||||
course.avg_rating = frappe.utils.flt(
|
||||
avg_rating, frappe.get_system_settings("float_precision") or 3
|
||||
)
|
||||
|
||||
course.instructors = get_instructors(course.name)
|
||||
if course.paid_course:
|
||||
course.price = frappe.utils.fmt_money(course.course_price, 0, course.currency)
|
||||
else:
|
||||
course.price = _("Free")
|
||||
|
||||
if frappe.session.user == "Guest":
|
||||
course.membership = None
|
||||
course.is_instructor = False
|
||||
else:
|
||||
course.membership = frappe.db.get_value(
|
||||
"LMS Enrollment",
|
||||
{"member": frappe.session.user, "course": course.name},
|
||||
["name", "course", "current_lesson", "progress"],
|
||||
as_dict=1,
|
||||
)
|
||||
course.is_instructor = is_instructor(course.name)
|
||||
return courses
|
||||
|
||||
|
||||
def get_categorized_courses(courses):
|
||||
live, upcoming, enrolled, created, under_review = [], [], [], [], []
|
||||
|
||||
for course in courses:
|
||||
if course.upcoming:
|
||||
upcoming.append(course)
|
||||
elif course.published:
|
||||
live.append(course)
|
||||
elif course.membership:
|
||||
enrolled.append(course)
|
||||
elif course.is_instructor:
|
||||
created.append(course)
|
||||
elif course.status == "Under Review":
|
||||
under_review.append(course)
|
||||
|
||||
return {
|
||||
"live": live,
|
||||
"upcoming": upcoming,
|
||||
"enrolled": enrolled,
|
||||
"created": created,
|
||||
"under_review": under_review,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
var N = Object.defineProperty,
|
||||
S = Object.defineProperties;
|
||||
var j = Object.getOwnPropertyDescriptors;
|
||||
var h = Object.getOwnPropertySymbols;
|
||||
var b = Object.prototype.hasOwnProperty,
|
||||
w = Object.prototype.propertyIsEnumerable;
|
||||
var p = (e, s, t) =>
|
||||
s in e
|
||||
? N(e, s, {
|
||||
enumerable: !0,
|
||||
configurable: !0,
|
||||
writable: !0,
|
||||
value: t,
|
||||
})
|
||||
: (e[s] = t),
|
||||
m = (e, s) => {
|
||||
for (var t in s || (s = {})) b.call(s, t) && p(e, t, s[t]);
|
||||
if (h) for (var t of h(s)) w.call(s, t) && p(e, t, s[t]);
|
||||
return e;
|
||||
},
|
||||
g = (e, s) => S(e, j(s));
|
||||
var C = (e, s) => {
|
||||
var t = {};
|
||||
for (var a in e) b.call(e, a) && s.indexOf(a) < 0 && (t[a] = e[a]);
|
||||
if (e != null && h)
|
||||
for (var a of h(e)) s.indexOf(a) < 0 && w.call(e, a) && (t[a] = e[a]);
|
||||
return t;
|
||||
};
|
||||
import {
|
||||
l as $,
|
||||
o,
|
||||
d as c,
|
||||
k as r,
|
||||
F as v,
|
||||
m as y,
|
||||
t as l,
|
||||
n as B,
|
||||
p as z,
|
||||
q as A,
|
||||
e as n,
|
||||
u,
|
||||
v as O,
|
||||
x as q,
|
||||
y as U,
|
||||
z as V,
|
||||
} from "./frappe-ui.8966d601.js";
|
||||
var _ = {
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
width: 24,
|
||||
height: 24,
|
||||
viewBox: "0 0 24 24",
|
||||
fill: "none",
|
||||
stroke: "currentColor",
|
||||
"stroke-width": 2,
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
};
|
||||
const F = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(),
|
||||
f =
|
||||
(e, s) =>
|
||||
(oe, { attrs: d, slots: x }) => {
|
||||
var k = oe,
|
||||
{
|
||||
size: t,
|
||||
strokeWidth: a = 2,
|
||||
absoluteStrokeWidth: i,
|
||||
color: M,
|
||||
} = k,
|
||||
I = C(k, [
|
||||
"size",
|
||||
"strokeWidth",
|
||||
"absoluteStrokeWidth",
|
||||
"color",
|
||||
]);
|
||||
return $(
|
||||
"svg",
|
||||
m(
|
||||
g(
|
||||
m(
|
||||
g(m({}, _), {
|
||||
width: t || _.width,
|
||||
height: t || _.height,
|
||||
stroke: M || _.stroke,
|
||||
"stroke-width": i
|
||||
? (Number(a) * 24) / Number(t)
|
||||
: a,
|
||||
}),
|
||||
d
|
||||
),
|
||||
{
|
||||
class: [
|
||||
"lucide",
|
||||
`lucide-${F(e)}`,
|
||||
(d == null ? void 0 : d.class) || "",
|
||||
],
|
||||
}
|
||||
),
|
||||
I
|
||||
),
|
||||
[...s.map((L) => $(...L)), ...(x.default ? [x.default()] : [])]
|
||||
);
|
||||
},
|
||||
H = f("BookOpenIcon", [
|
||||
[
|
||||
"path",
|
||||
{ d: "M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z", key: "vv98re" },
|
||||
],
|
||||
[
|
||||
"path",
|
||||
{ d: "M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z", key: "1cyq3y" },
|
||||
],
|
||||
]),
|
||||
D = f("StarIcon", [
|
||||
[
|
||||
"polygon",
|
||||
{
|
||||
points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2",
|
||||
key: "8f66p6",
|
||||
},
|
||||
],
|
||||
]),
|
||||
E = f("UsersIcon", [
|
||||
[
|
||||
"path",
|
||||
{ d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2", key: "1yyitq" },
|
||||
],
|
||||
["circle", { cx: "9", cy: "7", r: "4", key: "nufk8" }],
|
||||
["path", { d: "M22 21v-2a4 4 0 0 0-3-3.87", key: "kshegd" }],
|
||||
["path", { d: "M16 3.13a4 4 0 0 1 0 7.75", key: "1da9ce" }],
|
||||
]);
|
||||
const K = {
|
||||
class: "flex flex-col h-full border border-gray-200 rounded-md shadow-sm mt-5",
|
||||
},
|
||||
P = { class: "flex relative top-4 left-4" },
|
||||
R = { class: "course-card-pills rounded-md border border-gray-200" },
|
||||
Z = { key: 0, class: "flex flex-1 text-4xl font-bold" },
|
||||
G = { class: "p-4" },
|
||||
J = { class: "flex text-base items-center justify-between" },
|
||||
Q = { class: "flex items-center space-x-1 py-1" },
|
||||
T = { class: "flex items-center space-x-1 py-1" },
|
||||
X = { class: "flex items-center space-x-1 py-1" },
|
||||
Y = { class: "text-2xl font-semibold" },
|
||||
W = { class: "text-ellipsis truncate text-base" },
|
||||
ee = {
|
||||
__name: "CourseCard",
|
||||
props: { course: { type: Object, default: null } },
|
||||
setup(e) {
|
||||
return (s, t) => (
|
||||
o(),
|
||||
c("div", K, [
|
||||
r(
|
||||
"div",
|
||||
{
|
||||
class: z([
|
||||
"course-image",
|
||||
{ "default-image": !e.course.image },
|
||||
]),
|
||||
style: A({
|
||||
backgroundImage: "url(" + e.course.image + ")",
|
||||
}),
|
||||
},
|
||||
[
|
||||
r("div", P, [
|
||||
(o(!0),
|
||||
c(
|
||||
v,
|
||||
null,
|
||||
y(
|
||||
e.course.tags,
|
||||
(a) => (o(), c("div", R, l(a), 1))
|
||||
),
|
||||
256
|
||||
)),
|
||||
]),
|
||||
e.course.image
|
||||
? B("", !0)
|
||||
: (o(), c("div", Z, l(e.course.title[0]), 1)),
|
||||
],
|
||||
6
|
||||
),
|
||||
r("div", G, [
|
||||
r("div", J, [
|
||||
r("div", Q, [
|
||||
n(u(H), { class: "h-4 w-4 text-gray-700" }),
|
||||
r("span", null, l(e.course.lesson_count), 1),
|
||||
]),
|
||||
r("div", T, [
|
||||
n(u(E), { class: "h-4 w-4 text-gray-700" }),
|
||||
r(
|
||||
"span",
|
||||
null,
|
||||
l(e.course.enrollment_count),
|
||||
1
|
||||
),
|
||||
]),
|
||||
r("div", X, [
|
||||
n(u(D), { class: "h-4 w-4 text-gray-700" }),
|
||||
r("span", null, l(e.course.avg_rating), 1),
|
||||
]),
|
||||
]),
|
||||
r("div", Y, l(e.course.title), 1),
|
||||
r("div", W, l(e.course.short_introduction), 1),
|
||||
(o(!0),
|
||||
c(
|
||||
v,
|
||||
null,
|
||||
y(e.course.instructors, (a) => (o(), c("div"))),
|
||||
256
|
||||
)),
|
||||
]),
|
||||
])
|
||||
);
|
||||
},
|
||||
},
|
||||
se = {
|
||||
__name: "UserAvatar",
|
||||
props: { user: { type: Object, default: null } },
|
||||
setup(e) {
|
||||
return (s, t) =>
|
||||
e.user
|
||||
? (o(),
|
||||
O(
|
||||
u(U),
|
||||
q(
|
||||
{
|
||||
key: 0,
|
||||
class: "",
|
||||
label: e.user.full_name,
|
||||
image: e.user.user_image,
|
||||
},
|
||||
s.$attrs
|
||||
),
|
||||
null,
|
||||
16,
|
||||
["label", "image"]
|
||||
))
|
||||
: B("", !0);
|
||||
},
|
||||
},
|
||||
te = { class: "container" },
|
||||
ae = r("div", { class: "text-2xl font-semibold" }, " All Courses ", -1),
|
||||
re = { class: "grid grid-cols-3 gap-8" },
|
||||
ne = {
|
||||
__name: "Courses",
|
||||
setup(e) {
|
||||
const s = V({
|
||||
type: "list",
|
||||
doctype: "LMS Course",
|
||||
url: "lms.lms.utils.get_courses",
|
||||
auto: !0,
|
||||
});
|
||||
return (t, a) => (
|
||||
o(),
|
||||
c("div", te, [
|
||||
ae,
|
||||
r("div", re, [
|
||||
(o(!0),
|
||||
c(
|
||||
v,
|
||||
null,
|
||||
y(
|
||||
u(s).data,
|
||||
(i) => (
|
||||
o(),
|
||||
c("div", null, [
|
||||
n(ee, { course: i }, null, 8, [
|
||||
"course",
|
||||
]),
|
||||
n(
|
||||
se,
|
||||
{ user: i.instructors[0] },
|
||||
null,
|
||||
8,
|
||||
["user"]
|
||||
),
|
||||
])
|
||||
)
|
||||
),
|
||||
256
|
||||
)),
|
||||
]),
|
||||
])
|
||||
);
|
||||
},
|
||||
};
|
||||
export { ne as default };
|
||||
@@ -0,0 +1 @@
|
||||
.course-image{height:168px;width:100%;background-size:cover;background-position:center;background-repeat:no-repeat}.course-card-pills{background:#ffffff;margin-left:0;margin-right:.5rem;padding:3.5px 8px;font-size:11px;text-align:center;letter-spacing:.011em;text-transform:uppercase;font-weight:600;width:-moz-fit-content;width:fit-content}.default-image{display:flex;flex-direction:column;align-items:center;background-color:#ededed;color:#525252}
|
||||
@@ -0,0 +1,206 @@
|
||||
import {
|
||||
b as f,
|
||||
P as g,
|
||||
T as _,
|
||||
r as d,
|
||||
o,
|
||||
v as l,
|
||||
w as r,
|
||||
A as p,
|
||||
B as C,
|
||||
C as k,
|
||||
k as a,
|
||||
d as c,
|
||||
F as u,
|
||||
m,
|
||||
q as h,
|
||||
p as b,
|
||||
} from "./frappe-ui.8966d601.js";
|
||||
const v = {
|
||||
name: "FontColor",
|
||||
props: ["editor"],
|
||||
components: { Popover: g, Tooltip: _ },
|
||||
methods: {
|
||||
setBackgroundColor(t) {
|
||||
t.name != "Default"
|
||||
? this.editor
|
||||
.chain()
|
||||
.focus()
|
||||
.toggleHighlight({ color: t.hex })
|
||||
.run()
|
||||
: this.editor.chain().focus().unsetHighlight().run();
|
||||
},
|
||||
setForegroundColor(t) {
|
||||
t.name != "Default"
|
||||
? this.editor.chain().focus().setColor(t.hex).run()
|
||||
: this.editor.chain().focus().unsetColor().run();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
foregroundColors() {
|
||||
return [
|
||||
{ name: "Default", hex: "#1F272E" },
|
||||
{ name: "Yellow", hex: "#ca8a04" },
|
||||
{ name: "Orange", hex: "#ea580c" },
|
||||
{ name: "Red", hex: "#dc2626" },
|
||||
{ name: "Green", hex: "#16a34a" },
|
||||
{ name: "Blue", hex: "#1579D0" },
|
||||
{ name: "Purple", hex: "#9333ea" },
|
||||
{ name: "Pink", hex: "#db2777" },
|
||||
];
|
||||
},
|
||||
backgroundColors() {
|
||||
return [
|
||||
{ name: "Default", hex: null },
|
||||
{ name: "Yellow", hex: "#fef9c3" },
|
||||
{ name: "Orange", hex: "#ffedd5" },
|
||||
{ name: "Red", hex: "#fee2e2" },
|
||||
{ name: "Green", hex: "#dcfce7" },
|
||||
{ name: "Blue", hex: "#D3E9FC" },
|
||||
{ name: "Purple", hex: "#f3e8ff" },
|
||||
{ name: "Pink", hex: "#fce7f3" },
|
||||
];
|
||||
},
|
||||
},
|
||||
},
|
||||
y = { class: "p-2" },
|
||||
B = a("div", { class: "text-sm text-gray-700" }, "Text Color", -1),
|
||||
P = { class: "mt-1 grid grid-cols-8 gap-1" },
|
||||
F = ["aria-label", "onClick"],
|
||||
w = a(
|
||||
"div",
|
||||
{ class: "mt-2 text-sm text-gray-700" },
|
||||
"Background Color",
|
||||
-1
|
||||
),
|
||||
D = { class: "mt-1 grid grid-cols-8 gap-1" },
|
||||
T = ["aria-label", "onClick"];
|
||||
function A(t, z, E, R, $, n) {
|
||||
const i = d("Tooltip"),
|
||||
x = d("Popover");
|
||||
return (
|
||||
o(),
|
||||
l(
|
||||
x,
|
||||
{ transition: "default" },
|
||||
{
|
||||
target: r(({ togglePopover: e, isOpen: s }) => [
|
||||
p(
|
||||
t.$slots,
|
||||
"default",
|
||||
C(k({ onClick: () => e(), isActive: s }))
|
||||
),
|
||||
]),
|
||||
"body-main": r(() => [
|
||||
a("div", y, [
|
||||
B,
|
||||
a("div", P, [
|
||||
(o(!0),
|
||||
c(
|
||||
u,
|
||||
null,
|
||||
m(
|
||||
n.foregroundColors,
|
||||
(e) => (
|
||||
o(),
|
||||
l(
|
||||
i,
|
||||
{
|
||||
class: "flex",
|
||||
key: e.name,
|
||||
text: e.name,
|
||||
},
|
||||
{
|
||||
default: r(() => [
|
||||
a(
|
||||
"button",
|
||||
{
|
||||
"aria-label":
|
||||
e.name,
|
||||
class: "flex h-5 w-5 items-center justify-center rounded border text-base",
|
||||
style: h({
|
||||
color: e.hex,
|
||||
}),
|
||||
onClick: (s) =>
|
||||
n.setForegroundColor(
|
||||
e
|
||||
),
|
||||
},
|
||||
" A ",
|
||||
12,
|
||||
F
|
||||
),
|
||||
]),
|
||||
_: 2,
|
||||
},
|
||||
1032,
|
||||
["text"]
|
||||
)
|
||||
)
|
||||
),
|
||||
128
|
||||
)),
|
||||
]),
|
||||
w,
|
||||
a("div", D, [
|
||||
(o(!0),
|
||||
c(
|
||||
u,
|
||||
null,
|
||||
m(
|
||||
n.backgroundColors,
|
||||
(e) => (
|
||||
o(),
|
||||
l(
|
||||
i,
|
||||
{
|
||||
class: "flex",
|
||||
key: e.name,
|
||||
text: e.name,
|
||||
},
|
||||
{
|
||||
default: r(() => [
|
||||
a(
|
||||
"button",
|
||||
{
|
||||
"aria-label":
|
||||
e.name,
|
||||
class: b([
|
||||
"flex h-5 w-5 items-center justify-center rounded border text-base text-gray-900",
|
||||
e.hex
|
||||
? "border-transparent"
|
||||
: "border-gray-200",
|
||||
]),
|
||||
style: h({
|
||||
backgroundColor:
|
||||
e.hex,
|
||||
}),
|
||||
onClick: (s) =>
|
||||
n.setBackgroundColor(
|
||||
e
|
||||
),
|
||||
},
|
||||
" A ",
|
||||
14,
|
||||
T
|
||||
),
|
||||
]),
|
||||
_: 2,
|
||||
},
|
||||
1032,
|
||||
["text"]
|
||||
)
|
||||
)
|
||||
),
|
||||
128
|
||||
)),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
_: 3,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
const G = f(v, [["render", A]]);
|
||||
export { G as default };
|
||||
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
b as d,
|
||||
D as g,
|
||||
r,
|
||||
o as m,
|
||||
d as f,
|
||||
e as t,
|
||||
w as s,
|
||||
j as l,
|
||||
k as p,
|
||||
t as u,
|
||||
} from "./frappe-ui.8966d601.js";
|
||||
const D = {
|
||||
name: "Home",
|
||||
data() {
|
||||
return { showDialog: !1 };
|
||||
},
|
||||
resources: { ping: { url: "ping" } },
|
||||
components: { Dialog: g },
|
||||
},
|
||||
_ = { class: "max-w-3xl py-12 mx-auto" };
|
||||
function k(e, o, w, C, n, V) {
|
||||
const a = r("Button"),
|
||||
c = r("Dialog");
|
||||
return (
|
||||
m(),
|
||||
f("div", _, [
|
||||
t(
|
||||
a,
|
||||
{
|
||||
"icon-left": "code",
|
||||
onClick: e.$resources.ping.fetch,
|
||||
loading: e.$resources.ping.loading,
|
||||
},
|
||||
{
|
||||
default: s(() => [l(" Click to send 'ping' request ")]),
|
||||
_: 1,
|
||||
},
|
||||
8,
|
||||
["onClick", "loading"]
|
||||
),
|
||||
p("div", null, u(e.$resources.ping.data), 1),
|
||||
p("pre", null, u(e.$resources.ping), 1),
|
||||
t(
|
||||
a,
|
||||
{ onClick: o[0] || (o[0] = (i) => (n.showDialog = !0)) },
|
||||
{ default: s(() => [l("Open Dialog")]), _: 1 }
|
||||
),
|
||||
t(
|
||||
c,
|
||||
{
|
||||
title: "Title",
|
||||
modelValue: n.showDialog,
|
||||
"onUpdate:modelValue":
|
||||
o[1] || (o[1] = (i) => (n.showDialog = i)),
|
||||
},
|
||||
{ default: s(() => [l(" Dialog content ")]), _: 1 },
|
||||
8,
|
||||
["modelValue"]
|
||||
),
|
||||
])
|
||||
);
|
||||
}
|
||||
const B = d(D, [["render", k]]);
|
||||
export { B as default };
|
||||
@@ -0,0 +1,151 @@
|
||||
import {
|
||||
b as f,
|
||||
h as I,
|
||||
D,
|
||||
G as h,
|
||||
r as d,
|
||||
o as m,
|
||||
d as c,
|
||||
A as _,
|
||||
B as y,
|
||||
C,
|
||||
e as n,
|
||||
w as s,
|
||||
k as r,
|
||||
t as w,
|
||||
n as b,
|
||||
j as u,
|
||||
F as k,
|
||||
} from "./frappe-ui.8966d601.js";
|
||||
const v = {
|
||||
name: "InsertImage",
|
||||
props: ["editor"],
|
||||
expose: ["openDialog"],
|
||||
data() {
|
||||
return { addImageDialog: { url: "", file: null, show: !1 } };
|
||||
},
|
||||
components: { Button: I, Dialog: D },
|
||||
methods: {
|
||||
openDialog() {
|
||||
this.addImageDialog.show = !0;
|
||||
},
|
||||
onImageSelect(t) {
|
||||
let e = t.target.files[0];
|
||||
!e ||
|
||||
((this.addImageDialog.file = e),
|
||||
h(e).then((i) => {
|
||||
this.addImageDialog.url = i;
|
||||
}));
|
||||
},
|
||||
addImage(t) {
|
||||
this.editor.chain().focus().setImage({ src: t }).run(),
|
||||
this.reset();
|
||||
},
|
||||
reset() {
|
||||
this.addImageDialog = this.$options.data().addImageDialog;
|
||||
},
|
||||
},
|
||||
},
|
||||
B = {
|
||||
class: "relative cursor-pointer rounded-lg bg-gray-100 py-1 focus-within:bg-gray-200 hover:bg-gray-200",
|
||||
},
|
||||
x = { class: "absolute inset-0 select-none px-2 py-1 text-base" },
|
||||
S = ["src"];
|
||||
function V(t, e, i, A, a, o) {
|
||||
const g = d("Button"),
|
||||
p = d("Dialog");
|
||||
return (
|
||||
m(),
|
||||
c(
|
||||
k,
|
||||
null,
|
||||
[
|
||||
_(t.$slots, "default", y(C({ onClick: o.openDialog }))),
|
||||
n(
|
||||
p,
|
||||
{
|
||||
options: { title: "Add Image" },
|
||||
modelValue: a.addImageDialog.show,
|
||||
"onUpdate:modelValue":
|
||||
e[2] || (e[2] = (l) => (a.addImageDialog.show = l)),
|
||||
onAfterLeave: o.reset,
|
||||
},
|
||||
{
|
||||
"body-content": s(() => [
|
||||
r("label", B, [
|
||||
r(
|
||||
"input",
|
||||
{
|
||||
type: "file",
|
||||
class: "w-full opacity-0",
|
||||
onChange:
|
||||
e[0] ||
|
||||
(e[0] = (...l) =>
|
||||
o.onImageSelect &&
|
||||
o.onImageSelect(...l)),
|
||||
accept: "image/*",
|
||||
},
|
||||
null,
|
||||
32
|
||||
),
|
||||
r(
|
||||
"span",
|
||||
x,
|
||||
w(
|
||||
a.addImageDialog.file
|
||||
? "Select another image"
|
||||
: "Select an image"
|
||||
),
|
||||
1
|
||||
),
|
||||
]),
|
||||
a.addImageDialog.url
|
||||
? (m(),
|
||||
c(
|
||||
"img",
|
||||
{
|
||||
key: 0,
|
||||
src: a.addImageDialog.url,
|
||||
class: "mt-2 w-full rounded-lg",
|
||||
},
|
||||
null,
|
||||
8,
|
||||
S
|
||||
))
|
||||
: b("", !0),
|
||||
]),
|
||||
actions: s(() => [
|
||||
n(
|
||||
g,
|
||||
{
|
||||
variant: "solid",
|
||||
onClick:
|
||||
e[1] ||
|
||||
(e[1] = (l) =>
|
||||
o.addImage(a.addImageDialog.url)),
|
||||
},
|
||||
{
|
||||
default: s(() => [u(" Insert Image ")]),
|
||||
_: 1,
|
||||
}
|
||||
),
|
||||
n(
|
||||
g,
|
||||
{ onClick: o.reset },
|
||||
{ default: s(() => [u(" Cancel ")]), _: 1 },
|
||||
8,
|
||||
["onClick"]
|
||||
),
|
||||
]),
|
||||
_: 1,
|
||||
},
|
||||
8,
|
||||
["modelValue", "onAfterLeave"]
|
||||
),
|
||||
],
|
||||
64
|
||||
)
|
||||
);
|
||||
}
|
||||
const F = f(v, [["render", V]]);
|
||||
export { F as default };
|
||||
@@ -0,0 +1,120 @@
|
||||
import {
|
||||
b as d,
|
||||
h as g,
|
||||
I as L,
|
||||
D as m,
|
||||
r as i,
|
||||
o as p,
|
||||
d as f,
|
||||
A as D,
|
||||
B as h,
|
||||
C as c,
|
||||
e as l,
|
||||
w as a,
|
||||
E as w,
|
||||
j as _,
|
||||
F as v,
|
||||
} from "./frappe-ui.8966d601.js";
|
||||
const x = {
|
||||
name: "InsertLink",
|
||||
props: ["editor"],
|
||||
components: { Button: g, Input: L, Dialog: m },
|
||||
data() {
|
||||
return { setLinkDialog: { url: "", show: !1 } };
|
||||
},
|
||||
methods: {
|
||||
openDialog() {
|
||||
let t = this.editor.getAttributes("link").href;
|
||||
t && (this.setLinkDialog.url = t), (this.setLinkDialog.show = !0);
|
||||
},
|
||||
setLink(t) {
|
||||
t === ""
|
||||
? this.editor
|
||||
.chain()
|
||||
.focus()
|
||||
.extendMarkRange("link")
|
||||
.unsetLink()
|
||||
.run()
|
||||
: this.editor
|
||||
.chain()
|
||||
.focus()
|
||||
.extendMarkRange("link")
|
||||
.setLink({ href: t })
|
||||
.run(),
|
||||
(this.setLinkDialog.show = !1),
|
||||
(this.setLinkDialog.url = "");
|
||||
},
|
||||
reset() {
|
||||
this.setLinkDialog = this.$options.data().setLinkDialog;
|
||||
},
|
||||
},
|
||||
};
|
||||
function V(t, e, C, B, n, s) {
|
||||
const r = i("FormControl"),
|
||||
u = i("Button"),
|
||||
k = i("Dialog");
|
||||
return (
|
||||
p(),
|
||||
f(
|
||||
v,
|
||||
null,
|
||||
[
|
||||
D(t.$slots, "default", h(c({ onClick: s.openDialog }))),
|
||||
l(
|
||||
k,
|
||||
{
|
||||
options: { title: "Set Link" },
|
||||
modelValue: n.setLinkDialog.show,
|
||||
"onUpdate:modelValue":
|
||||
e[3] || (e[3] = (o) => (n.setLinkDialog.show = o)),
|
||||
onAfterLeave: s.reset,
|
||||
},
|
||||
{
|
||||
"body-content": a(() => [
|
||||
l(
|
||||
r,
|
||||
{
|
||||
type: "text",
|
||||
label: "URL",
|
||||
modelValue: n.setLinkDialog.url,
|
||||
"onUpdate:modelValue":
|
||||
e[0] ||
|
||||
(e[0] = (o) =>
|
||||
(n.setLinkDialog.url = o)),
|
||||
onKeydown:
|
||||
e[1] ||
|
||||
(e[1] = w(
|
||||
(o) => s.setLink(o.target.value),
|
||||
["enter"]
|
||||
)),
|
||||
},
|
||||
null,
|
||||
8,
|
||||
["modelValue"]
|
||||
),
|
||||
]),
|
||||
actions: a(() => [
|
||||
l(
|
||||
u,
|
||||
{
|
||||
variant: "solid",
|
||||
onClick:
|
||||
e[2] ||
|
||||
(e[2] = (o) =>
|
||||
s.setLink(n.setLinkDialog.url)),
|
||||
},
|
||||
{ default: a(() => [_(" Save ")]), _: 1 }
|
||||
),
|
||||
]),
|
||||
_: 1,
|
||||
},
|
||||
8,
|
||||
["modelValue", "onAfterLeave"]
|
||||
),
|
||||
],
|
||||
64
|
||||
)
|
||||
);
|
||||
}
|
||||
const b = d(x, [["render", V]]);
|
||||
export { b as default };
|
||||
@@ -0,0 +1,200 @@
|
||||
import {
|
||||
b as _,
|
||||
h as C,
|
||||
D as k,
|
||||
H as v,
|
||||
r,
|
||||
o as u,
|
||||
d as c,
|
||||
A as h,
|
||||
B,
|
||||
C as w,
|
||||
e as t,
|
||||
w as l,
|
||||
k as x,
|
||||
j as n,
|
||||
t as y,
|
||||
v as U,
|
||||
n as p,
|
||||
F,
|
||||
} from "./frappe-ui.8966d601.js";
|
||||
const A = {
|
||||
name: "InsertImage",
|
||||
props: ["editor"],
|
||||
expose: ["openDialog"],
|
||||
data() {
|
||||
return { addVideoDialog: { url: "", file: null, show: !1 } };
|
||||
},
|
||||
components: { Button: C, Dialog: k, FileUploader: v },
|
||||
methods: {
|
||||
openDialog() {
|
||||
this.addVideoDialog.show = !0;
|
||||
},
|
||||
onVideoSelect(i) {
|
||||
let o = i.target.files[0];
|
||||
!o || (this.addVideoDialog.file = o);
|
||||
},
|
||||
addVideo(i) {
|
||||
this.editor
|
||||
.chain()
|
||||
.focus()
|
||||
.insertContent(`<video src="${i}"></video>`)
|
||||
.run(),
|
||||
this.reset();
|
||||
},
|
||||
reset() {
|
||||
this.addVideoDialog = this.$options.data().addVideoDialog;
|
||||
},
|
||||
},
|
||||
},
|
||||
I = { class: "flex items-center space-x-2" },
|
||||
N = ["src"];
|
||||
function S(i, o, b, L, e, a) {
|
||||
const s = r("Button"),
|
||||
V = r("FileUploader"),
|
||||
g = r("Dialog");
|
||||
return (
|
||||
u(),
|
||||
c(
|
||||
F,
|
||||
null,
|
||||
[
|
||||
h(i.$slots, "default", B(w({ onClick: a.openDialog }))),
|
||||
t(
|
||||
g,
|
||||
{
|
||||
options: { title: "Add Video" },
|
||||
modelValue: e.addVideoDialog.show,
|
||||
"onUpdate:modelValue":
|
||||
o[2] || (o[2] = (d) => (e.addVideoDialog.show = d)),
|
||||
onAfterLeave: a.reset,
|
||||
},
|
||||
{
|
||||
"body-content": l(() => [
|
||||
t(
|
||||
V,
|
||||
{
|
||||
"file-types": "video/*",
|
||||
onSuccess:
|
||||
o[0] ||
|
||||
(o[0] = (d) =>
|
||||
(e.addVideoDialog.url =
|
||||
d.file_url)),
|
||||
},
|
||||
{
|
||||
default: l(
|
||||
({
|
||||
file: d,
|
||||
progress: f,
|
||||
uploading: m,
|
||||
openFileSelector: D,
|
||||
}) => [
|
||||
x("div", I, [
|
||||
t(
|
||||
s,
|
||||
{ onClick: D },
|
||||
{
|
||||
default: l(() => [
|
||||
n(
|
||||
y(
|
||||
m
|
||||
? `Uploading ${f}%`
|
||||
: e
|
||||
.addVideoDialog
|
||||
.url
|
||||
? "Change Video"
|
||||
: "Upload Video"
|
||||
),
|
||||
1
|
||||
),
|
||||
]),
|
||||
_: 2,
|
||||
},
|
||||
1032,
|
||||
["onClick"]
|
||||
),
|
||||
e.addVideoDialog.url
|
||||
? (u(),
|
||||
U(
|
||||
s,
|
||||
{
|
||||
key: 0,
|
||||
onClick: () => {
|
||||
(e.addVideoDialog.url =
|
||||
null),
|
||||
(e.addVideoDialog.file =
|
||||
null);
|
||||
},
|
||||
},
|
||||
{
|
||||
default: l(
|
||||
() => [
|
||||
n(
|
||||
" Remove "
|
||||
),
|
||||
]
|
||||
),
|
||||
_: 2,
|
||||
},
|
||||
1032,
|
||||
["onClick"]
|
||||
))
|
||||
: p("", !0),
|
||||
]),
|
||||
]
|
||||
),
|
||||
_: 1,
|
||||
}
|
||||
),
|
||||
e.addVideoDialog.url
|
||||
? (u(),
|
||||
c(
|
||||
"video",
|
||||
{
|
||||
key: 0,
|
||||
src: e.addVideoDialog.url,
|
||||
class: "mt-2 w-full rounded-lg",
|
||||
type: "video/mp4",
|
||||
controls: "",
|
||||
},
|
||||
null,
|
||||
8,
|
||||
N
|
||||
))
|
||||
: p("", !0),
|
||||
]),
|
||||
actions: l(() => [
|
||||
t(
|
||||
s,
|
||||
{
|
||||
variant: "solid",
|
||||
onClick:
|
||||
o[1] ||
|
||||
(o[1] = (d) =>
|
||||
a.addVideo(e.addVideoDialog.url)),
|
||||
},
|
||||
{
|
||||
default: l(() => [n(" Insert Video ")]),
|
||||
_: 1,
|
||||
}
|
||||
),
|
||||
t(
|
||||
s,
|
||||
{ onClick: a.reset },
|
||||
{ default: l(() => [n("Cancel")]), _: 1 },
|
||||
8,
|
||||
["onClick"]
|
||||
),
|
||||
]),
|
||||
_: 1,
|
||||
},
|
||||
8,
|
||||
["modelValue", "onAfterLeave"]
|
||||
),
|
||||
],
|
||||
64
|
||||
)
|
||||
);
|
||||
}
|
||||
const R = _(A, [["render", S]]);
|
||||
export { R as default };
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
.form-select{background-image:url("data:image/svg+xml;utf8,<svg fill='none' width='8' xmlns='http://www.w3.org/2000/svg' viewBox='-4 -2 16 16'><path d='M4.5 3.636 6.136 2l1.637 1.636M4.5 8.364 6.136 10l1.637-1.636' stroke='%23333C44' stroke-linecap='round' stroke-linejoin='round'/></svg>")}.spinner[data-v-10b50c78]{animation:rotate-10b50c78 2s linear infinite}.spinner-path[data-v-10b50c78]{stroke-linecap:round;animation:dash-10b50c78 1.5s ease-in-out infinite}@keyframes rotate-10b50c78{to{transform:rotate(360deg)}}@keyframes dash-10b50c78{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}to{stroke-dasharray:120,150;stroke-dashoffset:-124}}.item{display:block;margin:0;width:100%;text-align:left;background:transparent;border-radius:.4rem;border:1px solid transparent;padding:.2rem .4rem}.item.is-selected{border-color:#000}.ProseMirror{outline:none;caret-color:#171717;word-break:break-word}.ProseMirror-focused:focus-visible{outline:none}.ProseMirror:not(.ProseMirror-focused) p.is-editor-empty:first-child:before{content:attr(data-placeholder);float:left;color:#999;pointer-events:none;height:0}.ProseMirror-selectednode video,img.ProseMirror-selectednode{outline:2px solid #E2E2E2}.mention{font-weight:600;-webkit-box-decoration-break:clone;box-decoration-break:clone}.prose table p{margin:0}.ProseMirror table .selectedCell:after{z-index:2;position:absolute;content:"";inset:0;pointer-events:none;background:#E3F1FD;opacity:.3}.ProseMirror table .column-resize-handle{position:absolute;right:-1px;top:0;bottom:-2px;width:4px;background-color:#e3f1fd;pointer-events:none}.resize-cursor{cursor:ew-resize;cursor:col-resize}.ProseMirror mark{border-radius:3px;padding:0 2px}
|
||||
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
c as u,
|
||||
a as l,
|
||||
_ as i,
|
||||
b as f,
|
||||
r as p,
|
||||
o as d,
|
||||
d as m,
|
||||
e as _,
|
||||
f as h,
|
||||
g as y,
|
||||
h as g,
|
||||
s as v,
|
||||
i as L,
|
||||
} from "./frappe-ui.8966d601.js";
|
||||
(function () {
|
||||
const o = document.createElement("link").relList;
|
||||
if (o && o.supports && o.supports("modulepreload")) return;
|
||||
for (const e of document.querySelectorAll('link[rel="modulepreload"]'))
|
||||
c(e);
|
||||
new MutationObserver((e) => {
|
||||
for (const t of e)
|
||||
if (t.type === "childList")
|
||||
for (const s of t.addedNodes)
|
||||
s.tagName === "LINK" && s.rel === "modulepreload" && c(s);
|
||||
}).observe(document, { childList: !0, subtree: !0 });
|
||||
function n(e) {
|
||||
const t = {};
|
||||
return (
|
||||
e.integrity && (t.integrity = e.integrity),
|
||||
e.referrerpolicy && (t.referrerPolicy = e.referrerpolicy),
|
||||
e.crossorigin === "use-credentials"
|
||||
? (t.credentials = "include")
|
||||
: e.crossorigin === "anonymous"
|
||||
? (t.credentials = "omit")
|
||||
: (t.credentials = "same-origin"),
|
||||
t
|
||||
);
|
||||
}
|
||||
function c(e) {
|
||||
if (e.ep) return;
|
||||
e.ep = !0;
|
||||
const t = n(e);
|
||||
fetch(e.href, t);
|
||||
}
|
||||
})();
|
||||
const E = [
|
||||
{
|
||||
path: "/",
|
||||
name: "Home",
|
||||
component: () =>
|
||||
i(
|
||||
() => import("./Home.24891fef.js"),
|
||||
[
|
||||
"assets/Home.24891fef.js",
|
||||
"assets/frappe-ui.8966d601.js",
|
||||
"assets/frappe-ui.e894a05e.css",
|
||||
]
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/courses",
|
||||
name: "Courses",
|
||||
component: () =>
|
||||
i(
|
||||
() => import("./Courses.10129947.js"),
|
||||
[
|
||||
"assets/Courses.10129947.js",
|
||||
"assets/frappe-ui.8966d601.js",
|
||||
"assets/frappe-ui.e894a05e.css",
|
||||
"assets/Courses.4fd15046.css",
|
||||
]
|
||||
),
|
||||
},
|
||||
];
|
||||
let O = u({ history: l("/"), routes: E });
|
||||
const P = {};
|
||||
function b(a, o) {
|
||||
const n = p("router-view");
|
||||
return d(), m("div", null, [_(n)]);
|
||||
}
|
||||
const A = f(P, [["render", b]]);
|
||||
let r = h(A);
|
||||
v("resourceFetcher", L);
|
||||
r.use(O);
|
||||
r.use(y);
|
||||
r.component("Button", g);
|
||||
r.mount("#app");
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 440 B |
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Frappe UI App</title>
|
||||
<script type="module" crossorigin src="/assets/index.52b6e5cb.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/frappe-ui.8966d601.js">
|
||||
<link rel="stylesheet" href="/assets/frappe-ui.e894a05e.css">
|
||||
<link rel="stylesheet" href="/assets/index.943a3ec2.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="modals"></div>
|
||||
<div id="popovers"></div>
|
||||
|
||||
<script> window.csrf_token = '{{ csrf_token }}'; </script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user